blob: 0d34504e748f7d81f1f81aa806d4a10117f136da [file] [log] [blame]
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001#!/usr/bin/env perl
2
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003# $Id: imapsync,v 2.178 2022/01/12 21:28:37 gilles Exp gilles $
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004# structure
5# pod documentation
6# use pragmas
7# main program
8# global variables initialization
9# get_options( ) ;
10# default values
11# folder loop
12# subroutines
13# sub usage
14
15
16# pod documentation
17
18=pod
19
20=head1 NAME
21
22imapsync - Email IMAP tool for syncing, copying, migrating
23and archiving email mailboxes between two imap servers, one way,
24and without duplicates.
25
26=head1 VERSION
27
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010028This documentation refers to Imapsync $Revision: 2.178 $
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010029
30=head1 USAGE
31
32 To synchronize the source imap account
33 "test1" on server "test1.lamiral.info" with password "secret1"
34 to the destination imap account
35 "test2" on server "test2.lamiral.info" with password "secret2"
36 do:
37
38 imapsync \
39 --host1 test1.lamiral.info --user1 test1 --password1 secret1 \
40 --host2 test2.lamiral.info --user2 test2 --password2 secret2
41
42=head1 DESCRIPTION
43
44We sometimes need to transfer mailboxes from one imap server to
45one another.
46
47Imapsync command is a tool allowing incremental and
48recursive imap transfers from one mailbox to another.
49If you don't understand the previous sentence, it's normal,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020050it's pedantic computer-oriented jargon.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010051
52All folders are transferred, recursively, meaning
53the whole folder hierarchy is taken, all messages in them,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020054and all message flags (\Seen \Answered \Flagged etc.)
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010055are synced too.
56
57Imapsync reduces the amount of data transferred by not transferring
58a given message if it already resides on the destination side.
59Messages that are on the destination side but not on the
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020060source side stay as they are. See the --delete2
61option to have strict sync and delete them.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010062
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020063How imapsync know a message is already on both sides?
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010064Same specific headers and the transfer is done only once.
65By default, the identification headers are
66"Message-Id:" and "Received:" lines
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020067but this choice can be changed with the --useheader option,
68most often a duplicate problem is solved by using
69--useheader "Message-Id"
70
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010071
72All flags are preserved, unread messages will stay unread,
73read ones will stay read, deleted will stay deleted.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020074In the IMAP protocol, a deleted message is not really deleted,
75it is marked \Deleted and can be undelete. Real destruction
76comes with the EXPUNGE or UIDEXPUNGE IMAP commands.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010077
78You can abort the transfer at any time and restart it later,
79imapsync works well with bad connections and interruptions,
80by design. On a terminal hit Ctr-c twice within two seconds
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020081to abort the program. Hit Ctr-c just once makes
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010082imapsync reconnect to both imap servers.
83
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020084How do you know the sync is finished and well done?
85When imapsync ends by itself it mentions it with lines like those:
86
87 Exiting with return value 0 (EX_OK: successful termination) 0/50 nb_errors/max_errors PID 301
88 Removing pidfile /tmp/imapsync.pid
89 Log file is LOG_imapsync/2020_11_17_15_59_22_761_test1_test2.txt ( to change it, use --logfile filepath ; or use --nolog to turn off logging )
90
91If you don't have those lines it means that either the sync process is still
92running (or eventually hanging indefinitely) or that it ended without
93a whisper, a strong kill -9 on Linux for example.
94
95If you have those final lines then it means the sync process is properly
96finished. It may have encountered problems though.
97
98A good synchronization is mentioned by some lines above the last ones, especially
99those three lines:
100
101 The sync looks good, all 1745 identified messages in host1 are on host2.
102 There is no unidentified message on host1.
103 Detected 0 errors
104
105
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100106A classical scenario is synchronizing a mailbox B from another mailbox A
107where you just want to keep a strict copy of A in B. Strict meaning
108all messages in A will be in B but no more.
109
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200110For this, option --delete2 can be used, it deletes messages in the host2
111folder B that are not in the host1 folder A. If you also need to destroy
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100112host2 folders that are not in host1 then use --delete2folders. See also
113--delete2foldersonly and --delete2foldersbutnot to set up exceptions
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200114on folders to destroy. INBOX will never be destroyed, it's a mandatory
115folder in IMAP so imapsync doesn't even try to remove it.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100116
117A different scenario is to delete the messages from the source mailbox
118after a successful transfer, it can be a good feature when migrating
119mailboxes since messages will be only on one side. The source account
120will only have messages that are not on the destination yet, ie,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200121messages that arrived after a sync or that failed to be transferred.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100122
123In that case, use the --delete1 option. Option --delete1 implies also
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200124the option --expunge1 so all messages marked deleted on host1 will be
125deleted. In IMAP protocol deleting a message does not delete it,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100126it marks it with the flag \Deleted, allowing an undelete. Expunging
127a folder removes, definitively, all the messages marked as \Deleted
128in this folder.
129
130You can also decide to remove empty folders once all of their messages
131have been transferred. Add --delete1emptyfolders to obtain this
132behavior.
133
134
135Imapsync is not adequate for maintaining two active imap accounts
136in synchronization when the user plays independently on both sides.
137Use offlineimap (written by John Goerzen) or mbsync (written by
138Michael R. Elkins) for a 2 ways synchronization.
139
140
141=head1 OPTIONS
142
143 usage: imapsync [options]
144
145The standard options are the six values forming the credentials.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200146Three values on each side are needed in order to login into the IMAP
147servers. These six values are a hostname, a username, and a password, two times.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100148
149Conventions used in the following descriptions of the options:
150
151 str means string
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200152 int means integer number
153 flo means float number
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100154 reg means regular expression
155 cmd means command
156
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200157 --dry : Makes imapsync doing nothing for real; it just print what
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100158 would be done without --dry.
159
160=head2 OPTIONS/credentials
161
162
163 --host1 str : Source or "from" imap server.
164 --port1 int : Port to connect on host1.
165 Optional since default ports are the
166 well known ports imap/143 or imaps/993.
167 --user1 str : User to login on host1.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200168 --password1 str : Password of user1.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100169
170 --host2 str : "destination" imap server.
171 --port2 int : Port to connect on host2. Optional
172 --user2 str : User to login on host2.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200173 --password2 str : Password of user2.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100174
175 --showpasswords : Shows passwords on output instead of "MASKED".
176 Useful to restart a complete run by just reading
177 the command line used in the log,
178 or to debug passwords.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200179 It's not a secure practice at all!
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100180
181 --passfile1 str : Password file for the user1. It must contain the
182 password on the first line. This option avoids showing
183 the password on the command line like --password1 does.
184 --passfile2 str : Password file for the user2.
185
186You can also pass the passwords in the environment variables
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200187IMAPSYNC_PASSWORD1 and IMAPSYNC_PASSWORD2. If you don't pass
188the user1 password via --password1 nor --passfile1 nor $IMAPSYNC_PASSWORD1
189then imapsync will prompt to enter the password on the terminal.
190Same thing for user2 password.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100191
192=head2 OPTIONS/encryption
193
194 --nossl1 : Do not use a SSL connection on host1.
195 --ssl1 : Use a SSL connection on host1. On by default if possible.
196
197 --nossl2 : Do not use a SSL connection on host2.
198 --ssl2 : Use a SSL connection on host2. On by default if possible.
199
200 --notls1 : Do not use a TLS connection on host1.
201 --tls1 : Use a TLS connection on host1. On by default if possible.
202
203 --notls2 : Do not use a TLS connection on host2.
204 --tls2 : Use a TLS connection on host2. On by default if possible.
205
206 --debugssl int : SSL debug mode from 0 to 4.
207
208 --sslargs1 str : Pass any ssl parameter for host1 ssl or tls connection. Example:
209 --sslargs1 SSL_verify_mode=1 --sslargs1 SSL_version=SSLv3
210 See all possibilities in the new() method of IO::Socket::SSL
211 http://search.cpan.org/perldoc?IO::Socket::SSL#Description_Of_Methods
212 --sslargs2 str : Pass any ssl parameter for host2 ssl or tls connection.
213 See --sslargs1
214
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100215
216=head2 OPTIONS/authentication
217
218 --authmech1 str : Auth mechanism to use with host1:
219 PLAIN, LOGIN, CRAM-MD5 etc. Use UPPERCASE.
220 --authmech2 str : Auth mechanism to use with host2. See --authmech1
221
222 --authuser1 str : User to auth with on host1 (admin user).
223 Avoid using --authmech1 SOMETHING with --authuser1.
224 --authuser2 str : User to auth with on host2 (admin user).
225 --proxyauth1 : Use proxyauth on host1. Requires --authuser1.
226 Required by Sun/iPlanet/Netscape IMAP servers to
227 be able to use an administrative user.
228 --proxyauth2 : Use proxyauth on host2. Requires --authuser2.
229
230 --authmd51 : Use MD5 authentication for host1.
231 --authmd52 : Use MD5 authentication for host2.
232 --domain1 str : Domain on host1 (NTLM authentication).
233 --domain2 str : Domain on host2 (NTLM authentication).
234
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200235 --oauthaccesstoken1 str : The access token to authenticate with OAUTH2.
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100236 It will be combined with the --user1 value to form the
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200237 string to pass with XOAUTH2 authentication.
238 The password given by --password1 or --passfile1
239 is ignored.
240 Instead of the access token itself, the value can be a
241 file containing the access token on the first line.
242 If the value is a file, imapsync reads its first line
243 and take this line as the access token. The advantage
244 of the file is that if the access token changes then
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100245 imapsync can read it again when it needs to reconnect
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200246 during a run.
247
248
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100249 --oauthaccesstoken2 str : same thing as --oauthaccesstoken1
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200250
251 --oauthdirect1 str : The direct string to pass with XOAUTH2 authentication.
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100252 The password given by --password1 or --passfile1 and
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200253 the user given by --user1 are ignored.
254
255 --oauthdirect2 str : same thing as oauthdirect1
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100256
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100257
258=head2 OPTIONS/folders
259
260
261 --folder str : Sync this folder.
262 --folder str : and this one, etc.
263 --folderrec str : Sync this folder recursively.
264 --folderrec str : and this one, etc.
265
266 --folderfirst str : Sync this folder first. Ex. --folderfirst "INBOX"
267 --folderfirst str : then this one, etc.
268 --folderlast str : Sync this folder last. --folderlast "[Gmail]/All Mail"
269 --folderlast str : then this one, etc.
270
271 --nomixfolders : Do not merge folders when host1 is case-sensitive
272 while host2 is not (like Exchange). Only the first
273 similar folder is synced (example: with folders
274 "Sent", "SENT" and "sent"
275 on host1 only "Sent" will be synced to host2).
276
277 --skipemptyfolders : Empty host1 folders are not created on host2.
278
279 --include reg : Sync folders matching this regular expression
280 --include reg : or this one, etc.
281 If both --include --exclude options are used, then
282 include is done before.
283 --exclude reg : Skips folders matching this regular expression
284 Several folders to avoid:
285 --exclude 'fold1|fold2|f3' skips fold1, fold2 and f3.
286 --exclude reg : or this one, etc.
287
288 --automap : guesses folders mapping, for folders well known as
289 "Sent", "Junk", "Drafts", "All", "Archive", "Flagged".
290
291 --f1f2 str1=str2 : Force folder str1 to be synced to str2,
292 --f1f2 overrides --automap and --regextrans2.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200293 Use several --f1f2 options to map several folders.
294 Option --f1f2 is a one to one only folder mapping,
295 str1 and str2 have to be full path folder names.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100296
297 --subfolder2 str : Syncs the whole host1 folders hierarchy under the
298 host2 folder named str.
299 It does it internally by adding three
300 --regextrans2 options before all others.
301 Add --debug to see what's really going on.
302
303 --subfolder1 str : Syncs the host1 folders hierarchy which is under folder
304 str to the root hierarchy of host2.
305 It's the couterpart of a sync done by --subfolder2
306 when doing it in the reverse order.
307 Backup/Restore scenario:
308 Use --subfolder2 str for a backup to the folder str
309 on host2. Then use --subfolder1 str for restoring
310 from the folder str, after inverting
311 host1/host2 user1/user2 values.
312
313
314 --subscribed : Transfers subscribed folders.
315 --subscribe : Subscribe to the folders transferred on the
316 host2 that are subscribed on host1. On by default.
317 --subscribeall : Subscribe to the folders transferred on the
318 host2 even if they are not subscribed on host1.
319
320 --prefix1 str : Remove prefix str to all destination folders,
321 usually "INBOX." or "INBOX/" or an empty string "".
322 imapsync guesses the prefix if host1 imap server
323 does not have NAMESPACE capability. So this option
324 should not be used most of the time.
325 --prefix2 str : Add prefix to all host2 folders. See --prefix1
326
327 --sep1 str : Host1 separator. This option should not be used
328 most of the time.
329 Imapsync gets the separator from the server itself,
330 by using NAMESPACE, or it tries to guess it
331 from the folders listing (it counts
332 characters / . \\ \ in folder names and choose the
333 more frequent, or finally / if nothing is found.
334 --sep2 str : Host2 separator. See --sep1
335
336 --regextrans2 reg : Apply the whole regex to each destination folders.
337 --regextrans2 reg : and this one. etc.
338 When you play with the --regextrans2 option, first
339 add also the safe options --dry --justfolders
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200340 Then, when happy, remove --dry for a run, then
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100341 remove --justfolders for the next ones.
342 Have in mind that --regextrans2 is applied after
343 the automatic prefix and separator inversion.
344 For examples see:
345 https://imapsync.lamiral.info/FAQ.d/FAQ.Folders_Mapping.txt
346
347=head2 OPTIONS/folders sizes
348
349 --nofoldersizes : Do not calculate the size of each folder at the
350 beginning of the sync. Default is to calculate them.
351 --nofoldersizesatend: Do not calculate the size of each folder at the
352 end of the sync. Default is to calculate them.
353 --justfoldersizes : Exit after having printed the initial folder sizes.
354
355
356=head2 OPTIONS/tmp
357
358
359 --tmpdir str : Where to store temporary files and subdirectories.
360 Will be created if it doesn't exist.
361 Default is system specific, Unix is /tmp but
362 /tmp is often too small and deleted at reboot.
363 --tmpdir /var/tmp should be better.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200364
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100365 --pidfile str : The file where imapsync pid is written,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200366 it can be dirname/filename complete path.
367 The default name is imapsync.pid in tmpdir.
368
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100369 --pidfilelocking : Abort if pidfile already exists. Useful to avoid
370 concurrent transfers on the same mailbox.
371
372
373=head2 OPTIONS/log
374
375 --nolog : Turn off logging on file
376 --logfile str : Change the default log filename (can be dirname/filename).
377 --logdir str : Change the default log directory. Default is LOG_imapsync/
378
379The default logfile name is for example
380
381 LOG_imapsync/2019_12_22_23_57_59_532_user1_user2.txt
382
383where:
384
385 2019_12_22_23_57_59_532 is nearly the date of the start
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200386 YYYY_MM_DD_HH_MM_SS_mmm
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100387 year_month_day_hour_minute_seconde_millisecond
388
389and user1 user2 are the --user1 --user2 values.
390
391=head2 OPTIONS/messages
392
393 --skipmess reg : Skips messages matching the regex.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200394 Example: 'm/[\x80-\xff]/' # to avoid 8bits messages.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100395 --skipmess is applied before --regexmess
396 --skipmess reg : or this one, etc.
397
398 --skipcrossduplicates : Avoid copying messages that are already copied
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200399 in another folder, good from Gmail to XYZ when
400 XYZ is not also Gmail.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100401 Activated with --gmail1 unless --noskipcrossduplicates
402
403 --debugcrossduplicates : Prints which messages (UIDs) are skipped with
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200404 --skipcrossduplicates and in what other folders
405 they are.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100406
407 --pipemess cmd : Apply this cmd command to each message content
408 before the copy.
409 --pipemess cmd : and this one, etc.
410 With several --pipemess, the output of each cmd
411 command (STDOUT) is given to the input (STDIN)
412 of the next command.
413 For example,
414 --pipemess cmd1 --pipemess cmd2 --pipemess cmd3
415 is like a Unix pipe:
416 "cat message | cmd1 | cmd2 | cmd3"
417
418 --disarmreadreceipts : Disarms read receipts (host2 Exchange issue)
419
420 --regexmess reg : Apply the whole regex to each message before transfer.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200421 Example: 's/\000/ /g' # to replace null characters
422 by spaces.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100423 --regexmess reg : and this one, etc.
424
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100425 --truncmess int : truncates messages when their size exceed the int
426 value, specified in bytes. Good to sync too big
427 messages or to "suppress" attachments.
428 Have in mind that this way, messages become
429 uncoherent somehow.
430
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100431=head2 OPTIONS/labels
432
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200433Gmail present labels as folders in imap. Imapsync can accelerate the sync
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100434by syncing X-GM-LABELS, it will avoid to transfer messages when they are
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200435already on host2 in another folder.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100436
437
438 --synclabels : Syncs also Gmail labels when a message is copied to host2.
439 Activated by default with --gmail1 --gmail2 unless
440 --nosynclabels is added.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200441
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100442 --resynclabels : Resyncs Gmail labels when a message is already on host2.
443 Activated by default with --gmail1 --gmail2 unless
444 --noresynclabels is added.
445
446For Gmail syncs, see also:
447https://imapsync.lamiral.info/FAQ.d/FAQ.Gmail.txt
448
449=head2 OPTIONS/flags
450
451 If you encounter flag problems see also:
452 https://imapsync.lamiral.info/FAQ.d/FAQ.Flags.txt
453
454 --regexflag reg : Apply the whole regex to each flags list.
455 Example: 's/"Junk"//g' # to remove "Junk" flag.
456 --regexflag reg : then this one, etc.
457
458 --resyncflags : Resync flags for already transferred messages.
459 On by default.
460 --noresyncflags : Do not resync flags for already transferred messages.
461 May be useful when a user has already started to play
462 with its host2 account.
463
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200464 --filterbuggyflags : Filter flags known to be buggy and generators of errors
465 "BAD Invalid system flag" or "NO APPEND Invalid flag list".
466
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100467=head2 OPTIONS/deletions
468
469 --delete1 : Deletes messages on host1 server after a successful
470 transfer. Option --delete1 has the following behavior:
471 it marks messages as deleted with the IMAP flag
472 \Deleted, then messages are really deleted with an
473 EXPUNGE IMAP command. If expunging after each message
474 slows down too much the sync then use
475 --noexpungeaftereach to speed up, expunging will then be
476 done only twice per folder, one at the beginning and
477 one at the end of a folder sync.
478
479 --expunge1 : Expunge messages on host1 just before syncing a folder.
480 Expunge is done per folder.
481 Expunge aims is to really delete messages marked deleted.
482 An expunge is also done after each message copied
483 if option --delete1 is set (unless --noexpungeaftereach).
484
485 --noexpunge1 : Do not expunge messages on host1.
486
487 --delete1emptyfolders : Deletes empty folders on host1, INBOX excepted.
488 Useful with --delete1 since what remains on host1
489 is only what failed to be synced.
490
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200491 --delete2 : Delete messages in the host2 account that are not in
492 the host1 account. Useful for backup or pre-sync.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100493 --delete2 implies --uidexpunge2
494
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200495 --delete2duplicates : Deletes messages in host2 that are duplicates in host2.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100496 Works only without --useuid since duplicates are
497 detected with an header part of each message.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200498 NB: --delete2duplicates is far less violent than --delete2
499 since it removes only duplicates.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100500
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200501 --delete2folders : Delete folders in host2 that are not in host1.
502 For safety, first try it like this, it is safe:
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100503 --delete2folders --dry --justfolders --nofoldersizes
504 and see what folders will be deleted.
505
506 --delete2foldersonly reg : Delete only folders matching the regex reg.
507 Example: --delete2foldersonly "/^Junk$|^INBOX.Junk$/"
508 This option activates --delete2folders
509
510 --delete2foldersbutnot reg : Do not delete folders matching the regex rex.
511 Example: --delete2foldersbutnot "/Tasks$|Contacts$|Foo$/"
512 This option activates --delete2folders
513
514 --noexpunge2 : Do not expunge messages on host2.
515 --nouidexpunge2 : Do not uidexpunge messages on the host2 account
516 that are not on the host1 account.
517
518
519=head2 OPTIONS/dates
520
521 If you encounter problems with dates, see also:
522 https://imapsync.lamiral.info/FAQ.d/FAQ.Dates.txt
523
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200524 --syncinternaldates : Sets the internal dates on host2 as the same as host1.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100525 Turned on by default. Internal date is the date
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200526 a message arrived on a host (Unix mtime usually).
527 --idatefromheader : Sets the internal dates on host2 as same as the
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100528 ones in "Date:" headers.
529
530
531
532=head2 OPTIONS/message selection
533
534 --maxsize int : Skip messages larger (or equal) than int bytes
535 --minsize int : Skip messages smaller (or equal) than int bytes
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200536
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100537 --maxage int : Skip messages older than int days.
538 final stats (skipped) don't count older messages
539 see also --minage
540 --minage int : Skip messages newer than int days.
541 final stats (skipped) don't count newer messages
542 You can do (+ zone are the messages selected):
543 past|----maxage+++++++++++++++>now
544 past|+++++++++++++++minage---->now
545 past|----maxage+++++minage---->now (intersection)
546 past|++++minage-----maxage++++>now (union)
547
548 --search str : Selects only messages returned by this IMAP SEARCH
549 command. Applied on both sides.
550 For a complete set of what can be search see
551 https://imapsync.lamiral.info/FAQ.d/FAQ.Messages_Selection.txt
552
553 --search1 str : Same as --search but for selecting host1 messages only.
554 --search2 str : Same as --search but for selecting host2 messages only.
555 So --search CRIT equals --search1 CRIT --search2 CRIT
556
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200557 --noabletosearch : Makes --minage and --maxage options use the internal
558 dates given by a FETCH imap command instead of the
559 "Date:" header. Internal date is the arrival date
560 in the mailbox.
561 --noabletosearch equals --noabletosearch1 --noabletosearch2
562
563 --noabletosearch1 : Like --noabletosearch but for host1 only.
564 --noabletosearch2 : Like --noabletosearch but for host2 only.
565
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100566 --maxlinelength int : skip messages with a line length longer than int bytes.
567 RFC 2822 says it must be no more than 1000 bytes but
568 real life servers and email clients do more.
569
570
571 --useheader str : Use this header to compare messages on both sides.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200572 Example: "Message-Id" or "Received" or "Date".
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100573 --useheader str and this one, etc.
574
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200575 --syncduplicates : Sync also duplicates. Off by default.
576
577 --usecache : Use cache to speed up next syncs. Off by default.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100578 --nousecache : Do not use cache. Caveat: --useuid --nousecache creates
579 duplicates on multiple runs.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200580
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100581 --useuid : Use UIDs instead of headers as a criterion to recognize
582 messages. Option --usecache is then implied unless
583 --nousecache is used.
584
585
586=head2 OPTIONS/miscellaneous
587
588 --syncacls : Synchronizes acls (Access Control Lists).
589 Acls in IMAP are not standardized, be careful
590 since one acl code on one side may signify something
591 else on the other one.
592 --nosyncacls : Does not synchronize acls. This is the default.
593
594 --addheader : When a message has no headers to be identified,
595 --addheader adds a "Message-Id" header,
596 like "Message-Id: 12345@imapsync", where 12345
597 is the imap UID of the message on the host1 folder.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200598 Useful to sync folders "Sent" or "Draft".
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100599
600
601=head2 OPTIONS/debugging
602
603 --debug : Debug mode.
604 --debugfolders : Debug mode for the folders part only.
605 --debugcontent : Debug content of the messages transferred. Huge output.
606 --debugflags : Debug mode for flags.
607 --debugimap1 : IMAP debug mode for host1. Very verbose.
608 --debugimap2 : IMAP debug mode for host2. Very verbose.
609 --debugimap : IMAP debug mode for host1 and host2. Twice very verbose.
610 --debugmemory : Debug mode showing memory consumption after each copy.
611
612 --errorsmax int : Exit when int number of errors is reached. Default is 50.
613
614 --tests : Run local non-regression tests. Exit code 0 means all ok.
615 --testslive : Run a live test with test1.lamiral.info imap server.
616 Useful to check the basics. Needs internet connection.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200617 --testslive6 : Run a live test with ks6ipv6.lamiral.info imap server.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100618 Useful to check the ipv6 connectivity. Needs internet.
619
620
621=head2 OPTIONS/specific
622
623 --gmail1 : sets --host1 to Gmail and other options. See FAQ.Gmail.txt
624 --gmail2 : sets --host2 to Gmail and other options. See FAQ.Gmail.txt
625
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200626 --office1 : sets --host1 to Office365 and other options. See FAQ.Office365.txt
627 --office2 : sets --host2 to Office365 and other options. See FAQ.Office365.txt
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100628
629 --exchange1 : sets options for Exchange. See FAQ.Exchange.txt
630 --exchange2 : sets options for Exchange. See FAQ.Exchange.txt
631
632 --domino1 : sets options for Domino. See FAQ.Domino.txt
633 --domino2 : sets options for Domino. See FAQ.Domino.txt
634
635
636=head2 OPTIONS/behavior
637
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100638 --timeout1 flo : Connection timeout in seconds for host1.
639 Default is 120 and 0 means no timeout at all.
640 --timeout2 flo : Connection timeout in seconds for host2.
641 Default is 120 and 0 means no timeout at all.
642
643 Caveat, under CGI context, you may encounter a timeout
644 from the webserver, killing imapsync and the imap connexions.
645 See the document INSTALL.OnlineUI.txt and search
646 for "Timeout" for how to deal with this issue.
647
648 --keepalive1 : https://metacpan.org/pod/Mail::IMAPClient#Keepalive
649 Some firewalls and network gears like to timeout connections
650 prematurely if the connection sits idle.
651 This option enables SO_KEEPALIVE on the host1 socket.
652 --keepalive1 is on by default since imapsync release 2.169
653 Use --nokeepalive1 to disable it.
654
655 --keepalive2 : Same as --keepalive2 but for host2.
656 Use --nokeepalive2 to disable it.
657
658 --maxmessagespersecond flo : limits the average number of messages
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200659 transferred per second.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100660
661 --maxbytespersecond int : limits the average transfer rate per second.
662 --maxbytesafter int : starts --maxbytespersecond limitation only after
663 --maxbytesafter amount of data transferred.
664
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200665 --maxsleep flo : do not sleep more than int seconds.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100666 On by default, 2 seconds max, like --maxsleep 2
667
668 --abort : terminates a previous call still running.
669 It uses the pidfile to know what process to abort.
670
671 --exitwhenover int : Stop syncing and exits when int total bytes
672 transferred is reached.
673
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200674 --version : Print only the software version.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100675 --noreleasecheck : Do not check for any new imapsync release.
676 --releasecheck : Check for new imapsync release.
677 it's an http request to
678 http://imapsync.lamiral.info/prj/imapsync/VERSION
679
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200680 --noid : Do not send/receive IMAP "ID" command to imap servers.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100681
682 --justconnect : Just connect to both servers and print useful
683 information. Need only --host1 and --host2 options.
684 Obsolete since "imapsync --host1 imaphost" alone
685 implies --justconnect
686
687 --justlogin : Just login to both host1 and host2 with users
688 credentials, then exit.
689
690 --justfolders : Do only things about folders (ignore messages).
691
692 --help : print this help.
693
694 Example: to synchronize imap account "test1" on "test1.lamiral.info"
695 to imap account "test2" on "test2.lamiral.info"
696 with test1 password "secret1"
697 and test2 password "secret2"
698
699 imapsync \
700 --host1 test1.lamiral.info --user1 test1 --password1 secret1 \
701 --host2 test2.lamiral.info --user2 test2 --password2 secret2
702
703
704=cut
705# comment
706
707=pod
708
709
710
711=head1 SECURITY
712
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200713You can use --passfile1 instead of --password1 to mention the
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100714password since it is safer. With --password1 option, on Linux,
715any user on your host can see the password by using the 'ps auxwwww'
716command. Using a variable (like IMAPSYNC_PASSWORD1) is also
717dangerous because of the 'ps auxwwwwe' command. So, saving
718the password in a well protected file (600 or rw-------) is
719the best solution.
720
721Imapsync activates ssl or tls encryption by default, if possible.
722
723What detailed behavior is under this "if possible"?
724
725Imapsync activates ssl if the well known port imaps port (993) is open
726on the imap servers. If the imaps port is closed then it open a
727normal (clear) connection on port 143 but it looks for TLS support
728in the CAPABILITY list of the servers. If TLS is supported
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200729then imapsync goes to encryption with STARTTLS.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100730
731If the automatic ssl and the tls detections fail then imapsync will
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200732not protect against sniffing activities on the network, especially
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100733for passwords.
734
735If you want to force ssl or tls just use --ssl1 --ssl2 or --tls1 --tls2
736
737See also the document FAQ.Security.txt in the FAQ.d/ directory
738or at https://imapsync.lamiral.info/FAQ.d/FAQ.Security.txt
739
740=head1 EXIT STATUS
741
742Imapsync will exit with a 0 status (return code) if everything went good.
743Otherwise, it exits with a non-zero status. That's classical Unix behavior.
744Here is the list of the exit code values (an integer between 0 and 255).
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200745In Bourne Shells, this exit code value can be retrieved within the variable
746value "$?" if you read it just after the imapsync call.
747
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100748The names reflect their meaning:
749
750=for comment
751egrep '^Readonly my.*\$EX' imapsync | egrep -o 'EX.*' | sed 's_^_ _'
752
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100753 EX_OK => 0 ; #/* successful termination */
754 EX_USAGE => 64 ; #/* command line usage error */
755 EX_NOINPUT => 66 ; #/* cannot open input */
756 EX_UNAVAILABLE => 69 ; #/* service unavailable */
757 EX_SOFTWARE => 70 ; #/* internal software error */
758 EXIT_CATCH_ALL => 1 ; # Any other error
759 EXIT_BY_SIGNAL => 6 ; # Should be 128+n where n is the sig_num
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200760 EXIT_BY_FILE => 7 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100761 EXIT_PID_FILE_ERROR => 8 ;
762 EXIT_CONNECTION_FAILURE => 10 ;
763 EXIT_TLS_FAILURE => 12 ;
764 EXIT_AUTHENTICATION_FAILURE => 16 ;
765 EXIT_SUBFOLDER1_NO_EXISTS => 21 ;
766 EXIT_WITH_ERRORS => 111 ;
767 EXIT_WITH_ERRORS_MAX => 112 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200768 EXIT_OVERQUOTA => 113 ;
769 EXIT_ERR_APPEND => 114 ;
770 EXIT_ERR_FETCH => 115 ;
771 EXIT_ERR_CREATE => 116 ;
772 EXIT_ERR_SELECT => 117 ;
773 EXIT_TRANSFER_EXCEEDED => 118 ;
774 EXIT_ERR_APPEND_VIRUS => 119 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100775 EXIT_TESTS_FAILED => 254 ; # Like Test::More API
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200776 EXIT_CONNECTION_FAILURE_HOST1 => 101 ;
777 EXIT_CONNECTION_FAILURE_HOST2 => 102 ;
778 EXIT_AUTHENTICATION_FAILURE_USER1 => 161 ;
779 EXIT_AUTHENTICATION_FAILURE_USER2 => 162 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100780
781
782=head1 LICENSE AND COPYRIGHT
783
784Imapsync is free, open, public but not always gratis software
785cover by the NOLIMIT Public License, now called NLPL.
786See the LICENSE file included in the distribution or just read this
787simple sentence as it IS the licence text:
788
789 "No limits to do anything with this work and this license."
790
791In case it is not long enough, I repeat:
792
793 "No limits to do anything with this work and this license."
794
795Look at https://imapsync.lamiral.info/LICENSE
796
797=head1 AUTHOR
798
799Gilles LAMIRAL <gilles@lamiral.info>
800
801Good feedback is always welcome.
802Bad feedback is very often welcome.
803
804Gilles LAMIRAL earns his living by writing, installing,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200805configuring and sometimes teaching free, open and often gratis
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100806software. Imapsync used to be "always gratis" but now it is
807only "often gratis" because imapsync is sold by its author,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200808your servitor, a good way to maintain and support free open public
809software tools over decades.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100810
811=head1 BUGS AND LIMITATIONS
812
813See https://imapsync.lamiral.info/FAQ.d/FAQ.Reporting_Bugs.txt
814
815=head1 IMAP SERVERS supported
816
817See https://imapsync.lamiral.info/S/imapservers.shtml
818
819=head1 HUGE MIGRATION
820
821If you have many mailboxes to migrate think about a little
822shell program. Write a file called file.txt (for example)
823containing users and passwords.
824The separator used in this example is ';'
825
826The file.txt file contains:
827
828user001_1;password001_1;user001_2;password001_2
829user002_1;password002_1;user002_2;password002_2
830user003_1;password003_1;user003_2;password003_2
831user004_1;password004_1;user004_2;password004_2
832user005_1;password005_1;user005_2;password005_2
833...
834
835On Unix the shell program can be:
836
837 { while IFS=';' read u1 p1 u2 p2; do
838 imapsync --host1 imap.side1.org --user1 "$u1" --password1 "$p1" \
839 --host2 imap.side2.org --user2 "$u2" --password2 "$p2" ...
840 done ; } < file.txt
841
842On Windows the batch program can be:
843
844 FOR /F "tokens=1,2,3,4 delims=; eol=#" %%G IN (file.txt) DO imapsync ^
845 --host1 imap.side1.org --user1 %%G --password1 %%H ^
846 --host2 imap.side2.org --user2 %%I --password2 %%J ...
847
848The ... have to be replaced by nothing or any imapsync option.
849Welcome in shell or batch programming !
850
851You will find already written scripts at
852https://imapsync.lamiral.info/examples/
853
854=head1 INSTALL
855
856 Imapsync works under any Unix with Perl.
857
858 Imapsync works under most Windows (2000, XP, Vista, Seven, Eight, Ten
859 and all Server releases 2000, 2003, 2008 and R2, 2012 and R2, 2016)
860 as a standalone binary software called imapsync.exe,
861 usually launched from a batch file in order to avoid always typing
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200862 the options. There is also a 32bit binary called imapsync_32bit.exe
863
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100864 Imapsync works under OS X as a standalone binary
865 software called imapsync_bin_Darwin
866
867 Purchase latest imapsync at
868 https://imapsync.lamiral.info/
869
870 You'll receive a link to a compressed tarball called imapsync-x.xx.tgz
871 where x.xx is the version number. Untar the tarball where
872 you want (on Unix):
873
874 tar xzvf imapsync-x.xx.tgz
875
876 Go into the directory imapsync-x.xx and read the INSTALL file.
877 As mentioned at https://imapsync.lamiral.info/#install
878 the INSTALL file can also be found at
879 https://imapsync.lamiral.info/INSTALL.d/INSTALL.ANY.txt
880 It is now split in several files for each system
881 https://imapsync.lamiral.info/INSTALL.d/
882
883=head1 CONFIGURATION
884
885There is no specific configuration file for imapsync,
886everything is specified by the command line parameters
887and the default behavior.
888
889
890=head1 HACKING
891
892Feel free to hack imapsync as the NOLIMIT license permits it.
893
894
895=head1 SIMILAR SOFTWARE
896
897 See also https://imapsync.lamiral.info/S/external.shtml
898 for a better up to date list.
899
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200900List verified on Friday July 1, 2021.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100901
902 imapsync: https://github.com/imapsync/imapsync (this is an imapsync copy, sometimes delayed, with --noreleasecheck by default since release 1.592, 2014/05/22)
903 imap_tools: https://web.archive.org/web/20161228145952/http://www.athensfbc.com/imap_tools/. The imap_tools code is now at https://github.com/andrewnimmo/rick-sanders-imap-tools
904 imaputils: https://github.com/mtsatsenko/imaputils (very old imap_tools fork)
905 Doveadm-Sync: https://wiki2.dovecot.org/Tools/Doveadm/Sync ( Dovecot sync tool )
906 davmail: http://davmail.sourceforge.net/
907 offlineimap: http://offlineimap.org/
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200908 fdm: https://github.com/nicm/fdm
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100909 mbsync: http://isync.sourceforge.net/
910 mailsync: http://mailsync.sourceforge.net/
911 mailutil: https://www.washington.edu/imap/ part of the UW IMAP toolkit. (well, seems abandoned now)
912 imaprepl: https://bl0rg.net/software/ http://freecode.com/projects/imap-repl/
913 imapcopy (Pascal): http://www.ardiehl.de/imapcopy/
914 imapcopy (Java): https://code.google.com/archive/p/imapcopy/
915 imapsize: http://www.broobles.com/imapsize/
916 migrationtool: http://sourceforge.net/projects/migrationtool/
917 imapmigrate: http://sourceforge.net/projects/cyrus-utils/
918 larch: https://github.com/rgrove/larch (derived from wonko_imapsync, good at Gmail)
919 wonko_imapsync: http://wonko.com/article/554 (superseded by larch)
920 pop2imap: http://www.linux-france.org/prj/pop2imap/ (I wrote that too)
921 exchange-away: http://exchange-away.sourceforge.net/
922 SyncBackPro: http://www.2brightsparks.com/syncback/sbpro.html
923 ImapSyncClient: https://github.com/ridaamirini/ImapSyncClient
924 MailStore: https://www.mailstore.com/en/products/mailstore-home/
925 mnIMAPSync: https://github.com/manusa/mnIMAPSync
926 imap-upload: http://imap-upload.sourceforge.net/ (A tool for uploading a local mbox file to IMAP4 server)
927 imapbackup: https://github.com/rcarmo/imapbackup (A Python script for incremental backups of IMAP mailboxes)
928 BitRecover email-backup 99 USD, 299 USD https://www.bitrecover.com/email-backup/.
929 ImportExportTools: https://addons.thunderbird.net/en-us/thunderbird/addon/importexporttools/ ImportExportTools for Mozilla Thunderbird by Paolo Kaosmos. ImportExportTools does not do IMAP.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200930 rximapmail: https://sourceforge.net/projects/rximapmail/
931 CodeTwo: https://www.codetwo.com/ but CodeTwo does imap source to Office365 only.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100932
933=head1 HISTORY
934
935I initially wrote imapsync in July 2001 because an enterprise,
936called BaSystemes, paid me to install a new imap server
937without losing huge old mailboxes located in a far
938away remote imap server, accessible by an
939often broken low-bandwidth ISDN link.
940
941I had to verify every mailbox was well transferred, all folders, all messages,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200942without wasting bandwidth or creating duplicates upon resyncs. The imapsync
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100943design was made with the beautiful rsync command in mind.
944
945Imapsync started its life as a patch of the copy_folder.pl
946script. The script copy_folder.pl comes from the Mail-IMAPClient-2.1.3 perl
947module tarball source (more precisely in the examples/ directory of the
948Mail-IMAPClient tarball).
949
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200950So many changes happened since then that I wonder
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100951if it remains any lines of the original
952copy_folder.pl in imapsync source code.
953
954
955=cut
956
957
958# use pragmas
959#
960
961use strict ;
962use warnings ;
963use Carp ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200964use Cwd ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100965use Compress::Zlib ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100966use Data::Dumper ;
967use Digest::HMAC_SHA1 qw( hmac_sha1 hmac_sha1_hex ) ;
968use Digest::MD5 qw( md5 md5_hex md5_base64 ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200969use Encode ;
970use Encode::IMAPUTF7 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100971use English qw( -no_match_vars ) ;
972use Errno qw(EAGAIN EPIPE ECONNRESET) ;
973use Fcntl ;
974use File::Basename ;
975use File::Copy::Recursive ;
976use File::Glob qw( :glob ) ;
977use File::Path qw( mkpath rmtree ) ;
978use File::Spec ;
979use File::stat ;
980use Getopt::Long ( ) ;
981use IO::File ;
982use IO::Socket qw( :crlf SOL_SOCKET SO_KEEPALIVE ) ;
983use IO::Socket::INET6 ;
984use IO::Socket::SSL ;
985use IO::Tee ;
986use IPC::Open3 'open3' ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200987#use locale ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100988use Mail::IMAPClient 3.30 ;
989use MIME::Base64 ;
990use Pod::Usage qw(pod2usage) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200991use POSIX qw( uname SIGALRM :sys_wait_h ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100992use Sys::Hostname ;
993use Term::ReadKey ;
994use Test::More ;
995use Time::HiRes qw( time sleep ) ;
996use Time::Local ;
997use Unicode::String ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100998use Readonly ;
999use Sys::MemInfo ;
1000use Regexp::Common ;
1001use Text::ParseWords ; # for quotewords()
1002use File::Tail ;
1003
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001004
1005
1006local $OUTPUT_AUTOFLUSH = 1 ;
1007
1008# constants
1009
1010# Let us do like sysexits.h
1011# /usr/include/sysexits.h
1012# and https://www.tldp.org/LDP/abs/html/exitcodes.html
1013
1014# Should avoid 2 126 127 128..128+64=192 255
1015# Should use 0 1 3..125 193..254
1016
1017Readonly my $EX_OK => 0 ; #/* successful termination */
1018Readonly my $EX_USAGE => 64 ; #/* command line usage error */
1019#Readonly my $EX_DATAERR => 65 ; #/* data format error */
1020Readonly my $EX_NOINPUT => 66 ; #/* cannot open input */
1021#Readonly my $EX_NOUSER => 67 ; #/* addressee unknown */
1022#Readonly my $EX_NOHOST => 68 ; #/* host name unknown */
1023Readonly my $EX_UNAVAILABLE => 69 ; #/* service unavailable */
1024Readonly my $EX_SOFTWARE => 70 ; #/* internal software error */
1025#Readonly my $EX_OSERR => 71 ; #/* system error (e.g., can't fork) */
1026#Readonly my $EX_OSFILE => 72 ; #/* critical OS file missing */
1027#Readonly my $EX_CANTCREAT => 73 ; #/* can't create (user) output file */
1028#Readonly my $EX_IOERR => 74 ; #/* input/output error */
1029#Readonly my $EX_TEMPFAIL => 75 ; #/* temp failure; user is invited to retry */
1030#Readonly my $EX_PROTOCOL => 76 ; #/* remote error in protocol */
1031#Readonly my $EX_NOPERM => 77 ; #/* permission denied */
1032#Readonly my $EX_CONFIG => 78 ; #/* configuration error */
1033
1034# Mine
1035Readonly my $EXIT_CATCH_ALL => 1 ; # Any other error
1036Readonly my $EXIT_BY_SIGNAL => 6 ; # Should be 128+n where n is the sig_num
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001037Readonly my $EXIT_BY_FILE => 7 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001038Readonly my $EXIT_PID_FILE_ERROR => 8 ;
1039Readonly my $EXIT_CONNECTION_FAILURE => 10 ;
1040Readonly my $EXIT_TLS_FAILURE => 12 ;
1041Readonly my $EXIT_AUTHENTICATION_FAILURE => 16 ;
1042Readonly my $EXIT_SUBFOLDER1_NO_EXISTS => 21 ;
1043Readonly my $EXIT_WITH_ERRORS => 111 ;
1044Readonly my $EXIT_WITH_ERRORS_MAX => 112 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001045Readonly my $EXIT_OVERQUOTA => 113 ;
1046Readonly my $EXIT_ERR_APPEND => 114 ;
1047Readonly my $EXIT_ERR_FETCH => 115 ;
1048Readonly my $EXIT_ERR_CREATE => 116 ;
1049Readonly my $EXIT_ERR_SELECT => 117 ;
1050Readonly my $EXIT_TRANSFER_EXCEEDED => 118 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001051
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001052Readonly my $EXIT_ERR_APPEND_VIRUS => 119 ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001053Readonly my $EXIT_ERR_FLAGS => 120 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001054
1055Readonly my $EXIT_TESTS_FAILED => 254 ; # Like Test::More API
1056
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001057Readonly my $EXIT_CONNECTION_FAILURE_HOST1 => 101 ;
1058Readonly my $EXIT_CONNECTION_FAILURE_HOST2 => 102 ;
1059Readonly my $EXIT_AUTHENTICATION_FAILURE_USER1 => 161 ;
1060Readonly my $EXIT_AUTHENTICATION_FAILURE_USER2 => 162 ;
1061
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001062
1063Readonly my %EXIT_TXT => (
1064 $EX_OK => 'EX_OK: successful termination',
1065 $EX_USAGE => 'EX_USAGE: command line usage error',
1066 $EX_NOINPUT => 'EX_NOINPUT: cannot open input',
1067 $EX_UNAVAILABLE => 'EX_UNAVAILABLE: service unavailable',
1068 $EX_SOFTWARE => 'EX_SOFTWARE: internal software error',
1069
1070 $EXIT_CATCH_ALL => 'EXIT_CATCH_ALL',
1071 $EXIT_BY_SIGNAL => 'EXIT_BY_SIGNAL',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001072 $EXIT_BY_FILE => 'EXIT_BY_FILE',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001073 $EXIT_PID_FILE_ERROR => 'EXIT_PID_FILE_ERROR' ,
1074 $EXIT_CONNECTION_FAILURE => 'EXIT_CONNECTION_FAILURE',
1075 $EXIT_TLS_FAILURE => 'EXIT_TLS_FAILURE',
1076 $EXIT_AUTHENTICATION_FAILURE => 'EXIT_AUTHENTICATION_FAILURE',
1077 $EXIT_SUBFOLDER1_NO_EXISTS => 'EXIT_SUBFOLDER1_NO_EXISTS',
1078 $EXIT_WITH_ERRORS => 'EXIT_WITH_ERRORS',
1079 $EXIT_WITH_ERRORS_MAX => 'EXIT_WITH_ERRORS_MAX',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001080 $EXIT_OVERQUOTA => 'EXIT_OVERQUOTA',
1081 $EXIT_ERR_APPEND => 'EXIT_ERR_APPEND',
1082 $EXIT_ERR_APPEND_VIRUS => 'EXIT_ERR_APPEND_VIRUS',
1083 $EXIT_ERR_FETCH => 'EXIT_ERR_FETCH',
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001084 $EXIT_ERR_FLAGS => 'EXIT_ERR_FLAGS',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001085 $EXIT_ERR_CREATE => 'EXIT_ERR_CREATE',
1086 $EXIT_ERR_SELECT => 'EXIT_ERR_SELECT',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001087 $EXIT_TESTS_FAILED => 'EXIT_TESTS_FAILED',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001088 $EXIT_TRANSFER_EXCEEDED => 'EXIT_TRANSFER_EXCEEDED',
1089 $EXIT_CONNECTION_FAILURE_HOST1 => 'EXIT_CONNECTION_FAILURE_HOST1',
1090 $EXIT_CONNECTION_FAILURE_HOST2 => 'EXIT_CONNECTION_FAILURE_HOST2',
1091 $EXIT_AUTHENTICATION_FAILURE_USER1 => 'EXIT_AUTHENTICATION_FAILURE_USER1',
1092 $EXIT_AUTHENTICATION_FAILURE_USER2 => 'EXIT_AUTHENTICATION_FAILURE_USER2',
1093) ;
1094
1095
1096Readonly my %EXIT_VALUE_OF_ERR_TYPE => (
1097 ERR_APPEND_SIZE => $EXIT_ERR_APPEND,
1098 ERR_OVERQUOTA => $EXIT_OVERQUOTA,
1099 ERR_APPEND => $EXIT_ERR_APPEND,
1100 ERR_APPEND_VIRUS => $EXIT_ERR_APPEND_VIRUS,
1101 ERR_CREATE => $EXIT_ERR_CREATE,
1102 ERR_SELECT => $EXIT_ERR_SELECT,
1103 ERR_Host1_FETCH => $EXIT_ERR_FETCH,
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001104 ERR_FLAGS => $EXIT_ERR_FLAGS,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001105 ERR_UNCLASSIFIED => $EXIT_WITH_ERRORS,
1106 ERR_NOTHING_REPORTED => $EXIT_WITH_ERRORS,
1107 ERR_TRANSFER_EXCEEDED => $EXIT_TRANSFER_EXCEEDED,
1108 ERR_CONNECTION_FAILURE_HOST1 => $EXIT_CONNECTION_FAILURE_HOST1,
1109 ERR_CONNECTION_FAILURE_HOST2 => $EXIT_CONNECTION_FAILURE_HOST2,
1110 ERR_AUTHENTICATION_FAILURE_USER1 => $EXIT_AUTHENTICATION_FAILURE_USER1,
1111 ERR_AUTHENTICATION_FAILURE_USER2 => $EXIT_AUTHENTICATION_FAILURE_USER2,
1112 ERR_EXIT_TLS_FAILURE => $EXIT_TLS_FAILURE,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001113) ;
1114
1115
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001116
1117Readonly my %COMMENT_OF_ERR_TYPE => (
1118 ERR_APPEND_SIZE => \&comment_err_append_size,
1119 ERR_OVERQUOTA => \&comment_err_overquota,
1120 ERR_APPEND => \&comment_err_blank,
1121 ERR_APPEND_VIRUS => \&comment_err_blank,
1122 ERR_CREATE => \&comment_err_blank,
1123 ERR_SELECT => \&comment_err_blank,
1124 ERR_Host1_FETCH => \&comment_err_blank,
1125 ERR_FLAGS => \&comment_err_flags,
1126 ERR_UNCLASSIFIED => \&comment_err_blank,
1127 ERR_NOTHING_REPORTED => \&comment_err_blank,
1128 ERR_TRANSFER_EXCEEDED => \&comment_err_transfer_exceeded,
1129 ERR_CONNECTION_FAILURE_HOST1 => \&comment_err_connection_failure_host1,
1130 ERR_CONNECTION_FAILURE_HOST2 => \&comment_err_connection_failure_host2,
1131 ERR_AUTHENTICATION_FAILURE_USER1 => \&comment_err_authentication_failure_host1,
1132 ERR_AUTHENTICATION_FAILURE_USER2 => \&comment_err_authentication_failure_host2,
1133 ERR_EXIT_TLS_FAILURE => \&comment_err_blank,
1134) ;
1135
1136
1137sub comment_err_blank
1138{
1139 return '' ;
1140}
1141
1142
1143sub comment_err_append_size
1144{
1145 my $mysync = shift @ARG ;
1146
1147 my $comment = "The destination server refuses too big messages. Use --truncmess option. Read https://imapsync.lamiral.info/FAQ.d/FAQ.Messages_Too_Big.txt" ;
1148 return $comment ;
1149}
1150
1151
1152sub comment_err_authentication_failure_host1
1153{
1154 my $mysync = shift @ARG ;
1155
1156 my $comment = "Check the credentials for $mysync->{ user1 }." ;
1157 return $comment ;
1158}
1159
1160sub comment_err_authentication_failure_host2
1161{
1162 my $mysync = shift @ARG ;
1163
1164 my $comment = "Check the credentials for $mysync->{ user2 }." ;
1165 return $comment ;
1166}
1167
1168
1169sub comment_err_connection_failure_host1
1170{
1171 my $mysync = shift @ARG ;
1172
1173 my $comment = "Check that host1 $mysync->{ host1 } on port $mysync->{ port1 } is the right IMAP server to be contacted for your mailbox." ;
1174 return $comment ;
1175}
1176
1177sub comment_err_connection_failure_host2
1178{
1179 my $mysync = shift @ARG ;
1180
1181 my $comment = "Check that host1 $mysync->{ host2 } on port $mysync->{ port2 } is the right IMAP server to be contacted for your mailbox." ;
1182 return $comment ;
1183}
1184
1185sub comment_err_overquota
1186{
1187 my $mysync = shift @ARG ;
1188
1189 my $comment = 'The destination mailbox is 100% full, get free space on it and then resume the sync.' ;
1190 return $comment ;
1191}
1192
1193
1194sub comment_err_transfer_exceeded
1195{
1196 my $mysync = shift @ARG ;
1197
1198 my $size_limit_human = bytes_display_string_dec( $mysync->{ exitwhenover } ) ;
1199 my $comment = "The maximum transfer size for a single sync is reached ( over $size_limit_human ). Relaunch the sync to sync more." ;
1200 return $comment ;
1201}
1202
1203sub comment_err_flags
1204{
1205 my $mysync = shift @ARG ;
1206
1207 my $comment = 'Many STORE errors with FLAGS. Retry with the option --noresyncflags' ;
1208 return $comment ;
1209}
1210
1211
1212
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001213Readonly my $DEFAULT_LOGDIR => 'LOG_imapsync' ;
1214
1215Readonly my $ERRORS_MAX => 50 ; # exit after 50 errors.
1216Readonly my $ERRORS_MAX_CGI => 20 ; # exit after 20 errors in CGI context.
1217
1218
1219
1220Readonly my $INTERVAL_TO_EXIT => 2 ; # interval max to exit instead of reconnect
1221
1222Readonly my $SPLIT => 100 ; # By default, 100 at a time, not more.
1223Readonly my $SPLIT_FACTOR => 10 ; # init_imap() calls Maxcommandlength( $SPLIT_FACTOR * $split )
1224 # which means default Maxcommandlength is 10*100 = 1000 characters ;
1225
1226Readonly my $IMAP_PORT => 143 ; # Well know port for IMAP
1227Readonly my $IMAP_SSL_PORT => 993 ; # Well know port for IMAP over SSL
1228
1229Readonly my $LAST => -1 ;
1230Readonly my $MINUS_ONE => -1 ;
1231Readonly my $MINUS_TWO => -2 ;
1232
1233Readonly my $RELEASE_NUMBER_EXAMPLE_1 => '1.351' ;
1234Readonly my $RELEASE_NUMBER_EXAMPLE_2 => 42.4242 ;
1235
1236Readonly my $TCP_PING_TIMEOUT => 5 ;
1237Readonly my $DEFAULT_TIMEOUT => 120 ;
1238Readonly my $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND => 3 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001239
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001240Readonly my $DEFAULT_BUFFER_SIZE => 4096 ;
1241
1242Readonly my $MAX_SLEEP => 2 ; # 2 seconds max for limiting too long sleeps from --maxbytespersecond and --maxmessagespersecond
1243
1244Readonly my $DEFAULT_EXPIRATION_TIME_OAUTH2_PK12 => 3600 ;
1245
1246Readonly my $PERMISSION_FILTER => 7777 ;
1247
1248Readonly my $KIBI => 1024 ;
1249
1250Readonly my $NUMBER_10 => 10 ;
1251Readonly my $NUMBER_42 => 42 ;
1252Readonly my $NUMBER_100 => 100 ;
1253Readonly my $NUMBER_200 => 200 ;
1254Readonly my $NUMBER_300 => 300 ;
1255Readonly my $NUMBER_123456 => 123_456 ;
1256Readonly my $NUMBER_654321 => 654_321 ;
1257
1258Readonly my $NUMBER_20_000 => 20_000 ;
1259
1260Readonly my $QUOTA_PERCENT_LIMIT => 90 ;
1261
1262Readonly my $NUMBER_104_857_600 => 104_857_600 ;
1263
1264Readonly my $SIZE_MAX_STR => 64 ;
1265
1266Readonly my $NB_SECONDS_IN_A_DAY => 86_400 ;
1267
1268Readonly my $STD_CHAR_PER_LINE => 80 ;
1269
1270Readonly my $TRUE => 1 ;
1271Readonly my $FALSE => 0 ;
1272
1273Readonly my $LAST_RESSORT_SEPARATOR => q{/} ;
1274
1275Readonly my $CGI_TMPDIR_TOP => '/var/tmp/imapsync_cgi' ;
1276Readonly my $CGI_HASHFILE => '/var/tmp/imapsync_hash' ;
1277Readonly my $UMASK_PARANO => '0077' ;
1278
1279Readonly my $STR_use_releasecheck => q{Check if a new imapsync release is available by adding --releasecheck} ;
1280
1281Readonly my $GMAIL_MAXSIZE => 35_651_584 ;
1282
1283Readonly my $FORCE => 1 ;
1284
1285# if ( 'MSWin32' eq $OSNAME )
1286# if ( 'darwin' eq $OSNAME )
1287# if ( 'linux' eq $OSNAME )
1288
1289
1290
1291# global variables
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001292# Currently working to finish with only $sync, $acc1, $acc2
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001293# Not finished yet...
1294
1295my(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001296 $sync, $acc1, $acc2,
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001297 $debugflags,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001298 $debuglist, $debugdev, $debugmaxlinelength, $debugcgi,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001299 @include, @exclude, @folderrec,
1300 @folderfirst, @folderlast,
1301 @h1_folders_all, %h1_folders_all,
1302 @h2_folders_all, %h2_folders_all,
1303 @h2_folders_from_1_wanted, %h2_folders_from_1_all,
1304 %requested_folder,
1305 $h1_folders_wanted_nb, $h1_folders_wanted_ct,
1306 @h2_folders_not_in_1,
1307 %h1_subscribed_folder, %h2_subscribed_folder,
1308 %h2_folders_from_1_wanted,
1309 %h2_folders_from_1_several,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001310 $prefix1, $prefix2,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001311 @regexmess, @skipmess, @pipemess, $pipemesscheck,
1312 $syncflagsaftercopy,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001313 $syncinternaldates,
1314 $idatefromheader,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001315 $minsize, $maxage, $minage,
1316 $search,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001317 @useheader, %useheader,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001318 $skipsize, $allowsizemismatch, $buffersize,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001319 $authmd5, $authmd51, $authmd52,
1320 $subscribed, $subscribe, $subscribeall,
1321 $help,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001322 $nb_msg_skipped_dry_mode,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001323 $h2_nb_msg_noheader,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001324 $h1_bytes_processed,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001325 $h1_nb_msg_end, $h1_bytes_end,
1326 $h2_nb_msg_end, $h2_bytes_end,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001327 $timestart_int,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001328 $uid1, $uid2,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001329 $split1, $split2,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001330 $modulesversion,
1331 $delete2folders, $delete2foldersonly, $delete2foldersbutnot,
1332 $usecache, $debugcache, $cacheaftercopy,
1333 $wholeheaderifneeded, %h1_msgs_copy_by_uid, $useuid, $h2_uidguess,
1334 $checkmessageexists,
1335 $messageidnodomain,
1336 $fixInboxINBOX,
1337 $maxlinelength, $maxlinelengthcmd,
1338 $minmaxlinelength,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001339 $fixcolonbug,
1340 $create_folder_old,
1341 $skipcrossduplicates, $debugcrossduplicates,
1342 $disarmreadreceipts,
1343 $mixfolders,
1344 $fetch_hash_set,
1345 $cgidir,
1346 %month_abrev,
1347 $SSL_VERIFY_POLICY,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001348) ;
1349
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001350single_sync( $sync, $acc1, $acc2 );
1351
1352
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001353
1354sub single_sync
1355{
1356
1357# main program
1358# global variables initialization
1359
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001360# I'm currently removing all global variables except $sync $acc1 $acc2
1361# passing each of them under
1362# $sync->{variable_name}
1363# or $acc1->{variable_name}
1364# or $acc1->{variable_name}
1365
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001366#
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001367$acc1 = {} ;
1368$acc2 = {} ;
1369$sync->{ acc1 } = $acc1 ;
1370$sync->{ acc2 } = $acc2 ;
1371
1372$acc1->{ Side } = 'Host1' ;
1373$acc2->{ Side } = 'Host2' ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001374$acc1->{ N } = '1' ;
1375$acc2->{ N } = '2' ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001376
1377$sync->{timestart} = time ; # Is a float because of use Time::HiRres
1378
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001379$sync->{rcs} = q{$Id: imapsync,v 2.178 2022/01/12 21:28:37 gilles Exp gilles $} ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001380
1381$sync->{ memory_consumption_at_start } = memory_consumption( ) || 0 ;
1382
1383
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001384
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001385my @loadavg = loadavg( ) ;
1386
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001387$sync->{ cpu_number } = cpu_number( ) ;
1388$sync->{ loaddelay } = load_and_delay( $sync->{ cpu_number }, @loadavg ) ;
1389$sync->{ loaddelay } = 0 ;
1390
1391$sync->{ loadavg } = join( q{ }, $loadavg[ 0 ] )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001392 . " on $sync->{cpu_number} cores and "
1393 . ram_memory_info( ) ;
1394
1395
1396
1397$sync->{ total_bytes_transferred } = 0 ;
1398$sync->{ total_bytes_skipped } = 0 ;
1399$sync->{ nb_msg_transferred } = 0 ;
1400$sync->{ nb_msg_skipped } = $nb_msg_skipped_dry_mode = 0 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001401
1402$sync->{ acc1 }->{ nb_msg_deleted } = 0 ;
1403$sync->{ acc2 }->{ nb_msg_deleted } = 0 ;
1404
1405$sync->{ acc1 }->{ nb_msg_duplicate } = 0 ;
1406$sync->{ acc2 }->{ nb_msg_duplicate } = 0 ;
1407
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001408$sync->{ h1_nb_msg_noheader } = 0 ;
1409$h2_nb_msg_noheader = 0 ;
1410
1411
1412$sync->{ h1_nb_msg_start } = 0 ;
1413$sync->{ h1_bytes_start } = 0 ;
1414$sync->{ h2_nb_msg_start } = 0 ;
1415$sync->{ h2_bytes_start } = 0 ;
1416$sync->{ h1_nb_msg_processed } = $h1_bytes_processed = 0 ;
1417
1418$sync->{ h2_nb_msg_crossdup } = 0 ;
1419
1420#$h1_nb_msg_end = $h1_bytes_end = 0 ;
1421#$h2_nb_msg_end = $h2_bytes_end = 0 ;
1422
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001423$sync->{ nb_errors } = 0;
1424$sync->{ biggest_message_transferred } = 0;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001425
1426%month_abrev = (
1427 Jan => '00',
1428 Feb => '01',
1429 Mar => '02',
1430 Apr => '03',
1431 May => '04',
1432 Jun => '05',
1433 Jul => '06',
1434 Aug => '07',
1435 Sep => '08',
1436 Oct => '09',
1437 Nov => '10',
1438 Dec => '11',
1439);
1440
1441
1442
1443# Just create a CGI object if under cgi context only.
1444# Needed for the get_options() call
1445cgibegin( $sync ) ;
1446
1447# In cgi context, printing must start by the header so we delay other prints by using output() storage
1448my $options_good = get_options( $sync, @ARGV ) ;
1449# Is it the first myprint?
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001450cgibuildheader( $sync ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001451docker_context( $sync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001452
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001453print_output_if_needed( $sync ) ;
1454
1455
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001456output_reset_with( $sync ) ;
1457
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001458# don't go on if options are not all known.
1459if ( ! defined $options_good ) { exit $EX_USAGE ; }
1460
1461# If you want releasecheck not to be done by default (like the github maintainer),
1462# then just uncomment the first "$sync->{releasecheck} =" line, the line ending with "0 ;",
1463# the second line (ending with "1 ;") can then stay active or be commented,
1464# the result will be the same: no releasecheck by default (because 0 is then the defined value).
1465
1466#$sync->{releasecheck} = defined $sync->{releasecheck} ? $sync->{releasecheck} : 0 ;
1467$sync->{releasecheck} = defined $sync->{releasecheck} ? $sync->{releasecheck} : 1 ;
1468
1469# just the version
1470if ( $sync->{ version } ) {
1471 myprint( imapsync_version( $sync ), "\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001472 return 0 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001473}
1474
1475#$sync->{debugenv} = 1 ;
1476$sync->{debugenv} and printenv( $sync ) ; # if option --debugenv
1477load_modules( ) ;
1478
1479# after_get_options call usage and exit if --help or options were not well got
1480after_get_options( $sync, $options_good ) ;
1481
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001482#local $ENV{TZ} = 'GMT' if ( under_cgi_context( $sync ) and 'MSWin32' ne $OSNAME ) ;
1483#output( $sync, localtime(time) . " " . gmtime(time) . "\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001484
1485# Under CGI environment, fix caveat emptor potential issues
1486cgisetcontext( $sync ) ;
1487
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001488get_options_extra( $sync ) ;
1489
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001490# --gmail --gmail --exchange --office etc.
1491easyany( $sync ) ;
1492
1493$sync->{ sanitize } = defined $sync->{ sanitize } ? $sync->{ sanitize } : 1 ;
1494sanitize( $sync ) ;
1495
1496$sync->{ tmpdir } ||= File::Spec->tmpdir( ) ;
1497
1498# Unit tests
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001499my $unittestssuite = unittestssuite( $sync ) ;
1500
1501
1502if ( condition_to_leave_after_tests( $sync ) )
1503{
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001504 return $unittestssuite ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001505}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001506
1507# init live varaiables
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001508
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001509if ( $sync->{ testslive } )
1510{
1511 testslive_init( $sync ) ;
1512}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001513
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001514if ( $sync->{ testslive6 } )
1515{
1516 testslive6_init( $sync ) ;
1517}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001518
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001519define_pidfile( $sync ) ;
1520if ( $sync->{ abortbyfile } ) { $sync->{ abort } = 1 ; }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001521
1522install_signals( $sync ) ;
1523
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001524$sync->{ log } = defined $sync->{ log } ? $sync->{ log } : 1 ;
1525$sync->{ errorsdump } = defined $sync->{ errorsdump } ? $sync->{ errorsdump } : 1 ;
1526$sync->{ errorsmax } = defined $sync->{ errorsmax } ? $sync->{ errorsmax } : $ERRORS_MAX ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001527
1528# log and output
1529binmode STDOUT, ":encoding(UTF-8)" ;
1530
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001531
1532if ( $sync->{ log } ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001533 setlogfile( $sync ) ;
1534 teelaunch( $sync ) ;
1535 # now $sync->{tee} is a filehandle to STDOUT and the logfile
1536}
1537
1538#binmode STDERR, ":encoding(UTF-8)" ;
1539# STDERR goes to the same place: LOG and STDOUT (if logging is on)
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001540# Useful only for --debugssl
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001541$sync->{tee} and local *STDERR = *${$sync->{tee}}{IO} ;
1542
1543
1544
1545$timestart_int = int( $sync->{timestart} ) ;
1546$sync->{timebefore} = $sync->{timestart} ;
1547
1548
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001549$sync->{ timestart_str } = localtimez( $sync->{timestart} ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001550
1551# The prints in the log starts here
1552
1553myprint( localhost_info( $sync ), "\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001554myprint( "Transfer started at $sync->{ timestart_str }\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001555myprint( "PID is $PROCESS_ID my PPID is ", mygetppid( ), "\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001556announcelogfile( $sync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001557myprint( "Load is " . ( join( q{ }, loadavg( ) ) || 'unknown' ), " on $sync->{cpu_number} cores\n" ) ;
1558#myprintf( "Memory consumption so far: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI ) ;
1559myprint( 'Current directory is ' . getcwd( ) . "\n" ) ;
1560myprint( 'Real user id is ' . getpwuid_any_os( $REAL_USER_ID ) . " (uid $REAL_USER_ID)\n" ) ;
1561myprint( 'Effective user id is ' . getpwuid_any_os( $EFFECTIVE_USER_ID ). " (euid $EFFECTIVE_USER_ID)\n" ) ;
1562
1563$modulesversion = defined $modulesversion ? $modulesversion : 1 ;
1564
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001565$sync->{ warn_release } = ( $sync->{ releasecheck } ) ? check_last_release( ) : $STR_use_releasecheck ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001566
1567
1568$wholeheaderifneeded = defined $wholeheaderifneeded ? $wholeheaderifneeded : 1;
1569
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001570# Activate --usecache if --useuid is set and there is no --nousecache
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001571$usecache = 1 if ( $useuid and ( ! defined $usecache ) ) ;
1572$cacheaftercopy = 1 if ( $usecache and ( ! defined $cacheaftercopy ) ) ;
1573
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001574
1575
1576
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001577$sync->{ checkfoldersexist } = defined $sync->{ checkfoldersexist } ? $sync->{ checkfoldersexist } : 1 ;
1578$checkmessageexists = defined $checkmessageexists ? $checkmessageexists : 0 ;
1579$sync->{ expungeaftereach } = defined $sync->{ expungeaftereach } ? $sync->{ expungeaftereach } : 1 ;
1580
1581# abletosearch is on by default
1582$sync->{abletosearch} = defined $sync->{abletosearch} ? $sync->{abletosearch} : 1 ;
1583$sync->{abletosearch1} = defined $sync->{abletosearch1} ? $sync->{abletosearch1} : $sync->{abletosearch} ;
1584$sync->{abletosearch2} = defined $sync->{abletosearch2} ? $sync->{abletosearch2} : $sync->{abletosearch} ;
1585$checkmessageexists = 0 if ( not $sync->{abletosearch1} ) ;
1586
1587
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001588$sync->{ trylogin } = defined $sync->{ trylogin } ? $sync->{ trylogin } : 1 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001589$sync->{showpasswords} = defined $sync->{showpasswords} ? $sync->{showpasswords} : 0 ;
1590$sync->{ fixslash2 } = defined $sync->{ fixslash2 } ? $sync->{ fixslash2 } : 1 ;
1591$fixInboxINBOX = defined $fixInboxINBOX ? $fixInboxINBOX : 1 ;
1592$create_folder_old = defined $create_folder_old ? $create_folder_old : 0 ;
1593$mixfolders = defined $mixfolders ? $mixfolders : 1 ;
1594$sync->{automap} = defined $sync->{automap} ? $sync->{automap} : 0 ;
1595
1596$sync->{ delete2duplicates } = 1 if ( $sync->{ delete2 } and ( ! defined $sync->{ delete2duplicates } ) ) ;
1597
1598$sync->{maxmessagespersecond} = defined $sync->{maxmessagespersecond} ? $sync->{maxmessagespersecond} : 0 ;
1599$sync->{maxbytespersecond} = defined $sync->{maxbytespersecond} ? $sync->{maxbytespersecond} : 0 ;
1600
1601$sync->{sslcheck} = defined $sync->{sslcheck} ? $sync->{sslcheck} : 1 ;
1602
1603myprint( banner_imapsync( $sync, @ARGV ) ) ;
1604
1605myprint( "Temp directory is $sync->{ tmpdir } ( to change it use --tmpdir dirpath )\n" ) ;
1606
1607myprint( output( $sync ) ) ;
1608output_reset_with( $sync ) ;
1609
1610do_valid_directory( $sync->{ tmpdir } ) || croak "Error creating tmpdir $sync->{ tmpdir } : $OS_ERROR" ;
1611
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001612remove_pidfile_not_running( $sync->{ pidfile } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001613
1614# if another imapsync is running then tail -f its logfile and exit
1615# useful in cgi context
1616if ( $sync->{ tail } and tail( $sync ) )
1617{
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001618 exit_clean( $sync, $EX_OK, "Tail -f finished. Now finishing myself processus $PROCESS_ID\n" ) ;
1619 exit $EX_OK ;
1620}
1621
1622if ( ! write_pidfile( $sync ) ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001623 myprint( "Exiting with return value $EXIT_PID_FILE_ERROR ($EXIT_TXT{$EXIT_PID_FILE_ERROR}) $sync->{nb_errors}/$sync->{errorsmax} nb_errors/max_errors PID $PROCESS_ID\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001624 exit $EXIT_PID_FILE_ERROR ;
1625}
1626
1627
1628# New place for abort
1629# abort before simulong in order to be able to abort a simulong sync
1630if ( $sync->{ abort } )
1631{
1632 abort( $sync ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001633 # well, the abort job is done, because even when not succeeded
1634 # in aborting another run, this run has to end without doing any
1635 # thing else
1636
1637 exit $EX_OK ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001638}
1639
1640# simulong is just a loop printing some lines for xx seconds with option "--simulong xx".
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001641simulong( $sync ) ;
1642
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001643
1644
1645# New place for cgiload 2019_03_03
1646# because I want to log it
1647# Can break here if load is too heavy
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001648# Have in mind the CGI header has already a 503 Service Unavailable
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001649cgiload( $sync ) ;
1650
1651
1652$fixcolonbug = defined $fixcolonbug ? $fixcolonbug : 1 ;
1653
1654if ( $usecache and $fixcolonbug ) { tmpdir_fix_colon_bug( $sync ) } ;
1655
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001656$modulesversion and myprint( "Modules version list ( use --no-modulesversion to turn off printing this Perl modules list ):\n", modulesversion(), "\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001657
1658
1659check_lib_version( $sync ) or
1660 croak "imapsync needs perl lib Mail::IMAPClient release 3.30 or superior.\n";
1661
1662
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001663
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001664if ( $sync->{ justbanner } )
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001665{
1666 myprint( "Exiting because of --justbanner\n" ) ;
1667 exit_clean( $sync, $EX_OK ) ;
1668}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001669
1670# turn on RFC standard flags correction like \SEEN -> \Seen
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001671$sync->{ flagscase } = defined $sync->{ flagscase } ? $sync->{ flagscase } : 1 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001672
1673# Use PERMANENTFLAGS if available
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001674$sync->{ filterflags } = defined $sync->{ filterflags } ? $sync->{ filterflags } : 1 ;
1675
1676filterbuggyflags( $sync ) ;
1677
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001678
1679# sync flags just after an APPEND, some servers ignore the flags given in the APPEND
1680# like MailEnable IMAP server.
1681# Off by default since it takes time.
1682$syncflagsaftercopy = defined $syncflagsaftercopy ? $syncflagsaftercopy : 0 ;
1683
1684# update flags on host2 for already transferred messages
1685$sync->{resyncflags} = defined $sync->{resyncflags} ? $sync->{resyncflags} : 1 ;
1686if ( $sync->{resyncflags} ) {
1687 myprint( "Info: will resync flags for already transferred messages. Use --noresyncflags to not resync flags.\n" ) ;
1688}else{
1689 myprint( "Info: will not resync flags for already transferred messages. Use --resyncflags to resync flags.\n" ) ;
1690}
1691
1692
1693sslcheck( $sync ) ;
1694#print Data::Dumper->Dump( [ \$sync ] ) ;
1695
1696$split1 ||= $SPLIT ;
1697$split2 ||= $SPLIT ;
1698
1699#$sync->{host1} || missing_option( $sync, '--host1' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001700$sync->{host1} = sanitize_host( $sync->{host1} ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001701$sync->{port1} ||= ( $sync->{ssl1} ) ? $IMAP_SSL_PORT : $IMAP_PORT ;
1702
1703#$sync->{host2} || missing_option( $sync, '--host2' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001704$sync->{host2} = sanitize_host( $sync->{host2} ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001705$sync->{port2} ||= ( $sync->{ssl2} ) ? $IMAP_SSL_PORT : $IMAP_PORT ;
1706
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001707
1708$acc1->{ debugimap } = $acc2->{ debugimap } = 1 if ( $sync->{ debugimap } ) ;
1709# Set on debug mode if one of the imap dialogs are in debug.
1710# imap dialog without the debug mode is scary and useless.
1711$sync->{ debug } = 1 if ( $acc1->{ debugimap } or $acc2->{ debugimap } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001712
1713# By default, don't take size to compare
1714$skipsize = (defined $skipsize) ? $skipsize : 1;
1715
1716$uid1 = defined $uid1 ? $uid1 : 1;
1717$uid2 = defined $uid2 ? $uid2 : 1;
1718
1719$subscribe = defined $subscribe ? $subscribe : 1;
1720
1721# Allow size mismatch by default
1722$allowsizemismatch = defined $allowsizemismatch ? $allowsizemismatch : 1;
1723
1724
1725if ( defined $delete2foldersbutnot or defined $delete2foldersonly ) {
1726 $delete2folders = 1 ;
1727}
1728
1729
1730my %SSL_VERIFY_STR ;
1731
1732Readonly $SSL_VERIFY_POLICY => IO::Socket::SSL::SSL_VERIFY_NONE( ) ;
1733Readonly %SSL_VERIFY_STR => (
1734 IO::Socket::SSL::SSL_VERIFY_NONE( ) => 'SSL_VERIFY_NONE, ie, do not check the certificate server.' ,
1735 IO::Socket::SSL::SSL_VERIFY_PEER( ) => 'SSL_VERIFY_PEER, ie, check the certificate server' ,
1736) ;
1737
1738$IO::Socket::SSL::DEBUG = defined( $sync->{debugssl} ) ? $sync->{debugssl} : 1 ;
1739
1740
1741if ( $sync->{ssl1} or $sync->{ssl2} or $sync->{tls1} or $sync->{tls2}) {
1742 myprint( "SSL debug mode level is --debugssl $IO::Socket::SSL::DEBUG (can be set from 0 meaning no debug to 4 meaning max debug)\n" ) ;
1743}
1744
1745if ( $sync->{ssl1} ) {
1746 myprint( qq{Host1: SSL default mode is like --sslargs1 "SSL_verify_mode=$SSL_VERIFY_POLICY", meaning for host1 $SSL_VERIFY_STR{$SSL_VERIFY_POLICY}\n} ) ;
1747 myprint( 'Host1: Use --sslargs1 SSL_verify_mode=' . IO::Socket::SSL::SSL_VERIFY_PEER( ) . " to have $SSL_VERIFY_STR{IO::Socket::SSL::SSL_VERIFY_PEER( )} of host1\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001748 # $sync->{ acc1 }->{sslargs}->{SSL_verify_mode}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001749}
1750
1751if ( $sync->{ssl2} ) {
1752 myprint( qq{Host2: SSL default mode is like --sslargs2 "SSL_verify_mode=$SSL_VERIFY_POLICY", meaning for host2 $SSL_VERIFY_STR{$SSL_VERIFY_POLICY}\n} ) ;
1753 myprint( 'Host2: Use --sslargs2 SSL_verify_mode=' . IO::Socket::SSL::SSL_VERIFY_PEER( ) . " to have $SSL_VERIFY_STR{IO::Socket::SSL::SSL_VERIFY_PEER( )} of host2\n" ) ;
1754}
1755
1756# ID on by default since 1.832
1757$sync->{id} = defined $sync->{id} ? $sync->{id} : 1 ;
1758
1759if ( $sync->{justconnect}
1760 or not $sync->{user1}
1761 or not $sync->{user2}
1762 or not $sync->{host1}
1763 or not $sync->{host2}
1764 )
1765{
1766 my $justconnect = justconnect( $sync ) ;
1767
1768 myprint( debugmemory( $sync, " after justconnect() call" ) ) ;
1769 exit_clean( $sync, $EX_OK,
1770 "Exiting after a justconnect on host(s): $justconnect\n"
1771 ) ;
1772}
1773
1774
1775#$sync->{user1} || missing_option( $sync, '--user1' ) ;
1776#$sync->{user2} || missing_option( $sync, '--user2' ) ;
1777
1778$syncinternaldates = defined $syncinternaldates ? $syncinternaldates : 1;
1779
1780# Turn on expunge if there is not explicit option --noexpunge1 and option
1781# --delete1 is given.
1782# Done because --delete1 --noexpunge1 is very dangerous on the second run:
1783# the Deleted flag is then synced to all previously transferred messages.
1784# So --delete1 implies --expunge1 is a better usability default behavior.
1785if ( $sync->{ delete1 } ) {
1786 if ( ! defined $sync->{ expunge1 } ) {
1787 myprint( "Info: turning on --expunge1 because --delete1 --noexpunge1 is very dangerous on the second run.\n" ) ;
1788 $sync->{ expunge1 } = 1 ;
1789 }
1790 myprint( "Info: if expunging after each message slows down too much the sync then use --noexpungeaftereach to speed up\n" ) ;
1791}
1792
1793if ( $sync->{ uidexpunge2 } and not Mail::IMAPClient->can( 'uidexpunge' ) ) {
1794 myprint( "Failure: uidexpunge not supported (IMAPClient release < 3.17), use nothing or --expunge2 instead\n" ) ;
1795 $sync->{nb_errors}++ ;
1796 exit_clean( $sync, $EX_SOFTWARE ) ;
1797}
1798
1799if ( ( $sync->{ delete2 } or $sync->{ delete2duplicates } ) and not defined $sync->{ uidexpunge2 } ) {
1800 if ( Mail::IMAPClient->can( 'uidexpunge' ) ) {
1801 myprint( "Info: will act as --uidexpunge2\n" ) ;
1802 $sync->{ uidexpunge2 } = 1 ;
1803 }elsif ( not defined $sync->{ expunge2 } ) {
1804 myprint( "Info: will act as --expunge2 (no uidexpunge support)\n" ) ;
1805 $sync->{ expunge2 } = 1 ;
1806 }
1807}
1808
1809if ( $sync->{ delete1 } and $sync->{ delete2 } ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001810 myprint( "Warning: using --delete1 and --delete2 together is almost always a bad idea. "
1811 . "You should probably launch two runs, the first with --delete2 for a strict sync, "
1812 . "then the second with --delete1 to remove messages from the source account. "
1813 . "Exiting imapsync.\n" ) ;
1814 $sync->{ nb_errors }++ ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001815 exit_clean( $sync, $EX_USAGE ) ;
1816}
1817
1818if ( $idatefromheader ) {
1819 myprint( 'Turned ON idatefromheader, ',
1820 "will set the internal dates on host2 from the 'Date:' header line.\n" ) ;
1821 $syncinternaldates = 0 ;
1822}
1823
1824if ( $syncinternaldates ) {
1825 myprint( 'Info: turned ON syncinternaldates, ',
1826 "will set the internal dates (arrival dates) on host2 same as host1.\n" ) ;
1827}else{
1828 myprint( "Info: turned OFF syncinternaldates\n" ) ;
1829}
1830
1831if ( defined $authmd5 and $authmd5 ) {
1832 $authmd51 = 1 ;
1833 $authmd52 = 1 ;
1834}
1835
1836if ( defined $authmd51 and $authmd51 ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001837 $acc1->{ authmech } ||= 'CRAM-MD5' ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001838}
1839else{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001840 $acc1->{ authmech } ||= $acc1->{ authuser } ? 'PLAIN' : 'LOGIN' ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001841}
1842
1843if ( defined $authmd52 and $authmd52 ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001844 $acc2->{ authmech } ||= 'CRAM-MD5';
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001845}
1846else{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001847 $acc2->{ authmech } ||= $acc2->{ authuser } ? 'PLAIN' : 'LOGIN';
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001848}
1849
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001850$acc1->{ authmech } = uc $acc1->{ authmech } ;
1851$acc2->{ authmech } = uc $acc2->{ authmech } ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001852
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001853if ( defined $acc1->{ proxyauth } && !$acc1->{ authuser } )
1854{
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001855 missing_option( $sync, 'With --proxyauth1, --authuser1' ) ;
1856}
1857
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001858if ( defined $acc2->{ proxyauth } && !$acc2->{ authuser } )
1859{
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001860 missing_option( $sync, 'With --proxyauth2, --authuser2' ) ;
1861}
1862
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001863myprint( "Host1: will try to use $acc1->{ authmech } authentication on host1\n") ;
1864myprint( "Host2: will try to use $acc2->{ authmech } authentication on host2\n") ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001865
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001866$sync->{ timeout } = defined $sync->{ timeout } ?$sync->{ timeout } : $DEFAULT_TIMEOUT ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001867
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001868$sync->{ acc1 }->{timeout} = defined $sync->{ acc1 }->{timeout} ? $sync->{ acc1 }->{timeout} : $sync->{ timeout } ;
1869myprint( "Host1: imap connection timeout is $sync->{ acc1 }->{timeout} seconds\n") ;
1870$sync->{ acc2 }->{timeout} = defined $sync->{ acc2 }->{timeout} ? $sync->{ acc2 }->{timeout} : $sync->{ timeout } ;
1871myprint( "Host2: imap connection timeout is $sync->{ acc2 }->{timeout} seconds\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001872
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001873
1874keepalive1( $sync ) ;
1875keepalive2( $sync ) ;
1876
1877
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001878if ( under_cgi_context( $sync ) )
1879{
1880 myprint( "Under CGI context, a timeout can occur from the webserver, see https://imapsync.lamiral.info/INSTALL.d/INSTALL.OnlineUI.txt\n" ) ;
1881}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001882
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001883$sync->{ syncacls } = defined $sync->{ syncacls } ? $sync->{ syncacls } : 0 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001884
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001885# No folders sizes at the beginning if --justfolders, unless really wanted.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001886if (
1887 $sync->{ justfolders }
1888 and not defined $sync->{ foldersizes }
1889 and not $sync->{ justfoldersizes } )
1890{
1891 $sync->{ foldersizes } = 0 ;
1892 $sync->{ foldersizesatend } = 1 ;
1893}
1894
1895$sync->{ foldersizes } = ( defined $sync->{ foldersizes } ) ? $sync->{ foldersizes } : 1 ;
1896$sync->{ foldersizesatend } = ( defined $sync->{ foldersizesatend } ) ? $sync->{ foldersizesatend } : $sync->{ foldersizes } ;
1897
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001898#$sync->{ checknoabletosearch } = ( defined $sync->{ checknoabletosearch } ) ? $sync->{ checknoabletosearch } : 1 ;
1899set_checknoabletosearch( $sync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001900
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001901
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001902$acc1->{ fastio } = defined $acc1->{ fastio } ? $acc1->{ fastio } : 0 ;
1903$acc2->{ fastio } = defined $acc2->{ fastio } ? $acc2->{ fastio } : 0 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001904
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001905
1906$acc1->{ reconnectretry } = defined $acc1->{ reconnectretry } ? $acc1->{ reconnectretry } : $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ;
1907$acc2->{ reconnectretry } = defined $acc2->{ reconnectretry } ? $acc2->{ reconnectretry } : $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ;
1908
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001909# IMAP compression on by default
1910#$acc1->{ compress } = defined $acc1->{ compress } ? $acc1->{ compress } : 0 ;
1911#$acc2->{ compress } = defined $acc2->{ compress } ? $acc2->{ compress } : 0 ;
1912
1913
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001914
1915if ( ! @useheader ) { @useheader = qw( Message-Id Received ) ; }
1916
1917# Make a hash %useheader of each --useheader 'key' in uppercase
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001918for ( @useheader ) { $sync->{useheader}->{ uc $_ } = undef } ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001919
1920#myprint( Data::Dumper->Dump( [ \%useheader ] ) ) ;
1921#exit ;
1922
1923myprint( "Host1: IMAP server [$sync->{host1}] port [$sync->{port1}] user [$sync->{user1}]\n" ) ;
1924myprint( "Host2: IMAP server [$sync->{host2}] port [$sync->{port2}] user [$sync->{user2}]\n" ) ;
1925
1926get_password1( $sync ) ;
1927get_password2( $sync ) ;
1928
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001929# --dry1 make imapsync not fetching messages from host1, it is on when --dry is on.
1930# Use --dry --nodry1 to make imapsync fetching messages from host1,
1931# It is useful when debugging transformation options like --pipemess or --regexmess
1932$sync->{dry1} = defined $sync->{dry1} ? $sync->{dry1} : $sync->{dry} ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001933
1934$sync->{dry_message} = q{} ;
1935if( $sync->{dry} ) {
1936 $sync->{dry_message} = "\t(not really since --dry mode)" ;
1937}
1938
1939$sync->{ search1 } ||= $search if ( $search ) ;
1940$sync->{ search2 } ||= $search if ( $search ) ;
1941
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001942if ( $disarmreadreceipts )
1943{
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001944 push @regexmess, q{s{\A((?:[^\n]+\r\n)+|)(^Disposition-Notification-To:[^\n]*\n)(\r?\n|.*\n\r?\n)}{$1X-$2$3}ims} ;
1945}
1946
1947$pipemesscheck = ( defined $pipemesscheck ) ? $pipemesscheck : 1 ;
1948
1949if ( @pipemess and $pipemesscheck ) {
1950 myprint( 'Checking each --pipemess command, '
1951 . join( q{, }, @pipemess )
1952 . ", with an space string. ( Can avoid this check with --nopipemesscheck )\n" ) ;
1953 my $string = pipemess( q{ }, @pipemess ) ;
1954 # string undef means something was bad.
1955 if ( not ( defined $string ) ) {
1956 $sync->{nb_errors}++ ;
1957 exit_clean( $sync, $EX_USAGE,
1958 "Error: one of --pipemess command is bad, check it\n"
1959 ) ;
1960 }
1961 myprint( "Ok with each --pipemess @pipemess\n" ) ;
1962}
1963
1964if ( $maxlinelengthcmd ) {
1965 myprint( "Checking --maxlinelengthcmd command,
1966 $maxlinelengthcmd, with an space string.\n"
1967 ) ;
1968 my $string = pipemess( q{ }, $maxlinelengthcmd ) ;
1969 # string undef means something was bad.
1970 if ( not ( defined $string ) ) {
1971 $sync->{nb_errors}++ ;
1972 exit_clean( $sync, $EX_USAGE,
1973 "Error: --maxlinelengthcmd command is bad, check it\n"
1974 ) ;
1975 }
1976 myprint( "Ok with --maxlinelengthcmd $maxlinelengthcmd\n" ) ;
1977}
1978
1979if ( @regexmess ) {
1980 my $string = regexmess( q{ } ) ;
1981 myprint( "Checking each --regexmess command with an space string.\n" ) ;
1982 # string undef means one of the eval regex was bad.
1983 if ( not ( defined $string ) ) {
1984 #errors_incr( $sync, 'Warning: one of --regexmess option may be bad, check them' ) ;
1985 exit_clean( $sync, $EX_USAGE,
1986 "Error: one of --regexmess option is bad, check it\n"
1987 ) ;
1988 }
1989 myprint( "Ok with each --regexmess\n" ) ;
1990}
1991
1992if ( @skipmess ) {
1993 myprint( "Checking each --skipmess command with an space string.\n" ) ;
1994 my $match = skipmess( q{ } ) ;
1995 # match undef means one of the eval regex was bad.
1996 if ( not ( defined $match ) ) {
1997 $sync->{nb_errors}++ ;
1998 exit_clean( $sync, $EX_USAGE,
1999 "Error: one of --skipmess option is bad, check it\n"
2000 ) ;
2001 }
2002 myprint( "Ok with each --skipmess\n" ) ;
2003}
2004
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002005if ( $sync->{ regexflag } ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002006 myprint( "Checking each --regexflag command with an space string.\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002007 my $string = regexflags( $sync, q{ } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002008 # string undef means one of the eval regex was bad.
2009 if ( not ( defined $string ) ) {
2010 $sync->{nb_errors}++ ;
2011 exit_clean( $sync, $EX_USAGE,
2012 "Error: one of --regexflag option is bad, check it\n"
2013 ) ;
2014 }
2015 myprint( "Ok with each --regexflag\n" ) ;
2016}
2017
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002018$sync->{imap1} = login_imap( $sync->{host1}, $sync->{port1}, $sync->{user1}, $sync->{password1},
2019 $sync->{ssl1}, $sync->{tls1},
2020 $uid1, $split1, $sync->{ acc1 }, $sync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002021
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002022$sync->{imap2} = login_imap( $sync->{host2}, $sync->{port2}, $sync->{user2}, $sync->{password2},
2023 $sync->{ssl2}, $sync->{tls2},
2024 $uid2, $split2, $sync->{ acc2 }, $sync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002025
2026
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002027$sync->{ debug } and $sync->{imap1} and myprint( 'Host1 Buffer I/O: ', $sync->{imap1}->Buffer(), "\n" ) ;
2028$sync->{ debug } and $sync->{imap2} and myprint( 'Host2 Buffer I/O: ', $sync->{imap2}->Buffer(), "\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002029
2030
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002031if ( ! $sync->{imap1} || ! $sync->{imap2} )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002032{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002033 exit_most_errors( $sync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002034}
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002035
2036
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002037myprint( "Host1: state Authenticated\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002038myprint( "Host2: state Authenticated\n" ) ;
2039
2040myprint( 'Host1 capability once authenticated: ', join(q{ }, @{ $sync->{imap1}->capability() || [] }), "\n" ) ;
2041
2042#myprint( Data::Dumper->Dump( [ $sync->{imap1} ] ) ) ;
2043#myprint( "imap4rev1: " . $sync->{imap1}->imap4rev1() . "\n" ) ;
2044
2045myprint( 'Host2 capability once authenticated: ', join(q{ }, @{ $sync->{imap2}->capability() || [] }), "\n" ) ;
2046
2047imap_id_stuff( $sync ) ;
2048
2049#quota( $sync, $sync->{imap1}, 'h1' ) ; # quota on host1 is useless and pollute host2 output.
2050quota( $sync, $sync->{imap2}, 'h2' ) ;
2051
2052maxsize_setting( $sync ) ;
2053
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01002054acc_compress_imap( $acc1 ) ;
2055acc_compress_imap( $acc2 ) ;
2056
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002057if ( $sync->{ justlogin } ) {
2058 $sync->{imap1}->logout( ) ;
2059 $sync->{imap2}->logout( ) ;
2060 exit_clean( $sync, $EX_OK, "Exiting because of --justlogin\n" ) ;
2061}
2062
2063
2064#
2065# Folder stuff
2066#
2067
2068$h1_folders_wanted_nb = 0 ; # counter of folders to be done.
2069$h1_folders_wanted_ct = 0 ; # counter of folders done.
2070
2071# All folders on host1 and host2
2072
2073@h1_folders_all = sort $sync->{imap1}->folders( ) ;
2074@h2_folders_all = sort $sync->{imap2}->folders( ) ;
2075
2076myprint( 'Host1: found ', scalar @h1_folders_all , " folders.\n" ) ;
2077myprint( 'Host2: found ', scalar @h2_folders_all , " folders.\n" ) ;
2078
2079foreach my $f ( @h1_folders_all )
2080{
2081 $h1_folders_all{ $f } = 1
2082}
2083
2084foreach my $f ( @h2_folders_all )
2085{
2086 $h2_folders_all{ $f } = 1 ;
2087 $sync->{h2_folders_all_UPPER}{ uc $f } = 1 ;
2088}
2089
2090$sync->{h1_folders_all} = \%h1_folders_all ;
2091$sync->{h2_folders_all} = \%h2_folders_all ;
2092
2093
2094private_folders_separators_and_prefixes( ) ;
2095
2096
2097# Make a hash of subscribed folders in both servers.
2098
2099for ( $sync->{imap1}->subscribed( ) ) { $h1_subscribed_folder{ $_ } = 1 } ;
2100for ( $sync->{imap2}->subscribed( ) ) { $h2_subscribed_folder{ $_ } = 1 } ;
2101
2102
2103if ( defined $sync->{ subfolder1 } ) {
2104 subfolder1( $sync ) ;
2105}
2106
2107
2108
2109
2110if ( defined $sync->{ subfolder2 } ) {
2111 subfolder2( $sync ) ;
2112}
2113
2114if ( $fixInboxINBOX and ( my $reg = fix_Inbox_INBOX_mapping( \%h1_folders_all, \%h2_folders_all ) ) ) {
2115 push @{ $sync->{ regextrans2 } }, $reg ;
2116}
2117
2118
2119
2120if ( ( $sync->{ folder } and scalar @{ $sync->{ folder } } )
2121 or $subscribed
2122 or scalar @folderrec )
2123{
2124 # folders given by option --folder
2125 if ( $sync->{ folder } and scalar @{ $sync->{ folder } } ) {
2126 add_to_requested_folders( @{ $sync->{ folder } } ) ;
2127 }
2128
2129 # option --subscribed
2130 if ( $subscribed ) {
2131 add_to_requested_folders( keys %h1_subscribed_folder ) ;
2132 }
2133
2134 # option --folderrec
2135 if ( scalar @folderrec ) {
2136 foreach my $folderrec ( @folderrec ) {
2137 add_to_requested_folders( $sync->{imap1}->folders( $folderrec ) ) ;
2138 }
2139 }
2140}
2141else
2142{
2143 # no include, no folder/subscribed/folderrec options => all folders
2144 if ( not scalar @include ) {
2145 myprint( "Including all folders found by default. Use --subscribed or --folder or --folderrec or --include to select specific folders. Use --exclude to unselect specific folders.\n" ) ;
2146 add_to_requested_folders( @h1_folders_all ) ;
2147 }
2148}
2149
2150
2151# consider (optional) includes and excludes
2152if ( scalar @include ) {
2153 foreach my $include ( @include ) {
2154 # No, do not add /x after the regex, never.
2155 # Users would kill you!
2156 my @included_folders = grep { /$include/ } @h1_folders_all ;
2157 add_to_requested_folders( @included_folders ) ;
2158 myprint( "Including folders matching pattern $include\n" . jux_utf8_list( @included_folders ) . "\n" ) ;
2159 }
2160}
2161
2162if ( scalar @exclude ) {
2163 foreach my $exclude ( @exclude ) {
2164 my @requested_folder = sort keys %requested_folder ;
2165 # No, do not add /x after the regex, never.
2166 # Users would kill you!
2167 my @excluded_folders = grep { /$exclude/ } @requested_folder ;
2168 remove_from_requested_folders( @excluded_folders ) ;
2169 myprint( "Excluding folders matching pattern $exclude\n" . jux_utf8_list( @excluded_folders ) . "\n" ) ;
2170 }
2171}
2172
2173
2174# sort before is not very powerful
2175# it adds --folderfirst and --folderlast even if they don't exist on host1
2176#@h1_folders_wanted = sort_requested_folders( ) ;
2177$sync->{h1_folders_wanted} = [ sort_requested_folders( ) ] ;
2178
2179# Remove no selectable folders
2180
2181
2182if ( $sync->{ checkfoldersexist } ) {
2183 my @h1_folders_wanted_exist ;
2184 myprint( "Host1: Checking wanted folders exist. Use --nocheckfoldersexist to avoid this check (shared of public namespace targeted).\n" ) ;
2185 foreach my $folder ( @{ $sync->{h1_folders_wanted} } ) {
2186 ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "Checking $folder exists on host1\n" ) ;
2187 if ( ! exists $h1_folders_all{ $folder } ) {
2188 myprint( "Host1: warning! ignoring folder $folder because it is not in host1 whole folders list.\n" ) ;
2189 next ;
2190 }else{
2191 push @h1_folders_wanted_exist, $folder ;
2192 }
2193 }
2194 @{ $sync->{h1_folders_wanted} } = @h1_folders_wanted_exist ;
2195}else{
2196 myprint( "Host1: Not checking that wanted folders exist. Remove --nocheckfoldersexist to get this check.\n" ) ;
2197}
2198
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002199setcheckselectable( $sync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002200
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002201checkselectable( $sync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002202
2203
2204
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002205# Bugfix OpenFind folders named like "kk \*123" are in fact "kk *123" (no \)
2206#foreach my $folder ( @{ $sync->{ h1_folders_wanted } } )
2207#{
2208# $folder =~ s{ \\\*}{ *}g ;
2209#}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002210
2211
2212# this hack is because LWP post does not pass well a hash in the $form parameter
2213# but it does pass well an array
2214%{ $sync->{f1f2h} } = split_around_equal( @{ $sync->{f1f2} } ) ;
2215
2216automap( $sync ) ;
2217
2218
2219foreach my $h1_fold ( @{ $sync->{h1_folders_wanted} } ) {
2220 my $h2_fold ;
2221 $h2_fold = imap2_folder_name( $sync, $h1_fold ) ;
2222 $h2_folders_from_1_wanted{ $h2_fold }++ ;
2223 if ( 1 < $h2_folders_from_1_wanted{ $h2_fold } ) {
2224 $h2_folders_from_1_several{ $h2_fold }++ ;
2225 }
2226}
2227
2228@h2_folders_from_1_wanted = sort keys %h2_folders_from_1_wanted;
2229
2230
2231foreach my $h1_fold ( @h1_folders_all ) {
2232 my $h2_fold ;
2233 $h2_fold = imap2_folder_name( $sync, $h1_fold ) ;
2234 $h2_folders_from_1_all{ $h2_fold }++ ;
2235 # Follows a fix to avoid deleting folder $sync->{ subfolder2 }
2236 # because it usually does not exist on host1.
2237 if ( $sync->{ subfolder2 } )
2238 {
2239 $h2_folders_from_1_all{ $sync->{ h2_prefix } . $sync->{ subfolder2 } }++ ;
2240 $h2_folders_from_1_all{ $sync->{ subfolder2 } }++ ;
2241 }
2242}
2243
2244
2245
2246myprint( << 'END_LISTING' ) ;
2247
2248++++ Listing folders
2249All foldernames are presented between brackets like [X] where X is the foldername.
2250When a foldername contains non-ASCII characters it is presented in the form
2251[X] = [Y] where
2252X is the imap foldername you have to use in command line options and
2253Y is the utf8 output just printed for convenience, to recognize it.
2254
2255END_LISTING
2256
2257myprint(
2258 "Host1: folders list (first the raw imap format then the [X] = [Y]):\n",
2259 $sync->{imap1}->list( ),
2260 "\n",
2261 jux_utf8_list( @h1_folders_all ),
2262 "\n",
2263 "Host2: folders list (first the raw imap format then the [X] = [Y]):\n",
2264 $sync->{imap2}->list( ),
2265 "\n",
2266 jux_utf8_list( @h2_folders_all ),
2267 "\n",
2268 q{}
2269) ;
2270
2271if ( $subscribed ) {
2272 myprint(
2273 'Host1 subscribed folders list: ',
2274 jux_utf8_list( sort keys %h1_subscribed_folder ), "\n",
2275 ) ;
2276}
2277
2278
2279
2280@h2_folders_not_in_1 = list_folders_in_2_not_in_1( ) ;
2281
2282if ( @h2_folders_not_in_1 ) {
2283 myprint( "Folders in host2 not in host1:\n",
2284 jux_utf8_list( @h2_folders_not_in_1 ), "\n" ) ;
2285}
2286
2287
2288if ( keys %{ $sync->{f1f2auto} } ) {
2289 myprint( "Folders mapping from --automap feature (use --f1f2 to override any mapping):\n" ) ;
2290 foreach my $h1_fold ( keys %{ $sync->{f1f2auto} } ) {
2291 my $h2_fold = $sync->{f1f2auto}{$h1_fold} ;
2292 myprintf( "%-40s -> %-40s\n",
2293 jux_utf8( $h1_fold ), jux_utf8( $h2_fold ) ) ;
2294 }
2295 myprint( "\n" ) ;
2296}
2297
2298if ( keys %{ $sync->{f1f2h} } ) {
2299 myprint( "Folders mapping from --f1f2 options, it overrides --automap:\n" ) ;
2300 foreach my $h1_fold ( keys %{ $sync->{f1f2h} } ) {
2301 my $h2_fold = $sync->{f1f2h}{$h1_fold} ;
2302 my $warn = q{} ;
2303 if ( not exists $h1_folders_all{ $h1_fold } ) {
2304 $warn = "BUT $h1_fold does NOT exist on host1!" ;
2305 }
2306 myprintf( "%-40s -> %-40s %s\n",
2307 jux_utf8( $h1_fold ), jux_utf8( $h2_fold ), $warn ) ;
2308 }
2309 myprint( "\n" ) ;
2310}
2311
2312exit_clean( $sync, $EX_OK, "Exiting because of --justfolderlists\n" ) if ( $sync->{ justfolderlists } ) ;
2313exit_clean( $sync, $EX_OK, "Exiting because of --justautomap\n" ) if ( $sync->{ justautomap } ) ;
2314
2315debugsleep( $sync ) ;
2316
2317if ( $sync->{ skipemptyfolders } )
2318{
2319 myprint( "Host1: will not syncing empty folders on host1. Use --noskipemptyfolders to create them anyway on host2\n") ;
2320}
2321
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002322if ( $sync->{ checknoabletosearch } )
2323{
2324 myprint( "Checking SEARCH ALL works on both accounts. To avoid that check, use --nochecknoabletosearch\n" ) ;
2325 my $check1 = checknoabletosearch( $sync, $sync->{ imap1 }, 'INBOX', 'Host1' ) ;
2326 my $check2 = checknoabletosearch( $sync, $sync->{ imap2 }, 'INBOX', 'Host2' ) ;
2327 if ( $check1 or $check2 )
2328 {
2329 myprint( "At least one account can not SEARCH ALL. So acting like --noabletosearch\n" ) ;
2330 $sync->{abletosearch} = 0 ;
2331 $sync->{abletosearch1} = 0 ;
2332 $sync->{abletosearch2} = 0 ;
2333 }
2334 else
2335 {
2336 myprint( "Good! SEARCH ALL works on both accounts.\n" ) ;
2337 }
2338}
2339
2340
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002341
2342if ( $sync->{ foldersizes } ) {
2343
2344 foldersizes_at_the_beggining( $sync ) ;
2345 #foldersizes_at_the_beggining_old( $sync ) ;
2346}
2347
2348
2349
2350if ( $sync->{ justfoldersizes } )
2351{
2352 exit_clean( $sync, $EX_OK, "Exiting because of --justfoldersizes\n" ) ;
2353}
2354
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002355$sync->{can_do_stats} = 1 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002356
2357if ( $sync->{ delete1emptyfolders } ) {
2358 delete1emptyfolders( $sync ) ;
2359}
2360
2361delete_folders_in_2_not_in_1( ) if $delete2folders ;
2362
2363# folder loop
2364$h1_folders_wanted_nb = scalar @{ $sync->{h1_folders_wanted} } ;
2365
2366myprint( "++++ Looping on each one of $h1_folders_wanted_nb folders to sync\n" ) ;
2367
2368$sync->{begin_transfer_time} = time ;
2369
2370my %uid_candidate_for_deletion ;
2371my %uid_candidate_no_deletion ;
2372
2373$sync->{ h2_folders_of_md5 } = { } ;
2374
2375
2376FOLDER: foreach my $h1_fold ( @{ $sync->{h1_folders_wanted} } )
2377{
2378 $sync->{ h1_current_folder } = $h1_fold ;
2379 eta_print( $sync ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002380 abortifneeded( $sync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002381 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2382
2383 my $h2_fold = imap2_folder_name( $sync, $h1_fold ) ;
2384 $sync->{ h2_current_folder } = $h2_fold ;
2385
2386 $h1_folders_wanted_ct++ ;
2387 myprintf( "Folder %7s %-35s -> %-35s\n", "$h1_folders_wanted_ct/$h1_folders_wanted_nb",
2388 jux_utf8( $h1_fold ), jux_utf8( $h2_fold ) ) ;
2389 myprint( debugmemory( $sync, " at folder loop" ) ) ;
2390
2391 # host1 can not be fetched read only, select is needed because of expunge.
2392 select_folder( $sync, $sync->{imap1}, $h1_fold, 'Host1' ) or next FOLDER ;
2393
2394 debugsleep( $sync ) ;
2395
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002396 my $h1_msgs_all_hash_ref ;
2397 my @h1_msgs ;
2398 my $h1_msgs_nb ;
2399 my $h1_msgs_nb_from_select ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002400
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002401 $h1_msgs_nb_from_select = count_from_select( $sync->{imap1}->History ) ;
2402 myprint( "Host1: folder [$h1_fold] has $h1_msgs_nb_from_select messages in total (mentioned by SELECT)\n" ) ;
2403
2404 if ( $sync->{ skipemptyfolders } and 0 == $h1_msgs_nb_from_select ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002405 myprint( "Host1: skipping empty host1 folder [$h1_fold]\n" ) ;
2406 next FOLDER ;
2407 }
2408
2409 # Code added from https://github.com/imapsync/imapsync/issues/95
2410 # Thanks jh1995
2411 # Goal: do not create folder if --search or --max/minage return 0 message.
2412 # even if there are messages by SELECT (no not real empty, empty for the user point of vue).
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002413 if ( $sync->{ skipemptyfolders } or $sync->{ dry } )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002414 {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002415 $h1_msgs_all_hash_ref = { } ;
2416 @h1_msgs = select_msgs( $sync->{imap1}, $h1_msgs_all_hash_ref, $sync->{ search1 }, $sync->{abletosearch1}, $h1_fold ) ;
2417
2418 $h1_msgs_nb = scalar( @h1_msgs ) ;
2419 if ( 0 == $h1_msgs_nb and $sync->{ skipemptyfolders } ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002420 myprint( "Host1: skipping empty host1 folder [$h1_fold] (0 message found by SEARCH)\n" ) ;
2421 next FOLDER ;
2422 }
2423 }
2424
2425 if ( ! exists $h2_folders_all{ $h2_fold } ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002426 # In --dry mode I could count the messages to be transfered instead of 0
2427 # Messages transferred : 0 (could be 0 without dry mode)
2428 if ( ! create_folder( $sync, $sync->{imap2}, $h2_fold, $h1_fold ) )
2429 {
2430 if ( $sync->{ dry } )
2431 {
2432 $nb_msg_skipped_dry_mode += $h1_msgs_nb ;
2433 }
2434 next FOLDER ;
2435 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002436 }
2437
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002438 acls_sync( $sync, $h1_fold, $h2_fold ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002439
2440 # Sometimes the folder on host2 is listed (it exists) but is
2441 # not selectable but becomes selectable by a create (Gmail)
2442 select_folder( $sync, $sync->{imap2}, $h2_fold, 'Host2' )
2443 or ( create_folder( $sync, $sync->{imap2}, $h2_fold, $h1_fold )
2444 and select_folder( $sync, $sync->{imap2}, $h2_fold, 'Host2' ) )
2445 or next FOLDER ;
2446 my @select_results = $sync->{imap2}->Results( ) ;
2447
2448 my $h2_fold_nb_messages = count_from_select( @select_results ) ;
2449 myprint( "Host2: folder [$h2_fold] has $h2_fold_nb_messages messages in total (mentioned by SELECT)\n" ) ;
2450
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01002451 my $permanentflags2 = permanentflags( $sync, @select_results ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002452 myprint( "Host2: folder [$h2_fold] permanentflags: $permanentflags2\n" ) ;
2453
2454 if ( $sync->{ expunge1 } )
2455 {
2456 myprint( "Host1: Expunging $h1_fold $sync->{dry_message}\n" ) ;
2457 if ( ! $sync->{dry} )
2458 {
2459 $sync->{imap1}->expunge( ) ;
2460 }
2461 }
2462
2463 if ( ( ( $subscribe and exists $h1_subscribed_folder{ $h1_fold } ) or $subscribeall )
2464 and not exists $h2_subscribed_folder{ $h2_fold } )
2465 {
2466 myprint( "Host2: Subscribing to folder $h2_fold\n" ) ;
2467 if ( ! $sync->{dry} ) { $sync->{imap2}->subscribe( $h2_fold ) } ;
2468 }
2469
2470 next FOLDER if ( $sync->{ justfolders } ) ;
2471
2472 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2473
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002474
2475 if ( ! defined $h1_msgs_nb )
2476 {
2477 $h1_msgs_all_hash_ref = { } ;
2478 @h1_msgs = select_msgs( $sync->{imap1}, $h1_msgs_all_hash_ref, $sync->{ search1 }, $sync->{abletosearch1}, $h1_fold );
2479 $h1_msgs_nb = scalar @h1_msgs ;
2480 }else{
2481 # select_msgs already done.
2482 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002483
2484 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2485
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002486 myprint( "Host1: folder [$h1_fold] considering $h1_msgs_nb messages\n" ) ;
2487 ( $sync->{ debug } or $debuglist ) and myprint( "Host1: folder [$h1_fold] considering $h1_msgs_nb messages, LIST gives: @h1_msgs\n" ) ;
2488 $sync->{ debug } and myprint( "Host1: selecting messages of folder [$h1_fold] took ", timenext( $sync ), " s\n" ) ;
2489
2490 my $h2_msgs_all_hash_ref = { } ;
2491 my @h2_msgs = select_msgs( $sync->{imap2}, $h2_msgs_all_hash_ref, $sync->{ search2 }, $sync->{abletosearch2}, $h2_fold ) ;
2492
2493 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2494
2495 my $h2_msgs_nb = scalar @h2_msgs ;
2496
2497 myprint( "Host2: folder [$h2_fold] considering $h2_msgs_nb messages\n" ) ;
2498 ( $sync->{ debug } or $debuglist ) and myprint( "Host2: folder [$h2_fold] considering $h2_msgs_nb messages, LIST gives: @h2_msgs\n" ) ;
2499 $sync->{ debug } and myprint( "Host2: selecting messages of folder [$h2_fold] took ", timenext( $sync ), " s\n" ) ;
2500
2501 my $cache_base = "$sync->{ tmpdir }/imapsync_cache/" ;
2502 my $cache_dir = cache_folder( $cache_base,
2503 "$sync->{host1}/$sync->{user1}/$sync->{host2}/$sync->{user2}", $h1_fold, $h2_fold ) ;
2504 my ( $cache_1_2_ref, $cache_2_1_ref ) = ( {}, {} ) ;
2505
2506 my $h1_uidvalidity = $sync->{imap1}->uidvalidity( ) || q{} ;
2507 my $h2_uidvalidity = $sync->{imap2}->uidvalidity( ) || q{} ;
2508
2509 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2510
2511 if ( $usecache ) {
2512 myprint( "Local cache directory: $cache_dir ( " . length( $cache_dir ) . " characters long )\n" ) ;
2513 mkpath( "$cache_dir" ) ;
2514 ( $cache_1_2_ref, $cache_2_1_ref )
2515 = get_cache( $cache_dir, \@h1_msgs, \@h2_msgs, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) ;
2516 myprint( 'CACHE h1 h2: ', scalar keys %{ $cache_1_2_ref } , " files\n" ) ;
2517 $sync->{ debug } and myprint( '[',
2518 map ( { "$_->$cache_1_2_ref->{$_} " } keys %{ $cache_1_2_ref } ), " ]\n" ) ;
2519 }
2520
2521 my %h1_hash = ( ) ;
2522 my %h2_hash = ( ) ;
2523
2524 my ( %h1_msgs, %h2_msgs ) ;
2525 @h1_msgs{ @h1_msgs } = ( ) ;
2526 @h2_msgs{ @h2_msgs } = ( ) ;
2527
2528 my @h1_msgs_in_cache = sort { $a <=> $b } keys %{ $cache_1_2_ref } ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002529 my @h2_msgs_in_cache = sort { $a <=> $b } keys %{ $cache_2_1_ref } ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002530
2531 my ( %h1_msgs_not_in_cache, %h2_msgs_not_in_cache ) ;
2532 %h1_msgs_not_in_cache = %h1_msgs ;
2533 %h2_msgs_not_in_cache = %h2_msgs ;
2534 delete @h1_msgs_not_in_cache{ @h1_msgs_in_cache } ;
2535 delete @h2_msgs_not_in_cache{ @h2_msgs_in_cache } ;
2536
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002537 my @h1_msgs_not_in_cache = sort { $a <=> $b } keys %h1_msgs_not_in_cache ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002538 #myprint( "h1_msgs_not_in_cache: [@h1_msgs_not_in_cache]\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002539 my @h2_msgs_not_in_cache = sort { $a <=> $b } keys %h2_msgs_not_in_cache ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002540
2541 my @h2_msgs_delete2_not_in_cache = () ;
2542 %h1_msgs_copy_by_uid = ( ) ;
2543
2544 if ( $useuid ) {
2545 # use uid so we have to avoid getting header
2546 @h1_msgs_copy_by_uid{ @h1_msgs_not_in_cache } = ( ) ;
2547 @h2_msgs_delete2_not_in_cache = @h2_msgs_not_in_cache if $usecache ;
2548 @h1_msgs_not_in_cache = ( ) ;
2549 @h2_msgs_not_in_cache = ( ) ;
2550
2551 #myprint( "delete2: @h2_msgs_delete2_not_in_cache\n" ) ;
2552 }
2553
2554 $sync->{ debug } and myprint( "Host1: parsing headers of folder [$h1_fold]\n" ) ;
2555
2556 my ($h1_heads_ref, $h1_fir_ref) = ({}, {});
2557 $h1_heads_ref = $sync->{imap1}->parse_headers([@h1_msgs_not_in_cache], @useheader) if (@h1_msgs_not_in_cache);
2558 $sync->{ debug } and myprint( "Host1: parsing headers of folder [$h1_fold] took ", timenext( $sync ), " s\n" ) ;
2559
2560 @{ $h1_fir_ref }{@h1_msgs} = ( undef ) ;
2561
2562 $sync->{ debug } and myprint( "Host1: getting flags idate and sizes of folder [$h1_fold]\n" ) ;
2563
2564 my @h1_common_fetch_param = ( 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE' ) ;
2565 if ( $sync->{ synclabels } or $sync->{ resynclabels } ) { push @h1_common_fetch_param, 'X-GM-LABELS' ; }
2566
2567 if ( $sync->{abletosearch1} )
2568 {
2569 $h1_fir_ref = $sync->{imap1}->fetch_hash( \@h1_msgs, @h1_common_fetch_param, $h1_fir_ref )
2570 if ( @h1_msgs ) ;
2571 }
2572 else
2573 {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002574 my $fetch_hash_uids = $fetch_hash_set || "1:*" ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002575 $h1_fir_ref = $sync->{imap1}->fetch_hash( $fetch_hash_uids, @h1_common_fetch_param, $h1_fir_ref )
2576 if ( @h1_msgs ) ;
2577 }
2578
2579 $sync->{ debug } and myprint( "Host1: getting flags idate and sizes of folder [$h1_fold] took ", timenext( $sync ), " s\n" ) ;
2580 if ( ! $h1_fir_ref )
2581 {
2582 my $error = join( q{}, "Host1: folder $h1_fold : Could not fetch_hash ",
2583 scalar @h1_msgs, ' msgs: ', $sync->{imap1}->LastError || q{}, "\n" ) ;
2584 errors_incr( $sync, $error ) ;
2585 next FOLDER ;
2586 }
2587
2588 my @h1_msgs_duplicate;
2589 foreach my $m ( @h1_msgs_not_in_cache )
2590 {
2591 my $rc = parse_header_msg( $sync, $sync->{imap1}, $m, $h1_heads_ref, $h1_fir_ref, 'Host1', \%h1_hash ) ;
2592 if ( ! defined $rc )
2593 {
2594 my $h1_size = $h1_fir_ref->{$m}->{'RFC822.SIZE'} || 0;
2595 myprint( "Host1: $h1_fold/$m size $h1_size ignored (no wanted headers so we ignore this message. To solve this: use --addheader)\n" ) ;
2596 $sync->{ total_bytes_skipped } += $h1_size ;
2597 $sync->{ nb_msg_skipped } += 1 ;
2598 $sync->{ h1_nb_msg_noheader } +=1 ;
2599 $sync->{ h1_nb_msg_processed } +=1 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002600 } elsif( 0 == $rc )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002601 {
2602 # duplicate
2603 push @h1_msgs_duplicate, $m;
2604 # duplicate, same id same size?
2605 my $h1_size = $h1_fir_ref->{$m}->{'RFC822.SIZE'} || 0;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002606
2607 $sync->{ acc1 }->{ nb_msg_duplicate } += 1;
2608 if ( ! $sync->{ syncduplicates } ) {
2609 $sync->{ nb_msg_skipped } += 1 ;
2610 $sync->{ h1_nb_msg_processed } +=1 ;
2611 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002612 }
2613 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002614
2615
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002616 my $h1_msgs_duplicate_nb = scalar @h1_msgs_duplicate ;
2617
2618 myprint( "Host1: folder [$h1_fold] selected $h1_msgs_nb messages, duplicates $h1_msgs_duplicate_nb\n" ) ;
2619
2620 $sync->{ debug } and myprint( 'Host1: whole time parsing headers took ', timenext( $sync ), " s\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002621
2622
2623
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002624 # Getting headers and metada can be so long that host2 might be disconnected here
2625 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2626
2627
2628 $sync->{ debug } and myprint( "Host2: parsing headers of folder [$h2_fold]\n" ) ;
2629
2630 my ($h2_heads_ref, $h2_fir_ref) = ( {}, {} );
2631 $h2_heads_ref = $sync->{imap2}->parse_headers([@h2_msgs_not_in_cache], @useheader) if (@h2_msgs_not_in_cache);
2632 $sync->{ debug } and myprint( "Host2: parsing headers of folder [$h2_fold] took ", timenext( $sync ), " s\n" ) ;
2633
2634 $sync->{ debug } and myprint( "Host2: getting flags idate and sizes of folder [$h2_fold]\n" ) ;
2635 @{ $h2_fir_ref }{@h2_msgs} = ( ); # fetch_hash can select by uid with last arg as ref
2636
2637
2638 my @h2_common_fetch_param = ( 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE' ) ;
2639 if ( $sync->{ synclabels } or $sync->{ resynclabels } ) { push @h2_common_fetch_param, 'X-GM-LABELS' ; }
2640
2641 if ( $sync->{abletosearch2} and scalar( @h2_msgs ) ) {
2642 $h2_fir_ref = $sync->{imap2}->fetch_hash( \@h2_msgs, @h2_common_fetch_param, $h2_fir_ref) ;
2643 }else{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002644 my $fetch_hash_uids = $fetch_hash_set || "1:*" ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002645 $h2_fir_ref = $sync->{imap2}->fetch_hash( $fetch_hash_uids, @h2_common_fetch_param, $h2_fir_ref )
2646 if ( @h2_msgs ) ;
2647 }
2648
2649 $sync->{ debug } and myprint( "Host2: getting flags idate and sizes of folder [$h2_fold] took ", timenext( $sync ), " s\n" ) ;
2650
2651 my @h2_msgs_duplicate;
2652 foreach my $m (@h2_msgs_not_in_cache) {
2653 my $rc = parse_header_msg( $sync, $sync->{imap2}, $m, $h2_heads_ref, $h2_fir_ref, 'Host2', \%h2_hash ) ;
2654 my $h2_size = $h2_fir_ref->{$m}->{'RFC822.SIZE'} || 0 ;
2655 if (! defined $rc ) {
2656 myprint( "Host2: $h2_fold/$m size $h2_size ignored (no wanted headers so we ignore this message)\n" ) ;
2657 $h2_nb_msg_noheader += 1 ;
2658 } elsif( 0 == $rc ) {
2659 # duplicate
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002660 $sync->{ acc2 }->{ nb_msg_duplicate } += 1 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002661 push @h2_msgs_duplicate, $m ;
2662 }
2663 }
2664
2665 # %h2_folders_of_md5
2666 foreach my $md5 ( keys %h2_hash ) {
2667 $sync->{ h2_folders_of_md5 }->{ $md5 }->{ $h2_fold } ++ ;
2668 }
2669 # %h1_folders_of_md5
2670 foreach my $md5 ( keys %h1_hash ) {
2671 $sync->{ h1_folders_of_md5 }->{ $md5 }->{ $h2_fold } ++ ;
2672 }
2673
2674
2675 my $h2_msgs_duplicate_nb = scalar @h2_msgs_duplicate ;
2676
2677 myprint( "Host2: folder [$h2_fold] selected $h2_msgs_nb messages, duplicates $h2_msgs_duplicate_nb\n" ) ;
2678
2679 $sync->{ debug } and myprint( 'Host2 whole time parsing headers took ', timenext( $sync ), " s\n" ) ;
2680
2681 $sync->{ debug } and myprint( "++++ Verifying [$h1_fold] -> [$h2_fold]\n" ) ;
2682 # messages in host1 that are not in host2
2683
2684 my @h1_hash_keys_sorted_by_uid
2685 = sort {$h1_hash{$a}{'m'} <=> $h1_hash{$b}{'m'}} keys %h1_hash;
2686
2687 #myprint( map { $h1_hash{$_}{'m'} . q{ }} @h1_hash_keys_sorted_by_uid ) ;
2688
2689 my @h2_hash_keys_sorted_by_uid
2690 = sort {$h2_hash{$a}{'m'} <=> $h2_hash{$b}{'m'}} keys %h2_hash;
2691
2692 # Deletions on account2.
2693
2694 if( $sync->{ delete2duplicates } and not exists $h2_folders_from_1_several{ $h2_fold } ) {
2695 my @h2_expunge ;
2696
2697 foreach my $h2_msg ( @h2_msgs_duplicate ) {
2698 myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted [duplicate] on host2 $sync->{dry_message}\n" ) ;
2699 push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 } ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002700 if ( ! $sync->{ dry } ) {
2701 $sync->{ imap2 }->delete_message( $h2_msg ) ;
2702 $sync->{ acc2 }->{ nb_msg_deleted } += 1 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002703 }
2704 }
2705 my $cnt = scalar @h2_expunge ;
2706 if( @h2_expunge and not $sync->{ expunge2 } ) {
2707 myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n" ) ;
2708 $sync->{imap2}->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ;
2709 }
2710 if ( $sync->{ expunge2 } ){
2711 myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n" ) ;
2712 $sync->{imap2}->expunge( ) if ! $sync->{dry} ;
2713 }
2714 }
2715
2716 if( $sync->{ delete2 } and not exists $h2_folders_from_1_several{ $h2_fold } ) {
2717 # No host1 folders f1a f1b ... going all to same f2 (via --regextrans2)
2718 my @h2_expunge;
2719 foreach my $m_id (@h2_hash_keys_sorted_by_uid) {
2720 #myprint( "$m_id " ) ;
2721 if ( ! exists $h1_hash{$m_id} ) {
2722 my $h2_msg = $h2_hash{$m_id}{'m'};
2723 my $h2_flags = $h2_hash{$m_id}{'F'} || q{};
2724 my $isdel = $h2_flags =~ /\B\\Deleted\b/x ? 1 : 0;
2725 myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted on host2 [$m_id] $sync->{dry_message}\n" )
2726 if ! $isdel;
2727 push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 };
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002728 if ( ! ( $sync->{ dry } or $isdel ) ) {
2729 $sync->{ imap2 }->delete_message( $h2_msg );
2730 $sync->{ acc2 }->{ nb_msg_deleted } += 1;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002731 }
2732 }
2733 }
2734 foreach my $h2_msg ( @h2_msgs_delete2_not_in_cache ) {
2735 myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted [not in cache] on host2 $sync->{dry_message}\n" ) ;
2736 push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 };
2737 if ( ! $sync->{dry} ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002738 $sync->{ imap2 }->delete_message( $h2_msg );
2739 $sync->{ acc2 }->{ nb_msg_deleted } += 1;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002740 }
2741 }
2742 my $cnt = scalar @h2_expunge ;
2743
2744 if( @h2_expunge and not $sync->{ expunge2 } ) {
2745 myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n" ) ;
2746 $sync->{imap2}->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ;
2747 }
2748 if ( $sync->{ expunge2 } ) {
2749 myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n" ) ;
2750 $sync->{imap2}->expunge( ) if ! $sync->{dry} ;
2751 }
2752 }
2753
2754 if( $sync->{ delete2 } and exists $h2_folders_from_1_several{ $h2_fold } ) {
2755 myprint( "Host2: folder $h2_fold $h2_folders_from_1_several{ $h2_fold } folders left to sync there\n" ) ;
2756 my @h2_expunge;
2757 foreach my $m_id ( @h2_hash_keys_sorted_by_uid ) {
2758 my $h2_msg = $h2_hash{ $m_id }{ 'm' } ;
2759 if ( ! exists $h1_hash{ $m_id } ) {
2760 my $h2_flags = $h2_hash{ $m_id }{ 'F' } || q{} ;
2761 my $isdel = $h2_flags =~ /\B\\Deleted\b/x ? 1 : 0 ;
2762 if ( ! $isdel ) {
2763 $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion [$m_id]\n" ) ;
2764 $uid_candidate_for_deletion{ $h2_fold }{ $h2_msg }++ ;
2765 }
2766 }else{
2767 $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [$m_id]\n" ) ;
2768 $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
2769 }
2770 }
2771 foreach my $h2_msg ( @h2_msgs_delete2_not_in_cache ) {
2772 myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion [not in cache]\n" ) ;
2773 $uid_candidate_for_deletion{ $h2_fold }{ $h2_msg }++ ;
2774 }
2775
2776 foreach my $h2_msg ( @h2_msgs_in_cache ) {
2777 myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [in cache]\n" ) ;
2778 $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
2779 }
2780
2781
2782 if ( 0 == $h2_folders_from_1_several{ $h2_fold } ) {
2783 # last host1 folder going to $h2_fold
2784 myprint( "Last host1 folder going to $h2_fold\n" ) ;
2785 foreach my $h2_msg ( keys %{ $uid_candidate_for_deletion{ $h2_fold } } ) {
2786 $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion\n" ) ;
2787 if ( exists $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg } ) {
2788 $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg canceled deletion\n" ) ;
2789 }else{
2790 myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted $sync->{dry_message}\n" ) ;
2791 push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 } ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002792 if ( ! $sync->{ dry} ) {
2793 $sync->{ imap2 }->delete_message( $h2_msg ) ;
2794 $sync->{ acc2 }->{ nb_msg_deleted } += 1 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002795 }
2796 }
2797 }
2798 }
2799
2800 my $cnt = scalar @h2_expunge ;
2801 if( @h2_expunge and not $sync->{ expunge2 } ) {
2802 myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n" ) ;
2803 $sync->{imap2}->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ;
2804 }
2805 if ( $sync->{ expunge2 } ) {
2806 myprint( "Host2: Expunging host2 folder $h2_fold $sync->{dry_message}\n" ) ;
2807 $sync->{imap2}->expunge( ) if ! $sync->{dry} ;
2808 }
2809
2810 $h2_folders_from_1_several{ $h2_fold }-- ;
2811 }
2812
2813 my $h2_uidnext = $sync->{imap2}->uidnext( $h2_fold ) ;
2814 $sync->{ debug } and myprint( "Host2: uidnext is $h2_uidnext\n" ) ;
2815 $h2_uidguess = $h2_uidnext ;
2816
2817 # Getting host2 headers, metada and delete2 stuff can be so long that host1 might be disconnected here
2818 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2819
2820 my @h1_msgs_to_delete ;
2821 MESS: foreach my $m_id (@h1_hash_keys_sorted_by_uid) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002822 abortifneeded( $sync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002823 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2824
2825 #myprint( "h1_nb_msg_processed: $sync->{ h1_nb_msg_processed }\n" ) ;
2826 my $h1_size = $h1_hash{$m_id}{'s'};
2827 my $h1_msg = $h1_hash{$m_id}{'m'};
2828 my $h1_idate = $h1_hash{$m_id}{'D'};
2829
2830 #my $labels = labels( $sync->{imap1}, $h1_msg ) ;
2831 #print "LABELS: $labels\n" ;
2832
2833 if ( ( not exists $h2_hash{ $m_id } )
2834 and ( not ( exists $sync->{ h2_folders_of_md5 }->{ $m_id } )
2835 or not $skipcrossduplicates ) )
2836 {
2837 # copy
2838 my $h2_msg = copy_message( $sync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) ;
2839 if ( $h2_msg and $sync->{ delete1 } and not $sync->{ expungeaftereach } ) {
2840 # not expunged
2841 push @h1_msgs_to_delete, $h1_msg ;
2842 }
2843
2844 # A bug here with imapsync 1.920, fixed in 1.921
2845 # Added $h2_msg in the condition. Errors of APPEND were not counted as missing messages on host2!
2846 if ( $h2_msg and not $sync->{ dry } )
2847 {
2848 $sync->{ h2_folders_of_md5 }->{ $m_id }->{ $h2_fold } ++ ;
2849 }
2850
2851 #
2852 if( $sync->{ delete2 } and ( exists $h2_folders_from_1_several{ $h2_fold } ) and $h2_msg ) {
2853 myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [fresh copy] on host2\n" ) ;
2854 $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
2855 }
2856
2857 if ( total_bytes_max_reached( $sync ) ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002858 # Still a bug when using --delete1 --noexpungeaftereach
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002859 # same thing below on all total_bytes_max_reached!
2860 last FOLDER ;
2861 }
2862 next MESS;
2863 }
2864 else
2865 {
2866 # already on host2
2867 if ( exists $h2_hash{ $m_id } )
2868 {
2869 my $h2_msg = $h2_hash{$m_id}{'m'} ;
2870 $sync->{ debug } and myprint( "Host1: found that msg $h1_fold/$h1_msg equals Host2 $h2_fold/$h2_msg\n" ) ;
2871 if ( $usecache )
2872 {
2873 $debugcache and myprint( "touch $cache_dir/${h1_msg}_$h2_msg\n" ) ;
2874 touch( "$cache_dir/${h1_msg}_$h2_msg" )
2875 or croak( "Couldn't touch $cache_dir/${h1_msg}_$h2_msg" ) ;
2876 }
2877 }
2878 elsif( exists $sync->{ h2_folders_of_md5 }->{ $m_id } )
2879 {
2880 my @folders_dup = keys %{ $sync->{ h2_folders_of_md5 }->{ $m_id } } ;
2881 ( $sync->{ debug } or $debugcrossduplicates ) and myprint( "Host1: found that msg $h1_fold/$h1_msg is also in Host2 folders @folders_dup\n" ) ;
2882 $sync->{ h2_nb_msg_crossdup } +=1 ;
2883 }
2884 $sync->{ total_bytes_skipped } += $h1_size ;
2885 $sync->{ nb_msg_skipped } += 1 ;
2886 $sync->{ h1_nb_msg_processed } +=1 ;
2887 }
2888
2889 if ( exists $h2_hash{ $m_id } ) {
2890 #$debug and myprint( "MESSAGE $m_id\n" ) ;
2891 my $h2_msg = $h2_hash{$m_id}{'m'};
2892 if ( $sync->{resyncflags} ) {
2893 sync_flags_fir( $sync, $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) ;
2894 }
2895 # Good
2896 my $h2_size = $h2_hash{$m_id}{'s'};
2897 $sync->{ debug } and myprint(
2898 "Host1: size msg $h1_fold/$h1_msg = $h1_size <> $h2_size = Host2 $h2_fold/$h2_msg\n" ) ;
2899
2900 if ( $sync->{ resynclabels } )
2901 {
2902 resynclabels( $sync, $h1_msg, $h2_msg, $h1_fir_ref, $h2_fir_ref, $h1_fold )
2903 }
2904 }
2905
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002906 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002907
2908 if ( $sync->{ delete1 } ) {
2909 push @h1_msgs_to_delete, $h1_msg ;
2910 }
2911 }
2912 # END MESS: loop
2913
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002914 # @h1_msgs_in_cache are already synced too.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002915 delete_message_on_host1( $sync, $h1_fold, $sync->{ expunge1 }, @h1_msgs_to_delete, @h1_msgs_in_cache ) ;
2916
2917 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2918
2919 # MESS_IN_CACHE:
2920 if ( ! $sync->{ delete1 } )
2921 {
2922 foreach my $h1_msg ( @h1_msgs_in_cache )
2923 {
2924 my $h2_msg = $cache_1_2_ref->{ $h1_msg } ;
2925 $debugcache and myprint( "cache messages update flags $h1_msg->$h2_msg\n" ) ;
2926 if ( $sync->{resyncflags} )
2927 {
2928 sync_flags_fir( $sync, $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) ;
2929 }
2930 my $h1_size = $h1_fir_ref->{ $h1_msg }->{ 'RFC822.SIZE' } || 0 ;
2931 $sync->{ total_bytes_skipped } += $h1_size;
2932 $sync->{ nb_msg_skipped } += 1;
2933 $sync->{ h1_nb_msg_processed } +=1 ;
2934 }
2935 }
2936
2937 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2938
2939 @h1_msgs_to_delete = ( ) ;
2940 #myprint( "Messages by uid: ", map { "$_ " } keys %h1_msgs_copy_by_uid, "\n" ) ;
2941 # MESS_BY_UID:
2942 foreach my $h1_msg ( sort { $a <=> $b } keys %h1_msgs_copy_by_uid )
2943 {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002944 abortifneeded( $sync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002945 $sync->{ debug } and myprint( "Copy by uid $h1_fold/$h1_msg\n" ) ;
2946 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2947
2948 my $h2_msg = copy_message( $sync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) ;
2949 if( $sync->{ delete2 } and exists $h2_folders_from_1_several{ $h2_fold } and $h2_msg ) {
2950 myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [fresh copy] on host2\n" ) ;
2951 $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
2952 }
2953 last FOLDER if total_bytes_max_reached( $sync ) ;
2954 }
2955
2956 if ( $sync->{ expunge1 } ){
2957 myprint( "Host1: Expunging folder $h1_fold $sync->{dry_message}\n" ) ;
2958 if ( ! $sync->{dry} ) { $sync->{imap1}->expunge( ) } ;
2959 }
2960 if ( $sync->{ expunge2 } ){
2961 myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n" ) ;
2962 if ( ! $sync->{dry} ) { $sync->{imap2}->expunge( ) } ;
2963 }
2964 $sync->{ debug } and myprint( 'Time: ', timenext( $sync ), " s\n" ) ;
2965}
2966
2967eta_print( $sync ) ;
2968
2969myprint( "++++ End looping on each folder\n" ) ;
2970
2971if ( $sync->{ delete1 } and $sync->{ delete1emptyfolders } ) {
2972 delete1emptyfolders( $sync ) ;
2973}
2974
2975( $sync->{ debug } or $sync->{debugfolders} ) and myprint( 'Time: ', timenext( $sync ), " s\n" ) ;
2976
2977
2978if ( $sync->{ foldersizesatend } ) {
2979 myprint( << 'END_SIZE' ) ;
2980
2981Folders sizes after the synchronization.
2982You can remove this foldersizes listing by using "--nofoldersizesatend"
2983END_SIZE
2984
2985 foldersizesatend( $sync ) ;
2986}
2987
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002988#$sync->{imap1}->State( 0 ); # Unconnected
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002989if ( ! lost_connection( $sync, $sync->{imap1}, "for host1 [$sync->{host1}]" ) ) { $sync->{imap1}->logout( ) ; }
2990if ( ! lost_connection( $sync, $sync->{imap2}, "for host2 [$sync->{host2}]" ) ) { $sync->{imap2}->logout( ) ; }
2991
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002992do_and_print_stats( $sync ) ;
2993
2994
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01002995if ( $sync->{ nb_errors } )
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002996{
2997 myprint( errors_listing( $sync ) ) ;
2998}
2999
3000
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003001if ( $sync->{ testslive } or $sync->{ testslive6 } )
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003002{
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003003 tests_live_result( $sync->{ nb_errors } ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003004}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003005
3006
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003007if ( $sync->{ nb_errors } )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003008{
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003009 my $exit_value = exit_value( $sync, $sync->{ most_common_error } ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003010 exit_clean( $sync, $exit_value ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003011}
3012else
3013{
3014 exit_clean( $sync, $EX_OK ) ;
3015}
3016
3017return ;
3018}
3019
3020# END of sub single_sync
3021
3022
3023# subroutines
3024sub myprint
3025{
3026 #print @ARG ;
3027 print { $sync->{ tee } || \*STDOUT } @ARG ;
3028 return ;
3029}
3030
3031sub myprintf
3032{
3033 printf { $sync->{ tee } || \*STDOUT } @ARG ;
3034 return ;
3035}
3036
3037sub mysprintf
3038{
3039 my( $format, @list ) = @ARG ;
3040 return sprintf $format, @list ;
3041}
3042
3043sub output_start
3044{
3045 my $mysync = shift @ARG ;
3046
3047 if ( not $mysync ) { return ; }
3048
3049 my @output = @ARG ;
3050 $mysync->{ output } = join( q{}, @output ) . ( $mysync->{ output } || q{} ) ;
3051 return $mysync->{ output } ;
3052}
3053
3054
3055sub tests_output_start
3056{
3057 note( 'Entering tests_output_start()' ) ;
3058
3059 my $mysync = { } ;
3060
3061 is( undef, output_start( ), 'output_start: no args => undef' ) ;
3062 is( q{}, output_start( $mysync ), 'output_start: one arg => ""' ) ;
3063 is( 'rrrr', output_start( $mysync, 'rrrr' ), 'output_start: rrrr => rrrr' ) ;
3064 is( 'aaaarrrr', output_start( $mysync, 'aaaa' ), 'output_start: aaaa => aaaarrrr' ) ;
3065 is( "\naaaarrrr", output_start( $mysync, "\n" ), 'output_start: \n => \naaaarrrr' ) ;
3066 is( "ABC\naaaarrrr", output_start( $mysync, 'A', 'B', 'C' ), 'output_start: A B C => ABC\naaaarrrr' ) ;
3067
3068 note( 'Leaving tests_output_start()' ) ;
3069 return ;
3070}
3071
3072sub tests_output
3073{
3074 note( 'Entering tests_output()' ) ;
3075
3076 my $mysync = { } ;
3077
3078 is( undef, output( ), 'output: no args => undef' ) ;
3079 is( q{}, output( $mysync ), 'output: one arg => ""' ) ;
3080 is( 'rrrr', output( $mysync, 'rrrr' ), 'output: rrrr => rrrr' ) ;
3081 is( 'rrrraaaa', output( $mysync, 'aaaa' ), 'output: aaaa => rrrraaaa' ) ;
3082 is( "rrrraaaa\n", output( $mysync, "\n" ), 'output: \n => rrrraaaa\n' ) ;
3083 is( "rrrraaaa\nABC", output( $mysync, 'A', 'B', 'C' ), 'output: A B C => rrrraaaaABC\n' ) ;
3084
3085 note( 'Leaving tests_output()' ) ;
3086 return ;
3087}
3088
3089sub output
3090{
3091 my $mysync = shift @ARG ;
3092
3093 if ( not $mysync ) { return ; }
3094
3095 my @output = @ARG ;
3096 $mysync->{ output } .= join( q{}, @output ) ;
3097 return $mysync->{ output } ;
3098}
3099
3100
3101
3102sub tests_output_reset_with
3103{
3104 note( 'Entering tests_output_reset_with()' ) ;
3105
3106 my $mysync = { } ;
3107
3108 is( undef, output_reset_with( ), 'output_reset_with: no args => undef' ) ;
3109 is( q{}, output_reset_with( $mysync ), 'output_reset_with: one arg => ""' ) ;
3110 is( 'rrrr', output_reset_with( $mysync, 'rrrr' ), 'output_reset_with: rrrr => rrrr' ) ;
3111 is( 'aaaa', output_reset_with( $mysync, 'aaaa' ), 'output_reset_with: aaaa => aaaa' ) ;
3112 is( "\n", output_reset_with( $mysync, "\n" ), 'output_reset_with: \n => \n' ) ;
3113
3114 note( 'Leaving tests_output_reset_with()' ) ;
3115 return ;
3116}
3117
3118sub output_reset_with
3119{
3120 my $mysync = shift @ARG ;
3121
3122 if ( not $mysync ) { return ; }
3123
3124 my @output = @ARG ;
3125 $mysync->{ output } = join( q{}, @output ) ;
3126 return $mysync->{ output } ;
3127}
3128
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003129
3130sub tests_print_output_if_needed
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003131{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003132 note( 'Entering tests_print_output_if_needed()' ) ;
3133
3134 is( undef, print_output_if_needed( ), 'print_output_if_needed: no args => undef' ) ;
3135 my $mysync = { } ;
3136 is( q{}, print_output_if_needed( $mysync ), 'print_output_if_needed: undef => undef' ) ;
3137
3138 output( $mysync, "Hello\n" ) ;
3139 is( "Hello\n", print_output_if_needed( $mysync ), 'print_output_if_needed: Hello => Hello' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003140
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003141 $mysync->{ dockercontext } = 1 ;
3142 is( "Hello\n", print_output_if_needed( $mysync ), 'print_output_if_needed: dockercontext + Hello => Hello' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003143
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003144 $mysync->{ version } = 1 ;
3145 is( q{}, print_output_if_needed( $mysync ), 'print_output_if_needed: dockercontext + Hello + --version => ""' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003146
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003147 $mysync->{ dockercontext } = 0 ;
3148 is( "Hello\n", print_output_if_needed( $mysync ), 'print_output_if_needed: Hello + --version => Hello' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003149
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003150 note( 'Leaving tests_print_output_if_needed()' ) ;
3151 return ;
3152}
3153
3154
3155sub print_output_if_needed
3156{
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003157
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003158 my $mysync = shift @ARG ;
3159 if ( ! defined $mysync ) { return ; }
3160 my $output = output( $mysync ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003161
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003162 if ( $mysync->{ version } && under_docker_context( $mysync ) )
3163 {
3164 return q{} ;
3165 }
3166 else
3167 {
3168 myprint( $output ) ;
3169 return $output ;
3170 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003171
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003172}
3173
3174
3175
3176sub define_pidfile
3177{
3178 my $mysync = shift @ARG ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003179
3180 $mysync->{ pidfilelocking } = defined $mysync->{ pidfilelocking } ? $mysync->{ pidfilelocking } : 0 ;
3181
3182 my $host1 = $mysync->{ host1 } || q{} ;
3183 my $user1 = $mysync->{ user1 } || q{} ;
3184 my $host2 = $mysync->{ host2 } || q{} ;
3185 my $user2 = $mysync->{ user2 } || q{} ;
3186
3187 my $account1_filtered = filter_forbidden_characters( slash_to_underscore( $host1 . '_' . $user1 ) ) || q{} ;
3188 my $account2_filtered = filter_forbidden_characters( slash_to_underscore( $host2 . '_' . $user2 ) ) || q{} ;
3189
3190 my $pidfile_basename ;
3191
3192 if ( $ENV{ 'NET_SERVER_SOFTWARE' } and ( $ENV{ 'NET_SERVER_SOFTWARE' } =~ /Net::Server::HTTP/ ) )
3193 {
3194 # under local webserver
3195 $pidfile_basename = 'imapsync' . '_' . $account1_filtered . '_' . $account2_filtered . '.pid' ;
3196 }
3197 else
3198 {
3199 $pidfile_basename = 'imapsync.pid' ;
3200 }
3201
3202 $mysync->{ pidfile } = defined $mysync->{ pidfile } ? $mysync-> { pidfile } : $mysync->{ tmpdir } . "/$pidfile_basename" ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003203 $mysync->{ abortfile } = abortfile( $mysync, $PROCESS_ID ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003204 return ;
3205}
3206
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003207sub abortfile
3208{
3209 my $mysync = shift @ARG ;
3210 my $pid = shift @ARG ;
3211
3212 my $abortfile ;
3213 if ( $mysync->{ abort } )
3214 {
3215 $abortfile = $mysync->{ pidfile } . "abort$pid" ;
3216 }
3217 else
3218 {
3219 $abortfile = $mysync->{ pidfile } . "abort$PROCESS_ID" ;
3220 }
3221 return $abortfile ;
3222}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003223
3224sub tests_kill_zero
3225{
3226 note( 'Entering tests_kill_zero()' ) ;
3227
3228
3229
3230 SKIP: {
3231 if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests tests_kill_zero avoided on Windows', 8 ) ; }
3232
3233
3234 is( 1, kill( 'ZERO', $PROCESS_ID ), "kill ZERO : myself $PROCESS_ID => 1" ) ;
3235 is( 2, kill( 'ZERO', $PROCESS_ID, $PROCESS_ID ), "kill ZERO : myself $PROCESS_ID $PROCESS_ID => 2" ) ;
3236
3237 if ( (-e '/.dockerenv' ) or ( 0 == $EFFECTIVE_USER_ID) )
3238 {
3239 is( 1, kill( 'ZERO', 1 ), "kill ZERO : pid 1 => 1 (docker context or root)" ) ;
3240 is( 2, kill( 'ZERO', $PROCESS_ID, 1 ), "kill ZERO : myself + pid 1, $PROCESS_ID 1 => 2 (docker context or root)" ) ;
3241 }
3242 else
3243 {
3244 is( 0, kill( 'ZERO', 1 ), "kill ZERO : pid 1 => 0 (non root)" ) ;
3245 is( 1, kill( 'ZERO', $PROCESS_ID, 1 ), "kill ZERO : myself + pid 1, $PROCESS_ID 1 => 1 (one is non root)" ) ;
3246
3247 }
3248
3249
3250 my $pid_1 = fork( ) ;
3251 if ( $pid_1 )
3252 {
3253 # parent
3254 }
3255 else
3256 {
3257 # child
3258 sleep 3 ;
3259 exit ;
3260 }
3261
3262 my $pid_2 ;
3263 $pid_2 = fork( ) ;
3264 if ( $pid_2 )
3265 {
3266 # I am the parent
3267 ok( defined( $pid_2 ), "kill_zero: initial fork ok. I am the parent $PROCESS_ID" ) ;
3268 ok( $pid_2 , "kill_zero: initial fork ok, child pid is $pid_2" ) ;
3269 is( 3, kill( 'ZERO', $PROCESS_ID, $pid_2, $pid_1 ), "kill ZERO : myself $PROCESS_ID and child $pid_2 and brother $pid_1 => 3" ) ;
3270
3271 is( $pid_2, waitpid( $pid_2, 0 ), "kill_zero: child $pid_2 no more there => waitpid return $pid_2" ) ;
3272 }
3273 else
3274 {
3275 # I am the child
3276 note( 'This one fails under Windows, kill ZERO returns 0 instead of 2' ) ;
3277 is( 2, kill( 'ZERO', $PROCESS_ID, $pid_1 ), "kill ZERO : myself child $PROCESS_ID brother $pid_1 => 2" ) ;
3278 myprint( "I am the child pid $PROCESS_ID, Exiting\n" ) ;
3279 exit ;
3280 }
3281 wait( ) ;
3282
3283 # End of SKIP block
3284 }
3285
3286 note( 'Leaving tests_kill_zero()' ) ;
3287 return ;
3288}
3289
3290
3291
3292
3293sub tests_killpid_by_parent
3294{
3295 note( 'Entering tests_killpid_by_parent()' ) ;
3296
3297 SKIP: {
3298 if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests tests_killpid_by_parent avoided on Windows', 7 ) ; }
3299
3300 is( undef, killpid( ), 'killpid: no args => undef' ) ;
3301 note( "killpid: trying to kill myself pid $PROCESS_ID, hope I will not succeed" ) ;
3302 is( undef, killpid( $PROCESS_ID ), 'killpid: myself => undef' ) ;
3303
3304 local $SIG{'QUIT'} = sub { myprint "GOT SIG QUIT! I am PID $PROCESS_ID. Exiting\n" ; exit ; } ;
3305
3306 my $pid ;
3307 $pid = fork( ) ;
3308 if ( $pid )
3309 {
3310 # I am the parent
3311 ok( defined( $pid ), "killpid: initial fork ok. I am the parent $PROCESS_ID" ) ;
3312 ok( $pid , "killpid: initial fork ok, child pid is $pid" ) ;
3313
3314 is( 2, kill( 'ZERO', $PROCESS_ID, $pid ), "kill ZERO : myself $PROCESS_ID and child $pid => 2" ) ;
3315 is( 1, killpid( $pid ), "killpid: child $pid killed => 1" ) ;
3316 is( -1, waitpid( $pid, 0 ), "killpid: child $pid no more there => waitpid return -1" ) ;
3317 }
3318 else
3319 {
3320 # I am the child
3321 myprint( "I am the child pid $PROCESS_ID, sleeping 1 + 3 seconds then kill myself\n" ) ;
3322 sleep 1 ;
3323 myprint( "I am the child pid $PROCESS_ID, slept 1 second, should be killed by my parent now, PPID " . mygetppid( ) . "\n" ) ;
3324 sleep 3 ;
3325 # this test should not be run. If it happens => failure.
3326 ok( 0 == 1, "killpid: child pid $PROCESS_ID not dead => failure" ) ;
3327 myprint( "I am the child pid $PROCESS_ID, killing myself failure... Exiting\n" ) ;
3328 exit ;
3329 }
3330
3331 # End of SKIP block
3332 }
3333 note( 'Leaving tests_killpid_by_parent()' ) ;
3334 return ;
3335}
3336
3337sub tests_killpid_by_brother
3338{
3339 note( 'Entering tests_killpid_by_brother()' ) ;
3340
3341
3342 SKIP: {
3343 if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests tests_killpid_by_brother avoided on Windows', 2 ) ; }
3344
3345 local $SIG{'QUIT'} = sub { myprint "GOT SIG QUIT! I am PID $PROCESS_ID. Exiting\n" ; exit ; } ;
3346
3347 my $pid_parent = $PROCESS_ID ;
3348 myprint( "I am the parent pid $pid_parent\n" ) ;
3349 my $pid_1 = fork( ) ;
3350 if ( $pid_1 )
3351 {
3352 # parent
3353 }
3354 else
3355 {
3356 # child
3357 #while ( 1 ) { } ;
3358 sleep 2 ;
3359 sleep 2 ;
3360 # this test should not be run. If it happens => failure.
3361 # Well under Windows this always fails, shit!
3362 ok( 0 == 1 or ( 'MSWin32' eq $OSNAME ) , "killpid: child pid $PROCESS_ID killing by brother but not dead => failure" ) ;
3363 myprint( "I am the child pid $PROCESS_ID, killing by brother failed... Exiting\n" ) ;
3364 exit ;
3365 }
3366
3367 my $pid_2 ;
3368 $pid_2 = fork( ) ;
3369 if ( $pid_2 )
3370 {
3371 # parent
3372 }
3373 else
3374 {
3375 # I am the child
3376 myprint( "I am the child pid $PROCESS_ID, my brother has pid $pid_1\n" ) ;
3377 is( 1, killpid( $pid_1 ), "killpid: brother $pid_1 killed => 1" ) ;
3378 sleep 2 ;
3379 exit ;
3380 }
3381
3382 #sleep 1 ;
3383 is( $pid_1, waitpid( $pid_1, 0), "I am the parent $PROCESS_ID waitpid _1( $pid_1 )" ) ;
3384 is( $pid_2, waitpid( $pid_2, 0 ), "I am the parent $PROCESS_ID waitpid _2( $pid_2 )" ) ;
3385
3386
3387 # End of SKIP block
3388 }
3389
3390 note( 'Leaving tests_killpid_by_brother()' ) ;
3391 return ;
3392}
3393
3394
3395sub killpid
3396{
3397 my $pidtokill = shift ;
3398
3399 if ( ! $pidtokill ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003400 myprint( "No process to kill.\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003401 return ;
3402 }
3403
3404 if ( $PROCESS_ID == $pidtokill ) {
3405 myprint( "I will not kill myself pid $PROCESS_ID via killpid. Sractch it!\n" ) ;
3406 return ;
3407 }
3408
3409
3410 # First ask for suicide
3411 if ( kill( 'ZERO', $pidtokill ) or ( 'MSWin32' eq $OSNAME ) ) {
3412 myprint( "Sending signal QUIT to PID $pidtokill \n" ) ;
3413 kill 'QUIT', $pidtokill ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003414 sleep 3 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003415 waitpid( $pidtokill, WNOHANG) ;
3416 }else{
3417 myprint( "Can not send signal kill ZERO to PID $pidtokill.\n" ) ;
3418 return ;
3419 }
3420
3421 #while ( waitpid( $pidtokill, WNOHANG) > 0 ) { } ;
3422
3423 # Then murder
3424 if ( kill( 'ZERO', $pidtokill ) or ( 'MSWin32' eq $OSNAME ) ) {
3425 myprint( "Sending signal KILL to PID $pidtokill \n" ) ;
3426 kill 'KILL', $pidtokill ;
3427 sleep 1 ;
3428 waitpid( $pidtokill, WNOHANG) ;
3429 }else{
3430 myprint( "Process PID $pidtokill ended.\n" ) ;
3431 return 1;
3432 }
3433 # Well ...
3434 if ( kill( 'ZERO', $pidtokill ) or ( 'xMSWin32' eq $OSNAME ) ) {
3435 myprint( "Process PID $pidtokill seems still there. Can not do much.\n" ) ;
3436 return ;
3437 }else{
3438 myprint( "Process PID $pidtokill ended.\n" ) ;
3439 return 1;
3440 }
3441
3442 return ;
3443}
3444
3445sub tests_abort
3446{
3447 note( 'Entering tests_abort()' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003448 # Well, the abort behavior is tested by test.sh
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003449 is( undef, abort( ), 'abort: no args => undef' ) ;
3450 note( 'Leaving tests_abort()' ) ;
3451 return ;
3452}
3453
3454
3455
3456
3457sub abort
3458{
3459 my $mysync = shift @ARG ;
3460
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003461 myprint( "In abort\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003462 if ( not $mysync ) { return ; }
3463
3464 if ( ! -r $mysync->{pidfile} ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003465 myprint( "In abort: Can not read pidfile $mysync->{pidfile}\n" ) ;
3466 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003467 }
3468 my $pidtokill = firstline( $mysync->{pidfile} ) ;
3469 if ( ! $pidtokill ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003470 myprint( "In abort: No process to abort in $mysync->{pidfile}\n" ) ;
3471 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003472 }
3473
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003474 if ( ! match_a_pid_number( $pidtokill ) )
3475 {
3476 myprint( "In abort: pid $pidtokill in $mysync->{pidfile} is not a pid number\n" ) ;
3477 return ;
3478 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003479
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003480
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003481 if ( $mysync->{abortbyfile} )
3482 {
3483 abortbyfile( $mysync, $pidtokill ) ;
3484 }
3485 else
3486 {
3487 killpid( $pidtokill ) ;
3488 }
3489 return ;
3490}
3491
3492sub abortbyfile
3493{
3494 my $mysync = shift @ARG ;
3495 my $pidtokill = shift @ARG ;
3496
3497 my $abortfile = abortfile( $mysync, $pidtokill ) ;
3498 myprint( "touching $abortfile\n" ) ;
3499 touch( $abortfile ) ;
3500 return ;
3501}
3502
3503
3504sub tests_under_docker_context
3505{
3506 note( 'Entering tests_under_docker_context()' ) ;
3507
3508 is( undef, under_docker_context( ), 'under_docker_context: no args => undef' ) ;
3509
3510 my $mysync = { } ;
3511 $mysync->{ dockercontext } = 1 ;
3512 is( 1, under_docker_context( $mysync ), 'under_docker_context: --dockercontext => 1' ) ;
3513 $mysync->{ dockercontext } = 0 ;
3514 is( 0, under_docker_context( $mysync ), 'under_docker_context: --nodockercontext => 0' ) ;
3515
3516 $mysync = { } ;
3517 # Is not it a stupid test?
3518 if ( under_docker_context( $mysync ) )
3519 {
3520 is( 1, under_docker_context( $mysync ), 'under_docker_context: docker context => 1' ) ;
3521 }
3522 else
3523 {
3524 is( 0, under_docker_context( $mysync ), 'under_docker_context: not docker context => 0' ) ;
3525 }
3526
3527 note( 'Leaving tests_under_docker_context()' ) ;
3528 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003529}
3530
3531
3532sub under_docker_context
3533{
3534 my $mysync = shift ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003535
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003536 if ( ! defined $mysync ) { return ; }
3537
3538 if ( defined $mysync->{ dockercontext } )
3539 {
3540 return( $mysync->{ dockercontext } ) ;
3541 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003542
3543 if ( -e '/.dockerenv' )
3544 {
3545 return 1 ;
3546 }
3547 else
3548 {
3549 return 0 ;
3550 }
3551
3552 return ;
3553}
3554
3555
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003556sub docker_context
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003557{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003558 my $mysync = shift ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003559
3560 if ( ! under_docker_context( $mysync ) )
3561 {
3562 return ;
3563 }
3564
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003565 output( $mysync, "Docker context detected with the file /.dockerenv\n" ) ;
3566 # No pidfile by default
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003567
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003568 $mysync->{ pidfile } = defined( $mysync->{ pidfile } ) ? $mysync->{ pidfile } : q{} ;
3569 # No log by default
3570 if ( defined( $mysync->{ log } ) )
3571 {
3572 output( $mysync, "Logging in Docker context. Be sure you added access to it with a mount or similar. See https://docs.docker.com/storage/volumes/\n" ) ;
3573 }
3574 else
3575 {
3576 output( $mysync, "No log by default in Docker context. Use --log to trigger logging to the logfile.\n" ) ;
3577 $mysync->{ log } = 0 ;
3578 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003579
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003580 # In case something is written relatively to .
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003581 my $tmp_dir = "/var/tmp/uid_$EFFECTIVE_USER_ID" ;
3582 mkpath( $tmp_dir ) ; # silly? No. it is for imapsync --version being ok.
3583 do_valid_directory( $tmp_dir ) ;
3584 output( $mysync, "Changing current directory to $tmp_dir\n" ) ;
3585 chdir $tmp_dir ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003586
3587 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003588}
3589
3590sub cgibegin
3591{
3592 my $mysync = shift ;
3593 if ( ! under_cgi_context( $mysync ) ) { return ; }
3594 require CGI ;
3595 CGI->import( qw( -no_debug -utf8 ) ) ;
3596 require CGI::Carp ;
3597 CGI::Carp->import( qw( fatalsToBrowser ) ) ;
3598 $mysync->{cgi} = CGI->new( ) ;
3599 return ;
3600}
3601
3602sub tests_under_cgi_context
3603{
3604 note( 'Entering tests_under_cgi_context()' ) ;
3605
3606 # $ENV{SERVER_SOFTWARE} = 'under imapsync' ;
3607 do {
3608 # Not in cgi context
3609 delete local $ENV{SERVER_SOFTWARE} ;
3610 is( undef, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE unset => not in cgi context' ) ;
3611 } ;
3612 do {
3613 # In cgi context
3614 local $ENV{SERVER_SOFTWARE} = 'under imapsync' ;
3615 is( 1, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE set => in cgi context' ) ;
3616 } ;
3617 do {
3618 # Not in cgi context
3619 delete local $ENV{SERVER_SOFTWARE} ;
3620 is( undef, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE unset => not in cgi context' ) ;
3621 } ;
3622 do {
3623 # In cgi context
3624 local $ENV{SERVER_SOFTWARE} = 'under imapsync' ;
3625 is( 1, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE set => in cgi context' ) ;
3626 } ;
3627 note( 'Leaving tests_under_cgi_context()' ) ;
3628 return ;
3629}
3630
3631
3632sub under_cgi_context
3633{
3634 my $mysync = shift ;
3635 # Under cgi context
3636 if ( $ENV{SERVER_SOFTWARE} ) {
3637 return 1 ;
3638 }
3639 # Not in cgi context
3640 return ;
3641}
3642
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003643sub cgibuildheader
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003644{
3645 my $mysync = shift ;
3646 if ( ! under_cgi_context( $mysync ) ) { return ; }
3647
3648 my $imapsync_runs = $mysync->{cgi}->cookie( 'imapsync_runs' ) || 0 ;
3649 my $cookie = $mysync->{cgi}->cookie(
3650 -name => 'imapsync_runs',
3651 -value => 1 + $imapsync_runs,
3652 -expires => '+20y',
3653 -path => '/cgi-bin/imapsync',
3654 ) ;
3655 my $httpheader ;
3656 if ( $mysync->{ abort } ) {
3657 $httpheader = $mysync->{cgi}->header(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003658 -type => 'text/plain; charset=UTF-8',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003659 -status => '200 OK to abort syncing IMAP boxes' . ". Here is " . hostname(),
3660 ) ;
3661 }elsif( $mysync->{ loaddelay } ) {
3662# https://tools.ietf.org/html/rfc2616#section-10.5.4
3663# 503 Service Unavailable
3664# The server is currently unable to handle the request due to a temporary overloading or maintenance of the server.
3665 $httpheader = $mysync->{cgi}->header(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003666 -type => 'text/plain; charset=UTF-8',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003667 -status => '503 Service Unavailable' . ". Be back in $mysync->{ loaddelay } min. Load on " . hostname() . " is $mysync->{ loadavg }",
3668 ) ;
3669 }else{
3670 $httpheader = $mysync->{cgi}->header(
3671 -type => 'text/plain; charset=UTF-8',
3672 -status => '200 OK to sync IMAP boxes' . ". Load on " . hostname() . " is $mysync->{ loadavg }",
3673 -cookie => $cookie,
3674 ) ;
3675 }
3676 output_start( $mysync, $httpheader ) ;
3677
3678 return ;
3679}
3680
3681sub cgiload
3682{
3683 # Exit on heavy load in CGI context
3684 my $mysync = shift ;
3685 if ( ! under_cgi_context( $mysync ) ) { return ; }
3686 if ( $mysync->{ abort } ) { return ; } # keep going to abort since some ressources will be free soon
3687 if ( $mysync->{ loaddelay } )
3688 {
3689 $mysync->{nb_errors}++ ;
3690 exit_clean( $mysync, $EX_UNAVAILABLE,
3691 "Server is on heavy load. Be back in $mysync->{ loaddelay } min. Load is $mysync->{ loadavg }\n"
3692 ) ;
3693 }
3694 return ;
3695}
3696
3697sub tests_set_umask
3698{
3699 note( 'Entering tests_set_umask()' ) ;
3700
3701 my $save_umask = umask ;
3702
3703 my $mysync = {} ;
3704 if ( 'MSWin32' eq $OSNAME ) {
3705 is( undef, set_umask( $mysync ), "set_umask: set failure to $UMASK_PARANO on MSWin32" ) ;
3706 }else{
3707 is( 1, set_umask( $mysync ), "set_umask: set to $UMASK_PARANO" ) ;
3708 }
3709
3710 umask $save_umask ;
3711 note( 'Leaving tests_set_umask()' ) ;
3712 return ;
3713}
3714
3715sub set_umask
3716{
3717 my $mysync = shift ;
3718 my $previous_umask = umask_str( ) ;
3719 my $new_umask = umask_str( $UMASK_PARANO ) ;
3720 output( $mysync, "Umask set with $new_umask (was $previous_umask)\n" ) ;
3721 if ( $new_umask eq $UMASK_PARANO ) {
3722 return 1 ;
3723 }
3724 return ;
3725}
3726
3727sub tests_umask_str
3728{
3729 note( 'Entering tests_umask_str()' ) ;
3730
3731 my $save_umask = umask ;
3732
3733 is( umask_str( ), umask_str( ), 'umask_str: no parameters => idopotent' ) ;
3734 is( my $save_umask_str = umask_str( ), umask_str( ), 'umask_str: no parameters => idopotent + save' ) ;
3735 is( '0000', umask_str( q{ } ), 'umask_str: q{ } => 0000' ) ;
3736 is( '0000', umask_str( q{} ), 'umask_str: q{} => 0000' ) ;
3737 is( '0000', umask_str( '0000' ), 'umask_str: 0000 => 0000' ) ;
3738 is( '0000', umask_str( '0' ), 'umask_str: 0 => 0000' ) ;
3739 is( '0200', umask_str( '0200' ), 'umask_str: 0200 => 0200' ) ;
3740 is( '0400', umask_str( '0400' ), 'umask_str: 0400 => 0400' ) ;
3741 is( '0600', umask_str( '0600' ), 'umask_str: 0600 => 0600' ) ;
3742
3743 SKIP: {
3744 if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests success only for Unix', 6 ) ; }
3745 is( '0100', umask_str( '0100' ), 'umask_str: 0100 => 0100' ) ;
3746 is( '0001', umask_str( '0001' ), 'umask_str: 0001 => 0001' ) ;
3747 is( '0777', umask_str( '0777' ), 'umask_str: 0777 => 0777' ) ;
3748 is( '0777', umask_str( '00777' ), 'umask_str: 00777 => 0777' ) ;
3749 is( '0777', umask_str( ' 777 ' ), 'umask_str: 777 => 0777' ) ;
3750 is( "$UMASK_PARANO", umask_str( $UMASK_PARANO ), "umask_str: UMASK_PARANO $UMASK_PARANO => $UMASK_PARANO" ) ;
3751 }
3752
3753 is( $save_umask_str, umask_str( $save_umask_str ), 'umask_str: restore with str' ) ;
3754 is( $save_umask, umask, 'umask_str: umask is restored, controlled by direct umask' ) ;
3755 is( $save_umask, umask $save_umask, 'umask_str: umask is restored by direct umask' ) ;
3756 is( $save_umask, umask, 'umask_str: umask initial controlled by direct umask' ) ;
3757
3758 note( 'Leaving tests_umask_str()' ) ;
3759 return ;
3760}
3761
3762sub umask_str
3763{
3764 my $value = shift ;
3765
3766 if ( defined $value ) {
3767 umask oct( $value ) ;
3768 }
3769 my $current = umask ;
3770
3771 return( sprintf( '%#04o', $current ) ) ;
3772}
3773
3774sub tests_umask
3775{
3776 note( 'Entering tests_umask()' ) ;
3777
3778 my $save_umask ;
3779 is( umask, umask, 'umask: umask is umask' ) ;
3780 is( $save_umask = umask, umask, "umask: umask is umask again + save it: $save_umask" ) ;
3781 is( $save_umask, umask oct(0000), 'umask: umask 0000' ) ;
3782 is( oct(0000), umask, 'umask: umask is now 0000' ) ;
3783 is( oct(0000), umask oct(777), 'umask: umask 0777 call, previous 0000' ) ;
3784
3785 SKIP: {
3786 if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests success only for Unix', 2 ) ; }
3787 is( oct(777), umask, 'umask: umask is now 0777' ) ;
3788 is( oct(777), umask $save_umask, "umask: umask $save_umask restore inital value, previous 0777" ) ;
3789 }
3790
3791 ok( defined umask $save_umask, "umask: umask $save_umask restore inital value, previous defined" ) ;
3792 is( $save_umask, umask, 'umask: umask is umask restored' ) ;
3793 note( 'Leaving tests_umask()' ) ;
3794
3795 return ;
3796}
3797
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003798sub buggyflagsregex
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003799{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003800 # From /X analyse
3801 # cut -d: -f1 Error_112_all_syncs.txt | xargs egrep -oih 'Invalid system flag [^( ]+' | sort | uniq -c | sort -g
3802 my @buggyflagsregex = ( 's/\\\\RECEIPTCHECKED|\\\\Indexed|\\\\X-EON-HAS-ATTACHMENT|\\\\UNSEEN|\\\\ATTACHED|\\\\X-HAS-ATTACH|\\\\FORWARDED|\\\\FORWARD|\\\\X-FORWARDED|\\\\\$FORWARDED|\\\\PRIORITY|\\\\READRCPT//g' ) ;
3803 return( @buggyflagsregex ) ;
3804}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003805
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003806sub cgisetcontext
3807{
3808 my $mysync = shift ;
3809 if ( ! under_cgi_context( $mysync ) ) { return ; }
3810
3811 output( $mysync, "Under cgi context\n" ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003812
3813
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003814 set_umask( $mysync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003815
3816 # Remove all content in unsafe evaled options
3817 @{ $mysync->{ regextrans2 } } = ( ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003818
3819 @{ $mysync->{ regexflag } } = buggyflagsregex( ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003820
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003821 @regexmess = ( ) ;
3822 @skipmess = ( ) ;
3823 @pipemess = ( ) ;
3824 $delete2foldersonly = undef ;
3825 $delete2foldersbutnot = undef ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003826 $maxlinelengthcmd = undef ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003827
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003828 # Set safe default values (I hope...)
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003829
3830
3831 #$mysync->{pidfile} = 'imapsync.pid' ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003832 $mysync->{ pidfilelocking } = 1 ;
3833 $mysync->{ errorsmax } = $ERRORS_MAX_CGI ;
3834 $modulesversion = 0 ;
3835 $mysync->{ releasecheck } = defined $mysync->{ releasecheck } ? $mysync->{ releasecheck } : 1 ;
3836 $usecache = 0 ;
3837 $mysync->{ showpasswords } = 0 ;
3838 $mysync->{ acc1 }->{ debugimap } = 0 ;
3839 $mysync->{ acc2 }->{ debugimap } = 0 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003840
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003841 $mysync->{ acc1 }->{ reconnectretry } = $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ;
3842 $mysync->{ acc2 }->{ reconnectretry } = $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ;
3843
3844 $pipemesscheck = 0 ;
3845
3846 $mysync->{ hashfile } = $CGI_HASHFILE ;
3847 my $hashsynclocal = hashsynclocal( $mysync ) || die "Can not get hashsynclocal. Exiting\n" ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003848
3849 if ( $ENV{ 'NET_SERVER_SOFTWARE' } and ( $ENV{ 'NET_SERVER_SOFTWARE' } =~ /Net::Server::HTTP/ ) )
3850 {
3851 # under local webserver
3852 $cgidir = q{.} ;
3853 }
3854 else
3855 {
3856 $cgidir = $CGI_TMPDIR_TOP . '/' . $hashsynclocal ;
3857 }
3858 -d $cgidir or mkpath $cgidir or die "Can not create $cgidir: $OS_ERROR\n" ;
3859 $mysync->{ tmpdir } = $cgidir ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003860 $mysync->{ logdir } = '' ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003861
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003862 chdir $cgidir or die "Can not cd to $cgidir: $OS_ERROR\n" ;
3863 cgioutputenvcontext( $mysync ) ;
3864 $mysync->{ debug } and output( $mysync, 'Current directory is ' . getcwd( ) . "\n" ) ;
3865 $mysync->{ debug } and output( $mysync, 'Real user id is ' . getpwuid_any_os( $REAL_USER_ID ) . " (uid $REAL_USER_ID)\n" ) ;
3866 $mysync->{ debug } and output( $mysync, 'Effective user id is ' . getpwuid_any_os( $EFFECTIVE_USER_ID ). " (euid $EFFECTIVE_USER_ID)\n" ) ;
3867
3868 $mysync->{ skipemptyfolders } = defined $mysync->{ skipemptyfolders } ? $mysync->{ skipemptyfolders } : 1 ;
3869
3870 # Out of memory with messages over 1 GB ?
3871 $mysync->{ maxsize } = defined $mysync->{ maxsize } ? $mysync->{ maxsize } : 1_000_000_000 ;
3872
3873 # tail -f behaviour on by default
3874 $mysync->{ tail } = defined $mysync->{ tail } ? $mysync->{ tail } : 1 ;
3875
3876 # not sure it's for good
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003877 @useheader = qw( Message-Id Received ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003878
3879 # addheader on by default
3880 $mysync->{ addheader } = defined $mysync->{ addheader } ? $mysync->{ addheader } : 1 ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003881
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003882 # sync duplicates by default in cgi context
3883 $mysync->{ syncduplicates } = defined $mysync->{ syncduplicates } ? $mysync->{ syncduplicates } : 1 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003884
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003885 # log the logfile name by default in cgi context
3886 $mysync->{ loglogfile } = defined $mysync->{ loglogfile } ? $mysync->{ loglogfile } : 1 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003887 return ;
3888}
3889
3890sub cgioutputenvcontext
3891{
3892 my $mysync = shift @ARG ;
3893
3894 for my $envvar ( qw( REMOTE_ADDR REMOTE_HOST HTTP_REFERER HTTP_USER_AGENT SERVER_SOFTWARE SERVER_PORT HTTP_COOKIE ) ) {
3895
3896 my $envval = $ENV{ $envvar } || q{} ;
3897 if ( $envval ) { output( $mysync, "$envvar is $envval\n" ) } ;
3898 }
3899
3900 return ;
3901}
3902
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003903sub announcelogfile
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003904{
3905 my $mysync = shift ;
3906
3907 if ( $mysync->{ log } )
3908 {
3909 myprint( "Log file is $mysync->{ logfile } ( to change it, use --logfile path ; or use --nolog to turn off logging )\n" ) ;
3910 loglogfile( $mysync ) ;
3911 }
3912 else
3913 {
3914 myprint( "No log file because of option --nolog\n" ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003915 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003916 return ;
3917}
3918
3919
3920sub loglogfile
3921{
3922 my $mysync = shift ;
3923 if ( ! $mysync->{ loglogfile } ) { return ; }
3924 if ( ! $mysync->{ log } ) { return ; }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003925
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003926 my $cwd = getcwd( ) ;
3927 my $absolutelogfilepath ;
3928 # Fixme: add case when the logfile name is already absolute
3929 $absolutelogfilepath = "$cwd/$mysync->{ logfile }" ;
3930 my $loglogfilename = '../list_all_logs_auto.txt' ;
3931 myprint( "Writing log file name $absolutelogfilepath to $loglogfilename\n" ) ;
3932 if ( open( my $fh, '>>', $loglogfilename ) )
3933 {
3934 print $fh "$absolutelogfilepath\n" ;
3935 close $fh ;
3936 }
3937 else
3938 {
3939 myprint( "Could not open loglogfile $loglogfilename $!\n" ) ;
3940 }
3941 return ;
3942}
3943
3944
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003945sub checkselectable
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003946{
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003947 my $mysync = shift ;
3948
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003949 if ( $mysync->{ checkselectable } ) {
3950 my @h1_folders_wanted_selectable ;
3951 myprint( "Host1: Checking wanted folders are selectable. Use --nocheckselectable to avoid this check.\n" ) ;
3952 foreach my $folder ( @{ $mysync->{ h1_folders_wanted } } )
3953 {
3954 ( $mysync->{ debug } or $mysync->{ debugfolders } ) and myprint( "Checking $folder is selectable on host1\n" ) ;
3955 # It does an imap command LIST "" $folder and then search for no \Noselect
3956 if ( ! $mysync->{ imap1 }->selectable( $folder ) )
3957 {
3958 myprint( "Host1: warning! ignoring folder $folder because it is not selectable\n" ) ;
3959 }else
3960 {
3961 push @h1_folders_wanted_selectable, $folder ;
3962 }
3963 }
3964 @{ $mysync->{ h1_folders_wanted } } = @h1_folders_wanted_selectable ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003965 ( $mysync->{ debug } or $mysync->{ debugfolders } )
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003966 and myprint( 'Host1: checking folders took ', timenext( $mysync ), " s\n" ) ;
3967 }
3968 else
3969 {
3970 myprint( "Host1: Not checking that wanted folders are selectable. Use --checkselectable to force this check.\n" ) ;
3971 }
3972 return ;
3973}
3974
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003975sub setcheckselectable
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003976{
3977 my $mysync = shift ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003978
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003979 my $h1_folders_wanted_nb = scalar @{ $mysync->{ h1_folders_wanted } } ;
3980 # 152 because 98% of host1 accounts have less than 152 folders on /X service.
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003981 # command to get this value:
3982 # datamash_file_op_index G_Host1_Nb_folders.txt perc:98 4 %16.1f
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003983 if ( ! defined $mysync->{ checkselectable } )
3984 {
3985 if ( 152 >= $h1_folders_wanted_nb )
3986 {
3987 $mysync->{ checkselectable } = 1 ;
3988 }else{
3989 myprint( "Host1: Not checking that $h1_folders_wanted_nb wanted folders are selectable. Use --checkselectable to force this check.\n" ) ;
3990 $mysync->{ checkselectable } = 0 ;
3991 }
3992 }
3993 return ;
3994}
3995
3996
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003997
3998sub debugsleep
3999{
4000 my $mysync = shift @ARG ;
4001 if ( defined $mysync->{debugsleep} ) {
4002 myprint( "Info: sleeping $mysync->{debugsleep}s\n" ) ;
4003 sleep $mysync->{debugsleep} ;
4004 }
4005 return ;
4006}
4007
4008sub tests_foldersize
4009{
4010 note( 'Entering tests_foldersize()' ) ;
4011
4012 is( undef, foldersize( ), 'foldersize: no args => undef' ) ;
4013
4014
4015 #is_deeply( {}, {}, 'foldersize: a hash is a hash' ) ;
4016 #is_deeply( [], [], 'foldersize: an array is an array' ) ;
4017 note( 'Leaving tests_foldersize()' ) ;
4018 return ;
4019}
4020
4021
4022
4023# Globals:
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004024# $fetch_hash_set
4025#
4026sub foldersize
4027{
4028 # size of one folder
4029 my ( $mysync, $side, $imap, $search_cmd, $abletosearch, $folder ) = @ARG ;
4030
4031 if ( ! all_defined( $mysync, $side, $imap, $folder ) )
4032 {
4033 return ;
4034 }
4035
4036 # FTGate is RFC buggy with EXAMINE it does not act as SELECT
4037 #if ( ! $imap->examine( $folder ) ) {
4038 if ( ! $imap->select( $folder ) ) {
4039 my $error = join q{},
4040 "$side Folder $folder: Could not select: ",
4041 $imap->LastError, "\n" ;
4042 errors_incr( $mysync, $error ) ;
4043 return ;
4044 }
4045
4046 if ( $imap->IsUnconnected( ) )
4047 {
4048 return ;
4049 }
4050
4051 my $hash_ref = { } ;
4052 my @msgs = select_msgs( $imap, undef, $search_cmd, $abletosearch, $folder ) ;
4053 my $nb_msgs = scalar @msgs ;
4054 my $biggest_in_folder = 0 ;
4055 @{ $hash_ref }{ @msgs } = ( undef ) if @msgs ;
4056
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02004057
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004058 my $stot = 0 ;
4059
4060 if ( $imap->IsUnconnected( ) )
4061 {
4062 return ;
4063 }
4064
4065 if ( $nb_msgs > 0 and @msgs ) {
4066 if ( $abletosearch ) {
4067 if ( ! $imap->fetch_hash( \@msgs, 'RFC822.SIZE', $hash_ref) ) {
4068 my $error = "$side failure with fetch_hash: $EVAL_ERROR\n" ;
4069 errors_incr( $mysync, $error ) ;
4070 return ;
4071 }
4072 }
4073 else
4074 {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02004075 my $fetch_hash_uids = $fetch_hash_set || "1:*" ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004076 if ( ! $imap->fetch_hash( $fetch_hash_uids, 'RFC822.SIZE', $hash_ref ) ) {
4077 my $error = "$side failure with fetch_hash: $EVAL_ERROR\n" ;
4078 errors_incr( $mysync, $error ) ;
4079 return ;
4080 }
4081 }
4082 for ( keys %{ $hash_ref } ) {
4083 my $size = $hash_ref->{ $_ }->{ 'RFC822.SIZE' } ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02004084 if ( defined $size )
4085 {
4086 $stot += $size ;
4087 $biggest_in_folder = max( $biggest_in_folder, $size ) ;
4088 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004089 }
4090 }
4091 return( $stot, $nb_msgs, $biggest_in_folder ) ;
4092
4093}
4094
4095
4096# The old subroutine that performed just one side at a time.
4097# Still here for a while, until confident with sub foldersize_diff_compute()
4098sub foldersizes
4099{
4100 my ( $mysync, $side, $imap, $search_cmd, $abletosearch, @folders ) = @_ ;
4101 my $total_size = 0 ;
4102 my $total_nb = 0 ;
4103 my $biggest_in_all = 0 ;
4104
4105 my $nb_folders = scalar @folders ;
4106 my $ct_folders = 0 ; # folder counter.
4107 myprint( "++++ Calculating sizes of $nb_folders folders on $side\n" ) ;
4108 foreach my $folder ( @folders ) {
4109 my $stot = 0 ;
4110 my $nb_msgs = 0 ;
4111 my $biggest_in_folder = 0 ;
4112
4113 $ct_folders++ ;
4114 myprintf( "$side folder %7s %-35s", "$ct_folders/$nb_folders", jux_utf8( $folder ) ) ;
4115 if ( 'Host2' eq $side and not exists $mysync->{h2_folders_all_UPPER}{ uc $folder } ) {
4116 myprint( " does not exist yet\n") ;
4117 next ;
4118 }
4119 if ( 'Host1' eq $side and not exists $h1_folders_all{ $folder } ) {
4120 myprint( " does not exist\n" ) ;
4121 next ;
4122 }
4123
4124 last if $imap->IsUnconnected( ) ;
4125
4126 ( $stot, $nb_msgs, $biggest_in_folder ) = foldersize( $mysync, $side, $imap, $search_cmd, $abletosearch, $folder ) ;
4127
4128 myprintf( ' Size: %9s', $stot ) ;
4129 myprintf( ' Messages: %5s', $nb_msgs ) ;
4130 myprintf( " Biggest: %9s\n", $biggest_in_folder ) ;
4131 $total_size += $stot ;
4132 $total_nb += $nb_msgs ;
4133 $biggest_in_all = max( $biggest_in_all, $biggest_in_folder ) ;
4134 }
4135 myprintf( "%s Nb folders: %11s folders\n", $side, $nb_folders ) ;
4136 myprintf( "%s Nb messages: %11s messages\n", $side, $total_nb ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01004137 myprintf( "%s Total size: %11s bytes (%s)\n", $side, $total_size, bytes_display_string_bin( $total_size ) ) ;
4138 myprintf( "%s Biggest message: %11s bytes (%s)\n", $side, $biggest_in_all, bytes_display_string_bin( $biggest_in_all ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004139 myprintf( "%s Time spent on sizing: %11.1f seconds\n", $side, timenext( $mysync ) ) ;
4140 return( $total_nb, $total_size ) ;
4141}
4142
4143
4144sub foldersize_diff_present
4145{
4146 my $mysync = shift ;
4147 my $folder1 = shift ;
4148 my $folder2 = shift ;
4149 my $counter_str = shift ;
4150 my $force = shift ;
4151
4152 my $values1_str ;
4153 my $values2_str ;
4154
4155 if ( ! defined $mysync->{ folder1 }->{ $folder1 }->{ size } || $force )
4156 {
4157 foldersize_diff_compute( $mysync, $folder1, $folder2, $force ) ;
4158 }
4159
4160 # again, but this time it means no availaible data.
4161 if ( defined $mysync->{ folder1 }->{ $folder1 }->{ size } )
4162 {
4163 $values1_str = sprintf( "Size: %9s Messages: %5s Biggest: %9s\n",
4164 $mysync->{ folder1 }->{ $folder1 }->{ size },
4165 $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs },
4166 $mysync->{ folder1 }->{ $folder1 }->{ biggest },
4167 ) ;
4168 }
4169 else
4170 {
4171 $values1_str = " does not exist\n" ;
4172 }
4173
4174 if ( defined $mysync->{ folder2 }->{ $folder2 }->{ size } )
4175 {
4176 $values2_str = sprintf( "Size: %9s Messages: %5s Biggest: %9s\n",
4177 $mysync->{ folder2 }->{ $folder2 }->{ size },
4178 $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs },
4179 $mysync->{ folder2 }->{ $folder2 }->{ biggest },
4180 ) ;
4181 }
4182 else
4183 {
4184 $values2_str = " does not exist yet\n" ;
4185 }
4186
4187 myprintf( "Host1 folder %7s %-35s %s",
4188 "$counter_str",
4189 jux_utf8( $folder1 ),
4190 $values1_str
4191 ) ;
4192
4193 myprintf( "Host2 folder %7s %-35s %s",
4194 "$counter_str",
4195 jux_utf8( $folder2 ),
4196 $values2_str
4197 ) ;
4198
4199 myprintf( "Host2-Host1 %7s %-35s %9s %5s %9s\n\n",
4200 "",
4201 "",
4202 $mysync->{ folder1 }->{ $folder1 }->{ size_diff },
4203 $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs_diff },
4204 $mysync->{ folder1 }->{ $folder1 }->{ biggest_diff },
4205
4206 ) ;
4207
4208
4209
4210
4211 return ;
4212}
4213
4214sub foldersize_diff_compute
4215{
4216 my $mysync = shift ;
4217 my $folder1 = shift ;
4218 my $folder2 = shift ;
4219 my $force = shift ;
4220
4221
4222
4223 my ( $size_1, $nb_msgs_1, $biggest_1 ) ;
4224 # memoization
4225 if (
4226 exists $h1_folders_all{ $folder1 }
4227 &&
4228 (
4229 ! defined $mysync->{ folder1 }->{ $folder1 }->{ size }
4230 || $force
4231 )
4232 )
4233 {
4234 #myprint( "foldersize folder1 $h1_folders_all{ $folder1 }\n" ) ;
4235 ( $size_1, $nb_msgs_1, $biggest_1 ) =
4236 foldersize( $mysync,
4237 'Host1',
4238 $mysync->{ imap1 },
4239 $mysync->{ search1 },
4240 $mysync->{ abletosearch1 },
4241 $folder1
4242 ) ;
4243 $mysync->{ folder1 }->{ $folder1 }->{ size } = $size_1 ;
4244 $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs } = $nb_msgs_1 ;
4245 $mysync->{ folder1 }->{ $folder1 }->{ biggest } = $biggest_1 ;
4246 }
4247 else
4248 {
4249 $size_1 = $mysync->{ folder1 }->{ $folder1 }->{ size } ;
4250 $nb_msgs_1 = $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs } ;
4251 $biggest_1 = $mysync->{ folder1 }->{ $folder1 }->{ biggest } ;
4252
4253 }
4254
4255
4256 my ( $size_2, $nb_msgs_2, $biggest_2 ) ;
4257 if (
4258 exists $mysync->{ h2_folders_all_UPPER }{ uc $folder2 }
4259 &&
4260 (
4261 ! defined $mysync->{ folder2 }->{ $folder2 }->{ size }
4262 || $force
4263 )
4264 )
4265 {
4266 #myprint( "foldersize folder2\n" ) ;
4267 ( $size_2, $nb_msgs_2, $biggest_2 ) =
4268 foldersize( $mysync,
4269 'Host2',
4270 $mysync->{ imap2 },
4271 $mysync->{ search2 },
4272 $mysync->{ abletosearch2 },
4273 $folder2
4274 ) ;
4275
4276 $mysync->{ folder2 }->{ $folder2 }->{ size } = $size_2 ;
4277 $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs } = $nb_msgs_2 ;
4278 $mysync->{ folder2 }->{ $folder2 }->{ biggest } = $biggest_2 ;
4279 }
4280 else
4281 {
4282 $size_2 = $mysync->{ folder2 }->{ $folder2 }->{ size } ;
4283 $nb_msgs_2 = $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs } ;
4284 $biggest_2 = $mysync->{ folder2 }->{ $folder2 }->{ biggest } ;
4285
4286 }
4287
4288
4289 my $size_diff = diff( $size_2, $size_1 ) ;
4290 my $nb_msgs_diff = diff( $nb_msgs_2, $nb_msgs_1 ) ;
4291 my $biggest_diff = diff( $biggest_2, $biggest_1 ) ;
4292
4293 $mysync->{ folder1 }->{ $folder1 }->{ size_diff } = $size_diff ;
4294 $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs_diff } = $nb_msgs_diff ;
4295 $mysync->{ folder1 }->{ $folder1 }->{ biggest_diff } = $biggest_diff ;
4296
4297 # It's redundant but easier to access later
4298 $mysync->{ folder2 }->{ $folder2 }->{ size_diff } = $size_diff ;
4299 $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs_diff } = $nb_msgs_diff ;
4300 $mysync->{ folder2 }->{ $folder2 }->{ biggest_diff } = $biggest_diff ;
4301
4302 return ;
4303}
4304
4305sub diff
4306{
4307 my $x = shift ;
4308 my $y = shift ;
4309
4310 $x ||= 0 ;
4311 $y ||= 0 ;
4312
4313 return $x - $y ;
4314}
4315
4316sub add
4317{
4318 my $x = shift ;
4319 my $y = shift ;
4320
4321 $x ||= 0 ;
4322 $y ||= 0 ;
4323
4324 return $x + $y ;
4325}
4326
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02004327sub tests_checknoabletosearch
4328{
4329 note( 'Entering checknoabletosearch()' ) ;
4330
4331 is( undef, checknoabletosearch( ), 'checknoabletosearch: no args => undef' ) ;
4332
4333 note( 'Leaving checknoabletosearch()' ) ;
4334 return ;
4335}
4336
4337
4338
4339
4340sub checknoabletosearch
4341{
4342 # call example: checknoabletosearch( $sync, $sync->{ imap1 }, 'INBOX', 'Host1' ) ;
4343 # output:
4344 # * undef if something is not ok to decide
4345 # * 1 if SEARCH ALL failed
4346
4347 my( $mysync, $imap, $folder, $HostX ) = @ARG ;
4348
4349 if ( ! all_defined( $mysync, $imap, $folder, $HostX ) )
4350 {
4351 return ;
4352 }
4353
4354 myprint( "$HostX: checking if SEARCH ALL works on $folder\n" ) ;
4355 if ( ! select_folder( $mysync, $imap, $folder, $HostX ) )
4356 {
4357 myprint( "$HostX: can not SELECT folder [$folder]\n" ) ;
4358 return ;
4359 }
4360 my $count_from_select = count_from_select( $imap->History ) ;
4361 myprint( "$HostX: folder [$folder] has $count_from_select messages mentioned by SELECT\n" ) ;
4362
4363 my $msgs_all = $imap->messages( ) ;
4364 if ( ! $msgs_all )
4365 {
4366 myprint( "$HostX: can not SEARCH ALL folder [$folder]\n" ) ;
4367 myprint( "$HostX: ", $imap->LastError(), "\n" ) ;
4368 return 1 ;
4369 }
4370
4371 my $count_from_search_all = scalar( @{ $msgs_all } ) ;
4372 myprint( "$HostX: folder [$folder] has $count_from_search_all messages found by SEARCH ALL\n" ) ;
4373
4374 if ( $count_from_select == $count_from_search_all )
4375 {
4376 myprint( "$HostX: folder [$folder] has the same messages count ($count_from_select) by SELECT and SEARCH ALL\n" ) ;
4377 }
4378 else
4379 {
4380 myprint( "$HostX: Warning, folder [$folder] has not the same count by SELECT ($count_from_select) and SEARCH ALL ($count_from_search_all)\n" ) ;
4381 return 1 ;
4382 }
4383
4384 return ;
4385}
4386
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004387
4388sub foldersizes_diff_list
4389{
4390 my $mysync = shift ;
4391 my $force = shift ;
4392
4393 my @folders = @{ $mysync->{h1_folders_wanted} } ;
4394 my $nb_folders = scalar @folders ;
4395 my $ct_folders = 0 ; # folder counter.
4396
4397 foreach my $folder1 ( @folders )
4398 {
4399 $ct_folders++ ;
4400 my $counter_str = "$ct_folders/$nb_folders" ;
4401 my $folder2 = imap2_folder_name( $mysync, $folder1 ) ;
4402 foldersize_diff_present( $mysync, $folder1, $folder2, $counter_str, $force ) ;
4403 }
4404
4405 return ;
4406}
4407
4408sub foldersizes_total
4409{
4410 my $mysync = shift ;
4411
4412 my @folders_1 = @{ $mysync->{h1_folders_wanted} } ;
4413 my @folders_2 = @h2_folders_from_1_wanted ;
4414
4415 my $nb_folders_1 = scalar( @folders_1 ) ;
4416 my $nb_folders_2 = scalar( @folders_2 ) ;
4417
4418 my ( $total_size_1, $total_nb_1, $biggest_in_all_1 ) = ( 0, 0, 0 ) ;
4419 my ( $total_size_2, $total_nb_2, $biggest_in_all_2 ) = ( 0, 0, 0 ) ;
4420
4421 foreach my $folder1 ( @folders_1 )
4422 {
4423 $total_size_1 = add( $total_size_1, $mysync->{ folder1 }->{ $folder1 }->{ size } ) ;
4424 $total_nb_1 = add( $total_nb_1, $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs } ) ;
4425 $biggest_in_all_1 = max( $biggest_in_all_1 , $mysync->{ folder1 }->{ $folder1 }->{ biggest } ) ;
4426 }
4427
4428 foreach my $folder2 ( @folders_2 )
4429 {
4430 $total_size_2 = add( $total_size_2, $mysync->{ folder2 }->{ $folder2 }->{ size } ) ;
4431 $total_nb_2 = add( $total_nb_2, $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs } ) ;
4432 $biggest_in_all_2 = max( $biggest_in_all_2 , $mysync->{ folder2 }->{ $folder2 }->{ biggest } ) ;
4433
4434 }
4435
4436 myprintf( "Host1 Nb folders: %11s folders\n", $nb_folders_1 ) ;
4437 myprintf( "Host2 Nb folders: %11s folders\n", $nb_folders_2 ) ;
4438 myprint( "\n" ) ;
4439 myprintf( "Host1 Nb messages: %11s messages\n", $total_nb_1 ) ;
4440 myprintf( "Host2 Nb messages: %11s messages\n", $total_nb_2 ) ;
4441 myprint( "\n" ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01004442 myprintf( "Host1 Total size: %11s bytes (%s)\n", $total_size_1, bytes_display_string_bin( $total_size_1 ) ) ;
4443 myprintf( "Host2 Total size: %11s bytes (%s)\n", $total_size_2, bytes_display_string_bin( $total_size_2 ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004444 myprint( "\n" ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01004445 myprintf( "Host1 Biggest message: %11s bytes (%s)\n", $biggest_in_all_1, bytes_display_string_bin( $biggest_in_all_1 ) ) ;
4446 myprintf( "Host2 Biggest message: %11s bytes (%s)\n", $biggest_in_all_2, bytes_display_string_bin( $biggest_in_all_2 ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004447 myprint( "\n" ) ;
4448 myprintf( "Time spent on sizing: %11.1f seconds\n", timenext( $mysync ) ) ;
4449
4450 my @total_1_2 = ( $total_nb_1, $total_size_1, $total_nb_2, $total_size_2 ) ;
4451 return @total_1_2 ;
4452}
4453
4454sub foldersizesatend_old
4455{
4456 my $mysync = shift ;
4457 timenext( $mysync ) ;
4458 return if ( $mysync->{imap1}->IsUnconnected( ) ) ;
4459 return if ( $mysync->{imap2}->IsUnconnected( ) ) ;
4460 # Get all folders on host2 again since new were created
4461 @h2_folders_all = sort $mysync->{imap2}->folders();
4462 for ( @h2_folders_all ) {
4463 $h2_folders_all{ $_ } = 1 ;
4464 $mysync->{h2_folders_all_UPPER}{ uc $_ } = 1 ;
4465 } ;
4466 ( $h1_nb_msg_end, $h1_bytes_end ) = foldersizes( $mysync, 'Host1', $mysync->{imap1}, $mysync->{ search1 }, $mysync->{abletosearch1}, @{ $mysync->{h1_folders_wanted} } ) ;
4467 ( $h2_nb_msg_end, $h2_bytes_end ) = foldersizes( $mysync, 'Host2', $mysync->{imap2}, $mysync->{ search2 }, $mysync->{abletosearch2}, @h2_folders_from_1_wanted ) ;
4468 if ( not all_defined( $h1_nb_msg_end, $h1_bytes_end, $h2_nb_msg_end, $h2_bytes_end ) ) {
4469 my $error = "Failure getting foldersizes, final differences will not be calculated\n" ;
4470 errors_incr( $mysync, $error ) ;
4471 }
4472 return ;
4473}
4474
4475sub foldersizesatend
4476{
4477 my $mysync = shift ;
4478 timenext( $mysync ) ;
4479 return if ( $mysync->{imap1}->IsUnconnected( ) ) ;
4480 return if ( $mysync->{imap2}->IsUnconnected( ) ) ;
4481 # Get all folders on host2 again since new were created
4482 @h2_folders_all = sort $mysync->{imap2}->folders();
4483 for ( @h2_folders_all ) {
4484 $h2_folders_all{ $_ } = 1 ;
4485 $mysync->{h2_folders_all_UPPER}{ uc $_ } = 1 ;
4486 } ;
4487
4488
4489 foldersizes_diff_list( $mysync, $FORCE ) ;
4490
4491 ( $h1_nb_msg_end, $h1_bytes_end, $h2_nb_msg_end, $h2_bytes_end )
4492 = foldersizes_total( $mysync ) ;
4493
4494
4495 if ( not all_defined( $h1_nb_msg_end, $h1_bytes_end, $h2_nb_msg_end, $h2_bytes_end ) ) {
4496 my $error = "Failure getting foldersizes, final differences will not be calculated\n" ;
4497 errors_incr( $mysync, $error ) ;
4498 }
4499 return ;
4500}
4501
4502
4503sub foldersizes_at_the_beggining
4504{
4505 my $mysync = shift ;
4506
4507 myprint( << 'END_SIZE' ) ;
4508
4509Folders sizes before the synchronization.
4510You can remove foldersizes listings by using "--nofoldersizes" and "--nofoldersizesatend"
4511but then you will also lose the ETA (Estimation Time of Arrival) given after each message copy.
4512END_SIZE
4513
4514 foldersizes_diff_list( $mysync ) ;
4515
4516 ( $mysync->{ h1_nb_msg_start }, $mysync->{ h1_bytes_start },
4517 $mysync->{ h2_nb_msg_start }, $mysync->{ h2_bytes_start } )
4518 = foldersizes_total( $mysync ) ;
4519
4520
4521 if ( not all_defined(
4522 $mysync->{ h1_nb_msg_start },
4523 $mysync->{ h1_bytes_start },
4524 $mysync->{ h2_nb_msg_start },
4525 $mysync->{ h2_bytes_start } ) )
4526 {
4527 my $error = "Failure getting foldersizes, ETA and final diff will not be displayed\n" ;
4528 errors_incr( $mysync, $error ) ;
4529 $mysync->{ foldersizes } = 0 ;
4530 $mysync->{ foldersizesatend } = 0 ;
4531 return ;
4532 }
4533
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02004534 my $h2_bytes_limit = $mysync->{ acc2 }->{quota_limit_bytes} || 0 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004535 if ( $h2_bytes_limit and ( $h2_bytes_limit < $mysync->{ h1_bytes_start } ) )
4536 {
4537 my $quota_percent = mysprintf( '%.0f', $NUMBER_100 * $mysync->{ h1_bytes_start } / $h2_bytes_limit ) ;
4538 my $error = "Host2: Quota limit will be exceeded! Over $quota_percent % ( $mysync->{ h1_bytes_start } bytes / $h2_bytes_limit bytes )\n" ;
4539 errors_incr( $mysync, $error ) ;
4540 }
4541 return ;
4542}
4543
4544
4545# Globals:
4546# @h2_folders_from_1_wanted
4547
4548sub foldersizes_at_the_beggining_old
4549{
4550 my $mysync = shift ;
4551
4552 myprint( << 'END_SIZE' ) ;
4553
4554Folders sizes before the synchronization.
4555You can remove foldersizes listings by using "--nofoldersizes" and "--nofoldersizesatend"
4556but then you will also lose the ETA (Estimation Time of Arrival) given after each message copy.
4557END_SIZE
4558
4559 ( $mysync->{ h1_nb_msg_start }, $mysync->{ h1_bytes_start } ) =
4560 foldersizes( $mysync, 'Host1', $mysync->{imap1}, $mysync->{ search1 },
4561 $mysync->{abletosearch1}, @{ $mysync->{h1_folders_wanted} } ) ;
4562 ( $mysync->{ h2_nb_msg_start }, $mysync->{ h2_bytes_start } ) =
4563 foldersizes( $mysync, 'Host2', $mysync->{imap2}, $mysync->{ search2 },
4564 $mysync->{abletosearch2}, @h2_folders_from_1_wanted ) ;
4565
4566 if ( not all_defined( $mysync->{ h1_nb_msg_start },
4567 $mysync->{ h1_bytes_start }, $mysync->{ h2_nb_msg_start }, $mysync->{ h2_bytes_start } ) )
4568 {
4569 my $error = "Failure getting foldersizes, ETA and final diff will not be displayed\n" ;
4570 errors_incr( $mysync, $error ) ;
4571 $mysync->{ foldersizes } = 0 ;
4572 $mysync->{ foldersizesatend } = 0 ;
4573 return ;
4574 }
4575
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02004576 my $h2_bytes_limit = $mysync->{ acc2 }->{quota_limit_bytes} || 0 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004577 if ( $h2_bytes_limit and ( $h2_bytes_limit < $mysync->{ h1_bytes_start } ) )
4578 {
4579 my $quota_percent = mysprintf( '%.0f', $NUMBER_100 * $mysync->{ h1_bytes_start } / $h2_bytes_limit ) ;
4580 my $error = "Host2: Quota limit will be exceeded! Over $quota_percent % ( $mysync->{ h1_bytes_start } bytes / $h2_bytes_limit bytes )\n" ;
4581 errors_incr( $mysync, $error ) ;
4582 }
4583 return ;
4584}
4585
4586
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02004587sub tests_total_bytes_max_reached
4588{
4589 note( 'Entering tests_total_bytes_max_reached()' ) ;
4590
4591 is( undef, total_bytes_max_reached( ), 'total_bytes_max_reached: no args => undef' ) ;
4592
4593 my $mysync = {} ;
4594 is( undef, total_bytes_max_reached( $mysync ), 'total_bytes_max_reached: no exitwhenover => undef' ) ;
4595
4596 $mysync->{ exitwhenover } = 300 ;
4597 is( undef, total_bytes_max_reached( $mysync ), 'total_bytes_max_reached: exitwhenover 300 but no total_bytes_transferred => undef' ) ;
4598
4599 $mysync->{ total_bytes_transferred } = 200 ;
4600 is( undef, total_bytes_max_reached( $mysync ), 'total_bytes_max_reached: exitwhenover 300 but total_bytes_transferred 200 => undef' ) ;
4601
4602 $mysync->{ total_bytes_transferred } = 400 ;
4603 is( 1, total_bytes_max_reached( $mysync ), 'total_bytes_max_reached: exitwhenover 300 but total_bytes_transferred 400 => 1' ) ;
4604
4605
4606
4607 note( 'Leaving tests_total_bytes_max_reached()' ) ;
4608 return ;
4609}
4610
4611
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004612sub total_bytes_max_reached
4613{
4614 my $mysync = shift ;
4615
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02004616 if ( ! defined $mysync ) { return ; }
4617
4618 if ( ! $mysync->{ exitwhenover } )
4619 {
4620 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004621 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02004622
4623 if ( ! $mysync->{ total_bytes_transferred } )
4624 {
4625 return ;
4626 }
4627
4628 if ( $mysync->{ total_bytes_transferred } >= $mysync->{ exitwhenover } )
4629 {
4630 my $error = "Maximum bytes transferred reached, $mysync->{total_bytes_transferred} >= $mysync->{ exitwhenover }, ending sync\n" ;
4631 errors_incr( $mysync, $error ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004632 return( 1 ) ;
4633 }
4634 return ;
4635}
4636
4637
4638sub tests_mock_capability
4639{
4640 note( 'Entering tests_mock_capability()' ) ;
4641
4642 my $myimap ;
4643 ok( $myimap = mock_capability( ),
4644 'mock_capability: (1) no args => a Test::MockObject'
4645 ) ;
4646 ok( $myimap->isa( 'Test::MockObject' ),
4647 'mock_capability: (2) no args => a Test::MockObject'
4648 ) ;
4649
4650 is( undef, $myimap->capability( ),
4651 'mock_capability: (3) no args => capability undef'
4652 ) ;
4653
4654 ok( mock_capability( $myimap ),
4655 'mock_capability: (1) one arg => MockObject'
4656 ) ;
4657
4658 is( undef, $myimap->capability( ),
4659 'mock_capability: (2) one arg OO style => capability undef'
4660 ) ;
4661
4662 ok( mock_capability( $myimap, $NUMBER_123456 ),
4663 'mock_capability: (1) two args 123456 => capability 123456'
4664 ) ;
4665
4666 is( $NUMBER_123456, $myimap->capability( ),
4667 'mock_capability: (2) two args 123456 => capability 123456'
4668 ) ;
4669
4670 ok( mock_capability( $myimap, 'ABCD' ),
4671 'mock_capability: (1) two args ABCD => capability ABCD'
4672 ) ;
4673 is( 'ABCD', $myimap->capability( ),
4674 'mock_capability: (2) two args ABCD => capability ABCD'
4675 ) ;
4676
4677 ok( mock_capability( $myimap, [ 'ABCD' ] ),
4678 'mock_capability: (1) two args [ ABCD ] => capability [ ABCD ]'
4679 ) ;
4680 is_deeply( [ 'ABCD' ], $myimap->capability( ),
4681 'mock_capability: (2) two args [ ABCD ] => capability [ ABCD ]'
4682 ) ;
4683
4684 ok( mock_capability( $myimap, [ 'ABC', 'DEF' ] ),
4685 'mock_capability: (1) two args [ ABC, DEF ] => capability [ ABC, DEF ]'
4686 ) ;
4687 is_deeply( [ 'ABC', 'DEF' ], $myimap->capability( ),
4688 'mock_capability: (2) two args [ ABC, DEF ] => capability capability [ ABC, DEF ]'
4689 ) ;
4690
4691 ok( mock_capability( $myimap, 'ABC', 'DEF' ),
4692 'mock_capability: (1) two args ABC, DEF => capability [ ABC, DEF ]'
4693 ) ;
4694 is_deeply( [ 'ABC', 'DEF' ], [ $myimap->capability( ) ],
4695 'mock_capability: (2) two args ABC, DEF => capability capability [ ABC, DEF ]'
4696 ) ;
4697
4698 ok( mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ),
4699 'mock_capability: (1) two args IMAP4rev1, APPENDLIMIT=123456 => capability [ IMAP4rev1, APPENDLIMIT=123456 ]'
4700 ) ;
4701 is_deeply( [ 'IMAP4rev1', 'APPENDLIMIT=123456' ], [ $myimap->capability( ) ],
4702 'mock_capability: (2) two args IMAP4rev1, APPENDLIMIT=123456 => capability capability [ IMAP4rev1, APPENDLIMIT=123456 ]'
4703 ) ;
4704
4705 note( 'Leaving tests_mock_capability()' ) ;
4706 return ;
4707}
4708
4709sub sig_install_toggle_sleep
4710{
4711 my $mysync = shift ;
4712 if ( 'MSWin32' ne $OSNAME ) {
4713 #myprint( "sig_install( $mysync, \&toggle_sleep, 'USR1' )\n" ) ;
4714 sig_install( $mysync, 'toggle_sleep', 'USR1' ) ;
4715 }
4716 #myprint( "Leaving sig_install_toggle_sleep\n" ) ;
4717 return ;
4718}
4719
4720
4721sub mock_capability
4722{
4723 my $myimap = shift ;
4724 my @has_capability_value = @ARG ;
4725 my ( $has_capability_value ) = @has_capability_value ;
4726
4727 if ( ! $myimap )
4728 {
4729 require_ok( "Test::MockObject" ) ;
4730 $myimap = Test::MockObject->new( ) ;
4731 }
4732
4733 $myimap->mock(
4734 'capability',
4735 sub { return wantarray ?
4736 @has_capability_value
4737 : $has_capability_value ;
4738 }
4739 ) ;
4740
4741 return $myimap ;
4742}
4743
4744
4745sub tests_capability_of
4746{
4747 note( 'Entering tests_capability_of()' ) ;
4748
4749 is( undef, capability_of( ),
4750 'capability_of: no args => undef' ) ;
4751
4752 my $myimap ;
4753 is( undef, capability_of( $myimap ),
4754 'capability_of: undef => undef' ) ;
4755
4756
4757 $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ;
4758
4759 is( undef, capability_of( $myimap, 'CACA' ),
4760 'capability_of: two args unknown capability => undef' ) ;
4761
4762
4763 is( $NUMBER_123456, capability_of( $myimap, 'APPENDLIMIT' ),
4764 'capability_of: two args APPENDLIMIT 123456 => 123456 yeah!' ) ;
4765
4766 note( 'Leaving tests_capability_of()' ) ;
4767 return ;
4768}
4769
4770
4771sub capability_of
4772{
4773 my $imap = shift || return ;
4774 my $capability_keyword = shift || return ;
4775
4776 my @capability = $imap->capability ;
4777
4778 if ( ! @capability ) { return ; }
4779 my $capability_value = search_in_array( $capability_keyword, @capability ) ;
4780
4781 return $capability_value ;
4782}
4783
4784
4785sub tests_search_in_array
4786{
4787 note( 'Entering tests_search_in_array()' ) ;
4788
4789 is( undef, search_in_array( 'KA' ),
4790 'search_in_array: no array => undef ' ) ;
4791
4792 is( 'VA', search_in_array( 'KA', ( 'KA=VA' ) ),
4793 'search_in_array: KA KA=VA => VA ' ) ;
4794
4795 is( 'VA', search_in_array( 'KA', ( 'KA=VA', 'KB=VB' ) ),
4796 'search_in_array: KA KA=VA KB=VB => VA ' ) ;
4797
4798 is( 'VB', search_in_array( 'KB', ( 'KA=VA', 'KB=VB' ) ),
4799 'search_in_array: KA=VA KB=VB => VB ' ) ;
4800
4801 note( 'Leaving tests_search_in_array()' ) ;
4802 return ;
4803}
4804
4805sub search_in_array
4806{
4807 my ( $key, @array ) = @ARG ;
4808
4809 foreach my $item ( @array )
4810 {
4811
4812 if ( $item =~ /([^=]+)=(.*)/ )
4813 {
4814 if ( $1 eq $key )
4815 {
4816 return $2 ;
4817 }
4818 }
4819 }
4820
4821 return ;
4822}
4823
4824
4825
4826
4827sub tests_appendlimit_from_capability
4828{
4829 note( 'Entering tests_appendlimit_from_capability()' ) ;
4830
4831 is( undef, appendlimit_from_capability( ),
4832 'appendlimit_from_capability: no args => undef'
4833 ) ;
4834
4835 my $myimap ;
4836 is( undef, appendlimit_from_capability( $myimap ),
4837 'appendlimit_from_capability: undef arg => undef'
4838 ) ;
4839
4840
4841 $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ;
4842
4843 # Normal behavior
4844 is( $NUMBER_123456, appendlimit_from_capability( $myimap ),
4845 'appendlimit_from_capability: APPENDLIMIT=123456 => 123456'
4846 ) ;
4847
4848 # Not a number
4849 $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=ABC' ) ;
4850
4851 is( undef, appendlimit_from_capability( $myimap ),
4852 'appendlimit_from_capability: not a number => undef'
4853 ) ;
4854
4855 note( 'Leaving tests_appendlimit_from_capability()' ) ;
4856 return ;
4857}
4858
4859
4860sub appendlimit_from_capability
4861{
4862 my $myimap = shift ;
4863 if ( ! $myimap )
4864 {
4865 myprint( "Warn: no imap with call to appendlimit_from_capability\n" ) ;
4866 return ;
4867 }
4868
4869 #myprint( Data::Dumper->Dump( [ \$myimap ] ) ) ;
4870 my $appendlimit = capability_of( $myimap, 'APPENDLIMIT' ) ;
4871 #myprint( "has_capability APPENDLIMIT $appendlimit\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02004872 if ( is_integer( $appendlimit ) )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004873 {
4874 return $appendlimit ;
4875 }
4876 return ;
4877}
4878
4879
4880sub tests_appendlimit
4881{
4882 note( 'Entering tests_appendlimit()' ) ;
4883
4884 is( undef, appendlimit( ),
4885 'appendlimit: no args => undef'
4886 ) ;
4887
4888 my $mysync = { } ;
4889
4890 is( undef, appendlimit( $mysync ),
4891 'appendlimit: no imap2 => undef'
4892 ) ;
4893
4894 my $myimap ;
4895 $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ;
4896
4897 $mysync->{ imap2 } = $myimap ;
4898
4899 is( 123456, appendlimit( $mysync ),
4900 'appendlimit: imap2 with APPENDLIMIT=123456 => 123456'
4901 ) ;
4902
4903 note( 'Leaving tests_appendlimit()' ) ;
4904 return ;
4905}
4906
4907sub appendlimit
4908{
4909 my $mysync = shift || return ;
4910 my $myimap = $mysync->{ imap2 } ;
4911
4912 my $appendlimit = appendlimit_from_capability( $myimap ) ;
4913 if ( defined $appendlimit )
4914 {
4915 myprint( "Host2: found APPENDLIMIT=$appendlimit in CAPABILITY (use --appendlimit xxxx to override this automatic setting)\n" ) ;
4916 return $appendlimit ;
4917 }
4918 return ;
4919
4920}
4921
4922
4923sub tests_maxsize_setting
4924{
4925 note( 'Entering tests_maxsize_setting()' ) ;
4926
4927 is( undef, maxsize_setting( ),
4928 'maxsize_setting: no args => undef'
4929 ) ;
4930
4931 my $mysync ;
4932
4933 is( undef, maxsize_setting( $mysync ),
4934 'maxsize_setting: undef arg => undef'
4935 ) ;
4936
4937 $mysync = { } ;
4938 $mysync->{ maxsize } = $NUMBER_123456 ;
4939
4940 # --maxsize alone
4941 is( $NUMBER_123456, maxsize_setting( $mysync ),
4942 'maxsize_setting: --maxsize 123456 alone => 123456'
4943 ) ;
4944
4945
4946 $mysync = { } ;
4947 my $myimap ;
4948
4949 $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=654321' ) ;
4950 $mysync->{ imap2 } = $myimap ;
4951
4952 # APPENDLIMIT alone
4953 is( $NUMBER_654321, maxsize_setting( $mysync ),
4954 'maxsize_setting: APPENDLIMIT 654321 alone => 654321'
4955 ) ;
4956
4957 is( $NUMBER_654321, $mysync->{ maxsize },
4958 'maxsize_setting: APPENDLIMIT 654321 alone => maxsize 654321'
4959 ) ;
4960
4961 # APPENDLIMIT with --appendlimit => --appendlimit wins
4962 $mysync->{ appendlimit } = $NUMBER_123456 ;
4963
4964 is( $NUMBER_123456, maxsize_setting( $mysync ),
4965 'maxsize_setting: APPENDLIMIT 654321 + --appendlimit 123456 => 123456'
4966 ) ;
4967
4968 is( $NUMBER_123456, $mysync->{ maxsize },
4969 'maxsize_setting: APPENDLIMIT 654321 + --appendlimit 123456 => maxsize 123456'
4970 ) ;
4971
4972 # Fresh
4973 $mysync = { } ;
4974 $mysync->{ imap2 } = $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=654321' ) ;
4975
4976 # Case: "APPENDLIMIT >= --maxsize" => maxsize.
4977 $mysync->{ maxsize } = $NUMBER_123456 ;
4978
4979 is( $NUMBER_123456, maxsize_setting( $mysync ),
4980 'maxsize_setting: APPENDLIMIT 654321 --maxsize 123456 => 123456'
4981 ) ;
4982
4983 # Case: "APPENDLIMIT < --maxsize" => APPENDLIMIT.
4984
4985
4986 # Fresh
4987 $mysync = { } ;
4988 $mysync->{ imap2 } = $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ;
4989 $mysync->{ maxsize } = $NUMBER_654321 ;
4990
4991 is( $NUMBER_123456, maxsize_setting( $mysync ),
4992 'maxsize_setting: APPENDLIMIT 123456 --maxsize 654321 => 123456 '
4993 ) ;
4994
4995 # Now --truncmess stuff
4996
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004997 note( 'Leaving tests_maxsize_setting()' ) ;
4998
4999 return ;
5000}
5001
5002# Three variables to take account of
5003# appendlimit (given by --appendlimit or CAPABILITY...)
5004# maxsize
5005# truncmess
5006
5007sub maxsize_setting
5008{
5009 my $mysync = shift || return ;
5010
5011 if ( defined $mysync->{ appendlimit } )
5012 {
5013 myprint( "Host2: Getting appendlimit from --appendlimit $mysync->{ appendlimit }\n" ) ;
5014 }
5015 else
5016 {
5017 $mysync->{ appendlimit } = appendlimit( $mysync ) ;
5018 }
5019
5020
5021 if ( all_defined( $mysync->{ appendlimit }, $mysync->{ maxsize } ) )
5022 {
5023 my $min_maxsize_appendlimit = min( $mysync->{ maxsize }, $mysync->{ appendlimit } ) ;
5024 myprint( "Host2: Setting maxsize to $min_maxsize_appendlimit (min of --maxsize $mysync->{ maxsize } and appendlimit $mysync->{ appendlimit }\n" ) ;
5025 $mysync->{ maxsize } = $min_maxsize_appendlimit ;
5026 return $mysync->{ maxsize } ;
5027 }
5028 elsif ( defined $mysync->{ appendlimit } )
5029 {
5030 myprint( "Host2: Setting maxsize to appendlimit $mysync->{ appendlimit }\n" ) ;
5031 $mysync->{ maxsize } = $mysync->{ appendlimit } ;
5032 return $mysync->{ maxsize } ;
5033 }elsif ( defined $mysync->{ maxsize } )
5034 {
5035 return $mysync->{ maxsize } ;
5036 }else
5037 {
5038 return ;
5039 }
5040}
5041
5042
5043
5044
5045sub all_defined
5046{
5047 if ( not @ARG ) {
5048 return 0 ;
5049 }
5050 foreach my $elem ( @ARG ) {
5051 if ( not defined $elem ) {
5052 return 0 ;
5053 }
5054 }
5055 return 1 ;
5056}
5057
5058sub tests_all_defined
5059{
5060 note( 'Entering tests_all_defined()' ) ;
5061
5062 is( 0, all_defined( ), 'all_defined: no param => 0' ) ;
5063 is( 0, all_defined( () ), 'all_defined: void list => 0' ) ;
5064 is( 0, all_defined( undef ), 'all_defined: undef => 0' ) ;
5065 is( 0, all_defined( undef, undef ), 'all_defined: undef => 0' ) ;
5066 is( 0, all_defined( 1, undef ), 'all_defined: 1 undef => 0' ) ;
5067 is( 0, all_defined( undef, 1 ), 'all_defined: undef 1 => 0' ) ;
5068 is( 1, all_defined( 1, 1 ), 'all_defined: 1 1 => 1' ) ;
5069 is( 1, all_defined( (1, 1) ), 'all_defined: (1 1) => 1' ) ;
5070
5071 note( 'Leaving tests_all_defined()' ) ;
5072 return ;
5073}
5074
5075
5076sub tests_hashsynclocal
5077{
5078 note( 'Entering tests_hashsynclocal()' ) ;
5079
5080 my $mysync = {
5081 host1 => q{},
5082 user1 => q{},
5083 password1 => q{},
5084 host2 => q{},
5085 user2 => q{},
5086 password2 => q{},
5087 } ;
5088
5089 is( undef, hashsynclocal( $mysync ), 'hashsynclocal: no hashfile name' ) ;
5090
5091 $mysync->{ hashfile } = q{} ;
5092 is( undef, hashsynclocal( $mysync ), 'hashsynclocal: empty hashfile name' ) ;
5093
5094 $mysync->{ hashfile } = './noexist/rrr' ;
5095 is( undef, hashsynclocal( $mysync ), 'hashsynclocal: no exists hashfile dir' ) ;
5096
5097 SKIP: {
5098 if ( 'MSWin32' eq $OSNAME or '0' eq $EFFECTIVE_USER_ID ) { skip( 'Tests only for non-root Unix', 1 ) ; }
5099 $mysync->{ hashfile } = '/rrr' ;
5100 is( undef, hashsynclocal( $mysync ), 'hashsynclocal: permission denied' ) ;
5101 }
5102 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'hashsynclocal: mkpath W/tmp/tests/' ) ;
5103 $mysync->{ hashfile } = 'W/tmp/tests/imapsync_hash' ;
5104
5105 ok( ! -e 'W/tmp/tests/imapsync_hash' || unlink 'W/tmp/tests/imapsync_hash', 'hashsynclocal: unlink W/tmp/tests/imapsync_hash' ) ;
5106 ok( ! -e 'W/tmp/tests/imapsync_hash', 'hashsynclocal: verify there is no W/tmp/tests/imapsync_hash' ) ;
5107 is( 'ecdeb4ede672794d173da4e08c52b8ee19b7d252', hashsynclocal( $mysync, 'mukksyhpmbixkxkpjlqivmlqsulpictj' ), 'hashsynclocal: creating/reading W/tmp/tests/imapsync_hash' ) ;
5108 # A second time now
5109 is( 'ecdeb4ede672794d173da4e08c52b8ee19b7d252', hashsynclocal( $mysync ), 'hashsynclocal: reading W/tmp/tests/imapsync_hash second time => same' ) ;
5110
5111 note( 'Leaving tests_hashsynclocal()' ) ;
5112 return ;
5113}
5114
5115sub hashsynclocal
5116{
5117 my $mysync = shift ;
5118 my $hashkey = shift ; # Optional, only there for tests
5119 my $hashfile = $mysync->{ hashfile } ;
5120 $hashfile = createhashfileifneeded( $hashfile, $hashkey ) ;
5121 if ( ! $hashfile ) {
5122 return ;
5123 }
5124 $hashkey = firstline( $hashfile ) ;
5125 if ( ! $hashkey ) {
5126 myprint( "No hashkey!\n" ) ;
5127 return ;
5128 }
5129 my $hashsynclocal = hashsync( $mysync, $hashkey ) ;
5130 return( $hashsynclocal ) ;
5131
5132}
5133
5134sub tests_hashsync
5135{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02005136 note( 'Entering tests_hashsync()' ) ;
5137
5138 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hashsync( ), 'hashsync: no args' ) ;
5139
5140 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hashsync( {}, q{} ), 'hashsync: empty args' ) ;
5141 my $mysync ;
5142 $mysync->{ host1 } = 'zzz' ;
5143 is( 'e86a28a3611c1e7bbaf8057cd00ae122781a11fe', hashsync( $mysync, q{} ), 'hashsync: host1 zzz => ' ) ;
5144 is( '6a7b451ac99eab1531ad8e6cd544b32420c552ac', hashsync( $mysync, q{A} ), 'hashsync: host1 zzz => ' ) ;
5145 $mysync->{ host2 } = 'zzz' ;
5146 is( '15959573e4a86763253a7aedb1a2b0c60d133dc2', hashsync( $mysync, q{} ), 'hashsync: + host2 zzz => ' ) ;
5147 is( 'b8d4ab541b209c75928528020ca28ee43488bd8f', hashsync( $mysync, 'A' ), 'hashsync: + hashkey A => ' ) ;
5148
5149 $mysync = undef ;
5150 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hashsync( $mysync, q{} ), 'hashsync: undef $mysync' ) ;
5151 $mysync->{ password1 } = 'abcd' ;
5152 is( 'afa29ab8534495251ac8346a985717c54bc49c26', hashsync( $mysync, q{} ), 'hashsync: password1: abcd' ) ;
5153
5154 # A user reported a massive failure on /X (Thomas V. 21/04/2020 Ã 21:41 Subject: Error)
5155 # "Wide character in subroutine entry at /usr/local/lib/perl5/site_perl/Digest/HMAC.pm"
5156 # I can reproduce it now
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005157
5158
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02005159 # The eval is there to avoid a complete crash
5160 # this one is fatal so it is commented
5161 # is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', 1 / 0 , 'hashsync: 1 / 0 fatal' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005162
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02005163 my $eval ;
5164 # this one is not fatal
5165 is( undef, $eval = eval { 1 / 0 } , 'hashsync: 1/0 not fatal' ) ;
5166 # this one neither
5167 $mysync->{ password1 } = 'Ö' ;
5168 is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', $eval = eval { hashsync( $mysync, q{} ) } , 'hashsync: password1: Ö with eval' ) ;
5169
5170 $mysync->{ password1 } = 'Ö' ;
5171 is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hashsync( $mysync, q{} ), 'hashsync: password1: Ö without eval' ) ;
5172
5173 $mysync->{ password1 } = qq{\x{00D6}} ;
5174 is( 'bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a', $eval = eval { hashsync( $mysync, q{} ) }, 'hashsync: password1: \x{00D6}' ) ; #
5175
5176 print qq{1 00D6:Ö\n} ;
5177 print encode_utf8( qq{2 00D6:Ö\n} ) ;
5178 print qq{3 00D6:\x{00D6}\n} ;
5179 print encode_utf8( qq{4 00D6:\x{00D6}\n} ) ;
5180
5181
5182 print qq{5 6536:收\n} ;
5183 print encode_utf8( qq{6 6536:收\n} ) ;
5184 # the next one prints "Wide character in print at ./imapsync line xxxx"
5185 print qq{7 6536:\x{6536}\n} ;
5186 print encode_utf8( qq{8 6536:\x{6536}\n} ) ;
5187
5188 $mysync->{ password1 } = qq{收} ;
5189 is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hashsync( $mysync, q{} ), 'hashsync: password1: 收' ) ;
5190
5191 $mysync->{ password1 } = qq{\x{6536}} ;
5192 is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', $eval = eval{ hashsync( $mysync, q{} ) }, 'hashsync: password1: \x{6536} with eval' ) ;
5193
5194 # No side effect.
5195 $mysync->{ password1 } = 'abcd' ;
5196 is( 'afa29ab8534495251ac8346a985717c54bc49c26', hashsync( $mysync, q{} ), 'hashsync: password1: abcd again' ) ;
5197
5198 note( 'Leaving tests_hashsync()' ) ;
5199 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005200}
5201
5202sub hashsync
5203{
5204 my $mysync = shift ;
5205 my $hashkey = shift ;
5206
5207 my $mystring = join( q{},
5208 $mysync->{ host1 } || q{},
5209 $mysync->{ user1 } || q{},
5210 $mysync->{ password1 } || q{},
5211 $mysync->{ host2 } || q{},
5212 $mysync->{ user2 } || q{},
5213 $mysync->{ password2 } || q{},
5214 ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02005215 #my $hashsync = hmac_sha1_hex( $mystring, $hashkey ) ;
5216 my $hashsync = hmac_sha1_hex_robust( $mystring, $hashkey ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005217 #myprint( "$hashsync\n" ) ;
5218 return( $hashsync ) ;
5219}
5220
5221
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02005222sub tests_hmac_sha1_hex
5223{
5224 note( 'Entering tests_hmac_sha1_hex()' ) ;
5225
5226 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex( ), 'hmac_sha1_hex: no args => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
5227 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex( '' ), 'hmac_sha1_hex: empty string => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
5228 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex( '', '' ), 'hmac_sha1_hex: empty strings => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
5229 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex( '', '', 'caca' ), 'hmac_sha1_hex: empty strings + caca => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
5230
5231 # Good
5232 is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hmac_sha1_hex( 'Ö' ), 'hmac_sha1_hex: Ö => f1a3f3dac3f137fd658027c11678b895f773ce55' ) ;
5233 is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hmac_sha1_hex( encode_utf8(qq{\x{00D6}}) ), 'hmac_sha1_hex: encode_utf8 \x{00D6} => f1a3f3dac3f137fd658027c11678b895f773ce55' ) ;
5234 # Bad
5235 is( 'fe8dc3b9ba3e8850bb4a7b070b2279e911003af2', hmac_sha1_hex( encode_utf8( 'Ö' ) ), 'hmac_sha1_hex: encode_utf8 Ö => fe8dc3b9ba3e8850bb4a7b070b2279e911003af2' ) ;
5236 is( 'bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a', hmac_sha1_hex( qq{\x{00D6}} ), 'hmac_sha1_hex: qq{\x{00D6}} => bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a' ) ;
5237
5238 # Good
5239 is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex( 'A' ), 'hmac_sha1_hex: A => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
5240 is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex( encode_utf8(qq{\x{0041}}) ), 'hmac_sha1_hex: encode_utf8 \x{0041} => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
5241 is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex( encode_utf8( 'A' ) ), 'hmac_sha1_hex: encode_utf8 A => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
5242 is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex( qq{\x{0041}} ), 'hmac_sha1_hex: \x{0041} => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
5243
5244 # Good
5245 is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex( 'A', 'B' ), 'hmac_sha1_hex: A B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
5246 is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex( encode_utf8(qq{\x{0041}}), 'B' ), 'hmac_sha1_hex: encode_utf8 \x{0041} B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
5247 is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex( encode_utf8( 'A' ), 'B' ), 'hmac_sha1_hex: encode_utf8 A B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
5248 is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex( qq{\x{0041}}, 'B' ), 'hmac_sha1_hex: \x{0041} B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
5249
5250 # http://unicode.scarfboy.com/?s=U%2B6536
5251 # Good
5252 is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex( '收' ), 'hmac_sha1_hex: 收 => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ;
5253 is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex( encode_utf8(qq{\x{6536}}) ), 'hmac_sha1_hex: encode_utf8 \x{6536} => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ;
5254 # Bad
5255 is( 'e82217119628ad03e659cc89671d05ea4cee7238', hmac_sha1_hex( encode_utf8( '收' ) ), 'hmac_sha1_hex: encode_utf8 收 => e82217119628ad03e659cc89671d05ea4cee7238' ) ;
5256 # Very very bad, perl dies...
5257 #is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex( qq{\x{6536}} ), 'hmac_sha1_hex: \x{6536} => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ;
5258 # Ok but well, bad indeed
5259 is( undef, my $eval = eval{ hmac_sha1_hex( qq{\x{6536}} ) }, 'hmac_sha1_hex: \x{6536} => undef' ) ;
5260
5261
5262 note( 'Leaving tests_hmac_sha1_hex()' ) ;
5263 return ;
5264}
5265
5266sub tests_hmac_sha1_hex_robust
5267{
5268 note( 'Entering tests_hmac_sha1_hex_robust()' ) ;
5269
5270 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex_robust( ), 'hmac_sha1_hex_robust: no args => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
5271 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex_robust( '' ), 'hmac_sha1_hex_robust: empty string => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
5272 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex_robust( '', '' ), 'hmac_sha1_hex_robust: empty strings => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
5273 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex_robust( '', '', 'caca' ), 'hmac_sha1_hex_robust: empty strings + caca => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
5274
5275 # Good
5276 is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hmac_sha1_hex_robust( 'Ö' ), 'hmac_sha1_hex_robust: Ö => f1a3f3dac3f137fd658027c11678b895f773ce55' ) ;
5277 is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hmac_sha1_hex_robust( encode_utf8(qq{\x{00D6}}) ), 'hmac_sha1_hex_robust: encode_utf8 \x{00D6} => f1a3f3dac3f137fd658027c11678b895f773ce55' ) ;
5278 # Bad
5279 is( 'fe8dc3b9ba3e8850bb4a7b070b2279e911003af2', hmac_sha1_hex_robust( encode_utf8( 'Ö' ) ), 'hmac_sha1_hex_robust: encode_utf8 Ö => fe8dc3b9ba3e8850bb4a7b070b2279e911003af2' ) ;
5280 is( 'bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a', hmac_sha1_hex_robust( qq{\x{00D6}} ), 'hmac_sha1_hex_robust: qq{\x{00D6}} => bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a' ) ;
5281
5282 # Good
5283 is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex_robust( 'A' ), 'hmac_sha1_hex_robust: A => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
5284 is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex_robust( encode_utf8(qq{\x{0041}}) ), 'hmac_sha1_hex_robust: encode_utf8 \x{0041} => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
5285 is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex_robust( encode_utf8( 'A' ) ), 'hmac_sha1_hex_robust: encode_utf8 A => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
5286 is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex_robust( qq{\x{0041}} ), 'hmac_sha1_hex_robust: \x{0041} => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
5287
5288 # Good
5289 is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex_robust( 'A', 'B' ), 'hmac_sha1_hex_robust: A B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
5290 is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex_robust( encode_utf8(qq{\x{0041}}), 'B' ), 'hmac_sha1_hex_robust: encode_utf8 \x{0041} B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
5291 is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex_robust( encode_utf8( 'A' ), 'B' ), 'hmac_sha1_hex_robust: encode_utf8 A B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
5292 is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex_robust( qq{\x{0041}}, 'B' ), 'hmac_sha1_hex_robust: \x{0041} B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
5293
5294 # http://unicode.scarfboy.com/?s=U%2B6536
5295 # Good
5296 is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex_robust( '收' ), 'hmac_sha1_hex_robust: 收 => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ;
5297 is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex_robust( encode_utf8(qq{\x{6536}}) ), 'hmac_sha1_hex_robust: encode_utf8 \x{6536} => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ;
5298 # Bad
5299 is( 'e82217119628ad03e659cc89671d05ea4cee7238', hmac_sha1_hex_robust( encode_utf8( '收' ) ), 'hmac_sha1_hex_robust: encode_utf8 收 => e82217119628ad03e659cc89671d05ea4cee7238' ) ;
5300 # Good
5301 is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex_robust( qq{\x{6536}} ), 'hmac_sha1_hex_robust: \x{6536} => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ;
5302 # Good again
5303 is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', my $eval = eval{ hmac_sha1_hex_robust( qq{\x{6536}} ) }, 'hmac_sha1_hex_robust: \x{6536} => undef' ) ;
5304
5305 note( 'Leaving tests_hmac_sha1_hex_robust()' ) ;
5306 return ;
5307}
5308
5309
5310sub hmac_sha1_hex_robust
5311{
5312 my $string = shift ;
5313 my $val ;
5314 if ( defined( $val = eval{ hmac_sha1_hex( $string, @ARG ) } ) )
5315 {
5316 return $val ;
5317 }
5318 elsif( defined( $val = eval{ hmac_sha1_hex( encode_utf8( $string ), @ARG ) } ) )
5319 {
5320 return $val ;
5321 }
5322 else
5323 {
5324 return ;
5325 }
5326}
5327
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005328sub tests_createhashfileifneeded
5329{
5330 note( 'Entering tests_createhashfileifneeded()' ) ;
5331
5332 is( undef, createhashfileifneeded( ), 'createhashfileifneeded: no parameters => undef' ) ;
5333
5334 note( 'Leaving tests_createhashfileifneeded()' ) ;
5335 return ;
5336}
5337
5338sub createhashfileifneeded
5339{
5340 my $hashfile = shift ;
5341 my $hashkey = shift || rand32( ) ;
5342
5343 # no name
5344 if ( ! $hashfile ) {
5345 return ;
5346 }
5347 # already there
5348 if ( -e -r $hashfile ) {
5349 return $hashfile ;
5350 }
5351 # not creatable
5352 if ( ! -w dirname( $hashfile ) ) {
5353 return ;
5354 }
5355 # creatable
5356 open my $FILE_HANDLE, '>', $hashfile
5357 or do {
5358 myprint( "Could not open $hashfile for writing. Check permissions or disk space." ) ;
5359 return ;
5360 } ;
5361 myprint( "Writing random hashkey in $hashfile, once for all times\n" ) ;
5362 print $FILE_HANDLE $hashkey ;
5363 close $FILE_HANDLE ;
5364 # Should be there now
5365 if ( -e -r $hashfile ) {
5366 return $hashfile ;
5367 }
5368 # unknown failure
5369 return ;
5370}
5371
5372sub tests_rand32
5373{
5374 note( 'Entering tests_rand32()' ) ;
5375
5376 my $string = rand32( ) ;
5377 myprint( "$string\n" ) ;
5378 is( 32, length( $string ), 'rand32: 32 characters long' ) ;
5379 is( 32, length( rand32( ) ), 'rand32: 32 characters long, another one' ) ;
5380
5381 note( 'Leaving tests_rand32()' ) ;
5382 return ;
5383}
5384
5385sub rand32
5386{
5387 my @chars = ( "a".."z" ) ;
5388 my $string;
5389 $string .= $chars[rand @chars] for 1..32 ;
5390 return $string ;
5391}
5392
5393sub imap_id_stuff
5394{
5395 my $mysync = shift ;
5396
5397 if ( not $mysync->{id} ) { return ; } ;
5398
5399 $mysync->{h1_imap_id} = imap_id( $mysync, $mysync->{imap1}, 'Host1' ) ;
5400 #myprint( 'Host1: ' . $mysync->{h1_imap_id} ) ;
5401 $mysync->{h2_imap_id} = imap_id( $mysync, $mysync->{imap2}, 'Host2' ) ;
5402 #myprint( 'Host2: ' . $mysync->{h2_imap_id} ) ;
5403
5404 return ;
5405}
5406
5407sub imap_id
5408{
5409 my ( $mysync, $imap, $Side ) = @_ ;
5410
5411 if ( not $mysync->{id} ) { return q{} ; } ;
5412
5413 $Side ||= q{} ;
5414 my $imap_id_response = q{} ;
5415
5416 if ( not $imap->has_capability( 'ID' ) ) {
5417 $imap_id_response = 'No ID capability' ;
5418 myprint( "$Side: No ID capability\n" ) ;
5419 }else{
5420 my $id_inp = imapsync_id( $mysync, { side => lc $Side } ) ;
5421 myprint( "\n$Side: found ID capability. Sending/receiving ID, presented in raw IMAP for now.\n"
5422 . "In order to avoid sending/receiving ID, use option --noid\n" ) ;
5423 my $debug_before = $imap->Debug( ) ;
5424 $imap->Debug( 1 ) ;
5425 my $id_out = $imap->tag_and_run( 'ID ' . $id_inp ) ;
5426 #my $id_out = $imap->tag_and_run( 'ID NIL' ) ;
5427 myprint( "\n" ) ;
5428 $imap->Debug( $debug_before ) ;
5429 #$imap_id_response = Data::Dumper->Dump( [ $id_out ], [ 'IMAP_ID' ] ) ;
5430 }
5431 return( $imap_id_response ) ;
5432}
5433
5434sub imapsync_id
5435{
5436 my $mysync = shift ;
5437 my $overhashref = shift ;
5438 # See http://tools.ietf.org/html/rfc2971.html
5439
5440 my $imapsync_id = { } ;
5441
5442 my $imapsync_id_lamiral = {
5443 name => 'imapsync',
5444 version => imapsync_version( $mysync ),
5445 os => $OSNAME,
5446 vendor => 'Gilles LAMIRAL',
5447 'support-url' => 'https://imapsync.lamiral.info/',
5448 # Example of date-time: 19-Sep-2015 08:56:07
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01005449 date => date_from_rcs( q{$Date: 2022/01/12 21:28:37 $ } ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005450 } ;
5451
5452 my $imapsync_id_github = {
5453 name => 'imapsync',
5454 version => imapsync_version( $mysync ),
5455 os => $OSNAME,
5456 vendor => 'github',
5457 'support-url' => 'https://github.com/imapsync/imapsync',
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01005458 date => date_from_rcs( q{$Date: 2022/01/12 21:28:37 $ } ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005459 } ;
5460
5461 $imapsync_id = $imapsync_id_lamiral ;
5462 #$imapsync_id = $imapsync_id_github ;
5463 my %mix = ( %{ $imapsync_id }, %{ $overhashref } ) ;
5464 my $imapsync_id_str = format_for_imap_arg( \%mix ) ;
5465 #myprint( "$imapsync_id_str\n" ) ;
5466 return( $imapsync_id_str ) ;
5467}
5468
5469sub tests_imapsync_id
5470{
5471 note( 'Entering tests_imapsync_id()' ) ;
5472
5473 my $mysync ;
5474 ok( '("name" "imapsync" "version" "111" "os" "beurk" "vendor" "Gilles LAMIRAL" "support-url" "https://imapsync.lamiral.info/" "date" "22-12-1968" "side" "host1")'
5475 eq imapsync_id( $mysync,
5476 {
5477 version => 111,
5478 os => 'beurk',
5479 date => '22-12-1968',
5480 side => 'host1'
5481 }
5482 ),
5483 'tests_imapsync_id override'
5484 ) ;
5485
5486 note( 'Leaving tests_imapsync_id()' ) ;
5487 return ;
5488}
5489
5490sub format_for_imap_arg
5491{
5492 my $ref = shift ;
5493
5494 my $string = q{} ;
5495 my %terms = %{ $ref } ;
5496 my @terms = ( ) ;
5497 if ( not ( %terms ) ) { return( 'NIL' ) } ;
5498 # sort like in RFC then add extra key/values
5499 foreach my $key ( qw( name version os os-version vendor support-url address date command arguments environment) ) {
5500 if ( $terms{ $key } ) {
5501 push @terms, $key, $terms{ $key } ;
5502 delete $terms{ $key } ;
5503 }
5504 }
5505 push @terms, %terms ;
5506 $string = '(' . ( join q{ }, map { '"' . $_ . '"' } @terms ) . ')' ;
5507 return( $string ) ;
5508}
5509
5510
5511
5512sub tests_format_for_imap_arg
5513{
5514 note( 'Entering tests_format_for_imap_arg()' ) ;
5515
5516 ok( 'NIL' eq format_for_imap_arg( { } ), 'format_for_imap_arg empty hash ref' ) ;
5517 ok( '("name" "toto")' eq format_for_imap_arg( { name => 'toto' } ), 'format_for_imap_arg { name => toto }' ) ;
5518 ok( '("name" "toto" "key" "val")' eq format_for_imap_arg( { name => 'toto', key => 'val' } ), 'format_for_imap_arg 2 x key val' ) ;
5519
5520 note( 'Leaving tests_format_for_imap_arg()' ) ;
5521 return ;
5522}
5523
5524sub quota
5525{
5526 my ( $mysync, $imap, $side ) = @_ ;
5527
5528 my %side = (
5529 h1 => 'Host1',
5530 h2 => 'Host2',
5531 ) ;
5532 my $Side = $side{ $side } ;
5533 my $debug_before = $imap->Debug( ) ;
5534 $imap->Debug( 1 ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01005535 if ( not $imap->has_capability( 'QUOTA' ) )
5536 {
5537 myprint( "$Side: No QUOTA capability found, skipping it.\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005538 $imap->Debug( $debug_before ) ;
5539 return ;
5540 } ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01005541 myprint( "\n$Side: QUOTA capability found, presented in raw IMAP on next lines\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005542 my $getquotaroot = $imap->getquotaroot( 'INBOX' ) ;
5543 # Gmail INBOX quotaroot is "" but with it Mail::IMAPClient does a literal GETQUOTA {2} \n ""
5544 #$imap->quota( 'ROOT' ) ;
5545 #$imap->quota( '""' ) ;
5546 myprint( "\n" ) ;
5547 $imap->Debug( $debug_before ) ;
5548 my $quota_limit_bytes = quota_extract_storage_limit_in_bytes( $mysync, $getquotaroot ) ;
5549 my $quota_current_bytes = quota_extract_storage_current_in_bytes( $mysync, $getquotaroot ) ;
5550 $mysync->{$side}->{quota_limit_bytes} = $quota_limit_bytes ;
5551 $mysync->{$side}->{quota_current_bytes} = $quota_current_bytes ;
5552 my $quota_percent ;
5553 if ( $quota_limit_bytes > 0 ) {
5554 $quota_percent = mysprintf( '%.2f', $NUMBER_100 * $quota_current_bytes / $quota_limit_bytes ) ;
5555 }else{
5556 $quota_percent = 0 ;
5557 }
5558 myprint( "$Side: Quota current storage is $quota_current_bytes bytes. Limit is $quota_limit_bytes bytes. So $quota_percent % full\n" ) ;
5559 if ( $QUOTA_PERCENT_LIMIT < $quota_percent ) {
5560 my $error = "$Side: $quota_percent % full: it is time to find a bigger place! ( $quota_current_bytes bytes / $quota_limit_bytes bytes )\n" ;
5561 errors_incr( $mysync, $error ) ;
5562 }
5563 return ;
5564}
5565
5566sub tests_quota_extract_storage_limit_in_bytes
5567{
5568 note( 'Entering tests_quota_extract_storage_limit_in_bytes()' ) ;
5569
5570 my $mysync = {} ;
5571 my $imap_output = [
5572 '* QUOTAROOT "INBOX" "Storage quota" "Messages quota"',
5573 '* QUOTA "Storage quota" (STORAGE 1 104857600)',
5574 '* QUOTA "Messages quota" (MESSAGE 2 100000)',
5575 '5 OK Getquotaroot completed.'
5576 ] ;
5577 ok( $NUMBER_104_857_600 * $KIBI == quota_extract_storage_limit_in_bytes( $mysync, $imap_output ), 'quota_extract_storage_limit_in_bytes ') ;
5578
5579 note( 'Leaving tests_quota_extract_storage_limit_in_bytes()' ) ;
5580 return ;
5581}
5582
5583sub quota_extract_storage_limit_in_bytes
5584{
5585 my $mysync = shift ;
5586 my $imap_output = shift ;
5587
5588 my $limit_kb ;
5589 $limit_kb = ( map { /.*\(\s*STORAGE\s+\d+\s+(\d+)\s*\)/x ? $1 : () } @{ $imap_output } )[0] ;
5590 $limit_kb ||= 0 ;
5591 $mysync->{ debug } and myprint( "storage_limit_kb = $limit_kb\n" ) ;
5592 return( $KIBI * $limit_kb ) ;
5593}
5594
5595
5596sub tests_quota_extract_storage_current_in_bytes
5597{
5598 note( 'Entering tests_quota_extract_storage_current_in_bytes()' ) ;
5599
5600 my $mysync = {} ;
5601 my $imap_output = [
5602 '* QUOTAROOT "INBOX" "Storage quota" "Messages quota"',
5603 '* QUOTA "Storage quota" (STORAGE 1 104857600)',
5604 '* QUOTA "Messages quota" (MESSAGE 2 100000)',
5605 '5 OK Getquotaroot completed.'
5606 ] ;
5607 ok( 1*$KIBI == quota_extract_storage_current_in_bytes( $mysync, $imap_output ), 'quota_extract_storage_current_in_bytes: 1 => 1024 ') ;
5608
5609 note( 'Leaving tests_quota_extract_storage_current_in_bytes()' ) ;
5610 return ;
5611}
5612
5613sub quota_extract_storage_current_in_bytes
5614{
5615 my $mysync = shift ;
5616 my $imap_output = shift ;
5617
5618 my $current_kb ;
5619 $current_kb = ( map { /.*\(\s*STORAGE\s+(\d+)\s+\d+\s*\)/x ? $1 : () } @{ $imap_output } )[0] ;
5620 $current_kb ||= 0 ;
5621 $mysync->{ debug } and myprint( "storage_current_kb = $current_kb\n" ) ;
5622 return( $KIBI * $current_kb ) ;
5623
5624}
5625
5626
5627sub automap
5628{
5629 my ( $mysync ) = @_ ;
5630
5631 if ( $mysync->{automap} ) {
5632 myprint( "Turned on automapping folders ( use --noautomap to turn off automapping )\n" ) ;
5633 }else{
5634 myprint( "Turned off automapping folders ( use --automap to turn on automapping )\n" ) ;
5635 return ;
5636 }
5637
5638 $mysync->{h1_special} = special_from_folders_hash( $mysync, $mysync->{imap1}, 'Host1' ) ;
5639 $mysync->{h2_special} = special_from_folders_hash( $mysync, $mysync->{imap2}, 'Host2' ) ;
5640
5641 build_possible_special( $mysync ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01005642 build_guess_special( $mysync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005643 build_automap( $mysync ) ;
5644
5645 return ;
5646}
5647
5648
5649
5650
5651sub build_guess_special
5652{
5653 my ( $mysync ) = shift ;
5654
5655 foreach my $h1_fold ( sort keys %{ $mysync->{h1_folders_all} } ) {
5656 my $special = guess_special( $h1_fold, $mysync->{possible_special}, $mysync->{h1_prefix} ) ;
5657 if ( $special ) {
5658 $mysync->{h1_special_guessed}{$h1_fold} = $special ;
5659 my $already_guessed = $mysync->{h1_special_guessed}{$special} ;
5660 if ( $already_guessed ) {
5661 myprint( "Host1: $h1_fold not $special because set to $already_guessed\n" ) ;
5662 }else{
5663 $mysync->{h1_special_guessed}{$special} = $h1_fold ;
5664 }
5665 }
5666 }
5667 foreach my $h2_fold ( sort keys %{ $mysync->{h2_folders_all} } ) {
5668 my $special = guess_special( $h2_fold, $mysync->{possible_special}, $mysync->{h2_prefix} ) ;
5669 if ( $special ) {
5670 $mysync->{h2_special_guessed}{$h2_fold} = $special ;
5671 my $already_guessed = $mysync->{h2_special_guessed}{$special} ;
5672 if ( $already_guessed ) {
5673 myprint( "Host2: $h2_fold not $special because set to $already_guessed\n" ) ;
5674 }else{
5675 $mysync->{h2_special_guessed}{$special} = $h2_fold ;
5676 }
5677 }
5678 }
5679 return ;
5680}
5681
5682sub guess_special
5683{
5684 my( $folder, $possible_special_ref, $prefix ) = @_ ;
5685
5686 my $folder_no_prefix = $folder ;
5687 $folder_no_prefix =~ s/\Q${prefix}\E//xms ;
5688 #$debug and myprint( "folder_no_prefix: $folder_no_prefix\n" ) ;
5689
5690 my $guess_special = $possible_special_ref->{ $folder }
5691 || $possible_special_ref->{ $folder_no_prefix }
5692 || q{} ;
5693
5694 return( $guess_special ) ;
5695}
5696
5697sub tests_guess_special
5698{
5699 note( 'Entering tests_guess_special()' ) ;
5700
5701 my $possible_special_ref = build_possible_special( my $mysync ) ;
5702 ok( '\Sent' eq guess_special( 'Sent', $possible_special_ref, q{} ) ,'guess_special: Sent => \Sent' ) ;
5703 ok( q{} eq guess_special( 'Blabla', $possible_special_ref, q{} ) ,'guess_special: Blabla => q{}' ) ;
5704 ok( '\Sent' eq guess_special( 'INBOX.Sent', $possible_special_ref, 'INBOX.' ) ,'guess_special: INBOX.Sent => \Sent' ) ;
5705 ok( '\Sent' eq guess_special( 'IN BOX.Sent', $possible_special_ref, 'IN BOX.' ) ,'guess_special: IN BOX.Sent => \Sent' ) ;
5706
5707 note( 'Leaving tests_guess_special()' ) ;
5708 return ;
5709}
5710
5711sub build_automap
5712{
5713 my $mysync = shift ;
5714 $mysync->{ debug } and myprint( "Entering build_automap\n" ) ;
5715 foreach my $h1_fold ( @{ $mysync->{h1_folders_wanted} } ) {
5716 my $h2_fold ;
5717 my $h1_special = $mysync->{h1_special}{$h1_fold} ;
5718 my $h1_special_guessed = $mysync->{h1_special_guessed}{$h1_fold} ;
5719
5720 # Case 1: special on both sides.
5721 if ( $h1_special
5722 and exists $mysync->{h2_special}{$h1_special} ) {
5723 $h2_fold = $mysync->{h2_special}{$h1_special} ;
5724 $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ;
5725 next ;
5726 }
5727 # Case 2: special on host1, not on host2
5728 if ( $h1_special
5729 and ( not exists $mysync->{h2_special}{$h1_special} )
5730 and ( exists $mysync->{h2_special_guessed}{$h1_special} )
5731 ) {
5732 # special_guessed on host2
5733 $h2_fold = $mysync->{h2_special_guessed}{$h1_special} ;
5734 $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ;
5735 next ;
5736 }
5737 # Case 3: no special on host1, special on host2
5738 if ( ( not $h1_special )
5739 and ( $h1_special_guessed )
5740 and ( exists $mysync->{h2_special}{$h1_special_guessed} )
5741 ) {
5742 $h2_fold = $mysync->{h2_special}{$h1_special_guessed} ;
5743 $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ;
5744 next ;
5745 }
5746 # Case 4: no special on both sides.
5747 if ( ( not $h1_special )
5748 and ( $h1_special_guessed )
5749 and ( not exists $mysync->{h2_special}{$h1_special_guessed} )
5750 and ( exists $mysync->{h2_special_guessed}{$h1_special_guessed} )
5751 ) {
5752 $h2_fold = $mysync->{h2_special_guessed}{$h1_special_guessed} ;
5753 $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ;
5754 next ;
5755 }
5756 }
5757 return( $mysync->{f1f2auto} ) ;
5758}
5759
5760# I will not add what there is at:
5761# http://stackoverflow.com/questions/2185391/localized-gmail-imap-folders/2185548#2185548
5762# because it works well without
5763sub build_possible_special
5764{
5765 my $mysync = shift ;
5766 my $possible_special = { } ;
5767 # All|Archive|Drafts|Flagged|Junk|Sent|Trash
5768
5769 $possible_special->{'\All'} = [ 'All', 'All Messages', '&BBIEQQQ1-' ] ;
5770 $possible_special->{'\Archive'} = [ 'Archive', 'Archives', '&BBAEQARFBDgEMg-' ] ;
5771 $possible_special->{'\Drafts'} = [ 'Drafts', 'DRAFTS', '&BCcENQRABD0EPgQyBDgEOgQ4-', 'Szkice', 'Wersje robocze' ] ;
5772 $possible_special->{'\Flagged'} = [ 'Flagged', 'Starred', '&BB8EPgQ8BDUERwQ1BD0EPQRLBDU-' ] ;
5773 $possible_special->{'\Junk'} = [ 'Junk', 'junk', 'Spam', 'SPAM', '&BCEEPwQwBDw-',
5774 'Potwierdzony spam', 'Wiadomo&AVs-ci-&AVs-mieci',
5775 'Junk E-Mail', 'Junk Email'] ;
5776 $possible_special->{'\Sent'} = [ 'Sent', 'Sent Messages', 'Sent Items',
5777 'Gesendete Elemente', 'Gesendete Objekte',
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01005778 '&AMk-l&AOk-ments envoy&AOk-s', 'E&AwE-le&AwE-ments envoye&AwE-s', 'Envoy&AOk-', 'Objets envoy&AOk-s',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005779 'Elementos enviados',
5780 '&kAFP4W4IMH8wojCkMMYw4A-',
5781 '&BB4EQgQ,BEAEMAQyBDsENQQ9BD0ESwQ1-',
5782 'Elementy wys&AUI-ane'] ;
5783 $possible_special->{'\Trash'} = [ 'Trash', 'TRASH',
5784 '&BCMENAQwBDsENQQ9BD0ESwQ1-', '&BBoEPgRABDcEOAQ9BDA-',
5785 'Kosz',
5786 'Deleted Items', 'Deleted Messages' ] ;
5787
5788
5789 foreach my $special ( qw( \All \Archive \Drafts \Flagged \Junk \Sent \Trash ) ){
5790 foreach my $possible_folder ( @{ $possible_special->{$special} } ) {
5791 $possible_special->{ $possible_folder } = $special ;
5792 } ;
5793 }
5794 $mysync->{possible_special} = $possible_special ;
5795 $mysync->{ debug } and myprint( Data::Dumper->Dump( [ $possible_special ], [ 'possible_special' ] ) ) ;
5796 return( $possible_special ) ;
5797}
5798
5799sub tests_special_from_folders_hash
5800{
5801 note( 'Entering tests_special_from_folders_hash()' ) ;
5802
5803 my $mysync = {} ;
5804 require_ok( "Test::MockObject" ) ;
5805 my $imapT = Test::MockObject->new( ) ;
5806
5807 is( undef, special_from_folders_hash( ), 'special_from_folders_hash: no args' ) ;
5808 is( undef, special_from_folders_hash( $mysync ), 'special_from_folders_hash: undef args' ) ;
5809 is_deeply( {}, special_from_folders_hash( $mysync, $imapT ), 'special_from_folders_hash: $imap void' ) ;
5810
5811 $imapT->mock( 'folders_hash', sub { return( [ { name => 'Sent', attrs => [ '\Sent' ] } ] ) } ) ;
5812
5813 is_deeply( { Sent => '\Sent', '\Sent' => 'Sent' },
5814 special_from_folders_hash( $mysync, $imapT ), 'special_from_folders_hash: $imap \Sent' ) ;
5815
5816 note( 'Leaving tests_special_from_folders_hash()' ) ;
5817 return( ) ;
5818}
5819
5820sub special_from_folders_hash
5821{
5822 my ( $mysync, $imap, $side ) = @_ ;
5823 my %special = ( ) ;
5824
5825 if ( ! defined $imap ) { return ; }
5826 $side = defined $side ? $side : 'Host?' ;
5827
5828 if ( ! $imap->can( 'folders_hash' ) ) {
5829 my $error = "$side: To have automagic rfc6154 folder mapping, upgrade Mail::IMAPClient >= 3.34\n" ;
5830 errors_incr( $mysync, $error ) ;
5831 return( \%special ) ; # empty hash ref
5832 }
5833 my $folders_hash = $imap->folders_hash( ) ;
5834 foreach my $fhash (@{ $folders_hash } ) {
5835 my @special = grep { /\\(?:All|Archive|Drafts|Flagged|Junk|Sent|Trash)/x } @{ $fhash->{attrs} } ;
5836 if ( @special ) {
5837 my $special = $special[0] ; # keep first one. Could be not very good.
5838 if ( exists $special{ $special } ) {
5839 myprintf( "%s: special %-20s = %s already assigned to %s\n",
5840 $side, $fhash->{name}, join( q{ }, @special ), $special{ $special } ) ;
5841 }else{
5842 myprintf( "%s: special %-20s = %s\n",
5843 $side, $fhash->{name}, join( q{ }, @special ) ) ;
5844 $special{ $special } = $fhash->{name} ;
5845 $special{ $fhash->{name} } = $special ; # double entry value => key
5846 }
5847 }
5848 }
5849 myprint( "\n" ) if ( %special ) ;
5850 return( \%special ) ;
5851}
5852
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005853
5854sub tests_errors_log
5855{
5856 note( 'Entering tests_errors_log()' ) ;
5857 is( undef, errors_log( ), 'errors_log: no args => undef' ) ;
5858 my $mysync = {} ;
5859 is( undef, errors_log( $mysync ), 'errors_log: empty => undef' ) ;
5860 is_deeply( [ 'aieaie' ], [ errors_log( $mysync, 'aieaie' ) ], 'errors_log: aieaie => aieaie' ) ;
5861 # cumulative
5862 is_deeply( [ 'aieaie' ], [ errors_log( $mysync ) ], 'errors_log: nothing more => aieaie' ) ;
5863 is_deeply( [ 'aieaie', 'ouille' ], [ errors_log( $mysync, 'ouille' ) ], 'errors_log: ouille => aieaie ouille' ) ;
5864 is_deeply( [ 'aieaie', 'ouille' ], [ errors_log( $mysync ) ], 'errors_log: nothing more => aieaie ouille' ) ;
5865 note( 'Leaving tests_errors_log()' ) ;
5866 return ;
5867}
5868
5869sub errors_log
5870{
5871 my ( $mysync, @error ) = @ARG ;
5872
5873 if ( ! $mysync->{errors_log} ) {
5874 $mysync->{errors_log} = [] ;
5875 }
5876
5877 if ( @error ) {
5878 push @{ $mysync->{errors_log} }, join( q{}, @error ) ;
5879 }
5880 if ( @{ $mysync->{errors_log} } ) {
5881 return @{ $mysync->{errors_log} } ;
5882 }
5883 else {
5884 return ;
5885 }
5886}
5887
5888
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01005889
5890sub tests_comment_of_error_type
5891{
5892 note( 'Entering tests_comment_of_error_type()' ) ;
5893
5894 is( undef, comment_of_error_type( ), 'comment_of_error_type: no args => undef' ) ;
5895
5896 my $mysync = { } ;
5897 is( undef, comment_of_error_type( $mysync ), 'comment_of_error_type: undef => undef' ) ;
5898
5899 is( "", comment_of_error_type( $mysync, '' ), 'comment_of_error_type: "" => ""' ) ;
5900 is( "", comment_of_error_type( $mysync, 'blabla' ), 'comment_of_error_type: blabla => ""' ) ;
5901
5902 is( "", comment_of_error_type( $mysync, 'ERR_UNCLASSIFIED' ), 'comment_of_error_type: ERR_UNCLASSIFIED => ""' ) ;
5903
5904 like( comment_of_error_type( $mysync, 'ERR_OVERQUOTA' ), qr{100% full}, 'comment_of_error_type: ERR_OVERQUOTA => matches 100% full' ) ;
5905
5906
5907
5908 note( 'Leaving tests_comment_of_error_type()' ) ;
5909 return ;
5910}
5911
5912sub comment_of_error_type
5913{
5914 my $mysync = shift @ARG ;
5915 my $error_type = shift @ARG ;
5916
5917 if ( ! defined $mysync ) { return ; }
5918 if ( ! defined $error_type ) { return ; }
5919
5920 my $comment ;
5921
5922 if ( exists( $COMMENT_OF_ERR_TYPE{ $error_type } ) )
5923 {
5924 $comment = $COMMENT_OF_ERR_TYPE{ $error_type }->( $mysync ) ;
5925 }
5926 else
5927 {
5928 $comment = "" ;
5929 }
5930 return $comment ;
5931}
5932
5933
5934
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02005935sub tests_error_type
5936{
5937 note( 'Entering tests_error_type()' ) ;
5938
5939 is( 'ERR_NOTHING_REPORTED', error_type( ), 'error_type: no args => ERR_NOTHING_REPORTED' ) ;
5940 is( 'ERR_NOTHING_REPORTED', error_type( '' ), 'error_type: empty string => ERR_NOTHING_REPORTED' ) ;
5941
5942 is( 'ERR_UNCLASSIFIED', error_type( 'ERR_UNCLASSIFIED' ), 'error_type: ERR_UNCLASSIFIED => ERR_UNCLASSIFIED' ) ;
5943 is( 'ERR_UNCLASSIFIED', error_type( 'aie' ), 'error_type: aie => ERR_UNCLASSIFIED' ) ;
5944 is( 'ERR_UNCLASSIFIED', error_type( 'ouille' ), 'error_type: ouille => ERR_UNCLASSIFIED' ) ;
5945
5946 is( 'ERR_Host1_FETCH', error_type( 'Message xxx could not be fetched: blabla' ),
5947 'error_type: could not be fetched => ERR_Host1_FETCH'
5948 ) ;
5949
5950 is( 'ERR_APPEND_SIZE',
5951 error_type( 'could not append message xxx: BAD maximum message size exceeded' ),
5952 'error_type: could not append message xxx: BAD maximum message size exceeded => ERR_APPEND_SIZE'
5953 ) ;
5954
5955 is( 'ERR_OVERQUOTA',
5956 error_type( 'Quota limit will be exceeded' ),
5957 'error_type: Quota limit will be exceeded => ERR_OVERQUOTA'
5958 ) ;
5959
5960 is( 'ERR_APPEND', error_type( 'could not append' ), 'error_type: could not append => ERR_APPEND' ) ;
5961
5962 is( 'ERR_CREATE',
5963 error_type( 'Could not create folder' ),
5964 'error_type: Could not create folder => ERR_CREATE'
5965 ) ;
5966
5967 is( 'ERR_SELECT',
5968 error_type( 'Could not select: blabla' ),
5969 'error_type: Could not select: blabla => ERR_SELECT'
5970 ) ;
5971
5972
5973 #
5974 #Maximum bytes transferred reached, 423 >= 100, ending sync
5975 is( 'ERR_TRANSFER_EXCEEDED',
5976 error_type( 'Maximum bytes transferred reached, blabla' ),
5977 'error_type: Maximum bytes transferred reached, blabla => ERR_TRANSFER_EXCEEDED'
5978 ) ;
5979
5980 #
5981 is( 'ERR_CONNECTION_FAILURE_HOST1',
5982 error_type( 'Host1 failure: can not open imap connection on host1 [badhostkaka] with user [tata]: Unable to connect to badhostkaka: Invalid argument' ),
5983 'error_type: can not open imap connection on host1 => ERR_CONNECTION_FAILURE_HOST1'
5984 ) ;
5985
5986 is( 'ERR_CONNECTION_FAILURE_HOST2',
5987 error_type( 'Host2 failure: can not open imap connection on host2 [badhostkiki] with user [titi]: Unable to connect to badhostkiki: Invalid argument' ),
5988 'error_type: can not open imap connection on host2 => ERR_CONNECTION_FAILURE_HOST2'
5989 ) ;
5990
5991 is( 'ERR_APPEND_VIRUS',
5992 error_type( 'could not append ( Subject:[For Your Consideration], Date:["29-Nov-2016 03:21:10 -0800"], Size:[5505], Flags:[\Seen] ) to folder INBOX: 275 NO Message refused because it contains a virus' ),
5993 'error_type: could not append ... virus => ERR_APPEND_VIRUS'
5994 ) ;
5995
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01005996
5997 is( 'ERR_FLAGS',
5998 error_type( 'Host2: flags msg INBOX/957910 could not add flags [PasGlop \PasGlopRe]: 33 NO Error in IMAP command received by server.' ),
5999 'error_type: could not add flags => ERR_FLAGS'
6000 ) ;
6001
6002
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006003 note( 'Leaving tests_error_type()' ) ;
6004 return ;
6005}
6006
6007
6008
6009# Could be implemented with https://metacpan.org/pod/Tie::RegexpHash
6010# with just a hash of error regexes as keys and types as values.
6011
6012sub error_type
6013{
6014 my $error = shift ;
6015
6016 if ( ! defined $error ) { return 'ERR_NOTHING_REPORTED' ; }
6017 if ( ! $error ) { return 'ERR_NOTHING_REPORTED' ; }
6018
6019 #
6020 if ( $error =~ m{Host1 failure: Error login on} ) { return 'ERR_AUTHENTICATION_FAILURE_USER1' } ;
6021 if ( $error =~ m{Host2 failure: Error login on} ) { return 'ERR_AUTHENTICATION_FAILURE_USER2' } ;
6022
6023 if ( $error =~ m{Host. failure: Can not go to tls encryption on host.} ) { return 'ERR_EXIT_TLS_FAILURE' } ;
6024 #
6025
6026 if ( $error =~ m{could not be fetched:} ) { return 'ERR_Host1_FETCH' } ;
6027
6028 # could not append .*BAD maximum message size exceeded
6029 # could not append.*Maximum size of appendable message has been exceeded
6030 if ( $error =~ m{could not append .*BAD maximum message size exceeded} )
6031 { return 'ERR_APPEND_SIZE' ; } ;
6032
6033 if ( $error =~ m{could not append.*Maximum size of appendable message has been exceeded} )
6034 { return 'ERR_APPEND_SIZE' ; } ;
6035
6036 # Could not create folder *[OVERQUOTA] Not enough disk quota
6037 # could not append .*[OVERQUOTA] Not enough disk quota
6038 # could not append .*[OVERQUOTA] Mailbox is full / Blocks limit exceeded / Inode limit exceeded
6039 if ( $error =~ m{OVERQUOTA} ) { return 'ERR_OVERQUOTA' ; } ;
6040 if ( $error =~ m{Quota limit will be exceeded} ) { return 'ERR_OVERQUOTA' ; } ;
6041 if ( $error =~ m{full: it is time to find a bigger place} ) { return 'ERR_OVERQUOTA' ; } ;
6042
6043 # could not append ... to folder INBOX: 276 NO Message refused because it contains a virus
6044 if ( $error =~ m{could not append.*virus} )
6045 { return 'ERR_APPEND_VIRUS' ; } ;
6046
6047 # could not append .*Write failed 'Broken pipe'
6048 # could not append .*timeout waiting .* for data from server
6049 # could not append .*BAD Invalid Arguments: Unable to parse message
6050 # could not append .*BAD Command Argument Error. 11
6051 # could not append .*NO header limit reached
6052 if ( $error =~ m{could not append} ) { return 'ERR_APPEND' ; } ;
6053
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01006054 # could not add flags
6055 if ( $error =~ m{could not add flags} ) { return 'ERR_FLAGS' ; } ;
6056
6057
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006058 # Could not create folder .*Invalid mailbox name
6059 if ( $error =~ m{Could not create folder} ) { return 'ERR_CREATE' ; } ;
6060
6061
6062 # Could not select:.*NO [NOPERM] Permission denied
6063 # Could not select:.*NO Mailbox doesn't exist
6064 # Could not select:.*NO [SERVERBUG] Internal error occurred.
6065 # Could not select:.*[CANNOT] Mailbox isn't a valid mbox file
6066 if ( $error =~ m{Could not select:} ) { return 'ERR_SELECT' ; } ;
6067
6068 #Maximum bytes transferred reached, 423 >= 100, ending sync
6069 if ( $error =~ m{Maximum bytes transferred reached} ) { return 'ERR_TRANSFER_EXCEEDED' ; } ;
6070
6071 if ( $error =~ m{can not open imap connection on host1} ) { return 'ERR_CONNECTION_FAILURE_HOST1' ; } ;
6072 if ( $error =~ m{can not open imap connection on host2} ) { return 'ERR_CONNECTION_FAILURE_HOST2' ; } ;
6073
6074 # Default is ERR_UNCLASSIFIED
6075 return 'ERR_UNCLASSIFIED' ;
6076
6077}
6078
6079sub tests_errorclassify
6080{
6081 note( 'Entering tests_errorclassify()' ) ;
6082
6083 is( undef, errorclassify( ), 'errorclassify: no args => undef' ) ;
6084
6085 is_deeply( { 'ERR_UNCLASSIFIED' => 1 }, errorclassify( 'aie' ), 'errorclassify: aie => { ERR_UNCLASSIFIED => 1 }' ) ;
6086 is_deeply( { 'ERR_UNCLASSIFIED' => 2 }, errorclassify( 'aie', 'ouille' ), 'errorclassify: aie ouille => { ERR_UNCLASSIFIED => 2 }' ) ;
6087 is_deeply( { 'ERR_UNCLASSIFIED' => 2, 'ERR_NOTHING_REPORTED' => 1 }, errorclassify( 'aie', 'ouille', '' ), 'errorclassify: aie ouille "" => { ERR_UNCLASSIFIED => 2 }' ) ;
6088 is_deeply( { 'ERR_UNCLASSIFIED' => 3 }, errorclassify( 'aie', 'ouille', 'aie' ), 'errorclassify: aie ouille aie => { ERR_UNCLASSIFIED => 3 }' ) ;
6089 is_deeply( { 'ERR_UNCLASSIFIED' => 1, 'ERR_OVERQUOTA' => 2 }, errorclassify( 'aie', 'OVERQUOTA pipi', 'OVERQUOTA caca' ), 'errorclassify: aie OVERQUOTA OVERQUOTA' ) ;
6090 is_deeply( { 'ERR_NOTHING_REPORTED' => 1 }, errorclassify( '' ), 'errorclassify: "" => { ERR_NOTHING_REPORTED => 1 }' ) ;
6091 is_deeply( { 'ERR_NOTHING_REPORTED' => 2 }, errorclassify( '', '' ), 'errorclassify: "", "" => { ERR_NOTHING_REPORTED => 1 }' ) ;
6092
6093 note( 'Leaving tests_errorclassify()' ) ;
6094 return ;
6095}
6096
6097
6098
6099sub errorclassify
6100{
6101 my @errors = @ARG ;
6102
6103 if ( ! @errors ) { return ; } ;
6104
6105 my $error_type_count = { } ;
6106 foreach my $error ( @errors )
6107 {
6108 my $error_type = error_type( $error ) ;
6109 $error_type_count->{ $error_type }++ ;
6110 }
6111
6112 return $error_type_count ;
6113}
6114
6115sub tests_most_common_error
6116{
6117 note( 'Entering tests_most_common_error()' ) ;
6118
6119 is( 'ERR_NOTHING_REPORTED', most_common_error( ), 'most_common_error: no args => ERR_NOTHING_REPORTED' ) ;
6120 is( 'ERR_NOTHING_REPORTED', most_common_error( {} ), 'most_common_error: empty hash ref => ERR_NOTHING_REPORTED' ) ;
6121 is( 'ERR_NOTHING_REPORTED', most_common_error( 'blabla' ), 'most_common_error: not a hash ref => ERR_NOTHING_REPORTED' ) ;
6122
6123 is( 'ERR_FOO', most_common_error( { ERR_FOO => 1 } ), 'most_common_error: { ERR_FOO => 1 } => ERR_FOO' ) ;
6124 is( 'ERR_BAR', most_common_error( { ERR_FOO => 1, ERR_BAR => 2 } ), 'most_common_error: { ERR_FOO => 1, ERR_BAR => 2 } => ERR_BAR' ) ;
6125 is( 'ERR_FOO', most_common_error( { ERR_FOO => 2, ERR_BAR => 1 } ), 'most_common_error: { ERR_FOO => 2, ERR_BAR => 1 } => ERR_FOO' ) ;
6126 # exaequo => first lexical wins. ERR_BAR <= ERR_FOO
6127 is( 'ERR_BAR', most_common_error( { ERR_FOO => 2, ERR_BAR => 2 } ), 'most_common_error: { ERR_FOO => 2, ERR_BAR => 2 } => ERR_BAR' ) ;
6128
6129 is( 'A', most_common_error( { A => 5, B => 5, C => 5 } ), 'most_common_error: { A => 5, B => 5, C => 5 } => A' ) ;
6130 is( 'B', most_common_error( { A => 5, B => 6, C => 6 } ), 'most_common_error: { A => 5, B => 6, C => 6 } => B' ) ;
6131 is( 'C', most_common_error( { A => 5, B => 5, C => 7 } ), 'most_common_error: { A => 5, B => 5, C => 7 } => C' ) ;
6132 is( 'C', most_common_error( { A => 5, B => 6, C => 7 } ), 'most_common_error: { A => 5, B => 5, C => 7 } => C' ) ;
6133
6134 note( 'Leaving tests_most_common_error()' ) ;
6135 return ;
6136}
6137
6138
6139
6140sub most_common_error
6141{
6142 my $errors_counted_ref = shift ;
6143
6144 if ( ! defined $errors_counted_ref ) { return 'ERR_NOTHING_REPORTED' ; }
6145
6146 if ( 'HASH' ne ref $errors_counted_ref ) { return 'ERR_NOTHING_REPORTED' ; }
6147
6148 # empty hash
6149 if ( !%{ $errors_counted_ref } ) { return 'ERR_NOTHING_REPORTED' ; }
6150
6151 # non empty hash
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01006152 # in case of equality the winner error is the first in alphabetic order
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006153 my $most_common_error = ( sort
6154 {
6155 $errors_counted_ref->{$b} <=> $errors_counted_ref->{$a}
6156 || $a cmp $b
6157 } keys %{$errors_counted_ref} )[0] ;
6158
6159 return $most_common_error ;
6160
6161}
6162
6163
6164
6165sub tests_errorsanalyse
6166{
6167 note( 'Entering tests_errorsanalyse()' ) ;
6168
6169 is( 'ERR_NOTHING_REPORTED', errorsanalyse( ), 'errorsanalyse: no args => ERR_NOTHING_REPORTED' ) ;
6170 is( 'ERR_NOTHING_REPORTED', errorsanalyse( ( ) ), 'errorsanalyse: empty list => ERR_NOTHING_REPORTED' ) ;
6171 is( 'ERR_UNCLASSIFIED', errorsanalyse( 'aie' ), 'errorsanalyse: aie => ERR_UNCLASSIFIED' ) ;
6172
6173 # in case of equality, empty wins
6174 is( 'ERR_NOTHING_REPORTED', errorsanalyse( 'aie', '' ), 'errorsanalyse: aie => ERR_UNCLASSIFIED' ) ;
6175 is( 'ERR_NOTHING_REPORTED', errorsanalyse( '', 'aie' ), 'errorsanalyse: aie => ERR_UNCLASSIFIED' ) ;
6176
6177
6178 is( 'ERR_UNCLASSIFIED', errorsanalyse( 'aie', 'ouille' ), 'errorsanalyse: aie, ouille => ERR_UNCLASSIFIED' ) ;
6179 is( 'ERR_UNCLASSIFIED', errorsanalyse( 'aie', 'ouille', '' ), 'errorsanalyse: aie, ouille, "" => ERR_UNCLASSIFIED' ) ;
6180 is( 'ERR_UNCLASSIFIED', errorsanalyse( '', 'aie', 'ouille' ), 'errorsanalyse: aie, ouille, "" => ERR_UNCLASSIFIED' ) ;
6181
6182 is( 'ERR_NOTHING_REPORTED', errorsanalyse( '' ), 'errorsanalyse: "" => ERR_NOTHING_REPORTED' ) ;
6183 is( 'ERR_NOTHING_REPORTED', errorsanalyse( ( '' ) ), 'errorsanalyse: ( "" ) => ERR_NOTHING_REPORTED' ) ;
6184 is( 'ERR_NOTHING_REPORTED', errorsanalyse( ( '', '' ) ), 'errorsanalyse: ( "", "" ) => ERR_NOTHING_REPORTED' ) ;
6185
6186 note( 'Leaving tests_errorsanalyse()' ) ;
6187 return ;
6188}
6189
6190
6191
6192sub errorsanalyse
6193{
6194 my @errors = @ARG ;
6195 my $errors_types_counted = errorclassify( @errors ) ;
6196
6197 my $most_common_error = most_common_error( $errors_types_counted ) ;
6198
6199 return $most_common_error ;
6200}
6201
6202
6203
6204sub tests_errorsdump
6205{
6206 note( 'Entering tests_errorsdump()' ) ;
6207
6208 is( undef, errorsdump( ), 'errorsdump: no args => undef' ) ;
6209 is( undef, errorsdump( ( ) ), 'errorsdump: empty list => undef' ) ;
6210 is( "Err 1/1: ", errorsdump( '' ), 'errorsdump: one empty string => "Err 1/1: "' ) ;
6211 is( "Err 1/1: aieaieaie", errorsdump( 'aieaieaie' ), 'errorsdump: aieaieaie => "Err 1/1: aieaieaie"' ) ;
6212 is( "Err 1/2: Aie Err 2/2: Ouille", errorsdump( 'Aie ', 'Ouille' ), 'errorsdump: Aie Ouille => "Err 1/2: Aie Err 2/2: Ouille"' ) ;
6213 note( 'Leaving tests_errorsdump()' ) ;
6214 return ;
6215}
6216
6217
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006218sub errorsdump
6219{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006220 if ( ! @ARG ) { return ; }
6221
6222 my @errors_log = @ARG ;
6223 my $nb_errors = @errors_log ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006224 my $error_num = 0 ;
6225 my $errors_list = q{} ;
6226 if ( @errors_log ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006227 foreach my $error ( @errors_log )
6228 {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006229 $error_num++ ;
6230 $errors_list .= "Err $error_num/$nb_errors: $error" ;
6231 }
6232 }
6233 return( $errors_list ) ;
6234}
6235
6236
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006237
6238sub errors_listing
6239{
6240 my $mysync = shift ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01006241 $mysync->{ most_common_error } = errorsanalyse( errors_log( $mysync ) ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006242
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01006243 my $errors_listing = '' ;
6244
6245 if ( $mysync->{ errorsdump } )
6246 {
6247 $errors_listing = join( '',
6248 "++++ Listing $mysync->{nb_errors} errors encountered during the sync ( avoid this listing with --noerrorsdump ).\n",
6249 errorsdump( errors_log( $mysync ) ),
6250 ) ;
6251 }
6252
6253 $errors_listing .= join( '',
6254 "The most frequent error is $mysync->{ most_common_error }. ",
6255 comment_of_error_type( $mysync, $mysync->{ most_common_error } ),
6256 "\n",
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006257 ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01006258
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006259 return $errors_listing ;
6260}
6261
6262
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01006263sub errors_incr
6264{
6265 my ( $mysync, @error ) = @ARG ;
6266 $mysync->{ nb_errors }++ ;
6267
6268 if ( @error ) {
6269 errors_log( $mysync, @error ) ;
6270 myprint( @error ) ;
6271 }
6272
6273 $mysync->{ errorsmax } ||= $ERRORS_MAX ;
6274
6275
6276 if ( $mysync->{ nb_errors } >= $mysync->{ errorsmax } )
6277 {
6278 myprint( errorsmax_msg( $mysync ) ) ;
6279 myprint( errors_listing( $mysync ) ) ;
6280
6281 if ( $mysync->{ errorsdump } )
6282 {
6283 # again since errorsdump( ) can be very verbose and masquerade previous warning
6284 myprint( errorsmax_msg( $mysync ) ) ;
6285 }
6286 my $exit_value = exit_value( $mysync, $mysync->{ most_common_error } ) ;
6287 exit_clean( $mysync, $exit_value ) ;
6288 }
6289 return ;
6290}
6291
6292
6293
6294sub errorsmax_msg
6295{
6296 my $mysync = shift @ARG ;
6297 my $msg = "Maximum number of errors $mysync->{errorsmax} reached "
6298 . "( you can change $mysync->{errorsmax} to any value, for example 100 with --errorsmax 100 ). "
6299 . "Exiting.\n" ;
6300 return $msg ;
6301}
6302
6303
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006304
6305
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006306sub tests_live_result
6307{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006308 note( 'Entering tests_live_result()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006309
6310 my $nb_errors = shift ;
6311 if ( $nb_errors ) {
6312 myprint( "Live tests failed with $nb_errors errors\n" ) ;
6313 } else {
6314 myprint( "Live tests ended successfully\n" ) ;
6315 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006316 note( 'Leaving tests_live_result()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006317 return ;
6318}
6319
6320
6321sub size_filtered_flag
6322{
6323 my $mysync = shift ;
6324 my $h1_size = shift ;
6325
6326 if ( defined $mysync->{ maxsize } and $h1_size >= $mysync->{ maxsize } ) {
6327 return( 1 ) ;
6328 }
6329 if ( defined $minsize and $h1_size <= $minsize ) {
6330 return( 1 ) ;
6331 }
6332 return( 0 ) ;
6333}
6334
6335sub sync_flags_fir
6336{
6337 my ( $mysync, $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) = @_ ;
6338
6339 if ( not defined $h1_msg ) { return } ;
6340 if ( not defined $h2_msg ) { return } ;
6341
6342 my $h1_size = $h1_fir_ref->{$h1_msg}->{'RFC822.SIZE'} ;
6343 return if size_filtered_flag( $mysync, $h1_size ) ;
6344
6345 # used cached flag values for efficiency
6346 my $h1_flags = $h1_fir_ref->{ $h1_msg }->{ 'FLAGS' } || q{} ;
6347 my $h2_flags = $h2_fir_ref->{ $h2_msg }->{ 'FLAGS' } || q{} ;
6348
6349 sync_flags( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) ;
6350
6351 return ;
6352}
6353
6354sub sync_flags_after_copy
6355{
6356 # Activated with option --syncflagsaftercopy
6357 my( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $permanentflags2 ) = @_ ;
6358
6359 if ( my @h2_flags = $mysync->{imap2}->flags( $h2_msg ) ) {
6360 my $h2_flags = "@h2_flags" ;
6361 ( $mysync->{ debug } or $debugflags ) and myprint( "Host2: msg $h2_fold/$h2_msg flags before sync flags after copy ( $h2_flags )\n" ) ;
6362 sync_flags( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) ;
6363 }else{
6364 myprint( "Host2: msg $h2_fold/$h2_msg could not get its flags for sync flags after copy\n" ) ;
6365 }
6366 return ;
6367}
6368
6369# Globals
6370# $debug
6371# $debugflags
6372# $permanentflags2
6373
6374
6375sub sync_flags
6376{
6377 my( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) = @_ ;
6378
6379 ( $mysync->{ debug } or $debugflags ) and
6380 myprint( "Host1: flags init msg $h1_fold/$h1_msg flags( $h1_flags ) Host2 msg $h2_fold/$h2_msg flags( $h2_flags )\n" ) ;
6381
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006382 $h1_flags = flags_for_host2( $mysync, $h1_flags, $permanentflags2 ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006383
6384 $h2_flags = flagscase( $h2_flags ) ;
6385
6386 ( $mysync->{ debug } or $debugflags ) and
6387 myprint( "Host1: flags filt msg $h1_fold/$h1_msg flags( $h1_flags ) Host2 msg $h2_fold/$h2_msg flags( $h2_flags )\n" ) ;
6388
6389
6390 # compare flags - set flags if there a difference
6391 my @h1_flags = sort split(q{ }, $h1_flags );
6392 my @h2_flags = sort split(q{ }, $h2_flags );
6393 my $diff = compare_lists( \@h1_flags, \@h2_flags );
6394
6395 $diff and ( $mysync->{ debug } or $debugflags )
6396 and myprint( "Host2: flags msg $h2_fold/$h2_msg replacing h2 flags( $h2_flags ) with h1 flags( $h1_flags )\n" ) ;
6397
6398 # This sets flags exactly. So flags can be removed with this.
6399 # When you remove a \Seen flag on host1 you want it
6400 # to be removed on host2. Just add flags is not what
6401 # we need most of the time, so no + like in "+FLAGS.SILENT".
6402
6403 if ( not $mysync->{dry} and $diff and not $mysync->{imap2}->store( $h2_msg, "FLAGS.SILENT (@h1_flags)" ) ) {
6404 my $error_msg = join q{}, "Host2: flags msg $h2_fold/$h2_msg could not add flags [@h1_flags]: ",
6405 $mysync->{imap2}->LastError || q{}, "\n" ;
6406 errors_incr( $mysync, $error_msg ) ;
6407 }
6408
6409 return ;
6410}
6411
6412
6413
6414sub _filter
6415{
6416 my $mysync = shift ;
6417 my $str = shift or return q{} ;
6418 my $sz = $SIZE_MAX_STR ;
6419 my $len = length $str ;
6420 if ( not $mysync->{ debug } and $len > $sz*2 ) {
6421 my $beg = substr $str, 0, $sz ;
6422 my $end = substr $str, -$sz, $sz ;
6423 $str = $beg . '...' . $end ;
6424 }
6425 $str =~ s/\012?\015$//x ;
6426 return "(len=$len) " . $str ;
6427}
6428
6429
6430
6431sub lost_connection
6432{
6433 my( $mysync, $imap, $error_message ) = @_;
6434 if ( $imap->IsUnconnected( ) ) {
6435 $mysync->{nb_errors}++ ;
6436 my $lcomm = $imap->LastIMAPCommand || q{} ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006437
6438 my $einfo = imap_last_error( $imap ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006439
6440 # if string is long try reduce to a more reasonable size
6441 $lcomm = _filter( $mysync, $lcomm ) ;
6442 $einfo = _filter( $mysync, $einfo ) ;
6443 myprint( "Failure: last command: $lcomm\n") if ( $mysync->{ debug } && $lcomm) ;
6444 myprint( "Failure: lost connection $error_message: ", $einfo, "\n") ;
6445 return( 1 ) ;
6446 }
6447 else{
6448 return( 0 ) ;
6449 }
6450}
6451
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006452sub imap_last_error
6453{
6454 my $imap = shift ;
6455 my $einfo = $imap->LastError || @{$imap->History}[$LAST] || q{} ;
6456 chomp( $einfo ) ;
6457 return( $einfo ) ;
6458}
6459
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006460sub tests_max
6461{
6462 note( 'Entering tests_max()' ) ;
6463
6464 is( 0, max( 0 ), 'max 0 => 0' ) ;
6465 is( 1, max( 1 ), 'max 1 => 1' ) ;
6466 is( $MINUS_ONE, max( $MINUS_ONE ), 'max -1 => -1') ;
6467 is( undef, max( ), 'max no arg => undef' ) ;
6468 is( undef, max( undef ), 'undef => undef' ) ;
6469 is( undef, max( undef, undef ), 'undef, undef => undef' ) ;
6470
6471 is( $NUMBER_100, max( 1, $NUMBER_100 ), 'max 1 100 => 100' ) ;
6472 is( $NUMBER_100, max( $NUMBER_100, 1 ), 'max 100 1 => 100' ) ;
6473 is( $NUMBER_100, max( $NUMBER_100, $NUMBER_42, 1 ), 'max 100 42 1 => 100' ) ;
6474 is( $NUMBER_100, max( $NUMBER_100, '42', 1 ), 'max 100 42 1 => 100' ) ;
6475 is( $NUMBER_100, max( '100', '42', 1 ), 'max 100 42 1 => 100' ) ;
6476 is( $NUMBER_100, max( $NUMBER_100, 'haha', 1 ), 'max 100 haha 1 => 100') ;
6477 is( $NUMBER_100, max( 'bb', $NUMBER_100, 'haha' ), 'max bb 100 haha => 100') ;
6478 is( $MINUS_ONE, max( q{}, $MINUS_ONE, 'haha' ), 'max "" -1 haha => -1') ;
6479 is( $MINUS_ONE, max( q{}, $MINUS_ONE, $MINUS_TWO ), 'max "" -1 -2 => -1') ;
6480 is( $MINUS_ONE, max( 'haha', $MINUS_ONE, $MINUS_TWO ), 'max haha -1 -2 => -1') ;
6481 is( 1, max( $MINUS_ONE, 1 ), 'max -1 1 => 1') ;
6482 is( 1, max( undef, 1 ), 'max undef 1 => 1' ) ;
6483 is( 0, max( undef, 0 ), 'max undef 0 => 0' ) ;
6484 is( 'haha', max( 'haha' ), 'max haha => haha') ;
6485 is( 'bb', max( 'aa', 'bb' ), 'max aa bb => bb') ;
6486 is( 'bb', max( 'bb', 'aa' ), 'max bb aa => bb') ;
6487 is( 'bb', max( 'bb', 'aa', 'bb' ), 'max bb aa bb => bb') ;
6488 note( 'Leaving tests_max()' ) ;
6489 return ;
6490}
6491
6492sub max
6493{
6494 my @list = @_ ;
6495 return( undef ) if ( 0 == scalar @list ) ;
6496
6497 my( @numbers, @notnumbers ) ;
6498 foreach my $item ( @list )
6499 {
6500 if ( is_number( $item ) )
6501 {
6502 push @numbers, $item ;
6503 }
6504 elsif ( defined $item )
6505 {
6506 push @notnumbers, $item ;
6507 }
6508 }
6509
6510 my @sorted ;
6511
6512 if ( @numbers )
6513 {
6514 @sorted = sort { $a <=> $b } @numbers ;
6515 }
6516 elsif ( @notnumbers )
6517 {
6518 @sorted = sort { $a cmp $b } @notnumbers ;
6519 }
6520 else
6521 {
6522 return ;
6523 }
6524
6525 return( pop @sorted ) ;
6526}
6527
6528sub tests_is_number
6529{
6530 note( 'Entering tests_is_number()' ) ;
6531
6532 is( undef, is_number( ), 'is_number: no args => undef ' ) ;
6533 is( undef, is_number( undef ), 'is_number: undef => undef ' ) ;
6534 ok( is_number( 1 ), 'is_number: 1 => 1' ) ;
6535 ok( is_number( 1.1 ), 'is_number: 1.1 => 1' ) ;
6536 ok( is_number( 0 ), 'is_number: 0 => 1' ) ;
6537 ok( is_number( -1 ), 'is_number: -1 => 1' ) ;
6538 ok( ! is_number( 1.1.1 ), 'is_number: 1.1.1 => no' ) ;
6539 ok( ! is_number( q{} ), 'is_number: q{} => no' ) ;
6540 ok( ! is_number( 'haha' ), 'is_number: haha => no' ) ;
6541 ok( ! is_number( '0haha' ), 'is_number: 0haha => no' ) ;
6542 ok( ! is_number( '2haha' ), 'is_number: 2haha => no' ) ;
6543 ok( ! is_number( 'haha2' ), 'is_number: haha2 => no' ) ;
6544
6545 note( 'Leaving tests_is_number()' ) ;
6546 return ;
6547}
6548
6549
6550
6551sub is_number
6552{
6553 my $item = shift ;
6554
6555 if ( ! defined $item ) { return ; }
6556
6557 if ( $item =~ /\A$RE{num}{real}\Z/ ) {
6558 return 1 ;
6559 }
6560 return ;
6561}
6562
6563sub tests_min
6564{
6565 note( 'Entering tests_min()' ) ;
6566
6567 is( 0, min( 0 ), 'min 0 => 0' ) ;
6568 is( 1, min( 1 ), 'min 1 => 1' ) ;
6569 is( $MINUS_ONE, min( $MINUS_ONE ), 'min -1 => -1' ) ;
6570 is( undef, min( ), 'min no arg => undef' ) ;
6571 is( 1, min( 1, $NUMBER_100 ), 'min 1 100 => 1' ) ;
6572 is( 1, min( $NUMBER_100, 1 ), 'min 100 1 => 1' ) ;
6573 is( 1, min( $NUMBER_100, $NUMBER_42, 1 ), 'min 100 42 1 => 1' ) ;
6574 is( 1, min( $NUMBER_100, '42', 1 ), 'min 100 42 1 => 1' ) ;
6575 is( 1, min( '100', '42', 1 ), 'min 100 42 1 => 1' ) ;
6576 is( 1, min( $NUMBER_100, 'haha', 1 ), 'min 100 haha 1 => 1') ;
6577 is( $MINUS_ONE, min( $MINUS_ONE, 1 ), 'min -1 1 => -1') ;
6578
6579 is( 1, min( undef, 1 ), 'min undef 1 => 1' ) ;
6580 is( 0, min( undef, 0 ), 'min undef 0 => 0' ) ;
6581 is( 1, min( undef, 1 ), 'min undef 1 => 1' ) ;
6582 is( 0, min( undef, 2, 0, 1 ), 'min undef, 2, 0, 1 => 0' ) ;
6583
6584 is( 'haha', min( 'haha' ), 'min haha => haha') ;
6585 is( 'aa', min( 'aa', 'bb' ), 'min aa bb => aa') ;
6586 is( 'aa', min( 'bb', 'aa' ), 'min bb aa bb => aa') ;
6587 is( 'aa', min( 'bb', 'aa', 'bb' ), 'min bb aa bb => aa') ;
6588
6589 note( 'Leaving tests_min()' ) ;
6590 return ;
6591}
6592
6593
6594sub min
6595{
6596 my @list = @_ ;
6597 return( undef ) if ( 0 == scalar @list ) ;
6598
6599 my( @numbers, @notnumbers ) ;
6600 foreach my $item ( @list ) {
6601 if ( is_number( $item ) ) {
6602 push @numbers, $item ;
6603 }else{
6604 push @notnumbers, $item ;
6605 }
6606 }
6607
6608 my @sorted ;
6609 if ( @numbers ) {
6610 @sorted = sort { $a <=> $b } @numbers ;
6611 }elsif( @notnumbers ) {
6612 @sorted = sort { $a cmp $b } @notnumbers ;
6613 }else{
6614 return ;
6615 }
6616
6617 return( shift @sorted ) ;
6618}
6619
6620
6621sub check_lib_version
6622{
6623 my $mysync = shift ;
6624 $mysync->{ debug } and myprint( "IMAPClient $Mail::IMAPClient::VERSION\n" ) ;
6625 if ( '2.2.9' eq $Mail::IMAPClient::VERSION ) {
6626 myprint( "imapsync no longer supports Mail::IMAPClient 2.2.9, upgrade it\n" ) ;
6627 return 0 ;
6628 }
6629 else{
6630 # 3.x.x is no longer buggy with imapsync.
6631 # 3.30 or currently superior is imposed in the Perl "use Mail::IMAPClient line".
6632 return 1 ;
6633 }
6634 return ;
6635}
6636
6637sub module_version_str
6638{
6639 my( $module_name, $module_version ) = @_ ;
6640 my $str = mysprintf( "%-20s %s\n", $module_name, $module_version ) ;
6641 return( $str ) ;
6642}
6643
6644sub modulesversion
6645{
6646
6647 my @list_version;
6648
6649 my %modulesversion = (
6650 'Authen::NTLM' => sub { $Authen::NTLM::VERSION },
6651 'CGI' => sub { $CGI::VERSION },
6652 'Compress::Zlib' => sub { $Compress::Zlib::VERSION },
6653 'Crypt::OpenSSL::RSA' => sub { $Crypt::OpenSSL::RSA::VERSION },
6654 'Data::Uniqid' => sub { $Data::Uniqid::VERSION },
6655 'Digest::HMAC_MD5' => sub { $Digest::HMAC_MD5::VERSION },
6656 'Digest::HMAC_SHA1' => sub { $Digest::HMAC_SHA1::VERSION },
6657 'Digest::MD5' => sub { $Digest::MD5::VERSION },
6658 'Encode' => sub { $Encode::VERSION },
6659 'Encode::IMAPUTF7' => sub { $Encode::IMAPUTF7::VERSION },
6660 'File::Copy::Recursive' => sub { $File::Copy::Recursive::VERSION },
6661 'File::Spec' => sub { $File::Spec::VERSION },
6662 'Getopt::Long' => sub { $Getopt::Long::VERSION },
6663 'HTML::Entities' => sub { $HTML::Entities::VERSION },
6664 'IO::Socket' => sub { $IO::Socket::VERSION },
6665 'IO::Socket::INET' => sub { $IO::Socket::INET::VERSION },
6666 'IO::Socket::INET6' => sub { $IO::Socket::INET6::VERSION },
6667 'IO::Socket::IP' => sub { $IO::Socket::IP::VERSION },
6668 'IO::Socket::SSL' => sub { $IO::Socket::SSL::VERSION },
6669 'IO::Tee' => sub { $IO::Tee::VERSION },
6670 'JSON' => sub { $JSON::VERSION },
6671 'JSON::WebToken' => sub { $JSON::WebToken::VERSION },
6672 'LWP' => sub { $LWP::VERSION },
6673 'Mail::IMAPClient' => sub { $Mail::IMAPClient::VERSION },
6674 'MIME::Base64' => sub { $MIME::Base64::VERSION },
6675 'Net::Ping' => sub { $Net::Ping::VERSION },
6676 'Net::SSLeay' => sub { $Net::SSLeay::VERSION },
6677 'Term::ReadKey' => sub { $Term::ReadKey::VERSION },
6678 'Test::MockObject' => sub { $Test::MockObject::VERSION },
6679 'Time::HiRes' => sub { $Time::HiRes::VERSION },
6680 'Unicode::String' => sub { $Unicode::String::VERSION },
6681 'URI::Escape' => sub { $URI::Escape::VERSION },
6682 #'Lalala' => sub { $Lalala::VERSION },
6683 ) ;
6684
6685 foreach my $module_name ( sort keys %modulesversion ) {
6686 # trick from http://www.perlmonks.org/?node_id=152122
6687 my $file_name = $module_name . '.pm' ;
6688 $file_name =~s,::,/,xmgs; # Foo::Bar::Baz => Foo/Bar/Baz.pm
6689 my $v ;
6690 eval {
6691 require $file_name ;
6692 $v = defined $modulesversion{ $module_name } ? $modulesversion{ $module_name }->() : q{?} ;
6693 } or $v = q{Not installed} ;
6694
6695 push @list_version, module_version_str( $module_name, $v ) ;
6696 }
6697 return( @list_version ) ;
6698}
6699
6700
6701sub tests_command_line_nopassword
6702{
6703 note( 'Entering tests_command_line_nopassword()' ) ;
6704
6705 ok( q{} eq command_line_nopassword(), 'command_line_nopassword void' );
6706 my $mysync = {} ;
6707 ok( '--blabla' eq command_line_nopassword( $mysync, '--blabla' ), 'command_line_nopassword --blabla' );
6708 #myprint( command_line_nopassword((qw{ --password1 secret1 })), "\n" ) ;
6709 ok( '--password1 MASKED' eq command_line_nopassword( $mysync, qw{ --password1 secret1}), 'command_line_nopassword --password1' );
6710 ok( '--blabla --password1 MASKED --blibli'
6711 eq command_line_nopassword( $mysync, qw{ --blabla --password1 secret1 --blibli } ), 'command_line_nopassword --password1 --blibli' );
6712 $mysync->{showpasswords} = 1 ;
6713 ok( q{} eq command_line_nopassword(), 'command_line_nopassword void' );
6714 ok( '--blabla' eq command_line_nopassword( $mysync, '--blabla'), 'command_line_nopassword --blabla' );
6715 #myprint( command_line_nopassword((qw{ --password1 secret1 })), "\n" ) ;
6716 ok( '--password1 secret1' eq command_line_nopassword( $mysync, qw{ --password1 secret1} ), 'command_line_nopassword --password1' );
6717 ok( '--blabla --password1 secret1 --blibli'
6718 eq command_line_nopassword( $mysync, qw{ --blabla --password1 secret1 --blibli } ), 'command_line_nopassword --password1 --blibli' );
6719
6720 note( 'Leaving tests_command_line_nopassword()' ) ;
6721 return ;
6722}
6723
6724# Construct a command line copy with passwords replaced by MASKED.
6725sub command_line_nopassword
6726{
6727 my $mysync = shift @ARG ;
6728 my @argv = @ARG ;
6729 my @argv_nopassword ;
6730
6731 if ( $mysync->{ cmdcgi } ) {
6732 @argv_nopassword = mask_password_value( @{ $mysync->{ cmdcgi } } ) ;
6733 return( "@argv_nopassword" ) ;
6734 }
6735
6736 if ( $mysync->{showpasswords} )
6737 {
6738 return( "@argv" ) ;
6739 }
6740
6741 @argv_nopassword = mask_password_value( @argv ) ;
6742 return("@argv_nopassword") ;
6743}
6744
6745sub mask_password_value
6746{
6747 my @argv = @ARG ;
6748 my @argv_nopassword ;
6749 while ( @argv ) {
6750 my $arg = shift @argv ; # option name or value
6751 if ( $arg =~ m/-password[12]/x ) {
6752 shift @argv ; # password value
6753 push @argv_nopassword, $arg, 'MASKED' ; # option name and fake value
6754 }else{
6755 push @argv_nopassword, $arg ; # same option or value
6756 }
6757 }
6758 return @argv_nopassword ;
6759}
6760
6761
6762sub tests_get_stdin_masked
6763{
6764 note( 'Entering tests_get_stdin_masked()' ) ;
6765
6766 is( q{}, get_stdin_masked( ), 'get_stdin_masked: no args' ) ;
6767 is( q{}, get_stdin_masked( 'Please ENTER: ' ), 'get_stdin_masked: ENTER' ) ;
6768
6769 note( 'Leaving tests_get_stdin_masked()' ) ;
6770 return ;
6771}
6772
6773#######################################################
6774# The issue is that prompt() does not prompt the prompt
6775# when the program is used like
6776# { sleep 2 ; echo blablabla ; } | ./imapsync ...--host1 lo --user1 tata --host2 lo --user2 titi
6777
6778# use IO::Prompter ;
6779sub get_stdin_masked
6780{
6781 my $prompt = shift || 'Say something: ' ;
6782 local @ARGV = () ;
6783 my $input = prompt(
6784 -prompt => $prompt,
6785 -echo => '*',
6786 ) ;
6787 #myprint( "You said: $input\n" ) ;
6788 return $input ;
6789}
6790
6791sub ask_for_password_new
6792{
6793 my $prompt = shift ;
6794 my $password = get_stdin_masked( $prompt ) ;
6795 return $password ;
6796}
6797#########################################################
6798
6799
6800sub ask_for_password
6801{
6802 my $prompt = shift ;
6803 myprint( $prompt ) ;
6804 Term::ReadKey::ReadMode( 2 ) ;
6805 ## no critic (InputOutput::ProhibitExplicitStdin)
6806 my $password = <STDIN> ;
6807 chomp $password ;
6808 myprint( "\nGot it\n" ) ;
6809 Term::ReadKey::ReadMode( 0 ) ;
6810 return $password ;
6811}
6812
6813# Have to refactor get_password1() get_password2()
6814# to have only get_password() and two calls
6815sub get_password1
6816{
6817
6818 my $mysync = shift ;
6819
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006820 $mysync->{ password1 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006821 || $mysync->{ passfile1 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006822 || 'PREAUTH' eq $mysync->{ acc1 }->{ authmech }
6823 || 'EXTERNAL' eq $mysync->{ acc1 }->{ authmech }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006824 || $ENV{IMAPSYNC_PASSWORD1}
6825 || do
6826 {
6827 myprint( << 'FIN_PASSFILE' ) ;
6828
6829If you are afraid of giving password on the command line arguments, you can put the
6830password of user1 in a file named file1 and use "--passfile1 file1" instead of typing it.
6831Then give this file restrictive permissions with the command "chmod 600 file1".
6832An other solution is to set the environment variable IMAPSYNC_PASSWORD1
6833FIN_PASSFILE
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006834 my $user = $mysync->{ acc1 }->{ authuser } || $mysync->{ user1 } ;
6835 my $host = $mysync->{ host1 } ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006836 my $prompt = "What's the password for $user" . ' at ' . "$host? (not visible while you type, then enter RETURN) " ;
6837 $mysync->{password1} = ask_for_password( $prompt ) ;
6838 } ;
6839
6840 if ( defined $mysync->{ passfile1 } ) {
6841 if ( ! -e -r $mysync->{ passfile1 } ) {
6842 myprint( "Failure: file from parameter --passfile1 $mysync->{ passfile1 } does not exist or is not readable\n" ) ;
6843 $mysync->{nb_errors}++ ;
6844 exit_clean( $mysync, $EX_NOINPUT ) ;
6845 }
6846 # passfile1 readable
6847 $mysync->{password1} = firstline ( $mysync->{ passfile1 } ) ;
6848 return ;
6849 }
6850 if ( $ENV{IMAPSYNC_PASSWORD1} ) {
6851 $mysync->{password1} = $ENV{IMAPSYNC_PASSWORD1} ;
6852 return ;
6853 }
6854 return ;
6855}
6856
6857sub get_password2
6858{
6859
6860 my $mysync = shift ;
6861
6862 $mysync->{password2}
6863 || $mysync->{ passfile2 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006864 || 'PREAUTH' eq $mysync->{ acc2 }->{ authmech }
6865 || 'EXTERNAL' eq $mysync->{ acc2 }->{ authmech }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006866 || $ENV{IMAPSYNC_PASSWORD2}
6867 || do
6868 {
6869 myprint( << 'FIN_PASSFILE' ) ;
6870
6871If you are afraid of giving password on the command line arguments, you can put the
6872password of user2 in a file named file2 and use "--passfile2 file2" instead of typing it.
6873Then give this file restrictive permissions with the command "chmod 600 file2".
6874An other solution is to set the environment variable IMAPSYNC_PASSWORD2
6875FIN_PASSFILE
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006876 my $user = $mysync->{ acc2 }->{ authuser } || $mysync->{ user2 } ;
6877 my $host = $mysync->{ host2 } ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006878 my $prompt = "What's the password for $user" . ' at ' . "$host? (not visible while you type, then enter RETURN) " ;
6879 $mysync->{password2} = ask_for_password( $prompt ) ;
6880 } ;
6881
6882
6883 if ( defined $mysync->{ passfile2 } ) {
6884 if ( ! -e -r $mysync->{ passfile2 } ) {
6885 myprint( "Failure: file from parameter --passfile2 $mysync->{ passfile2 } does not exist or is not readable\n" ) ;
6886 $mysync->{nb_errors}++ ;
6887 exit_clean( $mysync, $EX_NOINPUT ) ;
6888 }
6889 # passfile2 readable
6890 $mysync->{password2} = firstline ( $mysync->{ passfile2 } ) ;
6891 return ;
6892 }
6893 if ( $ENV{IMAPSYNC_PASSWORD2} ) {
6894 $mysync->{password2} = $ENV{IMAPSYNC_PASSWORD2} ;
6895 return ;
6896 }
6897 return ;
6898}
6899
6900
6901
6902
6903sub remove_tmp_files
6904{
6905 my $mysync = shift or return ;
6906 $mysync->{pidfile} or return ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006907
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006908 if ( -e $mysync->{pidfile} ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006909 myprint( "Removing pidfile $mysync->{pidfile}\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006910 unlink $mysync->{pidfile} ;
6911 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006912 if ( -e $mysync->{abortfile} ) {
6913 myprint( "Removing pidfile $mysync->{abortfile}\n" ) ;
6914 unlink $mysync->{abortfile} ;
6915 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006916 return ;
6917}
6918
6919sub cleanup_before_exit
6920{
6921 my $mysync = shift ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01006922
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006923 remove_tmp_files( $mysync ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01006924
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006925 if ( $mysync->{imap1} and $mysync->{imap1}->IsConnected() )
6926 {
6927 myprint( "Disconnecting from host1 $mysync->{ host1 } user1 $mysync->{ user1 }\n" ) ;
6928 $mysync->{imap1}->logout( ) ;
6929 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01006930
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006931 if ( $mysync->{imap2} and $mysync->{imap2}->IsConnected() )
6932 {
6933 myprint( "Disconnecting from host2 $mysync->{ host2 } user2 $mysync->{ user2 }\n" ) ;
6934 $mysync->{imap2}->logout( ) ;
6935 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01006936
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006937 if ( $mysync->{log} ) {
6938 myprint( "Log file is $mysync->{logfile} ( to change it, use --logfile filepath ; or use --nolog to turn off logging )\n" ) ;
6939 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006940 else
6941 {
6942 myprint( "No log file because of option --nolog\n" ) ;
6943 }
6944
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006945 if ( $mysync->{log} and $mysync->{logfile_handle} ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006946 #print( "Closing $mysync->{ logfile }\n" ) ;
6947 teefinish( $mysync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006948 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01006949
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006950 return ;
6951}
6952
6953
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01006954sub tests_exit_value
6955{
6956 note( 'Entering tests_exit_value()' ) ;
6957
6958 is( $EXIT_CATCH_ALL, exit_value( ), 'exit_value: no args => EXIT_CATCH_ALL' ) ;
6959
6960 my $mysync = { } ;
6961 is( $EXIT_CATCH_ALL, exit_value( $mysync ), 'exit_value: undef => EXIT_CATCH_ALL' ) ;
6962
6963 is( $EXIT_CATCH_ALL, exit_value( $mysync, 'Blabla_unknown' ), 'exit_value: Blabla => EXIT_CATCH_ALL' ) ;
6964 is( $EXIT_CATCH_ALL, exit_value( $mysync, '' ), 'exit_value: empty => EXIT_CATCH_ALL' ) ;
6965
6966
6967 is( $EXIT_OVERQUOTA, exit_value( $mysync, 'ERR_OVERQUOTA' ), 'exit_value: ERR_OVERQUOTA => EXIT_OVERQUOTA' ) ;
6968 is( $EXIT_TRANSFER_EXCEEDED, exit_value( $mysync, 'ERR_TRANSFER_EXCEEDED' ), 'exit_value: ERR_TRANSFER_EXCEEDED => EXIT_TRANSFER_EXCEEDED' ) ;
6969
6970 note( 'Leaving tests_exit_value()' ) ;
6971 return ;
6972}
6973
6974sub exit_value
6975{
6976 my $mysync = shift @ARG ;
6977 my $most_common_error = shift @ARG ;
6978
6979 if ( ! defined $most_common_error ) { return $EXIT_CATCH_ALL ; }
6980 my $exit_value = $EXIT_VALUE_OF_ERR_TYPE{ $most_common_error } || $EXIT_CATCH_ALL ;
6981
6982 return $exit_value ;
6983}
6984
6985
6986
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006987sub exit_most_errors
6988{
6989 my $mysync = shift @ARG ;
6990
6991 myprint( errors_listing( $mysync ) ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01006992 my $exit_value = exit_value( $mysync, $mysync->{ most_common_error } ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006993 exit_clean( $mysync, $exit_value ) ;
6994 return ;
6995}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006996
6997sub exit_clean
6998{
6999 my $mysync = shift @ARG ;
7000 my $status = shift @ARG ;
7001 my @messages = @ARG ;
7002 if ( @messages )
7003 {
7004 myprint( @messages ) ;
7005 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007006 myprint( "Exiting with return value $status ($EXIT_TXT{$status}) $mysync->{nb_errors}/$mysync->{errorsmax} nb_errors/max_errors PID $PROCESS_ID\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007007 cleanup_before_exit( $mysync ) ;
7008
7009 exit $status ;
7010}
7011
7012sub missing_option
7013{
7014 my $mysync = shift ;
7015 my $option = shift ;
7016 $mysync->{nb_errors}++ ;
7017 exit_clean( $mysync, $EX_USAGE, "$option option is mandatory, for help run $PROGRAM_NAME --help\n" ) ;
7018 return ;
7019}
7020
7021
7022sub catch_ignore
7023{
7024 my $mysync = shift ;
7025 my $signame = shift ;
7026
7027 my $sigcounter = ++$mysync->{ sigcounter }{ $signame } ;
7028 myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ),
7029 "). Received $sigcounter $signame signals so far. Thanks!\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007030 do_and_print_stats( $mysync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007031 return ;
7032}
7033
7034
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007035
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007036sub catch_exit
7037{
7038 my $mysync = shift ;
7039 my $signame = shift || q{} ;
7040 if ( $signame ) {
7041 myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ),
7042 "). Asked to terminate\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007043 if ( $mysync->{can_do_stats} ) {
7044 do_and_print_stats( $mysync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007045 myprint( "Ended by a signal $signame (my PID is $PROCESS_ID my PPID is ",
7046 getppid( ), "). I am asked to terminate immediately.\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007047 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007048 myprint( "You should resynchronize those accounts by running a sync again,\n",
7049 "since some messages and entire folders might still be missing on host2.\n"
7050 ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007051 ## no critic (RequireLocalizedPunctuationVars)
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007052 # Well, restore default action does not work well
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007053 $SIG{ $signame } = 'DEFAULT'; # restore default action
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007054 #$SIG{ 'TERM' } = 'DEFAULT'; # restore default action
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007055 # kill myself with $signame
7056 # https://www.cons.org/cracauer/sigint.html
7057 myprint( "Killing myself with signal $signame\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007058 #cleanup_before_exit( $mysync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007059 kill( $signame, $PROCESS_ID ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007060 #kill( 'TERM', $PROCESS_ID ) ;
7061 #sleep 1 ;
7062 #while ( 1 ) { } ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007063 $mysync->{nb_errors}++ ;
7064 exit_clean( $mysync, $EXIT_BY_SIGNAL,
7065 "Still there after killing myself with signal $signame...\n"
7066 ) ;
7067 }
7068 else
7069 {
7070 $mysync->{nb_errors}++ ;
7071 exit_clean( $mysync, $EXIT_BY_SIGNAL, "Exiting in catch_exit with no signal...\n" ) ;
7072 }
7073 return ;
7074}
7075
7076
7077sub catch_print
7078{
7079 my $mysync = shift ;
7080 my $signame = shift ;
7081
7082 my $sigcounter = ++$mysync->{ sigcounter }{ $signame } ;
7083 myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ),
7084 "). Received $sigcounter $signame signals so far. Thanks!\n" ) ;
7085 return ;
7086}
7087
7088sub here_twice
7089{
7090 my $mysync = shift ;
7091 my $now = time ;
7092 my $previous = $mysync->{lastcatch} || 0 ;
7093 $mysync->{lastcatch} = $now ;
7094
7095 if ( $INTERVAL_TO_EXIT >= $now - $previous ) {
7096 return $TRUE ;
7097 }else{
7098 return $FALSE ;
7099 }
7100}
7101
7102
7103sub catch_reconnect
7104{
7105 my $mysync = shift ;
7106 my $signame = shift ;
7107 if ( here_twice( $mysync ) ) {
7108 myprint( "Got two signals $signame within $INTERVAL_TO_EXIT seconds. Exiting...\n" ) ;
7109 catch_exit( $mysync, $signame ) ;
7110 }else{
7111 myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ), ")\n",
7112 "Hit 2 ctr-c within 2 seconds to exit the program\n",
7113 "Hit only 1 ctr-c to reconnect to both imap servers\n",
7114 ) ;
7115 myprint( "For now only one signal $signame within $INTERVAL_TO_EXIT seconds.\n" ) ;
7116
7117 if ( ! defined $mysync->{imap1} ) { return ; }
7118 if ( ! defined $mysync->{imap2} ) { return ; }
7119
7120 myprint( "Info: reconnecting to host1 imap server $mysync->{host1}\n" ) ;
7121 $mysync->{imap1}->State( Mail::IMAPClient::Unconnected ) ;
7122 $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT} += 1 ;
7123 if ( $mysync->{imap1}->reconnect( ) )
7124 {
7125 myprint( "Info: reconnected to host1 imap server $mysync->{host1}\n" ) ;
7126 }
7127 else
7128 {
7129 $mysync->{nb_errors}++ ;
7130 exit_clean( $mysync, $EXIT_CONNECTION_FAILURE ) ;
7131 }
7132 myprint( "Info: reconnecting to host2 imap server\n" ) ;
7133 $mysync->{imap2}->State( Mail::IMAPClient::Unconnected ) ;
7134 $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT} += 1 ;
7135 if ( $mysync->{imap2}->reconnect( ) )
7136 {
7137 myprint( "Info: reconnected to host2 imap server $mysync->{host2}\n" ) ;
7138 }
7139 else
7140 {
7141 $mysync->{nb_errors}++ ;
7142 exit_clean( $mysync, $EXIT_CONNECTION_FAILURE ) ;
7143 }
7144 myprint( "Info: reconnected to both imap servers\n" ) ;
7145 }
7146 return ;
7147}
7148
7149sub install_signals
7150{
7151 my $mysync = shift ;
7152
7153 if ( under_docker_context( $mysync ) )
7154 {
7155 # output( $mysync, "Under docker context so leaving signals as they are\n" ) ;
7156 output( $mysync, "Under docker context so installing only signals to exit\n" ) ;
7157 @{ $mysync->{ sigexit } } = ( defined( $mysync->{ sigexit } ) ) ? @{ $mysync->{ sigexit } } : ( 'INT', 'QUIT', 'TERM' ) ;
7158 sig_install( $mysync, 'catch_exit', @{ $mysync->{ sigexit } } ) ;
7159 }
7160 else
7161 {
7162 # Unix signals
7163 @{ $mysync->{ sigexit } } = ( defined( $mysync->{ sigexit } ) ) ? @{ $mysync->{ sigexit } } : ( 'QUIT', 'TERM' ) ;
7164 @{ $mysync->{ sigreconnect } } = ( defined( $mysync->{ sigreconnect } ) ) ? @{ $mysync->{ sigreconnect } } : ( 'INT' ) ;
7165 @{ $mysync->{ sigprint } } = ( defined( $mysync->{ sigprint } ) ) ? @{ $mysync->{ sigprint } } : ( 'HUP' ) ;
7166 @{ $mysync->{ sigignore } } = ( defined( $mysync->{ sigignore } ) ) ? @{ $mysync->{ sigignore } } : ( ) ;
7167
7168 #local %SIG = %SIG ;
7169 sig_install( $mysync, 'catch_exit', @{ $mysync->{ sigexit } } ) ;
7170 sig_install( $mysync, 'catch_reconnect', @{ $mysync->{ sigreconnect } } ) ;
7171 sig_install( $mysync, 'catch_print', @{ $mysync->{ sigprint } } ) ;
7172 # --sigignore can override sigexit, sigreconnect and sigprint (for the same signals only)
7173 sig_install( $mysync, 'catch_ignore', @{ $mysync->{ sigignore } } ) ;
7174
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007175 # remove/add sleeping mechanism when receiving USR1 signal (except on Win32)
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007176 sig_install_toggle_sleep( $mysync ) ;
7177 }
7178
7179 return ;
7180}
7181
7182
7183
7184sub tests_reconnect_12_if_needed
7185{
7186 note( 'Entering tests_reconnect_12_if_needed()' ) ;
7187
7188 my $mysync ;
7189
7190 $mysync->{imap1} = Mail::IMAPClient->new( ) ;
7191 $mysync->{imap2} = Mail::IMAPClient->new( ) ;
7192 $mysync->{imap1}->Server( 'test1.lamiral.info' ) ;
7193 $mysync->{imap2}->Server( 'test2.lamiral.info' ) ;
7194 is( 2, reconnect_12_if_needed( $mysync ), 'reconnect_12_if_needed: test1&test2 .lamiral.info => 1' ) ;
7195 is( 1, $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_12_if_needed: test1.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ;
7196 is( 1, $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_12_if_needed: test2.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ;
7197
7198 note( 'Leaving tests_reconnect_12_if_needed()' ) ;
7199 return ;
7200}
7201
7202sub reconnect_12_if_needed
7203{
7204 my $mysync = shift ;
7205 #return 2 ;
7206 if ( ! reconnect_if_needed( $mysync->{imap1} ) ) {
7207 return ;
7208 }
7209 if ( ! reconnect_if_needed( $mysync->{imap2} ) ) {
7210 return ;
7211 }
7212 # both were good
7213 return 2 ;
7214}
7215
7216
7217sub tests_reconnect_if_needed
7218{
7219 note( 'Entering tests_reconnect_if_needed()' ) ;
7220
7221
7222 my $myimap ;
7223
7224 is( undef, reconnect_if_needed( ), 'reconnect_if_needed: no args => undef' ) ;
7225 is( undef, reconnect_if_needed( $myimap ), 'reconnect_if_needed: undef arg => undef' ) ;
7226
7227 $myimap = Mail::IMAPClient->new( ) ;
7228 $myimap->Debug( 1 ) ;
7229 is( undef, reconnect_if_needed( $myimap ), 'reconnect_if_needed: empty new Mail::IMAPClient => undef' ) ;
7230 $myimap->Server( 'test.lamiral.info' ) ;
7231 is( 1, reconnect_if_needed( $myimap ), 'reconnect_if_needed: test.lamiral.info => 1' ) ;
7232 is( 1, $myimap->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_if_needed: test.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ;
7233
7234 note( 'Leaving tests_reconnect_if_needed()' ) ;
7235 return ;
7236}
7237
7238sub reconnect_if_needed
7239{
7240 # return undef upon failure.
7241 # return 1 upon connection success, with or without reconnection.
7242
7243 my $imap = shift ;
7244
7245 if ( ! defined $imap ) { return ; }
7246 if ( ! $imap->Server( ) ) { return ; }
7247
7248 if ( $imap->IsUnconnected( ) ) {
7249 $imap->{IMAPSYNC_RECONNECT_COUNT} += 1 ;
7250 if ( $imap->reconnect( ) ) {
7251 return 1 ;
7252 }
7253 }else{
7254 return 1 ;
7255 }
7256
7257 # A last forced one
7258 $imap->State( Mail::IMAPClient::Unconnected ) ;
7259 $imap->reconnect( ) ;
7260 $imap->{IMAPSYNC_RECONNECT_COUNT} += 1 ;
7261 if ( $imap->noop ) {
7262 # NOOP is ok
7263 return 1 ;
7264 }
7265
7266 return ;
7267}
7268
7269
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007270sub justconnect
7271{
7272 my $mysync = shift ;
7273 my $justconnect1 = justconnect1( $sync ) ;
7274 my $justconnect2 = justconnect2( $sync ) ;
7275 return "$justconnect1 $justconnect2";
7276}
7277
7278sub justconnect1
7279{
7280 my $mysync = shift ;
7281 if ( $mysync->{host1} )
7282 {
7283 myprint( "Host1: Will just connect to $mysync->{host1} without login\n" ) ;
7284 $mysync->{imap1} = connect_imap(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007285 $mysync->{host1}, $mysync->{port1},
7286 $mysync->{ssl1}, $mysync->{tls1},
7287 $mysync->{ acc1 } ) ;
7288
7289 imap_id( $mysync, $mysync->{imap1}, $mysync->{ acc1 }->{ Side } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007290 $mysync->{imap1}->logout( ) ;
7291 return $mysync->{host1} ;
7292 }
7293
7294 return q{} ;
7295}
7296
7297sub justconnect2
7298{
7299 my $mysync = shift ;
7300 if ( $mysync->{host2} )
7301 {
7302 myprint( "Host2: Will just connect to $mysync->{host2} without login\n" ) ;
7303 $mysync->{imap2} = connect_imap(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007304 $mysync->{host2}, $mysync->{port2},
7305 $mysync->{ssl2}, $mysync->{tls2},
7306 $mysync->{ acc2 } ) ;
7307
7308 imap_id( $mysync, $mysync->{imap2}, $mysync->{ acc2 }->{ Side } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007309 $mysync->{imap2}->logout( ) ;
7310 return $mysync->{host2} ;
7311 }
7312
7313 return q{} ;
7314}
7315
7316sub skip_macosx
7317{
7318 #return ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007319 # hostname is sometimes "macosx.polarhome.com" sometimes "macosx"
7320 return( ( ( 'macosx.polarhome.com' eq hostname( ) ) || ( 'macosx' eq hostname( ) ) )
7321 && ( 'darwin' eq $OSNAME ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007322}
7323
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007324sub skip_macosx_binary
7325{
7326 #return ;
7327 return( skip_macosx( ) && ( $PROGRAM_NAME =~ m{imapsync_bin_Darwin} ) ) ;
7328}
7329
7330
7331
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007332sub tests_mailimapclient_connect
7333{
7334 note( 'Entering tests_mailimapclient_connect()' ) ;
7335
7336 my $imap ;
7337 # ipv4
7338 ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect ipv4: new' ) ;
7339 is( 'Mail::IMAPClient', ref( $imap ), 'mailimapclient_connect ipv4: ref is Mail::IMAPClient' ) ;
7340
7341 # Mail::IMAPClient 3.40 die on this... So we skip it, thanks to "mature" IO::Socket::IP
7342 # Mail::IMAPClient 3.42 is ok so this test is back.
7343 is( undef, $imap->connect( ), 'mailimapclient_connect ipv4: connect with no server => failure' ) ;
7344
7345
7346 is( 'test.lamiral.info', $imap->Server( 'test.lamiral.info' ), 'mailimapclient_connect ipv4: setting Server(test.lamiral.info)' ) ;
7347 is( 1, $imap->Debug( 1 ), 'mailimapclient_connect ipv4: setting Debug( 1 )' ) ;
7348 is( 143, $imap->Port( 143 ), 'mailimapclient_connect ipv4: setting Port( 143 )' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007349 is( 10, $imap->Timeout( 10 ), 'mailimapclient_connect ipv4: setting Timeout( 10 )' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007350 like( ref( $imap->connect( ) ), qr/IO::Socket::INET|IO::Socket::IP/, 'mailimapclient_connect ipv4: connect to test.lamiral.info' ) ;
7351 like( $imap->logout( ), qr/Mail::IMAPClient/, 'mailimapclient_connect ipv4: logout' ) ;
7352 is( undef, undef $imap, 'mailimapclient_connect ipv4: free variable' ) ;
7353
7354 # ipv4 + ssl
7355 ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect ipv4 + ssl: new' ) ;
7356 is( 'test.lamiral.info', $imap->Server( 'test.lamiral.info' ), 'mailimapclient_connect ipv4 + ssl: setting Server(test.lamiral.info)' ) ;
7357 is( 1, $imap->Debug( 1 ), 'mailimapclient_connect ipv4 + ssl: setting Debug( 1 )' ) ;
7358 ok( $imap->Ssl( [ SSL_verify_mode => SSL_VERIFY_NONE, SSL_cipher_list => 'DEFAULT:!DH' ] ), 'mailimapclient_connect ipv4 + ssl: setting Ssl( SSL_VERIFY_NONE )' ) ;
7359 is( 993, $imap->Port( 993 ), 'mailimapclient_connect ipv4 + ssl: setting Port( 993 )' ) ;
7360 like( ref( $imap->connect( ) ), qr/IO::Socket::SSL/, 'mailimapclient_connect ipv4 + ssl: connect to test.lamiral.info' ) ;
7361 like( $imap->logout( ), qr/Mail::IMAPClient/, 'mailimapclient_connect ipv4 + ssl: logout in ssl does not cause failure' ) ;
7362 is( undef, undef $imap, 'mailimapclient_connect ipv4 + ssl: free variable' ) ;
7363
7364 # ipv6 + ssl
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007365
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007366 ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect ipv6 + ssl: new' ) ;
7367 is( 'petiteipv6.lamiral.info', $imap->Server( 'petiteipv6.lamiral.info' ), 'mailimapclient_connect ipv6 + ssl: setting Server petiteipv6.lamiral.info' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007368 is( 10, $imap->Timeout( 10 ), 'mailimapclient_connect ipv6: setting Timeout( 10 )' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007369 ok( $imap->Ssl( [ SSL_verify_mode => SSL_VERIFY_NONE, SSL_cipher_list => 'DEFAULT:!DH' ] ), 'mailimapclient_connect ipv6 + ssl: setting Ssl( SSL_VERIFY_NONE )' ) ;
7370 is( 993, $imap->Port( 993 ), 'mailimapclient_connect ipv6 + ssl: setting Port( 993 )' ) ;
7371 SKIP: {
7372 if (
7373 'CUILLERE' eq hostname()
7374 or
7375 skip_macosx()
7376 or
7377 -e '/.dockerenv'
7378 or
7379 'pcHPDV7-HP' eq hostname()
7380 )
7381 {
7382 skip( 'Tests avoided on CUILLERE/pcHPDV7-HP/macosx.polarhome.com/docker cannot do ipv6', 4 ) ;
7383 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007384
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007385 is( 1, $imap->Debug( 1 ), 'mailimapclient_connect ipv4 + ssl: setting Debug( 1 )' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007386
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007387 # It sounds stupid but it avoids failures on the next test about $imap->connect
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007388 is( '2a01:e34:ecde:70d0:223:54ff:fec2:36d7', resolv( 'petiteipv6.lamiral.info' ), 'resolv: petiteipv6.lamiral.info => 2a01:e34:ecde:70d0:223:54ff:fec2:36d7' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007389
7390 like( ref( $imap->connect( ) ), qr/IO::Socket::SSL/, 'mailimapclient_connect ipv6 + ssl: connect to petiteipv6.lamiral.info' ) ;
7391 # This one is ok on petite, not on ks2, do not know why, so commented.
7392 like( ref( $imap->logout( ) ), qr/Mail::IMAPClient/, 'mailimapclient_connect ipv6 + ssl: logout in ssl is ok on petiteipv6.lamiral.info' ) ;
7393 }
7394
7395 is( undef, undef $imap, 'mailimapclient_connect ipv6 + ssl: free variable' ) ;
7396
7397
7398 note( 'Leaving tests_mailimapclient_connect()' ) ;
7399 return ;
7400}
7401
7402
7403sub tests_mailimapclient_connect_bug
7404{
7405 note( 'Entering tests_mailimapclient_connect_bug()' ) ;
7406
7407 my $imap ;
7408
7409 # ipv6
7410 ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect_bug ipv6: new' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007411 is( 'ks6ipv6.lamiral.info', $imap->Server( 'ks6ipv6.lamiral.info' ), 'mailimapclient_connect_bug ipv6: setting Server(ks6ipv6.lamiral.info)' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007412 is( 143, $imap->Port( 143 ), 'mailimapclient_connect_bug ipv6: setting Port( 993 )' ) ;
7413
7414 SKIP: {
7415 if (
7416 'CUILLERE' eq hostname()
7417 or
7418 skip_macosx()
7419 or
7420 -e '/.dockerenv'
7421 or
7422 'pcHPDV7-HP' eq hostname()
7423 )
7424 {
7425 skip( 'Tests avoided on CUILLERE/pcHPDV7-HP/macosx.polarhome.com/docker cannot do ipv6', 1 ) ;
7426 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007427 like( ref( $imap->connect( ) ), qr/IO::Socket::INET/, 'mailimapclient_connect_bug ipv6: connect to ks6ipv6.lamiral.info' )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007428 or diag( 'mailimapclient_connect_bug ipv6: ', $imap->LastError( ), $!, ) ;
7429 }
7430 #is( $imap->logout( ), undef, 'mailimapclient_connect_bug ipv6: logout in ssl causes failure' ) ;
7431 is( undef, undef $imap, 'mailimapclient_connect_bug ipv6: free variable' ) ;
7432
7433 note( 'Leaving tests_mailimapclient_connect_bug()' ) ;
7434 return ;
7435}
7436
7437
7438
7439sub tests_connect_socket
7440{
7441 note( 'Entering tests_connect_socket()' ) ;
7442
7443 is( undef, connect_socket( ), 'connect_socket: no args' ) ;
7444
7445 my $socket ;
7446 my $imap ;
7447 SKIP: {
7448 if (
7449 'CUILLERE' eq hostname()
7450 or
7451 skip_macosx()
7452 or
7453 -e '/.dockerenv'
7454 or
7455 'pcHPDV7-HP' eq hostname()
7456 )
7457 {
7458 skip( 'Tests avoided on CUILLERE/pcHPDV7-HP/macosx.polarhome.com/docker cannot do ipv6', 2 ) ;
7459 }
7460
7461 $socket = IO::Socket::INET6->new(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007462 PeerAddr => 'ks6ipv6.lamiral.info',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007463 PeerPort => 143,
7464 ) ;
7465
7466
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007467 ok( $imap = connect_socket( $socket ), 'connect_socket: ks6ipv6.lamiral.info port 143 IO::Socket::INET6' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007468 #$imap->Debug( 1 ) ;
7469 # myprint( $imap->capability( ) ) ;
7470 if ( $imap ) {
7471 $imap->logout( ) ;
7472 }
7473
7474 $IO::Socket::SSL::DEBUG = 4 ;
7475 $socket = IO::Socket::SSL->new(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007476 PeerHost => 'ks6ipv6.lamiral.info',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007477 PeerPort => 993,
7478 SSL_verify_mode => SSL_VERIFY_NONE,
7479 SSL_cipher_list => 'DEFAULT:!DH',
7480 ) ;
7481 # myprint( $socket ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007482 ok( $imap = connect_socket( $socket ), 'connect_socket: ks6ipv6.lamiral.info port 993 IO::Socket::SSL' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007483 #$imap->Debug( 1 ) ;
7484 # myprint( $imap->capability( ) ) ;
7485 # $socket->close( ) ;
7486 if ( $imap ) {
7487 $socket->close( ) ;
7488 }
7489 #$socket->close(SSL_no_shutdown => 1) ;
7490 #$imap->logout( ) ;
7491 #myprint( "\n" ) ;
7492 #$imap->logout( ) ;
7493 }
7494 note( 'Leaving tests_connect_socket()' ) ;
7495 return ;
7496}
7497
7498sub connect_socket
7499{
7500 my( $socket ) = @ARG ;
7501
7502 if ( ! defined $socket ) { return ; }
7503
7504 my $host = $socket->peerhost( ) ;
7505 my $port = $socket->peerport( ) ;
7506 #print "socket->peerhost: ", $socket->peerhost( ), "\n" ;
7507 #print "socket->peerport: ", $socket->peerport( ), "\n" ;
7508 my $imap = Mail::IMAPClient->new( ) ;
7509 $imap->Socket( $socket ) ;
7510 my $banner = $imap->Results()->[0] ;
7511 #myprint( "banner: $banner" ) ;
7512 return $imap ;
7513}
7514
7515
7516sub tests_probe_imapssl
7517{
7518 note( 'Entering tests_probe_imapssl()' ) ;
7519
7520 is( undef, probe_imapssl( ), 'probe_imapssl: no args => undef' ) ;
7521 is( undef, probe_imapssl( 'unknown' ), 'probe_imapssl: unknown => undef' ) ;
7522
7523 note( "hostname is: ", hostname() ) ;
7524 SKIP: {
7525 if (
7526 'CUILLERE' eq hostname()
7527 or
7528 skip_macosx()
7529 or
7530 -e '/.dockerenv'
7531 or
7532 'pcHPDV7-HP' eq hostname()
7533 )
7534 {
7535 skip( 'Tests avoided on CUILLERE or pcHPDV7-HP or Mac or docker: cannot do ipv6', 0 ) ;
7536 }
7537 # fed up with this one
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007538 #like( probe_imapssl( 'ks6ipv6.lamiral.info' ), qr/^\* OK/, 'probe_imapssl: ks6ipv6.lamiral.info matches "* OK"' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007539 } ;
7540
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007541
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007542 # It sounds stupid but it avoids failures on the next test about $imap->connect
7543 ok( resolv( 'imap.gmail.com' ), 'resolv: imap.gmail.com => something' ) ;
7544 like( probe_imapssl( 'imap.gmail.com' ), qr/^\* OK/, 'probe_imapssl: imap.gmail.com matches "* OK"' ) ;
7545
7546 like( probe_imapssl( 'test1.lamiral.info' ), qr/^\* OK/, 'probe_imapssl: test1.lamiral.info matches "* OK"' ) ;
7547
7548 note( 'Leaving tests_probe_imapssl()' ) ;
7549 return ;
7550}
7551
7552
7553sub probe_imapssl
7554{
7555 my $host = shift ;
7556
7557 if ( ! $host ) { return ; }
7558 $sync->{ debug } and $IO::Socket::SSL::DEBUG = 4 ;
7559 my $socket = IO::Socket::SSL->new(
7560 PeerHost => $host,
7561 PeerPort => $IMAP_SSL_PORT,
7562 SSL_verifycn_scheme => 'imap',
7563 SSL_verify_mode => $SSL_VERIFY_POLICY,
7564 SSL_cipher_list => 'DEFAULT:!DH',
7565 ) ;
7566 if ( ! $socket ) { return ; }
7567 $sync->{ debug } and print "socket: $socket\n" ;
7568
7569 my $banner ;
7570 $socket->sysread( $banner, 65_536 ) ;
7571 $sync->{ debug } and print "banner: $banner" ;
7572 $socket->close( ) ;
7573 return $banner ;
7574
7575}
7576
7577sub connect_imap
7578{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007579 my( $host, $port, $ssl, $tls, $acc ) = @_ ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007580 my $imap = Mail::IMAPClient->new( ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007581
7582 if ( $ssl ) { set_ssl( $imap, $acc ) }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007583 $imap->Server( $host ) ;
7584 $imap->Port( $port ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007585 $imap->Debug( $acc->{ debugimap } ) ;
7586 $imap->Timeout( $acc->{ timeout } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007587
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007588 #$imap->Keepalive( $acc->{ keepalive } ) ;
7589
7590
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007591 my $side = lc $acc->{ Side } ;
7592
7593 myprint( "$acc->{ Side }: connecting on $side [$host] port [$port]\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007594
7595 if ( ! $imap->connect( ) )
7596 {
7597 $sync->{nb_errors}++ ;
7598 exit_clean( $sync, $EXIT_CONNECTION_FAILURE,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007599 "$acc->{ Side }: Can not open imap connection on [$host]: ",
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007600 $imap->LastError,
7601 " $OS_ERROR\n"
7602 ) ;
7603 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007604 myprint( "$acc->{ Side } IP address: ", $imap->Socket->peerhost(), "\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007605
7606 my $banner = $imap->Results()->[0] ;
7607
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007608 myprint( "$acc->{ Side } banner: $banner" ) ;
7609 myprint( "$acc->{ Side } capability: ", join(q{ }, @{ $imap->capability() || [] }), "\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007610
7611 if ( $tls ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007612 set_tls( $imap, $acc ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007613 if ( ! $imap->starttls( ) )
7614 {
7615 $sync->{nb_errors}++ ;
7616 exit_clean( $sync, $EXIT_TLS_FAILURE,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007617 "$acc->{ Side }: Can not go to tls encryption on $side [$host]:",
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007618 $imap->LastError, "\n"
7619 ) ;
7620 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007621 myprint( "$acc->{ Side }: Socket successfully converted to SSL\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007622 }
7623 return( $imap ) ;
7624}
7625
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007626sub tests_compress_ssl
7627{
7628 note( 'Entering tests_compress_ssl()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007629
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007630 SKIP: {
7631 if ( skip_macosx( ) )
7632 {
7633 skip( 'Tests avoided on host polarhome macosx, no clue "ssl3_get_server_certificate:certificate verify failed"', 12 ) ;
7634 }
7635 else
7636 {
7637 my $myimap ;
7638 my $acc = {} ;
7639 $acc->{ Side } = 'HostK' ;
7640 $acc->{ authmech } = 'LOGIN' ;
7641 $acc->{ debugimap } = 1 ;
7642 $acc->{ compress } = 1 ;
7643 $acc->{ N } = 'K' ;
7644
7645 ok(
7646 $myimap = login_imap( 'test1.lamiral.info', 993, 'test1', 'secret1',
7647 1, undef,
7648 1, 100, $acc, {},
7649 ), 'acc_compress_imap: test1.lamiral.info test1 ssl' ) ;
7650 ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 ssl IsAuthenticated' ) ;
7651
7652
7653 is( $acc->{ imap }, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info ok" ) ;
7654 is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info 2nd nok" ) ;
7655
7656 ok(
7657 $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1',
7658 0, undef,
7659 1, 100, $acc, {},
7660 ), 'acc_compress_imap: test1.lamiral.info test1 tls' ) ;
7661 ok( $myimap && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 tls IsAuthenticated' ) ;
7662
7663 is( $acc->{ imap }, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info tls ok" ) ;
7664 is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info tls 2nd nok" ) ;
7665
7666 # Third, no compression
7667 $acc->{ compress } = 0 ;
7668 ok(
7669 $myimap = login_imap( 'test1.lamiral.info', 993, 'test1', 'secret1',
7670 1, undef,
7671 1, 100, $acc, {},
7672 ), 'acc_compress_imap: test1.lamiral.info test1 ssl' ) ;
7673 ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 ssl IsAuthenticated' ) ;
7674
7675
7676 is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info off ok" ) ;
7677 is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info 2nd off ok" ) ;
7678
7679 }
7680 }
7681 note( 'Leaving tests_compress_ssl()' ) ;
7682 return ;
7683}
7684
7685sub tests_compress
7686{
7687 note( 'Entering tests_compress()' ) ;
7688
7689 my $myimap ;
7690 my $acc = {} ;
7691 $acc->{ Side } = 'HostK' ;
7692 $acc->{ authmech } = 'LOGIN' ;
7693 $acc->{ debugimap } = 1 ;
7694 $acc->{ compress } = 1 ;
7695 $acc->{ N } = 'K' ;
7696
7697 ok(
7698 $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1',
7699 0, 0,
7700 1, 100, $acc, {},
7701 ), 'acc_compress_imap: test1.lamiral.info test1' ) ;
7702 ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 IsAuthenticated' ) ;
7703
7704
7705 is( $acc->{ imap }, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info ok" ) ;
7706 is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info 2nd nok" ) ;
7707
7708 ok(
7709 $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1',
7710 0, 0,
7711 1, 100, $acc, {},
7712 ), 'acc_compress_imap: test1.lamiral.info test1 tls' ) ;
7713 ok( $myimap && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 tls IsAuthenticated' ) ;
7714
7715 is( $acc->{ imap }, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info tls ok" ) ;
7716 is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info tls 2nd nok" ) ;
7717
7718 # Third, no compression
7719 $acc->{ compress } = 0 ;
7720 ok(
7721 $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1',
7722 0, 0,
7723 1, 100, $acc, {},
7724 ), 'acc_compress_imap: test1.lamiral.info test1 ssl' ) ;
7725 ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 ssl IsAuthenticated' ) ;
7726
7727
7728 is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info off ok" ) ;
7729 is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info 2nd off ok" ) ;
7730
7731 note( 'Leaving tests_compress()' ) ;
7732 return ;
7733}
7734
7735
7736sub acc_compress_imap
7737{
7738 my $acc = shift ;
7739
7740 if ( ! defined( $acc ) ) { return ; }
7741
7742 my $ret ;
7743 my $imap = $acc->{ imap } ;
7744 if ( ! defined $imap ) { return ; }
7745
7746 if ( $imap && $acc->{ compress } )
7747 {
7748 myprint( "$acc->{ Side }: Trying to turn imap compression on. Use --nocompress" . $acc->{ N } . " to avoid compression on " . lc( $acc->{ Side } ) . "\n" ) ;
7749 if ( $ret = $imap->compress() )
7750 {
7751 myprint( "$acc->{ Side }: Compression is on now\n" ) ;
7752 }
7753 else
7754 {
7755 myprint( "$acc->{ Side }: Failed to turn compression on\n" ) ;
7756 }
7757 }
7758 else
7759 {
7760 myprint( "$acc->{ Side }: Compression is off. Use --compress" . $acc->{ N } . " to allow compression on " . lc( $acc->{ Side } ) . "\n" ) ;
7761 }
7762 # $ret is $acc->{ imap } on success, undef on failure or when there is nothing to do.
7763 return $ret ;
7764}
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007765
7766sub tests_login_imap
7767{
7768 note( 'Entering tests_login_imap()' ) ;
7769
7770 is( undef, login_imap( ), 'login_imap: no args => undef' ) ;
7771
7772 SKIP: {
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007773 if ( skip_macosx( ) )
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007774 {
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007775 skip( 'Tests avoided only on binary on host polarhome macosx, no clue "ssl3_get_server_certificate:certificate verify failed"', 15 ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007776 }
7777 else{
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007778
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007779 my $myimap ;
7780 my $acc = {} ;
7781 $acc->{ Side } = 'HostK' ;
7782 $acc->{ authmech } = 'LOGIN' ;
7783 #$IO::Socket::SSL::DEBUG = 4 ;
7784 # Each month (trimester?):
7785 # echo | openssl s_client -crlf -connect test1.lamiral.info:993
7786 # ...
7787 # certificate has expired
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007788 # Fix: ssh root@test1.lamiral.info 'apt update && apt upgrade && /etc/init.d/dovecot restart'
7789 #
7790 # or
7791 # echo | openssl s_client -crlf -connect test1.lamiral.info:993
7792 # ...
7793 # Verify return code: 9 (certificate is not yet valid)
7794 # Fix: /etc/init.d/openntpd restart
7795 # 2021_09_04 done
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007796 ok(
7797 $myimap = login_imap( 'test1.lamiral.info', 993, 'test1', 'secret1',
7798 1, undef,
7799 1, 100, $acc, {},
7800 ), 'login_imap: test1.lamiral.info test1 ssl' ) ;
7801 ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: test1.lamiral.info test1 ssl IsAuthenticated' ) ;
7802
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007803 is( $myimap, $acc->{ imap }, "login_imap: acc->{ imap } ok test1 ssl") ;
7804
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007805 ok(
7806 $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1',
7807 0, undef,
7808 1, 100, $acc, {},
7809 ), 'login_imap: test1.lamiral.info test1 tls' ) ;
7810 ok( $myimap && $myimap->IsAuthenticated( ), 'login_imap: test1.lamiral.info test1 tls IsAuthenticated' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007811 is( $myimap, $acc->{ imap }, "login_imap: acc->{ imap } ok test1 tls") ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007812
7813 #$IO::Socket::SSL::DEBUG = 4 ;
7814 $acc->{sslargs} = { SSL_version => 'SSLv2' } ;
7815 # SSLv2 not supported
7816 is(
7817 undef, $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1',
7818 0, undef,
7819 1, 100, $acc, {},
7820 ), 'login_imap: test1.lamiral.info test1 tls SSLv2 not supported' ) ;
7821#SSL_verify_mode => 1
7822#SSL_version => 'TLSv1_1'
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007823 is( undef, $acc->{ imap }, "login_imap: acc->{ imap } test1 tls error => undef") ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007824
7825
7826 # I have left ? exit_clean to be replaced by errors_incr( $mysync, 'error message' )
7827 # 1 in login_imap()
7828
7829
7830 my $mysync = {} ;
7831 $acc = {} ;
7832 $acc->{ Side } = 'Host2' ;
7833 $acc->{ authmech } = 'LOGIN' ;
7834 is(
7835 undef, login_imap( 'noresol.lamiral.info', 143, 'test1', 'secret1',
7836 0, undef,
7837 1, 100, $acc, $mysync,
7838 ), 'login_imap: noresol.lamiral.info undef' ) ;
7839
7840 is( 'ERR_CONNECTION_FAILURE_HOST2', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host2 noresol.lamiral.info => ERR_CONNECTION_FAILURE_HOST2' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007841 is( undef, $acc->{ imap }, "login_imap: acc->{ imap } noresol error => undef") ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007842
7843 # authentication failure for user2
7844 $mysync = {} ;
7845 is(
7846 undef, login_imap( 'test1.lamiral.info', 143, 'test1', 'Ce crétin',
7847 0, undef,
7848 1, 100, $acc, $mysync,
7849 ), 'login_imap: user2 bad passord => undef' ) ;
7850
7851 is( 'ERR_AUTHENTICATION_FAILURE_USER2', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host2 bad password => ERR_AUTHENTICATION_FAILURE_USER2' ) ;
7852
7853 # authentication failure for user1
7854 $mysync = {} ;
7855 $acc = {} ;
7856 $acc->{ Side } = 'Host1' ;
7857 $acc->{ authmech } = 'LOGIN' ;
7858 is(
7859 undef, login_imap( 'test1.lamiral.info', 143, 'test1', 'Ce crétin',
7860 0, undef,
7861 1, 100, $acc, $mysync,
7862 ), 'login_imap: user1 bad passord => undef' ) ;
7863
7864 is( 'ERR_AUTHENTICATION_FAILURE_USER1', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host1 bad password => ERR_AUTHENTICATION_FAILURE_USER1' ) ;
7865
7866 }
7867 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007868
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007869 note( 'Leaving tests_login_imap()' ) ;
7870 return ;
7871}
7872
7873sub oauthgenerateaccess
7874{
7875 if ( "petite" eq hostname() )
7876 {
7877 myprint( "oauthgenerateaccess\n" ) ;
7878 my @output = backtick( 'cd oauth2 && pwd && ./generate_gmail_token imapsync.gl0@gmail.com' ) ;
7879 myprint( @output ) ;
7880 }
7881 return ;
7882}
7883
7884sub tests_login_imap_oauth
7885{
7886 note( 'Entering tests_login_imap_oauth()' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007887
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007888 oauthgenerateaccess() ;
7889
7890 SKIP: {
7891 if ( skip_macosx_binary( ) )
7892 {
7893 skip( 'Tests avoided only on binary on host polarhome macosx, no clue "ssl3_get_server_certificate:certificate verify failed"', 6 ) ;
7894 }
7895 else
7896 {
7897
7898 my $mysync ;
7899 my $acc ;
7900 # oauthdirect authentication failure for user2
7901 $mysync = {} ;
7902 $acc = {} ;
7903 $acc->{ oauthdirect } = 'caca2' ;
7904 $acc->{ debugimap } = 1 ;
7905 $mysync->{ showpasswords } = 1 ;
7906 $acc->{ Side } = 'Host2' ;
7907 $acc->{ authmech } = 'QQQ' ;
7908 is(
7909 undef, login_imap( 'imap.gmail.com', 993, 'test1', 'Ce crétin',
7910 1, undef,
7911 1, 100, $acc, $mysync,
7912 ), 'login_imap: user2 bad oauthdirect => undef' ) ;
7913
7914 is( 'ERR_AUTHENTICATION_FAILURE_USER2', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host2 bad oauthdirect => ERR_AUTHENTICATION_FAILURE_USER2' ) ;
7915
7916 # oauthdirect authentication failure for user1
7917 $mysync = {} ;
7918 $acc = {} ;
7919 $acc->{ Side } = 'Host1' ;
7920 $acc->{ oauthdirect } = 'caca1' ;
7921 $acc->{ debugimap } = 1 ;
7922 $mysync->{ showpasswords } = 1 ;
7923 $acc->{ authmech } = 'QQQ' ;
7924 is(
7925 undef, login_imap( 'imap.gmail.com', 993, 'test1', 'Ce crétin',
7926 1, undef,
7927 1, 100, $acc, $mysync,
7928 ), 'login_imap: user1 bad oauthdirect => undef' ) ;
7929
7930 is( 'ERR_AUTHENTICATION_FAILURE_USER1', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host1 bad oauthdirect => ERR_AUTHENTICATION_FAILURE_USER1' ) ;
7931
7932 # oauthdirect authentication failure for user1
7933 $mysync = {} ;
7934 $acc = {} ;
7935 $acc->{ Side } = 'Host1' ;
7936 $acc->{ oauthdirect } = '' ;
7937 $acc->{ debugimap } = 1 ;
7938 $mysync->{ showpasswords } = 1 ;
7939 $acc->{ authmech } = 'QQQ' ;
7940 is(
7941 undef, login_imap( 'imap.gmail.com', 993, 'test1', 'Ce crétin',
7942 1, undef,
7943 1, 100, $acc, $mysync,
7944 ), 'login_imap: user1 bad oauthdirect => undef' ) ;
7945
7946 is( 'ERR_AUTHENTICATION_FAILURE_USER1', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host1 no oauthdirect value => ERR_AUTHENTICATION_FAILURE_USER1' ) ;
7947
7948 }
7949 }
7950
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007951 # oauthdirect authentication success for user1
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007952 SKIP: {
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007953 if ( ! -r 'oauth2/D_oauth2_oauthdirect_imapsync.gl0@gmail.com.txt' )
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007954 {
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007955 skip( 'oauthdirect: no oauthdirect file', 6 ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007956 }
7957 my $myimap ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007958 my $mysync = {} ;
7959 my $acc = {} ;
7960 $acc->{ Side } = 'Host1' ;
7961 $acc->{ oauthdirect } = 'oauth2/D_oauth2_oauthdirect_imapsync.gl0@gmail.com.txt' ;
7962 $acc->{ debugimap } = 1 ;
7963 $mysync->{ showpasswords } = 1 ;
7964 $acc->{ authmech } = 'QQQ' ;
7965 isa_ok(
7966 $myimap = login_imap( 'imap.gmail.com', 993, 'user_useless', 'password_useless',
7967 1, undef,
7968 1, 100, $acc, $mysync,
7969 ), 'Mail::IMAPClient', 'login_imap: user1 good oauthdirect => Mail::IMAPClient' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007970
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007971 ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthdirect IsAuthenticated' ) ;
7972
7973 ok( defined( $myimap ) && $myimap->logout( ), 'login_imap: gmail oauth2 oauthdirect logout' ) ;
7974 ok( defined( $myimap ) && ! $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthdirect not IsAuthenticated after logout' ) ;
7975 ok( defined( $myimap ) && $myimap->reconnect( ), 'login_imap: gmail oauth2 oauthdirect reconnect ok' ) ;
7976 ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthdirect IsAuthenticated after reconnect' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007977 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007978
7979
7980
7981 # oauthaccesstoken authentication success for user1
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007982 SKIP: {
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007983 if ( ! -r 'oauth2/D_oauth2_access_token_imapsync.gl0@gmail.com.txt' )
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007984 {
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007985 skip( 'oauthaccesstoken: no access_token file', 6 ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007986 }
7987 my $myimap ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007988 my $mysync = {} ;
7989 my $acc = {} ;
7990 $acc->{ Side } = 'Host1' ;
7991 $acc->{ oauthaccesstoken } = 'oauth2/D_oauth2_access_token_imapsync.gl0@gmail.com.txt' ;
7992 $acc->{ debugimap } = 1 ;
7993 $mysync->{ showpasswords } = 1 ;
7994 $acc->{ authmech } = 'QQQ' ;
7995 isa_ok(
7996 $myimap = login_imap( 'imap.gmail.com', 993, 'imapsync.gl0@gmail.com', 'password_useless',
7997 1, undef,
7998 1, 100, $acc, $mysync,
7999 ), 'Mail::IMAPClient', 'login_imap: user1 good oauthaccesstoken => Mail::IMAPClient' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008000
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008001 ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthaccesstoken IsAuthenticated' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008002
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008003 ok( defined( $myimap ) && $myimap->logout( ), 'login_imap: gmail oauth2 oauthaccesstoken logout' ) ;
8004 ok( defined( $myimap ) && ! $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthaccesstoken not IsAuthenticated after logout' ) ;
8005 ok( defined( $myimap ) && $myimap->reconnect( ), 'login_imap: gmail oauth2 oauthaccesstoken reconnect ok' ) ;
8006 ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthaccesstoken IsAuthenticated after reconnect' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008007 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008008
8009
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008010 note( 'Leaving tests_login_imap_oauth()' ) ;
8011 return ;
8012}
8013
8014
8015
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008016sub login_imap
8017{
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008018 my @allargs = @_ ;
8019 my(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008020 $host, $port, $user, $password,
8021 $ssl, $tls,
8022 $uid, $split, $acc, $mysync ) = @allargs ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008023
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008024 $acc->{ imap } = undef ;
8025
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008026 if ( ! all_defined( $host, $port, $user, $acc->{ Side } ) )
8027 {
8028 return ;
8029 }
8030
8031 my $side = lc $acc->{ Side } ;
8032 myprint( "$acc->{ Side }: connecting and login on $side [$host] port [$port] with user [$user]\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008033
8034 my $imap = init_imap( @allargs ) ;
8035
8036 if ( ! $imap->connect() )
8037 {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008038 my $error = "$acc->{ Side } failure: can not open imap connection on $side [$host] with user [$user]: "
8039 . $imap->LastError . " $OS_ERROR\n" ;
8040 errors_incr( $mysync, $error ) ;
8041 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008042 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008043 myprint( "$acc->{ Side } IP address: ", $imap->Socket->peerhost(), "\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008044 my $banner = $imap->Results()->[0] ;
8045
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008046 myprint( "$acc->{ Side } banner: $banner" ) ;
8047 myprint( "$acc->{ Side } capability before authentication: ", join(q{ }, @{ $imap->capability() || [] }), "\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008048
8049 if ( (! $ssl) and (! defined $tls ) and $imap->has_capability( 'STARTTLS' ) ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008050 myprint( "$acc->{ Side }: going to ssl because STARTTLS is in CAPABILITY. Use --notls1 or --notls2 to avoid that behavior\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008051 $tls = 1 ;
8052 }
8053
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008054
8055 #myprint( Data::Dumper->Dump( [ @allargs ] ) ) ;
8056 if ( $tls ) {
8057 set_tls( $imap, $acc ) ;
8058
8059 if ( ! $imap->starttls( ) )
8060 {
8061 my $error = "$acc->{ Side } failure: Can not go to tls encryption on $side [$host]: "
8062 . $imap->LastError . "\n" ;
8063
8064 errors_incr( $mysync, $error ) ;
8065 return ;
8066 }
8067 myprint( "$acc->{ Side }: Socket successfully converted to SSL\n" ) ;
8068 }
8069
8070 if ( $acc->{ authmech } eq 'PREAUTH' ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008071 if ( $imap->IsAuthenticated( ) ) {
8072 $imap->Socket ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008073 myprintf("%s: Assuming PREAUTH for %s\n", $acc->{ Side }, $imap->Server ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008074 }else{
8075 $mysync->{nb_errors}++ ;
8076 exit_clean(
8077 $mysync, $EXIT_AUTHENTICATION_FAILURE,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008078 "$acc->{ Side } failure: error login on $side [$host] with user [$user] auth [PREAUTH]\n"
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008079 ) ;
8080 }
8081 }
8082
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008083
8084
8085 if ( authenticate_imap( $imap, @allargs ) )
8086 {
8087 myprint( "$acc->{ Side }: success login on [$host] with user [$user] auth [$acc->{ authmech }] or [LOGIN]\n" ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008088 $acc->{ imap } = $imap ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008089 return( $imap ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008090 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008091 else
8092 {
8093 # The errors are already printed
8094 myprint( "$acc->{ Side }: failed login on [$host] with user [$user] auth [$acc->{ authmech }]\n" ) ;
8095 return ;
8096 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008097}
8098
8099
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008100
8101sub init_imap
8102{
8103 my(
8104 $host, $port, $user, $password,
8105 $ssl, $tls,
8106 $uid, $split, $acc, $mysync ) = @_ ;
8107
8108 my ( $imap ) ;
8109
8110 $imap = Mail::IMAPClient->new() ;
8111
8112 if ( $mysync->{ tee } )
8113 {
8114 # Well, it does not change anything, does it?
8115 # It does when suppressing the hack with *STDERR
8116 $imap->Debug_fh( $mysync->{ tee } ) ;
8117 }
8118
8119 if ( $ssl ) { set_ssl( $imap, $acc ) }
8120 if ( $tls ) { } # can not do set_tls() here because connect() will directly do a STARTTLS
8121 $imap->Clear( 1 ) ;
8122 $imap->Server( $host ) ;
8123 $imap->Port( $port ) ;
8124 $imap->Fast_io( $acc->{ fastio } ) ;
8125 $imap->Buffer( $buffersize || $DEFAULT_BUFFER_SIZE ) ;
8126 $imap->Uid( $uid ) ;
8127
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008128 $imap->Peek( 1 ) ;
8129 $imap->Debug( $acc->{ debugimap } ) ;
8130 if ( $mysync->{ showpasswords } ) {
8131 $imap->Showcredentials( 1 ) ;
8132 }
8133
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008134 if ( defined( $acc->{ timeout } ) )
8135 {
8136 $imap->Timeout( $acc->{ timeout } ) ;
8137 }
8138
8139 if ( defined $acc->{ keepalive } )
8140 {
8141 $imap->Keepalive( $acc->{ keepalive } ) ;
8142 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008143
8144 if ( defined $acc->{ reconnectretry } )
8145 {
8146 $imap->Reconnectretry( $acc->{ reconnectretry } ) ;
8147 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008148
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008149 $imap->{IMAPSYNC_RECONNECT_COUNT} = 0 ;
8150 $imap->Ignoresizeerrors( $allowsizemismatch ) ;
8151 $split and $imap->Maxcommandlength( $SPLIT_FACTOR * $split ) ;
8152
8153
8154 return( $imap ) ;
8155
8156}
8157
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008158sub authenticate_imap
8159{
8160 my( $imap,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008161 $host, $port, $user, $password,
8162 $ssl, $tls,
8163 $uid, $split, $acc, $mysync ) = @_ ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008164
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008165 check_capability( $imap, $acc->{ authmech }, $acc->{ Side } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008166 $imap->User( $user ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008167
8168 if ( defined $acc->{ domain } )
8169 {
8170 $imap->Domain( $acc->{ domain } ) ;
8171 $mysync->{ debug } and myprint( "Domain: $acc->{ domain }\n" ) ;
8172 }
8173
8174 $imap->Authuser( $acc->{ authuser } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008175 $imap->Password( $password ) ;
8176
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008177 if ( 'X-MASTERAUTH' eq $acc->{ authmech } )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008178 {
8179 xmasterauth( $imap ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008180 return 1 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008181 }
8182
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008183
8184 if ( defined $acc->{ oauthdirect } )
8185 {
8186 $acc->{ authmech } = 'XOAUTH2 direct' ;
8187 return( oauthdirect( $mysync, $acc, $imap, $host, $user ) ) ;
8188 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008189
8190
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008191 if ( defined $acc->{ oauthaccesstoken } )
8192 {
8193 $acc->{ authmech } = 'XOAUTH2 accesstoken' ;
8194 return( oauthaccesstoken( $mysync, $acc, $imap, $host, $user ) ) ;
8195 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008196
8197
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008198
8199
8200 if ( $acc->{ proxyauth } ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008201 $imap->Authmechanism(q{}) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008202 $imap->User( $acc->{ authuser } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008203 } else {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008204 $imap->Authmechanism( $acc->{ authmech } ) unless ( $acc->{ authmech } eq 'LOGIN' or $acc->{ authmech } eq 'PREAUTH' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008205 }
8206
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008207 $imap->Authcallback(\&xoauth2) if ( 'XOAUTH2' eq $acc->{ authmech } ) ;
8208 $imap->Authcallback(\&plainauth) if ( ( 'PLAIN' eq $acc->{ authmech } ) or ( 'EXTERNAL' eq $acc->{ authmech } ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008209
8210
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008211 unless ( $acc->{ authmech } eq 'PREAUTH' or $imap->login( ) ) {
8212 my $info = "$acc->{ Side } failure: Error login on [$host] with user [$user] auth" ;
8213 my $einfo = imap_last_error( $imap ) ;
8214 my $error = "$info [$acc->{ authmech }]: $einfo\n" ;
8215
8216
8217 if ( ( $acc->{ authmech } eq 'LOGIN' ) or $imap->IsUnconnected( ) or $acc->{ authuser } ) {
8218 $acc->{ authuser } ||= "" ;
8219 myprint( "$acc->{ Side } info: authmech [$acc->{ authmech }] user [$user] authuser [$acc->{ authuser }] IsUnconnected [", $imap->IsUnconnected( ), "]\n" ) ;
8220 errors_incr( $mysync, $error ) ;
8221 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008222 }else{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008223 errors_incr( $mysync, $error ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008224 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008225
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008226 # It is not secure to try plain text LOGIN when another authmech failed
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008227 # but I do it anyway. This behavior is optional as option --notrylogin will skip it.
8228 if ( $mysync->{ trylogin } )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008229 {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008230 myprint( "$acc->{ Side } info: trying LOGIN Auth mechanism on [$host] with user [$user]. Use option --notrylogin to avoid this second chance to login via LOGIN auth\n" ) ;
8231 $imap->Authmechanism(q{}) ;
8232 if ( ! $imap->login( ) )
8233 {
8234 failure_login( $mysync, $acc, 'LOGIN', $imap, $host, $user ) ;
8235 return ;
8236 }
8237 else
8238 {
8239 myprint( "$acc->{ Side }: success login on [$host] with user [$user] auth [LOGIN] after [$acc->{ authmech }] failure\n" ) ;
8240 }
8241 }
8242 else
8243 {
8244 myprint( "$acc->{ Side } info: not trying LOGIN Auth mechanism on [$host] with user [$user]. Use option --trylogin to have this second chance to login via LOGIN auth\n" ) ;
8245 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008246 }
8247 }
8248
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008249 if ( $acc->{ proxyauth } ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008250 if ( ! $imap->proxyauth( $user ) ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008251 failure_proxyauth( $mysync, $acc, $acc->{ authmech }, $imap, $host, $user ) ;
8252 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008253 }
8254 }
8255
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008256 return 1;
8257}
8258
8259
8260sub failure_login
8261{
8262 my( $mysync, $acc, $authmech, $imap, $host, $user ) = @ARG ;
8263 my $info = "$acc->{ Side } failure: Error login on [$host] with user [$user] auth" ;
8264 my $einfo = imap_last_error( $imap ) ;
8265 my $error = "$info [$authmech]: $einfo\n" ;
8266 errors_incr( $mysync, $error ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008267 return ;
8268}
8269
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008270# failure_login and failure_proxyauth function are similar but
8271# variable $error so no factoring
8272sub failure_proxyauth
8273{
8274 my( $mysync, $acc, $authmech, $imap, $host, $user ) = @ARG ;
8275 my $info = "$acc->{ Side } failure: Error login on [$host] with user [$user] auth" ;
8276 my $einfo = imap_last_error( $imap ) ;
8277 my $error = "$info [$authmech] using proxy-login as [$acc->{ authuser }]: $einfo\n" ;
8278 errors_incr( $mysync, $error ) ;
8279 return ;
8280}
8281
8282
8283
8284
8285sub oauthdirect
8286{
8287 my( $mysync, $acc, $imap, $host, $user ) = @_ ;
8288
8289 my $oauthdirect_str ;
8290 if ( -f -r $acc->{ oauthdirect } )
8291 {
8292 $oauthdirect_str = firstline( $acc->{ oauthdirect } ) ;
8293 }
8294 else
8295 {
8296 $oauthdirect_str = $acc->{ oauthdirect } || 'Please define oauthdirect value' ;
8297 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008298
8299 $imap->Authmechanism( 'XOAUTH2' ) ;
8300 $imap->Authcallback( sub { return $oauthdirect_str } ) ;
8301
8302 #if ( $imap->authenticate('XOAUTH2', sub { return $oauthdirect_str } ) )
8303 if ( $imap->login( ) )
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008304 {
8305 return 1 ;
8306 }
8307 else
8308 {
8309 failure_login( $mysync, $acc, $acc->{ authmech }, $imap, $host, $user ) ;
8310 return ;
8311 }
8312 return ;
8313}
8314
8315
8316
8317
8318sub oauthaccesstoken
8319{
8320 my( $mysync, $acc, $imap, $host, $user ) = @_ ;
8321
8322 my $oauthaccesstoken_str ;
8323 if ( -f -r $acc->{ oauthaccesstoken } )
8324 {
8325 $oauthaccesstoken_str = firstline( $acc->{ oauthaccesstoken } ) ;
8326 }
8327 else
8328 {
8329 $oauthaccesstoken_str = $acc->{ oauthaccesstoken } || 'Please define oauthaccesstoken value' ;
8330 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008331
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008332 my $oauth_string = "user=" . $user . "\x01auth=Bearer ". $oauthaccesstoken_str . "\x01\x01" ;
8333 #myprint "oauth_string: $oauth_string\n" ;
8334
8335 my $oauth_string_base64 = encode_base64( $oauth_string , '' ) ;
8336 #myprint "oauth_string_base64: $oauth_string_base64\n" ;
8337
8338 my $oauthdirect_str = $oauth_string_base64 ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008339
8340 $imap->Authmechanism( 'XOAUTH2' ) ;
8341 $imap->Authcallback( sub { return $oauthdirect_str } ) ;
8342
8343 #if ( $imap->authenticate('XOAUTH2', sub { return $oauthdirect_str } ) )
8344 if ( $imap->login( ) )
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008345 {
8346 return 1 ;
8347 }
8348 else
8349 {
8350 failure_login( $mysync, $acc, $acc->{ authmech }, $imap, $host, $user ) ;
8351 return ;
8352 }
8353 return ;
8354}
8355
8356
8357
8358
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008359sub check_capability
8360{
8361
8362 my( $imap, $authmech, $Side ) = @_ ;
8363
8364
8365 if ( $imap->has_capability( "AUTH=$authmech" )
8366 or $imap->has_capability( $authmech ) )
8367 {
8368 myprintf("%s: %s says it has CAPABILITY for AUTHENTICATE %s\n",
8369 $Side, $imap->Server, $authmech) ;
8370 return ;
8371 }
8372
8373 if ( $authmech eq 'LOGIN' )
8374 {
8375 # Well, the warning is so common and useless that I prefer to remove it
8376 # No more "... says it has NO CAPABILITY for AUTHENTICATE LOGIN"
8377 return ;
8378 }
8379
8380
8381 myprintf( "%s: %s says it has NO CAPABILITY for AUTHENTICATE %s\n",
8382 $Side, $imap->Server, $authmech ) ;
8383
8384 if ( $authmech eq 'PLAIN' )
8385 {
8386 myprint( "$Side: frequently PLAIN is only supported with SSL, try --ssl or --tls options\n" ) ;
8387 }
8388
8389 return ;
8390}
8391
8392sub set_ssl
8393{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008394 my ( $imap, $acc ) = @_ ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008395 # SSL_version can be
8396 # SSLv3 SSLv2 SSLv23 SSLv23:!SSLv2 (last one is the default in IO-Socket-SSL-1.953)
8397 #
8398
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008399 my $sslargs_hash = $acc->{sslargs} ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008400
8401 my $sslargs_default = {
8402 SSL_verify_mode => $SSL_VERIFY_POLICY,
8403 SSL_verifycn_scheme => 'imap',
8404 SSL_cipher_list => 'DEFAULT:!DH',
8405 } ;
8406
8407 # initiate with default values
8408 my %sslargs_mix = %{ $sslargs_default } ;
8409 # now override with passed values
8410 @sslargs_mix{ keys %{ $sslargs_hash } } = values %{ $sslargs_hash } ;
8411 # remove keys with undef values
8412 foreach my $key ( keys %sslargs_mix ) {
8413 delete $sslargs_mix{ $key } if ( not defined $sslargs_mix{ $key } ) ;
8414 }
8415 # back to an ARRAY
8416 my @sslargs_mix = %sslargs_mix ;
8417 #myprint( Data::Dumper->Dump( [ $sslargs_hash, $sslargs_default, \%sslargs_mix, \@sslargs_mix ] ) ) ;
8418 $imap->Ssl( \@sslargs_mix ) ;
8419 return ;
8420}
8421
8422sub set_tls
8423{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008424 my ( $imap, $acc ) = @_ ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008425
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008426 my $sslargs_hash = $acc->{sslargs} ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008427
8428 my $sslargs_default = {
8429 SSL_verify_mode => $SSL_VERIFY_POLICY,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008430 SSL_cipher_list => 'DEFAULT:!DH',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008431 } ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008432 #myprint( Data::Dumper->Dump( [ $acc, $sslargs_hash, $sslargs_default ] ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008433
8434 # initiate with default values
8435 my %sslargs_mix = %{ $sslargs_default } ;
8436 # now override with passed values
8437 @sslargs_mix{ keys %{ $sslargs_hash } } = values %{ $sslargs_hash } ;
8438 # remove keys with undef values
8439 foreach my $key ( keys %sslargs_mix ) {
8440 delete $sslargs_mix{ $key } if ( not defined $sslargs_mix{ $key } ) ;
8441 }
8442 # back to an ARRAY
8443 my @sslargs_mix = %sslargs_mix ;
8444
8445 $imap->Starttls( \@sslargs_mix ) ;
8446 return ;
8447}
8448
8449
8450
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008451sub plainauth
8452{
8453 my $code = shift;
8454 my $imap = shift;
8455
8456 my $string = mysprintf("%s\x00%s\x00%s", $imap->User,
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008457 defined $imap->Authuser ? $imap->Authuser : "", $imap->Password);
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008458 return encode_base64("$string", q{});
8459}
8460
8461# Copy from https://github.com/imapsync/imapsync/pull/25/files
8462# Changes "use" pragmas to "require".
8463# The openssl system call shall be replaced by pure Perl and
8464# https://metacpan.org/pod/Crypt::OpenSSL::PKCS12
8465
8466# Now the Joaquin Lopez code:
8467#
8468# Used this as an example: https://gist.github.com/gsainio/6322375
8469#
8470# And this as a reference: https://developers.google.com/accounts/docs/OAuth2ServiceAccount
8471# (note there is an http/rest tab, where the real info is hidden away... went on a witch hunt
8472# until I noticed that...)
8473#
8474# This is targeted at gmail to maintain compatibility after google's oauth1 service is deactivated
8475# on May 5th, 2015: https://developers.google.com/gmail/oauth_protocol
8476# If there are other oauth2 implementations out there, this would need to be modified to be
8477# compatible
8478#
8479# This is a good guide on setting up the google api/apps side of the equation:
8480# http://www.limilabs.com/blog/oauth2-gmail-imap-service-account
8481#
8482# 2016/05/27: Updated to support oauth/key data in the .json files Google now defaults to
8483# when creating gmail service accounts. They're easier to work with since they neither
8484# requiring decrypting nor specifying the oauth2 client id separately.
8485#
8486# If the password arg ends in .json, it will assume this new json method, otherwise it
8487# will fallback to the "oauth client id;.p12" format it was previously using.
8488sub xoauth2
8489{
8490 require JSON::WebToken ;
8491 require LWP::UserAgent ;
8492 require HTML::Entities ;
8493 require JSON ;
8494 require JSON::WebToken::Crypt::RSA ;
8495 require Crypt::OpenSSL::RSA ;
8496 require Encode::Byte ;
8497 require IO::Socket::SSL ;
8498
8499 my $code = shift;
8500 my $imap = shift;
8501
8502 my ($iss,$key);
8503
8504 if( $imap->Password =~ /^(.*\.json)$/x )
8505 {
8506 my $json = JSON->new( ) ;
8507 my $filename = $1;
8508 $sync->{ debug } and myprint( "XOAUTH2 json file: $filename\n" ) ;
8509 my $FILE ;
8510 if ( ! open( $FILE, '<', $filename ) )
8511 {
8512 $sync->{nb_errors}++ ;
8513 exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE,
8514 "error [$filename]: $OS_ERROR\n"
8515 ) ;
8516 }
8517 my $jsonfile = $json->decode( join q{}, <$FILE> ) ;
8518 close $FILE ;
8519
8520 $iss = $jsonfile->{client_id};
8521 $key = $jsonfile->{private_key};
8522 $sync->{ debug } and myprint( "Service account: $iss\n");
8523 $sync->{ debug } and myprint( "Private key:\n$key\n");
8524 }
8525 else
8526 {
8527 # Get iss (service account address), keyfile name, and keypassword if necessary
8528 ( $iss, my $keyfile, my $keypass ) = $imap->Password =~ /([\-\d\w\@\.]+);([a-zA-Z0-9 \_\-\.\/]+);?(.*)?/x ;
8529
8530 # Assume key password is google default if not provided
8531 $keypass = 'notasecret' if not $keypass;
8532
8533 $sync->{ debug } and myprint( "Service account: $iss\nKey file: $keyfile\nKey password: $keypass\n");
8534
8535 # Get private key from p12 file (would be better in perl...)
8536 $key = `openssl pkcs12 -in "$keyfile" -nodes -nocerts -passin pass:$keypass -nomacver`;
8537
8538 $sync->{ debug } and myprint( "Private key:\n$key\n");
8539 }
8540
8541 # Create jwt of oauth2 request
8542 my $time = time ;
8543 my $jwt = JSON::WebToken->encode( {
8544 'iss' => $iss, # service account
8545 'scope' => 'https://mail.google.com/',
8546 'aud' => 'https://www.googleapis.com/oauth2/v3/token',
8547 'exp' => $time + $DEFAULT_EXPIRATION_TIME_OAUTH2_PK12,
8548 'iat' => $time,
8549 'prn' => $imap->User # user to auth as
8550 },
8551 $key, 'RS256', {'typ' => 'JWT'} ); # Crypt::OpenSSL::RSA needed here.
8552
8553 # Post oauth2 request
8554 my $ua = LWP::UserAgent->new( ) ;
8555 $ua->env_proxy( ) ;
8556
8557 my $response = $ua->post('https://www.googleapis.com/oauth2/v3/token',
8558 { grant_type => HTML::Entities::encode_entities('urn:ietf:params:oauth:grant-type:jwt-bearer'),
8559 assertion => $jwt } ) ;
8560
8561 unless( $response->is_success( ) ) {
8562 $sync->{nb_errors}++ ;
8563 exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE,
8564 $response->code, "\n", $response->content, "\n"
8565 ) ;
8566 }else{
8567 $sync->{ debug } and myprint( $response->content ) ;
8568 }
8569
8570 # access_token in response is what we need
8571 my $data = JSON::decode_json( $response->content ) ;
8572
8573 # format as oauth2 auth data
8574 my $xoauth2_string = encode_base64( 'user=' . $imap->User . "\1auth=Bearer " . $data->{access_token} . "\1\1", q{} ) ;
8575
8576 $sync->{ debug } and myprint( "XOAUTH2 String: $xoauth2_string\n");
8577 return($xoauth2_string);
8578}
8579
8580
8581
8582
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008583sub xmasterauth
8584{
8585 # This is Kerio auth admin
8586 # This code comes from
8587 # https://github.com/imapsync/imapsync/pull/53/files
8588
8589 my $imap = shift ;
8590
8591 my $user = $imap->User( ) ;
8592 my $password = $imap->Password( ) ;
8593 my $authmech = 'X-MASTERAUTH' ;
8594
8595 my @challenge = $imap->tag_and_run( $authmech, "+" ) ;
8596 if ( not defined $challenge[0] )
8597 {
8598 $sync->{nb_errors}++ ;
8599 exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE,
8600 "Failure authenticate with $authmech: ",
8601 $imap->LastError, "\n"
8602 ) ;
8603 return ; # hahaha!
8604 }
8605 $sync->{ debug } and myprint( "X-MASTERAUTH challenge: [@challenge]\n" ) ;
8606
8607 $challenge[1] =~ s/^\+ |^\s+|\s+$//g ;
8608 if ( ! $imap->_imap_command( { addcrlf => 1, addtag => 0, tag => $imap->Count }, md5_hex( $challenge[1] . $password ) ) )
8609 {
8610 $sync->{nb_errors}++ ;
8611 exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE,
8612 "Failure authenticate with $authmech: ",
8613 $imap->LastError, "\n"
8614 ) ;
8615 }
8616
8617 if ( ! $imap->tag_and_run( 'X-SETUSER ' . $user ) )
8618 {
8619 $sync->{nb_errors}++ ;
8620 exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE,
8621 "Failure authenticate with $authmech: ",
8622 "X-SETUSER ", $imap->LastError, "\n"
8623 ) ;
8624 }
8625
8626 $imap->State( Mail::IMAPClient::Authenticated ) ;
8627 # I comment this state because "Selected" state is usually done by SELECT or EXAMINE imap commands
8628 # $imap->State( Mail::IMAPClient::Selected ) ;
8629
8630 return ;
8631}
8632
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008633sub keepalive1
8634{
8635 my $mysync = shift ;
8636
8637 $mysync->{ acc1 }->{ keepalive } = defined $mysync->{ acc1 }->{ keepalive } ? $mysync->{ acc1 }->{ keepalive } : 1 ;
8638
8639 if ( $mysync->{ acc1 }->{ keepalive } )
8640 {
8641 myprint( "Host1: imap connection keepalive is on on host1. Use --nokeepalive1 to disable it.\n" ) ;
8642 }
8643 else
8644 {
8645 myprint( "Host1: imap connection keepalive is off on host1. Use --keepalive1 to enable it.\n" ) ;
8646 }
8647}
8648
8649sub keepalive2
8650{
8651 my $mysync = shift ;
8652
8653 $mysync->{ acc2 }->{ keepalive } = defined $mysync->{ acc2 }->{ keepalive } ? $mysync->{ acc2 }->{ keepalive } : 1 ;
8654
8655 if ( $mysync->{ acc2 }->{ keepalive } )
8656 {
8657 myprint( "Host2: imap connection keepalive is on on host2. Use --nokeepalive2 to disable it.\n" ) ;
8658 }
8659 else
8660 {
8661 myprint( "Host2: imap connection keepalive is off on host2. Use --keepalive2 to enable it.\n" ) ;
8662 }
8663}
8664
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008665
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008666
8667sub banner_imapsync
8668{
8669 my $mysync = shift @ARG ;
8670 my @argv = @ARG ;
8671
8672 my $banner_imapsync = join q{},
8673 q{$RCSfile: imapsync,v $ },
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008674 q{$Revision: 2.178 $ },
8675 q{$Date: 2022/01/12 21:28:37 $ },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008676 "\n",
8677 "Command line used, run by $EXECUTABLE_NAME:\n",
8678 "$PROGRAM_NAME ", command_line_nopassword( $mysync, @argv ), "\n" ;
8679
8680 return( $banner_imapsync ) ;
8681}
8682
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008683sub tests_do_valid_directory
8684{
8685 note( 'Entering tests_do_valid_directory()' ) ;
8686
8687 is( 1, do_valid_directory( '.'), 'do_valid_directory: . good' ) ;
8688 is( 1, do_valid_directory( './W/tmp/tests/valid/sub'), 'do_valid_directory: ./W/tmp/tests/valid/sub good' ) ;
8689
8690 Readonly my $NB_UNIX_tests_do_valid_directory_non_root => 2 ;
8691 diag( "OSNAME=$OSNAME EFFECTIVE_USER_ID=$EFFECTIVE_USER_ID" ) ;
8692
8693 SKIP: {
8694 skip( 'Tests only for non roor user', $NB_UNIX_tests_do_valid_directory_non_root ) if ( '0' eq $EFFECTIVE_USER_ID ) ;
8695 diag( 'The "Error / is not writable" is on purpose' ) ;
8696 ok( 0 == do_valid_directory( '/'), 'do_valid_directory: / bad' ) ;
8697 diag( 'The "Error permission denied" on /noway is on purpose' ) ;
8698 ok( 0 == do_valid_directory( '/noway'), 'do_valid_directory: /noway bad' ) ;
8699 }
8700
8701
8702 note( 'Leaving tests_do_valid_directory()' ) ;
8703 return ;
8704}
8705
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008706sub do_valid_directory
8707{
8708 my $dir = shift @ARG ;
8709
8710 # all good => return ok.
8711 return( 1 ) if ( -d $dir and -r _ and -w _ ) ;
8712
8713 # exist but bad
8714 if ( -e $dir and not -d _ ) {
8715 myprint( "Error: $dir exists but is not a directory\n" ) ;
8716 return( 0 ) ;
8717 }
8718 if ( -e $dir and not -w _ ) {
8719 my $sb = stat $dir ;
8720 myprintf( "Error: directory %s is not writable for user %s, permissions are %04o and owner is %s ( uid %s )\n",
8721 $dir, getpwuid_any_os( $EFFECTIVE_USER_ID ), ($sb->mode & oct($PERMISSION_FILTER) ), getpwuid_any_os( $sb->uid ), $sb->uid( ) ) ;
8722 return( 0 ) ;
8723 }
8724 # Trying to create it
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008725 myprint( "Creating directory $dir (current directory is " . getcwd( ) . ")\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008726 if ( ! eval { mkpath( $dir ) } ) {
8727 myprint( "$EVAL_ERROR" ) if ( $EVAL_ERROR ) ;
8728 }
8729 return( 1 ) if ( -d $dir and -r _ and -w _ ) ;
8730 return( 0 ) ;
8731}
8732
8733
8734sub tests_match_a_pid_number
8735{
8736 note( 'Entering tests_match_a_pid_number()' ) ;
8737
8738 is( undef, match_a_pid_number( ), 'match_a_pid_number: no args => undef' ) ;
8739 is( undef, match_a_pid_number( q{} ), 'match_a_pid_number: "" => undef' ) ;
8740 is( undef, match_a_pid_number( 'lalala' ), 'match_a_pid_number: lalala => undef' ) ;
8741 is( 1, match_a_pid_number( 1 ), 'match_a_pid_number: 1 => 1' ) ;
8742 is( 1, match_a_pid_number( 123 ), 'match_a_pid_number: 123 => 1' ) ;
8743 is( 1, match_a_pid_number( -123 ), 'match_a_pid_number: -123 => 1' ) ;
8744 is( 1, match_a_pid_number( '123' ), 'match_a_pid_number: "123" => 1' ) ;
8745 is( 1, match_a_pid_number( '-123' ), 'match_a_pid_number: "-123" => 1' ) ;
8746 is( undef, match_a_pid_number( 'a123' ), 'match_a_pid_number: a123 => undef' ) ;
8747 is( undef, match_a_pid_number( '-a123' ), 'match_a_pid_number: -a123 => undef' ) ;
8748 is( 1, match_a_pid_number( 99999 ), 'match_a_pid_number: 99999 => 1' ) ;
8749 is( 1, match_a_pid_number( -99999 ), 'match_a_pid_number: -99999 => 1' ) ;
8750 is( undef, match_a_pid_number( 0 ), 'match_a_pid_number: 0 => undef' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008751 is( 1, match_a_pid_number( 100000 ), 'match_a_pid_number: 100000 => 1' ) ;
8752 is( 1, match_a_pid_number( 123456 ), 'match_a_pid_number: 123456 => 1' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008753 is( undef, match_a_pid_number( '-0' ), 'match_a_pid_number: "-0" => undef' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008754 is( 1, match_a_pid_number( -100000 ), 'match_a_pid_number: -100000 => 1' ) ;
8755 is( 1, match_a_pid_number( -123456 ), 'match_a_pid_number: -123456 => 1' ) ;
8756 is( 1, match_a_pid_number( 2**22 ), 'match_a_pid_number: 2**22 => 1' ) ;
8757 is( undef, match_a_pid_number( 2**22 + 1 ), 'match_a_pid_number: 2**22 + 1 => undef' ) ;
8758 is( undef, match_a_pid_number( 4194304 + 1 ), 'match_a_pid_number: 2**22 + 1 = 4194305 => undef' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008759
8760 note( 'Leaving tests_match_a_pid_number()' ) ;
8761 return ;
8762}
8763
8764sub match_a_pid_number
8765{
8766 my $pid = shift @ARG ;
8767 if ( ! defined $pid ) { return ; }
8768 #print "$pid\n" ;
8769 if ( ! match( $pid, '^-?\d+$' ) ) { return ; }
8770 #print "$pid\n" ;
8771 # can be negative on Windows
8772 #if ( 0 > $pid ) { return ; }
8773 #if ( 65535 < $pid ) { return ; }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008774 if ( 2**22 < abs( $pid ) ) { return ; }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008775 if ( 0 == abs( $pid ) ) { return ; }
8776 return 1 ;
8777}
8778
8779sub tests_remove_pidfile_not_running
8780{
8781 note( 'Entering tests_remove_pidfile_not_running()' ) ;
8782
8783 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'remove_pidfile_not_running: mkpath W/tmp/tests/' ) ;
8784 is( undef, remove_pidfile_not_running( ), 'remove_pidfile_not_running: no args => undef' ) ;
8785 is( undef, remove_pidfile_not_running( './W' ), 'remove_pidfile_not_running: a dir => undef' ) ;
8786 is( undef, remove_pidfile_not_running( 'noexists' ), 'remove_pidfile_not_running: noexists => undef' ) ;
8787 is( 1, touch( 'W/tmp/tests/empty.pid' ), 'remove_pidfile_not_running: prepa empty W/tmp/tests/empty.pid' ) ;
8788 is( undef, remove_pidfile_not_running( 'W/tmp/tests/empty.pid' ), 'remove_pidfile_not_running: W/tmp/tests/empty.pid => undef' ) ;
8789 is( 'lalala', string_to_file( 'lalala', 'W/tmp/tests/lalala.pid' ), 'remove_pidfile_not_running: prepa W/tmp/tests/lalala.pid' ) ;
8790 is( undef, remove_pidfile_not_running( 'W/tmp/tests/lalala.pid' ), 'remove_pidfile_not_running: W/tmp/tests/lalala.pid => undef' ) ;
8791 is( '55555', string_to_file( '55555', 'W/tmp/tests/notrunning.pid' ), 'remove_pidfile_not_running: prepa W/tmp/tests/notrunning.pid' ) ;
8792 is( 1, remove_pidfile_not_running( 'W/tmp/tests/notrunning.pid' ), 'remove_pidfile_not_running: W/tmp/tests/notrunning.pid => 1' ) ;
8793 is( $PROCESS_ID, string_to_file( $PROCESS_ID, 'W/tmp/tests/running.pid' ), 'remove_pidfile_not_running: prepa W/tmp/tests/running.pid' ) ;
8794 is( undef, remove_pidfile_not_running( 'W/tmp/tests/running.pid' ), 'remove_pidfile_not_running: W/tmp/tests/running.pid => undef' ) ;
8795
8796 note( 'Leaving tests_remove_pidfile_not_running()' ) ;
8797 return ;
8798}
8799
8800sub remove_pidfile_not_running
8801{
8802 #
8803 my $pid_filename = shift @ARG ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008804
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008805 #myprint( "In remove_pidfile_not_running $pid_filename\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008806 if ( ! $pid_filename ) { myprint( "No variable pid_filename\n" ) ; return } ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008807 if ( ! -e $pid_filename )
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008808 {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008809 myprint( "File $pid_filename does not exist\n" ) ;
8810 return ;
8811 }
8812 #myprint( "Still In remove_pidfile_not_running $pid_filename\n" ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008813
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008814 if ( ! -f $pid_filename ) { myprint( "File $pid_filename is not a file\n" ) ; return } ;
8815
8816 my $pid = firstline( $pid_filename ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008817 if ( ! match_a_pid_number( $pid ) ) { myprint( "In remove_pidfile_not_running: pid $pid in $pid_filename is not a pid number\n" ) ; return } ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008818 # can't kill myself => do nothing
8819 if ( ! kill 'ZERO', $PROCESS_ID ) { myprint( "Can not kill ZERO myself $PROCESS_ID\n" ) ; return } ;
8820
8821 # can't kill ZERO the pid => it is gone or own by another user => remove pidfile
8822 if ( ! kill 'ZERO', $pid ) {
8823 myprint( "Removing old $pid_filename since its PID $pid is not running anymore (oo-killed?)\n" ) ;
8824 if ( unlink $pid_filename ) {
8825 myprint( "Removed old $pid_filename\n" ) ;
8826 return 1 ;
8827 }else{
8828 myprint( "Could not remove old $pid_filename because $!\n" ) ;
8829 return ;
8830 }
8831 }
8832 myprint( "Another imapsync process $pid is running as says pidfile $pid_filename\n" ) ;
8833 return ;
8834}
8835
8836
8837sub tests_tail
8838{
8839 note( 'Entering tests_tail()' ) ;
8840
8841 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'tail: mkpath W/tmp/tests/' ) ;
8842 ok( ( ! -e 'W/tmp/tests/tail.pid' || unlink 'W/tmp/tests/tail.pid' ), 'tail: unlink W/tmp/tests/tail.pid' ) ;
8843 ok( ( ! -e 'W/tmp/tests/tail.txt' || unlink 'W/tmp/tests/tail.txt' ), 'tail: unlink W/tmp/tests/tail.txt' ) ;
8844
8845 is( undef, tail( ), 'tail: no args => undef' ) ;
8846 my $mysync ;
8847 is( undef, tail( $mysync ), 'tail: no pidfile => undef' ) ;
8848
8849 $mysync->{pidfile} = 'W/tmp/tests/tail.pid' ;
8850 is( undef, tail( $mysync ), 'tail: no pidfilelocking => undef' ) ;
8851
8852 $mysync->{pidfilelocking} = 1 ;
8853 is( undef, tail( $mysync ), 'tail: pidfile no exists => undef' ) ;
8854
8855
8856 my $pidandlog = "33333\nW/tmp/tests/tail.txt\n" ;
8857 is( $pidandlog, string_to_file( $pidandlog, $mysync->{pidfile} ), 'tail: put pid 33333 and tail.txt in pidfile' ) ;
8858 is( undef, tail( $mysync ), 'tail: logfile to tail no exists => undef' ) ;
8859
8860 my $tailcontent = "L1\nL2\nL3\nL4\nL5\n" ;
8861 is( $tailcontent, string_to_file( $tailcontent, 'W/tmp/tests/tail.txt' ),
8862 'tail: put L1\nL2\nL3\nL4\nL5\n in W/tmp/tests/tail.txt' ) ;
8863
8864 is( undef, tail( $mysync ), 'tail: fake pid in pidfile + tail off => 1' ) ;
8865
8866 $mysync->{ tail } = 1 ;
8867 is( 1, tail( $mysync ), 'tail: fake pid in pidfile + tail on=> 1' ) ;
8868
8869 # put my own pid, won't do tail
8870 $pidandlog = "$PROCESS_ID\nW/tmp/tests/tail.txt\n" ;
8871 is( $pidandlog, string_to_file( $pidandlog, $mysync->{pidfile} ), 'tail: put my own PID in pidfile' ) ;
8872 is( undef, tail( $mysync ), 'tail: my own pid in pidfile => undef' ) ;
8873
8874 note( 'Leaving tests_tail()' ) ;
8875 return ;
8876}
8877
8878
8879
8880sub tail
8881{
8882 # return undef on failures
8883 # return 1 on success
8884
8885 my $mysync = shift ;
8886
8887 # no tail when aborting!
8888 if ( $mysync->{ abort } ) { return ; }
8889
8890 my $pidfile = $mysync->{pidfile} ;
8891 my $lock = $mysync->{pidfilelocking} ;
8892 my $tail = $mysync->{tail} ;
8893
8894 if ( ! $pidfile ) { return ; }
8895 if ( ! $lock ) { return ; }
8896 if ( ! $tail ) { return ; }
8897
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008898 if ( ! -e $pidfile ) { return ; }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008899
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008900 my $pidtotail = firstline( $pidfile ) ;
8901 if ( ! $pidtotail ) { return ; }
8902
8903
8904
8905 # It should not happen but who knows...
8906 if ( $pidtotail eq $PROCESS_ID ) { return ; }
8907
8908
8909 my $filetotail = secondline( $pidfile ) ;
8910 if ( ! $filetotail ) { return ; }
8911
8912 if ( ! -r $filetotail )
8913 {
8914 #myprint( "Error: can not read $filetotail\n" ) ;
8915 return ;
8916 }
8917
8918 myprint( "Doing a tail -f on $filetotail for processus pid $pidtotail until it is finished.\n" ) ;
8919 my $file = File::Tail->new(
8920 name => $filetotail,
8921 nowait => 1,
8922 interval => 1,
8923 tail => 1,
8924 adjustafter => 2
8925 );
8926
8927 my $moretimes = 200 ;
8928 # print one line at least
8929 my $line = $file->read ;
8930 myprint( $line ) ;
8931 while ( isrunning( $pidtotail, \$moretimes ) and defined( $line = $file->read ) )
8932 {
8933 myprint( $line );
8934 sleep( 0.02 ) ;
8935 }
8936
8937 return 1 ;
8938}
8939
8940sub isrunning
8941{
8942 my $pidtocheck = shift ;
8943 my $moretimes_ref = shift ;
8944
8945 if ( kill 'ZERO', $pidtocheck )
8946 {
8947 #myprint( "$pidtocheck running\n" ) ;
8948 return 1 ;
8949 }
8950 elsif ( $$moretimes_ref >= 0 )
8951 {
8952 # continue to consider it running
8953 $$moretimes_ref-- ;
8954 return 1 ;
8955 }
8956 else
8957 {
8958 myprint( "Tailed processus $pidtocheck ended\n" ) ;
8959 return ;
8960 }
8961}
8962
8963sub tests_write_pidfile
8964{
8965 note( 'Entering tests_write_pidfile()' ) ;
8966
8967 my $mysync ;
8968
8969 is( 1, write_pidfile( ), 'write_pidfile: no args => 1' ) ;
8970
8971 # no pidfile => ok
8972 $mysync->{pidfile} = q{} ;
8973 is( 1, write_pidfile( $mysync ), 'write_pidfile: no pidfile => undef' ) ;
8974
8975 # The pidfile path is bad => failure
8976 $mysync->{pidfile} = '/no/no/no.pid' ;
8977 is( undef, write_pidfile( $mysync ), 'write_pidfile: no permission for /no/no/no.pid, no lock => undef' ) ;
8978
8979 $mysync->{pidfilelocking} = 1 ;
8980 is( undef, write_pidfile( $mysync ), 'write_pidfile: no permission for /no/no/no.pid + lock => undef' ) ;
8981
8982 $mysync->{pidfile} = 'W/tmp/tests/test.pid' ;
8983 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'write_pidfile: mkpath W/tmp/tests/' ) ;
8984 is( 1, touch( $mysync->{pidfile} ), 'write_pidfile: lock prepa' ) ;
8985
8986 $mysync->{pidfilelocking} = 0 ;
8987 is( 1, write_pidfile( $mysync ), 'write_pidfile: W/tmp/tests/test.pid + no lock => 1' ) ;
8988 is( $PROCESS_ID, firstline( 'W/tmp/tests/test.pid' ), "write_pidfile: W/tmp/tests/test.pid contains $PROCESS_ID" ) ;
8989 is( q{}, secondline( 'W/tmp/tests/test.pid' ), "write_pidfile: W/tmp/tests/test.pid contains no second line" ) ;
8990
8991 $mysync->{pidfilelocking} = 1 ;
8992 is( undef, write_pidfile( $mysync ), 'write_pidfile: W/tmp/tests/test.pid + lock => undef' ) ;
8993
8994
8995 $mysync->{pidfilelocking} = 0 ;
8996 $mysync->{ logfile } = 'rrrr.txt' ;
8997 is( 1, write_pidfile( $mysync ), 'write_pidfile: W/tmp/tests/test.pid + no lock + logfile => 1' ) ;
8998 is( $PROCESS_ID, firstline( 'W/tmp/tests/test.pid' ), "write_pidfile: + no lock + logfile W/tmp/tests/test.pid contains $PROCESS_ID" ) ;
8999 is( q{rrrr.txt}, secondline( 'W/tmp/tests/test.pid' ), "write_pidfile: + no lock + logfile W/tmp/tests/test.pid contains rrrr.txt" ) ;
9000
9001
9002 note( 'Leaving tests_write_pidfile()' ) ;
9003 return ;
9004}
9005
9006
9007
9008sub write_pidfile
9009{
9010 # returns undef if something is considered fatal
9011 # returns 1 otherwise
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01009012
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009013 #myprint( "In write_pidfile\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009014 if ( ! @ARG ) { return 1 ; }
9015
9016 my $mysync = shift @ARG ;
9017
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009018 # Do not write the pid file if the current process goal is to abort the process designed by the pid file
9019 if ( $mysync->{ abort } ) { return 1 ; }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009020
9021 #
9022 my $pid_filename = $mysync->{ pidfile } ;
9023 my $lock = $mysync->{ pidfilelocking } ;
9024
9025 if ( ! $pid_filename )
9026 {
9027 myprint( "PID file is unset ( to set it, use --pidfile filepath ; to avoid it use --pidfile \"\" )\n" ) ;
9028 return( 1 ) ;
9029 }
9030
9031 myprint( "PID file is $pid_filename ( to change it, use --pidfile filepath ; to avoid it use --pidfile \"\" )\n" ) ;
9032 if ( -e $pid_filename and $lock ) {
9033 myprint( "$pid_filename already exists, another imapsync may be curently running. Aborting imapsync.\n" ) ;
9034 return ;
9035
9036 }
9037
9038 if ( -e $pid_filename ) {
9039 myprint( "$pid_filename already exists, overwriting it ( use --pidfilelocking to avoid concurrent runs )\n" ) ;
9040 }
9041
9042 my $pid_string = "$PROCESS_ID\n" ;
9043 my $pid_message = "Writing my PID $PROCESS_ID in $pid_filename\n" ;
9044
9045 if ( $mysync->{ logfile } )
9046 {
9047 $pid_string .= "$mysync->{ logfile }\n" ;
9048 $pid_message .= "Writing also my logfile name in $pid_filename : $mysync->{ logfile }\n" ;
9049 }
9050
9051 if ( open my $FILE_HANDLE, '>', $pid_filename ) {
9052 myprint( $pid_message ) ;
9053 print $FILE_HANDLE $pid_string ;
9054 close $FILE_HANDLE ;
9055 return( 1 ) ;
9056 }
9057 else
9058 {
9059 myprint( "Could not open $pid_filename for writing. Check permissions or disk space: $OS_ERROR\n" ) ;
9060 return ;
9061 }
9062}
9063
9064
9065sub fix_Inbox_INBOX_mapping
9066{
9067 my( $h1_all, $h2_all ) = @_ ;
9068
9069 my $regex = q{} ;
9070 SWITCH: {
9071 if ( exists $h1_all->{INBOX} and exists $h2_all->{INBOX} ) { $regex = q{} ; last SWITCH ; } ;
9072 if ( exists $h1_all->{Inbox} and exists $h2_all->{Inbox} ) { $regex = q{} ; last SWITCH ; } ;
9073 if ( exists $h1_all->{INBOX} and exists $h2_all->{Inbox} ) { $regex = q{s/^INBOX$/Inbox/x} ; last SWITCH ; } ;
9074 if ( exists $h1_all->{Inbox} and exists $h2_all->{INBOX} ) { $regex = q{s/^Inbox$/INBOX/x} ; last SWITCH ; } ;
9075 } ;
9076 return( $regex ) ;
9077}
9078
9079sub tests_fix_Inbox_INBOX_mapping
9080{
9081 note( 'Entering tests_fix_Inbox_INBOX_mapping()' ) ;
9082
9083
9084 my( $h1_all, $h2_all ) ;
9085
9086 $h1_all = { 'INBOX' => q{} } ;
9087 $h2_all = { 'INBOX' => q{} } ;
9088 ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX INBOX' ) ;
9089
9090 $h1_all = { 'Inbox' => q{} } ;
9091 $h2_all = { 'Inbox' => q{} } ;
9092 ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: Inbox Inbox' ) ;
9093
9094 $h1_all = { 'INBOX' => q{} } ;
9095 $h2_all = { 'Inbox' => q{} } ;
9096 ok( q{s/^INBOX$/Inbox/x} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX Inbox' ) ;
9097
9098 $h1_all = { 'Inbox' => q{} } ;
9099 $h2_all = { 'INBOX' => q{} } ;
9100 ok( q{s/^Inbox$/INBOX/x} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: Inbox INBOX' ) ;
9101
9102 $h1_all = { 'INBOX' => q{} } ;
9103 $h2_all = { 'rrrrr' => q{} } ;
9104 ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX rrrrrr' ) ;
9105
9106 $h1_all = { 'rrrrr' => q{} } ;
9107 $h2_all = { 'Inbox' => q{} } ;
9108 ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: rrrrr Inbox' ) ;
9109
9110 note( 'Leaving tests_fix_Inbox_INBOX_mapping()' ) ;
9111 return ;
9112}
9113
9114
9115sub jux_utf8_list
9116{
9117 my @s_inp = @_ ;
9118 my $s_out = q{} ;
9119 foreach my $s ( @s_inp ) {
9120 $s_out .= jux_utf8( $s ) . "\n" ;
9121 }
9122 return( $s_out ) ;
9123}
9124
9125sub tests_jux_utf8_list
9126{
9127 note( 'Entering tests_jux_utf8_list()' ) ;
9128
9129 use utf8 ;
9130 is( q{}, jux_utf8_list( ), 'jux_utf8_list: void' ) ;
9131 is( "[]\n", jux_utf8_list( q{} ), 'jux_utf8_list: empty string' ) ;
9132 is( "[INBOX]\n", jux_utf8_list( 'INBOX' ), 'jux_utf8_list: INBOX' ) ;
9133 is( "[&ANY-] = [Ö]\n", jux_utf8_list( '&ANY-' ), 'jux_utf8_list: [&ANY-] = [Ö]' ) ;
9134
9135 note( 'Leaving tests_jux_utf8_list()' ) ;
9136 return( 0 ) ;
9137}
9138
9139# editing utf8 can be tricky without an utf8 editor
9140sub tests_jux_utf8_old
9141{
9142 note( 'Entering tests_jux_utf8_old()' ) ;
9143
9144 no utf8 ;
9145
9146 is( '[]', jux_utf8_old( q{} ), 'jux_utf8_old: void => []' ) ;
9147 is( '[INBOX]', jux_utf8_old( 'INBOX'), 'jux_utf8_old: INBOX => [INBOX]' ) ;
9148 is( '[&ZTZO9nux-] = [收件箱]', jux_utf8_old( '&ZTZO9nux-'), 'jux_utf8_old: => [&ZTZO9nux-] = [收件箱]' ) ;
9149 is( '[&ANY-] = [Ö]', jux_utf8_old( '&ANY-'), 'jux_utf8_old: &ANY- => [&ANY-] = [Ö]' ) ;
9150 # +BD8EQAQ1BDQEOwQ+BDM- SHOULD stay as is!
9151 is( '[+BD8EQAQ1BDQEOwQ+BDM-] = [предлог]', jux_utf8_old( '+BD8EQAQ1BDQEOwQ+BDM-' ), 'jux_utf8_old: => [+BD8EQAQ1BDQEOwQ+BDM-] = [предлог]' ) ;
9152 is( '[&BB8EQAQ+BDUEOgRC-] = [Проект]', jux_utf8_old( '&BB8EQAQ+BDUEOgRC-' ), 'jux_utf8_old: => [&BB8EQAQ+BDUEOgRC-] = [Проект]' ) ;
9153
9154 note( 'Leaving tests_jux_utf8_old()' ) ;
9155 return ;
9156}
9157
9158sub jux_utf8_old
9159{
9160 # juxtapose utf8 at the right if different
9161 my ( $s_utf7 ) = shift ;
9162 my ( $s_utf8 ) = imap_utf7_decode_old( $s_utf7 ) ;
9163
9164 if ( $s_utf7 eq $s_utf8 ) {
9165 #myprint( "[$s_utf7]\n" ) ;
9166 return( "[$s_utf7]" ) ;
9167 }else{
9168 #myprint( "[$s_utf7] = [$s_utf8]\n" ) ;
9169 return( "[$s_utf7] = [$s_utf8]" ) ;
9170 }
9171}
9172
9173# Copied from http://cpansearch.perl.org/src/FABPOT/Unicode-IMAPUtf7-2.01/lib/Unicode/IMAPUtf7.pm
9174# and then fixed with
9175# https://rt.cpan.org/Public/Bug/Display.html?id=11172
9176sub imap_utf7_decode_old
9177{
9178 my ( $s ) = shift ;
9179
9180 # Algorithm
9181 # On remplace , par / dans les BASE 64 (, entre & et -)
9182 # On remplace les &, non suivi d'un - par +
9183 # On remplace les &- par &
9184 $s =~ s/&([^,&\-]*),([^,\-&]*)\-/&$1\/$2\-/xg ;
9185 $s =~ s/&(?!\-)/\+/xg ;
9186 $s =~ s/&\-/&/xg ;
9187 return( Unicode::String::utf7( $s )->utf8 ) ;
9188}
9189
9190
9191
9192
9193
9194sub tests_jux_utf8
9195{
9196 note( 'Entering tests_jux_utf8()' ) ;
9197 #no utf8 ;
9198 use utf8 ;
9199
9200 #binmode STDOUT, ":encoding(UTF-8)" ;
9201 binmode STDERR, ":encoding(UTF-8)" ;
9202
9203 # This test is because the binary can fail on it, a PAR.pm issue.
9204 # The failure was with the underlying Encode::IMAPUTF7 module line 66 release 1.05
9205 # Was solved by including Encode in imapsync and using "pp -x".
9206 ok( find_encoding( "UTF-16BE"), 'jux_utf8: Encode::find_encoding: UTF-16BE' ) ;
9207
9208 #
9209 is( '[]', jux_utf8( q{} ), 'jux_utf8: void => []' ) ;
9210 is( '[INBOX]', jux_utf8( 'INBOX'), 'jux_utf8: INBOX => [INBOX]' ) ;
9211 is( '[&ANY-] = [Ö]', jux_utf8( '&ANY-'), 'jux_utf8: &ANY- => [&ANY-] = [Ö]' ) ;
9212 # +BD8EQAQ1BDQEOwQ+BDM- must stay as is
9213 is( '[+BD8EQAQ1BDQEOwQ+BDM-]', jux_utf8( '+BD8EQAQ1BDQEOwQ+BDM-' ), 'jux_utf8: => [+BD8EQAQ1BDQEOwQ+BDM-] = [+BD8EQAQ1BDQEOwQ+BDM-]' ) ;
9214 is( '[&BB8EQAQ+BDUEOgRC-] = [Проект]', jux_utf8( '&BB8EQAQ+BDUEOgRC-' ), 'jux_utf8: => [&BB8EQAQ+BDUEOgRC-] = [Проект]' ) ;
9215
9216 is( '[R&AOk-ponses 1200+1201+1202] = [Réponses 1200+1201+1202]', jux_utf8( q{R&AOk-ponses 1200+1201+1202} ), 'jux_utf8: [R&AOk-ponses 1200+1201+1202] = [Réponses 1200+1201+1202]' ) ;
9217 my $str = Encode::IMAPUTF7::encode("IMAP-UTF-7", 'Réponses 1200+1201+1202' ) ;
9218 is( '[R&AOk-ponses 1200+1201+1202] = [Réponses 1200+1201+1202]', jux_utf8( $str ), "jux_utf8: [$str] = [Réponses 1200+1201+1202]" ) ;
9219
9220 is( '[INBOX.&AOkA4ADnAPk-&-*] = [INBOX.éà çù&*]', jux_utf8( 'INBOX.&AOkA4ADnAPk-&-*' ), "jux_utf8: [INBOX.&AOkA4ADnAPk-&-*] = [INBOX.éà çù&*]" ) ;
9221
9222 is( '[&ZTZO9nux-] = [收件箱]', jux_utf8( '&ZTZO9nux-'), 'jux_utf8: => [&ZTZO9nux-] = [收件箱]' ) ;
9223 #
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009224 #
9225 is( '[!Old Emails]', jux_utf8( '!Old Emails'), 'jux_utf8: !Old Emails => [!Old Emails]' ) ;
9226 is( '[2006 Budget & Fcst]', jux_utf8( '2006 Budget & Fcst'), 'jux_utf8: 2006 Budget & Fcst => [2006 Budget & Fcst]' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009227 note( 'Leaving tests_jux_utf8()' ) ;
9228 return ;
9229}
9230
9231sub jux_utf8
9232{
9233 #use utf8 ;
9234 # juxtapose utf8 at the right if different
9235 my ( $s_utf7 ) = shift ;
9236 my ( $s_utf8 ) = imap_utf7_decode( $s_utf7 ) ;
9237
9238 if ( $s_utf7 eq $s_utf8 ) {
9239 #myprint( "[$s_utf7]\n" ) ;
9240 return( "[$s_utf7]" ) ;
9241 }else{
9242 #myprint( "[$s_utf7] = [$s_utf8]\n" ) ;
9243 return( "[$s_utf7] = [$s_utf8]" ) ;
9244 }
9245}
9246
9247sub imap_utf7_decode
9248{
9249 #use utf8 ;
9250 my ( $s ) = shift ;
9251 return( Encode::IMAPUTF7::decode("IMAP-UTF-7", $s ) ) ;
9252}
9253
9254sub imap_utf7_encode
9255{
9256 #use utf8 ;
9257 my ( $s ) = shift ;
9258 return( Encode::IMAPUTF7::encode("IMAP-UTF-7", $s ) ) ;
9259}
9260
9261
9262
9263sub imap_utf7_encode_old
9264{
9265 my ( $s ) = @_ ;
9266
9267 $s = Unicode::String::utf8( $s )->utf7 ;
9268
9269 $s =~ s/\+([^\/&\-]*)\/([^\/\-&]*)\-/\+$1,$2\-/xg ;
9270 $s =~ s/&/&\-/xg ;
9271 $s =~ s/\+([^+\-]+)?\-/&$1\-/xg ;
9272 return( $s ) ;
9273}
9274
9275
9276
9277
9278sub select_folder
9279{
9280 my ( $mysync, $imap, $folder, $hostside ) = @_ ;
9281 if ( ! $imap->select( $folder ) ) {
9282 my $error = join q{},
9283 "$hostside folder $folder: Could not select: ",
9284 $imap->LastError, "\n" ;
9285 errors_incr( $mysync, $error ) ;
9286 return( 0 ) ;
9287 }else{
9288 # ok select succeeded
9289 return( 1 ) ;
9290 }
9291}
9292
9293sub examine_folder
9294{
9295 my ( $mysync, $imap, $folder, $hostside ) = @_ ;
9296 if ( ! $imap->examine( $folder ) ) {
9297 my $error = join q{},
9298 "$hostside folder $folder: Could not examine: ",
9299 $imap->LastError, "\n" ;
9300 errors_incr( $mysync, $error ) ;
9301 return( 0 ) ;
9302 }else{
9303 # ok select succeeded
9304 return( 1 ) ;
9305 }
9306}
9307
9308
9309sub count_from_select
9310{
9311 my @lines = @ARG ;
9312 my $count ;
9313 foreach my $line ( @lines ) {
9314 #myprint( "line = [$line]\n" ) ;
9315 if ( $line =~ m/^\*\s+(\d+)\s+EXISTS/x ) {
9316 $count = $1 ;
9317 return( $count ) ;
9318 }
9319 }
9320 return( undef ) ;
9321}
9322
9323
9324
9325sub create_folder_old
9326{
9327 my $mysync = shift @ARG ;
9328 my( $imap, $h2_fold, $h1_fold ) = @ARG ;
9329
9330 myprint( "Creating (old way) folder [$h2_fold] on host2\n" ) ;
9331 if ( ( 'INBOX' eq uc $h2_fold )
9332 and ( $imap->exists( $h2_fold ) ) ) {
9333 myprint( "Folder [$h2_fold] already exists\n" ) ;
9334 return( 1 ) ;
9335 }
9336 if ( ! $mysync->{dry} ){
9337 if ( ! $imap->create( $h2_fold ) ) {
9338 my $error = join q{},
9339 "Could not create folder [$h2_fold] from [$h1_fold]: ",
9340 $imap->LastError( ), "\n" ;
9341 errors_incr( $mysync, $error ) ;
9342 # success if folder exists ("already exists" error)
9343 return( 1 ) if $imap->exists( $h2_fold ) ;
9344 # failure since create failed
9345 return( 0 ) ;
9346 }else{
9347 #create succeeded
9348 myprint( "Created ( the old way ) folder [$h2_fold] on host2\n" ) ;
9349 return( 1 ) ;
9350 }
9351 }else{
9352 # dry mode, no folder so many imap will fail, assuming failure
9353 myprint( "Created ( the old way ) folder [$h2_fold] on host2 $mysync->{dry_message}\n" ) ;
9354 return( 0 ) ;
9355 }
9356}
9357
9358
9359sub create_folder
9360{
9361 my $mysync = shift @ARG ;
9362 my( $myimap2 , $h2_fold , $h1_fold ) = @ARG ;
9363 my( @parts , $parent ) ;
9364
9365 if ( $myimap2->IsUnconnected( ) ) {
9366 myprint( "Host2: Unconnected state\n" ) ;
9367 return( 0 ) ;
9368 }
9369
9370 if ( $create_folder_old ) {
9371 return( create_folder_old( $mysync, $myimap2 , $h2_fold , $h1_fold ) ) ;
9372 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009373
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01009374 # $imap->exists() calls $imap->status() that does an IMAP STATUS folder
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009375 myprint( "Creating folder [$h2_fold] on host2\n" ) ;
9376 if ( ( 'INBOX' eq uc $h2_fold )
9377 and ( $myimap2->exists( $h2_fold ) ) ) {
9378 myprint( "Folder [$h2_fold] already exists\n" ) ;
9379 return( 1 ) ;
9380 }
9381
9382 if ( $mixfolders and $myimap2->exists( $h2_fold ) ) {
9383 myprint( "Folder [$h2_fold] already exists (--nomixfolders is not set)\n" ) ;
9384 return( 1 ) ;
9385 }
9386
9387
9388 if ( ( not $mixfolders ) and ( $myimap2->exists( $h2_fold ) ) ) {
9389 myprint( "Folder [$h2_fold] already exists and --nomixfolders is set\n" ) ;
9390 return( 0 ) ;
9391 }
9392
9393 @parts = split /\Q$mysync->{ h2_sep }\E/x, $h2_fold ;
9394 pop @parts ;
9395 $parent = join $mysync->{ h2_sep }, @parts ;
9396 $parent =~ s/^\s+|\s+$//xg ;
9397 if ( ( $parent ne q{} ) and ( ! $myimap2->exists( $parent ) ) ) {
9398 create_folder( $mysync, $myimap2 , $parent , $h1_fold ) ;
9399 }
9400
9401 if ( ! $mysync->{dry} ) {
9402 if ( ! $myimap2->create( $h2_fold ) ) {
9403 my $error = join q{},
9404 "Could not create folder [$h2_fold] from [$h1_fold]: " ,
9405 $myimap2->LastError( ), "\n" ;
9406 errors_incr( $mysync, $error ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01009407 # success if folder exists ("already exists" error) or selectable
9408 if ( $myimap2->exists( $h2_fold ) or select_folder( $mysync, $myimap2, $h2_fold, 'Host2' ) )
9409 {
9410 return( 1 ) ;
9411 }
9412 # failure since create failed + not exist + not selectable
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009413 return( 0 ) ;
9414 }else{
9415 #create succeeded
9416 myprint( "Created folder [$h2_fold] on host2\n" ) ;
9417 return( 1 ) ;
9418 }
9419 }else{
9420 # dry mode, no folder so many imap will fail, assuming failure
9421 myprint( "Created folder [$h2_fold] on host2 $mysync->{dry_message}\n" ) ;
9422 if ( ! $mysync->{ justfolders } ) {
9423 myprint( "Since --dry mode is on and folder [$h2_fold] on host2 does not exist yet, syncing messages will not be simulated.\n"
9424 . "To simulate message syncing, use --justfolders without --dry to first create the missing folders then rerun the --dry sync.\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009425 # The messages that could be transferred are counted and the number is given at the end.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009426 }
9427 return( 0 ) ;
9428 }
9429}
9430
9431
9432
9433sub tests_folder_routines
9434{
9435 note( 'Entering tests_folder_routines()' ) ;
9436
9437 ok( !is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 1' );
9438 ok( add_to_requested_folders('folder_foo'), 'add_to_requested_folders folder_foo' );
9439 ok( is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 2' );
9440 ok( !is_requested_folder('folder_NO_EXIST'), 'is_requested_folder folder_NO_EXIST' );
9441
9442 is_deeply( [ 'folder_foo' ], [ remove_from_requested_folders( 'folder_foo' ) ], 'removed folder_foo => folder_foo' ) ;
9443 ok( !is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 3' );
9444 my @f ;
9445 ok( @f = add_to_requested_folders('folder_bar', 'folder_toto'), "add result: @f" );
9446 ok( is_requested_folder('folder_bar'), 'is_requested_folder 4' );
9447 ok( is_requested_folder('folder_toto'), 'is_requested_folder 5' );
9448 ok( remove_from_requested_folders('folder_toto'), 'remove_from_requested_folders: ' );
9449 ok( !is_requested_folder('folder_toto'), 'is_requested_folder 6' );
9450
9451 is_deeply( [ 'folder_bar' ], [ remove_from_requested_folders('folder_bar') ], 'remove_from_requested_folders: empty' ) ;
9452
9453 ok( 0 == compare_lists( [ sort_requested_folders( ) ], [] ), 'sort_requested_folders: all empty' ) ;
9454 ok( add_to_requested_folders( 'A_99', 'M_55', 'Z_11' ), 'add_to_requested_folders M_55 Z_11' );
9455 ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'A_99', 'M_55', 'Z_11' ] ), 'sort_requested_folders: middle' ) ;
9456
9457
9458 @folderfirst = ( 'Z_11' ) ;
9459
9460 ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'A_99', 'M_55' ] ), 'sort_requested_folders: first+middle' ) ;
9461
9462 is_deeply( [ 'Z_11', 'A_99', 'M_55' ], [ sort_requested_folders( ) ], 'sort_requested_folders: first+middle is_deeply' ) ;
9463
9464 @folderlast = ( 'A_99' ) ;
9465 ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'M_55', 'A_99' ] ), 'sort_requested_folders: first+middle+last 1' ) ;
9466
9467 ok( add_to_requested_folders('M_55', 'M_44',), 'add_to_requested_folders M_55 M_44' ) ;
9468
9469 ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'M_44', 'M_55', 'A_99'] ), 'sort_requested_folders: first+middle+last 2' ) ;
9470
9471
9472 ok( add_to_requested_folders('A_88', 'Z_22',), 'add_to_requested_folders A_88 Z_22' ) ;
9473 @folderfirst = qw( Z_22 Z_11 ) ;
9474 @folderlast = qw( A_99 A_88 ) ;
9475 ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_22', 'Z_11', 'M_44', 'M_55', 'A_99', 'A_88' ] ), 'sort_requested_folders: first+middle+last 3' ) ;
9476 undef @folderfirst ;
9477 undef @folderlast ;
9478
9479 note( 'Leaving tests_folder_routines()' ) ;
9480 return ;
9481}
9482
9483
9484sub sort_requested_folders
9485{
9486 my @requested_folders_sorted = () ;
9487
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009488 $sync->{ debug } and myprint "folderfirst: @folderfirst\n" ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009489 my @folderfirst_requested = remove_from_requested_folders( @folderfirst ) ;
9490 #myprint "folderfirst_requested: @folderfirst_requested\n" ;
9491
9492 my @folderlast_requested = remove_from_requested_folders( @folderlast ) ;
9493
9494 my @middle = sort keys %requested_folder ;
9495
9496 @requested_folders_sorted = ( @folderfirst_requested, @middle, @folderlast_requested ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009497 $sync->{ debug } and myprint "requested_folders_sorted: @requested_folders_sorted\n" ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009498 add_to_requested_folders( @requested_folders_sorted ) ;
9499
9500 return( @requested_folders_sorted ) ;
9501}
9502
9503sub is_requested_folder
9504{
9505 my ( $folder ) = @_;
9506
9507 return( defined $requested_folder{ $folder } ) ;
9508}
9509
9510
9511sub add_to_requested_folders
9512{
9513 my @wanted_folders = @_ ;
9514
9515 foreach my $folder ( @wanted_folders ) {
9516 ++$requested_folder{ $folder } ;
9517 }
9518 return( keys %requested_folder ) ;
9519}
9520
9521sub tests_remove_from_requested_folders
9522{
9523 note( 'Entering tests_remove_from_requested_folders()' ) ;
9524
9525 is( undef, undef, 'remove_from_requested_folders: undef is undef' ) ;
9526 is_deeply( [], [ remove_from_requested_folders( ) ], 'remove_from_requested_folders: no args' ) ;
9527 %requested_folder = (
9528 'F1' => 1,
9529 ) ;
9530 is_deeply( [], [ remove_from_requested_folders( ) ], 'remove_from_requested_folders: remove nothing among F1 => nothing' ) ;
9531 is_deeply( [], [ remove_from_requested_folders( 'Fno' ) ], 'remove_from_requested_folders: remove Fno among F1 => nothing' ) ;
9532 is_deeply( [ 'F1' ], [ remove_from_requested_folders( 'F1' ) ], 'remove_from_requested_folders: remove F1 among F1 => F1' ) ;
9533 is_deeply( { }, { %requested_folder }, 'remove_from_requested_folders: remove F1 among F1 => %requested_folder emptied' ) ;
9534
9535 %requested_folder = (
9536 'F1' => 1,
9537 'F2' => 1,
9538 ) ;
9539 is_deeply( [], [ remove_from_requested_folders( ) ], 'remove_from_requested_folders: remove nothing among F1 F2 => nothing' ) ;
9540 is_deeply( [], [ remove_from_requested_folders( 'Fno' ) ], 'remove_from_requested_folders: remove Fno among F1 F2 => nothing' ) ;
9541 is_deeply( [ 'F1' ], [ remove_from_requested_folders( 'F1' ) ], 'remove_from_requested_folders: remove F1 among F1 F2 => F1' ) ;
9542 is_deeply( { 'F2' => 1 }, { %requested_folder }, 'remove_from_requested_folders: remove F1 among F1 F2 => %requested_folder F2' ) ;
9543
9544 is_deeply( [], [ remove_from_requested_folders( 'F1' ) ], 'remove_from_requested_folders: remove F1 among F2 => nothing' ) ;
9545 is_deeply( [ 'F2' ], [ remove_from_requested_folders( 'F1', 'F2' ) ], 'remove_from_requested_folders: remove F1 F2 among F2 => F2' ) ;
9546 is_deeply( {}, { %requested_folder }, 'remove_from_requested_folders: remove F1 among F1 F2 => %requested_folder F2' ) ;
9547
9548 %requested_folder = (
9549 'F1' => 1,
9550 'F2' => 1,
9551 'F3' => 1,
9552 ) ;
9553 is_deeply( [ 'F1', 'F2' ], [ remove_from_requested_folders( 'F1', 'F2' ) ], 'remove_from_requested_folders: remove F1 F2 among F1 F2 F3 => F1 F2' ) ;
9554 is_deeply( { 'F3' => 1 }, { %requested_folder }, 'remove_from_requested_folders: remove F1 F2 among F1 F2 F3 => %requested_folder F3' ) ;
9555
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009556 undef %requested_folder ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009557
9558 note( 'Leaving tests_remove_from_requested_folders()' ) ;
9559 return ;
9560}
9561
9562
9563sub remove_from_requested_folders
9564{
9565 my @unwanted_folders = @_ ;
9566
9567 my @removed_folders = () ;
9568 foreach my $folder ( @unwanted_folders ) {
9569 if ( exists $requested_folder{ $folder } )
9570 {
9571 delete $requested_folder{ $folder } ;
9572 push @removed_folders, $folder ;
9573 }
9574 }
9575 return( @removed_folders ) ;
9576}
9577
9578sub compare_lists
9579{
9580 my ($list_1_ref, $list_2_ref) = @_;
9581
9582 return($MINUS_ONE) if ((not defined $list_1_ref) and defined $list_2_ref);
9583 return(0) if ((not defined $list_1_ref) and not defined $list_2_ref); # end if no list
9584 return(1) if (not defined $list_2_ref); # end if only one list
9585
9586 if (not ref $list_1_ref ) {$list_1_ref = [$list_1_ref]};
9587 if (not ref $list_2_ref ) {$list_2_ref = [$list_2_ref]};
9588
9589
9590 my $last_used_indice = $MINUS_ONE;
9591
9592
9593 ELEMENT:
9594 foreach my $indice ( 0 .. $#{ $list_1_ref } ) {
9595 $last_used_indice = $indice ;
9596
9597 # End of list_2
9598 return 1 if ($indice > $#{ $list_2_ref } ) ;
9599
9600 my $element_list_1 = $list_1_ref->[$indice] ;
9601 my $element_list_2 = $list_2_ref->[$indice] ;
9602 my $balance = $element_list_1 cmp $element_list_2 ;
9603 next ELEMENT if ($balance == 0) ;
9604 return $balance ;
9605 }
9606 # each element equal until last indice of list_1
9607 return $MINUS_ONE if ($last_used_indice < $#{ $list_2_ref } ) ;
9608
9609 # same size, each element equal
9610 return 0 ;
9611}
9612
9613sub tests_compare_lists
9614{
9615 note( 'Entering tests_compare_lists()' ) ;
9616
9617 my $empty_list_ref = [];
9618
9619 ok( 0 == compare_lists() , 'compare_lists, no args');
9620 ok( 0 == compare_lists(undef) , 'compare_lists, undef = nothing');
9621 ok( 0 == compare_lists(undef, undef) , 'compare_lists, undef = undef');
9622 ok($MINUS_ONE == compare_lists(undef , []) , 'compare_lists, undef < []');
9623 ok($MINUS_ONE == compare_lists(undef , [1]) , 'compare_lists, undef < [1]');
9624 ok($MINUS_ONE == compare_lists(undef , [0]) , 'compare_lists, undef < [0]');
9625 ok(+1 == compare_lists([]) , 'compare_lists, [] > nothing');
9626 ok(+1 == compare_lists([], undef) , 'compare_lists, [] > undef');
9627 ok( 0 == compare_lists([] , []) , 'compare_lists, [] = []');
9628
9629 ok($MINUS_ONE == compare_lists([] , [1]) , 'compare_lists, [] < [1]');
9630 ok(+1 == compare_lists([1] , []) , 'compare_lists, [1] > []');
9631
9632
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009633 ok( 0 == compare_lists( [1], 1 ) , 'compare_lists, [1] = 1 ') ;
9634 ok( 0 == compare_lists( 1 , [1] ) , 'compare_lists, 1 = [1]') ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009635 ok( 0 == compare_lists( 1 , 1 ) , 'compare_lists, 1 = 1 ') ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009636 ok( $MINUS_ONE == compare_lists( 0 , 1 ) , 'compare_lists, 0 < 1 ') ;
9637 ok( $MINUS_ONE == compare_lists( $MINUS_ONE , 0 ) , 'compare_lists, -1 < 0 ') ;
9638 ok( $MINUS_ONE == compare_lists( 1 , 2 ) , 'compare_lists, 1 < 2 ') ;
9639 ok( +1 == compare_lists( 2 , 1 ) , 'compare_lists, 2 > 1 ') ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009640
9641
9642 ok( 0 == compare_lists([1,2], [1,2]) , 'compare_lists, [1,2] = [1,2]' ) ;
9643 ok($MINUS_ONE == compare_lists([1], [1,2]) , 'compare_lists, [1] < [1,2]' ) ;
9644 ok(+1 == compare_lists([2], [1,2]) , 'compare_lists, [2] > [1,2]' ) ;
9645 ok($MINUS_ONE == compare_lists([1], [1,1]) , 'compare_lists, [1] < [1,1]' ) ;
9646 ok(+1 == compare_lists([1, 1], [1]) , 'compare_lists, [1, 1] > [1]' ) ;
9647 ok( 0 == compare_lists([1 .. $NUMBER_20_000] , [1 .. $NUMBER_20_000])
9648 , 'compare_lists, [1..20_000] = [1..20_000]' ) ;
9649 ok($MINUS_ONE == compare_lists([1], [2]) , 'compare_lists, [1] < [2]') ;
9650 ok( 0 == compare_lists([2], [2]) , 'compare_lists, [0] = [2]') ;
9651 ok(+1 == compare_lists([2], [1]) , 'compare_lists, [2] > [1]') ;
9652
9653 ok($MINUS_ONE == compare_lists(['a'], ['b']) , 'compare_lists, ["a"] < ["b"]') ;
9654 ok( 0 == compare_lists(['a'], ['a']) , 'compare_lists, ["a"] = ["a"]') ;
9655 ok( 0 == compare_lists(['ab'], ['ab']) , 'compare_lists, ["ab"] = ["ab"]') ;
9656 ok(+1 == compare_lists(['b'], ['a']) , 'compare_lists, ["b"] > ["a"]') ;
9657 ok($MINUS_ONE == compare_lists(['a'], ['aa']) , 'compare_lists, ["a"] < ["aa"]') ;
9658 ok($MINUS_ONE == compare_lists(['a'], ['a', 'a']), 'compare_lists, ["a"] < ["a", "a"]') ;
9659 ok( 0 == compare_lists([split q{ }, 'a b' ], ['a', 'b']), 'compare_lists, split') ;
9660 ok( 0 == compare_lists([sort split q{ }, 'b a' ], ['a', 'b']), 'compare_lists, sort split') ;
9661
9662 note( 'Leaving tests_compare_lists()' ) ;
9663 return ;
9664}
9665
9666
9667sub guess_prefix
9668{
9669 my @foldernames = @_ ;
9670
9671 my $prefix_guessed = q{} ;
9672 foreach my $folder ( @foldernames ) {
9673 next if ( $folder =~ m{^INBOX$}xi ) ; # no guessing from INBOX
9674 if ( $folder !~ m{^INBOX}xi ) {
9675 $prefix_guessed = q{} ; # prefix empty guessed
9676 last ;
9677 }
9678 if ( $folder =~ m{^(INBOX(?:\.|\/))}xi ) {
9679 $prefix_guessed = $1 ; # prefix Inbox/ or INBOX. guessed
9680 }
9681 }
9682 return( $prefix_guessed ) ;
9683}
9684
9685sub tests_guess_prefix
9686{
9687 note( 'Entering tests_guess_prefix()' ) ;
9688
9689 is( guess_prefix( ), q{}, 'guess_prefix: no args => empty string' ) ;
9690 is( q{} , guess_prefix( 'INBOX' ), 'guess_prefix: INBOX alone' ) ;
9691 is( q{} , guess_prefix( 'Inbox' ), 'guess_prefix: Inbox alone' ) ;
9692 is( q{} , guess_prefix( 'INBOX' ), 'guess_prefix: INBOX alone' ) ;
9693 is( 'INBOX/' , guess_prefix( 'INBOX', 'INBOX/Junk' ), 'guess_prefix: INBOX INBOX/Junk' ) ;
9694 is( 'INBOX.' , guess_prefix( 'INBOX', 'INBOX.Junk' ), 'guess_prefix: INBOX INBOX.Junk' ) ;
9695 is( 'Inbox/' , guess_prefix( 'Inbox', 'Inbox/Junk' ), 'guess_prefix: Inbox Inbox/Junk' ) ;
9696 is( 'Inbox.' , guess_prefix( 'Inbox', 'Inbox.Junk' ), 'guess_prefix: Inbox Inbox.Junk' ) ;
9697 is( 'INBOX/' , guess_prefix( 'INBOX', 'INBOX/Junk', 'INBOX/rrr' ), 'guess_prefix: INBOX INBOX/Junk INBOX/rrr' ) ;
9698 is( q{} , guess_prefix( 'INBOX', 'INBOX/Junk', 'INBOX/rrr', 'zzz' ), 'guess_prefix: INBOX INBOX/Junk INBOX/rrr zzz' ) ;
9699 is( q{} , guess_prefix( 'INBOX', 'Junk' ), 'guess_prefix: INBOX Junk' ) ;
9700 is( q{} , guess_prefix( 'INBOX', 'Junk' ), 'guess_prefix: INBOX Junk' ) ;
9701
9702 note( 'Leaving tests_guess_prefix()' ) ;
9703 return ;
9704}
9705
9706sub get_prefix
9707{
9708 my( $imap, $prefix_in, $prefix_opt, $Side, $folders_ref ) = @_ ;
9709 my( $prefix_out, $prefix_guessed ) ;
9710
9711 ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "$Side: Getting prefix\n" ) ;
9712 $prefix_guessed = guess_prefix( @{ $folders_ref } ) ;
9713 myprint( "$Side: guessing prefix from folder listing: [$prefix_guessed]\n" ) ;
9714 ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "$Side: Calling namespace capability\n" ) ;
9715 if ( $imap->has_capability( 'namespace' ) ) {
9716 my $r_namespace = $imap->namespace( ) ;
9717 $prefix_out = $r_namespace->[0][0][0] ;
9718 myprint( "$Side: prefix given by NAMESPACE: [$prefix_out]\n" ) ;
9719 if ( defined $prefix_in ) {
9720 myprint( "$Side: but using [$prefix_in] given by $prefix_opt\n" ) ;
9721 $prefix_out = $prefix_in ;
9722 return( $prefix_out ) ;
9723 }else{
9724 # all good
9725 return( $prefix_out ) ;
9726 }
9727 }
9728 else{
9729 if ( defined $prefix_in ) {
9730 myprint( "$Side: using [$prefix_in] given by $prefix_opt\n" ) ;
9731 $prefix_out = $prefix_in ;
9732 return( $prefix_out ) ;
9733 }else{
9734 myprint(
9735 "$Side: No NAMESPACE capability so using guessed prefix [$prefix_guessed]\n",
9736 help_to_guess_prefix( $imap, $prefix_opt ) ) ;
9737 return( $prefix_guessed ) ;
9738 }
9739 }
9740 return ;
9741}
9742
9743
9744sub guess_separator
9745{
9746 my @foldernames = @_ ;
9747
9748 #return( undef ) unless ( @foldernames ) ;
9749
9750 my $sep_guessed ;
9751 my %counter ;
9752 foreach my $folder ( @foldernames ) {
9753 $counter{'/'}++ while ( $folder =~ m{/}xg ) ; # count /
9754 $counter{'.'}++ while ( $folder =~ m{\.}xg ) ; # count .
9755 $counter{'\\\\'}++ while ( $folder =~ m{(\\){2}}xg ) ; # count \\
9756 $counter{'\\'}++ while ( $folder =~ m{[^\\](\\){1}(?=[^\\])}xg ) ; # count \
9757 }
9758 my @race_sorted = sort { $counter{ $b } <=> $counter{ $a } } keys %counter ;
9759 $sync->{ debug } and myprint( "@foldernames\n@race_sorted\n", %counter, "\n" ) ;
9760 $sep_guessed = shift @race_sorted || $LAST_RESSORT_SEPARATOR ; # / when nothing found.
9761 return( $sep_guessed ) ;
9762}
9763
9764sub tests_guess_separator
9765{
9766 note( 'Entering tests_guess_separator()' ) ;
9767
9768 ok( '/' eq guess_separator( ), 'guess_separator: no args' ) ;
9769 ok( '/' eq guess_separator( 'abcd' ), 'guess_separator: abcd' ) ;
9770 ok( '/' eq guess_separator( 'a/b/c.d' ), 'guess_separator: a/b/c.d' ) ;
9771 ok( '.' eq guess_separator( 'a.b/c.d' ), 'guess_separator: a.b/c.d' ) ;
9772 ok( '\\\\' eq guess_separator( 'a\\\\b\\\\c.c\\\\d/e/f' ), 'guess_separator: a\\\\b\\\\c.c\\\\d/e/f' ) ;
9773 ok( '\\' eq guess_separator( 'a\\b\\c.c\\d/e/f' ), 'guess_separator: a\\b\\c.c\\d/e/f' ) ;
9774 ok( '\\' eq guess_separator( 'a\\b' ), 'guess_separator: a\\b' ) ;
9775 ok( '\\' eq guess_separator( 'a\\b\\c' ), 'guess_separator: a\\b\\c' ) ;
9776
9777 note( 'Leaving tests_guess_separator()' ) ;
9778 return ;
9779}
9780
9781sub get_separator
9782{
9783 my( $imap, $sep_in, $sep_opt, $Side, $folders_ref ) = @_ ;
9784 my( $sep_out, $sep_guessed ) ;
9785
9786 ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "$Side: Getting separator\n" ) ;
9787 $sep_guessed = guess_separator( @{ $folders_ref } ) ;
9788 myprint( "$Side: guessing separator from folder listing: [$sep_guessed]\n" ) ;
9789
9790 ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "$Side: calling namespace capability\n" ) ;
9791 if ( $imap->has_capability( 'namespace' ) )
9792 {
9793 $sep_out = $imap->separator( ) ;
9794 if ( defined $sep_out ) {
9795 myprint( "$Side: separator given by NAMESPACE: [$sep_out]\n" ) ;
9796 if ( defined $sep_in ) {
9797 myprint( "$Side: but using [$sep_in] given by $sep_opt\n" ) ;
9798 $sep_out = $sep_in ;
9799 return( $sep_out ) ;
9800 }else{
9801 return( $sep_out ) ;
9802 }
9803 }else{
9804 if ( defined $sep_in ) {
9805 myprint( "$Side: NAMESPACE request failed but using [$sep_in] given by $sep_opt\n" ) ;
9806 $sep_out = $sep_in ;
9807 return( $sep_out ) ;
9808 }else{
9809 myprint(
9810 "$Side: NAMESPACE request failed so using guessed separator [$sep_guessed]\n",
9811 help_to_guess_sep( $imap, $sep_opt ) ) ;
9812 return( $sep_guessed ) ;
9813 }
9814 }
9815 }
9816 else
9817 {
9818 if ( defined $sep_in ) {
9819 myprint( "$Side: No NAMESPACE capability but using [$sep_in] given by $sep_opt\n" ) ;
9820 $sep_out = $sep_in ;
9821 return( $sep_out ) ;
9822 }else{
9823 myprint(
9824 "$Side: No NAMESPACE capability, so using guessed separator [$sep_guessed]\n",
9825 help_to_guess_sep( $imap, $sep_opt ) ) ;
9826 return( $sep_guessed ) ;
9827 }
9828 }
9829 return ;
9830}
9831
9832sub help_to_guess_sep
9833{
9834 my( $imap, $sep_opt ) = @_ ;
9835
9836 my $help_to_guess_sep = "You can set the separator character with the $sep_opt option,\n"
9837 . "the complete listing of folders may help you to find it\n"
9838 . folders_list_to_help( $imap ) ;
9839
9840 return( $help_to_guess_sep ) ;
9841}
9842
9843sub help_to_guess_prefix
9844{
9845 my( $imap, $prefix_opt ) = @_ ;
9846
9847 my $help_to_guess_prefix = "You can set the prefix namespace with the $prefix_opt option,\n"
9848 . "the folowing listing of folders may help you to find it:\n"
9849 . folders_list_to_help( $imap ) ;
9850
9851 return( $help_to_guess_prefix ) ;
9852}
9853
9854
9855sub folders_list_to_help
9856{
9857 my( $imap ) = shift ;
9858
9859 my @folders = $imap->folders ;
9860 my $listing = join q{}, map { "[$_]\n" } @folders ;
9861 return( $listing ) ;
9862}
9863
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009864# Globals are $sync @h1_folders_all @h2_folders_all $prefix1 $prefix2
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009865sub private_folders_separators_and_prefixes
9866{
9867# what are the private folders separators and prefixes for each server ?
9868
9869 ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "Getting separators\n" ) ;
9870 $sync->{ h1_sep } = get_separator( $sync->{imap1}, $sync->{ sep1 }, '--sep1', 'Host1', \@h1_folders_all ) ;
9871 $sync->{ h2_sep } = get_separator( $sync->{imap2}, $sync->{ sep2 }, '--sep2', 'Host2', \@h2_folders_all ) ;
9872
9873
9874 $sync->{ h1_prefix } = get_prefix( $sync->{imap1}, $prefix1, '--prefix1', 'Host1', \@h1_folders_all ) ;
9875 $sync->{ h2_prefix } = get_prefix( $sync->{imap2}, $prefix2, '--prefix2', 'Host2', \@h2_folders_all ) ;
9876
9877 myprint( "Host1: separator and prefix: [$sync->{ h1_sep }][$sync->{ h1_prefix }]\n" ) ;
9878 myprint( "Host2: separator and prefix: [$sync->{ h2_sep }][$sync->{ h2_prefix }]\n" ) ;
9879 return ;
9880}
9881
9882
9883sub subfolder1
9884{
9885 my $mysync = shift ;
9886 my $subfolder1 = sanitize_subfolder( $mysync->{ subfolder1 } ) ;
9887
9888 if ( $subfolder1 )
9889 {
9890 # turns off automap
9891 myprint( "Turning off automapping folders because of --subfolder1\n" ) ;
9892 $mysync->{ automap } = undef ;
9893 myprint( "Sanitizing subfolder1: [$mysync->{ subfolder1 }] => [$subfolder1]\n" ) ;
9894 $mysync->{ subfolder1 } = $subfolder1 ;
9895 if ( ! add_subfolder1_to_folderrec( $mysync ) )
9896 {
9897 $mysync->{nb_errors}++ ;
9898 exit_clean( $mysync, $EXIT_SUBFOLDER1_NO_EXISTS, "subfolder1 $subfolder1 does not exist\n" ) ;
9899 }
9900 }
9901 else
9902 {
9903 $mysync->{ subfolder1 } = undef ;
9904 }
9905
9906 return ;
9907}
9908
9909sub subfolder2
9910{
9911 my $mysync = shift ;
9912 my $subfolder2 = sanitize_subfolder( $mysync->{ subfolder2 } ) ;
9913 if ( $subfolder2 )
9914 {
9915 # turns off automap
9916 myprint( "Turning off automapping folders because of --subfolder2\n" ) ;
9917 $mysync->{ automap } = undef ;
9918 myprint( "Sanitizing subfolder2: [$mysync->{ subfolder2 }] => [$subfolder2]\n" ) ;
9919 $mysync->{ subfolder2 } = $subfolder2 ;
9920 set_regextrans2_for_subfolder2( $mysync ) ;
9921 }
9922 else
9923 {
9924 $mysync->{ subfolder2 } = undef ;
9925 }
9926
9927 return ;
9928}
9929
9930sub tests_sanitize_subfolder
9931{
9932 note( 'Entering tests_sanitize_subfolder()' ) ;
9933
9934 is( undef, sanitize_subfolder( ), 'sanitize_subfolder: no args => undef' ) ;
9935 is( undef, sanitize_subfolder( q{} ), 'sanitize_subfolder: empty => undef' ) ;
9936 is( undef, sanitize_subfolder( ' ' ), 'sanitize_subfolder: blank => undef' ) ;
9937 is( undef, sanitize_subfolder( ' ' ), 'sanitize_subfolder: blanks => undef' ) ;
9938 is( 'abcd', sanitize_subfolder( 'abcd' ), 'sanitize_subfolder: abcd => abcd' ) ;
9939 is( 'ab cd', sanitize_subfolder( ' ab cd ' ), 'sanitize_subfolder: " ab cd " => "ab cd"' ) ;
9940 is( 'abcd', sanitize_subfolder( q{a&~b#\\c[]=d;} ), 'sanitize_subfolder: "a&~b#\\c[]=d;" => "abcd"' ) ;
9941 is( 'aA.b-_ 8c/dD', sanitize_subfolder( 'aA.b-_ 8c/dD' ), 'sanitize_subfolder: aA.b-_ 8c/dD => aA.b-_ 8c/dD' ) ;
9942 note( 'Leaving tests_sanitize_subfolder()' ) ;
9943 return ;
9944}
9945
9946
9947sub sanitize_subfolder
9948{
9949 my $subfolder = shift ;
9950
9951 if ( ! $subfolder )
9952 {
9953 return ;
9954 }
9955 # Remove edging blanks
9956 $subfolder =~ s,^ +| +$,,g ;
9957 # Keep only abcd...ABCD...0123... and -_./
9958 $subfolder =~ tr,-_a-zA-Z0-9./ ,,cd ;
9959
9960 # A blank subfolder is not a subfolder
9961 if ( ! $subfolder )
9962 {
9963 return ;
9964 }
9965 else
9966 {
9967 return $subfolder ;
9968 }
9969}
9970
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009971sub tests_sanitize_host
9972{
9973 note( 'Entering tests_sanitize_host()' ) ;
9974
9975 is( undef, sanitize_host( ), 'sanitize_host: no args => undef' ) ;
9976 is( '', sanitize_host( '' ), 'sanitize_host: empty => empty' ) ;
9977 is( 'imap.example.org', sanitize_host( 'imap.example.org' ), 'sanitize_host: imap.example.org => imap.example.org' ) ;
9978 is( 'imap.example.org', sanitize_host( ' imap.example.org' ), 'sanitize_host: imap.example.org 1 => imap.example.org' ) ;
9979 is( 'imap.example.org', sanitize_host( 'imap.example.org ' ), 'sanitize_host: imap.example.org 2 => imap.example.org' ) ;
9980 is( 'imap.example.org', sanitize_host( 'imap.exam ple.org' ), 'sanitize_host: imap.example.org 3 => imap.example.org' ) ;
9981 is( 'imap.example.org', sanitize_host( ' imap.exam ple.org ' ), 'sanitize_host: imap.example.org 4 => imap.example.org' ) ;
9982 is( 'imap.example.org', sanitize_host( 'imap.exa/mple.org/' ), 'sanitize_host: imap.example.org/ => imap.example.org' ) ;
9983
9984 note( 'Leaving tests_sanitize_host()' ) ;
9985 return ;
9986}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009987
9988
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009989sub sanitize_host
9990{
9991 my $host = shift ;
9992 if ( ! defined $host ) { return ; }
9993
9994 $host =~ tr{ /}{}d ;
9995 return $host ;
9996}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009997
9998
9999sub tests_add_subfolder1_to_folderrec
10000{
10001 note( 'Entering tests_add_subfolder1_to_folderrec()' ) ;
10002
10003 is( undef, add_subfolder1_to_folderrec( ), 'add_subfolder1_to_folderrec: undef => undef' ) ;
10004 is_deeply( [], [ add_subfolder1_to_folderrec( ) ], 'add_subfolder1_to_folderrec: no args => empty array' ) ;
10005 @folderrec = () ;
10006 my $mysync = {} ;
10007 is_deeply( [ ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: empty => empty array' ) ;
10008 is_deeply( [ ], [ @folderrec ], 'add_subfolder1_to_folderrec: empty => empty folderrec' ) ;
10009 $mysync->{ subfolder1 } = 'SUBI' ;
10010 $h1_folders_all{ 'SUBI' } = 1 ;
10011 $mysync->{ h1_prefix } = 'INBOX/' ;
10012 is_deeply( [ 'SUBI' ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: SUBI => SUBI' ) ;
10013 is_deeply( [ 'SUBI' ], [ @folderrec ], 'add_subfolder1_to_folderrec: SUBI => folderrec SUBI ' ) ;
10014
10015 @folderrec = () ;
10016 $mysync->{ subfolder1 } = 'SUBO' ;
10017 is_deeply( [ ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: SUBO no exists => empty array' ) ;
10018 is_deeply( [ ], [ @folderrec ], 'add_subfolder1_to_folderrec: SUBO no exists => empty folderrec' ) ;
10019 $h1_folders_all{ 'INBOX/SUBO' } = 1 ;
10020 is_deeply( [ 'INBOX/SUBO' ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: SUBO + INBOX/SUBO exists => INBOX/SUBO' ) ;
10021 is_deeply( [ 'INBOX/SUBO' ], [ @folderrec ], 'add_subfolder1_to_folderrec: SUBO + INBOX/SUBO exists => INBOX/SUBO folderrec' ) ;
10022
10023 note( 'Leaving tests_add_subfolder1_to_folderrec()' ) ;
10024 return ;
10025}
10026
10027
10028sub add_subfolder1_to_folderrec
10029{
10030 my $mysync = shift ;
10031 if ( ! $mysync || ! $mysync->{ subfolder1 } )
10032 {
10033 return ;
10034 }
10035
10036 my $subfolder1 = $mysync->{ subfolder1 } ;
10037 my $subfolder1_extended = $mysync->{ h1_prefix } . $subfolder1 ;
10038
10039 if ( exists $h1_folders_all{ $subfolder1 } )
10040 {
10041 myprint( qq{Acting like --folderrec "$subfolder1"\n} ) ;
10042 push @folderrec, $subfolder1 ;
10043 }
10044 elsif ( exists $h1_folders_all{ $subfolder1_extended } )
10045 {
10046 myprint( qq{Acting like --folderrec "$subfolder1_extended"\n} ) ;
10047 push @folderrec, $subfolder1_extended ;
10048 }
10049 else
10050 {
10051 myprint( qq{Nor folder "$subfolder1" nor "$subfolder1_extended" exists on host1\n} ) ;
10052 }
10053 return @folderrec ;
10054}
10055
10056sub set_regextrans2_for_subfolder2
10057{
10058 my $mysync = shift ;
10059
10060
10061 unshift @{ $mysync->{ regextrans2 } },
10062 q(s,^$mysync->{ h2_prefix }(.*),$mysync->{ h2_prefix }$mysync->{ subfolder2 }$mysync->{ h2_sep }$1,),
10063 q(s,^INBOX$,$mysync->{ h2_prefix }$mysync->{ subfolder2 }$mysync->{ h2_sep }INBOX,),
10064 q(s,^($mysync->{ h2_prefix }){2},$mysync->{ h2_prefix },);
10065
10066 #myprint( "@{ $mysync->{ regextrans2 } }\n" ) ;
10067 return ;
10068}
10069
10070
10071
10072# Looks like no globals here
10073
10074sub tests_imap2_folder_name
10075{
10076 note( 'Entering tests_imap2_folder_name()' ) ;
10077
10078 my $mysync = {} ;
10079 $mysync->{ h1_prefix } = q{} ;
10080 $mysync->{ h2_prefix } = q{} ;
10081 $mysync->{ h1_sep } = '/';
10082 $mysync->{ h2_sep } = '.';
10083
10084 $mysync->{ debug } and myprint( <<"EOS"
10085prefix1: [$mysync->{ h1_prefix }]
10086prefix2: [$mysync->{ h2_prefix }]
10087sep1: [$sync->{ h1_sep }]
10088sep2: [$sync->{ h2_sep }]
10089EOS
10090) ;
10091
10092 $mysync->{ fixslash2 } = 0 ;
10093 is( q{INBOX}, imap2_folder_name( $mysync, q{} ), 'imap2_folder_name: empty string' ) ;
10094 is( 'blabla', imap2_folder_name( $mysync, 'blabla' ), 'imap2_folder_name: blabla' ) ;
10095 is('spam.spam', imap2_folder_name( $mysync, 'spam/spam' ), 'imap2_folder_name: spam/spam' ) ;
10096
10097 is( 'spam/spam', imap2_folder_name( $mysync, 'spam.spam' ), 'imap2_folder_name: spam.spam') ;
10098 is( 'spam.spam/spam', imap2_folder_name( $mysync, 'spam/spam.spam' ), 'imap2_folder_name: spam/spam.spam' ) ;
10099 is( 's pam.spam/sp am', imap2_folder_name( $mysync, 's pam/spam.sp am' ), 'imap2_folder_name: s pam/spam.sp am' ) ;
10100
10101 $mysync->{f1f2h}{ 'auto' } = 'moto' ;
10102 is( 'moto', imap2_folder_name( $mysync, 'auto' ), 'imap2_folder_name: auto' ) ;
10103 $mysync->{f1f2h}{ 'auto/auto' } = 'moto x 2' ;
10104 is( 'moto x 2', imap2_folder_name( $mysync, 'auto/auto' ), 'imap2_folder_name: auto/auto' ) ;
10105
10106 @{ $mysync->{ regextrans2 } } = ( 's,/,X,g' ) ;
10107 is( q{INBOX}, imap2_folder_name( $mysync, q{} ), 'imap2_folder_name: empty string [s,/,X,g]' ) ;
10108 is( 'blabla', imap2_folder_name( $mysync, 'blabla' ), 'imap2_folder_name: blabla [s,/,X,g]' ) ;
10109 is('spam.spam', imap2_folder_name( $mysync, 'spam/spam'), 'imap2_folder_name: spam/spam [s,/,X,g]');
10110 is('spamXspam', imap2_folder_name( $mysync, 'spam.spam'), 'imap2_folder_name: spam.spam [s,/,X,g]');
10111 is('spam.spamXspam', imap2_folder_name( $mysync, 'spam/spam.spam'), 'imap2_folder_name: spam/spam.spam [s,/,X,g]');
10112
10113 @{ $mysync->{ regextrans2 } } = ( 's, ,_,g' ) ;
10114 is('blabla', imap2_folder_name( $mysync, 'blabla'), 'imap2_folder_name: blabla [s, ,_,g]');
10115 is('bla_bla', imap2_folder_name( $mysync, 'bla bla'), 'imap2_folder_name: blabla [s, ,_,g]');
10116
10117 @{ $mysync->{ regextrans2 } } = ( q{s,(.*),\U$1,} ) ;
10118 is( 'BLABLA', imap2_folder_name( $mysync, 'blabla' ), q{imap2_folder_name: blabla [s,\U(.*)\E,$1,]} ) ;
10119
10120 $mysync->{ fixslash2 } = 1 ;
10121 @{ $mysync->{ regextrans2 } } = ( ) ;
10122 is(q{INBOX}, imap2_folder_name( $mysync, q{}), 'imap2_folder_name: empty string');
10123 is('blabla', imap2_folder_name( $mysync, 'blabla'), 'imap2_folder_name: blabla');
10124 is('spam.spam', imap2_folder_name( $mysync, 'spam/spam'), 'imap2_folder_name: spam/spam -> spam.spam');
10125 is('spam_spam', imap2_folder_name( $mysync, 'spam.spam'), 'imap2_folder_name: spam.spam -> spam_spam');
10126 is('spam.spam_spam', imap2_folder_name( $mysync, 'spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam_spam');
10127 is('s pam.spam_spa m', imap2_folder_name( $mysync, 's pam/spam.spa m'), 'imap2_folder_name: s pam/spam.spa m -> s pam.spam_spa m');
10128
10129 $mysync->{ h1_sep } = '.';
10130 $mysync->{ h2_sep } = '/';
10131 is( q{INBOX}, imap2_folder_name( $mysync, q{}), 'imap2_folder_name: empty string');
10132 is('blabla', imap2_folder_name( $mysync, 'blabla'), 'imap2_folder_name: blabla');
10133 is('spam.spam', imap2_folder_name( $mysync, 'spam/spam'), 'imap2_folder_name: spam/spam -> spam.spam');
10134 is('spam/spam', imap2_folder_name( $mysync, 'spam.spam'), 'imap2_folder_name: spam.spam -> spam/spam');
10135 is('spam.spam/spam', imap2_folder_name( $mysync, 'spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam');
10136
10137
10138
10139 $mysync->{ fixslash2 } = 0 ;
10140 $mysync->{ h1_prefix } = q{ };
10141
10142 is( 'spam.spam/spam', imap2_folder_name( $mysync, 'spam/spam.spam' ), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam' ) ;
10143 is( 'spam.spam/spam', imap2_folder_name( $mysync, ' spam/spam.spam' ), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam' ) ;
10144
10145 $mysync->{ h1_sep } = '.' ;
10146 $mysync->{ h2_sep } = '/' ;
10147 $mysync->{ h1_prefix } = 'INBOX.' ;
10148 $mysync->{ h2_prefix } = q{} ;
10149 @{ $mysync->{ regextrans2 } } = ( q{s,(.*),\U$1,} ) ;
10150 is( 'BLABLA', imap2_folder_name( $mysync, 'blabla' ), 'imap2_folder_name: blabla' ) ;
10151 is( 'TEST/TEST/TEST/TEST', imap2_folder_name( $mysync, 'INBOX.TEST.test.Test.tesT' ), 'imap2_folder_name: INBOX.TEST.test.Test.tesT' ) ;
10152 @{ $mysync->{ regextrans2 } } = ( q{s,(.*),\L$1,} ) ;
10153 is( 'test/test/test/test', imap2_folder_name( $mysync, 'INBOX.TEST.test.Test.tesT' ), 'imap2_folder_name: INBOX.TEST.test.Test.tesT' ) ;
10154
10155 # INBOX
10156 $mysync = {} ;
10157 $mysync->{ h1_prefix } = q{Pf1.} ;
10158 $mysync->{ h2_prefix } = q{Pf2/} ;
10159 $mysync->{ h1_sep } = '.';
10160 $mysync->{ h2_sep } = '/';
10161
10162 #
10163 #$mysync->{ debug } = 1 ;
10164 is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'F1.F2.F3' ), 'imap2_folder_name: F1.F2.F3 -> Pf2/F1/F2/F3' ) ;
10165 is( 'Pf2/F1/INBOX', imap2_folder_name( $mysync, 'F1.INBOX' ), 'imap2_folder_name: F1.INBOX -> Pf2/F1/INBOX' ) ;
10166 is( 'INBOX', imap2_folder_name( $mysync, 'INBOX' ), 'imap2_folder_name: INBOX -> INBOX' ) ;
10167
10168 is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'Pf1.F1.F2.F3' ), 'imap2_folder_name: Pf1.F1.F2.F3 -> Pf2/F1/F2/F3' ) ;
10169 is( 'Pf2/F1/INBOX', imap2_folder_name( $mysync, 'Pf1.F1.INBOX' ), 'imap2_folder_name: Pf1.F1.INBOX -> Pf2/F1/INBOX' ) ;
10170 is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.INBOX' ), 'imap2_folder_name: Pf1.INBOX -> INBOX' ) ; # not Pf2/INBOX: Yes I can!
10171
10172
10173
10174 # subfolder2
10175 $mysync = {} ;
10176 $mysync->{ h1_prefix } = q{} ;
10177 $mysync->{ h2_prefix } = q{} ;
10178 $mysync->{ h1_sep } = '/';
10179 $mysync->{ h2_sep } = '.';
10180
10181
10182 set_regextrans2_for_subfolder2( $mysync ) ;
10183 $mysync->{ subfolder2 } = 'S1.S2' ;
10184 is( 'S1.S2.F1.F2.F3', imap2_folder_name( $mysync, 'F1/F2/F3' ), 'imap2_folder_name: F1/F2/F3 -> S1.S2.F1.F2.F3' ) ;
10185 is( 'S1.S2.INBOX', imap2_folder_name( $mysync, 'INBOX' ), 'imap2_folder_name: F1/F2/F3 -> S1.S2.INBOX' ) ;
10186
10187 $mysync = {} ;
10188 $mysync->{ h1_prefix } = q{Pf1/} ;
10189 $mysync->{ h2_prefix } = q{Pf2.} ;
10190 $mysync->{ h1_sep } = '/';
10191 $mysync->{ h2_sep } = '.';
10192 #$mysync->{ debug } = 1 ;
10193
10194 set_regextrans2_for_subfolder2( $mysync ) ;
10195 $mysync->{ subfolder2 } = 'Pf2.S1.S2' ;
10196 is( 'Pf2.S1.S2.F1.F2.F3', imap2_folder_name( $mysync, 'F1/F2/F3' ), 'imap2_folder_name: F1/F2/F3 -> Pf2.S1.S2.F1.F2.F3' ) ;
10197 is( 'Pf2.S1.S2.INBOX', imap2_folder_name( $mysync, 'INBOX' ), 'imap2_folder_name: INBOX -> Pf2.S1.S2.INBOX' ) ;
10198 is( 'Pf2.S1.S2.F1.F2.F3', imap2_folder_name( $mysync, 'Pf1/F1/F2/F3' ), 'imap2_folder_name: F1/F2/F3 -> Pf2.S1.S2.F1.F2.F3' ) ;
10199 is( 'Pf2.S1.S2.INBOX', imap2_folder_name( $mysync, 'Pf1/INBOX' ), 'imap2_folder_name: INBOX -> Pf2.S1.S2.INBOX' ) ;
10200
10201 # subfolder1
10202 # scenario as the reverse of the previous tests, separators point of vue
10203 $mysync = {} ;
10204 $mysync->{ h1_prefix } = q{Pf1.} ;
10205 $mysync->{ h2_prefix } = q{Pf2/} ;
10206 $mysync->{ h1_sep } = '.';
10207 $mysync->{ h2_sep } = '/';
10208 #$mysync->{ debug } = 1 ;
10209
10210 $mysync->{ subfolder1 } = 'S1.S2' ;
10211 is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'S1.S2.F1.F2.F3' ), 'imap2_folder_name: S1.S2.F1.F2.F3 -> Pf2/F1/F2/F3' ) ;
10212 is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'Pf1.S1.S2.F1.F2.F3' ), 'imap2_folder_name: Pf1.S1.S2.F1.F2.F3 -> Pf2/F1/F2/F3' ) ;
10213
10214 is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.INBOX' ), 'imap2_folder_name: S1.S2.INBOX -> INBOX' ) ;
10215 is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2' ), 'imap2_folder_name: S1.S2 -> INBOX' ) ;
10216 is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.' ), 'imap2_folder_name: S1.S2. -> INBOX' ) ;
10217
10218 is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.INBOX' ), 'imap2_folder_name: Pf1.S1.S2.INBOX -> INBOX' ) ;
10219 is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2' ), 'imap2_folder_name: Pf1.S1.S2 -> INBOX' ) ;
10220 is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.' ), 'imap2_folder_name: Pf1.S1.S2. -> INBOX' ) ;
10221
10222
10223 $mysync->{ subfolder1 } = 'S1.S2.' ;
10224 is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'S1.S2.F1.F2.F3' ), 'imap2_folder_name: S1.S2.F1.F2.F3 -> Pf2/F1/F2/F3' ) ;
10225 is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'Pf1.S1.S2.F1.F2.F3' ), 'imap2_folder_name: Pf1.S1.S2.F1.F2.F3 -> Pf2/F1/F2/F3' ) ;
10226
10227 is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.INBOX' ), 'imap2_folder_name: S1.S2.INBOX -> INBOX' ) ;
10228 is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2' ), 'imap2_folder_name: S1.S2 -> INBOX' ) ;
10229 is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.' ), 'imap2_folder_name: S1.S2. -> INBOX' ) ;
10230
10231 is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.INBOX' ), 'imap2_folder_name: Pf1.S1.S2.INBOX -> INBOX' ) ;
10232 is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2' ), 'imap2_folder_name: Pf1.S1.S2 -> INBOX' ) ;
10233 is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.' ), 'imap2_folder_name: Pf1.S1.S2. -> INBOX' ) ;
10234
10235
10236 # subfolder1
10237 # scenario as Gmail
10238 $mysync = {} ;
10239 $mysync->{ h1_prefix } = q{} ;
10240 $mysync->{ h2_prefix } = q{} ;
10241 $mysync->{ h1_sep } = '/';
10242 $mysync->{ h2_sep } = '/';
10243 #$mysync->{ debug } = 1 ;
10244
10245 $mysync->{ subfolder1 } = 'S1/S2' ;
10246 is( 'F1/F2/F3', imap2_folder_name( $mysync, 'S1/S2/F1/F2/F3' ), 'imap2_folder_name: S1/S2/F1/F2/F3 -> F1/F2/F3' ) ;
10247 is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/INBOX' ), 'imap2_folder_name: S1/S2/INBOX -> INBOX' ) ;
10248 is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2' ), 'imap2_folder_name: S1/S2 -> INBOX' ) ;
10249 is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/' ), 'imap2_folder_name: S1/S2/ -> INBOX' ) ;
10250
10251 $mysync->{ subfolder1 } = 'S1/S2/' ;
10252 is( 'F1/F2/F3', imap2_folder_name( $mysync, 'S1/S2/F1/F2/F3' ), 'imap2_folder_name: S1/S2/F1/F2/F3 -> F1/F2/F3' ) ;
10253 is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/INBOX' ), 'imap2_folder_name: S1/S2/INBOX -> INBOX' ) ;
10254 is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2' ), 'imap2_folder_name: S1/S2 -> INBOX' ) ;
10255 is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/' ), 'imap2_folder_name: S1/S2/ -> INBOX' ) ;
10256
10257
10258 note( 'Leaving tests_imap2_folder_name()' ) ;
10259 return ;
10260}
10261
10262
10263# Global variables to remove:
10264# None?
10265
10266
10267sub imap2_folder_name
10268{
10269 my $mysync = shift ;
10270 my ( $h1_fold ) = shift ;
10271 my ( $h2_fold ) ;
10272 if ( $mysync->{f1f2h}{ $h1_fold } ) {
10273 $h2_fold = $mysync->{f1f2h}{ $h1_fold } ;
10274 ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "f1f2 [$h1_fold] -> [$h2_fold]\n" ) ;
10275 return( $h2_fold ) ;
10276 }
10277 if ( $mysync->{f1f2auto}{ $h1_fold } ) {
10278 $h2_fold = $mysync->{f1f2auto}{ $h1_fold } ;
10279 ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "automap [$h1_fold] -> [$h2_fold]\n" ) ;
10280 return( $h2_fold ) ;
10281 }
10282
10283 if ( $mysync->{ subfolder1 } )
10284 {
10285 my $esc_h1_sep = "\\" . $mysync->{ h1_sep } ;
10286 # case where subfolder1 has the sep1 at the end, then remove it
10287 my $part_to_removed = remove_last_char_if_is( $mysync->{ subfolder1 }, $mysync->{ h1_sep } ) ;
10288 # remove the subfolder1 part and the sep1 if present after
10289 $h1_fold =~ s{$part_to_removed($esc_h1_sep)?}{} ;
10290 #myprint( "h1_fold=$h1_fold\n" ) ;
10291 }
10292
10293 if ( ( q{} eq $h1_fold ) or ( $mysync->{ h1_prefix } eq $h1_fold ) )
10294 {
10295 $h1_fold = 'INBOX' ;
10296 }
10297
10298 $h2_fold = prefix_seperator_invertion( $mysync, $h1_fold ) ;
10299 $h2_fold = regextrans2( $mysync, $h2_fold ) ;
10300 return( $h2_fold ) ;
10301}
10302
10303
10304sub tests_remove_last_char_if_is
10305{
10306 note( 'Entering tests_remove_last_char_if_is()' ) ;
10307
10308 is( undef, remove_last_char_if_is( ), 'remove_last_char_if_is: no args => undef' ) ;
10309 is( q{}, remove_last_char_if_is( q{} ), 'remove_last_char_if_is: empty => empty' ) ;
10310 is( q{}, remove_last_char_if_is( q{}, 'Z' ), 'remove_last_char_if_is: empty Z => empty' ) ;
10311 is( q{}, remove_last_char_if_is( 'Z', 'Z' ), 'remove_last_char_if_is: Z Z => empty' ) ;
10312 is( 'abc', remove_last_char_if_is( 'abcZ', 'Z' ), 'remove_last_char_if_is: abcZ Z => abc' ) ;
10313 is( 'abcY', remove_last_char_if_is( 'abcY', 'Z' ), 'remove_last_char_if_is: abcY Z => abcY' ) ;
10314 note( 'Leaving tests_remove_last_char_if_is()' ) ;
10315 return ;
10316}
10317
10318
10319
10320
10321sub remove_last_char_if_is
10322{
10323 my $string = shift ;
10324 my $char = shift ;
10325
10326 if ( ! defined $string )
10327 {
10328 return ;
10329 }
10330
10331 if ( ! defined $char )
10332 {
10333 return $string ;
10334 }
10335
10336 my $last_char = substr $string, -1 ;
10337 if ( $char eq $last_char )
10338 {
10339 chop $string ;
10340 return $string ;
10341 }
10342 else
10343 {
10344 return $string ;
10345 }
10346}
10347
10348sub tests_prefix_seperator_invertion
10349{
10350 note( 'Entering tests_prefix_seperator_invertion()' ) ;
10351
10352 is( undef, prefix_seperator_invertion( ), 'prefix_seperator_invertion: no args => undef' ) ;
10353 is( q{}, prefix_seperator_invertion( undef, q{} ), 'prefix_seperator_invertion: empty string => empty string' ) ;
10354 is( 'lalala', prefix_seperator_invertion( undef, 'lalala' ), 'prefix_seperator_invertion: lalala => lalala' ) ;
10355 is( 'lal/ala', prefix_seperator_invertion( undef, 'lal/ala' ), 'prefix_seperator_invertion: lal/ala => lal/ala' ) ;
10356 is( 'lal.ala', prefix_seperator_invertion( undef, 'lal.ala' ), 'prefix_seperator_invertion: lal.ala => lal.ala' ) ;
10357 is( '////', prefix_seperator_invertion( undef, '////' ), 'prefix_seperator_invertion: //// => ////' ) ;
10358 is( '.....', prefix_seperator_invertion( undef, '.....' ), 'prefix_seperator_invertion: ..... => .....' ) ;
10359
10360 my $mysync = {
10361 h1_prefix => q{},
10362 h2_prefix => q{},
10363 h1_sep => '/',
10364 h2_sep => '/',
10365 } ;
10366
10367 is( q{}, prefix_seperator_invertion( $mysync, q{} ), 'prefix_seperator_invertion: $mysync empty string => empty string' ) ;
10368 is( 'lalala', prefix_seperator_invertion( $mysync, 'lalala' ), 'prefix_seperator_invertion: $mysync lalala => lalala' ) ;
10369 is( 'lal/ala', prefix_seperator_invertion( $mysync, 'lal/ala' ), 'prefix_seperator_invertion: $mysync lal/ala => lal/ala' ) ;
10370 is( 'lal.ala', prefix_seperator_invertion( $mysync, 'lal.ala' ), 'prefix_seperator_invertion: $mysync lal.ala => lal.ala' ) ;
10371 is( '////', prefix_seperator_invertion( $mysync, '////' ), 'prefix_seperator_invertion: $mysync //// => ////' ) ;
10372 is( '.....', prefix_seperator_invertion( $mysync, '.....' ), 'prefix_seperator_invertion: $mysync ..... => .....' ) ;
10373
10374 $mysync = {
10375 h1_prefix => 'PPP',
10376 h2_prefix => 'QQQ',
10377 h1_sep => 's',
10378 h2_sep => 't',
10379 } ;
10380
10381 is( q{QQQ}, prefix_seperator_invertion( $mysync, q{} ), 'prefix_seperator_invertion: PPPQQQst empty string => QQQ' ) ;
10382 is( 'QQQlalala', prefix_seperator_invertion( $mysync, 'lalala' ), 'prefix_seperator_invertion: PPPQQQst lalala => QQQlalala' ) ;
10383 is( 'QQQlal/ala', prefix_seperator_invertion( $mysync, 'lal/ala' ), 'prefix_seperator_invertion: PPPQQQst lal/ala => QQQlal/ala' ) ;
10384 is( 'QQQlal.ala', prefix_seperator_invertion( $mysync, 'lal.ala' ), 'prefix_seperator_invertion: PPPQQQst lal.ala => QQQlal.ala' ) ;
10385 is( 'QQQ////', prefix_seperator_invertion( $mysync, '////' ), 'prefix_seperator_invertion: PPPQQQst //// => QQQ////' ) ;
10386 is( 'QQQ.....', prefix_seperator_invertion( $mysync, '.....' ), 'prefix_seperator_invertion: PPPQQQst ..... => QQQ.....' ) ;
10387
10388 is( 'QQQPlalala', prefix_seperator_invertion( $mysync, 'PPPPlalala' ), 'prefix_seperator_invertion: PPPQQQst PPPPlalala => QQQPlalala' ) ;
10389 is( 'QQQ', prefix_seperator_invertion( $mysync, 'PPP' ), 'prefix_seperator_invertion: PPPQQQst PPP => QQQ' ) ;
10390 is( 'QQQttt', prefix_seperator_invertion( $mysync, 'sss' ), 'prefix_seperator_invertion: PPPQQQst sss => QQQttt' ) ;
10391 is( 'QQQt', prefix_seperator_invertion( $mysync, 's' ), 'prefix_seperator_invertion: PPPQQQst s => QQQt' ) ;
10392 is( 'QQQtAAAtBBB', prefix_seperator_invertion( $mysync, 'PPPsAAAsBBB' ), 'prefix_seperator_invertion: PPPQQQst PPPsAAAsBBB => QQQtAAAtBBB' ) ;
10393
10394 note( 'Leaving tests_prefix_seperator_invertion()' ) ;
10395 return ;
10396}
10397
10398# Global variables to remove:
10399
10400
10401sub prefix_seperator_invertion
10402{
10403 my $mysync = shift ;
10404 my $h1_fold = shift ;
10405 my $h2_fold ;
10406
10407 if ( not defined $h1_fold ) { return ; }
10408
10409 my $my_h1_prefix = $mysync->{ h1_prefix } || q{} ;
10410 my $my_h2_prefix = $mysync->{ h2_prefix } || q{} ;
10411 my $my_h1_sep = $mysync->{ h1_sep } || '/' ;
10412 my $my_h2_sep = $mysync->{ h2_sep } || '/' ;
10413
10414 # first we remove the prefix
10415 $h1_fold =~ s/^\Q$my_h1_prefix\E//x ;
10416 ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "removed host1 prefix: [$h1_fold]\n" ) ;
10417 $h2_fold = separator_invert( $mysync, $h1_fold, $my_h1_sep, $my_h2_sep ) ;
10418 ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "inverted separators: [$h2_fold]\n" ) ;
10419
10420 # Adding the prefix supplied by namespace or the --prefix2 option
10421 # except for INBOX or Inbox
10422 if ( $h2_fold !~ m/^INBOX$/xi )
10423 {
10424 $h2_fold = $my_h2_prefix . $h2_fold ;
10425 }
10426
10427 ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "added host2 prefix: [$h2_fold]\n" ) ;
10428 return( $h2_fold ) ;
10429}
10430
10431sub tests_separator_invert
10432{
10433 note( 'Entering tests_separator_invert()' ) ;
10434
10435 my $mysync = {} ;
10436 $mysync->{ fixslash2 } = 0 ;
10437 ok( not( defined separator_invert( ) ), 'separator_invert: no args' ) ;
10438 ok( not( defined separator_invert( q{} ) ), 'separator_invert: not enough args' ) ;
10439 ok( not( defined separator_invert( q{}, q{} ) ), 'separator_invert: not enough args' ) ;
10440
10441 ok( q{} eq separator_invert( $mysync, q{}, q{}, q{} ), 'separator_invert: 3 empty strings' ) ;
10442 ok( 'lalala' eq separator_invert( $mysync, 'lalala', q{}, q{} ), 'separator_invert: empty separator' ) ;
10443 ok( 'lalala' eq separator_invert( $mysync, 'lalala', '/', '/' ), 'separator_invert: same separator /' ) ;
10444 ok( 'lal/ala' eq separator_invert( $mysync, 'lal/ala', '/', '/' ), 'separator_invert: same separator / 2' ) ;
10445 ok( 'lal.ala' eq separator_invert( $mysync, 'lal/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
10446 ok( 'lal/ala' eq separator_invert( $mysync, 'lal.ala', '.', '/' ), 'separator_invert: separators ./' ) ;
10447 ok( 'la.l/ala' eq separator_invert( $mysync, 'la/l.ala', '.', '/' ), 'separator_invert: separators ./' ) ;
10448
10449 ok( 'l/al.ala' eq separator_invert( $mysync, 'l.al/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
10450 $mysync->{ fixslash2 } = 1 ;
10451 ok( 'l_al.ala' eq separator_invert( $mysync, 'l.al/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
10452
10453 note( 'Leaving tests_separator_invert()' ) ;
10454 return ;
10455}
10456
10457# Global variables to remove:
10458#
10459sub separator_invert
10460{
10461 my( $mysync, $h1_fold, $h1_separator, $h2_separator ) = @_ ;
10462
10463 return( undef ) if ( not all_defined( $mysync, $h1_fold, $h1_separator, $h2_separator ) ) ;
10464 # The separator we hope we'll never encounter: 00000000 == 0x00
10465 my $o_sep = "\000" ;
10466
10467 my $h2_fold = $h1_fold ;
10468 $h2_fold =~ s,\Q$h2_separator,$o_sep,xg ;
10469 $h2_fold =~ s,\Q$h1_separator,$h2_separator,xg ;
10470 $h2_fold =~ s,\Q$o_sep,$h1_separator,xg ;
10471 $h2_fold =~ s,/,_,xg if( $mysync->{ fixslash2 } and '/' ne $h2_separator and '/' eq $h1_separator ) ;
10472 return( $h2_fold ) ;
10473}
10474
10475
10476sub regextrans2
10477{
10478 my( $mysync, $h2_fold ) = @_ ;
10479 # Transforming the folder name by the --regextrans2 option(s)
10480 foreach my $regextrans2 ( @{ $mysync->{ regextrans2 } } ) {
10481 my $h2_fold_before = $h2_fold ;
10482 my $ret = eval "\$h2_fold =~ $regextrans2 ; 1 " ;
10483 ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "[$h2_fold_before] -> [$h2_fold] using regextrans2 [$regextrans2]\n" ) ;
10484 if ( not ( defined $ret ) or $EVAL_ERROR ) {
10485 $mysync->{nb_errors}++ ;
10486 exit_clean( $mysync, $EX_USAGE,
10487 "error: eval regextrans2 '$regextrans2': $EVAL_ERROR\n"
10488 ) ;
10489 }
10490 }
10491 return( $h2_fold ) ;
10492}
10493
10494
10495sub tests_decompose_regex
10496{
10497 note( 'Entering tests_decompose_regex()' ) ;
10498
10499 ok( 1, 'decompose_regex 1' ) ;
10500 ok( 0 == compare_lists( [ q{}, q{} ], [ decompose_regex( q{} ) ] ), 'decompose_regex empty string' ) ;
10501 ok( 0 == compare_lists( [ '.*', 'lala' ], [ decompose_regex( 's/.*/lala/' ) ] ), 'decompose_regex s/.*/lala/' ) ;
10502
10503 note( 'Leaving tests_decompose_regex()' ) ;
10504 return ;
10505}
10506
10507sub decompose_regex
10508{
10509 my $regex = shift ;
10510 my( $left_part, $right_part ) ;
10511
10512 ( $left_part, $right_part ) = $regex =~ m{^s/((?:[^/]|\\/)+)/((?:[^/]|\\/)+)/}x;
10513 return( q{}, q{} ) if not $left_part ;
10514 return( $left_part, $right_part ) ;
10515}
10516
10517
10518
10519sub tests_timenext
10520{
10521 note( 'Entering tests_timenext()' ) ;
10522
10523 is( undef, timenext( ), 'timenext: no args => undef' ) ;
10524 my $mysync ;
10525 is( undef, timenext( $mysync ), 'timenext: undef => undef' ) ;
10526 $mysync = {} ;
10527 ok( time - timenext( $mysync ) <= 1e-02, 'timenext: defined first time => ~ time' ) ;
10528 ok( timenext( $mysync ) <= 1e-02, 'timenext: second time => less than 1e-02' ) ;
10529 ok( timenext( $mysync ) <= 1e-02, 'timenext: third time => less than 1e-02' ) ;
10530
10531 note( 'Leaving tests_timenext()' ) ;
10532 return ;
10533}
10534
10535
10536sub timenext
10537{
10538 my $mysync = shift ;
10539
10540 if ( ! defined $mysync )
10541 {
10542 return ;
10543 }
10544 my ( $timenow, $timediff ) ;
10545
10546 $mysync->{ timebefore } ||= 0; # epoch...
10547 $timenow = time ;
10548 $timediff = $timenow - $mysync->{ timebefore } ;
10549 $mysync->{ timebefore } = $timenow ;
10550 # myprint( "timenext: $timediff\n" ) ;
10551 return( $timediff ) ;
10552}
10553
10554
10555sub tests_timesince
10556{
10557 note( 'Entering tests_timesince()' ) ;
10558
10559 ok( timesince( time - 1 ) - 1 <= 1e-02, 'timesince: time - 1 => <= 1 + 1e-02' ) ;
10560 ok( timesince( time ) <= 1e-02, 'timesince: time => <= 1e-02' ) ;
10561 ok( timesince( ) - time <= 1e-02, 'timesince: no args => <= time + 1e-02' ) ;
10562 note( 'Leaving tests_timesince()' ) ;
10563 return ;
10564}
10565
10566
10567
10568sub timesince
10569{
10570 my $timeinit = shift || 0 ;
10571 my ( $timenow, $timediff ) ;
10572 $timenow = time ;
10573 $timediff = $timenow - $timeinit ;
10574 # Often used in a division so no 0 but a nano seconde.
10575 return( max( $timediff, min( 1e-09, $timediff ) ) ) ;
10576}
10577
10578
10579
10580
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010581sub tests_regexflags
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010582{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010583 note( 'Entering tests_regexflags()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010584
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010585 my $mysync = {} ;
10586
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010587 ok( q{} eq regexflags( $mysync, q{} ), 'regexflags, null string q{}' ) ;
10588 ok( q{\Seen NonJunk $Spam} eq regexflags( $mysync, q{\Seen NonJunk $Spam} ), q{regexflags, nothing to do} ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010589
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010590 @{ $mysync->{ regexflag } } = ('I am BAD' ) ;
10591 ok( not ( defined regexflags( $mysync, q{} ) ), 'regexflags, bad regex' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010592
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010593 @{ $mysync->{ regexflag } } = ( 's/NonJunk//g' ) ;
10594 ok( q{\Seen $Spam} eq regexflags( $mysync, q{\Seen NonJunk $Spam} ), q{regexflags, remove NonJunk: 's/NonJunk//g'} ) ;
10595 @{ $mysync->{ regexflag } } = ( q{s/\$Spam//g} ) ;
10596 ok( q{\Seen NonJunk } eq regexflags( $mysync, q{\Seen NonJunk $Spam} ), q{regexflags, remove $Spam: 's/\$Spam//g'} ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010597
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010598 @{ $mysync->{ regexflag } } = ( 's/\\\\Seen//g' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010599
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010600 ok( q{ NonJunk $Spam} eq regexflags( $mysync, q{\Seen NonJunk $Spam} ), q{regexflags, remove \Seen: 's/\\\\\\\\Seen//g'} ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010601
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010602 @{ $mysync->{ regexflag } } = ( 's/(\s|^)[^\\\\]\w+//g' ) ;
10603 ok( q{\Seen \Middle \End} eq regexflags( $mysync, q{\Seen NonJunk \Middle $Spam \End} ), q{regexflags: only \word among \Seen NonJunk \Middle $Spam \End} ) ;
10604 ok( q{ \Seen \Middle \End1} eq regexflags( $mysync, q{Begin \Seen NonJunk \Middle $Spam \End1 End} ),
10605 q{regexflags: only \word among Begin \Seen NonJunk \Middle $Spam \End1 End} ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010606
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010607 @{ $mysync->{ regexflag } } = ( q{s/.*?(Keep1|Keep2|Keep3)/$1 /g} ) ;
10608 ok( 'Keep1 Keep2 ReB' eq regexflags( $mysync, 'ReA Keep1 REM Keep2 ReB' ), 'Keep only regex' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010609
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010610 ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM REM Keep1 Keep2' ), 'Keep only regex' ) ;
10611 ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 REM REM Keep2' ), 'Keep only regex' ) ;
10612 ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM Keep1 REM REM Keep2' ), 'Keep only regex' ) ;
10613 ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 Keep2' ), 'Keep only regex' ) ;
10614 ok( 'Keep1 ' eq regexflags( $mysync, 'REM Keep1' ), 'Keep only regex' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010615
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010616 @{ $mysync->{ regexflag } } = ( q{s/(Keep1|Keep2|Keep3) (?!(Keep1|Keep2|Keep3)).*/$1 /g} ) ;
10617 ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 Keep2 ReB' ), 'Keep only regex' ) ;
10618 ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 Keep2 REM REM REM' ), 'Keep only regex' ) ;
10619 ok( 'Keep2 ' eq regexflags( $mysync, 'Keep2 REM REM REM' ), 'Keep only regex' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010620
10621
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010622 @{ $mysync->{ regexflag } } = ( q{s/.*?(Keep1|Keep2|Keep3)/$1 /g},
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010623 's/(Keep1|Keep2|Keep3) (?!(Keep1|Keep2|Keep3)).*/$1 /g' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010624 ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM Keep1 REM Keep2 REM' ), 'Keep only regex' ) ;
10625 ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 REM Keep2 REM' ), 'Keep only regex' ) ;
10626 ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM Keep1 Keep2 REM' ), 'Keep only regex' ) ;
10627 ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM Keep1 REM Keep2' ), 'Keep only regex' ) ;
10628 ok( 'Keep1 Keep2 Keep3 ' eq regexflags( $mysync, 'REM Keep1 REM Keep2 REM REM Keep3 REM' ), 'Keep only regex' ) ;
10629 ok( 'Keep1 ' eq regexflags( $mysync, 'REM REM Keep1 REM REM REM ' ), 'Keep only regex' ) ;
10630 ok( 'Keep1 Keep3 ' eq regexflags( $mysync, 'RE1 Keep1 RE2 Keep3 RE3 RE4 RE5 ' ), 'Keep only regex' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010631
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010632 @{ $mysync->{ regexflag } } = ( 's/(.*)/$1 jrdH8u/' ) ;
10633 ok('REM REM REM REM REM jrdH8u' eq regexflags( $mysync, 'REM REM REM REM REM' ), q{Add jrdH8u 's/(.*)/\$1 jrdH8u/'} ) ;
10634 @{ $mysync->{ regexflag } } = ('s/jrdH8u *//' );
10635 ok('REM REM REM REM REM ' eq regexflags( $mysync, 'REM REM REM REM REM jrdH8u' ), q{Remove jrdH8u s/jrdH8u *//} ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010636
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010637 @{ $mysync->{ regexflag } } = (
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010638 's/.*?(?:(\\\\(?:Answered|Flagged|Deleted|Seen|Draft)\s?)|$)/defined($1)?$1:q()/eg'
10639 );
10640
10641 ok( '\\Deleted \\Answered '
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010642 eq regexflags( $mysync, 'Blabla \$Junk \\Deleted machin \\Answered truc' ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010643 'Keep only regex: Exchange case (Phil)' ) ;
10644
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010645 ok( q{} eq regexflags( $mysync, q{} ), 'Keep only regex: Exchange case, null string (Phil)' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010646
10647 ok( q{}
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010648 eq regexflags( $mysync, 'Blabla $Junk machin truc' ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010649 'Keep only regex: Exchange case, no accepted flags (Phil)' ) ;
10650
10651 ok('\\Deleted \\Answered \\Draft \\Flagged '
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010652 eq regexflags( $mysync, '\\Deleted \\Answered \\Draft \\Flagged ' ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010653 'Keep only regex: Exchange case (Phil)' ) ;
10654
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010655 @{ $mysync->{ regexflag } } = ( 's/\\\\Flagged//g' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010656
10657 is('\Deleted \Answered \Draft ',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010658 regexflags( $mysync, '\\Deleted \\Answered \\Draft \\Flagged ' ),
10659 'regexflags: remove \Flagged 1' ) ;
10660
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010661 is('\\Deleted \\Answered \\Draft',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010662 regexflags( $mysync, '\\Deleted \\Flagged \\Answered \\Draft' ),
10663 'regexflags: remove \Flagged 2' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010664
10665 # I didn't understand why it gives \F
10666 # https://perldoc.perl.org/perlrebackslash.html
10667 # \F Foldcase till \E. Not in [].
10668 # https://perldoc.perl.org/functions/fc.html
10669
10670 # \F Not available in old Perl so I comment the test
10671
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010672 # @{ $mysync->{ regexflag } } = ( 's/\\Flagged/X/g' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010673 #is('\Deleted FX \Answered \FX \Draft \FX',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010674 #regexflags( '\Deleted Flagged \Answered \Flagged \Draft \Flagged' ),
10675 # 'regexflags: remove \Flagged 3 mistery...' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010676
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010677 $mysync->{ regexflag } = [ ] ;
10678 $mysync->{ filterbuggyflags } = 1 ;
10679 filterbuggyflags( $mysync ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010680
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010681 is( '\Deleted \Answered \Draft \Flagged',
10682 regexflags( $mysync, '\\Deleted \\Answered \\RECEIPTCHECKED \\Draft \\Indexed \\Flagged' ),
10683 'regexflags: remove famous /X 1' ) ;
10684
10685 is( '\\Deleted \\Flagged \\Answered \\Draft',
10686 regexflags( $mysync, '\\Deleted \\RECEIPTCHECKED \\Flagged \\Answered \\Indexed \\Draft' ),
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010687 'regexflags: remove famous /X 2' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010688
10689 is( '\ ', '\\ ', 'regexflags: \ is \\ ' ) ;
10690 is( '\\ ', '\\ ', 'regexflags: \\ is \\ ' ) ;
10691 is( '\\ \ ', '\ \\ ', 'regexflags: \\ \ is \ \\ ' ) ;
10692 note( 'Leaving tests_regexflags()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010693 return ;
10694}
10695
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010696sub regexflags
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010697{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010698 my $mysync = shift ;
10699 my $flags = shift ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010700
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010701 foreach my $regexflag ( @{ $mysync->{ regexflag } } )
10702 {
10703 my $flags_orig = $flags ;
10704 $debugflags and myprint( "eval \$flags =~ $regexflag\n" ) ;
10705 my $ret = eval "\$flags =~ $regexflag ; 1 " ;
10706 $debugflags and myprint( "regexflag $regexflag [$flags_orig] -> [$flags]\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010707 if( not ( defined $ret ) or $EVAL_ERROR ) {
10708 myprint( "Error: eval regexflag '$regexflag': $EVAL_ERROR\n" ) ;
10709 return( undef ) ;
10710 }
10711 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010712 return( $flags ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010713}
10714
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010715
10716sub filterbuggyflags
10717{
10718 my $mysync = shift ;
10719 if ( $mysync->{ filterbuggyflags } )
10720 {
10721 unshift @{ $mysync->{ regexflag } }, buggyflagsregex( ) ;
10722 }
10723 return ;
10724}
10725
10726
10727sub tests_remove_doublequotes_if_any
10728{
10729 note( 'Entering tests_remove_doublequotes_if_any()' ) ;
10730 # the number of tests is stupid here
10731 is( undef, remove_doublequotes_if_any( ), 'remove_doublequotes_if_any: no args => undef' ) ;
10732 is( q{}, remove_doublequotes_if_any( q{} ), 'remove_doublequotes_if_any: empty string => empty string' ) ;
10733 is( q{}, remove_doublequotes_if_any( q{""} ), 'remove_doublequotes_if_any: double-quotes => empty string' ) ;
10734 is( q{}, remove_doublequotes_if_any( q{"""} ), 'remove_doublequotes_if_any: double-quotes => empty string' ) ;
10735 is( q{}, remove_doublequotes_if_any( q{"""} ), 'remove_doublequotes_if_any: double-quotes => empty string' ) ;
10736 is( q{toto}, remove_doublequotes_if_any( q{"toto"} ), 'remove_doublequotes_if_any: "toto" => toto' ) ;
10737 is( q{toto}, remove_doublequotes_if_any( q{toto} ), 'remove_doublequotes_if_any: toto => toto' ) ;
10738 is( q{toto}, remove_doublequotes_if_any( q{to"to} ), 'remove_doublequotes_if_any: to"to => toto' ) ;
10739 is( q{toto}, remove_doublequotes_if_any( q{toto"} ), 'remove_doublequotes_if_any: toto" => toto' ) ;
10740 is( q{toto}, remove_doublequotes_if_any( q{"toto} ), 'remove_doublequotes_if_any: "toto => toto' ) ;
10741 is( q{toto}, remove_doublequotes_if_any( q{"to"to} ), 'remove_doublequotes_if_any: "to"to => toto' ) ;
10742 is( q{toto}, remove_doublequotes_if_any( q{to"to"} ), 'remove_doublequotes_if_any: to"to" => toto' ) ;
10743
10744 is( q{toto}, remove_doublequotes_if_any( q{to\"to} ), 'remove_doublequotes_if_any: to\"to => toto' ) ;
10745 is( q{toto}, remove_doublequotes_if_any( q{toto\"} ), 'remove_doublequotes_if_any: toto\" => toto' ) ;
10746 is( q{toto}, remove_doublequotes_if_any( q{\"toto} ), 'remove_doublequotes_if_any: \"toto => toto' ) ;
10747 is( q{toto}, remove_doublequotes_if_any( q{\"to\"to} ), 'remove_doublequotes_if_any: \"to\"to => toto' ) ;
10748 is( q{toto}, remove_doublequotes_if_any( q{to\"to\"} ), 'remove_doublequotes_if_any: to\"to" => toto' ) ;
10749
10750
10751 note( 'Leaving tests_remove_doublequotes_if_any()' ) ;
10752 return ;
10753}
10754
10755
10756
10757sub remove_doublequotes_if_any
10758{
10759 my $string = shift ;
10760
10761 if ( ! defined $string ) { return ; }
10762 $string =~ s/\\\"//g ;
10763 $string =~ tr/"//d ;
10764 return $string ;
10765}
10766
10767
10768# No globals here
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010769sub acls_sync
10770{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010771# https://tools.ietf.org/html/rfc4314
10772# Standard Rights:
10773# https://tools.ietf.org/html/rfc4314#section-2.1
10774
10775 my( $mysync, $h1_fold, $h2_fold ) = @_ ;
10776 if ( $mysync->{ syncacls } ) {
10777 my $h1_hash = $mysync->{imap1}->getacl($h1_fold)
10778 or myprint( "Host1: Could not getacl for $h1_fold: $EVAL_ERROR\n" ) ;
10779 my $h2_hash = $mysync->{imap2}->getacl($h2_fold)
10780 or myprint( "Host2: Could not getacl for $h2_fold: $EVAL_ERROR\n" ) ;
10781
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010782 my %users = map { ($_, 1) } ( keys %{ $h1_hash} , keys %{ $h2_hash } ) ;
10783 foreach my $user (sort keys %users ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010784 my $h1_acl = remove_doublequotes_if_any( $h1_hash->{$user} ) || '' ;
10785 my $h2_acl = remove_doublequotes_if_any( $h2_hash->{$user} ) || '' ;
10786 myprint( "Host1: user $user has acl [$h1_acl] on host1\n" ) ;
10787 myprint( "Host2: user $user has acl [$h2_acl] on host2\n" ) ;
10788 # removes surrounding double-quotes if any
10789 my $user_no_quotes = remove_doublequotes_if_any( $user ) ;
10790
10791 if ( $h1_hash->{$user}
10792 && $h2_hash->{$user}
10793 && $h1_hash->{$user} eq $h2_hash->{$user} )
10794 {
10795 myprint( "Host2: user $user_no_quotes has already the same acl, no need to set it.\n" ) ;
10796 next ;
10797 }
10798 myprint( "Host2: setting acl for folder $h2_fold user $user_no_quotes acl $h1_acl $mysync->{dry_message}\n" ) ;
10799 unless ( $mysync->{dry} ) {
10800 $mysync->{imap2}->setacl( $h2_fold, $user_no_quotes, $h1_acl )
10801 or myprint( "Could not set acl for user $user_no_quotes on host2: $EVAL_ERROR\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010802 }
10803 }
10804 }
10805 return ;
10806}
10807
10808
10809sub tests_permanentflags
10810{
10811 note( 'Entering tests_permanentflags()' ) ;
10812
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010813 my $mysync = { } ;
10814 ok( q{} eq permanentflags( $mysync, ' * OK [PERMANENTFLAGS (\* \Draft \Answered)] Limited' ),
10815 'permanentflags \*' ) ;
10816
10817 ok( '\Draft \Answered' eq permanentflags( $mysync, ' * OK [PERMANENTFLAGS (\Draft \Answered)] Limited' ),
10818 'permanentflags \Draft \Answered' ) ;
10819
10820 ok( '\Draft \Answered'
10821 eq permanentflags( $mysync, 'Blabla',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010822 ' * OK [PERMANENTFLAGS (\Draft \Answered)] Limited',
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010823 'Blabla' ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010824 'permanentflags \Draft \Answered'
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010825 ) ;
10826
10827 ok( q{} eq permanentflags( $mysync, 'Blabla' ), 'permanentflags nothing' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010828
10829 note( 'Leaving tests_permanentflags()' ) ;
10830 return ;
10831}
10832
10833sub permanentflags
10834{
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010835 my $mysync = shift ;
10836
10837 my @lines = @_ ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010838
10839 foreach my $line (@lines) {
10840 if ( $line =~ m{\[PERMANENTFLAGS\s\(([^)]+?)\)\]}x ) {
10841 ( $debugflags or $sync->{ debug } ) and myprint( "permanentflags: $line" ) ;
10842 my $permanentflags = $1 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010843 if ( $permanentflags =~ m{\\\*}x )
10844 {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010845 $permanentflags = q{} ;
10846 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010847 return( $permanentflags ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010848 } ;
10849 }
10850 return( q{} ) ;
10851}
10852
10853sub tests_flags_filter
10854{
10855 note( 'Entering tests_flags_filter()' ) ;
10856
10857 ok( '\Seen' eq flags_filter('\Seen', '\Draft \Seen \Answered'), 'flags_filter ' );
10858 ok( q{} eq flags_filter('\Seen', '\Draft \Answered'), 'flags_filter ' );
10859 ok( '\Seen' eq flags_filter('\Seen', '\Seen'), 'flags_filter ' );
10860 ok( '\Seen' eq flags_filter('\Seen', ' \Seen '), 'flags_filter ' );
10861 ok( '\Seen \Draft'
10862 eq flags_filter('\Seen \Draft', '\Draft \Seen \Answered'), 'flags_filter ' );
10863 ok( '\Seen \Draft'
10864 eq flags_filter('\Seen \Draft', ' \Draft \Seen \Answered '), 'flags_filter ' );
10865
10866 note( 'Leaving tests_flags_filter()' ) ;
10867 return ;
10868}
10869
10870sub flags_filter
10871{
10872 my( $flags, $allowed_flags ) = @_ ;
10873
10874 my @flags = split /\s+/x, $flags ;
10875 my %allowed_flags = map { $_ => 1 } split q{ }, $allowed_flags ;
10876 my @flags_out = map { exists $allowed_flags{$_} ? $_ : () } @flags ;
10877
10878 my $flags_out = join q{ }, @flags_out ;
10879
10880 return( $flags_out ) ;
10881}
10882
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010883sub tests_flagscase
10884{
10885 note( 'Entering tests_flagscase()' ) ;
10886
10887 ok( '\Seen' eq flagscase( '\Seen' ), 'flagscase: \Seen -> \Seen' ) ;
10888 ok( '\Seen' eq flagscase( '\SEEN' ), 'flagscase: \SEEN -> \Seen' ) ;
10889
10890 ok( '\Seen \Draft' eq flagscase( '\SEEN \DRAFT' ), 'flagscase: \SEEN \DRAFT -> \Seen \Draft' ) ;
10891 ok( '\Draft \Seen' eq flagscase( '\DRAFT \SEEN' ), 'flagscase: \DRAFT \SEEN -> \Draft \Seen' ) ;
10892
10893 ok( '\Draft LALA \Seen' eq flagscase( '\DRAFT LALA \SEEN' ), 'flagscase: \DRAFT LALA \SEEN -> \Draft LALA \Seen' ) ;
10894 ok( '\Draft lala \Seen' eq flagscase( '\DRAFT lala \SEEN' ), 'flagscase: \DRAFT lala \SEEN -> \Draft lala \Seen' ) ;
10895
10896 note( 'Leaving tests_flagscase()' ) ;
10897 return ;
10898}
10899
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010900sub flagscase
10901{
10902 my $flags = shift ;
10903
10904 my @flags = split /\s+/x, $flags ;
10905 my %rfc_flags = map { $_ => 1 } split q{ }, '\Answered \Flagged \Deleted \Seen \Draft' ;
10906 my @flags_out = map { exists $rfc_flags{ ucsecond( lc $_ ) } ? ucsecond( lc $_ ) : $_ } @flags ;
10907
10908 my $flags_out = join q{ }, @flags_out ;
10909
10910 return( $flags_out ) ;
10911}
10912
10913
10914
10915sub tests_flags_for_host2
10916{
10917 note( 'Entering tests_flags_for_host2()' ) ;
10918
10919 is( undef, flags_for_host2( ), 'flags_for_host2: no args => undef' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010920
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010921 my $mysync ;
10922 is( undef, flags_for_host2( $mysync ), 'flags_for_host2: undef => undef' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010923
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010924 $mysync = { } ;
10925 is( undef, flags_for_host2( $mysync ), 'flags_for_host2: nothing => undef' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010926
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010927 is( q{}, flags_for_host2( $mysync, '' ), 'flags_for_host2: no flags => empty string' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010928
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010929 is( q{}, flags_for_host2( $mysync, '\Recent' ), 'flags_for_host2: \Recent => empty string' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010930
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010931 is( q{\Seen}, flags_for_host2( $mysync, '\Recent \Seen' ), 'flags_for_host2: \Recent \Seen => \Seen' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010932
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010933 is( q{\Deleted \Seen}, flags_for_host2( $mysync, '\Deleted \Recent \Seen' ), 'flags_for_host2: \Deleted \Recent \Seen => \Deleted \Seen' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010934
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010935 $mysync->{ flagscase } = 0 ;
10936 is( q{\DELETED \Seen}, flags_for_host2( $mysync, '\DELETED \Seen' ), 'flags_for_host2: flagscase = 0 \DELETED \Seen => \DELETED \Seen' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010937
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010938 $mysync->{ flagscase } = 1 ;
10939 is( q{\Deleted \Seen}, flags_for_host2( $mysync, '\DELETED \Seen' ), 'flags_for_host2: flagscase = 1 \DELETED \Seen => \Deleted \Seen' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010940
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010941 $mysync->{ filterflags } = 0 ;
10942 is( q{\Seen \Blabla}, flags_for_host2( $mysync, '\Seen \Blabla', '\Seen \Junk' ), 'flags_for_host2: filterflags = 0 \Seen \Blabla among \Seen \Junk => \Seen \Blabla' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010943
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010944 $mysync->{ filterflags } = 1 ;
10945 is( q{\Seen}, flags_for_host2( $mysync, '\Seen \Blabla', '\Seen \Junk' ), 'flags_for_host2: filterflags = 1 \Seen \Blabla among \Seen \Junk => \Seen' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010946
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010947 $mysync->{ filterflags } = 1 ;
10948 is( q{\Seen \Blabla}, flags_for_host2( $mysync, '\Seen \Blabla', '' ), 'flags_for_host2: filterflags = 1 \Seen \Blabla among "" => \Seen \Blabla' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010949
10950
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010951 note( 'Leaving tests_flags_for_host2()' ) ;
10952 return ;
10953}
10954
10955
10956
10957
10958sub flags_for_host2
10959{
10960 my $mysync = shift ;
10961 my $h1_flags = shift ;
10962 my $permanentflags2 = shift ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010963
10964 if ( ! all_defined( $mysync, $h1_flags ) ) { return ; } ;
10965
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010966 # RFC 2060: This flag can not be altered by any client
10967 $h1_flags =~ s@\\Recent\s?@@xgi ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010968
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010969 my $h1_flags_re ;
10970 if ( $mysync->{ regexflag } and defined( $h1_flags_re = regexflags( $mysync, $h1_flags ) ) ) {
10971 $h1_flags = $h1_flags_re ;
10972 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010973
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010974 if ( $mysync->{ flagscase } )
10975 {
10976 $h1_flags = flagscase( $h1_flags ) ;
10977 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010978
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010979 if ( $permanentflags2 and $mysync->{ filterflags } )
10980 {
10981 $h1_flags = flags_filter( $h1_flags, $permanentflags2 ) ;
10982 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010983
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010984 return( $h1_flags ) ;
10985}
10986
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010987
10988
10989sub ucsecond
10990{
10991 my $string = shift ;
10992 my $output ;
10993
10994 return( $string ) if ( 1 >= length $string ) ;
10995
10996 $output = ( substr( $string, 0, 1) ) . ( uc substr $string, 1, 1 ) . ( substr $string, 2 ) ;
10997 #myprint( "UUU $string -> $output\n" ) ;
10998 return( $output ) ;
10999}
11000
11001
11002sub tests_ucsecond
11003{
11004 note( 'Entering tests_ucsecond()' ) ;
11005
11006 ok( 'aBcde' eq ucsecond( 'abcde' ), 'ucsecond: abcde -> aBcde' ) ;
11007 ok( 'ABCDE' eq ucsecond( 'ABCDE' ), 'ucsecond: ABCDE -> ABCDE' ) ;
11008 ok( 'ABCDE' eq ucsecond( 'AbCDE' ), 'ucsecond: AbCDE -> ABCDE' ) ;
11009 ok( 'ABCde' eq ucsecond( 'AbCde' ), 'ucsecond: AbCde -> ABCde' ) ;
11010 ok( 'A' eq ucsecond( 'A' ), 'ucsecond: A -> A' ) ;
11011 ok( 'AB' eq ucsecond( 'Ab' ), 'ucsecond: Ab -> AB' ) ;
11012 ok( '\B' eq ucsecond( '\b' ), 'ucsecond: \b -> \B' ) ;
11013 ok( '\Bcde' eq ucsecond( '\bcde' ), 'ucsecond: \bcde -> \Bcde' ) ;
11014
11015 note( 'Leaving tests_ucsecond()' ) ;
11016 return ;
11017}
11018
11019
11020sub select_msgs
11021{
11022 my ( $imap, $msgs_all_hash_ref, $search_cmd, $abletosearch, $folder ) = @_ ;
11023 my ( @msgs ) ;
11024
11025 if ( $abletosearch ) {
11026 @msgs = select_msgs_by_search( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) ;
11027 }else{
11028 @msgs = select_msgs_by_fetch( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) ;
11029 }
11030 return( @msgs ) ;
11031
11032}
11033
11034sub select_msgs_by_search
11035{
11036 my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ;
11037 my ( @msgs, @msgs_all ) ;
11038
11039 # Need to have the whole list in msgs_all_hash_ref
11040 # without calling messages() several times.
11041 # Need all messages list to avoid deleting useful cache part
11042 # in case of --search or --minage or --maxage
11043
11044 if ( ( defined $msgs_all_hash_ref and $usecache )
11045 or ( not defined $maxage and not defined $minage and not defined $search_cmd )
11046 ) {
11047
11048 $debugdev and myprint( "Calling messages()\n" ) ;
11049 @msgs_all = $imap->messages( ) ;
11050
11051 return if ( $#msgs_all == 0 && !defined $msgs_all[0] ) ;
11052
11053 if ( defined $msgs_all_hash_ref ) {
11054 @{ $msgs_all_hash_ref }{ @msgs_all } = () ;
11055 }
11056 # return all messages
11057 if ( not defined $maxage and not defined $minage and not defined $search_cmd ) {
11058 return( @msgs_all ) ;
11059 }
11060 }
11061
11062 if ( defined $search_cmd ) {
11063 @msgs = $imap->search( $search_cmd ) ;
11064 return( @msgs ) ;
11065 }
11066
11067 # we are here only if $maxage or $minage is defined
11068 @msgs = select_msgs_by_age( $imap ) ;
11069 return( @msgs );
11070}
11071
11072
11073sub select_msgs_by_fetch
11074{
11075 my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ;
11076 my ( @msgs, @msgs_all, %fetch ) ;
11077
11078 # Need to have the whole list in msgs_all_hash_ref
11079 # without calling messages() several times.
11080 # Need all messages list to avoid deleting useful cache part
11081 # in case of --search or --minage or --maxage
11082
11083
11084 $debugdev and myprint( "Calling fetch_hash()\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011085 my $fetch_hash_uids = $fetch_hash_set || "1:*" ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011086 %fetch = %{$imap->fetch_hash( $fetch_hash_uids, 'INTERNALDATE' ) } ;
11087
11088 @msgs_all = sort { $a <=> $b } keys %fetch ;
11089 $debugdev and myprint( "Done fetch_hash()\n" ) ;
11090
11091 return if ( $#msgs_all == 0 && !defined $msgs_all[0] ) ;
11092
11093 if ( defined $msgs_all_hash_ref ) {
11094 @{ $msgs_all_hash_ref }{ @msgs_all } = () ;
11095 }
11096 # return all messages
11097 if ( not defined $maxage and not defined $minage and not defined $search_cmd ) {
11098 return( @msgs_all ) ;
11099 }
11100
11101 if ( defined $search_cmd ) {
11102 myprint( "Warning: strange to see --search with --noabletosearch, an error can happen\n" ) ;
11103 @msgs = $imap->search( $search_cmd ) ;
11104 return( @msgs ) ;
11105 }
11106
11107 # we are here only if $maxage or $minage is defined
11108 my( @max, @min, $maxage_epoch, $minage_epoch ) ;
11109 if ( defined $maxage ) { $maxage_epoch = $timestart_int - $NB_SECONDS_IN_A_DAY * $maxage ; }
11110 if ( defined $minage ) { $minage_epoch = $timestart_int - $NB_SECONDS_IN_A_DAY * $minage ; }
11111 foreach my $msg ( @msgs_all ) {
11112 my $idate = $fetch{ $msg }->{'INTERNALDATE'} ;
11113 #myprint( "$idate\n" ) ;
11114 if ( defined $maxage and ( epoch( $idate ) >= $maxage_epoch ) ) {
11115 push @max, $msg ;
11116 }
11117 if ( defined $minage and ( epoch( $idate ) <= $minage_epoch ) ) {
11118 push @min, $msg ;
11119 }
11120 }
11121 @msgs = msgs_from_maxmin( \@max, \@min ) ;
11122 return( @msgs ) ;
11123}
11124
11125sub select_msgs_by_age
11126{
11127 my( $imap ) = @_ ;
11128
11129 my( @max, @min, @msgs, @inter, @union ) ;
11130
11131 if ( defined $maxage ) {
11132 @max = $imap->sentsince( $timestart_int - $NB_SECONDS_IN_A_DAY * $maxage ) ;
11133 }
11134 if ( defined $minage ) {
11135 @min = $imap->sentbefore( $timestart_int - $NB_SECONDS_IN_A_DAY * $minage ) ;
11136 }
11137
11138 @msgs = msgs_from_maxmin( \@max, \@min ) ;
11139 return( @msgs ) ;
11140}
11141
11142sub msgs_from_maxmin
11143{
11144 my( $max_ref, $min_ref ) = @_ ;
11145 my( @max, @min, @msgs, @inter, @union ) ;
11146
11147 @max = @{ $max_ref } ;
11148 @min = @{ $min_ref } ;
11149
11150 SWITCH: {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011151 if ( not ( defined $minage or defined $maxage ) )
11152 {
11153 return ;
11154 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011155 unless( defined $minage ) { @msgs = @max ; last SWITCH } ;
11156 unless( defined $maxage ) { @msgs = @min ; last SWITCH } ;
11157 my ( %union, %inter ) ;
11158 foreach my $m ( @min, @max ) { $union{ $m }++ && $inter{ $m }++ }
11159 @inter = sort { $a <=> $b } keys %inter ;
11160 @union = sort { $a <=> $b } keys %union ;
11161 # normal case
11162 if ( $minage <= $maxage ) { @msgs = @inter ; last SWITCH } ;
11163 # just exclude messages between
11164 if ( $minage > $maxage ) { @msgs = @union ; last SWITCH } ;
11165
11166 }
11167 return( @msgs ) ;
11168}
11169
11170sub tests_msgs_from_maxmin
11171{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011172 note( 'Entering tests_msgs_from_maxmin()' ) ;
11173
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011174
11175 my @msgs ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011176
11177 # no maxage nor minage
11178 @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
11179 is_deeply( [ ], \@msgs , 'msgs_from_maxmin: no maxage nor minage => empty result' ) ;
11180
11181 # maxage alone
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011182 $maxage = $NUMBER_200 ;
11183 @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011184 is_deeply( [ '1', '2' ], \@msgs , 'msgs_from_maxmin: maxage++' ) ;
11185
11186 # maxage > minage -> intersection
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011187 $minage = $NUMBER_100 ;
11188 @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011189 is_deeply( [ '2' ], \@msgs , 'msgs_from_maxmin: -maxage++minage-' ) ;
11190
11191 # maxage < minage -> union
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011192 $minage = $NUMBER_300 ;
11193 @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011194 is_deeply( [ '1', '2', '3' ], \@msgs, 'msgs_from_maxmin: ++maxage-minage++' ) ;
11195
11196
11197 # minage alone
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011198 $maxage = undef ;
11199 @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011200 is_deeply( [ '2', '3' ], \@msgs, 'msgs_from_maxmin: ++minage-' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011201
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011202
11203 note( 'Leaving tests_msgs_from_maxmin()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011204 return ;
11205}
11206
11207sub tests_info_date_from_uid
11208{
11209 note( 'Entering tests_info_date_from_uid()' ) ;
11210 note( 'Leaving tests_info_date_from_uid()' ) ;
11211
11212 return ;
11213}
11214
11215sub info_date_from_uid
11216{
11217
11218 #my $first_uid = $msgs_all[ 0 ] ;
11219 #my $first_idate = $fetch{ $first_uid }->{'INTERNALDATE'} ;
11220 #my $first_epoch = epoch( $first_idate ) ;
11221 #my $first_days = ( $timestart_int - $first_epoch ) / $NB_SECONDS_IN_A_DAY ;
11222 #myprint( "\nOldest msg has UID $first_uid INTERNALDATE $first_idate EPOCH $first_epoch DAYS AGO $first_days\n" ) ;
11223}
11224
11225
11226sub lastuid
11227{
11228 my $imap = shift ;
11229 my $folder = shift ;
11230 my $lastuid_guess = shift ;
11231 my $lastuid ;
11232
11233 # rfc3501: The only reliable way to identify recent messages is to
11234 # look at message flags to see which have the \Recent flag
11235 # set, or to do a SEARCH RECENT.
11236 # SEARCH RECENT doesn't work this way on courrier.
11237
11238 my @recent_messages ;
11239 # SEARCH RECENT for each transfer can be expensive with a big folder
11240 # Call commented for now
11241 #@recent_messages = $imap->recent( ) ;
11242 #myprint( "Recent: @recent_messages\n" ) ;
11243
11244 my $max_recent ;
11245 $max_recent = max( @recent_messages ) ;
11246
11247 if ( defined $max_recent and ($lastuid_guess <= $max_recent ) ) {
11248 $lastuid = $max_recent ;
11249 }else{
11250 $lastuid = $lastuid_guess
11251 }
11252 return( $lastuid ) ;
11253}
11254
11255sub size_filtered
11256{
11257 my( $h1_size, $h1_msg, $h1_fold, $h2_fold ) = @_ ;
11258
11259 $h1_size = 0 if ( ! $h1_size ) ; # null if empty or undef
11260 if ( defined $sync->{ maxsize } and $h1_size > $sync->{ maxsize } ) {
11261 myprint( "msg $h1_fold/$h1_msg skipped ($h1_size exceeds maxsize limit $sync->{ maxsize } bytes)\n" ) ;
11262 $sync->{ total_bytes_skipped } += $h1_size;
11263 $sync->{ nb_msg_skipped } += 1;
11264 return( 1 ) ;
11265 }
11266 if ( defined $minsize and $h1_size <= $minsize ) {
11267 myprint( "msg $h1_fold/$h1_msg skipped ($h1_size smaller than minsize $minsize bytes)\n" ) ;
11268 $sync->{ total_bytes_skipped } += $h1_size;
11269 $sync->{ nb_msg_skipped } += 1;
11270 return( 1 ) ;
11271 }
11272 return( 0 ) ;
11273}
11274
11275sub message_exists
11276{
11277 my( $imap, $msg ) = @_ ;
11278 return( 1 ) if not $imap->Uid( ) ;
11279
11280 my $search_uid ;
11281 ( $search_uid ) = $imap->search( "UID $msg" ) ;
11282 #myprint( "$search ? $msg\n" ) ;
11283 return( 1 ) if ( $search_uid eq $msg ) ;
11284 return( 0 ) ;
11285}
11286
11287
11288# Globals
11289# $sync->{ total_bytes_skipped }
11290# $sync->{ nb_msg_skipped }
11291# $mysync->{ h1_nb_msg_processed }
11292sub stats_update_skip_message
11293{
11294 my $mysync = shift ; # to be used
11295 my $h1_size = shift ;
11296
11297 $mysync->{ total_bytes_skipped } += $h1_size ;
11298 $mysync->{ nb_msg_skipped } += 1 ;
11299 $mysync->{ h1_nb_msg_processed } +=1 ;
11300 return ;
11301}
11302
11303sub copy_message
11304{
11305 # copy
11306
11307 my ( $mysync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) = @_ ;
11308 ( $mysync->{ debug } or $mysync->{dry} )
11309 and myprint( "msg $h1_fold/$h1_msg copying to $h2_fold $mysync->{dry_message} " . eta( $mysync ) . "\n" ) ;
11310
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011311 if ( $mysync->{dry1} )
11312 {
11313 $mysync->{ h1_nb_msg_processed } +=1 ;
11314 $nb_msg_skipped_dry_mode += 1 ;
11315 return ;
11316 }
11317
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011318 my $h1_size = $h1_fir_ref->{$h1_msg}->{'RFC822.SIZE'} || 0 ;
11319 my $h1_flags = $h1_fir_ref->{$h1_msg}->{'FLAGS'} || q{} ;
11320 my $h1_idate = $h1_fir_ref->{$h1_msg}->{'INTERNALDATE'} || q{} ;
11321
11322
11323 if ( size_filtered( $h1_size, $h1_msg, $h1_fold, $h2_fold ) ) {
11324 $mysync->{ h1_nb_msg_processed } +=1 ;
11325 return ;
11326 }
11327
11328 debugsleep( $mysync ) ;
11329 myprint( "- msg $h1_fold/$h1_msg S[$h1_size] F[$h1_flags] I[$h1_idate] has RFC822.SIZE null!\n" ) if ( ! $h1_size ) ;
11330
11331 if ( $checkmessageexists and not message_exists( $mysync->{imap1}, $h1_msg ) ) {
11332 stats_update_skip_message( $mysync, $h1_size ) ;
11333 return ;
11334 }
11335 myprint( debugmemory( $mysync, " at C1" ) ) ;
11336
11337 my ( $string, $string_len ) ;
11338 ( $string_len ) = message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, \$string ) ;
11339
11340 myprint( debugmemory( $mysync, " at C2" ) ) ;
11341
11342 # not defined or empty $string
11343 if ( ( not $string ) or ( not $string_len ) ) {
11344 myprint( "- msg $h1_fold/$h1_msg skipped.\n" ) ;
11345 stats_update_skip_message( $mysync, $h1_size ) ;
11346 return ;
11347 }
11348
11349 # Lines too long (or not enough) => do no copy or fix
11350 if ( ( defined $maxlinelength ) or ( defined $minmaxlinelength ) ) {
11351 $string = linelengthstuff( $string, $h1_fold, $h1_msg, $string_len, $h1_size, $h1_flags, $h1_idate ) ;
11352 if ( not defined $string ) {
11353 stats_update_skip_message( $mysync, $h1_size ) ;
11354 return ;
11355 }
11356 }
11357
11358 my $h1_date = date_for_host2( $h1_msg, $h1_idate ) ;
11359
11360 ( $mysync->{ debug } or $debugflags ) and
11361 myprint( "Host1: flags init msg $h1_fold/$h1_msg date [$h1_date] flags [$h1_flags] size [$h1_size]\n" ) ;
11362
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011363 $h1_flags = flags_for_host2( $mysync, $h1_flags, $permanentflags2 ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011364
11365 ( $mysync->{ debug } or $debugflags ) and
11366 myprint( "Host1: flags filt msg $h1_fold/$h1_msg date [$h1_date] flags [$h1_flags] size [$h1_size]\n" ) ;
11367
11368 $h1_date = undef if ( $h1_date eq q{} ) ;
11369
11370 my $new_id = append_message_on_host2( $mysync, \$string, $h1_fold, $h1_msg, $string_len, $h2_fold, $h1_size, $h1_flags, $h1_date, $cache_dir ) ;
11371
11372
11373
11374 if ( $new_id and $syncflagsaftercopy ) {
11375 sync_flags_after_copy( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $new_id, $permanentflags2 ) ;
11376 }
11377
11378 myprint( debugmemory( $mysync, " at C3" ) ) ;
11379
11380 return $new_id ;
11381}
11382
11383
11384
11385sub linelengthstuff
11386{
11387 my( $string, $h1_fold, $h1_msg, $string_len, $h1_size, $h1_flags, $h1_idate ) = @_ ;
11388 my $maxlinelength_string = max_line_length( $string ) ;
11389 $debugmaxlinelength and myprint( "msg $h1_fold/$h1_msg maxlinelength: $maxlinelength_string\n" ) ;
11390
11391 if ( ( defined $minmaxlinelength ) and ( $maxlinelength_string <= $minmaxlinelength ) ) {
11392 my $subject = subject( $string ) ;
11393 $debugdev and myprint( "- msg $h1_fold/$h1_msg skipped S[$h1_size] F[$h1_flags] I[$h1_idate] "
11394 . "(Subject:[$subject]) (max line length under minmaxlinelength $minmaxlinelength bytes)\n" ) ;
11395 return ;
11396 }
11397
11398 if ( ( defined $maxlinelength ) and ( $maxlinelength_string > $maxlinelength ) ) {
11399 my $subject = subject( $string ) ;
11400 if ( $maxlinelengthcmd ) {
11401 $string = pipemess( $string, $maxlinelengthcmd ) ;
11402 # string undef means something was bad.
11403 if ( not ( defined $string ) ) {
11404 myprint( "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate] "
11405 . "(Subject:[$subject]) could not be successfully transformed by --maxlinelengthcmd option\n" ) ;
11406 return ;
11407 }else{
11408 return $string ;
11409 }
11410 }
11411 myprint( "- msg $h1_fold/$h1_msg skipped S[$h1_size] F[$h1_flags] I[$h1_idate] "
11412 . "(Subject:[$subject]) (line length exceeds maxlinelength $maxlinelength bytes)\n" ) ;
11413 return ;
11414 }
11415 return $string ;
11416}
11417
11418
11419sub message_for_host2
11420{
11421
11422# global variable list:
11423# @skipmess
11424# @regexmess
11425# @pipemess
11426# $debugcontent
11427# $debug
11428#
11429# API current
11430#
11431# at failure:
11432# * return nothing ( will then be undef or () )
11433# * $string_ref content is undef or empty
11434# at success:
11435# * return string length ($string_ref content length)
11436# * $string_ref content filled with message
11437
11438# API future
11439#
11440#
11441 my ( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ) = @_ ;
11442
11443 # abort when missing a parameter
11444 if ( ( ! $mysync ) or ( ! $h1_msg ) or ( ! $h1_fold ) or ( ! defined $h1_size )
11445 or ( ! defined $h1_flags) or ( ! defined $h1_idate )
11446 or ( ! $h1_fir_ref) or ( ! $string_ref ) )
11447 {
11448 return ;
11449 }
11450
11451 myprint( debugmemory( $mysync, " at M1" ) ) ;
11452
11453
11454 my $string_ok = $mysync->{imap1}->message_to_file( $string_ref, $h1_msg ) ;
11455
11456 myprint( debugmemory( $mysync, " at M2" ) ) ;
11457
11458 my $string_len = length_ref( $string_ref ) ;
11459
11460
11461 unless ( defined $string_ok and $string_len ) {
11462 # undef or 0 length
11463 my $error = join q{},
11464 "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate] could not be fetched: ",
11465 $mysync->{imap1}->LastError || q{}, "\n" ;
11466 errors_incr( $mysync, $error ) ;
11467 $mysync->{ h1_nb_msg_processed } +=1 ;
11468 return ;
11469 }
11470
11471 if ( @skipmess ) {
11472 my $match = skipmess( ${ $string_ref } ) ;
11473 # string undef means the eval regex was bad.
11474 if ( not ( defined $match ) ) {
11475 myprint(
11476 "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
11477 . " could not be skipped by --skipmess option, bad regex\n" ) ;
11478 return ;
11479 }
11480 if ( $match ) {
11481 my $subject = subject( ${ $string_ref } ) ;
11482 myprint( "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
11483 . " (Subject:[$subject]) skipped by --skipmess\n" ) ;
11484 return ;
11485 }
11486 }
11487
11488 if ( @regexmess ) {
11489 ${ $string_ref } = regexmess( ${ $string_ref } ) ;
11490 # string undef means the eval regex was bad.
11491 if ( not ( defined ${ $string_ref } ) ) {
11492 myprint(
11493 "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
11494 . " could not be transformed by --regexmess\n" ) ;
11495 return ;
11496 }
11497 }
11498
11499 if ( @pipemess ) {
11500 ${ $string_ref } = pipemess( ${ $string_ref }, @pipemess ) ;
11501 # string undef means something was bad.
11502 if ( not ( defined ${ $string_ref } ) ) {
11503 myprint(
11504 "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
11505 . " could not be successfully transformed by --pipemess option\n" ) ;
11506 return ;
11507 }
11508 }
11509
11510 if ( $mysync->{addheader} and defined $h1_fir_ref->{$h1_msg}->{'NO_HEADER'} ) {
11511 my $header = add_header( $h1_msg ) ;
11512 $mysync->{ debug } and myprint( "msg $h1_fold/$h1_msg adding custom header [$header]\n" ) ;
11513 ${ $string_ref } = $header . "\r\n" . ${ $string_ref } ;
11514 }
11515
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011516 if ( ( defined $mysync->{ truncmess } ) and is_integer( $mysync->{ truncmess } ) )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011517 {
11518 ${ $string_ref } = truncmess( ${ $string_ref }, $mysync->{ truncmess } ) ;
11519 }
11520
11521 $string_len = length_ref( $string_ref ) ;
11522
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010011523 $mysync->{ debugcontent } and myprint( debugcontent( $mysync, $string_ref ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011524
11525 myprint( debugmemory( $mysync, " at M3" ) ) ;
11526
11527 return $string_len ;
11528}
11529
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010011530sub tests_debugcontent
11531{
11532 note( 'Entering tests_debugcontent()' ) ;
11533
11534 is( undef, debugcontent( ), 'debugcontent: no args => undef' ) ;
11535 my $mysync = { } ;
11536 is( undef, debugcontent( $mysync ), 'debugcontent: undef => undef' ) ;
11537 is( undef, debugcontent( $mysync, 'mm' ), 'debugcontent: undef, mm => undef' ) ;
11538 #my $string_ref = \'zztop' ;
11539 my $string = '================================================================================
11540F message content begin next line (2 characters long)
11541mm
11542F message content ended on previous line
11543================================================================================
11544' ;
11545 is( $string, debugcontent( $mysync, \'mm' ), 'debugcontent: undef, mm => mm' ) ;
11546
11547 note( 'Leaving tests_debugcontent()' ) ;
11548 return ;
11549}
11550
11551sub debugcontent
11552{
11553 my $mysync = shift @ARG ;
11554 if ( ! defined $mysync ) { return ; }
11555
11556 my $string_ref = shift @ARG ;
11557 if ( ! defined $string_ref ) { return ; }
11558 if ( 'SCALAR' ne ref( $string_ref ) ) { return ; }
11559
11560 my $string_len = length_ref( $string_ref ) ;
11561
11562 my $string = join( '',
11563 q{=} x $STD_CHAR_PER_LINE, "\n",
11564 "F message content begin next line ($string_len characters long)\n",
11565 ${ $string_ref },
11566 "\nF message content ended on previous line\n", q{=} x $STD_CHAR_PER_LINE, "\n",
11567 ) ;
11568
11569 return $string ;
11570}
11571
11572
11573
11574
11575
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011576sub tests_truncmess
11577{
11578 note( 'Entering tests_truncmess()' ) ;
11579
11580 is( undef, truncmess( ), 'truncmess: no args => undef' ) ;
11581 is( 'abc', truncmess( 'abc' ), 'truncmess: abc => abc' ) ;
11582 is( 'ab', truncmess( 'abc', 2 ), 'truncmess: abc 2 => ab' ) ;
11583 is( 'abc', truncmess( 'abc', 3 ), 'truncmess: abc 3 => abc' ) ;
11584 is( 'abc', truncmess( 'abc', 4 ), 'truncmess: abc 4 => abc' ) ;
11585 is( '12345', truncmess( "123456789\n", 5 ), 'truncmess: "123456789\n", 5 => 12345' ) ;
11586 is( "123456789\n" x 5000, truncmess( "123456789\n" x 100000, 50000 ), 'truncmess: "123456789\n" x 100000, 50000 => "123456789\n" x 5000' ) ;
11587 note( 'Leaving tests_truncmess()' ) ;
11588 return ;
11589}
11590
11591sub truncmess
11592{
11593 my $string = shift ;
11594 my $length = shift ;
11595
11596 if ( not defined $string ) { return ; }
11597 if ( not defined $length ) { return $string ; }
11598
11599 $string = substr $string, 0, $length ;
11600 return $string ;
11601}
11602
11603sub tests_message_for_host2
11604{
11605 note( 'Entering tests_message_for_host2()' ) ;
11606
11607
11608 my ( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ) ;
11609
11610 is( undef, message_for_host2( ), q{message_for_host2: no args} ) ;
11611 is( undef, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ), q{message_for_host2: undef args} ) ;
11612
11613 require_ok( "Test::MockObject" ) ;
11614 my $imapT = Test::MockObject->new( ) ;
11615 $mysync->{imap1} = $imapT ;
11616 my $string ;
11617
11618 $h1_msg = 1 ;
11619 $h1_fold = 'FoldFoo';
11620 $h1_size = 9 ;
11621 $h1_flags = q{} ;
11622 $h1_idate = '10-Jul-2015 09:00:00 +0200' ;
11623 $h1_fir_ref = {} ;
11624 $string_ref = \$string ;
11625 $imapT->mock( 'message_to_file',
11626 sub {
11627 my ( $imap, $mystring_ref, $msg ) = @_ ;
11628 ${$mystring_ref} = 'blablabla' ;
11629 return length ${$mystring_ref} ;
11630 }
11631 ) ;
11632 is( 9, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
11633 q{message_for_host2: msg 1 == "blablabla", length} ) ;
11634 is( 'blablabla', $string, q{message_for_host2: msg 1 == "blablabla", value} ) ;
11635
11636 # so far so good
11637 # now the --pipemess stuff
11638
11639 SKIP: {
11640 Readonly my $NB_WIN_tests_message_for_host2 => 0 ;
11641 skip( 'Not on MSWin32', $NB_WIN_tests_message_for_host2 ) if ('MSWin32' ne $OSNAME) ;
11642 # Windows
11643 # "type" command does not accept redirection of STDIN with <
11644 # "sort" does
11645
11646 } ;
11647
11648 SKIP: {
11649 Readonly my $NB_UNX_tests_message_for_host2 => 6 ;
11650 skip( 'Not on Unix', $NB_UNX_tests_message_for_host2 ) if ('MSWin32' eq $OSNAME) ;
11651 # Unix
11652
11653 # no change by cat
11654 @pipemess = ( 'cat' ) ;
11655 is( 9, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
11656 q{message_for_host2: --pipemess 'cat', length} ) ;
11657 is( 'blablabla', $string, q{message_for_host2: --pipemess 'cat', value} ) ;
11658
11659
11660 # failure by false
11661 @pipemess = ( 'false' ) ;
11662 is( undef, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
11663 q{message_for_host2: --pipemess 'false', length} ) ;
11664 is( undef, $string, q{message_for_host2: --pipemess 'false', value} ) ;
11665
11666 # failure by true since no output
11667 @pipemess = ( 'true' ) ;
11668 is( undef, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
11669 q{message_for_host2: --pipemess 'true', length} ) ;
11670 is( undef, $string, q{message_for_host2: --pipemess 'true', value} ) ;
11671 }
11672
11673 note( 'Leaving tests_message_for_host2()' ) ;
11674 return ;
11675}
11676
11677sub tests_labels_remove_subfolder1
11678{
11679 note( 'Entering tests_labels_remove_subfolder1()' ) ;
11680 is( undef, labels_remove_subfolder1( ), 'labels_remove_subfolder1: no parameters => undef' ) ;
11681 is( 'Blabla', labels_remove_subfolder1( 'Blabla' ), 'labels_remove_subfolder1: one parameter Blabla => Blabla' ) ;
11682 is( 'Blan blue', labels_remove_subfolder1( 'Blan blue' ), 'labels_remove_subfolder1: one parameter Blan blue => Blan blue' ) ;
11683 is( '\Bla "Blan blan" Blabla', labels_remove_subfolder1( '\Bla "Blan blan" Blabla' ),
11684 'labels_remove_subfolder1: one parameter \Bla "Blan blan" Blabla => \Bla "Blan blan" Blabla' ) ;
11685
11686 is( 'Bla', labels_remove_subfolder1( 'Subf/Bla', 'Subf' ), 'labels_remove_subfolder1: Subf/Bla Subf => "Bla"' ) ;
11687
11688
11689 is( '"\\\\Bla"', labels_remove_subfolder1( '"\\\\Bla"', 'Subf' ), 'labels_remove_subfolder1: "\\\\Bla" Subf => "\\\\Bla"' ) ;
11690
11691 is( 'Bla Kii', labels_remove_subfolder1( 'Subf/Bla Subf/Kii', 'Subf' ),
11692 'labels_remove_subfolder1: Subf/Bla Subf/Kii, Subf => "Bla" "Kii"' ) ;
11693
11694 is( '"\\\\Bla" Kii', labels_remove_subfolder1( '"\\\\Bla" Subf/Kii', 'Subf' ),
11695 'labels_remove_subfolder1: "\\\\Bla" Subf/Kii Subf => "\\\\Bla" Kii' ) ;
11696
11697 is( '"Blan blan"', labels_remove_subfolder1( '"Subf/Blan blan"', 'Subf' ),
11698 'labels_remove_subfolder1: "Subf/Blan blan" Subf => "Blan blan"' ) ;
11699
11700 is( '"\\\\Loo" "Blan blan" Kii', labels_remove_subfolder1( '"\\\\Loo" "Subf/Blan blan" Subf/Kii', 'Subf' ),
11701 'labels_remove_subfolder1: "\\\\Loo" "Subf/Blan blan" Subf/Kii + Subf => "\\\\Loo" "Blan blan" Kii' ) ;
11702
11703 is( '"\\\\Inbox"', labels_remove_subfolder1( 'Subf/INBOX', 'Subf' ),
11704 'labels_remove_subfolder1: Subf/INBOX + Subf => "\\\\Inbox"' ) ;
11705
11706 is( '"\\\\Loo" "Blan blan" Kii "\\\\Inbox"', labels_remove_subfolder1( '"\\\\Loo" "Subf/Blan blan" Subf/Kii Subf/INBOX', 'Subf' ),
11707 'labels_remove_subfolder1: "\\\\Loo" "Subf/Blan blan" Subf/Kii Subf/INBOX + Subf => "\\\\Loo" "Blan blan" Kii "\\\\Inbox"' ) ;
11708
11709
11710 note( 'Leaving tests_labels_remove_subfolder1()' ) ;
11711 return ;
11712}
11713
11714
11715
11716sub labels_remove_subfolder1
11717{
11718 my $labels = shift ;
11719 my $subfolder1 = shift ;
11720
11721 if ( not defined $labels ) { return ; }
11722 if ( not defined $subfolder1 ) { return $labels ; }
11723
11724 my @labels = quotewords('\s+', 1, $labels ) ;
11725 #myprint( "@labels\n" ) ;
11726 my @labels_subfolder2 ;
11727
11728 foreach my $label ( @labels )
11729 {
11730 if ( $label =~ m{zzzzzzzzzz} )
11731 {
11732 # \Seen \Deleted ... stay the same
11733 push @labels_subfolder2, $label ;
11734 }
11735 else
11736 {
11737 # Remove surrounding quotes if any, to add them again in case of space
11738 $label = join( q{}, quotewords('\s+', 0, $label ) ) ;
11739 $label =~ s{$subfolder1/?}{} ;
11740 if ( 'INBOX' eq $label )
11741 {
11742 push @labels_subfolder2, q{"\\\\Inbox"} ;
11743 }
11744 elsif ( $label =~ m{\\} )
11745 {
11746 push @labels_subfolder2, qq{"\\$label"} ;
11747 }
11748 elsif ( $label =~ m{ } )
11749 {
11750 push @labels_subfolder2, qq{"$label"} ;
11751 }
11752 else
11753 {
11754 push @labels_subfolder2, $label ;
11755 }
11756 }
11757 }
11758
11759 my $labels_subfolder2 = join( ' ', sort uniq( @labels_subfolder2 ) ) ;
11760
11761 return $labels_subfolder2 ;
11762}
11763
11764sub tests_labels_remove_special
11765{
11766 note( 'Entering tests_labels_remove_special()' ) ;
11767
11768 is( undef, labels_remove_special( ), 'labels_remove_special: no parameters => undef' ) ;
11769 is( q{}, labels_remove_special( q{} ), 'labels_remove_special: empty string => empty string' ) ;
11770 is( q{}, labels_remove_special( '"\\\\Inbox"' ), 'labels_remove_special:"\\\\Inbox" => empty string' ) ;
11771 is( q{}, labels_remove_special( '"\\\\Inbox" "\\\\Starred"' ), 'labels_remove_special:"\\\\Inbox" "\\\\Starred" => empty string' ) ;
11772 is( 'Bar Foo', labels_remove_special( 'Foo Bar' ), 'labels_remove_special:Foo Bar => Bar Foo' ) ;
11773 is( 'Bar Foo', labels_remove_special( 'Foo Bar "\\\\Inbox"' ), 'labels_remove_special:Foo Bar "\\\\Inbox" => Bar Foo' ) ;
11774 note( 'Leaving tests_labels_remove_special()' ) ;
11775 return ;
11776}
11777
11778
11779
11780
11781sub labels_remove_special
11782{
11783 my $labels = shift ;
11784
11785 if ( not defined $labels ) { return ; }
11786
11787 my @labels = quotewords('\s+', 1, $labels ) ;
11788 myprint( "labels before remove_non_folded: @labels\n" ) ;
11789 my @labels_remove_special ;
11790
11791 foreach my $label ( @labels )
11792 {
11793 if ( $label =~ m{^\"\\\\} )
11794 {
11795 # not kept
11796 }
11797 else
11798 {
11799 push @labels_remove_special, $label ;
11800 }
11801 }
11802
11803 my $labels_remove_special = join( ' ', sort @labels_remove_special ) ;
11804
11805 return $labels_remove_special ;
11806}
11807
11808
11809sub tests_labels_add_subfolder2
11810{
11811 note( 'Entering tests_labels_add_subfolder2()' ) ;
11812 is( undef, labels_add_subfolder2( ), 'labels_add_subfolder2: no parameters => undef' ) ;
11813 is( 'Blabla', labels_add_subfolder2( 'Blabla' ), 'labels_add_subfolder2: one parameter Blabla => Blabla' ) ;
11814 is( 'Blan blue', labels_add_subfolder2( 'Blan blue' ), 'labels_add_subfolder2: one parameter Blan blue => Blan blue' ) ;
11815 is( '\Bla "Blan blan" Blabla', labels_add_subfolder2( '\Bla "Blan blan" Blabla' ),
11816 'labels_add_subfolder2: one parameter \Bla "Blan blan" Blabla => \Bla "Blan blan" Blabla' ) ;
11817
11818 is( 'Subf/Bla', labels_add_subfolder2( 'Bla', 'Subf' ), 'labels_add_subfolder2: Bla Subf => "Subf/Bla"' ) ;
11819
11820
11821 is( 'Subf/\Bla', labels_add_subfolder2( '\\\\Bla', 'Subf' ), 'labels_add_subfolder2: \Bla Subf => \Bla' ) ;
11822
11823 is( 'Subf/Bla Subf/Kii', labels_add_subfolder2( 'Bla Kii', 'Subf' ),
11824 'labels_add_subfolder2: Bla Kii Subf => "Subf/Bla" "Subf/Kii"' ) ;
11825
11826 is( 'Subf/Kii Subf/\Bla', labels_add_subfolder2( '\\\\Bla Kii', 'Subf' ),
11827 'labels_add_subfolder2: \Bla Kii Subf => \Bla Subf/Kii' ) ;
11828
11829 is( '"Subf/Blan blan"', labels_add_subfolder2( '"Blan blan"', 'Subf' ),
11830 'labels_add_subfolder2: "Blan blan" Subf => "Subf/Blan blan"' ) ;
11831
11832 is( '"Subf/Blan blan" Subf/Kii Subf/\Loo', labels_add_subfolder2( '\\\\Loo "Blan blan" Kii', 'Subf' ),
11833 'labels_add_subfolder2: \Loo "Blan blan" Kii + Subf => "Subf/Blan blan" Subf/Kii Subf/\Loo' ) ;
11834
11835 # "\\Inbox" is special, add to subfolder INBOX also because Gmail will but ...
11836 is( '"Subf/\\\\Inbox" Subf/INBOX', labels_add_subfolder2( '"\\\\Inbox"', 'Subf' ),
11837 'labels_add_subfolder2: "\\\\Inbox" Subf => "Subf/\\\\Inbox" Subf/INBOX' ) ;
11838
11839 # but not with INBOX folder
11840 is( '"Subf/\\\\Inbox"', labels_add_subfolder2( '"\\\\Inbox"', 'Subf', 'INBOX' ),
11841 'labels_add_subfolder2: "\\\\Inbox" Subf INBOX => "Subf/\\\\Inbox"' ) ;
11842
11843 # two times => one time
11844 is( '"Subf/\\\\Inbox" Subf/INBOX', labels_add_subfolder2( '"\\\\Inbox" "\\\\Inbox"', 'Subf' ),
11845 'labels_add_subfolder2: "\\\\Inbox" "\\\\Inbox" Subf => "Subf/\\\\Inbox"' ) ;
11846
11847 is( '"Subf/\\\\Starred"', labels_add_subfolder2( '"\\\\Starred"', 'Subf' ),
11848 'labels_add_subfolder2: "\\\\Starred" Subf => "Subf/\\\\Starred"' ) ;
11849
11850 note( 'Leaving tests_labels_add_subfolder2()' ) ;
11851 return ;
11852}
11853
11854sub labels_add_subfolder2
11855{
11856 my $labels = shift ;
11857 my $subfolder2 = shift ;
11858 my $h1_folder = shift || q{} ;
11859
11860 if ( not defined $labels ) { return ; }
11861 if ( not defined $subfolder2 ) { return $labels ; }
11862
11863 # Isn't it messy?
11864 if ( 'INBOX' eq $h1_folder )
11865 {
11866 $labels .= ' "\\\\Inbox"' ;
11867 }
11868
11869 my @labels = uniq( quotewords('\s+', 1, $labels ) ) ;
11870 myprint( "labels before subfolder2: @labels\n" ) ;
11871 my @labels_subfolder2 ;
11872
11873
11874 foreach my $label ( @labels )
11875 {
11876 # Isn't it more messy?
11877 if ( ( q{"\\\\Inbox"} eq $label ) and ( 'INBOX' ne $h1_folder ) )
11878 {
11879 if ( $subfolder2 =~ m{ } )
11880 {
11881 push @labels_subfolder2, qq{"$subfolder2/INBOX"} ;
11882 }
11883 else
11884 {
11885 push @labels_subfolder2, "$subfolder2/INBOX" ;
11886 }
11887 }
11888 if ( $label =~ m{^\"\\\\} )
11889 {
11890 # \Seen \Deleted ... stay the same
11891 #push @labels_subfolder2, $label ;
11892 # Remove surrounding quotes if any, to add them again
11893 $label = join( q{}, quotewords('\s+', 0, $label ) ) ;
11894 push @labels_subfolder2, qq{"$subfolder2/\\$label"} ;
11895
11896 }
11897 else
11898 {
11899 # Remove surrounding quotes if any, to add them again in case of space
11900 $label = join( q{}, quotewords('\s+', 0, $label ) ) ;
11901 if ( $label =~ m{ } )
11902 {
11903 push @labels_subfolder2, qq{"$subfolder2/$label"} ;
11904 }
11905 else
11906 {
11907 push @labels_subfolder2, "$subfolder2/$label" ;
11908 }
11909 }
11910 }
11911
11912 my $labels_subfolder2 = join( ' ', sort @labels_subfolder2 ) ;
11913
11914 return $labels_subfolder2 ;
11915}
11916
11917sub tests_labels
11918{
11919 note( 'Entering tests_labels()' ) ;
11920
11921 is( undef, labels( ), 'labels: no parameters => undef' ) ;
11922 is( undef, labels( undef ), 'labels: undef => undef' ) ;
11923 require_ok( "Test::MockObject" ) ;
11924 my $myimap = Test::MockObject->new( ) ;
11925
11926 $myimap->mock( 'fetch_hash',
11927 sub {
11928 return(
11929 { '1' => {
11930 'X-GM-LABELS' => '\Seen Blabla'
11931 }
11932 }
11933 ) ;
11934 }
11935 ) ;
11936 $myimap->mock( 'Debug' , sub { } ) ;
11937 $myimap->mock( 'Unescape', sub { return Mail::IMAPClient::Unescape( @_ ) } ) ; # real one
11938
11939 is( undef, labels( $myimap ), 'labels: one parameter => undef' ) ;
11940 is( '\Seen Blabla', labels( $myimap, '1' ), 'labels: $mysync UID_1 => \Seen Blabla' ) ;
11941
11942 note( 'Leaving tests_labels()' ) ;
11943 return ;
11944}
11945
11946sub labels
11947{
11948 my ( $myimap, $uid ) = @ARG ;
11949
11950 if ( not all_defined( $myimap, $uid ) ) {
11951 return ;
11952 }
11953
11954 my $hash = $myimap->fetch_hash( [ $uid ], 'X-GM-LABELS' ) ;
11955
11956 my $labels = $hash->{ $uid }->{ 'X-GM-LABELS' } ;
11957 #$labels = $myimap->Unescape( $labels ) ;
11958 return $labels ;
11959}
11960
11961sub tests_synclabels
11962{
11963 note( 'Entering tests_synclabels()' ) ;
11964
11965 is( undef, synclabels( ), 'synclabels: no parameters => undef' ) ;
11966 is( undef, synclabels( undef ), 'synclabels: undef => undef' ) ;
11967 my $mysync ;
11968 is( undef, synclabels( $mysync ), 'synclabels: var undef => undef' ) ;
11969
11970 require_ok( "Test::MockObject" ) ;
11971 $mysync = {} ;
11972
11973 my $myimap1 = Test::MockObject->new( ) ;
11974 $myimap1->mock( 'fetch_hash',
11975 sub {
11976 return(
11977 { '1' => {
11978 'X-GM-LABELS' => '\Seen Blabla'
11979 }
11980 }
11981 ) ;
11982 }
11983 ) ;
11984 $myimap1->mock( 'Debug', sub { } ) ;
11985 $myimap1->mock( 'Unescape', sub { return Mail::IMAPClient::Unescape( @_ ) } ) ; # real one
11986
11987 my $myimap2 = Test::MockObject->new( ) ;
11988
11989 $myimap2->mock( 'store',
11990 sub {
11991 return 1 ;
11992 }
11993 ) ;
11994
11995
11996 $mysync->{imap1} = $myimap1 ;
11997 $mysync->{imap2} = $myimap2 ;
11998
11999 is( undef, synclabels( $mysync ), 'synclabels: fresh $mysync => undef' ) ;
12000
12001 is( undef, synclabels( $mysync, '1' ), 'synclabels: $mysync UID_1 alone => undef' ) ;
12002 is( 1, synclabels( $mysync, '1', '2' ), 'synclabels: $mysync UID_1 UID_2 => 1' ) ;
12003
12004 note( 'Leaving tests_synclabels()' ) ;
12005 return ;
12006}
12007
12008
12009sub synclabels
12010{
12011 my( $mysync, $uid1, $uid2 ) = @ARG ;
12012
12013 if ( not all_defined( $mysync, $uid1, $uid2 ) ) {
12014 return ;
12015 }
12016 my $myimap1 = $mysync->{ 'imap1' } || return ;
12017 my $myimap2 = $mysync->{ 'imap2' } || return ;
12018
12019 $mysync->{debuglabels} and $myimap1->Debug( 1 ) ;
12020 my $labels1 = labels( $myimap1, $uid1 ) ;
12021 $mysync->{debuglabels} and $myimap1->Debug( 0 ) ;
12022 $mysync->{debuglabels} and myprint( "Host1 labels: $labels1\n" ) ;
12023
12024
12025
12026 if ( $mysync->{ subfolder1 } and $labels1 )
12027 {
12028 $labels1 = labels_remove_subfolder1( $labels1, $mysync->{ subfolder1 } ) ;
12029 $mysync->{debuglabels} and myprint( "Host1 labels with subfolder1: $labels1\n" ) ;
12030 }
12031
12032 if ( $mysync->{ subfolder2 } and $labels1 )
12033 {
12034 $labels1 = labels_add_subfolder2( $labels1, $mysync->{ subfolder2 } ) ;
12035 $mysync->{debuglabels} and myprint( "Host1 labels with subfolder2: $labels1\n" ) ;
12036 }
12037
12038 my $store ;
12039 if ( $labels1 and not $mysync->{ dry } )
12040 {
12041 $mysync->{ debuglabels } and $myimap2->Debug( 1 ) ;
12042 $store = $myimap2->store( $uid2, "X-GM-LABELS ($labels1)" ) ;
12043 $mysync->{ debuglabels } and $myimap2->Debug( 0 ) ;
12044 }
12045 return $store ;
12046}
12047
12048
12049sub tests_resynclabels
12050{
12051 note( 'Entering tests_resynclabels()' ) ;
12052
12053 is( undef, resynclabels( ), 'resynclabels: no parameters => undef' ) ;
12054 is( undef, resynclabels( undef ), 'resynclabels: undef => undef' ) ;
12055 my $mysync ;
12056 is( undef, resynclabels( $mysync ), 'resynclabels: var undef => undef' ) ;
12057
12058 my ( $h1_fir_ref, $h2_fir_ref ) ;
12059
12060 $mysync->{ debuglabels } = 1 ;
12061 $h1_fir_ref->{ 11 }->{ 'X-GM-LABELS' } = '\Seen Baa Kii' ;
12062 $h2_fir_ref->{ 22 }->{ 'X-GM-LABELS' } = '\Seen Baa Kii' ;
12063
12064 # labels are equal
12065 is( 1, resynclabels( $mysync, 11, 22, $h1_fir_ref, $h2_fir_ref ),
12066 'resynclabels: $mysync UID_1 UID_2 labels are equal => 1' ) ;
12067
12068 # labels are different
12069 $h2_fir_ref->{ 22 }->{ 'X-GM-LABELS' } = '\Seen Zuu' ;
12070 require_ok( "Test::MockObject" ) ;
12071 my $myimap2 = Test::MockObject->new( ) ;
12072 $myimap2->mock( 'store',
12073 sub {
12074 return 1 ;
12075 }
12076 ) ;
12077 $myimap2->mock( 'Debug', sub { } ) ;
12078 $mysync->{imap2} = $myimap2 ;
12079
12080 is( 1, resynclabels( $mysync, 11, 22, $h1_fir_ref, $h2_fir_ref ),
12081 'resynclabels: $mysync UID_1 UID_2 labels are not equal => store => 1' ) ;
12082
12083 note( 'Leaving tests_resynclabels()' ) ;
12084 return ;
12085}
12086
12087
12088
12089sub resynclabels
12090{
12091 my( $mysync, $uid1, $uid2, $h1_fir_ref, $h2_fir_ref, $h1_folder ) = @ARG ;
12092
12093 if ( not all_defined( $mysync, $uid1, $uid2, $h1_fir_ref, $h2_fir_ref ) ) {
12094 return ;
12095 }
12096
12097 my $labels1 = $h1_fir_ref->{ $uid1 }->{ 'X-GM-LABELS' } || q{} ;
12098 my $labels2 = $h2_fir_ref->{ $uid2 }->{ 'X-GM-LABELS' } || q{} ;
12099
12100 if ( $mysync->{ subfolder1 } and $labels1 )
12101 {
12102 $labels1 = labels_remove_subfolder1( $labels1, $mysync->{ subfolder1 } ) ;
12103 }
12104
12105 if ( $mysync->{ subfolder2 } and $labels1 )
12106 {
12107 $labels1 = labels_add_subfolder2( $labels1, $mysync->{ subfolder2 }, $h1_folder ) ;
12108 $labels2 = labels_remove_special( $labels2 ) ;
12109 }
12110 $mysync->{ debuglabels } and myprint( "Host1 labels fixed: $labels1\n" ) ;
12111 $mysync->{ debuglabels } and myprint( "Host2 labels : $labels2\n" ) ;
12112
12113 my $store ;
12114 if ( $labels1 eq $labels2 )
12115 {
12116 # no sync needed
12117 $mysync->{ debuglabels } and myprint( "Labels are already equal\n" ) ;
12118 return 1 ;
12119 }
12120 elsif ( not $mysync->{ dry } )
12121 {
12122 # sync needed
12123 $mysync->{debuglabels} and $mysync->{imap2}->Debug( 1 ) ;
12124 $store = $mysync->{imap2}->store( $uid2, "X-GM-LABELS ($labels1)" ) ;
12125 $mysync->{debuglabels} and $mysync->{imap2}->Debug( 0 ) ;
12126 }
12127
12128 return $store ;
12129}
12130
12131sub tests_uniq
12132{
12133 note( 'Entering tests_uniq()' ) ;
12134
12135 is( 0, uniq( ), 'uniq: undef => 0' ) ;
12136 is_deeply( [ 'one' ], [ uniq( 'one' ) ], 'uniq: one => one' ) ;
12137 is_deeply( [ 'one' ], [ uniq( 'one', 'one' ) ], 'uniq: one one => one' ) ;
12138 is_deeply( [ 'one', 'two' ], [ uniq( 'one', 'one', 'two', 'one', 'two' ) ], 'uniq: one one two one two => one two' ) ;
12139 note( 'Leaving tests_uniq()' ) ;
12140 return ;
12141}
12142
12143sub uniq
12144{
12145 my @list = @ARG ;
12146 my %seen = ( ) ;
12147 my @uniq = ( ) ;
12148 foreach my $item ( @list ) {
12149 if ( ! $seen{ $item } ) {
12150 $seen{ $item } = 1 ;
12151 push( @uniq, $item ) ;
12152 }
12153 }
12154 return @uniq ;
12155}
12156
12157
12158sub length_ref
12159{
12160 my $string_ref = shift ;
12161 my $string_len = defined ${ $string_ref } ? length( ${ $string_ref } ) : q{} ; # length or empty string
12162 return $string_len ;
12163}
12164
12165sub tests_length_ref
12166{
12167 note( 'Entering tests_length_ref()' ) ;
12168
12169 my $notdefined ;
12170 is( q{}, length_ref( \$notdefined ), q{length_ref: value not defined} ) ;
12171 my $notref ;
12172 is( q{}, length_ref( $notref ), q{length_ref: param not a ref} ) ;
12173
12174 my $lala = 'lala' ;
12175 is( 4, length_ref( \$lala ), q{length_ref: lala length == 4} ) ;
12176 is( 4, length_ref( \'lili' ), q{length_ref: lili length == 4} ) ;
12177
12178 note( 'Leaving tests_length_ref()' ) ;
12179 return ;
12180}
12181
12182sub date_for_host2
12183{
12184 my( $h1_msg, $h1_idate ) = @_ ;
12185
12186 my $h1_date = q{} ;
12187
12188 if ( $syncinternaldates ) {
12189 $h1_date = $h1_idate ;
12190 $sync->{ debug } and myprint( "internal date from host1: [$h1_date]\n" ) ;
12191 $h1_date = good_date( $h1_date ) ;
12192 $sync->{ debug } and myprint( "internal date from host1: [$h1_date] (fixed)\n" ) ;
12193 }
12194
12195 if ( $idatefromheader ) {
12196 $h1_date = $sync->{imap1}->get_header( $h1_msg, 'Date' ) ;
12197 $sync->{ debug } and myprint( "header date from host1: [$h1_date]\n" ) ;
12198 $h1_date = good_date( $h1_date ) ;
12199 $sync->{ debug } and myprint( "header date from host1: [$h1_date] (fixed)\n" ) ;
12200 }
12201
12202 return( $h1_date ) ;
12203}
12204
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012205
12206sub subject
12207{
12208 my $string = shift ;
12209 my $subject = q{} ;
12210
12211 my $header = extract_header( $string ) ;
12212
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012213 if( $header =~ m/^Subject:[ \t]*([^\n\r]*)\r?$/msx ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012214 #myprint( "MMM[$1]\n" ) ;
12215 $subject = $1 ;
12216 }
12217 return( $subject ) ;
12218}
12219
12220sub tests_subject
12221{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012222 note( 'Entering tests_subject()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012223
12224 ok( q{} eq subject( q{} ), 'subject: null') ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012225 is( '', subject( 'Subject:' ), 'Subject:') ;
12226 is( '', subject( "Subject:\r\n" ), 'Subject:\r\n') ;
12227 ok( 'toto le hero' eq subject( 'Subject: toto le hero' ), 'Subject: toto le hero') ;
12228 ok( 'toto le hero' eq subject( 'Subject:toto le hero' ), 'Subject:toto le hero') ;
12229 ok( 'toto le hero' eq subject( "Subject:toto le hero\r\n" ), 'Subject: toto le hero\r\n') ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012230
12231 my $MESS ;
12232 $MESS = <<'EOF';
12233From: lalala
12234Subject: toto le hero
12235Date: zzzzzz
12236
12237Boogie boogie
12238EOF
12239 ok( 'toto le hero' eq subject( $MESS ), 'subject: toto le hero 2') ;
12240
12241 $MESS = <<'EOF';
12242Subject: toto le hero
12243From: lalala
12244Date: zzzzzz
12245
12246Boogie boogie
12247EOF
12248 ok( 'toto le hero' eq subject( $MESS ), 'subject: toto le hero 3') ;
12249
12250
12251 $MESS = <<'EOF';
12252From: lalala
12253Subject: cuicui
12254Date: zzzzzz
12255
12256Subject: toto le hero
12257EOF
12258 ok( 'cuicui' eq subject( $MESS ), 'subject: cuicui') ;
12259
12260 $MESS = <<'EOF';
12261From: lalala
12262Date: zzzzzz
12263
12264Subject: toto le hero
12265EOF
12266 ok( q{} eq subject( $MESS ), 'subject: null but body could') ;
12267
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012268
12269 $MESS = <<'EOF';
12270From: lalala
12271Subject:
12272Date: zzzzzz
12273
12274Subject: toto le hero
12275EOF
12276 is( '', subject( $MESS ), 'Subject:') ;
12277
12278
12279
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012280 note( 'Leaving tests_subject()' ) ;
12281 return ;
12282}
12283
12284
12285# GlobVar
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012286# $h2_uidguess
12287# ...
12288#
12289#
12290sub append_message_on_host2
12291{
12292 my( $mysync, $string_ref, $h1_fold, $h1_msg, $string_len, $h2_fold, $h1_size, $h1_flags, $h1_date, $cache_dir ) = @_ ;
12293 myprint( debugmemory( $mysync, " at A1" ) ) ;
12294
12295 my $new_id ;
12296 if ( ! $mysync->{dry} ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012297 $new_id = $mysync->{imap2}->append_string( $h2_fold, ${ $string_ref }, $h1_flags, $h1_date ) ;
12298 myprint( debugmemory( $mysync, " at A2" ) ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012299 if ( ! defined $new_id ){
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012300 my $subject = subject( ${ $string_ref } ) ;
12301 my $error_imap = $mysync->{imap2}->LastError || q{} ;
12302 my $error = "- msg $h1_fold/$h1_msg {$string_len} could not append ( Subject:[$subject], Date:[$h1_date], Size:[$h1_size], Flags:[$h1_flags] ) to folder $h2_fold: $error_imap\n" ;
12303 errors_incr( $mysync, $error ) ;
12304 $mysync->{ h1_nb_msg_processed } +=1 ;
12305 return ;
12306 }
12307 else{
12308 # good
12309 # $new_id is an id if the IMAP server has the
12310 # UIDPLUS capability else just a ref
12311 if ( $new_id !~ m{^\d+$}x ) {
12312 $new_id = lastuid( $mysync->{imap2}, $h2_fold, $h2_uidguess ) ;
12313 }
12314 if ( $mysync->{ synclabels } ) { synclabels( $mysync, $h1_msg, $new_id ) }
12315 $h2_uidguess += 1 ;
12316 $mysync->{ total_bytes_transferred } += $string_len ;
12317 $mysync->{ nb_msg_transferred } += 1 ;
12318 $mysync->{ h1_nb_msg_processed } +=1 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012319 $mysync->{ biggest_message_transferred } = max( $string_len, $mysync->{ biggest_message_transferred } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012320
12321 my $time_spent = timesince( $mysync->{begin_transfer_time} ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010012322 my $rate = bytes_display_string_bin( $mysync->{total_bytes_transferred} / $time_spent ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012323 my $eta = eta( $mysync ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010012324 my $amount_transferred = bytes_display_string_bin( $mysync->{total_bytes_transferred} ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012325 myprintf( "msg %s/%-19s copied to %s/%-10s %.2f msgs/s %s/s %s copied %s\n",
12326 $h1_fold, "$h1_msg {$string_len}", $h2_fold, $new_id, $mysync->{nb_msg_transferred}/$time_spent, $rate,
12327 $amount_transferred,
12328 $eta );
12329 sleep_if_needed( $mysync ) ;
12330 if ( $usecache and $cacheaftercopy and $new_id =~ m{^\d+$}x ) {
12331 $debugcache and myprint( "touch $cache_dir/${h1_msg}_$new_id\n" ) ;
12332 touch( "$cache_dir/${h1_msg}_$new_id" )
12333 or croak( "Couldn't touch $cache_dir/${h1_msg}_$new_id" ) ;
12334 }
12335 if ( $mysync->{ delete1 } ) {
12336 delete_message_on_host1( $mysync, $h1_fold, $mysync->{ expungeaftereach }, $h1_msg ) ;
12337 }
12338 #myprint( "PRESS ENTER" ) and my $a = <> ;
12339
12340 return( $new_id ) ;
12341 }
12342 }
12343 else{
12344 $nb_msg_skipped_dry_mode += 1 ;
12345 $mysync->{ h1_nb_msg_processed } += 1 ;
12346 }
12347
12348 return ;
12349}
12350
12351
12352sub tests_sleep_if_needed
12353{
12354 note( 'Entering tests_sleep_if_needed()' ) ;
12355
12356 is( undef, sleep_if_needed( ), 'sleep_if_needed: no args => undef' ) ;
12357 my $mysync ;
12358 is( undef, sleep_if_needed( $mysync ), 'sleep_if_needed: arg undef => undef' ) ;
12359
12360 $mysync->{maxbytespersecond} = 1000 ;
12361 is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: maxbytespersecond only => no sleep => 0' ) ;
12362 $mysync->{begin_transfer_time} = time ; # now
12363 is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: begin_transfer_time now => no sleep => 0' ) ;
12364 $mysync->{begin_transfer_time} = time - 2 ; # 2 s before
12365 is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 0 => no sleep => 0' ) ;
12366
12367 $mysync->{total_bytes_transferred} = 2200 ;
12368 $mysync->{begin_transfer_time} = time - 2 ; # 2 s before
12369 is( '0.20', sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 2200 since 2s => sleep 0.2s' ) ;
12370 is( '0', sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 2200 since 2+2 == 4s => no sleep' ) ;
12371
12372 $mysync->{maxsleep} = 0.1 ;
12373 $mysync->{begin_transfer_time} = time - 2 ; # 2 s before again
12374 is( '0.10', sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 4000 since 2s but maxsleep 0.1s => sleep 0.1s' ) ;
12375
12376 $mysync->{maxbytesafter} = 4000 ;
12377 $mysync->{begin_transfer_time} = time - 2 ; # 2 s before again
12378 is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: maxbytesafter == total_bytes_transferred => no sleep => 0' ) ;
12379
12380 note( 'Leaving tests_sleep_if_needed()' ) ;
12381 return ;
12382}
12383
12384
12385sub sleep_if_needed
12386{
12387 my( $mysync ) = shift ;
12388
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012389 if ( ! $mysync ) {
12390 return ;
12391 }
12392 # No need to go further if there is no limit set
12393 if (
12394 not (
12395 $mysync->{maxmessagespersecond}
12396 or $mysync->{maxbytespersecond}
12397 )
12398 ) {
12399 return ;
12400 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012401
12402 $mysync->{maxsleep} = defined $mysync->{maxsleep} ? $mysync->{maxsleep} : $MAX_SLEEP ;
12403 # Must be positive
12404 $mysync->{maxsleep} = max( 0, $mysync->{maxsleep} ) ;
12405
12406 my $time_spent = timesince( $mysync->{begin_transfer_time} ) ;
12407 my $sleep_max_messages = sleep_max_messages( $mysync->{nb_msg_transferred}, $time_spent, $mysync->{maxmessagespersecond} ) ;
12408
12409 my $maxbytesafter = $mysync->{maxbytesafter} || 0 ;
12410 my $total_bytes_transferred = $mysync->{total_bytes_transferred} || 0 ;
12411 my $total_bytes_to_consider = $total_bytes_transferred - $maxbytesafter ;
12412
12413 #myprint( "maxbytesafter:$maxbytesafter\n" ) ;
12414 #myprint( "total_bytes_to_consider:$total_bytes_to_consider\n" ) ;
12415
12416 my $sleep_max_bytes = sleep_max_bytes( $total_bytes_to_consider, $time_spent, $mysync->{maxbytespersecond} ) ;
12417 my $sleep_max = min( $mysync->{maxsleep}, max( $sleep_max_messages, $sleep_max_bytes ) ) ;
12418 $sleep_max = mysprintf( "%.2f", $sleep_max ) ; # round with 2 decimals.
12419 if ( $sleep_max > 0 ) {
12420 myprint( "sleeping $sleep_max s\n" ) ;
12421 sleep $sleep_max ;
12422 # Slept
12423 return $sleep_max ;
12424 }
12425 # No sleep
12426 return 0 ;
12427}
12428
12429sub sleep_max_messages
12430{
12431 # how long we have to sleep to go under max_messages_per_second
12432 my( $nb_msg_transferred, $time_spent, $maxmessagespersecond ) = @_ ;
12433 if ( ( not defined $maxmessagespersecond ) or $maxmessagespersecond <= 0 ) { return( 0 ) } ;
12434 my $sleep = ( $nb_msg_transferred / $maxmessagespersecond ) - $time_spent ;
12435 # the sleep must be positive
12436 return( max( 0, $sleep ) ) ;
12437}
12438
12439
12440sub tests_sleep_max_messages
12441{
12442 note( 'Entering tests_sleep_max_messages()' ) ;
12443
12444 ok( 0 == sleep_max_messages( 4, 2, undef ), 'sleep_max_messages: maxmessagespersecond = undef') ;
12445 ok( 0 == sleep_max_messages( 4, 2, 0 ), 'sleep_max_messages: maxmessagespersecond = 0') ;
12446 ok( 0 == sleep_max_messages( 4, 2, $MINUS_ONE ), 'sleep_max_messages: maxmessagespersecond = -1') ;
12447 ok( 0 == sleep_max_messages( 4, 2, 2 ), 'sleep_max_messages: maxmessagespersecond = 2 max reached') ;
12448 ok( 2 == sleep_max_messages( 8, 2, 2 ), 'sleep_max_messages: maxmessagespersecond = 2 max over') ;
12449 ok( 0 == sleep_max_messages( 2, 2, 2 ), 'sleep_max_messages: maxmessagespersecond = 2 max not reached') ;
12450
12451 note( 'Leaving tests_sleep_max_messages()' ) ;
12452 return ;
12453}
12454
12455
12456sub sleep_max_bytes
12457{
12458 # how long we have to sleep to go under max_bytes_per_second
12459 my( $total_bytes_to_consider, $time_spent, $maxbytespersecond ) = @_ ;
12460 $total_bytes_to_consider ||= 0 ;
12461 $time_spent ||= 0 ;
12462
12463 if ( ( not defined $maxbytespersecond ) or $maxbytespersecond <= 0 ) { return( 0 ) } ;
12464 #myprint( "total_bytes_to_consider:$total_bytes_to_consider\n" ) ;
12465 my $sleep = ( $total_bytes_to_consider / $maxbytespersecond ) - $time_spent ;
12466 # the sleep must be positive
12467 return( max( 0, $sleep ) ) ;
12468}
12469
12470
12471sub tests_sleep_max_bytes
12472{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012473 note( 'Entering tests_sleep_max_bytes()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012474
12475 ok( 0 == sleep_max_bytes( 4000, 2, undef ), 'sleep_max_bytes: maxbytespersecond == undef => sleep 0' ) ;
12476 ok( 0 == sleep_max_bytes( 4000, 2, 0 ), 'sleep_max_bytes: maxbytespersecond = 0 => sleep 0') ;
12477 ok( 0 == sleep_max_bytes( 4000, 2, $MINUS_ONE ), 'sleep_max_bytes: maxbytespersecond = -1 => sleep 0') ;
12478 ok( 0 == sleep_max_bytes( 4000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max reached sharp => sleep 0') ;
12479 ok( 2 == sleep_max_bytes( 8000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max over => sleep a little') ;
12480 ok( 0 == sleep_max_bytes( -8000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max not reached => sleep 0') ;
12481 ok( 0 == sleep_max_bytes( 2000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max not reached => sleep 0') ;
12482 ok( 0 == sleep_max_bytes( -2000, 2, 1000 ), 'sleep_max_bytes: maxbytespersecond = 1k max not reached => sleep 0') ;
12483
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012484 note( 'Leaving tests_sleep_max_bytes()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012485 return ;
12486}
12487
12488
12489sub delete_message_on_host1
12490{
12491 my( $mysync, $h1_fold, $expunge, @h1_msg ) = @_ ;
12492 if ( ! $mysync->{ delete1 } ) { return ; }
12493 if ( ! @h1_msg ) { return ; }
12494 delete_messages_on_any(
12495 $mysync,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012496 $mysync->{ acc1 },
12497 $mysync->{ imap1 },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012498 "Host1: $h1_fold",
12499 $expunge,
12500 $split1,
12501 @h1_msg ) ;
12502 return ;
12503}
12504
12505sub tests_operators_and_exclam_precedence
12506{
12507 note( 'Entering tests_operators_and_exclam_precedence()' ) ;
12508
12509 is( 1, ! 0, 'tests_operators_and_exclam_precedence: ! 0 => 1' ) ;
12510 is( "", ! 1, 'tests_operators_and_exclam_precedence: ! 1 => ""' ) ;
12511 is( 1, not( 0 ), 'tests_operators_and_exclam_precedence: not( 0 ) => 1' ) ;
12512 is( "", not( 1 ), 'tests_operators_and_exclam_precedence: not( 1 ) => ""' ) ;
12513
12514 # I wrote those tests to avoid perlcrit "Mixed high and low-precedence booleans"
12515 # and change sub delete_messages_on_any() but got 4 more warnings... So now commented.
12516
12517 #is( 0, ( ! 0 and 0 ), 'tests_operators_and_exclam_precedence: ! 0 and 0 ) => 0' ) ;
12518 #is( 1, ( ! 0 and 1 ), 'tests_operators_and_exclam_precedence: ! 0 and 1 ) => 1' ) ;
12519 #is( "", ( ! 1 and 0 ), 'tests_operators_and_exclam_precedence: ! 1 and 0 ) => ""' ) ;
12520 #is( "", ( ! 1 and 1 ), 'tests_operators_and_exclam_precedence: ! 1 and 1 ) => ""' ) ;
12521
12522 is( 0, ( ! 0 && 0 ), 'tests_operators_and_exclam_precedence: ! 0 && 0 ) => 0' ) ;
12523 is( 1, ( ! 0 && 1 ), 'tests_operators_and_exclam_precedence: ! 0 && 1 ) => 1' ) ;
12524 is( "", ( ! 1 && 0 ), 'tests_operators_and_exclam_precedence: ! 1 && 0 ) => ""' ) ;
12525 is( "", ( ! 1 && 1 ), 'tests_operators_and_exclam_precedence: ! 1 && 1 ) => ""' ) ;
12526
12527 is( 2, ( ! 0 && 2 ), 'tests_operators_and_exclam_precedence: ! 0 && 2 ) => 1' ) ;
12528
12529 note( 'Leaving tests_operators_and_exclam_precedence()' ) ;
12530 return ;
12531}
12532
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012533
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012534sub delete_messages_on_any
12535{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012536 # $acc is not used yet,
12537 #
12538 my( $mysync, $acc, $imap, $hostX_folder, $expunge, $split, @messages ) = @_ ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012539 my $expunge_message = q{} ;
12540
12541 my $dry_message = $mysync->{ dry_message } ;
12542 $expunge_message = 'and expunged' if ( $expunge ) ;
12543 # "Host1: msg "
12544
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012545 # $imap->Debug( 1 ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012546
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012547 my @messages_to_mark_deleted = @messages ;
12548 while ( my @messages_part = splice @messages_to_mark_deleted, 0, $split )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012549 {
12550 foreach my $message ( @messages_part )
12551 {
12552 myprint( "$hostX_folder/$message marking deleted $expunge_message $dry_message\n" ) ;
12553 }
12554 if ( ! $mysync->{dry} && @messages_part )
12555 {
12556 my $nb_deleted = $imap->delete_message( $imap->Range( @messages_part ) ) ;
12557 if ( defined $nb_deleted )
12558 {
12559 # $nb_deleted is not accurate
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012560 $acc->{ nb_msg_deleted } += scalar @messages_part ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012561 }
12562 else
12563 {
12564 my $error_imap = $imap->LastError || q{} ;
12565 my $error = join( q{}, "$hostX_folder folder, could not delete ",
12566 scalar @messages_part, ' messages: ', $error_imap, "\n" ) ;
12567 errors_incr( $mysync, $error ) ;
12568 }
12569 }
12570 }
12571
12572 if ( $expunge ) {
12573 uidexpunge_or_expunge( $mysync, $imap, @messages ) ;
12574 }
12575
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012576 #$imap->Debug( 0 ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012577
12578 return ;
12579}
12580
12581
12582sub tests_uidexpunge_or_expunge
12583{
12584 note( 'Entering tests_uidexpunge_or_expunge()' ) ;
12585
12586
12587 is( undef, uidexpunge_or_expunge( ), 'uidexpunge_or_expunge: no args => undef' ) ;
12588 my $mysync ;
12589 is( undef, uidexpunge_or_expunge( $mysync ), 'uidexpunge_or_expunge: undef args => undef' ) ;
12590 $mysync = {} ;
12591 is( undef, uidexpunge_or_expunge( $mysync ), 'uidexpunge_or_expunge: arg empty => undef' ) ;
12592 my $imap ;
12593 is( undef, uidexpunge_or_expunge( $mysync, $imap ), 'uidexpunge_or_expunge: undef Mail-IMAPClient instance => undef' ) ;
12594
12595 require_ok( "Test::MockObject" ) ;
12596 $imap = Test::MockObject->new( ) ;
12597 is( undef, uidexpunge_or_expunge( $mysync, $imap ), 'uidexpunge_or_expunge: no message (1) to uidexpunge => undef' ) ;
12598
12599 my @messages = ( ) ;
12600 is( undef, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: no message (2) to uidexpunge => undef' ) ;
12601
12602 @messages = ( '2', '1' ) ;
12603 $imap->mock( 'uidexpunge', sub { return ; } ) ;
12604 $imap->mock( 'expunge', sub { return ; } ) ;
12605 is( undef, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: uidexpunge failure => expunge failure => undef' ) ;
12606
12607 $imap->mock( 'expunge', sub { return 1 ; } ) ;
12608 is( 1, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: uidexpunge failure => expunge ok => 1' ) ;
12609
12610 $imap->mock( 'uidexpunge', sub { return 1 ; } ) ;
12611 is( 1, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: messages to uidexpunge ok => 1' ) ;
12612
12613 note( 'Leaving tests_uidexpunge_or_expunge()' ) ;
12614 return ;
12615}
12616
12617sub uidexpunge_or_expunge
12618{
12619 my $mysync = shift ;
12620 my $imap = shift ;
12621 my @messages = @ARG ;
12622
12623 if ( ! $imap ) { return ; } ;
12624 if ( ! @messages ) { return ; } ;
12625
12626 # Doing uidexpunge
12627 my @uidexpunge_result = $imap->uidexpunge( @messages ) ;
12628 if ( @uidexpunge_result ) {
12629 return 1 ;
12630 }
12631 # Failure so doing expunge
12632 my $expunge_result = $imap->expunge( ) ;
12633 if ( $expunge_result ) {
12634 return 1 ;
12635 }
12636 # bad trip
12637 return ;
12638}
12639
12640sub eta_print
12641{
12642 my $mysync = shift ;
12643 if ( my $eta = eta( $mysync ) )
12644 {
12645 myprint( "$eta\n" ) ;
12646 }
12647 return ;
12648}
12649
12650sub tests_eta
12651{
12652 note( 'Entering tests_eta()' ) ;
12653
12654 is( q{}, eta( ), 'eta: no args => ""' ) ;
12655 is( q{}, eta( undef ), 'eta: undef => ""' ) ;
12656 my $mysync = {} ;
12657 # No foldersizes
12658 is( q{}, eta( $mysync ), 'eta: No foldersizes => ""' ) ;
12659
12660 $mysync->{ foldersizes } = 1 ;
12661
12662 $mysync->{ begin_transfer_time } = time ; # Now
12663 $mysync->{ h1_nb_msg_processed } = 0 ;
12664
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012665 is( "ETA: " . localtimez( time ) . " 0 s 0/0 msgs left",
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012666 eta( $mysync ),
12667 'eta: no args => ETA: "Now" 0 s 0/0 msgs left' ) ;
12668
12669 $mysync->{ h1_nb_msg_processed } = 1 ;
12670 $mysync->{ h1_nb_msg_start } = 2 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012671 is( "ETA: " . localtimez( time ) . " 0 s 1/2 msgs left",
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012672 eta( $mysync ),
12673 'eta: 1, 1, 2 => ETA: "Now" 0 s 1/2 msgs left' ) ;
12674
12675 note( 'Leaving tests_eta()' ) ;
12676 return ;
12677}
12678
12679
12680sub eta
12681{
12682 my( $mysync ) = shift ;
12683
12684 if ( ! $mysync )
12685 {
12686 return q{} ;
12687 }
12688
12689 return( q{} ) if not $mysync->{ foldersizes } ;
12690
12691 my $h1_nb_msg_start = $mysync->{ h1_nb_msg_start } ;
12692 my $h1_nb_processed = $mysync->{ h1_nb_msg_processed } ;
12693 my $nb_msg_transferred = ( $mysync->{dry} ) ? $mysync->{ h1_nb_msg_processed } : $mysync->{ nb_msg_transferred } ;
12694 my $time_spent = timesince( $mysync->{ begin_transfer_time } ) ;
12695 $h1_nb_processed ||= 0 ;
12696 $h1_nb_msg_start ||= 0 ;
12697 $time_spent ||= 0 ;
12698
12699 my $time_remaining = time_remaining( $time_spent, $h1_nb_processed, $h1_nb_msg_start, $nb_msg_transferred ) ;
12700 $mysync->{ debug } and myprint( "time_spent: $time_spent time_remaining: $time_remaining\n" ) ;
12701 my $nb_msg_remaining = $h1_nb_msg_start - $h1_nb_processed ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012702 my $eta_date = localtimez( time + $time_remaining ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012703 return( mysprintf( 'ETA: %s %1.0f s %s/%s msgs left',
12704 $eta_date, $time_remaining, $nb_msg_remaining, $h1_nb_msg_start ) ) ;
12705}
12706
12707
12708
12709
12710sub time_remaining
12711{
12712
12713 my( $my_time_spent, $h1_nb_processed, $h1_nb_msg_start, $nb_transferred ) = @_ ;
12714
12715 $nb_transferred ||= 1 ; # At least one is done (no division by zero)
12716 $h1_nb_processed ||= 0 ;
12717 $h1_nb_msg_start ||= $h1_nb_processed ;
12718 $my_time_spent ||= 0 ;
12719
12720 my $time_remaining = ( $my_time_spent / $nb_transferred ) * ( $h1_nb_msg_start - $h1_nb_processed ) ;
12721 return( $time_remaining ) ;
12722}
12723
12724
12725sub tests_time_remaining
12726{
12727 note( 'Entering tests_time_remaining()' ) ;
12728
12729 # time_spent, nb_processed, nb_to_do_total, nb_transferred
12730 is( 0, time_remaining( ), 'time_remaining: no args -> 0' ) ;
12731 is( 0, time_remaining( 0, 0, 0, 0 ), 'time_remaining: 0, 0, 0, 0 -> 0' ) ;
12732 is( 1, time_remaining( 1, 1, 2, 1 ), 'time_remaining: 1, 1, 2, 1 -> 1' ) ;
12733 is( 1, time_remaining( 9, 9, 10, 9 ), 'time_remaining: 9, 9, 10, 9 -> 1' ) ;
12734 is( 9, time_remaining( 1, 1, 10, 1 ), 'time_remaining: 1, 1, 10, 1 -> 9' ) ;
12735 is( 5, time_remaining( 5, 5, 10, 5 ), 'time_remaining: 5, 5, 10, 5 -> 5' ) ;
12736 is( 25, time_remaining( 5, 5, 10, 0 ), 'time_remaining: 5, 5, 10, 0 -> ( 5 / 1 ) * ( 10 - 5) = 25' ) ;
12737 is( 25, time_remaining( 5, 5, 10, 1 ), 'time_remaining: 5, 5, 10, 1 -> ( 5 / 1 ) * ( 10 - 5) = 25' ) ;
12738
12739 note( 'Leaving tests_time_remaining()' ) ;
12740 return ;
12741}
12742
12743
12744sub cache_map
12745{
12746 my ( $cache_files_ref, $h1_msgs_ref, $h2_msgs_ref ) = @_;
12747 my ( %map1_2, %map2_1, %done2 ) ;
12748
12749 my $h1_msgs_hash_ref = { } ;
12750 my $h2_msgs_hash_ref = { } ;
12751
12752 @{ $h1_msgs_hash_ref }{ @{ $h1_msgs_ref } } = ( ) ;
12753 @{ $h2_msgs_hash_ref }{ @{ $h2_msgs_ref } } = ( ) ;
12754
12755 foreach my $file ( sort @{ $cache_files_ref } ) {
12756 $debugcache and myprint( "C12: $file\n" ) ;
12757 ( $uid1, $uid2 ) = match_a_cache_file( $file ) ;
12758
12759 if ( exists( $h1_msgs_hash_ref->{ defined $uid1 ? $uid1 : q{} } )
12760 and exists( $h2_msgs_hash_ref->{ defined $uid2 ? $uid2 : q{} } ) ) {
12761 # keep only the greatest uid2
12762 # 130_2301 and
12763 # 130_231 => keep only 130 -> 2301
12764
12765 # keep only the greatest uid1
12766 # 1601_260 and
12767 # 161_260 => keep only 1601 -> 260
12768 my $max_uid2 = max( $uid2, $map1_2{ $uid1 } || $MINUS_ONE ) ;
12769 if ( exists $done2{ $max_uid2 } ) {
12770 if ( $done2{ $max_uid2 } < $uid1 ) {
12771 $map1_2{ $uid1 } = $max_uid2 ;
12772 delete $map1_2{ $done2{ $max_uid2 } } ;
12773 $done2{ $max_uid2 } = $uid1 ;
12774 }
12775 }else{
12776 $map1_2{ $uid1 } = $max_uid2 ;
12777 $done2{ $max_uid2 } = $uid1 ;
12778 }
12779 };
12780
12781 }
12782 %map2_1 = reverse %map1_2 ;
12783 return( \%map1_2, \%map2_1) ;
12784}
12785
12786sub tests_cache_map
12787{
12788 note( 'Entering tests_cache_map()' ) ;
12789
12790 #$debugcache = 1 ;
12791 my @cache_files = qw (
12792 100_200
12793 101_201
12794 120_220
12795 142_242
12796 143_243
12797 177_277
12798 177_278
12799 177_279
12800 155_255
12801 180_280
12802 181_280
12803 182_280
12804 130_231
12805 130_2301
12806 161_260
12807 1601_260
12808 ) ;
12809
12810 my $msgs_1 = [120, 142, 143, 144, 161, 1601, 177, 182, 130 ];
12811 my $msgs_2 = [ 242, 243, 260, 299, 377, 279, 255, 280, 231, 2301 ];
12812
12813 my( $c12, $c21 ) ;
12814 ok( ( $c12, $c21 ) = cache_map( \@cache_files, $msgs_1, $msgs_2 ), 'cache_map: 02' );
12815 my $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
12816 my $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
12817 ok( 0 == compare_lists( [ 130, 142, 143, 177, 182, 1601 ], $a1 ), 'cache_map: 03' );
12818 ok( 0 == compare_lists( [ 242, 243, 260, 279, 280, 2301 ], $a2 ), 'cache_map: 04' );
12819 ok( ! $c12->{161}, 'cache_map: ! 161 -> 260' );
12820 ok( 260 == $c12->{1601}, 'cache_map: 1601 -> 260' );
12821 ok( 2301 == $c12->{130}, 'cache_map: 130 -> 2301' );
12822 #myprint( $c12->{1601}, "\n" ) ;
12823
12824 note( 'Leaving tests_cache_map()' ) ;
12825 return ;
12826
12827}
12828
12829sub cache_dir_fix
12830{
12831 my $cache_dir = shift ;
12832 $cache_dir =~ s/([;<>\*\|`&\$!#\(\)\[\]\{\}:'"\\])/\\$1/xg ;
12833 #myprint( "cache_dir_fix: $cache_dir\n" ) ;
12834 return( $cache_dir ) ;
12835}
12836
12837sub tests_cache_dir_fix
12838{
12839 note( 'Entering tests_cache_dir_fix()' ) ;
12840
12841 ok( 'lalala' eq cache_dir_fix('lalala'), 'cache_dir_fix: lalala -> lalala' );
12842 ok( 'ii\\\\ii' eq cache_dir_fix('ii\ii'), 'cache_dir_fix: ii\ii -> ii\\\\ii' );
12843 ok( 'ii@ii' eq cache_dir_fix('ii@ii'), 'cache_dir_fix: ii@ii -> ii@ii' );
12844 ok( 'ii@ii\\:ii' eq cache_dir_fix('ii@ii:ii'), 'cache_dir_fix: ii@ii:ii -> ii@ii\\:ii' );
12845 ok( 'i\\\\i\\\\ii' eq cache_dir_fix('i\i\ii'), 'cache_dir_fix: i\i\ii -> i\\\\i\\\\ii' );
12846 ok( 'i\\\\ii' eq cache_dir_fix('i\\ii'), 'cache_dir_fix: i\\ii -> i\\\\\\\\ii' );
12847 ok( '\\\\ ' eq cache_dir_fix('\\ '), 'cache_dir_fix: \\ -> \\\\\ ' );
12848 ok( '\\\\ ' eq cache_dir_fix('\ '), 'cache_dir_fix: \ -> \\\\\ ' );
12849 ok( '\[bracket\]' eq cache_dir_fix('[bracket]'), 'cache_dir_fix: [bracket] -> \[bracket\]' );
12850
12851 note( 'Leaving tests_cache_dir_fix()' ) ;
12852 return ;
12853}
12854
12855sub cache_dir_fix_win
12856{
12857 my $cache_dir = shift ;
12858 $cache_dir =~ s/(\[|\])/[$1]/xg ;
12859 #myprint( "cache_dir_fix_win: $cache_dir\n" ) ;
12860 return( $cache_dir ) ;
12861}
12862
12863sub tests_cache_dir_fix_win
12864{
12865 note( 'Entering tests_cache_dir_fix_win()' ) ;
12866
12867 ok( 'lalala' eq cache_dir_fix_win('lalala'), 'cache_dir_fix_win: lalala -> lalala' );
12868 ok( '[[]bracket[]]' eq cache_dir_fix_win('[bracket]'), 'cache_dir_fix_win: [bracket] -> [[]bracket[]]' );
12869
12870 note( 'Leaving tests_cache_dir_fix_win()' ) ;
12871 return ;
12872}
12873
12874
12875
12876
12877sub get_cache
12878{
12879 my ( $cache_dir, $h1_msgs_ref, $h2_msgs_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) = @_;
12880
12881 $debugcache and myprint( "Entering get_cache\n" ) ;
12882
12883 -d $cache_dir or return( undef ); # exit if cache directory doesn't exist
12884 $debugcache and myprint( "cache_dir : $cache_dir\n" ) ;
12885
12886
12887 if ( 'MSWin32' ne $OSNAME ) {
12888 $cache_dir = cache_dir_fix( $cache_dir ) ;
12889 }else{
12890 $cache_dir = cache_dir_fix_win( $cache_dir ) ;
12891 }
12892
12893 $debugcache and myprint( "cache_dir_fix: $cache_dir\n" ) ;
12894
12895 my @cache_files = bsd_glob( "$cache_dir/*" ) ;
12896 #$debugcache and myprint( "cache_files: [@cache_files]\n" ) ;
12897
12898 $debugcache and myprint( 'cache_files: ', scalar @cache_files , " files found\n" ) ;
12899
12900 my( $cache_1_2_ref, $cache_2_1_ref )
12901 = cache_map( \@cache_files, $h1_msgs_ref, $h2_msgs_ref ) ;
12902
12903 clean_cache( \@cache_files, $cache_1_2_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) ;
12904
12905 $debugcache and myprint( "Exiting get_cache\n" ) ;
12906 return( $cache_1_2_ref, $cache_2_1_ref ) ;
12907}
12908
12909
12910sub tests_get_cache
12911{
12912 note( 'Entering tests_get_cache()' ) ;
12913
12914 ok( not( get_cache('/cache_no_exist') ), 'get_cache: /cache_no_exist' );
12915 ok( ( not -d 'W/tmp/cache/F1/F2' or rmtree( 'W/tmp/cache/F1/F2' ) ), 'get_cache: rmtree W/tmp/cache/F1/F2' ) ;
12916 ok( mkpath( 'W/tmp/cache/F1/F2' ), 'get_cache: mkpath W/tmp/cache/F1/F2' ) ;
12917
12918 my @test_files_cache = ( qw(
12919 W/tmp/cache/F1/F2/100_200
12920 W/tmp/cache/F1/F2/101_201
12921 W/tmp/cache/F1/F2/120_220
12922 W/tmp/cache/F1/F2/142_242
12923 W/tmp/cache/F1/F2/143_243
12924 W/tmp/cache/F1/F2/177_277
12925 W/tmp/cache/F1/F2/177_377
12926 W/tmp/cache/F1/F2/177_777
12927 W/tmp/cache/F1/F2/155_255
12928 ) ) ;
12929 ok( touch( @test_files_cache ), 'get_cache: touch W/tmp/cache/F1/F2/...' ) ;
12930
12931
12932 # on cache: 100_200 101_201 142_242 143_243 177_277 177_377 177_777 155_255
12933 # on live:
12934 my $msgs_1 = [120, 142, 143, 144, 177 ];
12935 my $msgs_2 = [ 242, 243, 299, 377, 777, 255 ];
12936
12937 my $msgs_all_1 = { 120 => 0, 142 => 0, 143 => 0, 144 => 0, 177 => 0 } ;
12938 my $msgs_all_2 = { 242 => 0, 243 => 0, 299 => 0, 377 => 0, 777 => 0, 255 => 0 } ;
12939
12940 my( $c12, $c21 ) ;
12941 ok( ( $c12, $c21 ) = get_cache( 'W/tmp/cache/F1/F2', $msgs_1, $msgs_2, $msgs_all_1, $msgs_all_2 ), 'get_cache: 02' );
12942 my $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
12943 my $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
12944 ok( 0 == compare_lists( [ 142, 143, 177 ], $a1 ), 'get_cache: 03' );
12945 ok( 0 == compare_lists( [ 242, 243, 777 ], $a2 ), 'get_cache: 04' );
12946 ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 142_242');
12947 ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 143_243');
12948 ok( ! -f 'W/tmp/cache/F1/F2/100_200', 'get_cache: file removed 100_200');
12949 ok( ! -f 'W/tmp/cache/F1/F2/101_201', 'get_cache: file removed 101_201');
12950
12951 # test clean_cache executed
12952 $maxage = 2 ;
12953 ok( touch(@test_files_cache), 'get_cache: touch W/tmp/cache/F1/F2/...' ) ;
12954 ok( ( $c12, $c21 ) = get_cache('W/tmp/cache/F1/F2', $msgs_1, $msgs_2, $msgs_all_1, $msgs_all_2 ), 'get_cache: 02' );
12955 ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 142_242');
12956 ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 143_243');
12957 ok( ! -f 'W/tmp/cache/F1/F2/100_200', 'get_cache: file NOT removed 100_200');
12958 ok( ! -f 'W/tmp/cache/F1/F2/101_201', 'get_cache: file NOT removed 101_201');
12959
12960
12961 # strange files
12962 #$debugcache = 1 ;
12963 $maxage = undef ;
12964 ok( ( not -d 'W/tmp/cache/rr\uee' or rmtree( 'W/tmp/cache/rr\uee' )), 'get_cache: rmtree W/tmp/cache/rr\uee' ) ;
12965 ok( mkpath( 'W/tmp/cache/rr\uee' ), 'get_cache: mkpath W/tmp/cache/rr\uee' ) ;
12966
12967 @test_files_cache = ( qw(
12968 W/tmp/cache/rr\uee/100_200
12969 W/tmp/cache/rr\uee/101_201
12970 W/tmp/cache/rr\uee/120_220
12971 W/tmp/cache/rr\uee/142_242
12972 W/tmp/cache/rr\uee/143_243
12973 W/tmp/cache/rr\uee/177_277
12974 W/tmp/cache/rr\uee/177_377
12975 W/tmp/cache/rr\uee/177_777
12976 W/tmp/cache/rr\uee/155_255
12977 ) ) ;
12978 ok( touch(@test_files_cache), 'get_cache: touch strange W/tmp/cache/...' ) ;
12979
12980 # on cache: 100_200 101_201 142_242 143_243 177_277 177_377 177_777 155_255
12981 # on live:
12982 $msgs_1 = [120, 142, 143, 144, 177 ] ;
12983 $msgs_2 = [ 242, 243, 299, 377, 777, 255 ] ;
12984
12985 $msgs_all_1 = { 120 => q{}, 142 => q{}, 143 => q{}, 144 => q{}, 177 => q{} } ;
12986 $msgs_all_2 = { 242 => q{}, 243 => q{}, 299 => q{}, 377 => q{}, 777 => q{}, 255 => q{} } ;
12987
12988 ok( ( $c12, $c21 ) = get_cache('W/tmp/cache/rr\uee', $msgs_1, $msgs_2, $msgs_all_1, $msgs_all_2), 'get_cache: strange path 02' );
12989 $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
12990 $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
12991 ok( 0 == compare_lists( [ 142, 143, 177 ], $a1 ), 'get_cache: strange path 03' );
12992 ok( 0 == compare_lists( [ 242, 243, 777 ], $a2 ), 'get_cache: strange path 04' );
12993 ok( -f 'W/tmp/cache/rr\uee/142_242', 'get_cache: strange path file kept 142_242');
12994 ok( -f 'W/tmp/cache/rr\uee/142_242', 'get_cache: strange path file kept 143_243');
12995 ok( ! -f 'W/tmp/cache/rr\uee/100_200', 'get_cache: strange path file removed 100_200');
12996 ok( ! -f 'W/tmp/cache/rr\uee/101_201', 'get_cache: strange path file removed 101_201');
12997
12998 note( 'Leaving tests_get_cache()' ) ;
12999 return ;
13000}
13001
13002sub match_a_cache_file
13003{
13004 my $file = shift ;
13005 my ( $cache_uid1, $cache_uid2 ) ;
13006
13007 return( ( undef, undef ) ) if ( ! $file ) ;
13008 if ( $file =~ m{(?:^|/)(\d+)_(\d+)$}x ) {
13009 $cache_uid1 = $1 ;
13010 $cache_uid2 = $2 ;
13011 }
13012 return( $cache_uid1, $cache_uid2 ) ;
13013}
13014
13015sub tests_match_a_cache_file
13016{
13017 note( 'Entering tests_match_a_cache_file()' ) ;
13018
13019 my ( $tuid1, $tuid2 ) ;
13020 ok( ( $tuid1, $tuid2 ) = match_a_cache_file( ), 'match_a_cache_file: no arg' ) ;
13021 ok( ! defined $tuid1 , 'match_a_cache_file: no arg 1' ) ;
13022 ok( ! defined $tuid2 , 'match_a_cache_file: no arg 2' ) ;
13023
13024 ok( ( $tuid1, $tuid2 ) = match_a_cache_file( q{} ), 'match_a_cache_file: empty arg' ) ;
13025 ok( ! defined $tuid1 , 'match_a_cache_file: empty arg 1' ) ;
13026 ok( ! defined $tuid2 , 'match_a_cache_file: empty arg 2' ) ;
13027
13028 ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '000_000' ), 'match_a_cache_file: 000_000' ) ;
13029 ok( '000' eq $tuid1, 'match_a_cache_file: 000_000 1' ) ;
13030 ok( '000' eq $tuid2, 'match_a_cache_file: 000_000 2' ) ;
13031
13032 ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '123_456' ), 'match_a_cache_file: 123_456' ) ;
13033 ok( '123' eq $tuid1, 'match_a_cache_file: 123_456 1' ) ;
13034 ok( '456' eq $tuid2, 'match_a_cache_file: 123_456 2' ) ;
13035
13036 ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '/tmp/truc/123_456' ), 'match_a_cache_file: /tmp/truc/123_456' ) ;
13037 ok( '123' eq $tuid1, 'match_a_cache_file: /tmp/truc/123_456 1' ) ;
13038 ok( '456' eq $tuid2, 'match_a_cache_file: /tmp/truc/123_456 2' ) ;
13039
13040 ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '/lala123_456' ), 'match_a_cache_file: NO /lala123_456' ) ;
13041 ok( ! $tuid1, 'match_a_cache_file: /lala123_456 1' ) ;
13042 ok( ! $tuid2, 'match_a_cache_file: /lala123_456 2' ) ;
13043
13044 ok( ( $tuid1, $tuid2 ) = match_a_cache_file( 'la123_456' ), 'match_a_cache_file: NO la123_456' ) ;
13045 ok( ! $tuid1, 'match_a_cache_file: la123_456 1' ) ;
13046 ok( ! $tuid2, 'match_a_cache_file: la123_456 2' ) ;
13047
13048 note( 'Leaving tests_match_a_cache_file()' ) ;
13049 return ;
13050}
13051
13052sub clean_cache
13053{
13054 my ( $cache_files_ref, $cache_1_2_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) = @_ ;
13055
13056 $debugcache and myprint( "Entering clean_cache\n" ) ;
13057
13058 $debugcache and myprint( map { "$_ -> " . $cache_1_2_ref->{ $_ } . "\n" } keys %{ $cache_1_2_ref } ) ;
13059 foreach my $file ( @{ $cache_files_ref } ) {
13060 $debugcache and myprint( "$file\n" ) ;
13061 my ( $cache_uid1, $cache_uid2 ) = match_a_cache_file( $file ) ;
13062 $debugcache and myprint( "u1: $cache_uid1 u2: $cache_uid2 c12: ", $cache_1_2_ref->{ $cache_uid1 } || q{}, "\n") ;
13063# or ( ! exists( $cache_1_2_ref->{ $cache_uid1 } ) )
13064# or ( ! ( $cache_uid2 == $cache_1_2_ref->{ $cache_uid1 } ) )
13065 if ( ( not defined $cache_uid1 )
13066 or ( not defined $cache_uid2 )
13067 or ( not exists $h1_msgs_all_hash_ref->{ $cache_uid1 } )
13068 or ( not exists $h2_msgs_all_hash_ref->{ $cache_uid2 } )
13069 ) {
13070 $debugcache and myprint( "remove $file\n" ) ;
13071 unlink $file or myprint( "$OS_ERROR" ) ;
13072 }
13073 }
13074
13075 $debugcache and myprint( "Exiting clean_cache\n" ) ;
13076 return( 1 ) ;
13077}
13078
13079sub tests_clean_cache
13080{
13081 note( 'Entering tests_clean_cache()' ) ;
13082
13083 ok( ( not -d 'W/tmp/cache/G1/G2' or rmtree( 'W/tmp/cache/G1/G2' )), 'clean_cache: rmtree W/tmp/cache/G1/G2' ) ;
13084 ok( mkpath( 'W/tmp/cache/G1/G2' ), 'clean_cache: mkpath W/tmp/cache/G1/G2' ) ;
13085
13086 my @test_files_cache = ( qw(
13087 W/tmp/cache/G1/G2/100_200
13088 W/tmp/cache/G1/G2/101_201
13089 W/tmp/cache/G1/G2/120_220
13090 W/tmp/cache/G1/G2/142_242
13091 W/tmp/cache/G1/G2/143_243
13092 W/tmp/cache/G1/G2/177_277
13093 W/tmp/cache/G1/G2/177_377
13094 W/tmp/cache/G1/G2/177_777
13095 W/tmp/cache/G1/G2/155_255
13096 ) ) ;
13097 ok( touch(@test_files_cache), 'clean_cache: touch W/tmp/cache/G1/G2/...' ) ;
13098
13099 ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache: 100_200 before' );
13100 ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache: 142_242 before' );
13101 ok( -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache: 177_277 before' );
13102 ok( -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache: 177_377 before' );
13103 ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache: 177_777 before' );
13104 ok( -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache: 155_255 before' );
13105
13106 my $cache = {
13107 142 => 242,
13108 177 => 777,
13109 } ;
13110
13111 my $all_1 = {
13112 142 => q{},
13113 177 => q{},
13114 } ;
13115
13116 my $all_2 = {
13117 200 => q{},
13118 242 => q{},
13119 777 => q{},
13120 } ;
13121 ok( clean_cache( \@test_files_cache, $cache, $all_1, $all_2 ), 'clean_cache: ' ) ;
13122
13123 ok( ! -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache: 100_200 after' );
13124 ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache: 142_242 after' );
13125 ok( ! -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache: 177_277 after' );
13126 ok( ! -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache: 177_377 after' );
13127 ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache: 177_777 after' );
13128 ok( ! -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache: 155_255 after' );
13129
13130 note( 'Leaving tests_clean_cache()' ) ;
13131 return ;
13132}
13133
13134sub tests_clean_cache_2
13135{
13136 note( 'Entering tests_clean_cache_2()' ) ;
13137
13138 ok( ( not -d 'W/tmp/cache/G1/G2' or rmtree( 'W/tmp/cache/G1/G2' )), 'clean_cache_2: rmtree W/tmp/cache/G1/G2' ) ;
13139 ok( mkpath( 'W/tmp/cache/G1/G2' ), 'clean_cache_2: mkpath W/tmp/cache/G1/G2' ) ;
13140
13141 my @test_files_cache = ( qw(
13142 W/tmp/cache/G1/G2/100_200
13143 W/tmp/cache/G1/G2/101_201
13144 W/tmp/cache/G1/G2/120_220
13145 W/tmp/cache/G1/G2/142_242
13146 W/tmp/cache/G1/G2/143_243
13147 W/tmp/cache/G1/G2/177_277
13148 W/tmp/cache/G1/G2/177_377
13149 W/tmp/cache/G1/G2/177_777
13150 W/tmp/cache/G1/G2/155_255
13151 ) ) ;
13152 ok( touch(@test_files_cache), 'clean_cache_2: touch W/tmp/cache/G1/G2/...' ) ;
13153
13154 ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache_2: 100_200 before' );
13155 ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache_2: 142_242 before' );
13156 ok( -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache_2: 177_277 before' );
13157 ok( -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache_2: 177_377 before' );
13158 ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache_2: 177_777 before' );
13159 ok( -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache_2: 155_255 before' );
13160
13161 my $cache = {
13162 142 => 242,
13163 177 => 777,
13164 } ;
13165
13166 my $all_1 = {
13167 $NUMBER_100 => q{},
13168 142 => q{},
13169 177 => q{},
13170 } ;
13171
13172 my $all_2 = {
13173 200 => q{},
13174 242 => q{},
13175 777 => q{},
13176 } ;
13177
13178
13179
13180 ok( clean_cache( \@test_files_cache, $cache, $all_1, $all_2 ), 'clean_cache_2: ' ) ;
13181
13182 ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache_2: 100_200 after' );
13183 ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache_2: 142_242 after' );
13184 ok( ! -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache_2: 177_277 after' );
13185 ok( ! -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache_2: 177_377 after' );
13186 ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache_2: 177_777 after' );
13187 ok( ! -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache_2: 155_255 after' );
13188
13189 note( 'Leaving tests_clean_cache_2()' ) ;
13190 return ;
13191}
13192
13193
13194
13195sub tests_mkpath
13196{
13197 note( 'Entering tests_mkpath()' ) ;
13198
13199 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' )), 'mkpath: mkpath W/tmp/tests/' ) ;
13200
13201 SKIP: {
13202 skip( 'Tests only for Unix', 10 ) if ( 'MSWin32' eq $OSNAME ) ;
13203 my $long_path_unix = '123456789/' x 30 ;
13204 ok( ( -d "W/tmp/tests/long/$long_path_unix" or mkpath( "W/tmp/tests/long/$long_path_unix" ) ), 'mkpath: mkpath 300 char' ) ;
13205 ok( -d "W/tmp/tests/long/$long_path_unix", 'mkpath: mkpath > 300 char verified' ) ;
13206 ok( ( -d "W/tmp/tests/long/$long_path_unix" and rmtree( 'W/tmp/tests/long/' ) ), 'mkpath: rmtree 300 char' ) ;
13207 ok( ! -d "W/tmp/tests/long/$long_path_unix", 'mkpath: rmtree 300 char verified' ) ;
13208
13209 ok( ( -d 'W/tmp/tests/trailing_dots...' or mkpath( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: mkpath trailing_dots...' ) ;
13210 ok( -d 'W/tmp/tests/trailing_dots...', 'mkpath: mkpath trailing_dots... verified' ) ;
13211 ok( ( -d 'W/tmp/tests/trailing_dots...' and rmtree( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: rmtree trailing_dots...' ) ;
13212 ok( ! -d 'W/tmp/tests/trailing_dots...', 'mkpath: rmtree trailing_dots... verified' ) ;
13213
13214 eval { ok( 1 / 0, 'mkpath: divide by 0' ) ; } or ok( 1, 'mkpath: can not divide by 0' ) ;
13215 ok( 1, 'mkpath: still alive' ) ;
13216 } ;
13217
13218 SKIP: {
13219 skip( 'Tests only for MSWin32', 13 ) if ( 'MSWin32' ne $OSNAME ) ;
13220 my $long_path_2_prefix = ".\\imapsync_tests" || '\\\?\\E:\\TEMP\\imapsync_tests' ;
13221 myprint( "long_path_2_prefix: $long_path_2_prefix\n" ) ;
13222
13223 my $long_path_100 = $long_path_2_prefix . '\\' . '123456789\\' x 10 . 'END' ;
13224 my $long_path_300 = $long_path_2_prefix . '\\' . '123456789\\' x 30 . 'END' ;
13225
13226 #myprint( "$long_path_100\n" ) ;
13227
13228 ok( ( -d $long_path_2_prefix or mkpath( $long_path_2_prefix ) ), 'mkpath: -d mkpath small path' ) ;
13229 ok( ( -d $long_path_2_prefix ), 'mkpath: -d mkpath small path done' ) ;
13230 ok( ( -d $long_path_100 or mkpath( $long_path_100 ) ), 'mkpath: mkpath > 100 char' ) ;
13231 ok( ( -d $long_path_100 ), 'mkpath: -d mkpath > 200 char done' ) ;
13232 ok( ( -d $long_path_2_prefix and rmtree( $long_path_2_prefix ) ), 'mkpath: rmtree > 100 char' ) ;
13233 ok( (! -d $long_path_2_prefix ), 'mkpath: ! -d rmtree done' ) ;
13234
13235 # Without the eval the following mkpath 300 just kill the whole process without a whisper
13236 #myprint( "$long_path_300\n" ) ;
13237 eval { ok( ( -d $long_path_300 or mkpath( $long_path_300 ) ), 'mkpath: create a path with 300 characters' ) ; }
13238 or ok( 1, 'mkpath: can not create a path with 300 characters' ) ;
13239 ok( ( ( ! -d $long_path_300 ) or -d $long_path_300 and rmtree( $long_path_300 ) ), 'mkpath: rmtree the 300 character path' ) ;
13240 ok( 1, 'mkpath: still alive' ) ;
13241
13242 ok( ( -d 'W/tmp/tests/trailing_dots...' or mkpath( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: mkpath trailing_dots...' ) ;
13243 ok( -d 'W/tmp/tests/trailing_dots...', 'mkpath: mkpath trailing_dots... verified' ) ;
13244 ok( ( -d 'W/tmp/tests/trailing_dots...' and rmtree( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: rmtree trailing_dots...' ) ;
13245 ok( ! -d 'W/tmp/tests/trailing_dots...', 'mkpath: rmtree trailing_dots... verified' ) ;
13246
13247
13248 } ;
13249
13250 note( 'Leaving tests_mkpath()' ) ;
13251 # Keep this because of the eval used by the caller (failed badly?)
13252 return 1 ;
13253}
13254
13255sub tests_touch
13256{
13257 note( 'Entering tests_touch()' ) ;
13258
13259 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' )), 'touch: mkpath W/tmp/tests/' ) ;
13260 ok( 1 == touch( 'W/tmp/tests/lala'), 'touch: W/tmp/tests/lala') ;
13261 ok( 1 == touch( 'W/tmp/tests/\y'), 'touch: W/tmp/tests/\y') ;
13262 ok( 0 == touch( '/no/no/no/aaa'), 'touch: not /aaa') ;
13263 ok( 1 == touch( 'W/tmp/tests/lili', 'W/tmp/tests/lolo'), 'touch: 2 files') ;
13264 ok( 0 == touch( 'W/tmp/tests/\y', '/no/no/aaa'), 'touch: 2 files, 1 fails' ) ;
13265
13266 note( 'Leaving tests_touch()' ) ;
13267 return ;
13268}
13269
13270
13271sub touch
13272{
13273 my @files = @_ ;
13274 my $failures = 0 ;
13275
13276 foreach my $file ( @files ) {
13277 my $fh = IO::File->new ;
13278 if ( $fh->open(">> $file" ) ) {
13279 $fh->close ;
13280 }else{
13281 myprint( "Could not open file $file in write/append mode\n" ) ;
13282 $failures++ ;
13283 }
13284 }
13285 return( ! $failures );
13286}
13287
13288
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013289
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013290sub tests_tmpdir_has_colon_bug
13291{
13292 note( 'Entering tests_tmpdir_has_colon_bug()' ) ;
13293
13294 ok( 0 == tmpdir_has_colon_bug( q{} ), 'tmpdir_has_colon_bug: ' ) ;
13295 ok( 0 == tmpdir_has_colon_bug( '/tmp' ), 'tmpdir_has_colon_bug: /tmp' ) ;
13296 ok( 1 == tmpdir_has_colon_bug( 'C:' ), 'tmpdir_has_colon_bug: C:' ) ;
13297 ok( 1 == tmpdir_has_colon_bug( 'C:\temp' ), 'tmpdir_has_colon_bug: C:\temp' ) ;
13298
13299 note( 'Leaving tests_tmpdir_has_colon_bug()' ) ;
13300 return ;
13301}
13302
13303sub tmpdir_has_colon_bug
13304{
13305 my $path = shift ;
13306
13307 my $path_filtered = filter_forbidden_characters( $path ) ;
13308 if ( $path_filtered ne $path ) {
13309 ( -d $path_filtered ) and myprint( "Path $path was previously mistakely changed to $path_filtered\n" ) ;
13310 return( 1 ) ;
13311 }
13312 return( 0 ) ;
13313}
13314
13315sub tmpdir_fix_colon_bug
13316{
13317 my $mysync = shift ;
13318 my $err = 0 ;
13319 if ( not (-d $mysync->{ tmpdir } and -r _ and -w _) ) {
13320 myprint( "tmpdir $mysync->{ tmpdir } is not valid\n" ) ;
13321 return( 0 ) ;
13322 }
13323 my $cachedir_new = "$mysync->{ tmpdir }/imapsync_cache" ;
13324
13325 if ( not tmpdir_has_colon_bug( $cachedir_new ) ) { return( 0 ) } ;
13326
13327 # check if old cache directory already exists
13328 my $cachedir_old = filter_forbidden_characters( $cachedir_new ) ;
13329 if ( not ( -d $cachedir_old ) ) {
13330 myprint( "Old cache directory $cachedir_new no exists, nothing to do\n" ) ;
13331 return( 1 ) ;
13332 }
13333 # check if new cache directory already exists
13334 if ( -d $cachedir_new ) {
13335 myprint( "New fixed cache directory $cachedir_new already exists, not moving the old one $cachedir_old. Fix this manually.\n" ) ;
13336 return( 0 ) ;
13337 }else{
13338 # move the old one to the new place
13339 myprint( "Moving $cachedir_old to $cachedir_new Do not interrupt this task.\n" ) ;
13340 File::Copy::Recursive::rmove( $cachedir_old, $cachedir_new )
13341 or do {
13342 myprint( "Could not move $cachedir_old to $cachedir_new\n" ) ;
13343 $err++ ;
13344 } ;
13345 # check it succeeded
13346 if ( -d $cachedir_new and -r _ and -w _ ) {
13347 myprint( "New fixed cache directory $cachedir_new ok\n" ) ;
13348 }else{
13349 myprint( "New fixed cache directory $cachedir_new does not exist\n" ) ;
13350 $err++ ;
13351 }
13352 if ( -d $cachedir_old ) {
13353 myprint( "Old cache directory $cachedir_old still exists\n" ) ;
13354 $err++ ;
13355 }else{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013356 myprint( "Old cache directory $cachedir_old successfully moved\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013357 }
13358 }
13359 return( not $err ) ;
13360}
13361
13362
13363sub tests_cache_folder
13364{
13365 note( 'Entering tests_cache_folder()' ) ;
13366
13367 ok( '/path/fold1/fold2' eq cache_folder( q{}, '/path', 'fold1', 'fold2'), 'cache_folder: /path, fold1, fold2 -> /path/fold1/fold2' ) ;
13368 ok( '/pa_th/fold1/fold2' eq cache_folder( q{}, '/pa*th', 'fold1', 'fold2'), 'cache_folder: /pa*th, fold1, fold2 -> /path/fold1/fold2' ) ;
13369 ok( '/_p_a__th/fol_d1/fold2' eq cache_folder( q{}, '/>p<a|*th', 'fol*d1', 'fold2'), 'cache_folder: />p<a|*th, fol*d1, fold2 -> /path/fol_d1/fold2' ) ;
13370
13371 ok( 'D:/path/fold1/fold2' eq cache_folder( 'D:', '/path', 'fold1', 'fold2'), 'cache_folder: /path, fold1, fold2 -> /path/fold1/fold2' ) ;
13372 ok( 'D:/pa_th/fold1/fold2' eq cache_folder( 'D:', '/pa*th', 'fold1', 'fold2'), 'cache_folder: /pa*th, fold1, fold2 -> /path/fold1/fold2' ) ;
13373 ok( 'D:/_p_a__th/fol_d1/fold2' eq cache_folder( 'D:', '/>p<a|*th', 'fol*d1', 'fold2'), 'cache_folder: />p<a|*th, fol*d1, fold2 -> /path/fol_d1/fold2' ) ;
13374 ok( '//' eq cache_folder( q{}, q{}, q{}, q{}), 'cache_folder: -> //' ) ;
13375 ok( '//_______' eq cache_folder( q{}, q{}, q{}, '*|?:"<>'), 'cache_folder: *|?:"<> -> //_______' ) ;
13376
13377 note( 'Leaving tests_cache_folder()' ) ;
13378 return ;
13379}
13380
13381sub cache_folder
13382{
13383 my( $cache_base, $cache_dir, $h1_fold, $h2_fold ) = @_ ;
13384
13385 my $sep_1 = $sync->{ h1_sep } || '/';
13386 my $sep_2 = $sync->{ h2_sep } || '/';
13387
13388 #myprint( "$cache_dir h1_fold $h1_fold sep1 $sep_1 h2_fold $h2_fold sep2 $sep_2\n" ) ;
13389 $h1_fold = convert_sep_to_slash( $h1_fold, $sep_1 ) ;
13390 $h2_fold = convert_sep_to_slash( $h2_fold, $sep_2 ) ;
13391
13392 my $cache_folder = "$cache_base" . filter_forbidden_characters( "$cache_dir/$h1_fold/$h2_fold" ) ;
13393 #myprint( "cache_folder [$cache_folder]\n" ) ;
13394 return( $cache_folder ) ;
13395}
13396
13397sub tests_filter_forbidden_characters
13398{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013399 note( 'Entering tests_filter_forbidden_characters()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013400
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013401 is( undef , filter_forbidden_characters( ), 'filter_forbidden_characters: no args -> undef' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013402
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013403 is( 'a_b' , filter_forbidden_characters( 'a_b' ), 'filter_forbidden_characters: a_b -> a_b' ) ;
13404 is( 'a_b' , filter_forbidden_characters( 'a*b' ), 'filter_forbidden_characters: a*b -> a_b' ) ;
13405 is( 'a_b' , filter_forbidden_characters( 'a|b' ), 'filter_forbidden_characters: a|b -> a_b' ) ;
13406 is( 'a_b' , filter_forbidden_characters( 'a?b' ), 'filter_forbidden_characters: a?b -> a_b' ) ;
13407 is( 'a________b', filter_forbidden_characters( q{a*|?:"<>'b} ), q{filter_forbidden_characters: a*|?:"<>'b -> a________b} ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013408
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013409
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013410 is( 'a_b_' , filter_forbidden_characters( 'a b ' ), 'filter_forbidden_characters: "a b " -> "a_b_"' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013411
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013412
13413 is( 'a_b' , filter_forbidden_characters( "a\tb" ), 'filter_forbidden_characters: a\tb -> a_b' ) ;
13414 is( "a_b" , filter_forbidden_characters( "a\rb" ), 'filter_forbidden_characters: a\rb -> a_b' ) ;
13415 is( "a_b" , filter_forbidden_characters( "a\nb" ), 'filter_forbidden_characters: a\nb -> a_b' ) ;
13416 is( "a_b" , filter_forbidden_characters( "a\\b" ), 'filter_forbidden_characters: a\b -> a_b' ) ;
13417
13418 is( 'a-b' , filter_forbidden_characters( 'a-b' ), 'filter_forbidden_characters: a-b -> a-b' ) ;
13419 is( 'a__-__-__-__-__b' , filter_forbidden_characters( 'aé-è-à -ç-Öb' ), 'filter_forbidden_characters: aé-è-à -ç-Öb -> a__-__-__-__-__b' ) ;
13420
13421 is( 'abcdABCDwxyzWXYZ012789' , filter_forbidden_characters( 'abcdABCDwxyzWXYZ012789' ),
13422 'filter_forbidden_characters: abcdABCDwxyzWXYZ012789 -> abcdABCDwxyzWXYZ012789' ) ;
13423
13424
13425 note( 'Leaving tests_filter_forbidden_characters()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013426 return ;
13427}
13428
13429sub filter_forbidden_characters
13430{
13431 my $string = shift ;
13432
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013433 if ( ! defined $string ) { return ; }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013434
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013435 $string =~ s{[\Q*|?:"<>' \E\t\r\n\\]}{_}xg ;
13436 # replace all non-ascii and control characters by _
13437 $string =~ s/[[:^ascii:][:cntrl:]]/_/xg ;
13438
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013439 #myprint( "[$string]\n" ) ;
13440 return( $string ) ;
13441}
13442
13443sub tests_convert_sep_to_slash
13444{
13445 note( 'Entering tests_convert_sep_to_slash()' ) ;
13446
13447
13448 ok(q{} eq convert_sep_to_slash(q{}, '/'), 'convert_sep_to_slash: no folder');
13449 ok('INBOX' eq convert_sep_to_slash('INBOX', '/'), 'convert_sep_to_slash: INBOX');
13450 ok('INBOX/foo' eq convert_sep_to_slash('INBOX/foo', '/'), 'convert_sep_to_slash: INBOX/foo');
13451 ok('INBOX/foo' eq convert_sep_to_slash('INBOX_foo', '_'), 'convert_sep_to_slash: INBOX_foo');
13452 ok('INBOX/foo/zob' eq convert_sep_to_slash('INBOX_foo_zob', '_'), 'convert_sep_to_slash: INBOX_foo_zob');
13453 ok('INBOX/foo' eq convert_sep_to_slash('INBOX.foo', '.'), 'convert_sep_to_slash: INBOX.foo');
13454 ok('INBOX/foo/hi' eq convert_sep_to_slash('INBOX.foo.hi', '.'), 'convert_sep_to_slash: INBOX.foo.hi');
13455
13456 note( 'Leaving tests_convert_sep_to_slash()' ) ;
13457 return ;
13458}
13459
13460sub convert_sep_to_slash
13461{
13462 my ( $folder, $sep ) = @_ ;
13463
13464 $folder =~ s{\Q$sep\E}{/}xg ;
13465 return( $folder ) ;
13466}
13467
13468
13469sub tests_regexmess
13470{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013471 note( 'Entering tests_regexmess()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013472
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013473 ok( 'blabla' eq regexmess( 'blabla' ), 'regexmess: no regexmess, nothing to do' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013474
13475 @regexmess = ( 'lalala' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013476 ok( not( defined regexmess( 'popopo' ) ), 'regexmess: bad regex lalala' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013477
13478 @regexmess = ( 's/p/Z/g' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013479 ok( 'ZoZoZo' eq regexmess( 'popopo' ), 'regexmess: s/p/Z/g' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013480
13481 @regexmess = ( 's{c}{C}gxms' ) ;
13482 ok("H1: abC\nH2: Cde\n\nBody abC"
13483 eq regexmess( "H1: abc\nH2: cde\n\nBody abc"),
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013484 'regexmess: c->C');
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013485
13486 @regexmess = ( 's{\AFrom\ }{From:}gxms' ) ;
13487 ok( q{}
13488 eq regexmess(q{}),
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013489 'regexmess: From mbox 1 add colon blank');
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013490
13491 ok( 'From:<tartanpion@machin.truc>'
13492 eq regexmess('From <tartanpion@machin.truc>'),
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013493 'regexmess: From mbox 2 add colo');
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013494
13495 ok( "\n" . 'From <tartanpion@machin.truc>'
13496 eq regexmess("\n" . 'From <tartanpion@machin.truc>'),
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013497 'regexmess: From mbox 3 add colo') ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013498
13499 ok( "From: zzz\n" . 'From <tartanpion@machin.truc>'
13500 eq regexmess("From zzz\n" . 'From <tartanpion@machin.truc>'),
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013501 'regexmess: From mbox 4 add colo') ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013502
13503 @regexmess = ( 's{\AFrom\ [^\n]*(\n)?}{}gxms' ) ;
13504 ok( q{}
13505 eq regexmess(q{}),
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013506 'regexmess: From mbox 1 remove, blank');
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013507
13508 ok( q{}
13509 eq regexmess('From <tartanpion@machin.truc>'),
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013510 'regexmess: From mbox 2 remove');
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013511
13512 ok( "\n" . 'From <tartanpion@machin.truc>'
13513 eq regexmess("\n" . 'From <tartanpion@machin.truc>'),
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013514 'regexmess: From mbox 3 remove');
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013515
13516 #myprint( "[", regexmess("From zzz\n" . 'From <tartanpion@machin.truc>'), "]" ) ;
13517 ok( q{} . 'From <tartanpion@machin.truc>'
13518 eq regexmess("From zzz\n" . 'From <tartanpion@machin.truc>'),
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013519 'regexmess: From mbox 4 remove');
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013520
13521
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013522 is(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013523<<'EOM'
13524Date: Sat, 10 Jul 2010 05:34:45 -0700
13525From:<tartanpion@machin.truc>
13526
13527Hello,
13528Bye.
13529EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013530 , regexmess(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013531<<'EOM'
13532From zzz
13533Date: Sat, 10 Jul 2010 05:34:45 -0700
13534From:<tartanpion@machin.truc>
13535
13536Hello,
13537Bye.
13538EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013539 ), 'regexmess: From mbox 5 remove');
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013540
13541
13542@regexmess = ( 's{\A((?:[^\n]+\n)+|)^Disposition-Notification-To:[^\n]*\n(\r?\n|.*\n\r?\n)}{$1$2}xms' ) ; # SUPER SUPER BEST!
13543 ok(
13544<<'EOM'
13545Date: Sat, 10 Jul 2010 05:34:45 -0700
13546From:<tartanpion@machin.truc>
13547
13548Hello,
13549Bye.
13550EOM
13551 eq regexmess(
13552<<'EOM'
13553Date: Sat, 10 Jul 2010 05:34:45 -0700
13554Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13555From:<tartanpion@machin.truc>
13556
13557Hello,
13558Bye.
13559EOM
13560 ),
13561 'regexmess: 1 Delete header Disposition-Notification-To:');
13562
13563 ok(
13564<<'EOM'
13565Date: Sat, 10 Jul 2010 05:34:45 -0700
13566From:<tartanpion@machin.truc>
13567
13568Hello,
13569Bye.
13570EOM
13571 eq regexmess(
13572<<'EOM'
13573Date: Sat, 10 Jul 2010 05:34:45 -0700
13574From:<tartanpion@machin.truc>
13575Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13576
13577Hello,
13578Bye.
13579EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013580 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013581 'regexmess: 2 Delete header Disposition-Notification-To:');
13582
13583 ok(
13584<<'EOM'
13585Date: Sat, 10 Jul 2010 05:34:45 -0700
13586From:<tartanpion@machin.truc>
13587
13588Hello,
13589Bye.
13590EOM
13591 eq regexmess(
13592<<'EOM'
13593Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13594Date: Sat, 10 Jul 2010 05:34:45 -0700
13595From:<tartanpion@machin.truc>
13596
13597Hello,
13598Bye.
13599EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013600 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013601 'regexmess: 3 Delete header Disposition-Notification-To:');
13602
13603 ok(
13604<<'EOM'
13605Date: Sat, 10 Jul 2010 05:34:45 -0700
13606From:<tartanpion@machin.truc>
13607
13608Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13609Bye.
13610EOM
13611 eq regexmess(
13612<<'EOM'
13613Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13614Date: Sat, 10 Jul 2010 05:34:45 -0700
13615From:<tartanpion@machin.truc>
13616
13617Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13618Bye.
13619EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013620 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013621 'regexmess: 4 Delete header Disposition-Notification-To:');
13622
13623
13624 ok(
13625<<'EOM'
13626Date: Sat, 10 Jul 2010 05:34:45 -0700
13627From:<tartanpion@machin.truc>
13628
13629Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13630Bye.
13631EOM
13632 eq regexmess(
13633<<'EOM'
13634Date: Sat, 10 Jul 2010 05:34:45 -0700
13635From:<tartanpion@machin.truc>
13636
13637Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13638Bye.
13639EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013640 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013641 'regexmess: 5 Delete header Disposition-Notification-To:');
13642
13643
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013644 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013645<<'EOM'
13646Date: Sat, 10 Jul 2010 05:34:45 -0700
13647From:<tartanpion@machin.truc>
13648
13649Hello,
13650Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13651Bye.
13652EOM
13653 eq regexmess(
13654<<'EOM'
13655Date: Sat, 10 Jul 2010 05:34:45 -0700
13656From:<tartanpion@machin.truc>
13657
13658Hello,
13659Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13660Bye.
13661EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013662 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013663 'regexmess: 6 Delete header Disposition-Notification-To:');
13664
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013665 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013666<<'EOM'
13667Date: Sat, 10 Jul 2010 05:34:45 -0700
13668From:<tartanpion@machin.truc>
13669
13670Hello,
13671Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13672
13673Bye.
13674EOM
13675 eq regexmess(
13676<<'EOM'
13677Date: Sat, 10 Jul 2010 05:34:45 -0700
13678From:<tartanpion@machin.truc>
13679
13680Hello,
13681Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13682
13683Bye.
13684EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013685 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013686 'regexmess: 7 Delete header Disposition-Notification-To:');
13687
13688
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013689 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013690<<'EOM'
13691Date: Sat, 10 Jul 2010 05:34:45 -0700
13692From:<tartanpion@machin.truc>
13693
13694Hello,
13695Bye.
13696EOM
13697 eq regexmess(
13698<<'EOM'
13699Date: Sat, 10 Jul 2010 05:34:45 -0700
13700From:<tartanpion@machin.truc>
13701
13702Hello,
13703Bye.
13704EOM
13705),
13706 'regexmess: 8 Delete header Disposition-Notification-To:');
13707
13708
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013709 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013710<<'EOM'
13711Date: Sat, 10 Jul 2010 05:34:45 -0700
13712From:<tartanpion@machin.truc>
13713
13714Hello,
13715Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13716Bye.
13717EOM
13718 eq regexmess(
13719<<'EOM'
13720Date: Sat, 10 Jul 2010 05:34:45 -0700
13721From:<tartanpion@machin.truc>
13722
13723Hello,
13724Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13725Bye.
13726EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013727 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013728 'regexmess: 9 Delete header Disposition-Notification-To:');
13729
13730
13731
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013732 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013733<<'EOM'
13734Date: Sat, 10 Jul 2010 05:34:45 -0700
13735From:<tartanpion@machin.truc>
13736
13737Hello,
13738Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13739
13740
13741Bye.
13742EOM
13743 eq regexmess(
13744<<'EOM'
13745Date: Sat, 10 Jul 2010 05:34:45 -0700
13746From:<tartanpion@machin.truc>
13747
13748Hello,
13749Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13750
13751
13752Bye.
13753EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013754 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013755 'regexmess: 10 Delete header Disposition-Notification-To:');
13756
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013757 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013758<<'EOM'
13759Date: Sat, 10 Jul 2010 05:34:45 -0700
13760From:<tartanpion@machin.truc>
13761
13762Hello,
13763
13764Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13765
13766Bye.
13767EOM
13768 eq regexmess(
13769<<'EOM'
13770Date: Sat, 10 Jul 2010 05:34:45 -0700
13771From:<tartanpion@machin.truc>
13772
13773Hello,
13774
13775Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13776
13777Bye.
13778EOM
13779),
13780 'regexmess: 11 Delete header Disposition-Notification-To:');
13781
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013782 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013783<<'EOM'
13784Date: Sat, 10 Jul 2010 05:34:45 -0700
13785From:<tartanpion@machin.truc>
13786
13787Hello,
13788
13789Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13790
13791Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13792
13793Bye.
13794EOM
13795 eq regexmess(
13796<<'EOM'
13797Date: Sat, 10 Jul 2010 05:34:45 -0700
13798From:<tartanpion@machin.truc>
13799
13800Hello,
13801
13802Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13803
13804Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13805
13806Bye.
13807EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013808 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013809 'regexmess: 12 Delete header Disposition-Notification-To:');
13810
13811
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013812 @regexmess = ( 's{\A(.*?(?! ^$))^Disposition-Notification-To:(.*?)$}{$1X-Disposition-Notification-To:$2}igxms' ) ; # BAD!
13813 @regexmess = ( 's{\A((?:[^\n]+\n)+|)(^Disposition-Notification-To:[^\n]*\n)(\r?\n|.*\n\r?\n)}{$1X-$2$3}ims' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013814
13815
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013816 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013817<<'EOM'
13818Date: Sat, 10 Jul 2010 05:34:45 -0700
13819From:<tartanpion@machin.truc>
13820
13821Hello,
13822
13823Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13824
13825Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13826
13827Bye.
13828EOM
13829 eq regexmess(
13830<<'EOM'
13831Date: Sat, 10 Jul 2010 05:34:45 -0700
13832From:<tartanpion@machin.truc>
13833
13834Hello,
13835
13836Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13837
13838Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13839
13840Bye.
13841EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013842 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013843 'regexmess: 13 Delete header Disposition-Notification-To:');
13844
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013845 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013846<<'EOM'
13847Date: Sat, 10 Jul 2010 05:34:45 -0700
13848X-Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13849From:<tartanpion@machin.truc>
13850
13851Hello,
13852
13853Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13854
13855Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13856
13857Bye.
13858EOM
13859 eq regexmess(
13860<<'EOM'
13861Date: Sat, 10 Jul 2010 05:34:45 -0700
13862Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13863From:<tartanpion@machin.truc>
13864
13865Hello,
13866
13867Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13868
13869Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13870
13871Bye.
13872EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013873 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013874 'regexmess: 14 Delete header Disposition-Notification-To:');
13875
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013876 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013877<<'EOM'
13878Date: Sat, 10 Jul 2010 05:34:45 -0700
13879X-Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13880From:<tartanpion@machin.truc>
13881
13882Hello,
13883
13884Bye.
13885EOM
13886 eq regexmess(
13887<<'EOM'
13888Date: Sat, 10 Jul 2010 05:34:45 -0700
13889Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13890From:<tartanpion@machin.truc>
13891
13892Hello,
13893
13894Bye.
13895EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013896 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013897 'regexmess: 15 Delete header Disposition-Notification-To:');
13898
13899
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013900 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013901<<'EOM'
13902Date: Sat, 10 Jul 2010 05:34:45 -0700
13903From:<tartanpion@machin.truc>
13904X-Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13905
13906Hello,
13907
13908Bye.
13909EOM
13910 eq regexmess(
13911<<'EOM'
13912Date: Sat, 10 Jul 2010 05:34:45 -0700
13913From:<tartanpion@machin.truc>
13914Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13915
13916Hello,
13917
13918Bye.
13919EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013920 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013921 'regexmess: 16 Delete header Disposition-Notification-To:');
13922
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013923 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013924<<'EOM'
13925X-Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13926Date: Sat, 10 Jul 2010 05:34:45 -0700
13927From:<tartanpion@machin.truc>
13928
13929Hello,
13930
13931Bye.
13932EOM
13933 eq regexmess(
13934<<'EOM'
13935Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13936Date: Sat, 10 Jul 2010 05:34:45 -0700
13937From:<tartanpion@machin.truc>
13938
13939Hello,
13940
13941Bye.
13942EOM
13943),
13944 'regexmess: 17 Delete header Disposition-Notification-To:');
13945
13946 @regexmess = ( 's/.{11}\K.*//gs' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013947 is( "0123456789\n", regexmess( "0123456789\n" x 100 ), 'regexmess: truncate whole message after 11 characters' ) ;
13948 is( "0123456789\n", regexmess( "0123456789\n" x 100_000 ), 'regexmess: truncate whole message after 11 characters ~ 1MB' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013949
13950 @regexmess = ( 's/.{10000}\K.*//gs' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013951 is( "123456789\n" x 1000, regexmess( "123456789\n" x 100_000 ), 'regexmess: truncate whole message after 10000 characters ~ 1MB' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013952
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013953 @regexmess = ( 's/^(X-Ham-Report.*?\n)^X-/X-/sm' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013954
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013955 is(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013956<<'EOM'
13957X-Spam-Score: -1
13958X-Spam-Bar: /
13959X-Spam-Flag: NO
13960Date: Sat, 10 Jul 2010 05:34:45 -0700
13961From:<tartanpion@machin.truc>
13962
13963Hello,
13964
13965Bye.
13966EOM
13967,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013968 regexmess(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013969<<'EOM'
13970X-Spam-Score: -1
13971X-Spam-Bar: /
13972X-Ham-Report: =?utf-8?Q?Spam_detection_software=2C_running?=
13973 =?utf-8?Q?_on_the_system_=22ohp-ag006.int200?=
13974_has_NOT_identified_thi?=
13975 =?utf-8?Q?s_incoming_email_as_spam.__The_o?=
13976_message_has_been_attac?=
13977 =?utf-8?Q?hed_to_this_so_you_can_view_it_o?=
13978___________________________?=
13979 =?utf-8?Q?__author's_domain
13980X-Spam-Flag: NO
13981Date: Sat, 10 Jul 2010 05:34:45 -0700
13982From:<tartanpion@machin.truc>
13983
13984Hello,
13985
13986Bye.
13987EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013988 ),
13989 'regexmess: Delete header X-Ham-Report:');
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013990
13991
13992# regex to play with Date: from the FAQ
13993#@regexmess = 's{\A(.*?(?! ^$))^Date:(.*?)$}{$1Date:$2\nX-Date:$2}gxms'
13994
13995
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013996# Change 8bit characters in whole email to X characters
13997 @regexmess = ( 's{[\x80-\xff]}{X}gxms' ) ;
13998 is( 'X-8bit: kaka 1 XX kiki', regexmess('X-8bit: kaka 1 ¤ kiki'), 'regexmess: 1 Change 8bit characters in whole email to X characters');
13999
14000# Same change but using tr
14001 @regexmess = ( 'tr [\x80-\xff] [X]' ) ;
14002 is( 'X-8bit: kaka 1 XXXX kiki', regexmess('X-8bit: kaka 1 ¤£ kiki'), 'regexmess: 2 Change 8bit characters in whole email to X characters, using tr');
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014003
14004
14005
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014006# Add a final \r\n if missing
14007 @regexmess = ( 's{(?<![\n])\z}{\r\n}gxms' ) ;
14008 is( "\r\n", regexmess(""), 'regexmess: 1. Add a final \r\n if missing. Missing' ) ;
14009 is( "abc\r\n", regexmess("abc"), 'regexmess: 2. Add a final \r\n if missing. Missing' ) ;
14010 is( "abc\ndef\r\n", regexmess("abc\ndef"), 'regexmess: 3. Add a final \r\n if missing. Missing' ) ;
14011 is( "abc\r\ndef\r\n", regexmess("abc\r\ndef"), 'regexmess: 3. Add a final \r\n if missing. Missing' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014012
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014013 is( "\r\n", regexmess("\r\n"), 'regexmess: 3. Add a final \r\n if missing. Not missing' ) ;
14014 is( "abc\n", regexmess("abc\n"), 'regexmess: 4. Add a final \r\n if missing. Not missing' ) ;
14015 is( "abc\r\n", regexmess("abc\r\n"), 'regexmess: 4. Add a final \r\n if missing. Not missing' ) ;
14016 is( "abc\ndef\n", regexmess("abc\ndef\n"), 'regexmess: 4. Add a final \r\n if missing. Not missing' ) ;
14017 is( "abc\r\ndef\r\n", regexmess("abc\r\ndef\r\n"), 'regexmess: 4. Add a final \r\n if missing. Not missing' ) ;
14018
14019# Remove the fucking buggy X-Spam-Report: a bad header on several lines that can even begin without a space!
14020
14021 @regexmess = ( 's{X-Spam-Report:.*?\n(^[^\n]+:|^\r?\n)}{$1}xms' ) ;
14022 # Damien regexes:
14023 #@regexmess = ( 's{X-Spam-Report:.*?\n(^[a-zA-Z0-9\-]+:)}{$1}xms' ) ;
14024 #@regexmess = ( 's{X-Spam-Report:.*?\n(^[a-zA-Z0-9\-]+:|^\r?\n)}{$1}xms' ) ;
14025
14026 is(
14027<<'EOM'
14028Date: Sat, 10 Jul 2010 05:34:45 -0700
14029From:<tartanpion@machin.truc>
14030LaSuite: super
14031
14032Hello,
14033Bye.
14034EOM
14035 , regexmess(
14036<<'EOM'
14037Date: Sat, 10 Jul 2010 05:34:45 -0700
14038From:<tartanpion@machin.truc>
14039X-Spam-Report: caca
14040caca
14041 caca
14042caca
14043LaSuite: super
14044
14045Hello,
14046Bye.
14047EOM
14048 ), 'regexmess: 1 remove buggy X-Spam-Report: across several lines, not the final header');
14049
14050
14051 is(
14052<<'EOM'
14053Date: Sat, 10 Jul 2010 05:34:45 -0700
14054From:<tartanpion@machin.truc>
14055LaSuite: super
14056LaSuite2: super 2
14057
14058Hello,
14059Bye.
14060EOM
14061 , regexmess(
14062<<'EOM'
14063Date: Sat, 10 Jul 2010 05:34:45 -0700
14064From:<tartanpion@machin.truc>
14065X-Spam-Report: caca
14066caca
14067 caca
14068caca
14069LaSuite: super
14070LaSuite2: super 2
14071
14072Hello,
14073Bye.
14074EOM
14075 ), 'regexmess: 2 remove buggy X-Spam-Report: across several lines, not the final header');
14076
14077
14078 is(
14079<<'EOM'
14080Date: Sat, 10 Jul 2010 05:34:45 -0700
14081From:<tartanpion@machin.truc>
14082LaSuite: super
14083LaSuite2: super 2
14084
14085Hello,
14086Bye.
14087EOM
14088 , regexmess(
14089<<'EOM'
14090X-Spam-Report: caca
14091caca
14092 caca
14093caca
14094Date: Sat, 10 Jul 2010 05:34:45 -0700
14095From:<tartanpion@machin.truc>
14096LaSuite: super
14097LaSuite2: super 2
14098
14099Hello,
14100Bye.
14101EOM
14102 ), 'regexmess: 3 remove buggy X-Spam-Report: across several lines, first header');
14103
14104
14105
14106
14107 is(
14108<<'EOM'
14109Date: Sat, 10 Jul 2010 05:34:45 -0700
14110From:<tartanpion@machin.truc>
14111
14112Hello,
14113Bye.
14114EOM
14115 , regexmess(
14116<<'EOM'
14117Date: Sat, 10 Jul 2010 05:34:45 -0700
14118From:<tartanpion@machin.truc>
14119X-Spam-Report: caca
14120caca
14121 caca
14122caca
14123
14124Hello,
14125Bye.
14126EOM
14127 ), 'regexmess: 4 remove buggy X-Spam-Report: across several lines, final header');
14128
14129
14130 is(
14131<<'EOM'
14132Date: Sat, 10 Jul 2010 05:34:45 -0700
14133From:<tartanpion@machin.truc>
14134
14135Hello,
14136Bye.
14137EOM
14138 , regexmess(
14139<<'EOM'
14140Date: Sat, 10 Jul 2010 05:34:45 -0700
14141From:<tartanpion@machin.truc>
14142
14143Hello,
14144Bye.
14145EOM
14146 ), 'regexmess: 5 remove buggy X-Spam-Report: not there at all');
14147
14148
14149 is(
14150<<"EOM"
14151Date: Sat, 10 Jul 2010 05:34:45 -0700\r
14152From:<tartanpion>\r
14153LaSuite: super\r
14154LaSuite2: super 2\r
14155\r
14156Hello,\r
14157Bye.\r
14158EOM
14159 , regexmess(
14160<<"EOM"
14161X-Spam-Report: caca\r
14162caca\r
14163 caca\r
14164caca\r
14165Date: Sat, 10 Jul 2010 05:34:45 -0700\r
14166From:<tartanpion>\r
14167LaSuite: super\r
14168LaSuite2: super 2\r
14169\r
14170Hello,\r
14171Bye.\r
14172EOM
14173 ), 'regexmess: 6 remove buggy X-Spam-Report: across several lines, first header, with \r');
14174
14175
14176 is(
14177<<"EOM"
14178Date: Sat, 10 Jul 2010 05:34:45 -0700\r
14179From:<tartanpion>\r
14180LaSuite: super\r
14181LaSuite2: super 2\r
14182\r
14183Hello,\r
14184Bye.\r
14185EOM
14186 , regexmess(
14187<<"EOM"
14188Date: Sat, 10 Jul 2010 05:34:45 -0700\r
14189From:<tartanpion>\r
14190X-Spam-Report: caca\r
14191caca\r
14192 caca\r
14193caca\r
14194LaSuite: super\r
14195LaSuite2: super 2\r
14196\r
14197Hello,\r
14198Bye.\r
14199EOM
14200 ), 'regexmess: 7 remove buggy X-Spam-Report: across several lines, middle header, with \r');
14201
14202
14203 is(
14204<<"EOM"
14205Date: Sat, 10 Jul 2010 05:34:45 -0700\r
14206From:<tartanpion>\r
14207\r
14208Hello,\r
14209Bye.\r
14210EOM
14211 , regexmess(
14212<<"EOM"
14213Date: Sat, 10 Jul 2010 05:34:45 -0700\r
14214From:<tartanpion>\r
14215X-Spam-Report: caca\r
14216caca\r
14217 caca\r
14218caca\r
14219\r
14220Hello,\r
14221Bye.\r
14222EOM
14223 ), 'regexmess: 8 remove buggy X-Spam-Report: across several lines, final header, with \r');
14224
14225
14226 undef @regexmess ;
14227 note( 'Leaving tests_regexmess()' ) ;
14228 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014229}
14230
14231sub regexmess
14232{
14233 my ( $string ) = @_ ;
14234 foreach my $regexmess ( @regexmess ) {
14235 $sync->{ debug } and myprint( "eval \$string =~ $regexmess\n" ) ;
14236 my $ret = eval "\$string =~ $regexmess ; 1" ;
14237 #myprint( "eval [$ret]\n" ) ;
14238 if ( ( not $ret ) or $EVAL_ERROR ) {
14239 myprint( "Error: eval regexmess '$regexmess': $EVAL_ERROR" ) ;
14240 return( undef ) ;
14241 }
14242 }
14243 $sync->{ debug } and myprint( "$string\n" ) ;
14244 return( $string ) ;
14245}
14246
14247
14248sub tests_skipmess
14249{
14250 note( 'Entering tests_skipmess()' ) ;
14251
14252 ok( not( defined skipmess( 'blabla' ) ), 'skipmess, no skipmess, no skip' ) ;
14253
14254 @skipmess = ('[') ;
14255 ok( not( defined skipmess( 'popopo' ) ), 'skipmess, bad regex [' ) ;
14256
14257 @skipmess = ('lalala') ;
14258 ok( not( defined skipmess( 'popopo' ) ), 'skipmess, bad regex lalala' ) ;
14259
14260 @skipmess = ('/popopo/') ;
14261 ok( 1 == skipmess( 'popopo' ), 'skipmess, popopo match regex /popopo/' ) ;
14262
14263 @skipmess = ('/popopo/') ;
14264 ok( 0 == skipmess( 'rrrrrr' ), 'skipmess, rrrrrr does not match regex /popopo/' ) ;
14265
14266 @skipmess = ('m{^$}') ;
14267 ok( 1 == skipmess( q{} ), 'skipmess: empty string yes' ) ;
14268 ok( 0 == skipmess( 'Hi!' ), 'skipmess: empty string no' ) ;
14269
14270 @skipmess = ('m{i}') ;
14271 ok( 1 == skipmess( 'Hi!' ), 'skipmess: i string yes' ) ;
14272 ok( 0 == skipmess( 'Bye!' ), 'skipmess: i string no' ) ;
14273
14274 @skipmess = ('m{[\x80-\xff]}') ;
14275 ok( 0 == skipmess( 'Hi!' ), 'skipmess: i 8bit no' ) ;
14276 ok( 1 == skipmess( "\xff" ), 'skipmess: \xff 8bit yes' ) ;
14277
14278 @skipmess = ('m{A}', 'm{B}') ;
14279 ok( 0 == skipmess( 'Hi!' ), 'skipmess: A or B no' ) ;
14280 ok( 0 == skipmess( 'lala' ), 'skipmess: A or B no' ) ;
14281 ok( 0 == skipmess( "\xff" ), 'skipmess: A or B no' ) ;
14282 ok( 1 == skipmess( 'AB' ), 'skipmess: A or B yes' ) ;
14283 ok( 1 == skipmess( 'BA' ), 'skipmess: A or B yes' ) ;
14284 ok( 1 == skipmess( 'AA' ), 'skipmess: A or B yes' ) ;
14285 ok( 1 == skipmess( 'Ok Bye' ), 'skipmess: A or B yes' ) ;
14286
14287
14288 @skipmess = ( 'm#\A((?:[^\n]+\n)+|)^Content-Type: Message/Partial;[^\n]*\n(?:\n|.*\n\n)#ism' ) ; # SUPER BEST!
14289
14290
14291
14292 ok( 1 == skipmess(
14293<<'EOM'
14294Date: Sat, 10 Jul 2010 05:34:45 -0700
14295Content-Type: Message/Partial; blabla
14296From:<tartanpion@machin.truc>
14297
14298Hello!
14299Bye.
14300EOM
14301),
14302 'skipmess: 1 match Content-Type: Message/Partial' ) ;
14303
14304 ok( 0 == skipmess(
14305<<'EOM'
14306Date: Sat, 10 Jul 2010 05:34:45 -0700
14307From:<tartanpion@machin.truc>
14308
14309Hello!
14310Bye.
14311EOM
14312),
14313 'skipmess: 2 not match Content-Type: Message/Partial' ) ;
14314
14315
14316 ok( 1 == skipmess(
14317<<'EOM'
14318Date: Sat, 10 Jul 2010 05:34:45 -0700
14319From:<tartanpion@machin.truc>
14320Content-Type: Message/Partial; blabla
14321
14322Hello!
14323Bye.
14324EOM
14325),
14326 'skipmess: 3 match Content-Type: Message/Partial' ) ;
14327
14328 ok( 0 == skipmess(
14329<<'EOM'
14330Date: Sat, 10 Jul 2010 05:34:45 -0700
14331From:<tartanpion@machin.truc>
14332
14333Hello!
14334Content-Type: Message/Partial; blabla
14335Bye.
14336EOM
14337),
14338 'skipmess: 4 not match Content-Type: Message/Partial' ) ;
14339
14340
14341 ok( 0 == skipmess(
14342<<'EOM'
14343Date: Sat, 10 Jul 2010 05:34:45 -0700
14344From:<tartanpion@machin.truc>
14345
14346Hello!
14347Content-Type: Message/Partial; blabla
14348
14349Bye.
14350EOM
14351),
14352 'skipmess: 5 not match Content-Type: Message/Partial' ) ;
14353
14354
14355 ok( 1 == skipmess(
14356<<'EOM'
14357Date: Sat, 10 Jul 2010 05:34:45 -0700
14358Content-Type: Message/Partial; blabla
14359From:<tartanpion@machin.truc>
14360
14361Hello!
14362
14363Content-Type: Message/Partial; blabla
14364
14365Bye.
14366EOM
14367),
14368 'skipmess: 6 match Content-Type: Message/Partial' ) ;
14369
14370 ok( 1 == skipmess(
14371<<'EOM'
14372Date: Sat, 10 Jul 2010 05:34:45 -0700
14373Content-Type: Message/Partial;
14374From:<tartanpion@machin.truc>
14375
14376Hello!
14377Bye.
14378EOM
14379),
14380 'skipmess: 7 match Content-Type: Message/Partial' ) ;
14381
14382 ok( 1 == skipmess(
14383<<'EOM'
14384Date: Wed, 2 Jul 2014 02:26:40 +0000
14385MIME-Version: 1.0
14386Content-Type: message/partial;
14387 id="TAN_U_P<1404267997.00007489ed17>";
14388 number=3;
14389 total=3
14390
143916HQ6Hh3CdXj77qEGixerQ6zHx0OnQ/Cf5On4W0Y6vtU2crABZQtD46Hx1EOh8dDz4+OnTr1G
14392
14393
14394Hello!
14395Bye.
14396EOM
14397),
14398 'skipmess: 8 match Content-Type: Message/Partial' ) ;
14399
14400
14401ok( 1 == skipmess(
14402<<'EOM'
14403Return-Path: <gilles@lamiral.info>
14404Received: by lamiral.info (Postfix, from userid 1000)
14405 id 21EB12443BF; Mon, 2 Mar 2015 15:38:35 +0100 (CET)
14406Subject: test: aethaecohngiexao
14407To: <tata@petite.lamiral.info>
14408X-Mailer: mail (GNU Mailutils 2.2)
14409Message-Id: <20150302143835.21EB12443BF@lamiral.info>
14410Content-Type: message/partial;
14411 id="TAN_U_P<1404267997.00007489ed17>";
14412 number=3;
14413 total=3
14414Date: Mon, 2 Mar 2015 15:38:34 +0100 (CET)
14415From: gilles@lamiral.info (Gilles LAMIRAL)
14416
14417test: aethaecohngiexao
14418EOM
14419),
14420 'skipmess: 9 match Content-Type: Message/Partial' ) ;
14421
14422ok( 1 == skipmess(
14423<<'EOM'
14424Date: Mon, 2 Mar 2015 15:38:34 +0100 (CET)
14425From: gilles@lamiral.info (Gilles LAMIRAL)
14426Content-Type: message/partial;
14427 id="TAN_U_P<1404267997.00007489ed17>";
14428 number=3;
14429 total=3
14430
14431test: aethaecohngiexao
14432EOM
14433. "lalala\n" x 3_000_000
14434),
14435 'skipmess: 10 match Content-Type: Message/Partial' ) ;
14436
14437ok( 0 == skipmess(
14438<<'EOM'
14439Date: Mon, 2 Mar 2015 15:38:34 +0100 (CET)
14440From: gilles@lamiral.info (Gilles LAMIRAL)
14441
14442test: aethaecohngiexao
14443EOM
14444. "lalala\n" x 3_000_000
14445),
14446 'skipmess: 11 match Content-Type: Message/Partial' ) ;
14447
14448
14449ok( 0 == skipmess(
14450<<"EOM"
14451From: fff\r
14452To: fff\r
14453Subject: Testing imapsync --skipmess\r
14454Date: Mon, 22 Aug 2011 08:40:20 +0800\r
14455Mime-Version: 1.0\r
14456Content-Type: text/plain; charset=iso-8859-1\r
14457Content-Transfer-Encoding: 7bit\r
14458\r
14459EOM
14460. qq{!#"d%&'()*+,-./0123456789:;<=>?\@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefg\r\n } x 32_730
14461),
14462 'skipmess: 12 not match Content-Type: Message/Partial' ) ;
14463 # Complex regular subexpression recursion limit (32766) exceeded with more lines
14464 # exit;
14465
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014466
14467 undef @skipmess ;
14468 note( 'Leaving tests_skipmess()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014469 return ;
14470}
14471
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014472
14473sub tests_skipmess_neg
14474{
14475 note( 'Entering tests_skipmess_neg()' ) ;
14476
14477
14478 @skipmess = ('m{i}') ;
14479 ok( 1 == skipmess( 'Hi!' ), 'skipmess: i string yes' ) ;
14480 ok( 0 == skipmess( 'Ho!' ), 'skipmess: i string no' ) ;
14481
14482 @skipmess = ('m{\A(?!.*i)}') ;
14483 ok( 0 == skipmess( 'Hi!' ), 'skipmess: not i string no' ) ;
14484 ok( 1 == skipmess( 'Ho!' ), 'skipmess: not i string yes' ) ;
14485
14486
14487 @skipmess = ('m{\A(?!.*^From:[^\n]*tartanpion\@machin\.truc)}xms') ;
14488
14489 ok( 0 == skipmess(
14490<<'EOM'
14491Date: Sat, 10 Jul 2010 05:34:45 -0700
14492From: <tartanpion@machin.truc>
14493
14494Bye.
14495EOM
14496),
14497 'skipmess: 1 not From tartanpion@machin.truc' ) ;
14498
14499ok( 1 == skipmess(
14500<<'EOM'
14501Date: Sat, 10 Jul 2010 05:34:45 -0700
14502From: <kikiki@machin.truc>
14503
14504Bye.
14505EOM
14506),
14507 'skipmess: 2 not From tartanpion@machin.truc' ) ;
14508
14509
14510
14511
14512 ok( 0 == skipmess(
14513<<'EOM'
14514Date: Sat, 10 Jul 2010 05:34:45 -0700
14515From: <tartanpion@machin.truc>
14516
14517 From: <tartanpion@machin.truc>
14518Bye.
14519EOM
14520),
14521 'skipmess: 3 not From tartanpion@machin.truc' ) ;
14522
14523ok( 1 == skipmess(
14524<<'EOM'
14525Date: Sat, 10 Jul 2010 05:34:45 -0700
14526From: <kikiki@machin.truc>
14527
14528 From: <tartanpion@machin.truc>
14529Bye.
14530EOM
14531),
14532 'skipmess: 4 not From tartanpion@machin.truc' ) ;
14533
14534
14535
14536
14537 undef @skipmess ;
14538 note( 'Leaving tests_skipmess_neg()' ) ;
14539 return ;
14540}
14541
14542
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014543sub skipmess
14544{
14545 my ( $string ) = @_ ;
14546 my $match ;
14547 #myprint( "$string\n" ) ;
14548 foreach my $skipmess ( @skipmess ) {
14549 $sync->{ debug } and myprint( "eval \$match = \$string =~ $skipmess\n" ) ;
14550 my $ret = eval "\$match = \$string =~ $skipmess ; 1" ;
14551 #myprint( "eval [$ret]\n" ) ;
14552 $sync->{ debug } and myprint( "match [$match]\n" ) ;
14553 if ( ( not $ret ) or $EVAL_ERROR ) {
14554 myprint( "Error: eval skipmess '$skipmess': $EVAL_ERROR" ) ;
14555 return( undef ) ;
14556 }
14557 return( $match ) if ( $match ) ;
14558 }
14559 return( $match ) ;
14560}
14561
14562
14563
14564
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010014565sub tests_bytes_display_string_bin
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014566{
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010014567 note( 'Entering tests_bytes_display_string_bin()' ) ;
14568
14569 is( 'NA', bytes_display_string_bin( ), 'bytes_display_string_bin: no args => NA' ) ;
14570 is( 'NA', bytes_display_string_bin( undef ), 'bytes_display_string_bin: undef => NA' ) ;
14571 is( 'NA', bytes_display_string_bin( 'blabla' ), 'bytes_display_string_bin: blabla => NA' ) ;
14572
14573 is( '0.000 KiB', bytes_display_string_bin( 0 ), 'bytes_display_string_bin: 0 => 0.000 KiB' ) ;
14574 is( '0.001 KiB', bytes_display_string_bin( 1 ), 'bytes_display_string_bin: 1 => 0.001 KiB' ) ;
14575 is( '0.010 KiB', bytes_display_string_bin( 10 ), 'bytes_display_string_bin: 10 => 0.010 KiB' ) ;
14576 is( '0.976 KiB', bytes_display_string_bin( 999 ), 'bytes_display_string_bin: 999 => 0.976 KiB' ) ;
14577 note( bytes_display_string_bin( 999 ) ) ;
14578
14579 is( '0.999 KiB', bytes_display_string_bin( 1023 ), 'bytes_display_string_bin: 1023 => 0.999 KiB' ) ;
14580 note( bytes_display_string_bin( 1023 ) ) ;
14581 is( '1.000 KiB', bytes_display_string_bin( 1024 ), 'bytes_display_string_bin: 1024 => 1.000 KiB' ) ;
14582 note( bytes_display_string_bin( 1024 ) ) ;
14583 is( '1.001 KiB', bytes_display_string_bin( 1025 ), 'bytes_display_string_bin: 1025 => 1.001 KiB' ) ;
14584
14585 is( '9.999 KiB', bytes_display_string_bin( 10_239 ), 'bytes_display_string_bin: 10_239 => 9.999 KiB' ) ;
14586 note( bytes_display_string_bin( 10_239 ) ) ;
14587
14588 is( '10.000 KiB', bytes_display_string_bin( 10_240 ), 'bytes_display_string_bin: 10_240 => 10.000 KiB' ) ;
14589 note( bytes_display_string_bin( 10_240 ) ) ;
14590
14591 is( '999.999 KiB', bytes_display_string_bin( 1_023_999 ), 'bytes_display_string_bin: 1_023_999 => 999.999 KiB' ) ;
14592 note( bytes_display_string_bin( 1_023_999 ) ) ;
14593
14594 is( '0.977 MiB', bytes_display_string_bin( 1_024_000 ), 'bytes_display_string_bin: 1_024_000 => 0.977 MiB' ) ;
14595 note( bytes_display_string_bin( 1_024_000 ) ) ;
14596
14597 is( '0.999 MiB', bytes_display_string_bin( 1_047_527 ), 'bytes_display_string_bin: 1_047_527 => 0.999 MiB' ) ;
14598 note( bytes_display_string_bin( 1_047_527 ) ) ;
14599
14600 is( '0.999 MiB', bytes_display_string_bin( 1_048_051 ), 'bytes_display_string_bin: 1_048_051 => 0.999 MiB' ) ;
14601 note( bytes_display_string_bin( 1_048_051 ) ) ;
14602
14603 is( '1.000 MiB', bytes_display_string_bin( 1_048_052 ), 'bytes_display_string_bin: 1_048_052 => 1.000 MiB' ) ;
14604 note( bytes_display_string_bin( 1_048_052 ) ) ;
14605
14606 is( '1.000 MiB', bytes_display_string_bin( 1_048_575 ), 'bytes_display_string_bin: 1_048_575 => 1.000 MiB' ) ;
14607 is( '1.000 MiB', bytes_display_string_bin( 1_048_576 ), 'bytes_display_string_bin: 1_048_576 => 1.000 MiB' ) ;
14608
14609 is( '1.000 GiB', bytes_display_string_bin( 1_073_741_823 ), 'bytes_display_string_bin: 1_073_741_823 => 1.000 GiB' ) ;
14610 is( '1.000 GiB', bytes_display_string_bin( 1_073_741_824 ), 'bytes_display_string_bin: 1_073_741_824 => 1.000 GiB' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014611
14612
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010014613 is( '1.000 TiB', bytes_display_string_bin( 1_099_511_627_775 ), 'bytes_display_string_bin: 1_099_511_627_775 => 1.000 TiB' ) ;
14614 is( '1.000 TiB', bytes_display_string_bin( 1_099_511_627_776 ), 'bytes_display_string_bin: 1_099_511_627_776 => 1.000 TiB' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014615
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010014616 is( '1.000 PiB', bytes_display_string_bin( 1_125_899_906_842_623 ), 'bytes_display_string_bin: 1_125_899_906_842_623 => 1.000 PiB' ) ;
14617 is( '1.000 PiB', bytes_display_string_bin( 1_125_899_906_842_624 ), 'bytes_display_string_bin: 1_125_899_906_842_624 => 1.000 PiB' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014618
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010014619 is( '1024.000 PiB', bytes_display_string_bin( 1_152_921_504_606_846_975 ), 'bytes_display_string_bin: 1_152_921_504_606_846_975 => 1024.000 PiB' ) ;
14620 is( '1024.000 PiB', bytes_display_string_bin( 1_152_921_504_606_846_976 ), 'bytes_display_string_bin: 1_152_921_504_606_846_976 => 1024.000 PiB' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014621
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010014622 is( '1048576.000 PiB', bytes_display_string_bin( 1_180_591_620_717_411_303_424 ), 'bytes_display_string_bin: 1_180_591_620_717_411_303_424 => 1048576.000 PiB' ) ;
14623 note( bytes_display_string_bin( 1_180_591_620_717_411_303_424 ) ) ;
14624 note( bytes_display_string_bin( 3_000_000_000 ) ) ;
14625 note( 'Leaving tests_bytes_display_string_bin()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014626
14627 return ;
14628}
14629
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010014630sub bytes_display_string_bin
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014631{
14632 my ( $bytes ) = @_ ;
14633
14634 my $readable_value = q{} ;
14635
14636 if ( ! defined( $bytes ) ) {
14637 return( 'NA' ) ;
14638 }
14639
14640 if ( not match_number( $bytes ) ) {
14641 return( 'NA' ) ;
14642 }
14643
14644
14645
14646 SWITCH: {
14647 if ( abs( $bytes ) < ( 1000 * $KIBI ) ) {
14648 $readable_value = mysprintf( '%.3f KiB', $bytes / $KIBI) ;
14649 last SWITCH ;
14650 }
14651 if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI ) ) {
14652 $readable_value = mysprintf( '%.3f MiB', $bytes / ($KIBI * $KIBI) ) ;
14653 last SWITCH ;
14654 }
14655 if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI * $KIBI) ) {
14656 $readable_value = mysprintf( '%.3f GiB', $bytes / ($KIBI * $KIBI * $KIBI) ) ;
14657 last SWITCH ;
14658 }
14659 if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI * $KIBI * $KIBI) ) {
14660 $readable_value = mysprintf( '%.3f TiB', $bytes / ($KIBI * $KIBI * $KIBI * $KIBI) ) ;
14661 last SWITCH ;
14662 } else {
14663 $readable_value = mysprintf( '%.3f PiB', $bytes / ($KIBI * $KIBI * $KIBI * $KIBI * $KIBI) ) ;
14664 }
14665 # if you have exabytes (EiB) of email to transfer, you have too much email!
14666 }
14667 #myprint( "$bytes = $readable_value\n" ) ;
14668 return( $readable_value ) ;
14669}
14670
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010014671sub tests_bytes_display_string_dec
14672{
14673 note( 'Entering tests_bytes_display_string_dec()' ) ;
14674
14675 is( 'NA', bytes_display_string_dec( ), 'bytes_display_string_dec: no args => NA' ) ;
14676 is( 'NA', bytes_display_string_dec( undef ), 'bytes_display_string_dec: undef => NA' ) ;
14677 is( 'NA', bytes_display_string_dec( 'blabla' ), 'bytes_display_string_dec: blabla => NA' ) ;
14678
14679 is( '0 bytes', bytes_display_string_dec( 0 ), 'bytes_display_string_dec: 0 => 0 bytes' ) ;
14680 is( '1 bytes', bytes_display_string_dec( 1 ), 'bytes_display_string_dec: 1 => 1 bytes' ) ;
14681 is( '10 bytes', bytes_display_string_dec( 10 ), 'bytes_display_string_dec: 10 => 10 bytes' ) ;
14682 is( '999 bytes', bytes_display_string_dec( 999 ), 'bytes_display_string_dec: 999 => 999 bytes' ) ;
14683
14684 is( '1.000 KB', bytes_display_string_dec( 1000 ), 'bytes_display_string_dec: 1000 => 1.000 KB' ) ;
14685 is( '1.001 KB', bytes_display_string_dec( 1001 ), 'bytes_display_string_dec: 1000 => 1.1001 KB' ) ;
14686
14687 is( '999.999 KB', bytes_display_string_dec( 999_999 ), 'bytes_display_string_dec: 999_999 => 999.999 KB' ) ;
14688
14689 is( '1.000 MB', bytes_display_string_dec( 1_000_000 ), 'bytes_display_string_dec: 1_000_000 => 1.000 MB' ) ;
14690 is( '1.000 MB', bytes_display_string_dec( 1_000_500 ), 'bytes_display_string_dec: 1_000_500 => 1.000 MB' ) ;
14691 is( '1.001 MB', bytes_display_string_dec( 1_000_501 ), 'bytes_display_string_dec: 1_000_501 => 1.001 MB' ) ;
14692 is( '999.999 MB', bytes_display_string_dec( 999_999_000 ), 'bytes_display_string_dec: 999_999_000 => 999.999 MB' ) ;
14693 is( '999.999 MB', bytes_display_string_dec( 999_999_499 ), 'bytes_display_string_dec: 999_999_499 => 999.999 MB' ) ;
14694 is( '1.000 GB', bytes_display_string_dec( 999_999_500 ), 'bytes_display_string_dec: 999_999_500 => 1.000 GB' ) ;
14695
14696 is( '1.000 GB', bytes_display_string_dec( 1_000_000_000 ), 'bytes_display_string_dec: 1_000_000_000 => 1.000 GB' ) ;
14697 is( '1.000 GB', bytes_display_string_dec( 1_000_500_000 ), 'bytes_display_string_dec: 1_000_500_000 => 1.000 GB' ) ;
14698 is( '1.001 GB', bytes_display_string_dec( 1_000_500_001 ), 'bytes_display_string_dec: 1_000_501_000 => 1.001 GB' ) ;
14699 is( '999.999 GB', bytes_display_string_dec( 999_999_000_000 ), 'bytes_display_string_dec: 999_999_000_000 => 999.999 GB' ) ;
14700 is( '999.999 GB', bytes_display_string_dec( 999_999_499_999 ), 'bytes_display_string_dec: 999_999_499_999 => 999.999 GB' ) ;
14701 is( '1.000 TB', bytes_display_string_dec( 999_999_500_000 ), 'bytes_display_string_dec: 999_999_500_000 => 1.000 TB' ) ;
14702
14703 is( '1.000 TB', bytes_display_string_dec( 1_000_000_000_000 ), 'bytes_display_string_dec: 1_000_000_000_000 => 1.000 TB' ) ;
14704 is( '1.000 TB', bytes_display_string_dec( 1_000_500_000_000 ), 'bytes_display_string_dec: 1_000_500_000_000 => 1.000 TB' ) ;
14705 is( '1.001 TB', bytes_display_string_dec( 1_000_500_000_001 ), 'bytes_display_string_dec: 1_000_500_000_000 => 1.000 TB' ) ;
14706 is( '999.999 TB', bytes_display_string_dec( 999_999_000_000_000 ), 'bytes_display_string_dec: 999_999_000_000_000 => 999.999 TB' ) ;
14707 is( '999.999 TB', bytes_display_string_dec( 999_999_499_999_999 ), 'bytes_display_string_dec: 999_999_499_999_999 => 999.999 TB' ) ;
14708 is( '1.000 PB', bytes_display_string_dec( 999_999_500_000_000 ), 'bytes_display_string_dec: 999_999_500_000_000 => 1.000 PB' ) ;
14709
14710 is( '3.000 GB', bytes_display_string_dec( 3_000_000_000 ), 'bytes_display_string_dec: 3_000_000_000 => 3.000 GB' ) ;
14711
14712 note( 'Leaving tests_bytes_display_string_dec()' ) ;
14713 return ;
14714}
14715
14716sub bytes_display_string_dec
14717{
14718 my ( $bytes ) = @_ ;
14719
14720 my $readable_value = q{} ;
14721
14722 if ( ! defined( $bytes ) ) {
14723 return( 'NA' ) ;
14724 }
14725
14726 if ( not match_number( $bytes ) ) {
14727 return( 'NA' ) ;
14728 }
14729
14730 SWITCH: {
14731 if ( abs( $bytes ) < ( 1000 ) ) {
14732 $readable_value = mysprintf( '%.0f bytes', $bytes ) ;
14733 last SWITCH ;
14734 }
14735 if ( abs( $bytes ) < ( 1000**2 ) ) {
14736 $readable_value = mysprintf( '%.3f KB', $bytes / 1000 ) ;
14737 last SWITCH ;
14738 }
14739 if ( abs( $bytes ) < ( 999_999_500 ) ) {
14740 $readable_value = mysprintf( '%.3f MB', $bytes / ( 1000**2 ) ) ;
14741 last SWITCH ;
14742 }
14743 if ( abs( $bytes ) < ( 999_999_500_000 ) ) {
14744 $readable_value = mysprintf( '%.3f GB', $bytes / ( 1000**3 ) ) ;
14745 last SWITCH ;
14746 }
14747 if ( abs( $bytes ) < ( 999_999_500_000_000 ) ) {
14748 $readable_value = mysprintf( '%.3f TB', $bytes / ( 1000**4 ) ) ;
14749 last SWITCH ;
14750 } else {
14751 $readable_value = mysprintf( '%.3f PB', $bytes / ( 1000**5 ) ) ;
14752 }
14753 # if you have exabytes (EiB) of email to transfer, you have too much email!
14754 }
14755 #myprint( "$bytes = $readable_value\n" ) ;
14756
14757 return( $readable_value ) ;
14758}
14759
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014760
14761sub tests_useheader_suggestion
14762{
14763 note( 'Entering tests_useheader_suggestion()' ) ;
14764
14765 is( undef, useheader_suggestion( ), 'useheader_suggestion: no args => undef' ) ;
14766 my $mysync = {} ;
14767
14768 $mysync->{ h1_nb_msg_noheader } = 0 ;
14769 is( q{}, useheader_suggestion( $mysync ), 'useheader_suggestion: h1_nb_msg_noheader count null => no suggestion' ) ;
14770 $mysync->{ h1_nb_msg_noheader } = 2 ;
14771 is( q{in order to sync those 2 unidentified messages, add option --addheader}, useheader_suggestion( $mysync ),
14772 'useheader_suggestion: h1_nb_msg_noheader count 2 => suggestion of --addheader' ) ;
14773
14774 note( 'Leaving tests_useheader_suggestion()' ) ;
14775 return ;
14776}
14777
14778sub useheader_suggestion
14779{
14780 my $mysync = shift ;
14781 if ( ! defined $mysync->{ h1_nb_msg_noheader } )
14782 {
14783 return ;
14784 }
14785 elsif ( 1 <= $mysync->{ h1_nb_msg_noheader } )
14786 {
14787 return qq{in order to sync those $mysync->{ h1_nb_msg_noheader } unidentified messages, add option --addheader} ;
14788 }
14789 else
14790 {
14791 return q{} ;
14792 }
14793 return ;
14794}
14795
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014796sub do_and_print_stats
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014797{
14798 my $mysync = shift ;
14799
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014800 if ( ! $mysync->{can_do_stats} ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014801 return ;
14802 }
14803
14804 my $timeend = time ;
14805 my $timediff = $timeend - $mysync->{timestart} ;
14806
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014807 my $timeend_str = localtimez( $timeend ) ;
14808
14809 my $cpu_time = cpu_time( $mysync ) ;
14810 my $cpu_percent = cpu_percent( $mysync, $cpu_time, $timediff ) ;
14811 my $cpu_percent_global = cpu_percent_global( $mysync, $cpu_percent ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014812
14813 my $memory_consumption_at_end = memory_consumption( ) || 0 ;
14814 my $memory_consumption_at_start = $mysync->{ memory_consumption_at_start } || 0 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014815 my $memory_ratio = ( $mysync->{ biggest_message_transferred } ) ?
14816 mysprintf( '%.1f', $memory_consumption_at_end / $mysync->{ biggest_message_transferred } ) : 'NA' ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014817
14818 # my $useheader_suggestion = useheader_suggestion( $mysync ) ;
14819 myprint( "++++ Statistics\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014820 myprint( "Transfer started on : $mysync->{ timestart_str }\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014821 myprint( "Transfer ended on : $timeend_str\n" ) ;
14822 myprintf( "Transfer time : %.1f sec\n", $timediff ) ;
14823 myprint( "Folders synced : $h1_folders_wanted_ct/$h1_folders_wanted_nb synced\n" ) ;
14824 myprint( "Messages transferred : $mysync->{ nb_msg_transferred } " ) ;
14825 myprint( "(could be $nb_msg_skipped_dry_mode without dry mode)" ) if ( $mysync->{dry} ) ;
14826 myprint( "\n" ) ;
14827 myprint( "Messages skipped : $mysync->{ nb_msg_skipped }\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014828 myprint( "Messages found duplicate on host1 : $mysync->{ acc1 }->{ nb_msg_duplicate }\n" ) ;
14829 myprint( "Messages found duplicate on host2 : $mysync->{ acc2 }->{ nb_msg_duplicate }\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014830 myprint( "Messages found crossduplicate on host2 : $mysync->{ h2_nb_msg_crossdup }\n" ) ;
14831 myprint( "Messages void (noheader) on host1 : $mysync->{ h1_nb_msg_noheader } ", useheader_suggestion( $mysync ), "\n" ) ;
14832 myprint( "Messages void (noheader) on host2 : $h2_nb_msg_noheader\n" ) ;
14833 nb_messages_in_1_not_in_2( $mysync ) ;
14834 nb_messages_in_2_not_in_1( $mysync ) ;
14835 myprintf( "Messages found in host1 not in host2 : %s messages\n", $mysync->{ nb_messages_in_1_not_in_2 } ) ;
14836 myprintf( "Messages found in host2 not in host1 : %s messages\n", $mysync->{ nb_messages_in_2_not_in_1 } ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014837 myprint( "Messages deleted on host1 : $mysync->{ acc1 }->{ nb_msg_deleted }\n" ) ;
14838 myprint( "Messages deleted on host2 : $mysync->{ acc2 }->{ nb_msg_deleted }\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014839 myprintf( "Total bytes transferred : %s (%s)\n",
14840 $mysync->{total_bytes_transferred},
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010014841 bytes_display_string_bin( $mysync->{total_bytes_transferred} ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014842 myprintf( "Total bytes skipped : %s (%s)\n",
14843 $mysync->{ total_bytes_skipped },
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010014844 bytes_display_string_bin( $mysync->{ total_bytes_skipped } ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014845 $timediff ||= 1 ; # No division per 0
14846 myprintf("Message rate : %.1f messages/s\n", $mysync->{nb_msg_transferred} / $timediff ) ;
14847 myprintf("Average bandwidth rate : %.1f KiB/s\n", $mysync->{total_bytes_transferred} / $KIBI / $timediff ) ;
14848 myprint( "Reconnections to host1 : $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT}\n" ) ;
14849 myprint( "Reconnections to host2 : $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT}\n" ) ;
14850 myprintf("Memory consumption at the end : %.1f MiB (started with %.1f MiB)\n",
14851 $memory_consumption_at_end / $KIBI / $KIBI,
14852 $memory_consumption_at_start / $KIBI / $KIBI ) ;
14853 myprint( "Load end is : " . ( join( q{ }, loadavg( ) ) || 'unknown' ), " on $mysync->{cpu_number} cores\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014854 myprint( "CPU time and %cpu : $cpu_time sec $cpu_percent %cpu $cpu_percent_global %allcpus\n" ) ;
14855 myprintf("Biggest message transferred : %s bytes (%s)\n",
14856 $mysync->{ biggest_message_transferred },
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010014857 bytes_display_string_bin( $mysync->{ biggest_message_transferred } ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014858 myprint( "Memory/biggest message ratio : $memory_ratio\n" ) ;
14859 if ( $mysync->{ foldersizesatend } and $mysync->{ foldersizes } ) {
14860
14861
14862 my $nb_msg_start_diff = diff_or_NA( $mysync->{ h2_nb_msg_start }, $mysync->{ h1_nb_msg_start } ) ;
14863 my $bytes_start_diff = diff_or_NA( $mysync->{ h2_bytes_start }, $mysync->{ h1_bytes_start } ) ;
14864
14865 myprintf("Start difference host2 - host1 : %s messages, %s bytes (%s)\n", $nb_msg_start_diff,
14866 $bytes_start_diff,
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010014867 bytes_display_string_bin( $bytes_start_diff ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014868
14869 my $nb_msg_end_diff = diff_or_NA( $h2_nb_msg_end, $h1_nb_msg_end ) ;
14870 my $bytes_end_diff = diff_or_NA( $h2_bytes_end, $h1_bytes_end ) ;
14871
14872 myprintf("Final difference host2 - host1 : %s messages, %s bytes (%s)\n", $nb_msg_end_diff,
14873 $bytes_end_diff,
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010014874 bytes_display_string_bin( $bytes_end_diff ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014875 }
14876
14877 comment_on_final_diff_in_1_not_in_2( $mysync ) ;
14878 comment_on_final_diff_in_2_not_in_1( $mysync ) ;
14879 myprint( "Detected $mysync->{nb_errors} errors\n\n" ) ;
14880
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010014881 myprint( $mysync->{ warn_release }, "\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014882 myprint( homepage( ), "\n" ) ;
14883 return ;
14884}
14885
14886sub diff_or_NA
14887{
14888 my( $n1, $n2 ) = @ARG ;
14889
14890 if ( not defined $n1 or not defined $n2 ) {
14891 return 'NA' ;
14892 }
14893
14894 if ( not match_number( $n1 )
14895 or not match_number( $n2 ) ) {
14896 return 'NA' ;
14897 }
14898
14899 return( $n1 - $n2 ) ;
14900}
14901
14902sub match_number
14903{
14904 my $n = shift @ARG ;
14905
14906 if ( not defined $n ) {
14907 return 0 ;
14908 }
14909 if ( $n =~ /[0-9]+\.?[0-9]?/x ) {
14910 return 1 ;
14911 }
14912 else {
14913 return 0 ;
14914 }
14915}
14916
14917
14918sub tests_match_number
14919{
14920 note( 'Entering tests_match_number()' ) ;
14921
14922
14923 is( 0, match_number( ), 'match_number: no parameters => 0' ) ;
14924 is( 0, match_number( undef ), 'match_number: undef => 0' ) ;
14925 is( 0, match_number( 'blabla' ), 'match_number: blabla => 0' ) ;
14926 is( 1, match_number( 0 ), 'match_number: 0 => 1' ) ;
14927 is( 1, match_number( 1 ), 'match_number: 1 => 1' ) ;
14928 is( 1, match_number( 1.0 ), 'match_number: 1.0 => 1' ) ;
14929 is( 1, match_number( 0.0 ), 'match_number: 0.0 => 1' ) ;
14930
14931 note( 'Leaving tests_match_number()' ) ;
14932 return ;
14933}
14934
14935
14936
14937sub tests_diff_or_NA
14938{
14939 note( 'Entering tests_diff_or_NA()' ) ;
14940
14941
14942 is( 'NA', diff_or_NA( ), 'diff_or_NA: no parameters => NA' ) ;
14943 is( 'NA', diff_or_NA( undef ), 'diff_or_NA: undef => NA' ) ;
14944 is( 'NA', diff_or_NA( undef, undef ), 'diff_or_NA: undef undef => NA' ) ;
14945 is( 'NA', diff_or_NA( undef, 1 ), 'diff_or_NA: undef 1 => NA' ) ;
14946 is( 'NA', diff_or_NA( 1, undef ), 'diff_or_NA: 1 undef => NA' ) ;
14947 is( 'NA', diff_or_NA( 'blabla', 1 ), 'diff_or_NA: blabla 1 => NA' ) ;
14948 is( 'NA', diff_or_NA( 1, 'blabla' ), 'diff_or_NA: 1 blabla => NA' ) ;
14949 is( 0, diff_or_NA( 1, 1 ), 'diff_or_NA: 1 1 => 0' ) ;
14950 is( 1, diff_or_NA( 1, 0 ), 'diff_or_NA: 1 0 => 1' ) ;
14951 is( -1, diff_or_NA( 0, 1 ), 'diff_or_NA: 0 1 => -1' ) ;
14952 is( 0, diff_or_NA( 1.0, 1 ), 'diff_or_NA: 1.0 1 => 0' ) ;
14953 is( 1, diff_or_NA( 1.0, 0 ), 'diff_or_NA: 1.0 0 => 1' ) ;
14954 is( -1, diff_or_NA( 0, 1.0 ), 'diff_or_NA: 0 1.0 => -1' ) ;
14955
14956 note( 'Leaving tests_diff_or_NA()' ) ;
14957 return ;
14958}
14959
14960sub homepage
14961{
14962 return( 'Homepage: https://imapsync.lamiral.info/' ) ;
14963}
14964
14965
14966sub load_modules
14967{
14968 if ( $sync->{ssl1}
14969 or $sync->{ssl2}
14970 or $sync->{tls1}
14971 or $sync->{tls2}) {
14972 if ( $sync->{inet4} ) {
14973 IO::Socket::SSL->import( 'inet4' ) ;
14974 }
14975 if ( $sync->{inet6} ) {
14976 IO::Socket::SSL->import( 'inet6' ) ;
14977 }
14978 }
14979 return ;
14980}
14981
14982
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014983# Globals: $skipsize $wholeheaderifneeded
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014984sub parse_header_msg
14985{
14986 my ( $mysync, $imap, $m_uid, $s_heads, $s_fir, $side, $s_hash ) = @_ ;
14987
14988 my $head = $s_heads->{$m_uid} ;
14989 my $headnum = scalar keys %{ $head } ;
14990 $mysync->{ debug } and myprint( "$side: uid $m_uid number of headers, pass one: ", $headnum, "\n" ) ;
14991
14992 if ( ( ! $headnum ) and ( $wholeheaderifneeded ) ){
14993 $mysync->{ debug } and myprint( "$side: uid $m_uid no header by parse_headers so taking whole header with BODY.PEEK[HEADER]\n" ) ;
14994 $imap->fetch($m_uid, 'BODY.PEEK[HEADER]' ) ;
14995 my $whole_header = $imap->_transaction_literals ;
14996
14997 #myprint( $whole_header ) ;
14998 $head = decompose_header( $whole_header ) ;
14999
15000 $headnum = scalar keys %{ $head } ;
15001 $mysync->{ debug } and myprint( "$side: uid $m_uid number of headers, pass two: ", $headnum, "\n" ) ;
15002 }
15003
15004 #myprint( Data::Dumper->Dump( [ $head, \%useheader ] ) ) ;
15005
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015006 my $headstr = header_construct( $mysync, $head, $side, $m_uid ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015007
15008 if ( ( ! $headstr ) and ( $mysync->{addheader} ) and ( $side eq 'Host1' ) ) {
15009 my $header = add_header( $m_uid ) ;
15010 $mysync->{ debug } and myprint( "$side: uid $m_uid no header found so adding our own [$header]\n" ) ;
15011 $headstr .= uc $header ;
15012 $s_fir->{$m_uid}->{NO_HEADER} = 1;
15013 }
15014
15015 return if ( ! $headstr ) ;
15016
15017 my $size = $s_fir->{$m_uid}->{'RFC822.SIZE'} ;
15018 my $flags = $s_fir->{$m_uid}->{'FLAGS'} ;
15019 my $idate = $s_fir->{$m_uid}->{'INTERNALDATE'} ;
15020 $size = length $headstr unless ( $size ) ;
15021 my $m_md5 = md5_base64( $headstr ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015022
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015023 my $key ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015024 if ( $skipsize ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015025 $key = "$m_md5";
15026 }
15027 else {
15028 $key = "$m_md5:$size";
15029 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015030
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015031 if ( exists $s_hash->{"$key"} )
15032 {
15033 # 0 return code is used to identify duplicate message hash
15034 my $dup_ref = $s_hash->{"$key"}->{'U'} ;
15035 my $num = scalar( @{ $dup_ref } ) ;
15036 push( @{ $dup_ref }, $m_uid ) ;
15037 my $keydup = "$key#$num" ;
15038 $mysync->{ debug } and myprint( "$side: uid $m_uid sig $keydup size $size idate $idate dup @{ $dup_ref }\n" ) ;
15039 if ( $mysync->{ syncduplicates } )
15040 {
15041 $s_hash->{"$keydup"}{'5'} = $m_md5 ;
15042 $s_hash->{"$keydup"}{'s'} = $size ;
15043 $s_hash->{"$keydup"}{'D'} = $idate ;
15044 $s_hash->{"$keydup"}{'F'} = $flags ;
15045 $s_hash->{"$keydup"}{'m'} = $m_uid ;
15046 }
15047 return 0 ;
15048 }
15049 else
15050 {
15051 $s_hash->{"$key"}{'5'} = $m_md5 ;
15052 $s_hash->{"$key"}{'s'} = $size ;
15053 $s_hash->{"$key"}{'D'} = $idate ;
15054 $s_hash->{"$key"}{'F'} = $flags ;
15055 $s_hash->{"$key"}{'m'} = $m_uid ;
15056 $s_hash->{"$key"}{'U'} = [ $m_uid ] ; # ? or [ ] ?
15057 $mysync->{ debug } and myprint( "$side: uid $m_uid sig $key size $size idate $idate\n" ) ;
15058 return( 1 ) ;
15059 }
15060
15061 # we should not be here
15062 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015063}
15064
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015065sub tests_header_construct
15066{
15067 note( 'Entering tests_header_construct()' ) ;
15068
15069 is( undef, header_construct( ), 'header_construct: no args => undef' ) ;
15070 my $mysync = {} ;
15071 my $head = {
15072 'key1' => [ 'val1_key1' ]
15073 } ;
15074 is( undef, header_construct( $mysync, $head, 'Host1', '1' ), 'header_construct: key1 val1_key1 no useheader => undef' ) ;
15075
15076 $mysync->{useheader}->{ 'KEY1' } = 1 ;
15077 is( 'KEY1: VAL1_KEY1', header_construct( $mysync, $head, 'Host1', '1' ), 'header_construct: key1 val1_key1 => KEY1: VAL1_KEY1' ) ;
15078
15079
15080
15081 $head = {
15082 'key1' => [ 'val1_key1', 'val3_key1', 'val2_key1' ]
15083 } ;
15084 is( 'KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1', header_construct( $mysync, $head, 'Host1', '1' ),
15085 'header_construct: key1 val1_key1 val3_key1 val2_key1 => KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1' ) ;
15086
15087 $head = {
15088 'key1' => [ 'val1_key1', 'val3_key1', ' val2_key1' ]
15089 } ;
15090 is( 'KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1', header_construct( $mysync, $head, 'Host1', '1' ),
15091 'header_construct: key1 val1_key1 val3_key1 val2_key1 => KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1' ) ;
15092
15093 $mysync->{useheader}->{ 'ALL' } = 1 ;
15094
15095 is( 'KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1', header_construct( $mysync, $head, 'Host1', '1' ),
15096 'header_construct: key1 val1_key1 val3_key1 val2_key1 useheader ALL => KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1' ) ;
15097
15098 $mysync->{skipheader} = 'key1' ;
15099 is( undef, header_construct( $mysync, $head, 'Host1', '1' ),
15100 'header_construct: key1 val1_key1 val3_key1 val2_key1 useheader ALL => undef' ) ;
15101
15102 $head = {
15103 'key1' => [ 'val1_key1', 'val3_key1', ' val2_key1' ],
15104 'key2' => [ 'val1_key2', 'val3_key2', ' val2_key2' ]
15105 } ;
15106 is( 'KEY2: VAL1_KEY2KEY2: VAL2_KEY2KEY2: VAL3_KEY2', header_construct( $mysync, $head, 'Host1', '1' ),
15107 'header_construct: ... useheader ALL skipheader key1 => KEY2: VAL1_KEY2KEY2: VAL2_KEY2KEY2: VAL3_KEY2' ) ;
15108
15109
15110 note( 'Leaving tests_header_construct()' ) ;
15111 return ;
15112}
15113
15114
15115# No global in header_construct
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015116sub header_construct
15117{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015118 my( $mysync, $head, $side, $m_uid ) = @_ ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015119
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015120 my @headstr ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015121 foreach my $h ( sort keys %{ $head } ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015122 next if ( not ( exists $mysync->{useheader}->{ uc $h } )
15123 and ( not exists $mysync->{useheader}->{ 'ALL' } )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015124 ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015125 foreach my $val ( @{$head->{$h}} ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015126
15127 my $H = header_line_normalize( $h, $val ) ;
15128
15129 # show stuff in debug mode
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015130 $mysync->{ debug } and myprint( "$side uid $m_uid header [$H]", "\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015131
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015132 if ( $mysync->{skipheader} and $H =~ m/$mysync->{skipheader}/xi) {
15133 $mysync->{ debug } and myprint( "$side uid $m_uid skipping header [$H]\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015134 next ;
15135 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015136 push @headstr, $H ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015137 }
15138 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015139 my $headstr = join( '', sort @headstr ) || undef ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015140 return( $headstr ) ;
15141}
15142
15143
15144sub header_line_normalize
15145{
15146 my( $header_key, $header_val ) = @_ ;
15147
15148 # no 8-bit data in headers !
15149 $header_val =~ s/[\x80-\xff]/X/xog;
15150
15151 # change tabulations to space (Gmail bug on with "Received:" on multilines)
15152 $header_val =~ s/\t/\ /xgo ;
15153
15154 # remove the first blanks ( dbmail bug? )
15155 $header_val =~ s/^\s*//xo;
15156
15157 # remove the last blanks ( Gmail bug )
15158 $header_val =~ s/\s*$//xo;
15159
15160 # remove successive blanks ( Mailenable does it )
15161 $header_val =~ s/\s+/ /xgo;
15162
15163 # remove Message-Id value domain part ( Mailenable changes it )
15164 if ( ( $messageidnodomain ) and ( 'MESSAGE-ID' eq uc $header_key ) ) { $header_val =~ s/^([^@]+).*$/$1/xo ; }
15165
15166 # and uppercase header line
15167 # (dbmail and dovecot)
15168
15169 my $header_line = uc "$header_key: $header_val" ;
15170
15171 return( $header_line ) ;
15172}
15173
15174sub tests_header_line_normalize
15175{
15176 note( 'Entering tests_header_line_normalize()' ) ;
15177
15178
15179 ok( ': ' eq header_line_normalize( q{}, q{} ), 'header_line_normalize: empty args' ) ;
15180 ok( 'HHH: VVV' eq header_line_normalize( 'hhh', 'vvv' ), 'header_line_normalize: hhh vvv ' ) ;
15181 ok( 'HHH: VVV' eq header_line_normalize( 'hhh', ' vvv' ), 'header_line_normalize: remove first blancs' ) ;
15182 ok( 'HHH: AA BB CCC D' eq header_line_normalize( 'hhh', 'aa bb ccc d' ), 'header_line_normalize: remove succesive blanks' ) ;
15183 ok( 'HHH: AA BB CCC' eq header_line_normalize( 'hhh', 'aa bb ccc ' ), 'header_line_normalize: remove last blanks' ) ;
15184 ok( 'HHH: VVV XX YY' eq header_line_normalize( 'hhh', "vvv\t\txx\tyy" ), 'header_line_normalize: tabs' ) ;
15185 ok( 'HHH: XABX' eq header_line_normalize( 'hhh', "\x80AB\xff" ), 'header_line_normalize: 8bit' ) ;
15186
15187 note( 'Leaving tests_header_line_normalize()' ) ;
15188 return ;
15189}
15190
15191
15192sub tests_firstline
15193{
15194 note( 'Entering tests_firstline()' ) ;
15195
15196 is( q{}, firstline( 'W/tmp/tests/noexist.txt' ), 'firstline: getting empty string from inexisting W/tmp/tests/noexist.txt' ) ;
15197
15198 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'firstline: mkpath W/tmp/tests/' ) ;
15199
15200 is( "blabla\n" , string_to_file( "blabla\n", 'W/tmp/tests/firstline.txt' ), 'firstline: put blabla in W/tmp/tests/firstline.txt' ) ;
15201 is( 'blabla' , firstline( 'W/tmp/tests/firstline.txt' ), 'firstline: get blabla from W/tmp/tests/firstline.txt' ) ;
15202
15203 is( q{} , string_to_file( q{}, 'W/tmp/tests/firstline2.txt' ), 'firstline: put empty string in W/tmp/tests/firstline2.txt' ) ;
15204 is( q{} , firstline( 'W/tmp/tests/firstline2.txt' ), 'firstline: get empty string from W/tmp/tests/firstline2.txt' ) ;
15205
15206 is( "\n" , string_to_file( "\n", 'W/tmp/tests/firstline3.txt' ), 'firstline: put CR in W/tmp/tests/firstline3.txt' ) ;
15207 is( q{} , firstline( 'W/tmp/tests/firstline3.txt' ), 'firstline: get empty string from W/tmp/tests/firstline3.txt' ) ;
15208
15209 is( "blabla\nTiti\n" , string_to_file( "blabla\nTiti\n", 'W/tmp/tests/firstline4.txt' ), 'firstline: put blabla\nTiti\n in W/tmp/tests/firstline4.txt' ) ;
15210 is( 'blabla' , firstline( 'W/tmp/tests/firstline4.txt' ), 'firstline: get blabla from W/tmp/tests/firstline4.txt' ) ;
15211
15212 note( 'Leaving tests_firstline()' ) ;
15213 return ;
15214}
15215
15216sub firstline
15217{
15218 # extract the first line of a file (without \n)
15219 # return empty string if error or empty string
15220
15221 my $file = shift ;
15222 my $line ;
15223
15224 $line = nthline( $file, 1 ) ;
15225 return $line ;
15226}
15227
15228
15229
15230sub tests_secondline
15231{
15232 note( 'Entering tests_secondline()' ) ;
15233
15234 is( q{}, secondline( 'W/tmp/tests/noexist.txt' ), 'secondline: getting empty string from inexisting W/tmp/tests/noexist.txt' ) ;
15235 is( q{}, secondline( 'W/tmp/tests/noexist.txt', 2 ), 'secondline: 2nd getting empty string from inexisting W/tmp/tests/noexist.txt' ) ;
15236
15237 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'secondline: mkpath W/tmp/tests/' ) ;
15238
15239 is( "L1\nL2\nL3\nL4\n" , string_to_file( "L1\nL2\nL3\nL4\n", 'W/tmp/tests/secondline.txt' ), 'secondline: put L1\nL2\nL3\nL4\n in W/tmp/tests/secondline.txt' ) ;
15240 is( 'L2' , secondline( 'W/tmp/tests/secondline.txt' ), 'secondline: get L2 from W/tmp/tests/secondline.txt' ) ;
15241
15242
15243 note( 'Leaving tests_secondline()' ) ;
15244 return ;
15245}
15246
15247
15248sub secondline
15249{
15250 # extract the second line of a file (without \n)
15251 # return empty string if error or empty string
15252
15253 my $file = shift ;
15254 my $line ;
15255
15256 $line = nthline( $file, 2 ) ;
15257 return $line ;
15258}
15259
15260
15261
15262
15263sub tests_nthline
15264{
15265 note( 'Entering tests_nthline()' ) ;
15266
15267 is( q{}, nthline( 'W/tmp/tests/noexist.txt' ), 'nthline: getting empty string from inexisting W/tmp/tests/noexist.txt' ) ;
15268 is( q{}, nthline( 'W/tmp/tests/noexist.txt', 2 ), 'nthline: 2nd getting empty string from inexisting W/tmp/tests/noexist.txt' ) ;
15269
15270 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'nthline: mkpath W/tmp/tests/' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015271 is( "L1\nL2\nL3\nL4\n" , string_to_file( "L1\nL2\nL3\nL4\n", 'W/tmp/tests/nthline.txt' ), 'nthline: put L1\nL2\nL3\nL4\n in W/tmp/tests/nthline.txt' ) ;
15272 is( 'L3' , nthline( 'W/tmp/tests/nthline.txt', 3 ), 'nthline: get L3 from W/tmp/tests/nthline.txt' ) ;
15273
15274
15275 note( 'Leaving tests_nthline()' ) ;
15276 return ;
15277}
15278
15279
15280sub nthline
15281{
15282 # extract the nth line of a file (without \n)
15283 # return empty string if error or empty string
15284
15285 my $file = shift ;
15286 my $num = shift ;
15287
15288 if ( ! all_defined( $file, $num ) ) { return q{} ; }
15289
15290 my $line ;
15291
15292 $line = ( file_to_array( $file ) )[$num - 1] ;
15293 if ( ! defined $line )
15294 {
15295 return q{} ;
15296 }
15297 else
15298 {
15299 chomp $line ;
15300 return $line ;
15301 }
15302}
15303
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015304sub tests_file_to_array
15305{
15306 note( 'Entering tests_file_to_array()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015307
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015308 is( undef, file_to_array( ), 'file_to_array: no args => undef' ) ;
15309 is( undef, file_to_array( '/noexist' ), 'file_to_array: /noexist => undef' ) ;
15310 is( undef, file_to_array( '/' ), 'file_to_array: reading a directory => undef' ) ;
15311
15312 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'file_to_array: mkpath W/tmp/tests/' ) ;
15313 is( "L1\nL2\nL3\nL4\n" , string_to_file( "L1\nL2\nL3\nL4\n", 'W/tmp/tests/file_to_array.txt' ), 'file_to_array: put L1\nL2\nL3\nL4\n in W/tmp/tests/file_to_array.txt' ) ;
15314 is_deeply( [ "L1\n", "L2\n", "L3\n", "L4\n" ] , [ file_to_array( 'W/tmp/tests/file_to_array.txt' ) ], 'file_to_array: get back L1\n L2\n L3\n L4\n from W/tmp/tests/file_to_array.txt' ) ;
15315
15316 note( 'Leaving tests_file_to_array()' ) ;
15317 return ;
15318}
15319
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015320sub file_to_array
15321{
15322
15323 my( $file ) = shift ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015324 if ( ! $file ) { return ; }
15325 if ( ! -e $file ) { return ; }
15326 if ( ! -f $file ) { return ; }
15327 if ( ! -r $file ) { return ; }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010015328
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015329 my @string ;
15330
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015331 if ( open my $FILE, '<', $file )
15332 {
15333 @string = <$FILE> ;
15334 close $FILE ;
15335 return( @string ) ;
15336 }
15337 else
15338 {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015339 myprint( "Error reading file $file : $OS_ERROR\n" ) ;
15340 return ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015341 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015342}
15343
15344
15345sub tests_file_to_string
15346{
15347 note( 'Entering tests_file_to_string()' ) ;
15348
15349 is( undef, file_to_string( ), 'file_to_string: no args => undef' ) ;
15350 is( undef, file_to_string( '/noexist' ), 'file_to_string: /noexist => undef' ) ;
15351 is( undef, file_to_string( '/' ), 'file_to_string: reading a directory => undef' ) ;
15352 ok( file_to_string( $PROGRAM_NAME ), 'file_to_string: reading myself' ) ;
15353
15354 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'file_to_string: mkpath W/tmp/tests/' ) ;
15355
15356 is( 'lilili', string_to_file( 'lilili', 'W/tmp/tests/canbewritten' ), 'file_to_string: string_to_file filling W/tmp/tests/canbewritten with lilili' ) ;
15357 is( 'lilili', file_to_string( 'W/tmp/tests/canbewritten' ), 'file_to_string: reading W/tmp/tests/canbewritten is lilili' ) ;
15358
15359 is( q{}, string_to_file( q{}, 'W/tmp/tests/empty' ), 'file_to_string: string_to_file filling W/tmp/tests/empty with empty string' ) ;
15360 is( q{}, file_to_string( 'W/tmp/tests/empty' ), 'file_to_string: reading W/tmp/tests/empty is empty' ) ;
15361
15362 note( 'Leaving tests_file_to_string()' ) ;
15363 return ;
15364}
15365
15366sub file_to_string
15367{
15368 my $file = shift ;
15369 if ( ! $file ) { return ; }
15370 if ( ! -e $file ) { return ; }
15371 if ( ! -f $file ) { return ; }
15372 if ( ! -r $file ) { return ; }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010015373
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015374 return( join q{}, file_to_array( $file ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015375}
15376
15377
15378sub tests_string_to_file
15379{
15380 note( 'Entering tests_string_to_file()' ) ;
15381
15382 is( undef, string_to_file( ), 'string_to_file: no args => undef' ) ;
15383 is( undef, string_to_file( 'lalala' ), 'string_to_file: one arg => undef' ) ;
15384 is( undef, string_to_file( 'lalala', '.' ), 'string_to_file: writing a directory => undef' ) ;
15385 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'string_to_file: mkpath W/tmp/tests/' ) ;
15386 is( 'lalala', string_to_file( 'lalala', 'W/tmp/tests/canbewritten' ), 'string_to_file: W/tmp/tests/canbewritten with lalala' ) ;
15387 is( q{}, string_to_file( q{}, 'W/tmp/tests/empty' ), 'string_to_file: W/tmp/tests/empty with empty string' ) ;
15388
15389 SKIP: {
15390 Readonly my $NB_UNX_tests_string_to_file => 1 ;
15391 skip( 'Not on Unix non-root', $NB_UNX_tests_string_to_file ) if ('MSWin32' eq $OSNAME or '0' eq $EFFECTIVE_USER_ID ) ;
15392 is( undef, string_to_file( 'lalala', '/cantouch' ), 'string_to_file: /cantouch denied => undef' ) ;
15393 }
15394
15395 note( 'Leaving tests_string_to_file()' ) ;
15396 return ;
15397}
15398
15399sub string_to_file
15400{
15401 my( $string, $file ) = @_ ;
15402 if( ! defined $string ) { return ; }
15403 if( ! defined $file ) { return ; }
15404
15405 if ( ! -e $file && ! -w dirname( $file ) ) {
15406 myprint( "string_to_file: directory of $file is not writable\n" ) ;
15407 return ;
15408 }
15409
15410 if ( ! sysopen( FILE, $file, O_WRONLY|O_TRUNC|O_CREAT, 0600) ) {
15411 myprint( "string_to_file: failure writing to $file with error: $OS_ERROR\n" ) ;
15412 return ;
15413 }
15414 print FILE $string ;
15415 close FILE ;
15416 return $string ;
15417}
15418
154190 and <<'MULTILINE_COMMENT' ;
15420This is a multiline comment.
15421Based on David Carter discussion, to do:
15422* Call parameters stay the same.
15423* Now always "return( $string, $error )". Descriptions below.
15424OK * Still capture STDOUT via "1> $output_tmpfile" to finish in $string and "return( $string, $error )"
15425OK * Now also capture STDERR via "2> $error_tmpfile" to finish in $error and "return( $string, $error )"
15426OK * in case of CHILD_ERROR, return( undef, $error )
15427 and print $error, with folder/UID/maybeSubject context,
15428 on console and at the end with the final error listing. Count this as a sync error.
15429* in case of good command, take final $string as is, unless void. In case $error with value then print it.
15430* in case of good command and final $string empty, consider it like CHILD_ERROR =>
15431 return( undef, $error ) and print $error, with folder/UID/maybeSubject context,
15432 on console and at the end with the final error listing. Count this as a sync error.
15433MULTILINE_COMMENT
15434# End of multiline comment.
15435
15436sub pipemess
15437{
15438 my ( $string, @commands ) = @_ ;
15439 my $error = q{} ;
15440 foreach my $command ( @commands ) {
15441 my $input_tmpfile = "$sync->{ tmpdir }/imapsync_tmp_file.$PROCESS_ID.inp.txt" ;
15442 my $output_tmpfile = "$sync->{ tmpdir }/imapsync_tmp_file.$PROCESS_ID.out.txt" ;
15443 my $error_tmpfile = "$sync->{ tmpdir }/imapsync_tmp_file.$PROCESS_ID.err.txt" ;
15444 string_to_file( $string, $input_tmpfile ) ;
15445 ` $command < $input_tmpfile 1> $output_tmpfile 2> $error_tmpfile ` ;
15446 my $is_command_ko = $CHILD_ERROR ;
15447 my $error_cmd = file_to_string( $error_tmpfile ) ;
15448 chomp( $error_cmd ) ;
15449 $string = file_to_string( $output_tmpfile ) ;
15450 my $string_len = length( $string ) ;
15451 unlink $input_tmpfile, $output_tmpfile, $error_tmpfile ;
15452
15453 if ( $is_command_ko or ( ! $string_len ) ) {
15454 my $cmd_exit_value = $CHILD_ERROR >> 8 ;
15455 my $cmd_end_signal = $CHILD_ERROR & 127 ;
15456 my $signal_log = ( $cmd_end_signal ) ? " signal $cmd_end_signal and" : q{} ;
15457 my $error_log = qq{Failure: --pipemess command "$command" ended with$signal_log "$string_len" characters exit value "$cmd_exit_value" and STDERR "$error_cmd"\n} ;
15458 myprint( $error_log ) ;
15459 if ( wantarray ) {
15460 return @{ [ undef, $error_log ] }
15461 }else{
15462 return ;
15463 }
15464 }
15465 if ( $error_cmd ) {
15466 $error .= qq{STDERR of --pipemess "$command": $error_cmd\n} ;
15467 myprint( qq{STDERR of --pipemess "$command": $error_cmd\n} ) ;
15468 }
15469 }
15470 #myprint( "[$string]\n" ) ;
15471 if ( wantarray ) {
15472 return ( $string, $error ) ;
15473 }else{
15474 return $string ;
15475 }
15476}
15477
15478
15479
15480sub tests_pipemess
15481{
15482 note( 'Entering tests_pipemess()' ) ;
15483
15484
15485 SKIP: {
15486 Readonly my $NB_WIN_tests_pipemess => 3 ;
15487 skip( 'Not on MSWin32', $NB_WIN_tests_pipemess ) if ('MSWin32' ne $OSNAME) ;
15488 # Windows
15489 # "type" command does not accept redirection of STDIN with <
15490 # "sort" does
15491 ok( "nochange\n" eq pipemess( 'nochange', 'sort' ), 'pipemess: nearly no change by sort' ) ;
15492 ok( "nochange2\n" eq pipemess( 'nochange2', qw( sort sort ) ), 'pipemess: nearly no change by sort,sort' ) ;
15493 # command not found
15494 #diag( 'Warning and failure about cacaprout are on purpose' ) ;
15495 ok( ! defined( pipemess( q{}, 'cacaprout' ) ), 'pipemess: command not found' ) ;
15496
15497 } ;
15498
15499 my ( $stringT, $errorT ) ;
15500
15501 SKIP: {
15502 Readonly my $NB_UNX_tests_pipemess => 25 ;
15503 skip( 'Not on Unix', $NB_UNX_tests_pipemess ) if ('MSWin32' eq $OSNAME) ;
15504 # Unix
15505 ok( 'nochange' eq pipemess( 'nochange', 'cat' ), 'pipemess: no change by cat' ) ;
15506
15507 ok( 'nochange2' eq pipemess( 'nochange2', 'cat', 'cat' ), 'pipemess: no change by cat,cat' ) ;
15508
15509 ok( " 1\tnumberize\n" eq pipemess( "numberize\n", 'cat -n' ), 'pipemess: numberize by cat -n' ) ;
15510 ok( " 1\tnumberize\n 2\tnumberize\n" eq pipemess( "numberize\nnumberize\n", 'cat -n' ), 'pipemess: numberize by cat -n' ) ;
15511
15512 ok( "A\nB\nC\n" eq pipemess( "A\nC\nB\n", 'sort' ), 'pipemess: sort' ) ;
15513
15514 # command not found
15515 #diag( 'Warning and failure about cacaprout are on purpose' ) ;
15516 is( undef, pipemess( q{}, 'cacaprout' ), 'pipemess: command not found' ) ;
15517
15518 # success with true but no output at all
15519 is( undef, pipemess( q{blabla}, 'true' ), 'pipemess: true but no output' ) ;
15520
15521 # failure with false and no output at all
15522 is( undef, pipemess( q{blabla}, 'false' ), 'pipemess: false and no output' ) ;
15523
15524 # Failure since pipemess is not a real pipe, so first cat wait for standard input
15525 is( q{blabla}, pipemess( q{blabla}, '( cat|cat ) ' ), 'pipemess: ok by ( cat|cat )' ) ;
15526
15527
15528 ( $stringT, $errorT ) = pipemess( 'nochange', 'cat' ) ;
15529 is( $stringT, 'nochange', 'pipemess: list context, no change by cat, string' ) ;
15530 is( $errorT, q{}, 'pipemess: list context, no change by cat, no error' ) ;
15531
15532 ( $stringT, $errorT ) = pipemess( 'dontcare', 'true' ) ;
15533 is( $stringT, undef, 'pipemess: list context, true but no output, string' ) ;
15534 like( $errorT, qr{\QFailure: --pipemess command "true" ended with "0" characters exit value "0" and STDERR ""\E}xm, 'pipemess: list context, true but no output, error' ) ;
15535
15536 ( $stringT, $errorT ) = pipemess( 'dontcare', 'false' ) ;
15537 is( $stringT, undef, 'pipemess: list context, false and no output, string' ) ;
15538 like( $errorT, qr{\QFailure: --pipemess command "false" ended with "0" characters exit value "1" and STDERR ""\E}xm,
15539 'pipemess: list context, false and no output, error' ) ;
15540
15541 ( $stringT, $errorT ) = pipemess( 'dontcare', '/bin/echo -n blablabla' ) ;
15542 is( $stringT, q{blablabla}, 'pipemess: list context, "echo -n blablabla", string' ) ;
15543 is( $errorT, q{}, 'pipemess: list context, "echo blablabla", error' ) ;
15544
15545
15546 ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo -n blablabla 3>&1 1>&2 2>&3 )' ) ;
15547 is( $stringT, undef, 'pipemess: list context, "no output STDERR blablabla", string' ) ;
15548 like( $errorT, qr{blablabla"}xm, 'pipemess: list context, "no output STDERR blablabla", error' ) ;
15549
15550
15551 ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo -n blablabla 3>&1 1>&2 2>&3 )', 'false' ) ;
15552 is( $stringT, undef, 'pipemess: list context, "no output STDERR blablabla then false", string' ) ;
15553 like( $errorT, qr{blablabla"}xm, 'pipemess: list context, "no output STDERR blablabla then false", error' ) ;
15554
15555 ( $stringT, $errorT ) = pipemess( 'dontcare', 'false', '( echo -n blablabla 3>&1 1>&2 2>&3 )' ) ;
15556 is( $stringT, undef, 'pipemess: list context, "false then STDERR blablabla", string' ) ;
15557 like( $errorT, qr{\QFailure: --pipemess command "false" ended with "0" characters exit value "1" and STDERR ""\E}xm,
15558 'pipemess: list context, "false then STDERR blablabla", error' ) ;
15559
15560 ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo rrrrr ; echo -n error_blablabla 3>&1 1>&2 2>&3 )' ) ;
15561 like( $stringT, qr{rrrrr}xm, 'pipemess: list context, "STDOUT rrrrr STDERR error_blablabla", string' ) ;
15562 like( $errorT, qr{STDERR.*error_blablabla}xm, 'pipemess: list context, "STDOUT rrrrr STDERR error_blablabla", error' ) ;
15563
15564 }
15565
15566 ( $stringT, $errorT ) = pipemess( 'dontcare', 'cacaprout' ) ;
15567 is( $stringT, undef, 'pipemess: list context, cacaprout not found, string' ) ;
15568 like( $errorT, qr{\QFailure: --pipemess command "cacaprout" ended with "0" characters exit value\E}xm,
15569 'pipemess: list context, cacaprout not found, error' ) ;
15570
15571 note( 'Leaving tests_pipemess()' ) ;
15572 return ;
15573}
15574
15575
15576
15577sub tests_is_a_release_number
15578{
15579 note( 'Entering tests_is_a_release_number()' ) ;
15580
15581 is( undef, is_a_release_number( ), 'is_a_release_number: no args => undef' ) ;
15582 ok( is_a_release_number( $RELEASE_NUMBER_EXAMPLE_1 ), 'is_a_release_number 1.351' ) ;
15583 ok( is_a_release_number( $RELEASE_NUMBER_EXAMPLE_2 ), 'is_a_release_number 42.4242' ) ;
15584 ok( is_a_release_number( imapsync_version( $sync ) ), 'is_a_release_number imapsync_version( )' ) ;
15585 ok( ! is_a_release_number( 'blabla' ), '! is_a_release_number blabla' ) ;
15586
15587 note( 'Leaving tests_is_a_release_number()' ) ;
15588 return ;
15589}
15590
15591sub is_a_release_number
15592{
15593 my $number = shift ;
15594 if ( ! defined $number ) { return ; }
15595 return( $number =~ m{^\d+\.\d+$}xo ) ;
15596}
15597
15598
15599
15600sub imapsync_version_public
15601{
15602
15603 my $local_version = imapsync_version( $sync ) ;
15604 my $imapsync_basename = imapsync_basename( ) ;
15605 my $context = imapsync_context( ) ;
15606 my $agent_info = "$OSNAME system, perl "
15607 . mysprintf( '%vd', $PERL_VERSION)
15608 . ", Mail::IMAPClient $Mail::IMAPClient::VERSION"
15609 . " $imapsync_basename"
15610 . " $context" ;
15611 my $sock = IO::Socket::INET->new(
15612 PeerAddr => 'imapsync.lamiral.info',
15613 PeerPort => 80,
15614 Proto => 'tcp',
15615 ) ;
15616 return( 'unknown' ) if not $sock ;
15617 print $sock
15618 "GET /prj/imapsync/VERSION HTTP/1.0\r\n",
15619 "User-Agent: imapsync/$local_version ($agent_info)\r\n",
15620 "Host: ks.lamiral.info\r\n\r\n" ;
15621 my @line = <$sock> ;
15622 close $sock ;
15623 my $last_release = $line[$LAST] ;
15624 chomp $last_release ;
15625 return( $last_release ) ;
15626}
15627
15628sub not_long_imapsync_version_public
15629{
15630 #myprint( "Entering not_long_imapsync_version_public\n" ) ;
15631
15632 my $fake = shift ;
15633 if ( $fake ) { return $fake }
15634
15635 my $val ;
15636
15637 # Doesn't work with gethostbyname (see perlipc)
15638 #local $SIG{ALRM} = sub { die "alarm\n" } ;
15639
15640 if ('MSWin32' eq $OSNAME) {
15641 local $SIG{ALRM} = sub { die "alarm\n" } ;
15642 }else{
15643
15644 POSIX::sigaction(SIGALRM,
15645 POSIX::SigAction->new(sub { croak 'alarm' } ) )
15646 or myprint( "Error setting SIGALRM handler: $OS_ERROR\n" ) ;
15647 }
15648
15649 my $ret = eval {
15650 alarm 3 ;
15651 {
15652 $val = imapsync_version_public( ) ;
15653 #sleep 4 ;
15654 #myprint( "End of imapsync_version_public\n" ) ;
15655 }
15656 alarm 0 ;
15657 1 ;
15658 } ;
15659 #myprint( "eval [$ret]\n" ) ;
15660 if ( ( not $ret ) or $EVAL_ERROR ) {
15661 #myprint( "$EVAL_ERROR" ) ;
15662 if ($EVAL_ERROR =~ /alarm/) {
15663 # timed out
15664 return('timeout') ;
15665 }else{
15666 alarm 0 ;
15667 return( 'unknown' ) ; # propagate unexpected errors
15668 }
15669 }else {
15670 # Good!
15671 return( $val ) ;
15672 }
15673}
15674
15675sub tests_not_long_imapsync_version_public
15676{
15677 note( 'Entering tests_not_long_imapsync_version_public()' ) ;
15678
15679
15680 is( 1, is_a_release_number( not_long_imapsync_version_public( ) ),
15681 'not_long_imapsync_version_public: public release is a number' ) ;
15682
15683 note( 'Leaving tests_not_long_imapsync_version_public()' ) ;
15684 return ;
15685}
15686
15687sub check_last_release
15688{
15689 my $fake = shift ;
15690 my $public_release = not_long_imapsync_version_public( $fake ) ;
15691 $sync->{ debug } and myprint( "check_last_release: [$public_release]\n" ) ;
15692 my $inline_help_when_on = '( Use --noreleasecheck to avoid this release check. )' ;
15693
15694 if ( $public_release eq 'unknown' ) {
15695 return( 'Imapsync public release is unknown.' . $inline_help_when_on ) ;
15696 }
15697
15698 if ( $public_release eq 'timeout' ) {
15699 return( 'Imapsync public release is unknown (timeout).' . $inline_help_when_on ) ;
15700 }
15701
15702 if ( ! is_a_release_number( $public_release ) ) {
15703 return( "Imapsync public release is unknown ($public_release)." . $inline_help_when_on ) ;
15704 }
15705
15706 my $imapsync_here = imapsync_version( $sync ) ;
15707
15708 if ( $public_release > $imapsync_here ) {
15709 return( 'This imapsync is not up to date. ' . "( local $imapsync_here < official $public_release )" . $inline_help_when_on ) ;
15710 }else{
15711 return( 'This imapsync is up to date. ' . "( local $imapsync_here >= official $public_release )" . $inline_help_when_on ) ;
15712 }
15713
15714 return( 'really unknown' ) ; # Should never arrive here
15715}
15716
15717sub tests_check_last_release
15718{
15719 note( 'Entering tests_check_last_release()' ) ;
15720
15721 diag( check_last_release( 1.1 ) ) ;
15722 # \Q \E here to avoid putting \ before each space
15723 like( check_last_release( 1.1 ), qr/\Qis up to date\E/mxs, 'check_last_release: up to date' ) ;
15724 like( check_last_release( 1.1 ), qr/1\.1/mxs, 'check_last_release: up to date, include number' ) ;
15725 diag( check_last_release( 999.999 ) ) ;
15726 like( check_last_release( 999.999 ), qr/\Qnot up to date\E/mxs, 'check_last_release: not up to date' ) ;
15727 like( check_last_release( 999.999 ), qr/999\.999/mxs, 'check_last_release: not up to date, include number' ) ;
15728 like( check_last_release( 'unknown' ), qr/\QImapsync public release is unknown\E/mxs, 'check_last_release: unknown' ) ;
15729 like( check_last_release( 'timeout' ), qr/\QImapsync public release is unknown (timeout)\E/mxs, 'check_last_release: timeout' ) ;
15730 like( check_last_release( 'lalala' ), qr/\QImapsync public release is unknown (lalala)\E/mxs, 'check_last_release: lalala' ) ;
15731 diag( check_last_release( ) ) ;
15732
15733 note( 'Leaving tests_check_last_release()' ) ;
15734 return ;
15735}
15736
15737sub tests_imapsync_context
15738{
15739 note( 'Entering tests_imapsync_context()' ) ;
15740
15741 like( imapsync_context( ), qr/^CGI|^Docker|^DockerCGI|^Standard/, 'imapsync_context: CGI or Docker or DockerCGI or Standard' ) ;
15742 note( 'Leaving tests_imapsync_context()' ) ;
15743 return ;
15744}
15745
15746sub imapsync_context
15747{
15748 my $mysync = shift ;
15749
15750 my $context = q{} ;
15751
15752 if ( under_docker_context( $mysync ) && under_cgi_context( $mysync ) )
15753 {
15754 $context = 'DockerCGI' ;
15755 }
15756 elsif ( under_docker_context( $mysync ) )
15757 {
15758 $context = 'Docker' ;
15759 }
15760 elsif ( under_cgi_context( $mysync ) )
15761 {
15762 $context = 'CGI' ;
15763 }
15764 else
15765 {
15766 $context = 'Standard' ;
15767 }
15768
15769 return $context ;
15770
15771}
15772
15773sub imapsync_version
15774{
15775 my $mysync = shift ;
15776 my $rcs = $mysync->{rcs} ;
15777 my $version ;
15778
15779 $version = version_from_rcs( $rcs ) ;
15780 return( $version ) ;
15781}
15782
15783
15784sub tests_version_from_rcs
15785{
15786 note( 'Entering tests_version_from_rcs()' ) ;
15787
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015788 is( undef, version_from_rcs( ), 'version_from_rcs: no args => undef' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015789 is( 1.831, version_from_rcs( q{imapsync,v 1.831 2017/08/27} ), 'version_from_rcs: imapsync,v 1.831 2017/08/27 => 1.831' ) ;
15790 is( 'UNKNOWN', version_from_rcs( 1.831 ), 'version_from_rcs: 1.831 => UNKNOWN' ) ;
15791
15792 note( 'Leaving tests_version_from_rcs()' ) ;
15793 return ;
15794}
15795
15796
15797sub version_from_rcs
15798{
15799
15800 my $rcs = shift ;
15801 if ( ! $rcs ) { return ; }
15802
15803 my $version = 'UNKNOWN' ;
15804
15805 if ( $rcs =~ m{,v\s+(\d+\.\d+)}mxso ) {
15806 $version = $1
15807 }
15808
15809 return( $version ) ;
15810}
15811
15812
15813sub tests_imapsync_basename
15814{
15815 note( 'Entering tests_imapsync_basename()' ) ;
15816
15817 ok( imapsync_basename() =~ m/imapsync/, 'imapsync_basename: match imapsync');
15818 ok( 'blabla' ne imapsync_basename(), 'imapsync_basename: do not equal blabla');
15819
15820 note( 'Leaving tests_imapsync_basename()' ) ;
15821 return ;
15822}
15823
15824sub imapsync_basename
15825{
15826
15827 return basename( $PROGRAM_NAME ) ;
15828
15829}
15830
15831
15832sub localhost_info
15833{
15834 my $mysync = shift ;
15835 my( $infos ) = join( q{},
15836 "Here is imapsync ", imapsync_version( $mysync ),
15837 " on host " . hostname(),
15838 ", a $OSNAME system with ",
15839 ram_memory_info( ),
15840 "\n",
15841 'with Perl ',
15842 mysprintf( '%vd ', $PERL_VERSION),
15843 "and Mail::IMAPClient $Mail::IMAPClient::VERSION",
15844 ) ;
15845 return( $infos ) ;
15846}
15847
15848sub tests_cpu_number
15849{
15850 note( 'Entering tests_cpu_number()' ) ;
15851
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015852 is( 1, is_integer( cpu_number( ) ), "cpu_number: is_integer" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015853 ok( 1 <= cpu_number( ), "cpu_number: 1 or more" ) ;
15854 is( 1, cpu_number( 1 ), "cpu_number: 1 => 1" ) ;
15855 is( 1, cpu_number( $MINUS_ONE ), "cpu_number: -1 => 1" ) ;
15856 is( 1, cpu_number( 'lalala' ), "cpu_number: lalala => 1" ) ;
15857 is( $NUMBER_42, cpu_number( $NUMBER_42 ), "cpu_number: $NUMBER_42 => $NUMBER_42" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015858
15859 note( "cpu_number = " . cpu_number( ) . "\n" ) ;
15860 note( "hostname = " . hostname( ) . "\n" ) ;
15861 SKIP: {
15862 if ( ! ( 'i005' eq hostname() ) )
15863 {
15864 skip( 'cpu_number on host != i005 (FreeBSD)', 1 ) ;
15865 }
15866 is( 4, cpu_number( ), "cpu_number: on i005 (FreeBSD) => 4" ) ;
15867 } ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010015868
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015869 SKIP: {
15870 if ( ! ( 'petite' eq hostname() ) )
15871 {
15872 skip( 'cpu_number on host != petite (Linux)', 1 ) ;
15873 }
15874 is( 2, cpu_number( ), "cpu_number: on petite (Linux) => 2" ) ;
15875 } ;
15876
15877 SKIP: {
15878 if ( ! ( skip_macosx( ) ) )
15879 {
15880 skip( 'cpu_number on host != polarhome macosx (Darwin MacOS X 10.7.5 Lion)', 1 ) ;
15881 }
15882 is( 2, cpu_number( ), "cpu_number: on polarhome macosx (Darwin MacOS X 10.7.5 Lion) => 2" ) ;
15883 } ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010015884
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015885 SKIP: {
15886 if ( ! ( 'pcHPDV7-HP' eq hostname() ) )
15887 {
15888 skip( 'cpu_number on host != pcHPDV7-HP (Windows 7, 64bits)', 1 ) ;
15889 }
15890 is( 2, cpu_number( ), "cpu_number: on pcHPDV7-HP (Windows 7, 64bits) => 2" ) ;
15891 } ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010015892
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015893 SKIP: {
15894 if ( ! ( 'CUILLERE' eq hostname() ) )
15895 {
15896 skip( 'cpu_number on host != CUILLERE (Windows XP, 32bits)', 1 ) ;
15897 }
15898 is( 1, cpu_number( ), "cpu_number: on CUILLERE (Windows XP, 32bits) => 1" ) ;
15899 } ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010015900
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015901
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015902 note( 'Leaving tests_cpu_number()' ) ;
15903 return ;
15904}
15905
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015906
15907sub cpu_number {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015908
15909 my $cpu_number_forced = shift ;
15910 # Well, here 1 is better than 0 or undef
15911 my $cpu_number = 1 ; # Default value, erased if better found
15912
15913 my @cpuinfo ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015914 if ( $ENV{"NUMBER_OF_PROCESSORS"} )
15915 {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015916 # might be under a Windows system
15917 $cpu_number = $ENV{"NUMBER_OF_PROCESSORS"} ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015918 #myprint( "Number of processors found by env var NUMBER_OF_PROCESSORS: $cpu_number\n" ) ;
15919 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010015920
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015921 if ( 'darwin' eq $OSNAME )
15922 {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015923 $cpu_number = backtick( "sysctl -n hw.ncpu" ) ;
15924 chomp( $cpu_number ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015925 #myprint( "Number of processors found by cmd 'sysctl -n hw.ncpu': $cpu_number\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015926 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010015927
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015928 if ( 'freebsd' eq $OSNAME )
15929 {
15930 $cpu_number = backtick( "sysctl -n kern.smp.cpus" ) ;
15931 chomp( $cpu_number ) ;
15932 #myprint( "Number of processors found by cmd 'sysctl -n kern.smp.cpus': $cpu_number\n" ) ;
15933 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010015934
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015935 if ( 'linux' eq $OSNAME && -e '/proc/cpuinfo' )
15936 {
15937 @cpuinfo = file_to_array( '/proc/cpuinfo' ) ;
15938 $cpu_number = grep { /^processor/mxs } @cpuinfo ;
15939 #myprint( "Number of processors found via /proc/cpuinfo: $cpu_number\n" ) ;
15940 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010015941
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015942 if ( defined $cpu_number_forced )
15943 {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015944 $cpu_number = $cpu_number_forced ;
15945 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010015946
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015947 return( integer_or_1( $cpu_number ) ) ;
15948}
15949
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015950sub tests_integer_or_1
15951{
15952 note( 'Entering tests_integer_or_1()' ) ;
15953
15954 is( 1, integer_or_1( ), 'integer_or_1: no args => 1' ) ;
15955 is( 1, integer_or_1( undef ), 'integer_or_1: undef => 1' ) ;
15956 is( $NUMBER_10, integer_or_1( $NUMBER_10 ), 'integer_or_1: 10 => 10' ) ;
15957 is( 1, integer_or_1( q{} ), 'integer_or_1: empty string => 1' ) ;
15958 is( 1, integer_or_1( 'lalala' ), 'integer_or_1: lalala => 1' ) ;
15959
15960 note( 'Leaving tests_integer_or_1()' ) ;
15961 return ;
15962}
15963
15964sub integer_or_1
15965{
15966 my $number = shift ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015967 if ( is_integer( $number ) ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015968 return $number ;
15969 }
15970 # else
15971 return 1 ;
15972}
15973
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015974sub tests_is_integer
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015975{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015976 note( 'Entering tests_is_integer()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015977
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015978 is( undef, is_integer( ), 'is_integer: no args => undef ' ) ;
15979 ok( is_integer( 1 ), 'is_integer: 1 => yes ') ;
15980 ok( is_integer( $NUMBER_42 ), 'is_integer: 42 => yes ') ;
15981 ok( is_integer( "$NUMBER_42" ), 'is_integer: "$NUMBER_42" => yes ') ;
15982 ok( is_integer( '42' ), 'is_integer: "42" => yes ') ;
15983 ok( is_integer( $NUMBER_104_857_600 ), 'is_integer: 104_857_600 => yes') ;
15984 ok( is_integer( "$NUMBER_104_857_600" ), 'is_integer: "$NUMBER_104_857_600" => yes') ;
15985 ok( is_integer( '104857600' ), 'is_integer: 104857600 => yes') ;
15986 ok( ! is_integer( 'blabla' ), 'is_integer: blabla => no' ) ;
15987 ok( ! is_integer( q{} ), 'is_integer: empty string => no' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015988
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015989 note( 'Leaving tests_is_integer()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015990 return ;
15991}
15992
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015993sub is_integer
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015994{
15995 my $number = shift ;
15996 if ( ! defined $number ) { return ; }
15997 return( $number =~ m{^\d+$}xo ) ;
15998}
15999
16000
16001
16002
16003sub tests_loadavg
16004{
16005 note( 'Entering tests_loadavg()' ) ;
16006
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016007 SKIP: {
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016008 skip( 'Tests for darwin', 3 ) if ('darwin' ne $OSNAME) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016009 is( undef, loadavg( '/noexist' ), 'loadavg: /noexist => undef' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016010 is_deeply(
16011 [ '0.11', '0.22', '0.33' ],
16012 [ loadavg( 'vm.loadavg: { 0.11 0.22 0.33 }' ) ],
16013 'loadavg: "vm.loadavg: { 0.11 0.22 0.33 }" => 0.11 0.22 0.33'
16014 ) ;
16015 note( join( " ", "loadavg:", loadavg( ) ) ) ;
16016 is( 3, scalar( my @loadavg = loadavg( ) ), 'loadavg: 3 values' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016017 } ;
16018
16019 SKIP: {
16020 skip( 'Tests for linux', 3 ) if ('linux' ne $OSNAME) ;
16021 is( undef, loadavg( '/noexist' ), 'loadavg: /noexist => undef' ) ;
16022 ok( loadavg( ), 'loadavg: no args' ) ;
16023
16024 is_deeply( [ '0.39', '0.30', '0.37', '1/602' ],
16025 [ loadavg( '0.39 0.30 0.37 1/602 6073' ) ],
16026 'loadavg 0.39 0.30 0.37 1/602 6073 => [0.39, 0.30, 0.37, 1/602]' ) ;
16027 } ;
16028
16029 SKIP: {
16030 skip( 'Tests for Windows', 1 ) if ('MSWin32' ne $OSNAME) ;
16031 is_deeply( [ 0 ],
16032 [ loadavg( ) ],
16033 'loadavg on MSWin32 => 0' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016034 } ;
16035
16036 note( 'Leaving tests_loadavg()' ) ;
16037 return ;
16038}
16039
16040
16041sub loadavg
16042{
16043 if ( 'linux' eq $OSNAME ) {
16044 return ( loadavg_linux( @ARG ) ) ;
16045 }
16046 if ( 'freebsd' eq $OSNAME ) {
16047 return ( loadavg_freebsd( @ARG ) ) ;
16048 }
16049 if ( 'darwin' eq $OSNAME ) {
16050 return ( loadavg_darwin( @ARG ) ) ;
16051 }
16052 if ( 'MSWin32' eq $OSNAME ) {
16053 return ( loadavg_windows( @ARG ) ) ;
16054 }
16055 return( 'unknown' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016056}
16057
16058sub loadavg_linux
16059{
16060 my $line = shift ;
16061
16062 if ( ! $line ) {
16063 $line = firstline( '/proc/loadavg' ) or return ;
16064 }
16065
16066 my ( $avg_1_min, $avg_5_min, $avg_15_min, $current_runs ) = split /\s/mxs, $line ;
16067 if ( all_defined( $avg_1_min, $avg_5_min, $avg_15_min ) ) {
16068 $sync->{ debug } and myprint( "System load: $avg_1_min $avg_5_min $avg_15_min $current_runs\n" ) ;
16069 return ( $avg_1_min, $avg_5_min, $avg_15_min, $current_runs ) ;
16070 }
16071 return ;
16072}
16073
16074sub loadavg_freebsd
16075{
16076 my $file = shift ;
16077 # Example of output of command "sysctl vm.loadavg":
16078 # vm.loadavg: { 0.15 0.08 0.08 }
16079 my $loadavg ;
16080
16081 if ( ! defined $file ) {
16082 eval {
16083 $loadavg = `/sbin/sysctl vm.loadavg` ;
16084 #myprint( "LOADAVG FREEBSD: $loadavg\n" ) ;
16085 } ;
16086 if ( $EVAL_ERROR ) { myprint( "[$EVAL_ERROR]\n" ) ; return ; }
16087 }else{
16088 $loadavg = firstline( $file ) or return ;
16089 }
16090
16091 my ( $avg_1_min, $avg_5_min, $avg_15_min )
16092 = $loadavg =~ /vm\.loadavg\s*[:=]\s*\{?\s*(\d+\.?\d*)\s+(\d+\.?\d*)\s+(\d+\.?\d*)/mxs ;
16093 $sync->{ debug } and myprint( "System load: $avg_1_min $avg_5_min $avg_15_min\n" ) ;
16094 return ( $avg_1_min, $avg_5_min, $avg_15_min ) ;
16095}
16096
16097sub loadavg_darwin
16098{
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016099 my $line = shift ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016100 # Example of output of command "sysctl vm.loadavg":
16101 # vm.loadavg: { 0.15 0.08 0.08 }
16102 my $loadavg ;
16103
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016104 if ( ! defined $line ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016105 eval {
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016106 # $loadavg = `/usr/sbin/sysctl vm.loadavg` ;
16107 $loadavg = `LANG= /usr/sbin/sysctl vm.loadavg` ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016108 #myprint( "LOADAVG DARWIN: $loadavg\n" ) ;
16109 } ;
16110 if ( $EVAL_ERROR ) { myprint( "[$EVAL_ERROR]\n" ) ; return ; }
16111 }else{
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016112 $loadavg = $line ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016113 }
16114
16115 my ( $avg_1_min, $avg_5_min, $avg_15_min )
16116 = $loadavg =~ /vm\.loadavg\s*[:=]\s*\{?\s*(\d+\.?\d*)\s+(\d+\.?\d*)\s+(\d+\.?\d*)/mxs ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016117 #$sync->{ debug } and myprint( "System load: $avg_1_min $avg_5_min $avg_15_min\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016118 return ( $avg_1_min, $avg_5_min, $avg_15_min ) ;
16119}
16120
16121sub loadavg_windows
16122{
16123 my $file = shift ;
16124 # Example of output of command "wmic cpu get loadpercentage":
16125 # LoadPercentage
16126 # 12
16127 my $loadavg ;
16128
16129 if ( ! defined $file ) {
16130 eval {
16131 #$loadavg = `CMD wmic cpu get loadpercentage` ;
16132 $loadavg = "LoadPercentage\n0\n" ;
16133 #myprint( "LOADAVG WIN: $loadavg\n" ) ;
16134 } ;
16135 if ( $EVAL_ERROR ) { myprint( "[$EVAL_ERROR]\n" ) ; return ; }
16136 }else{
16137 $loadavg = file_to_string( $file ) or return ;
16138 #myprint( "$loadavg" ) ;
16139 }
16140 $loadavg =~ /LoadPercentage\n(\d+)/xms ;
16141 my $num = $1 ;
16142 $num /= 100 ;
16143
16144 $sync->{ debug } and myprint( "System load: $num\n" ) ;
16145 return ( $num ) ;
16146}
16147
16148
16149
16150
16151
16152
16153sub tests_load_and_delay
16154{
16155 note( 'Entering tests_load_and_delay()' ) ;
16156
16157 is( undef, load_and_delay( ), 'load_and_delay: no args => undef ' ) ;
16158 is( undef, load_and_delay( 1 ), 'load_and_delay: not 4 args => undef ' ) ;
16159 is( undef, load_and_delay( 0, 1, 1, 1 ), 'load_and_delay: division per 0 => undef ' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016160
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016161# ( $cpu_num, $avg_1_min, $avg_5_min, $avg_15_min )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016162
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016163 is( 0, load_and_delay( 1, 1, 1, 1 ), 'load_and_delay: one core, loads are all 1 => ok ' ) ;
16164 is( 0, load_and_delay( 1, 1, 1, 1, 'lalala' ), 'load_and_delay: five arguments is ok' ) ;
16165 is( 0, load_and_delay( 2, 2, 2, 2 ), 'load_and_delay: two core, loads are all 2 => ok ' ) ;
16166 is( 0, load_and_delay( 2, 2, 4, 5 ), 'load_and_delay: two core, load1m is 2 => ok ' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016167
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016168
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016169 is( 0, load_and_delay( 1, 0, 0, 0 ), 'load_and_delay: one core, load1m=0 load5m=0 load15m=0 => 0 ' ) ;
16170 is( 0, load_and_delay( 1, 0, 0, 2 ), 'load_and_delay: one core, load1m=0 load5m=0 load15m=2 => 0 ' ) ;
16171 is( 0, load_and_delay( 1, 0, 2, 0 ), 'load_and_delay: one core, load1m=0 load5m=2 load15m=0 => 0 ' ) ;
16172 is( 0, load_and_delay( 1, 0, 2, 2 ), 'load_and_delay: one core, load1m=0 load5m=2 load15m=2 => 0 ' ) ;
16173 is( 0, load_and_delay( 1, 0, 3, 3 ), 'load_and_delay: one core, load1m=0 load5m=3 load15m=3 => 0 ' ) ;
16174 is( 0, load_and_delay( 1, 0, 4, 4 ), 'load_and_delay: one core, load1m=0 load5m=3 load15m=3 => 0 ' ) ;
16175 is( 0, load_and_delay( 1, 2, 0, 0 ), 'load_and_delay: one core, load1m=2 load5m=0 load15m=0 => 0 ' ) ;
16176 is( 0, load_and_delay( 1, 2, 0, 2 ), 'load_and_delay: one core, load1m=2 load5m=0 load15m=2 => 0 ' ) ;
16177 is( 0, load_and_delay( 1, 2, 2, 0 ), 'load_and_delay: one core, load1m=2 load5m=2 load15m=0 => 0 ' ) ;
16178 is( 0, load_and_delay( 1, 2, 2, 2 ), 'load_and_delay: one core, load1m=2 load5m=2 load15m=2 => 0 ' ) ;
16179 is( 0, load_and_delay( 1, 2.9, 2.9, 2.9 ), 'load_and_delay: one core, load1m=2.9 load5m=2.9 load15m=2.9 => 0 ' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016180
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016181 is( 0, load_and_delay( 1, 3, 0, 0 ), 'load_and_delay: one core, load1m=3 load5m=0 load15m=0 => 0 ' ) ;
16182 is( 0, load_and_delay( 1, 3, 2.9, 2.9 ), 'load_and_delay: one core, load1m=3 load5m=2.9 load15m=2.9 => 0 ' ) ;
16183 is( 0, load_and_delay( 1, 3, 3, 2.9 ), 'load_and_delay: one core, load1m=3 load5m=3 load15m=2.9 => 0 ' ) ;
16184 is( 0, load_and_delay( 1, 3, 3, 3 ), 'load_and_delay: one core, load1m=3 load5m=3 load15m=3 => 0 ' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016185
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016186 is( 1, load_and_delay( 1, 6, 0, 0 ), 'load_and_delay: one core, load1m=3 load5m=0 load15m=0 => 1 ' ) ;
16187 is( 1, load_and_delay( 1, 6, 5.9, 5.9 ), 'load_and_delay: one core, load1m=3 load5m=2.9 load15m=2.9 => 1 ' ) ;
16188 is( 5, load_and_delay( 1, 6, 6, 5.9 ), 'load_and_delay: one core, load1m=3 load5m=3 load15m=2.9 => 5 ' ) ;
16189 is( 15, load_and_delay( 1, 6, 6, 6 ), 'load_and_delay: one core, load1m=3 load5m=3 load15m=3 => 15 ' ) ;
16190
16191
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016192
16193 note( 'Leaving tests_load_and_delay()' ) ;
16194 return ;
16195}
16196
16197sub load_and_delay
16198{
16199 # Basically return 0 if load is not heavy, ie <= 1 per processor
16200
16201 # Not enough arguments
16202 if ( 4 > scalar @ARG ) { return ; }
16203
16204 my ( $cpu_num, $avg_1_min, $avg_5_min, $avg_15_min ) = @ARG ;
16205
16206 if ( 0 == $cpu_num ) { return ; }
16207
16208 # Let divide by number of cores
16209 ( $avg_1_min, $avg_5_min, $avg_15_min ) = map { $_ / $cpu_num } ( $avg_1_min, $avg_5_min, $avg_15_min ) ;
16210 # One of avg ok => ok, for now it is a OR
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016211 if ( $avg_1_min < 6 ) { return 0 ; }
16212 if ( $avg_5_min < 6 ) { return 1 ; } # Retry in 1 minute
16213 if ( $avg_15_min < 6 ) { return 5 ; } # Retry in 5 minutes
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016214 return 15 ; # Retry in 15 minutes
16215}
16216
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016217
16218sub tests_cpu_time
16219{
16220 note( 'Entering tests_cpu_time()' ) ;
16221
16222 ok( is_number( cpu_time( ) ), 'cpu_time: no args => a number' ) ;
16223
16224 my $mysync = { } ;
16225 $mysync->{ debug } = 1 ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016226 ok( is_number( cpu_time( $mysync ) ), 'cpu_time: {} => a number' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016227
16228 note( 'Leaving tests_cpu_time()' ) ;
16229 return ;
16230}
16231
16232sub cpu_time
16233{
16234 my $mysync = shift ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016235
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016236 my @cpu_times = times ;
16237 if ( ! @cpu_times ) { return ; }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016238
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016239 my $cpu_time = 0 ;
16240 # last element is the sum of all elements
16241 $cpu_time = ( map { $cpu_time += $_ } @cpu_times )[ -1 ] ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016242 my $cpu_time_round = mysprintf( '%.2f', $cpu_time ) ;
16243 $mysync->{ debug } and myprint( join(' + ', @cpu_times), " = $cpu_time ~ $cpu_time_round\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016244 return $cpu_time ;
16245}
16246
16247
16248sub tests_cpu_percent
16249{
16250 note( 'Entering tests_cpu_percent()' ) ;
16251
16252 is( '0.0', cpu_percent( ), 'cpu_percent: no args => 0.0' ) ;
16253 my $mysync = { } ;
16254 $mysync->{ debug } = 1 ;
16255 is( '0.0', cpu_percent( $mysync ), 'cpu_percent: {} => 0.0' ) ;
16256 is( '0.0', cpu_percent( $mysync, 0 ), 'cpu_percent: {} 0 => 0.0' ) ;
16257 is( '300.0', cpu_percent( $mysync, 3 ), 'cpu_percent: {} 3 => 300.0' ) ;
16258 is( '30.0', cpu_percent( $mysync, 3, 10 ), 'cpu_percent: {} 3 10 => 30.0' ) ;
16259 is( '0.0', cpu_percent( $mysync, 0, 10 ), 'cpu_percent: {} 0 10 => 0.0' ) ;
16260
16261 note( 'Leaving tests_cpu_percent()' ) ;
16262 return ;
16263}
16264
16265sub cpu_percent
16266{
16267 my $mysync = shift ;
16268 my $cpu_time = shift || 0 ;
16269 my $timediff = shift || 1 ; # no division by 0
16270
16271 if ( $cpu_time > $timediff )
16272 {
16273 myprint( "Strange: cpu_time $cpu_time > timediff $timediff\n" ) ;
16274 }
16275 my $cpu_percent = 0 ;
16276 $cpu_percent = mysprintf( '%.1f', 100 * $cpu_time / $timediff ) ;
16277 $mysync->{ debug } and myprint( "cpu_percent: $cpu_percent \n" ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016278
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016279 return $cpu_percent ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016280
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016281}
16282
16283sub tests_cpu_percent_global
16284{
16285 note( 'Entering tests_cpu_percent_global()' ) ;
16286
16287 is( '0.0', cpu_percent_global( ), 'cpu_percent_global: no args => 0' ) ;
16288 my $mysync = { } ;
16289 $mysync->{ debug } = 1 ;
16290 is( '0.0', cpu_percent_global( $mysync ), 'cpu_percent_global: {} => 0' ) ;
16291 is( '0.0', cpu_percent_global( $mysync, 0 ), 'cpu_percent_global: {} 0 => 0' ) ;
16292
16293 SKIP: {
16294 if ( ! ( 'i005' eq hostname() ) )
16295 {
16296 skip( 'cpu_percent_global on host != i005', 1 ) ;
16297 }
16298 is( '25.0', cpu_percent_global( $mysync, 100 ), 'cpu_percent_global: {} 100 => 25 on host i005' ) ;
16299 } ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016300
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016301 SKIP: {
16302 if ( ! ( 'petite' eq hostname() ) )
16303 {
16304 skip( 'cpu_percent_global on host != petite', 1 ) ;
16305 }
16306 is( '50.0', cpu_percent_global( $mysync, 100 ), 'cpu_percent_global: {} 100 => 50 on host petite' ) ;
16307 } ;
16308
16309 note( 'Leaving tests_cpu_percent_global()' ) ;
16310 return ;
16311}
16312
16313sub cpu_percent_global
16314{
16315 my $mysync = shift ;
16316 my $cpu_percent = shift || 0 ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016317
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016318 my $cpu_number = cpu_number( ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016319
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016320 my $cpu_percent_global ;
16321 $cpu_percent_global = mysprintf( '%.1f', $cpu_percent / $cpu_number ) ;
16322 $mysync->{ debug } and myprint( "cpu_percent_global: $cpu_percent_global \n" ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016323
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016324 return( $cpu_percent_global ) ;
16325}
16326
16327
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016328sub ram_memory_info
16329{
16330 # In GigaBytes so division by 1024 * 1024 * 1024
16331 #
16332 return(
16333 sprintf( "%.1f/%.1f free GiB of RAM",
16334 Sys::MemInfo::get("freemem") / ( $KIBI ** 3 ),
16335 Sys::MemInfo::get("totalmem") / ( $KIBI ** 3 ),
16336 )
16337 ) ;
16338}
16339
16340
16341
16342sub tests_memory_stress
16343{
16344 note( 'Entering tests_memory_stress()' ) ;
16345
16346 is( undef, memory_stress( ), 'memory_stress: => undef' ) ;
16347
16348 note( 'Leaving tests_memory_stress()' ) ;
16349 return ;
16350}
16351
16352sub memory_stress
16353{
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016354 my $total_ram_in_MB = Sys::MemInfo::get("totalmem") / ( $KIBI * $KIBI ) ;
16355 my $i = 1 ;
16356
16357 myprintf("Stress memory consumption before: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI ) ;
16358 while ( $i < $total_ram_in_MB / 1.7 ) { $a .= "A" x 1000_000; $i++ } ;
16359 myprintf("Stress memory consumption after: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI ) ;
16360 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016361}
16362
16363sub tests_memory_consumption
16364{
16365 note( 'Entering tests_memory_consumption()' ) ;
16366
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016367 note( "memory_consumption: " . memory_consumption() . " bytes aka " . bytes_display_string_dec( memory_consumption() ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016368 like( memory_consumption( ), qr{\d+}xms,'memory_consumption no args') ;
16369 like( memory_consumption( 1 ), qr{\d+}xms,'memory_consumption 1') ;
16370 like( memory_consumption( $PROCESS_ID ), qr{\d+}xms,"memory_consumption_of_pids $PROCESS_ID") ;
16371
16372 like( memory_consumption_ratio(), qr{\d+}xms, 'memory_consumption_ratio' ) ;
16373 like( memory_consumption_ratio(1), qr{\d+}xms, 'memory_consumption_ratio 1' ) ;
16374 like( memory_consumption_ratio(10), qr{\d+}xms, 'memory_consumption_ratio 10' ) ;
16375
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016376
16377 note( 'Leaving tests_memory_consumption()' ) ;
16378 return ;
16379}
16380
16381sub memory_consumption
16382{
16383 # memory consumed by imapsync until now in bytes
16384 return( ( memory_consumption_of_pids( ) )[0] );
16385}
16386
16387sub debugmemory
16388{
16389 my $mysync = shift ;
16390 if ( ! $mysync->{debugmemory} ) { return q{} ; }
16391
16392 my $precision = shift ;
16393 return( mysprintf( "Memory consumption$precision: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI ) ) ;
16394}
16395
16396sub memory_consumption_of_pids
16397{
16398
16399 my @pid = @_;
16400 @pid = ( @pid ) ? @pid : ( $PROCESS_ID ) ;
16401
16402 $sync->{ debug } and myprint( "memory_consumption_of_pids PIDs: @pid\n" ) ;
16403 my @val ;
16404 if ( ( 'MSWin32' eq $OSNAME ) or ( 'cygwin' eq $OSNAME ) ) {
16405 @val = memory_consumption_of_pids_win32( @pid ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016406 }
16407 elsif ( 'darwin' eq $OSNAME )
16408 {
16409 @val = memory_consumption_of_pids_mac( @pid ) ;
16410 }
16411 else
16412 {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016413 # Unix
16414 my @ps = qx{ ps -o vsz -p @pid } ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016415 shift @ps ; # First line is column name "VSZ"
16416 chomp @ps ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016417 # convert to octets
16418
16419 @val = map { $_ * $KIBI } @ps ;
16420 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016421 return( @val ) ;
16422}
16423
16424
16425sub memory_consumption_of_pids_mac
16426{
16427 my @pid = @_ ;
16428 # Use IPC::Open3 from perlcrit -3
16429 # But it stalls on Darwin, I don't understand why!
16430 #my @ps = backtick( "ps -o rss -p @pid" ) ;
16431 #myprint( "ps: @ps" ) ;
16432 my @ps = qx{ ps -o rss -p @pid } ;
16433 shift @ps ; # First line is column name "RSS"
16434 chomp @ps ;
16435 my @val = map { $_ * $KIBI } @ps ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016436 return( @val ) ;
16437}
16438
16439sub memory_consumption_of_pids_win32
16440{
16441 # Windows
16442 my @PID = @_;
16443 my %PID;
16444 # hash of pids as key values
16445 map { $PID{$_}++ } @PID;
16446
16447 # Does not work but should work reading the tasklist documentation
16448 #@ps = qx{ tasklist /FI "PID eq @PID" };
16449
16450 my @ps = qx{ tasklist /NH /FO CSV } ;
16451 #my @ps = backtick( 'tasklist /NH /FO CSV' ) ;
16452 #myprint( "-" x $STD_CHAR_PER_LINE, "\n", @ps, "-" x $STD_CHAR_PER_LINE, "\n" ) ;
16453 my @val;
16454 foreach my $line (@ps) {
16455 my($name, $pid, $mem) = (split ',', $line )[0,1,4];
16456 next if (! $pid);
16457 #myprint( "[$name][$pid][$mem]" ) ;
16458 if ($PID{remove_qq($pid)}) {
16459 #myprint( "MATCH !\n" ) ;
16460 chomp $mem ;
16461 $mem = remove_qq($mem);
16462 $mem = remove_Ko($mem);
16463 $mem = remove_not_num($mem);
16464 #myprint( "[$mem]\n" ) ;
16465 push @val, $mem * $KIBI;
16466 }
16467 }
16468 return(@val);
16469}
16470
16471
16472sub tests_backtick
16473{
16474 note( 'Entering tests_backtick()' ) ;
16475
16476 is( undef, backtick( ), 'backtick: no args' ) ;
16477 is( undef, backtick( q{} ), 'backtick: empty command' ) ;
16478
16479 SKIP: {
16480 skip( 'test for MSWin32', 5 ) if ('MSWin32' ne $OSNAME) ;
16481 my @output ;
16482 @output = backtick( 'echo Hello World!' ) ;
16483 # Add \r on Windows.
16484 ok( "Hello World!\r\n" eq $output[0], 'backtick: echo Hello World!' ) ;
16485 $sync->{ debug } and myprint( "[@output]" ) ;
16486 @output = backtick( 'echo Hello & echo World!' ) ;
16487 ok( "Hello \r\n" eq $output[0], 'backtick: echo Hello & echo World! line 1' ) ;
16488 ok( "World!\r\n" eq $output[1], 'backtick: echo Hello & echo World! line 2' ) ;
16489 $sync->{ debug } and myprint( "[@output][$output[0]][$output[1]]" ) ;
16490 # Scalar context
16491 ok( "Hello World!\r\n" eq backtick( 'echo Hello World!' ),
16492 'backtick: echo Hello World! scalar' ) ;
16493 ok( "Hello \r\nWorld!\r\n" eq backtick( 'echo Hello & echo World!' ),
16494 'backtick: echo Hello & echo World! scalar 2 lines' ) ;
16495 } ;
16496 SKIP: {
16497 skip( 'test for Unix', 7 ) if ('MSWin32' eq $OSNAME) ;
16498 is( undef, backtick( 'aaaarrrg' ), 'backtick: aaaarrrg command not found' ) ;
16499 # Array context
16500 my @output ;
16501 @output = backtick( 'echo Hello World!' ) ;
16502 ok( "Hello World!\n" eq $output[0], 'backtick: echo Hello World!' ) ;
16503 $sync->{ debug } and myprint( "[@output]" ) ;
16504 @output = backtick( "echo Hello\necho World!" ) ;
16505 ok( "Hello\n" eq $output[0], 'backtick: echo Hello; echo World! line 1' ) ;
16506 ok( "World!\n" eq $output[1], 'backtick: echo Hello; echo World! line 2' ) ;
16507 $sync->{ debug } and myprint( "[@output]" ) ;
16508 # Scalar context
16509 ok( "Hello World!\n" eq backtick( 'echo Hello World!' ),
16510 'backtick: echo Hello World! scalar' ) ;
16511 ok( "Hello\nWorld!\n" eq backtick( "echo Hello\necho World!" ),
16512 'backtick: echo Hello; echo World! scalar 2 lines' ) ;
16513 # Return error positive value, that's ok
16514 is( undef, backtick( 'false' ), 'backtick: false returns no output' ) ;
16515 my $mem = backtick( "ps -o vsz -p $PROCESS_ID" ) ;
16516 $sync->{ debug } and myprint( "MEM=$mem\n" ) ;
16517
16518 }
16519
16520 note( 'Leaving tests_backtick()' ) ;
16521 return ;
16522}
16523
16524
16525sub backtick
16526{
16527 my $command = shift ;
16528
16529 if ( ! $command ) { return ; }
16530
16531 my ( $writer, $reader, $err ) ;
16532 my @output ;
16533 my $pid ;
16534 my $eval = eval {
16535 $pid = IPC::Open3::open3( $writer, $reader, $err, $command ) ;
16536 } ;
16537 if ( $EVAL_ERROR ) {
16538 myprint( $EVAL_ERROR ) ;
16539 return ;
16540 }
16541 if ( ! $eval ) { return ; }
16542 if ( ! $pid ) { return ; }
16543 waitpid( $pid, 0 ) ;
16544 @output = <$reader>; # Output here
16545 #
16546 #my @errors = <$err>; #Errors here, instead of the console
16547 if ( not @output ) { return ; }
16548 #myprint( @output ) ;
16549
16550 if ( $output[0] =~ /\Qopen3: exec of $command failed\E/mxs ) { return ; }
16551 if ( wantarray ) {
16552 return( @output ) ;
16553 } else {
16554 return( join( q{}, @output) ) ;
16555 }
16556}
16557
16558
16559
16560sub tests_check_binary_embed_all_dyn_libs
16561{
16562 note( 'Entering tests_check_binary_embed_all_dyn_libs()' ) ;
16563
16564 is( 1, check_binary_embed_all_dyn_libs( ), 'check_binary_embed_all_dyn_libs: no args => 1' ) ;
16565
16566 note( 'Leaving tests_check_binary_embed_all_dyn_libs()' ) ;
16567
16568 return ;
16569}
16570
16571
16572sub check_binary_embed_all_dyn_libs
16573{
16574 my @search_dyn_lib_locale = search_dyn_lib_locale( ) ;
16575
16576 if ( @search_dyn_lib_locale )
16577 {
16578 myprint( "Found myself $PROGRAM_NAME pid $PROCESS_ID using locale dynamic libraries that seems out of myself:\n" ) ;
16579 myprint( @search_dyn_lib_locale ) ;
16580 if ( $PROGRAM_NAME =~ m{imapsync_bin_Darwin} )
16581 {
16582 return 0 ;
16583 }
16584 elsif ( $PROGRAM_NAME =~ m{imapsync.*\.exe} )
16585 {
16586 return 0 ;
16587 }
16588 else
16589 {
16590 # is always ok for non binary
16591 return 1 ;
16592 }
16593 }
16594 else
16595 {
16596 # Found only embedded dynamic lib
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016597 myprint( "Found only embedded dynamic lib. Good!\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016598 return 1 ;
16599 }
16600}
16601
16602sub search_dyn_lib_locale
16603{
16604 if ( 'darwin' eq $OSNAME )
16605 {
16606 return search_dyn_lib_locale_darwin( ) ;
16607 }
16608 if ( 'linux' eq $OSNAME )
16609 {
16610 return search_dyn_lib_locale_linux( ) ;
16611 }
16612 if ( 'MSWin32' eq $OSNAME )
16613 {
16614 return search_dyn_lib_locale_MSWin32( ) ;
16615 }
16616
16617}
16618
16619sub search_dyn_lib_locale_darwin
16620{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016621 my $command = qq{ lsof -p $PROCESS_ID | grep ' REG ' | grep .dylib | grep -v '/par-' } ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016622 myprint( "Search non embeded dynamic libs with the command: $command\n" ) ;
16623 return backtick( $command ) ;
16624}
16625
16626sub search_dyn_lib_locale_linux
16627{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016628 my $command = qq{ lsof -p $PROCESS_ID | grep ' REG ' | grep -v '/tmp/par-' | grep '\.so' } ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016629 myprint( "Search non embeded dynamic libs with the command: $command\n" ) ;
16630 return backtick( $command ) ;
16631}
16632
16633sub search_dyn_lib_locale_MSWin32
16634{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016635 my $command = qq{ Listdlls.exe $PROCESS_ID|findstr Strawberry } ;
16636 # $command = qq{ Listdlls.exe $PROCESS_ID|findstr Strawberry } ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016637 myprint( "Search non embeded dynamic libs with the command: $command\n" ) ;
16638 return qx( $command ) ;
16639}
16640
16641
16642
16643sub remove_not_num
16644{
16645
16646 my $string = shift ;
16647 $string =~ tr/0-9//cd ;
16648 #myprint( "tr [$string]\n" ) ;
16649 return( $string ) ;
16650}
16651
16652sub tests_remove_not_num
16653{
16654 note( 'Entering tests_remove_not_num()' ) ;
16655
16656 ok( '123' eq remove_not_num( 123 ), 'remove_not_num( 123 )' ) ;
16657 ok( '123' eq remove_not_num( '123' ), q{remove_not_num( '123' )} ) ;
16658 ok( '123' eq remove_not_num( '12 3' ), q{remove_not_num( '12 3' )} ) ;
16659 ok( '123' eq remove_not_num( 'a 12 3 Ko' ), q{remove_not_num( 'a 12 3 Ko' )} ) ;
16660
16661 note( 'Leaving tests_remove_not_num()' ) ;
16662 return ;
16663}
16664
16665sub remove_Ko
16666{
16667 my $string = shift;
16668 if ($string =~ /^(.*)\sKo$/xo) {
16669 return($1);
16670 }else{
16671 return($string);
16672 }
16673}
16674
16675sub remove_qq
16676{
16677 my $string = shift;
16678 if ($string =~ /^"(.*)"$/xo) {
16679 return($1);
16680 }else{
16681 return($string);
16682 }
16683}
16684
16685sub memory_consumption_ratio
16686{
16687
16688 my ($base) = @_;
16689 $base ||= 1;
16690 my $consu = memory_consumption();
16691 return($consu / $base);
16692}
16693
16694
16695sub date_from_rcs
16696{
16697 my $d = shift ;
16698
16699 my %num2mon = qw( 01 Jan 02 Feb 03 Mar 04 Apr 05 May 06 Jun 07 Jul 08 Aug 09 Sep 10 Oct 11 Nov 12 Dec ) ;
16700 if ($d =~ m{(\d{4})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) {
16701 # Handles the following format
16702 # 2015/07/10 11:05:59 -- Generated by RCS Date tag.
16703 #myprint( "$d\n" ) ;
16704 #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
16705 my ($year, $month, $day, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6) ;
16706 $month = $num2mon{$month} ;
16707 $d = "$day-$month-$year $hour:$min:$sec +0000" ;
16708 #myprint( "$d\n" ) ;
16709 }
16710 return( $d ) ;
16711}
16712
16713sub tests_date_from_rcs
16714{
16715 note( 'Entering tests_date_from_rcs()' ) ;
16716
16717 ok('19-Sep-2015 16:11:07 +0000'
16718 eq date_from_rcs('Date: 2015/09/19 16:11:07 '), 'date_from_rcs from RCS date' ) ;
16719
16720 note( 'Leaving tests_date_from_rcs()' ) ;
16721 return ;
16722}
16723
16724sub good_date
16725{
16726 # two incoming formats:
16727 # header Tue, 24 Aug 2010 16:00:00 +0200
16728 # internal 24-Aug-2010 16:00:00 +0200
16729
16730 # outgoing format: internal date format
16731 # 24-Aug-2010 16:00:00 +0200
16732
16733 my $d = shift ;
16734 return(q{}) if not defined $d;
16735
16736 SWITCH: {
16737 if ( $d =~ m{(\d?)(\d-...-\d{4})(\s\d{2}:\d{2}:\d{2})(\s(?:\+|-)\d{4})?}xo ) {
16738 #myprint( "internal: [$1][$2][$3][$4]\n" ) ;
16739 my ($day_1, $date_rest, $hour, $zone) = ($1,$2,$3,$4) ;
16740 $day_1 = '0' if ($day_1 eq q{}) ;
16741 $zone = ' +0000' if not defined $zone ;
16742 $d = $day_1 . $date_rest . $hour . $zone ;
16743 last SWITCH ;
16744 }
16745
16746 if ($d =~ m{(?:\w{3,},\s)?(\d{1,2}),?\s+(\w{3,})\s+(\d{2,4})\s+(\d{1,2})(?::|\.)(\d{1,2})(?:(?::|\.)(\d{1,2}))?\s*((?:\+|-)\d{4})?}xo ) {
16747 # Handles any combination of following formats
16748 # Tue, 24 Aug 2010 16:00:00 +0200 -- Standard
16749 # 24 Aug 2010 16:00:00 +0200 -- Missing Day of Week
16750 # Tue, 24 Aug 97 16:00:00 +0200 -- Two digit year
16751 # Tue, 24 Aug 1997 16.00.00 +0200 -- Periods instead of colons
16752 # Tue, 24 Aug 1997 16:00:00 +0200 -- Extra whitespace between year and hour
16753 # Tue, 24 Aug 1997 6:5:2 +0200 -- Single digit hour, min, or second
16754 # Tue, 24, Aug 1997 16:00:00 +0200 -- Extra comma
16755
16756 #myprint( "header: [$1][$2][$3][$4][$5][$6][$7][$8]\n" ) ;
16757 my ($day, $month, $year, $hour, $min, $sec, $zone) = ($1,$2,$3,$4,$5,$6,$7,$8);
16758 $year = '19' . $year if length($year) == 2 && $year =~ m/^[789]/xo;
16759 $year = '20' . $year if length($year) == 2;
16760
16761 $month = substr $month, 0, 3 if length($month) > 4;
16762 $day = mysprintf( '%02d', $day);
16763 $hour = mysprintf( '%02d', $hour);
16764 $min = mysprintf( '%02d', $min);
16765 $sec = '00' if not defined $sec ;
16766 $sec = mysprintf( '%02d', $sec ) ;
16767 $zone = '+0000' if not defined $zone ;
16768 $d = "$day-$month-$year $hour:$min:$sec $zone" ;
16769 last SWITCH ;
16770 }
16771
16772 if ($d =~ m{(?:.{3})\s(...)\s+(\d{1,2})\s(\d{1,2}):(\d{1,2}):(\d{1,2})\s(?:\w{3})?\s?(\d{4})}xo ) {
16773 # Handles any combination of following formats
16774 # Sun Aug 20 11:55:09 2006
16775 # Wed Jan 24 11:58:38 MST 2007
16776 # Wed Jan 2 08:40:57 2008
16777
16778 #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
16779 my ($month, $day, $hour, $min, $sec, $year) = ($1,$2,$3,$4,$5,$6);
16780 $day = mysprintf( '%02d', $day ) ;
16781 $hour = mysprintf( '%02d', $hour ) ;
16782 $min = mysprintf( '%02d', $min ) ;
16783 $sec = mysprintf( '%02d', $sec ) ;
16784 $d = "$day-$month-$year $hour:$min:$sec +0000" ;
16785 last SWITCH ;
16786 }
16787 my %num2mon = qw( 01 Jan 02 Feb 03 Mar 04 Apr 05 May 06 Jun 07 Jul 08 Aug 09 Sep 10 Oct 11 Nov 12 Dec ) ;
16788
16789 if ($d =~ m{(\d{4})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) {
16790 # Handles the following format
16791 # 2015/07/10 11:05:59 -- Generated by RCS Date tag.
16792 #myprint( "$d\n" ) ;
16793 #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
16794 my ($year, $month, $day, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6) ;
16795 $month = $num2mon{$month} ;
16796 $d = "$day-$month-$year $hour:$min:$sec +0000" ;
16797 #myprint( "$d\n" ) ;
16798 last SWITCH ;
16799 }
16800
16801 if ($d =~ m{(\d{2})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) {
16802 # Handles the following format
16803 # 02/06/09 22:18:08 -- Generated by AVTECH TemPageR devices
16804
16805 #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
16806 my ($month, $day, $year, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6);
16807 $year = '20' . $year;
16808 $month = $num2mon{$month};
16809 $d = "$day-$month-$year $hour:$min:$sec +0000";
16810 last SWITCH ;
16811 }
16812
16813 if ($d =~ m{\w{6,},\s(\w{3})\w+\s+(\d{1,2}),\s(\d{4})\s(\d{2}):(\d{2})\s(AM|PM)}xo ) {
16814 # Handles the following format
16815 # Saturday, December 14, 2002 05:00 PM - KBtoys.com order confirmations
16816
16817 my ($month, $day, $year, $hour, $min, $apm) = ($1,$2,$3,$4,$5,$6);
16818
16819 $hour += 12 if $apm eq 'PM' ;
16820 $day = mysprintf( '%02d', $day ) ;
16821 $d = "$day-$month-$year $hour:$min:00 +0000" ;
16822 last SWITCH ;
16823 }
16824
16825 if ($d =~ m{(\w{3})\s(\d{1,2})\s(\d{4})\s(\d{2}):(\d{2}):(\d{2})\s((?:\+|-)\d{4})}xo ) {
16826 # Handles the following format
16827 # Saturday, December 14, 2002 05:00 PM - jr.com order confirmations
16828
16829 my ($month, $day, $year, $hour, $min, $sec, $zone) = ($1,$2,$3,$4,$5,$6,$7);
16830
16831 $day = mysprintf( '%02d', $day ) ;
16832 $d = "$day-$month-$year $hour:$min:$sec $zone";
16833 last SWITCH ;
16834 }
16835
16836 if ($d =~ m{(\d{1,2})-(\w{3})-(\d{4})}xo ) {
16837 # Handles the following format
16838 # 21-Jun-2001 - register.com domain transfer email circa 2001
16839
16840 my ($day, $month, $year) = ($1,$2,$3);
16841 $day = mysprintf( '%02d', $day);
16842 $d = "$day-$month-$year 11:11:11 +0000";
16843 last SWITCH ;
16844 }
16845
16846 # unknown or unmatch => return same string
16847 return($d);
16848 }
16849
16850 $d = qq("$d") ;
16851 return( $d ) ;
16852}
16853
16854
16855sub tests_good_date
16856{
16857 note( 'Entering tests_good_date()' ) ;
16858
16859 ok(q{} eq good_date(), 'good_date no arg');
16860 ok('"24-Aug-2010 16:00:00 +0200"' eq good_date('24-Aug-2010 16:00:00 +0200'), 'good_date internal 2digit zone');
16861 ok('"24-Aug-2010 16:00:00 +0000"' eq good_date('24-Aug-2010 16:00:00'), 'good_date internal 2digit no zone');
16862 ok('"01-Sep-2010 16:00:00 +0200"' eq good_date( '1-Sep-2010 16:00:00 +0200'), 'good_date internal SP 1digit');
16863 ok('"24-Aug-2010 16:00:00 +0200"' eq good_date('Tue, 24 Aug 2010 16:00:00 +0200'), 'good_date header 2digit zone');
16864 ok('"01-Sep-2010 16:00:00 +0000"' eq good_date('Wed, 1 Sep 2010 16:00:00'), 'good_date header SP 1digit zone');
16865 ok('"01-Sep-2010 16:00:00 +0200"' eq good_date('Wed, 1 Sep 2010 16:00:00 +0200'), 'good_date header SP 1digit zone');
16866 ok('"01-Sep-2010 16:00:00 +0200"' eq good_date('Wed, 1 Sep 2010 16:00:00 +0200 (CEST)'), 'good_date header SP 1digit zone');
16867 ok('"06-Feb-2009 22:18:08 +0000"' eq good_date('02/06/09 22:18:08'), 'good_date header TemPageR');
16868 ok('"02-Jan-2008 08:40:57 +0000"' eq good_date('Wed Jan 2 08:40:57 2008'), 'good_date header dice.com support 1digit day');
16869 ok('"20-Aug-2006 11:55:09 +0000"' eq good_date('Sun Aug 20 11:55:09 2006'), 'good_date header dice.com support 2digit day');
16870 ok('"24-Jan-2007 11:58:38 +0000"' eq good_date('Wed Jan 24 11:58:38 MST 2007'), 'good_date header status-now.com');
16871 ok('"24-Aug-2010 16:00:00 +0200"' eq good_date('24 Aug 2010 16:00:00 +0200'), 'good_date header missing date of week');
16872 ok('"24-Aug-2067 16:00:00 +0200"' eq good_date('Tue, 24 Aug 67 16:00:00 +0200'), 'good_date header 2digit year');
16873 ok('"24-Aug-1977 16:00:00 +0200"' eq good_date('Tue, 24 Aug 77 16:00:00 +0200'), 'good_date header 2digit year');
16874 ok('"24-Aug-1987 16:00:00 +0200"' eq good_date('Tue, 24 Aug 87 16:00:00 +0200'), 'good_date header 2digit year');
16875 ok('"24-Aug-1997 16:00:00 +0200"' eq good_date('Tue, 24 Aug 97 16:00:00 +0200'), 'good_date header 2digit year');
16876 ok('"24-Aug-2004 16:00:00 +0200"' eq good_date('Tue, 24 Aug 04 16:00:00 +0200'), 'good_date header 2digit year');
16877 ok('"24-Aug-1997 16:00:00 +0200"' eq good_date('Tue, 24 Aug 1997 16.00.00 +0200'), 'good_date header period time sep');
16878 ok('"24-Aug-1997 16:00:00 +0200"' eq good_date('Tue, 24 Aug 1997 16:00:00 +0200'), 'good_date header extra white space type1');
16879 ok('"24-Aug-1997 05:06:02 +0200"' eq good_date('Tue, 24 Aug 1997 5:6:2 +0200'), 'good_date header 1digit time vals');
16880 ok('"24-Aug-1997 05:06:02 +0200"' eq good_date('Tue, 24, Aug 1997 05:06:02 +0200'), 'good_date header extra commas');
16881 ok('"01-Oct-2003 12:45:24 +0000"' eq good_date('Wednesday, 01 October 2003 12:45:24 CDT'), 'good_date header no abbrev');
16882 ok('"11-Jan-2005 17:58:27 -0500"' eq good_date('Tue, 11 Jan 2005 17:58:27 -0500'), 'good_date extra white space');
16883 ok('"18-Dec-2002 15:07:00 +0000"' eq good_date('Wednesday, December 18, 2002 03:07 PM'), 'good_date kbtoys.com orders');
16884 ok('"16-Dec-2004 02:01:49 -0500"' eq good_date('Dec 16 2004 02:01:49 -0500'), 'good_date jr.com orders');
16885 ok('"21-Jun-2001 11:11:11 +0000"' eq good_date('21-Jun-2001'), 'good_date register.com domain transfer');
16886 ok('"18-Nov-2012 18:34:38 +0100"' eq good_date('Sun, 18 Nov 2012 18:34:38 +0100'), 'good_date pop2imap bug (Westeuropäische Normalzeit)');
16887 ok('"19-Sep-2015 16:11:07 +0000"' eq good_date('Date: 2015/09/19 16:11:07 '), 'good_date from RCS date' ) ;
16888
16889 note( 'Leaving tests_good_date()' ) ;
16890 return ;
16891}
16892
16893
16894sub tests_list_keys_in_2_not_in_1
16895{
16896 note( 'Entering tests_list_keys_in_2_not_in_1()' ) ;
16897
16898
16899 my @list;
16900 ok( ! list_keys_in_2_not_in_1( {}, {}), 'list_keys_in_2_not_in_1: {} {}');
16901 ok( 0 == compare_lists( [], [ list_keys_in_2_not_in_1( {}, {} ) ] ), 'list_keys_in_2_not_in_1: {} {}');
16902 ok( 0 == compare_lists( ['a','b'], [ list_keys_in_2_not_in_1( {}, {'a' => 1, 'b' => 1}) ]), 'list_keys_in_2_not_in_1: {} {a, b}');
16903 ok( 0 == compare_lists( ['b'], [ list_keys_in_2_not_in_1( {'a' => 1}, {'a' => 1, 'b' => 1}) ]), 'list_keys_in_2_not_in_1: {a} {a, b}');
16904 ok( 0 == compare_lists( [], [ list_keys_in_2_not_in_1( {'a' => 1, 'b' => 1}, {'a' => 1, 'b' => 1}) ]), 'list_keys_in_2_not_in_1: {a, b} {a, b}');
16905 ok( 0 == compare_lists( [], [ list_keys_in_2_not_in_1( {'a' => 1, 'b' => 1, 'c' => 1}, {'a' => 1, 'b' => 1}) ]), 'list_keys_in_2_not_in_1: {a, b, c} {a, b}');
16906 ok( 0 == compare_lists( ['b'], [ list_keys_in_2_not_in_1( {'a' => 1, 'c' => 1}, {'a' => 1, 'b' => 1}) ]), 'list_keys_in_2_not_in_1: {a, b, c} {a, b}');
16907
16908 note( 'Leaving tests_list_keys_in_2_not_in_1()' ) ;
16909 return ;
16910}
16911
16912sub list_keys_in_2_not_in_1
16913{
16914 my $hash_1_ref = shift;
16915 my $hash_2_ref = shift;
16916 my @list;
16917
16918 foreach my $key ( sort keys %{ $hash_2_ref } ) {
16919 #$sync->{ debug } and print "$key\n" ;
16920 if ( exists $hash_1_ref->{$key} )
16921 {
16922 next ;
16923 }
16924 #$sync->{ debug } and print "list_keys_in_2_not_in_1: $key\n" ;
16925 push @list, $key ;
16926 }
16927 #$sync->{ debug } and print "@list\n" ;
16928 return( @list ) ;
16929}
16930
16931
16932sub list_folders_in_2_not_in_1
16933{
16934
16935 my ( @h2_folders_not_in_h1, %h2_folders_not_in_h1 ) ;
16936 @h2_folders_not_in_h1 = list_keys_in_2_not_in_1( \%h1_folders_all, \%h2_folders_all ) ;
16937 map { $h2_folders_not_in_h1{$_} = 1} @h2_folders_not_in_h1 ;
16938 @h2_folders_not_in_h1 = list_keys_in_2_not_in_1( \%h2_folders_from_1_all, \%h2_folders_not_in_h1 ) ;
16939 #$sync->{ debug } and print "h2_folders_not_in_h1: @h2_folders_not_in_h1\n" ;
16940 return( reverse @h2_folders_not_in_h1 ) ;
16941}
16942
16943sub tests_nb_messages_in_2_not_in_1
16944{
16945 note( 'Entering tests_stats_across_folders()' ) ;
16946 is( undef, nb_messages_in_2_not_in_1( ), 'nb_messages_in_2_not_in_1: no args => undef' ) ;
16947
16948 my $mysync->{ h1_folders_of_md5 }->{ 'some_id_01' }->{ 'some_folder_01' } = 1 ;
16949 is( 0, nb_messages_in_2_not_in_1( $mysync ), 'nb_messages_in_2_not_in_1: no messages in 2 => 0' ) ;
16950
16951 $mysync->{ h1_folders_of_md5 }->{ 'some_id_in_1_and_2' }->{ 'some_folder_01' } = 2 ;
16952 $mysync->{ h2_folders_of_md5 }->{ 'some_id_in_1_and_2' }->{ 'some_folder_02' } = 4 ;
16953
16954 is( 0, nb_messages_in_2_not_in_1( $mysync ), 'nb_messages_in_2_not_in_1: a common message => 0' ) ;
16955
16956 $mysync->{ h2_folders_of_md5 }->{ 'some_id_in_2_not_in_1' }->{ 'some_folder_02' } = 1 ;
16957 is( 1, nb_messages_in_2_not_in_1( $mysync ), 'nb_messages_in_2_not_in_1: one message in_2_not_in_1 => 1' ) ;
16958
16959 $mysync->{ h2_folders_of_md5 }->{ 'some_other_id_in_2_not_in_1' }->{ 'some_folder_02' } = 3 ;
16960 is( 2, nb_messages_in_2_not_in_1( $mysync ), 'nb_messages_in_2_not_in_1: two messages in_2_not_in_1 => 2' ) ;
16961
16962 note( 'Leaving tests_stats_across_folders()' ) ;
16963 return ;
16964}
16965
16966sub nb_messages_in_2_not_in_1
16967{
16968 my $mysync = shift ;
16969 if ( not defined $mysync ) { return ; }
16970
16971 $mysync->{ nb_messages_in_2_not_in_1 } = scalar(
16972 list_keys_in_2_not_in_1(
16973 $mysync->{ h1_folders_of_md5 },
16974 $mysync->{ h2_folders_of_md5 } ) ) ;
16975
16976 return $mysync->{ nb_messages_in_2_not_in_1 } ;
16977}
16978
16979
16980sub nb_messages_in_1_not_in_2
16981{
16982 my $mysync = shift ;
16983 if ( not defined $mysync ) { return ; }
16984
16985 $mysync->{ nb_messages_in_1_not_in_2 } = scalar(
16986 list_keys_in_2_not_in_1(
16987 $mysync->{ h2_folders_of_md5 },
16988 $mysync->{ h1_folders_of_md5 } ) ) ;
16989
16990 return $mysync->{ nb_messages_in_1_not_in_2 } ;
16991}
16992
16993
16994
16995sub comment_on_final_diff_in_1_not_in_2
16996{
16997 my $mysync = shift ;
16998
16999 if ( not defined $mysync
17000 or $mysync->{ justfolders }
17001 or $mysync->{ useuid }
17002 )
17003 {
17004 return ;
17005 }
17006
17007 my $nb_identified_h1_messages = scalar( keys %{ $mysync->{ h1_folders_of_md5 } } ) ;
17008 my $nb_identified_h2_messages = scalar( keys %{ $mysync->{ h2_folders_of_md5 } } ) ;
17009 $mysync->{ debug } and myprint( "nb_keys h1_folders_of_md5 $nb_identified_h1_messages\n" ) ;
17010 $mysync->{ debug } and myprint( "nb_keys h2_folders_of_md5 $nb_identified_h2_messages\n" ) ;
17011
17012 if ( 0 == $nb_identified_h1_messages ) { return ; }
17013
17014 # Calculate if not yet done
17015 if ( not defined $mysync->{ nb_messages_in_1_not_in_2 } )
17016 {
17017 nb_messages_in_1_not_in_2( $mysync ) ;
17018 }
17019
17020
17021 if ( 0 == $mysync->{ nb_messages_in_1_not_in_2 } )
17022 {
17023 myprint( "The sync looks good, all ",
17024 $nb_identified_h1_messages,
17025 " identified messages in host1 are on host2.\n" ) ;
17026 }
17027 else
17028 {
17029 myprint( "The sync is not finished, there are ",
17030 $mysync->{ nb_messages_in_1_not_in_2 },
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017031 " among ",
17032 $nb_identified_h1_messages,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017033 " identified messages in host1 that are not on host2.\n" ) ;
17034 }
17035
17036
17037 if ( 1 <= $mysync->{ h1_nb_msg_noheader } )
17038 {
17039 myprint( "There are ",
17040 $mysync->{ h1_nb_msg_noheader },
17041 " unidentified messages (usually Sent or Draft messages).",
17042 " To sync them add option --addheader\n" ) ;
17043 }
17044 else
17045 {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017046 myprint( "There is no unidentified message on host1.\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017047 }
17048
17049 return ;
17050}
17051
17052sub comment_on_final_diff_in_2_not_in_1
17053{
17054 my $mysync = shift ;
17055
17056 if ( not defined $mysync
17057 or $mysync->{ justfolders }
17058 or $mysync->{ useuid }
17059 )
17060 {
17061 return ;
17062 }
17063
17064 my $nb_identified_h2_messages = scalar( keys %{ $mysync->{ h2_folders_of_md5 } } ) ;
17065 # Calculate if not done yet
17066 if ( not defined $mysync->{ nb_messages_in_2_not_in_1 } )
17067 {
17068 nb_messages_in_2_not_in_1( $mysync ) ;
17069 }
17070
17071 if ( 0 == $mysync->{ nb_messages_in_2_not_in_1 } )
17072 {
17073 myprint( "The sync is strict, all ",
17074 $nb_identified_h2_messages,
17075 " identified messages in host2 are on host1.\n" ) ;
17076 }
17077 else
17078 {
17079 myprint( "The sync is not strict, there are ",
17080 $mysync->{ nb_messages_in_2_not_in_1 },
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017081 " among ",
17082 $nb_identified_h2_messages,
17083 " identified messages in host2 that are not on host1.",
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010017084 " Use --delete2 and sync again to delete them and have a strict sync.\n"
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017085 ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017086 }
17087 return ;
17088}
17089
17090
17091sub tests_match
17092{
17093 note( 'Entering tests_match()' ) ;
17094
17095 # undef serie
17096 is( undef, match( ), 'match: no args => undef' ) ;
17097 is( undef, match( 'lalala' ), 'match: one args => undef' ) ;
17098
17099 # This one gives 0 under a binary made by pp
17100 # but 1 under "normal" Perl interpreter. So a PAR bug?
17101 #is( 1, match( q{}, q{} ), 'match: q{} =~ q{} => 1' ) ;
17102
17103 is( 'lalala', match( 'lalala', 'lalala' ), 'match: lalala =~ lalala => lalala' ) ;
17104 is( 'lalala', match( 'lalala', '^lalala' ), 'match: lalala =~ ^lalala => lalala' ) ;
17105 is( 'lalala', match( 'lalala', 'lalala$' ), 'match: lalala =~ lalala$ => lalala' ) ;
17106 is( 'lalala', match( 'lalala', '^lalala$' ), 'match: lalala =~ ^lalala$ => lalala' ) ;
17107 is( '_lalala_', match( '_lalala_', 'lalala' ), 'match: _lalala_ =~ lalala => _lalala_' ) ;
17108 is( 'lalala', match( 'lalala', '.*' ), 'match: lalala =~ .* => lalala' ) ;
17109 is( 'lalala', match( 'lalala', '.' ), 'match: lalala =~ . => lalala' ) ;
17110 is( '/lalala/', match( '/lalala/', '/lalala/' ), 'match: /lalala/ =~ /lalala/ => /lalala/' ) ;
17111
17112 is( 0, match( 'foo', 's/foo/bar/g' ), 'match: foo =~ s/foo/bar/g => 0' ) ;
17113 is( 's/foo/bar/g', match( 's/foo/bar/g', 's/foo/bar/g' ), 'match: s/foo/bar/g =~ s/foo/bar/g => s/foo/bar/g' ) ;
17114
17115
17116 is( 0, match( 'lalala', 'ooo' ), 'match: lalala =~ ooo => 0' ) ;
17117 is( 0, match( 'lalala', 'lal_ala' ), 'match: lalala =~ lal_ala => 0' ) ;
17118 is( 0, match( 'lalala', '\.' ), 'match: lalala =~ \. => 0' ) ;
17119 is( 0, match( 'lalalaX', '^lalala$' ), 'match: lalalaX =~ ^lalala$ => 0' ) ;
17120 is( 0, match( 'lalala', '/lalala/' ), 'match: lalala =~ /lalala/ => 0' ) ;
17121
17122 is( 'LALALA', match( 'LALALA', '(?i:lalala)' ), 'match: LALALA =~ (?i:lalala) => 1' ) ;
17123
17124 is( undef, match( 'LALALA', '(?{`ls /`})' ), 'match: LALALA =~ (?{`ls /`}) => undef' ) ;
17125 is( undef, match( 'LALALA', '(?{print "CACA"})' ), 'match: LALALA =~ (?{print "CACA"}) => undef' ) ;
17126 is( undef, match( 'CACA', '(??{print "CACA"})' ), 'match: CACA =~ (??{print "CACA"}) => undef' ) ;
17127
17128 note( 'Leaving tests_match()' ) ;
17129
17130 return ;
17131}
17132
17133sub match
17134{
17135 my( $var, $regex ) = @ARG ;
17136
17137 # undef cases
17138 if ( ( ! defined $var ) or ( ! defined $regex ) ) { return ; }
17139
17140 # normal cases
17141 if ( eval { $var =~ qr{$regex} } ) {
17142 return $var ;
17143 }elsif ( $EVAL_ERROR ) {
17144 myprint( "Fatal regex $regex\n" ) ;
17145 return ;
17146 } else {
17147 return 0 ;
17148 }
17149 return ;
17150}
17151
17152
17153sub tests_notmatch
17154{
17155 note( 'Entering tests_notmatch()' ) ;
17156
17157 # undef serie
17158 is( undef, notmatch( ), 'notmatch: no args => undef' ) ;
17159 is( undef, notmatch( 'lalala' ), 'notmatch: one args => undef' ) ;
17160
17161 is( 1, notmatch( 'lalala', '/lalala/' ), 'notmatch: lalala !~ /lalala/ => 1' ) ;
17162 is( 0, notmatch( '/lalala/', '/lalala/' ), 'notmatch: /lalala/ !~ /lalala/ => 0' ) ;
17163 is( 1, notmatch( 'lalala', '/ooo/' ), 'notmatch: lalala !~ /ooo/ => 1' ) ;
17164
17165 # This one gives 1 under a binary made by pp
17166 # but 0 under "normal" Perl interpreter. So a PAR bug, same in tests_match .
17167 #is( 0, notmatch( q{}, q{} ), 'notmatch: q{} !~ q{} => 0' ) ;
17168
17169 is( 0, notmatch( 'lalala', 'lalala' ), 'notmatch: lalala !~ lalala => 0' ) ;
17170 is( 0, notmatch( 'lalala', '^lalala' ), 'notmatch: lalala !~ ^lalala => 0' ) ;
17171 is( 0, notmatch( 'lalala', 'lalala$' ), 'notmatch: lalala !~ lalala$ => 0' ) ;
17172 is( 0, notmatch( 'lalala', '^lalala$' ), 'notmatch: lalala !~ ^lalala$ => 0' ) ;
17173 is( 0, notmatch( '_lalala_', 'lalala' ), 'notmatch: _lalala_ !~ lalala => 0' ) ;
17174 is( 0, notmatch( 'lalala', '.*' ), 'notmatch: lalala !~ .* => 0' ) ;
17175 is( 0, notmatch( 'lalala', '.' ), 'notmatch: lalala !~ . => 0' ) ;
17176
17177
17178 is( 1, notmatch( 'lalala', 'ooo' ), 'notmatch: does not match regex => 1' ) ;
17179 is( 1, notmatch( 'lalala', 'lal_ala' ), 'notmatch: does not match regex => 1' ) ;
17180 is( 1, notmatch( 'lalala', '\.' ), 'notmatch: matches regex => 0' ) ;
17181 is( 1, notmatch( 'lalalaX', '^lalala$' ), 'notmatch: does not match regex => 1' ) ;
17182
17183 note( 'Leaving tests_notmatch()' ) ;
17184
17185 return ;
17186}
17187
17188sub notmatch
17189{
17190 my( $var, $regex ) = @ARG ;
17191
17192 # undef cases
17193 if ( ( ! defined $var ) or ( ! defined $regex ) ) { return ; }
17194
17195 # normal cases
17196 if ( eval { $var !~ $regex } ) {
17197 return 1 ;
17198 }elsif ( $EVAL_ERROR ) {
17199 myprint( "Fatal regex $regex\n" ) ;
17200 return ;
17201 }else{
17202 return 0 ;
17203 }
17204 return ;
17205}
17206
17207
17208sub delete_folders_in_2_not_in_1
17209{
17210
17211 foreach my $folder ( @h2_folders_not_in_1 ) {
17212 if ( defined $delete2foldersonly and eval "\$folder !~ $delete2foldersonly" ) {
17213 myprint( "Not deleting $folder because of --delete2foldersonly $delete2foldersonly\n" ) ;
17214 next ;
17215 }
17216 if ( defined $delete2foldersbutnot and eval "\$folder =~ $delete2foldersbutnot" ) {
17217 myprint( "Not deleting $folder because of --delete2foldersbutnot $delete2foldersbutnot\n" ) ;
17218 next ;
17219 }
17220 my $res = $sync->{dry} ; # always success in dry mode!
17221 $sync->{imap2}->unsubscribe( $folder ) if ( ! $sync->{dry} ) ;
17222 $res = $sync->{imap2}->delete( $folder ) if ( ! $sync->{dry} ) ;
17223 if ( $res ) {
17224 myprint( "Deleted $folder", "$sync->{dry_message}", "\n" ) ;
17225 }else{
17226 myprint( "Deleting $folder failed", "\n" ) ;
17227 }
17228 }
17229 return ;
17230}
17231
17232sub delete_folder
17233{
17234 my ( $mysync, $imap, $folder, $Side ) = @_ ;
17235 if ( ! $mysync ) { return ; }
17236 if ( ! $imap ) { return ; }
17237 if ( ! $folder ) { return ; }
17238 $Side ||= 'HostX' ;
17239
17240 my $res = $mysync->{dry} ; # always success in dry mode!
17241 if ( ! $mysync->{dry} ) {
17242 $imap->unsubscribe( $folder ) ;
17243 $res = $imap->delete( $folder ) ;
17244 }
17245 if ( $res ) {
17246 myprint( "$Side deleted $folder", $mysync->{dry_message}, "\n" ) ;
17247 return 1 ;
17248 }else{
17249 myprint( "$Side deleting $folder failed", "\n" ) ;
17250 return ;
17251 }
17252}
17253
17254sub delete1emptyfolders
17255{
17256 my $mysync = shift ;
17257 if ( ! $mysync ) { return ; } # abort if no parameter
17258 if ( ! $mysync->{delete1emptyfolders} ) { return ; } # abort if --delete1emptyfolders off
17259 my $imap = $mysync->{imap1} ;
17260 if ( ! $imap ) { return ; } # abort if no imap
17261 if ( $imap->IsUnconnected( ) ) { return ; } # abort if disconnected
17262
17263 my %folders_kept ;
17264 myprint( qq{Host1 deleting empty folders\n} ) ;
17265 foreach my $folder ( reverse sort @{ $mysync->{h1_folders_wanted} } ) {
17266 my $parenthood = $imap->is_parent( $folder ) ;
17267 if ( defined $parenthood and $parenthood ) {
17268 myprint( "Host1: folder $folder has subfolders\n" ) ;
17269 $folders_kept{ $folder }++ ;
17270 next ;
17271 }
17272 my $nb_messages_select = examine_folder_and_count( $mysync, $imap, $folder, 'Host1' ) ;
17273 if ( ! defined $nb_messages_select ) { next ; } # Select failed => Neither continue nor keep this folder }
17274 my $nb_messages_search = scalar( @{ $imap->messages( ) } ) ;
17275 if ( 0 != $nb_messages_select and 0 != $nb_messages_search ) {
17276 myprint( "Host1: folder $folder has messages: $nb_messages_search (search) $nb_messages_select (select)\n" ) ;
17277 $folders_kept{ $folder }++ ;
17278 next ;
17279 }
17280 if ( 0 != $nb_messages_select + $nb_messages_search ) {
17281 myprint( "Host1: folder $folder odd messages count: $nb_messages_search (search) $nb_messages_select (select)\n" ) ;
17282 $folders_kept{ $folder }++ ;
17283 next ;
17284 }
17285 # Here we must have 0 messages by messages() aka "SEARCH ALL" and also "EXAMINE"
17286 if ( uc $folder eq 'INBOX' ) {
17287 myprint( "Host1: Not deleting $folder\n" ) ;
17288 $folders_kept{ $folder }++ ;
17289 next ;
17290 }
17291 myprint( "Host1: deleting empty folder $folder\n" ) ;
17292 # can not delete a SELECTed or EXAMINEd folder so closing it
17293 # could changed be SELECT INBOX
17294 $imap->close( ) ; # close after examine does not expunge; anyway expunging an empty folder...
17295 if ( delete_folder( $mysync, $imap, $folder, 'Host1' ) ) {
17296 next ; # Deleted, good!
17297 }else{
17298 $folders_kept{ $folder }++ ;
17299 next ; # Not deleted, bad!
17300 }
17301 }
17302 remove_deleted_folders_from_wanted_list( $mysync, %folders_kept ) ;
17303 myprint( qq{Host1 ended deleting empty folders\n} ) ;
17304 return ;
17305}
17306
17307sub remove_deleted_folders_from_wanted_list
17308{
17309 my ( $mysync, %folders_kept ) = @ARG ;
17310
17311 my @h1_folders_wanted_init = @{ $mysync->{h1_folders_wanted} } ;
17312 my @h1_folders_wanted_last ;
17313 foreach my $folder ( @h1_folders_wanted_init ) {
17314 if ( $folders_kept{ $folder } ) {
17315 push @h1_folders_wanted_last, $folder ;
17316 }
17317 }
17318 @{ $mysync->{h1_folders_wanted} } = @h1_folders_wanted_last ;
17319 return ;
17320}
17321
17322
17323sub examine_folder_and_count
17324{
17325 my ( $mysync, $imap, $folder, $Side ) = @_ ;
17326 $Side ||= 'HostX' ;
17327
17328 if ( ! examine_folder( $mysync, $imap, $folder, $Side ) ) {
17329 return ;
17330 }
17331 my $nb_messages_select = count_from_select( $imap->History ) ;
17332 return $nb_messages_select ;
17333}
17334
17335
17336sub tests_delete1emptyfolders
17337{
17338 note( 'Entering tests_delete1emptyfolders()' ) ;
17339
17340
17341 is( undef, delete1emptyfolders( ), q{delete1emptyfolders: undef} ) ;
17342 my $syncT ;
17343 is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: undef 2} ) ;
17344 my $imapT ;
17345 $syncT->{imap1} = $imapT ;
17346 is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: undef imap} ) ;
17347
17348 require_ok( "Test::MockObject" ) ;
17349 $imapT = Test::MockObject->new( ) ;
17350 $syncT->{imap1} = $imapT ;
17351
17352 $imapT->set_true( 'IsUnconnected' ) ;
17353 is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: Unconnected imap} ) ;
17354
17355 # Now connected tests
17356 $imapT->set_false( 'IsUnconnected' ) ;
17357 $imapT->mock( 'LastError', sub { q{LastError mocked} } ) ;
17358
17359 $syncT->{delete1emptyfolders} = 0 ;
17360 tests_delete1emptyfolders_unit(
17361 $syncT,
17362 [ qw{ INBOX DELME1 DELME2 } ],
17363 [ qw{ INBOX DELME1 DELME2 } ],
17364 q{tests_delete1emptyfolders: --delete1emptyfolders OFF}
17365 ) ;
17366
17367 # All are parents => no deletion at all
17368 $imapT->set_true( 'is_parent' ) ;
17369 $syncT->{delete1emptyfolders} = 1 ;
17370 tests_delete1emptyfolders_unit(
17371 $syncT,
17372 [ qw{ INBOX DELME1 DELME2 } ],
17373 [ qw{ INBOX DELME1 DELME2 } ],
17374 q{tests_delete1emptyfolders: --delete1emptyfolders ON}
17375 ) ;
17376
17377 # No parents but examine false for all => skip all
17378 $imapT->set_false( 'is_parent', 'examine' ) ;
17379
17380 tests_delete1emptyfolders_unit(
17381 $syncT,
17382 [ qw{ INBOX DELME1 DELME2 } ],
17383 [ ],
17384 q{tests_delete1emptyfolders: EXAMINE fails}
17385 ) ;
17386
17387 # examine ok for all but History bad => skip all
17388 $imapT->set_true( 'examine' ) ;
17389 $imapT->mock( 'History', sub { ( q{History badly mocked} ) } ) ;
17390 tests_delete1emptyfolders_unit(
17391 $syncT,
17392 [ qw{ INBOX DELME1 DELME2 } ],
17393 [ ],
17394 q{tests_delete1emptyfolders: examine ok but History badly mocked so count messages fails}
17395 ) ;
17396
17397 # History good but some messages EXISTS == messages() => no deletion
17398 $imapT->mock( 'History', sub { ( q{* 2 EXISTS} ) } ) ;
17399 $imapT->mock( 'messages', sub { [ qw{ UID_1 UID_2 } ] } ) ;
17400 tests_delete1emptyfolders_unit(
17401 $syncT,
17402 [ qw{ INBOX DELME1 DELME2 } ],
17403 [ qw{ INBOX DELME1 DELME2 } ],
17404 q{tests_delete1emptyfolders: History EXAMINE ok, several messages}
17405 ) ;
17406
17407 # 0 EXISTS but != messages() => no deletion
17408 $imapT->mock( 'History', sub { ( q{* 0 EXISTS} ) } ) ;
17409 $imapT->mock( 'messages', sub { [ qw{ UID_1 UID_2 } ] } ) ;
17410 tests_delete1emptyfolders_unit(
17411 $syncT,
17412 [ qw{ INBOX DELME1 DELME2 } ],
17413 [ qw{ INBOX DELME1 DELME2 } ],
17414 q{tests_delete1emptyfolders: 0 EXISTS but 2 by messages()}
17415 ) ;
17416
17417 # 1 EXISTS but != 0 == messages() => no deletion
17418 $imapT->mock( 'History', sub { ( q{* 1 EXISTS} ) } ) ;
17419 $imapT->mock( 'messages', sub { [ ] } ) ;
17420 tests_delete1emptyfolders_unit(
17421 $syncT,
17422 [ qw{ INBOX DELME1 DELME2 } ],
17423 [ qw{ INBOX DELME1 DELME2 } ],
17424 q{tests_delete1emptyfolders: 1 EXISTS but 0 by messages()}
17425 ) ;
17426
17427 # 0 EXISTS and 0 == messages() => deletion except INBOX
17428 $imapT->mock( 'History', sub { ( q{* 0 EXISTS} ) } ) ;
17429 $imapT->mock( 'messages', sub { [ ] } ) ;
17430 $imapT->set_true( qw{ delete close unsubscribe } ) ;
17431 $syncT->{dry_message} = q{ (not really since in a mocked test)} ;
17432 tests_delete1emptyfolders_unit(
17433 $syncT,
17434 [ qw{ INBOX DELME1 DELME2 } ],
17435 [ qw{ INBOX } ],
17436 q{tests_delete1emptyfolders: 0 EXISTS 0 by messages() delete folders, keep INBOX}
17437 ) ;
17438
17439 note( 'Leaving tests_delete1emptyfolders()' ) ;
17440 return ;
17441}
17442
17443sub tests_delete1emptyfolders_unit
17444{
17445 note( 'Entering tests_delete1emptyfolders_unit()' ) ;
17446
17447 my $syncT = shift ;
17448 my $folders1wanted_init_ref = shift ;
17449 my $folders1wanted_after_ref = shift ;
17450 my $comment = shift || q{delete1emptyfolders:} ;
17451
17452 my @folders1wanted_init = @{ $folders1wanted_init_ref } ;
17453 my @folders1wanted_after = @{ $folders1wanted_after_ref } ;
17454
17455 @{ $syncT->{h1_folders_wanted} } = @folders1wanted_init ;
17456
17457 is_deeply( $syncT->{h1_folders_wanted}, \@folders1wanted_init, qq{$comment, init check} ) ;
17458 delete1emptyfolders( $syncT ) ;
17459 is_deeply( $syncT->{h1_folders_wanted}, \@folders1wanted_after, qq{$comment, after check} ) ;
17460
17461 note( 'Leaving tests_delete1emptyfolders_unit()' ) ;
17462 return ;
17463}
17464
17465sub extract_header
17466{
17467 my $string = shift ;
17468
17469 my ( $header ) = split /\n\n/x, $string ;
17470 if ( ! $header ) { return( q{} ) ; }
17471 #myprint( "[$header]\n" ) ;
17472 return( $header ) ;
17473}
17474
17475sub tests_extract_header
17476{
17477 note( 'Entering tests_extract_header()' ) ;
17478
17479my $h = <<'EOM';
17480Message-Id: <20100428101817.A66CB162474E@plume.est.belle>
17481Date: Wed, 28 Apr 2010 12:18:17 +0200 (CEST)
17482From: gilles@louloutte.dyndns.org (Gilles LAMIRAL)
17483EOM
17484chomp $h ;
17485ok( $h eq extract_header(
17486<<'EOM'
17487Message-Id: <20100428101817.A66CB162474E@plume.est.belle>
17488Date: Wed, 28 Apr 2010 12:18:17 +0200 (CEST)
17489From: gilles@louloutte.dyndns.org (Gilles LAMIRAL)
17490
17491body
17492lalala
17493EOM
17494), 'extract_header: 1') ;
17495
17496
17497
17498 note( 'Leaving tests_extract_header()' ) ;
17499 return ;
17500}
17501
17502sub decompose_header{
17503 my $string = shift ;
17504
17505 # a hash, for a keyword header KEY value are list of strings [VAL1, VAL1_other, etc]
17506 # Think of multiple "Received:" header lines.
17507 my $header = { } ;
17508
17509 my ($key, $val ) ;
17510 my @line = split /\n|\r\n/x, $string ;
17511 foreach my $line ( @line ) {
17512 #myprint( "DDD $line\n" ) ;
17513 # End of header
17514 last if ( $line =~ m{^$}xo ) ;
17515 # Key: value
17516 if ( $line =~ m/(^[^:]+):\s(.*)/xo ) {
17517 $key = $1 ;
17518 $val = $2 ;
17519 $debugdev and myprint( "DDD KV [$key] [$val]\n" ) ;
17520 push @{ $header->{ $key } }, $val ;
17521 # blanc and value => value from previous line continues
17522 }elsif( $line =~ m/^(\s+)(.*)/xo ) {
17523 $val = $2 ;
17524 $debugdev and myprint( "DDD V [$val]\n" ) ;
17525 @{ $header->{ $key } }[ $LAST ] .= " $val" if $key ;
17526 # dirty line?
17527 }else{
17528 next ;
17529 }
17530 }
17531
17532 #myprint( Data::Dumper->Dump( [ $header ] ) ) ;
17533
17534 return( $header ) ;
17535}
17536
17537
17538sub tests_decompose_header{
17539 note( 'Entering tests_decompose_header()' ) ;
17540
17541
17542 my $header_dec ;
17543
17544 $header_dec = decompose_header(
17545<<'EOH'
17546KEY_1: VAL_1
17547KEY_2: VAL_2
17548 VAL_2_+
17549 VAL_2_++
17550KEY_3: VAL_3
17551KEY_1: VAL_1_other
17552KEY_4: VAL_4
17553 VAL_4_+
17554KEY_5 BLANC: VAL_5
17555
17556KEY_6_BAD_BODY: VAL_6
17557EOH
17558 ) ;
17559
17560 ok( 'VAL_3'
17561 eq $header_dec->{ 'KEY_3' }[0], 'decompose_header: VAL_3' ) ;
17562
17563 ok( 'VAL_1'
17564 eq $header_dec->{ 'KEY_1' }[0], 'decompose_header: VAL_1' ) ;
17565
17566 ok( 'VAL_1_other'
17567 eq $header_dec->{ 'KEY_1' }[1], 'decompose_header: VAL_1_other' ) ;
17568
17569 ok( 'VAL_2 VAL_2_+ VAL_2_++'
17570 eq $header_dec->{ 'KEY_2' }[0], 'decompose_header: VAL_2 VAL_2_+ VAL_2_++' ) ;
17571
17572 ok( 'VAL_4 VAL_4_+'
17573 eq $header_dec->{ 'KEY_4' }[0], 'decompose_header: VAL_4 VAL_4_+' ) ;
17574
17575 ok( ' VAL_5'
17576 eq $header_dec->{ 'KEY_5 BLANC' }[0], 'decompose_header: KEY_5 BLANC' ) ;
17577
17578 ok( not( defined $header_dec->{ 'KEY_6_BAD_BODY' }[0] ), 'decompose_header: KEY_6_BAD_BODY' ) ;
17579
17580
17581 $header_dec = decompose_header(
17582<<'EOH'
17583Message-Id: <20100428101817.A66CB162474E@plume.est.belle>
17584Date: Wed, 28 Apr 2010 12:18:17 +0200 (CEST)
17585From: gilles@louloutte.dyndns.org (Gilles LAMIRAL)
17586EOH
17587 ) ;
17588
17589 ok( '<20100428101817.A66CB162474E@plume.est.belle>'
17590 eq $header_dec->{ 'Message-Id' }[0], 'decompose_header: 1' ) ;
17591
17592 $header_dec = decompose_header(
17593<<'EOH'
17594Return-Path: <gilles@louloutte.dyndns.org>
17595Received: by plume.est.belle (Postfix, from userid 1000)
17596 id 120A71624742; Wed, 28 Apr 2010 01:46:40 +0200 (CEST)
17597Subject: test:eekahceishukohpe
17598EOH
17599) ;
17600 ok(
17601'by plume.est.belle (Postfix, from userid 1000) id 120A71624742; Wed, 28 Apr 2010 01:46:40 +0200 (CEST)'
17602 eq $header_dec->{ 'Received' }[0], 'decompose_header: 2' ) ;
17603
17604 $header_dec = decompose_header(
17605<<'EOH'
17606Received: from plume (localhost [127.0.0.1])
17607 by plume.est.belle (Postfix) with ESMTP id C6EB73F6C9
17608 for <gilles@localhost>; Mon, 26 Nov 2007 10:39:06 +0100 (CET)
17609Received: from plume [192.168.68.7]
17610 by plume with POP3 (fetchmail-6.3.6)
17611 for <gilles@localhost> (single-drop); Mon, 26 Nov 2007 10:39:06 +0100 (CET)
17612EOH
17613 ) ;
17614 ok(
17615 'from plume (localhost [127.0.0.1]) by plume.est.belle (Postfix) with ESMTP id C6EB73F6C9 for <gilles@localhost>; Mon, 26 Nov 2007 10:39:06 +0100 (CET)'
17616 eq $header_dec->{ 'Received' }[0], 'decompose_header: 3' ) ;
17617 ok(
17618 'from plume [192.168.68.7] by plume with POP3 (fetchmail-6.3.6) for <gilles@localhost> (single-drop); Mon, 26 Nov 2007 10:39:06 +0100 (CET)'
17619 eq $header_dec->{ 'Received' }[1], 'decompose_header: 3' ) ;
17620
17621# Bad header beginning with a blank character
17622 $header_dec = decompose_header(
17623<<'EOH'
17624 KEY_1: VAL_1
17625KEY_2: VAL_2
17626 VAL_2_+
17627 VAL_2_++
17628KEY_3: VAL_3
17629KEY_1: VAL_1_other
17630EOH
17631 ) ;
17632
17633 ok( 'VAL_3'
17634 eq $header_dec->{ 'KEY_3' }[0], 'decompose_header: Bad header VAL_3' ) ;
17635
17636 ok( 'VAL_1_other'
17637 eq $header_dec->{ 'KEY_1' }[0], 'decompose_header: Bad header VAL_1_other' ) ;
17638
17639 ok( 'VAL_2 VAL_2_+ VAL_2_++'
17640 eq $header_dec->{ 'KEY_2' }[0], 'decompose_header: Bad header VAL_2 VAL_2_+ VAL_2_++' ) ;
17641
17642 note( 'Leaving tests_decompose_header()' ) ;
17643 return ;
17644}
17645
17646sub tests_epoch
17647{
17648 note( 'Entering tests_epoch()' ) ;
17649
17650 ok( '1282658400' eq epoch( '24-Aug-2010 16:00:00 +0200' ), 'epoch 24-Aug-2010 16:00:00 +0200 -> 1282658400' ) ;
17651 ok( '1282658400' eq epoch( '24-Aug-2010 14:00:00 +0000' ), 'epoch 24-Aug-2010 14:00:00 +0000 -> 1282658400' ) ;
17652 ok( '1282658400' eq epoch( '24-Aug-2010 12:00:00 -0200' ), 'epoch 24-Aug-2010 12:00:00 -0200 -> 1282658400' ) ;
17653 ok( '1282658400' eq epoch( '24-Aug-2010 16:01:00 +0201' ), 'epoch 24-Aug-2010 16:01:00 +0201 -> 1282658400' ) ;
17654 ok( '1282658400' eq epoch( '24-Aug-2010 14:01:00 +0001' ), 'epoch 24-Aug-2010 14:01:00 +0001 -> 1282658400' ) ;
17655
17656 ok( '1280671200' eq epoch( '1-Aug-2010 16:00:00 +0200' ), 'epoch 1-Aug-2010 16:00:00 +0200 -> 1280671200' ) ;
17657 ok( '1280671200' eq epoch( '1-Aug-2010 14:00:00 +0000' ), 'epoch 1-Aug-2010 14:00:00 +0000 -> 1280671200' ) ;
17658 ok( '1280671200' eq epoch( '1-Aug-2010 12:00:00 -0200' ), 'epoch 1-Aug-2010 12:00:00 -0200 -> 1280671200' ) ;
17659 ok( '1280671200' eq epoch( '1-Aug-2010 16:01:00 +0201' ), 'epoch 1-Aug-2010 16:01:00 +0201 -> 1280671200' ) ;
17660 ok( '1280671200' eq epoch( '1-Aug-2010 14:01:00 +0001' ), 'epoch 1-Aug-2010 14:01:00 +0001 -> 1280671200' ) ;
17661
17662 is( '1280671200', epoch( '1-Aug-2010 14:01:00 +0001' ), 'epoch 1-Aug-2010 14:01:00 +0001 -> 1280671200' ) ;
17663 is( '946684800', epoch( '00-Jan-0000 00:00:00 +0000' ), 'epoch 1-Aug-2010 14:01:00 +0001 -> 1280671200' ) ;
17664
17665 note( 'Leaving tests_epoch()' ) ;
17666 return ;
17667}
17668
17669sub epoch
17670{
17671 # incoming format:
17672 # internal date 24-Aug-2010 16:00:00 +0200
17673
17674 # outgoing format: epoch
17675
17676
17677 my $d = shift ;
17678 return(q{}) if not defined $d;
17679
17680 my ( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m ) ;
17681 my $time ;
17682
17683 if ( $d =~ m{(\d{1,2})-([A-Z][a-z]{2})-(\d{4})\s(\d{2}):(\d{2}):(\d{2})\s((?:\+|-))(\d{2})(\d{2})}xo ) {
17684 #myprint( "internal: [$1][$2][$3][$4][$5][$6][$7][$8][$9]\n" ) ;
17685 ( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m )
17686 = ( $1, $2, $3, $4, $5, $6, $7, $8, $9 ) ;
17687 #myprint( "( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m )\n" ) ;
17688
17689 $sign = +1 if ( '+' eq $sign ) ;
17690 $sign = $MINUS_ONE if ( '-' eq $sign ) ;
17691
17692 if ( 0 == $mday ) {
17693 myprint( "buggy day in $d. Fixed to 01\n" ) ;
17694 $mday = '01' ;
17695 }
17696 $time = timegm( $sec, $min, $hour, $mday, $month_abrev{$month}, $year )
17697 - $sign * ( 3600 * $zone_h + 60 * $zone_m ) ;
17698
17699 #myprint( "$time ", scalar localtime($time), "\n");
17700 }
17701 return( $time ) ;
17702}
17703
17704sub tests_add_header
17705{
17706 note( 'Entering tests_add_header()' ) ;
17707
17708 ok( 'Message-Id: <mistake@imapsync>' eq add_header(), 'add_header no arg' ) ;
17709 ok( 'Message-Id: <123456789@imapsync>' eq add_header( '123456789' ), 'add_header 123456789' ) ;
17710
17711 note( 'Leaving tests_add_header()' ) ;
17712 return ;
17713}
17714
17715sub add_header
17716{
17717 my $header_uid = shift || 'mistake' ;
17718 my $header_Message_Id = 'Message-Id: <' . $header_uid . '@imapsync>' ;
17719 return( $header_Message_Id ) ;
17720}
17721
17722
17723
17724
17725sub tests_max_line_length
17726{
17727 note( 'Entering tests_max_line_length()' ) ;
17728
17729 ok( 0 == max_line_length( q{} ), 'max_line_length: 0 == null string' ) ;
17730 ok( 1 == max_line_length( "\n" ), 'max_line_length: 1 == \n' ) ;
17731 ok( 1 == max_line_length( "\n\n" ), 'max_line_length: 1 == \n\n' ) ;
17732 ok( 1 == max_line_length( "\n" x 500 ), 'max_line_length: 1 == 500 \n' ) ;
17733 ok( 1 == max_line_length( 'a' ), 'max_line_length: 1 == a' ) ;
17734 ok( 2 == max_line_length( "a\na" ), 'max_line_length: 2 == a\na' ) ;
17735 ok( 2 == max_line_length( "a\na\n" ), 'max_line_length: 2 == a\na\n' ) ;
17736 ok( 3 == max_line_length( "a\nab\n" ), 'max_line_length: 3 == a\nab\n' ) ;
17737 ok( 3 == max_line_length( "a\nab\n" x 1_000 ), 'max_line_length: 3 == 1_000 a\nab\n' ) ;
17738 ok( 3 == max_line_length( "a\nab\nabc" ), 'max_line_length: 3 == a\nab\nabc' ) ;
17739
17740 ok( 4 == max_line_length( "a\nab\nabc\n" ), 'max_line_length: 4 == a\nab\nabc\n' ) ;
17741 ok( 5 == max_line_length( "a\nabcd\nabc\n" ), 'max_line_length: 5 == a\nabcd\nabc\n' ) ;
17742 ok( 5 == max_line_length( "a\nabcd\nabc\n\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd" ), 'max_line_length: 5 == a\nabcd\nabc\n\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd' ) ;
17743
17744 note( 'Leaving tests_max_line_length()' ) ;
17745 return ;
17746}
17747
17748sub max_line_length
17749{
17750 my $string = shift ;
17751 my $max = 0 ;
17752
17753 while ( $string =~ m/([^\n]*\n?)/msxg ) {
17754 $max = max( $max, length $1 ) ;
17755 }
17756 return( $max ) ;
17757}
17758
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010017759sub set_checknoabletosearch
17760{
17761 my $mysync = shift @ARG ;
17762 if ( defined $mysync->{ checknoabletosearch } )
17763 {
17764 return ;
17765 }
17766 elsif ( $mysync->{ justfolders } )
17767 {
17768 $mysync->{ checknoabletosearch } = 0 ;
17769 }
17770 else
17771 {
17772 $mysync->{ checknoabletosearch } = 1 ;
17773 }
17774 return ;
17775}
17776
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017777
17778sub tests_setlogfile
17779{
17780 note( 'Entering tests_setlogfile()' ) ;
17781
17782 my $mysync = {} ;
17783 $mysync->{logdir} = 'vallogdir' ;
17784 $mysync->{logfile} = 'vallogfile.txt' ;
17785 is( 'vallogdir/vallogfile.txt', setlogfile( $mysync ),
17786 'setlogfile: logdir vallogdir, logfile vallogfile.txt, vallogdir/vallogfile.txt' ) ;
17787
17788 SKIP: {
17789 skip( 'Too hard to have a well known timezone on Windows', 9 ) if ( 'MSWin32' eq $OSNAME ) ;
17790
17791 local $ENV{TZ} = 'GMT' ;
17792
17793 $mysync = {
17794 timestart => 2,
17795 } ;
17796
17797 is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000__.txt", setlogfile( $mysync ),
17798 "setlogfile: default is like $DEFAULT_LOGDIR/1970_01_01_00_00_02_000__.txt" ) ;
17799
17800 $mysync = {
17801 timestart => 2,
17802 user1 => 'user1',
17803 user2 => 'user2',
17804 abort => 1,
17805 } ;
17806
17807 is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_abort.txt", setlogfile( $mysync ),
17808 "setlogfile: default is like $DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_abort.txt" ) ;
17809
17810 $mysync = {
17811 timestart => 2,
17812 user1 => 'user1',
17813 user2 => 'user2',
17814 remote => 'zzz',
17815 } ;
17816
17817 is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_remote.txt", setlogfile( $mysync ),
17818 "setlogfile: default is like $DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_remote.txt" ) ;
17819
17820 $mysync = {
17821 timestart => 2,
17822 user1 => 'user1',
17823 user2 => 'user2',
17824 remote => 'zzz',
17825 abort => 1,
17826 } ;
17827
17828 is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_remote_abort.txt", setlogfile( $mysync ),
17829 "setlogfile: default is like $DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_remote_abort.txt" ) ;
17830
17831
17832 $mysync = {
17833 timestart => 2,
17834 user1 => 'user1',
17835 user2 => 'user2',
17836 } ;
17837
17838 is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2.txt", setlogfile( $mysync ),
17839 "setlogfile: default is like $DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2.txt" ) ;
17840
17841 $mysync->{logdir} = undef ;
17842 $mysync->{logfile} = undef ;
17843 is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2.txt", setlogfile( $mysync ),
17844 "setlogfile: logdir undef, $DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2.txt" ) ;
17845
17846 $mysync->{logdir} = q{} ;
17847 $mysync->{logfile} = undef ;
17848 is( '1970_01_01_00_00_02_000_user1_user2.txt', setlogfile( $mysync ),
17849 'setlogfile: logdir empty, 1970_01_01_00_00_02_000_user1_user2.txt' ) ;
17850
17851 $mysync->{logdir} = 'vallogdir' ;
17852 $mysync->{logfile} = undef ;
17853 is( 'vallogdir/1970_01_01_00_00_02_000_user1_user2.txt', setlogfile( $mysync ),
17854 'setlogfile: logdir vallogdir, vallogdir/1970_01_01_00_00_02_000_user1_user2.txt' ) ;
17855
17856 $mysync = {
17857 user1 => 'us/er1a*|?:"<>b',
17858 user2 => 'u/ser2a*|?:"<>b',
17859 } ;
17860
17861 is( "$DEFAULT_LOGDIR/1970_01_01_00_00_00_000_us_er1a_______b_u_ser2a_______b.txt", setlogfile( $mysync ),
17862 "setlogfile: logdir undef, $DEFAULT_LOGDIR/1970_01_01_00_00_00_000_us_er1a_______b_u_ser2a_______b.txt" ) ;
17863
17864
17865
17866 } ;
17867
17868 note( 'Leaving tests_setlogfile()' ) ;
17869 return ;
17870}
17871
17872sub setlogfile
17873{
17874 my( $mysync ) = shift ;
17875
17876 # When aborting another process the log file name finishes with "_abort.txt"
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017877 my $abort_suffix = ( $mysync->{ abort } ) ? '_abort' : q{} ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010017878
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017879 # When acting as a proxy the log file name finishes with "_remote.txt"
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017880 # proxy mode is not done in imapsync, it is done by proximapsync
17881 my $remote_suffix = ( $mysync->{ remote } ) ? '_remote' : q{} ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017882
17883 my $suffix = (
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017884 filter_forbidden_characters( slash_to_underscore( $mysync->{ user1 } ) ) || q{} )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017885 . '_'
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017886 . ( filter_forbidden_characters( slash_to_underscore( $mysync->{ user2 } ) ) || q{} )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017887 . $remote_suffix . $abort_suffix ;
17888
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017889 $mysync->{ logdir } = defined $mysync->{ logdir } ? $mysync->{ logdir } : $DEFAULT_LOGDIR ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017890
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017891 $mysync->{ logfile } = defined $mysync->{ logfile }
17892 ? "$mysync->{ logdir }/$mysync->{ logfile }"
17893 : logfile( $mysync->{ timestart }, $suffix, $mysync->{ logdir } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017894
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017895 return( $mysync->{ logfile } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017896}
17897
17898sub tests_logfile
17899{
17900 note( 'Entering tests_logfile()' ) ;
17901
17902 SKIP: {
17903 # Too hard to have a well known timezone on Windows
17904 skip( 'Too hard to have a well known timezone on Windows', 10 ) if ( 'MSWin32' eq $OSNAME ) ;
17905
17906 local $ENV{TZ} = 'GMT' ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017907 {
17908 POSIX::tzset unless ('MSWin32' eq $OSNAME) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017909 is( '1970_01_01_00_00_00_000.txt', logfile( ), 'logfile: no args => 1970_01_01_00_00_00.txt' ) ;
17910 is( '1970_01_01_00_00_00_000.txt', logfile( 0 ), 'logfile: 0 => 1970_01_01_00_00_00.txt' ) ;
17911 is( '1970_01_01_00_01_01_000.txt', logfile( 61 ), 'logfile: 0 => 1970_01_01_00_01_01.txt' ) ;
17912 is( '1970_01_01_00_01_01_234.txt', logfile( 61.234 ), 'logfile: 0 => 1970_01_01_00_01_01.txt' ) ;
17913 is( '2010_08_24_14_00_00_000.txt', logfile( 1_282_658_400 ), 'logfile: 1_282_658_400 => 2010_08_24_14_00_00.txt' ) ;
17914 is( '2010_08_24_14_01_01_000.txt', logfile( 1_282_658_461 ), 'logfile: 1_282_658_461 => 2010_08_24_14_01_01.txt' ) ;
17915 is( '2010_08_24_14_01_01_000_poupinette.txt', logfile( 1_282_658_461, 'poupinette' ), 'logfile: 1_282_658_461 poupinette => 2010_08_24_14_01_01_poupinette.txt' ) ;
17916 is( '2010_08_24_14_01_01_000_removeblanks.txt', logfile( 1_282_658_461, ' remove blanks ' ), 'logfile: 1_282_658_461 remove blanks => 2010_08_24_14_01_01_000_removeblanks' ) ;
17917
17918 is( '2010_08_24_14_01_01_234_poup.txt', logfile( 1_282_658_461.2347, 'poup' ),
17919 'logfile: 1_282_658_461.2347 poup => 2010_08_24_14_01_01_234_poup.txt' ) ;
17920
17921 is( 'dirdir/2010_08_24_14_01_01_234_poup.txt', logfile( 1_282_658_461.2347, 'poup', 'dirdir' ),
17922 'logfile: 1_282_658_461.2347 poup dirdir => dirdir/2010_08_24_14_01_01_234_poup.txt' ) ;
17923
17924
17925
17926 }
17927 POSIX::tzset unless ('MSWin32' eq $OSNAME) ;
17928 } ;
17929
17930 note( 'Leaving tests_logfile()' ) ;
17931 return ;
17932}
17933
17934
17935sub logfile
17936{
17937 my ( $time, $suffix, $dir ) = @_ ;
17938
17939 $time ||= 0 ;
17940 $suffix ||= q{} ;
17941 $suffix =~ tr/ //ds ;
17942 my $sep_suffix = ( $suffix ) ? '_' : q{} ;
17943 $dir ||= q{} ;
17944 my $sep_dir = ( $dir ) ? '/' : q{} ;
17945
17946 my $date_str = POSIX::strftime( '%Y_%m_%d_%H_%M_%S', localtime $time ) ;
17947 # Because of ab tests or web accesses, more than one sync withing one second is possible
17948 # so we add also milliseconds
17949 $date_str .= sprintf "_%03d", ($time - int( $time ) ) * 1000 ; # without rounding
17950 my $logfile = "${dir}${sep_dir}${date_str}${sep_suffix}${suffix}.txt" ;
17951 return( $logfile ) ;
17952}
17953
17954
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017955sub tests_localtimez
17956{
17957 note( 'Entering tests_localtimez()' ) ;
17958
17959 SKIP: {
17960 # Too hard to have a well known timezone on Windows
17961 skip( 'Too hard to have a well known timezone on Windows', 1 ) if ( 'MSWin32' eq $OSNAME ) ;
17962 local $ENV{TZ} = 'GMT' ;
17963 like( localtimez( 0 ), qr'1970-01-01 00:00:00 \+0000 (GMT|UTC)', 'localtimez: 0 => match 1970-01-01 00:00:00 +0000 GMT' ) ;
17964 }
17965
17966 is( localtimez( ), localtimez( time ), 'localtimez: undef => equals currrent' ) ;
17967 note( 'Leaving tests_localtimez()' ) ;
17968 return ;
17969}
17970
17971
17972
17973sub localtimez
17974{
17975 my $time = shift ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010017976
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017977 $time = defined( $time ) ? $time : time ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010017978
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017979 my $datetimestr = POSIX::strftime( '%A %e %B %Y-%m-%d %H:%M:%S %z %Z', localtime( $time ) ) ;
17980
17981 #myprint( "$datetimestr\n" ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010017982 return $datetimestr ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017983}
17984
17985
17986
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017987
17988sub tests_slash_to_underscore
17989{
17990 note( 'Entering tests_slash_to_underscore()' ) ;
17991
17992 is( undef, slash_to_underscore( ), 'slash_to_underscore: no parameters => undef' ) ;
17993 is( '_', slash_to_underscore( '/' ), 'slash_to_underscore: / => _' ) ;
17994 is( '_abc_def_', slash_to_underscore( '/abc/def/' ), 'slash_to_underscore: /abc/def/ => _abc_def_' ) ;
17995 note( 'Leaving tests_slash_to_underscore()' ) ;
17996 return ;
17997}
17998
17999sub slash_to_underscore
18000{
18001 my $string = shift ;
18002
18003 if ( ! defined $string ) { return ; }
18004
18005 $string =~ tr{/}{_} ;
18006
18007 return( $string ) ;
18008}
18009
18010
18011
18012
18013sub tests_million_folders_baby_2
18014{
18015 note( 'Entering tests_million_folders_baby_2()' ) ;
18016
18017 my %long ;
18018 @long{ 1 .. 900_000 } = (1) x 900_000 ;
18019 #myprint( %long, "\n" ) ;
18020 my $pasglop = 0 ;
18021 foreach my $elem ( 1 .. 900_000 ) {
18022 #$debug and myprint( "$elem " ) ;
18023 if ( not exists $long{ $elem } ) {
18024 $pasglop++ ;
18025 }
18026 }
18027 ok( 0 == $pasglop, 'tests_million_folders_baby_2: search among 900_000' ) ;
18028 # myprint( "$pasglop\n" ) ;
18029
18030 note( 'Leaving tests_million_folders_baby_2()' ) ;
18031 return ;
18032}
18033
18034
18035
18036sub tests_always_fail
18037{
18038 note( 'Entering tests_always_fail()' ) ;
18039
18040 is( 0, 1, 'always_fail: 0 is 1' ) ;
18041
18042 note( 'Leaving tests_always_fail()' ) ;
18043 return ;
18044}
18045
18046
18047sub tests_logfileprepa
18048{
18049 note( 'Entering tests_logfileprepa()' ) ;
18050
18051 is( undef, logfileprepa( ), 'logfileprepa: no args => undef' ) ;
18052 my $logfile = 'W/tmp/tests/tests_logfileprepa.txt' ;
18053 is( 1, logfileprepa( $logfile ), 'logfileprepa: W/tmp/tests/tests_logfileprepa.txt => 1' ) ;
18054
18055 note( 'Leaving tests_logfileprepa()' ) ;
18056 return ;
18057}
18058
18059sub logfileprepa
18060{
18061 my $logfile = shift ;
18062
18063 if ( ! defined( $logfile ) )
18064 {
18065 return ;
18066 }else
18067 {
18068 #myprint( "[$logfile]\n" ) ;
18069 my $dirname = dirname( $logfile ) ;
18070 do_valid_directory( $dirname ) || return( 0 ) ;
18071 return( 1 ) ;
18072 }
18073}
18074
18075
18076sub tests_teelaunch
18077{
18078 note( 'Entering tests_teelaunch()' ) ;
18079
18080 is( undef, teelaunch( ), 'teelaunch: no args => undef' ) ;
18081 my $mysync = {} ;
18082 is( undef, teelaunch( $mysync ), 'teelaunch: arg empty {} => undef' ) ;
18083 $mysync->{logfile} = q{} ;
18084 is( undef, teelaunch( $mysync ), 'teelaunch: logfile empty string => undef' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018085
18086 # First time, learning IO::Tee intrasics
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018087 $mysync->{logfile} = 'W/tmp/tests/tests_teelaunch.txt' ;
18088 isa_ok( my $tee = teelaunch( $mysync ), 'IO::Tee' , 'teelaunch: logfile W/tmp/tests/tests_teelaunch.txt' ) ;
18089 is( 1, print( $tee "Hi!\n" ), 'teelaunch: write Hi!') ;
18090 is( "Hi!\n", file_to_string( 'W/tmp/tests/tests_teelaunch.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch.txt is Hi!\n' ) ;
18091 is( 1, print( $tee "Hoo\n" ), 'teelaunch: write Hoo') ;
18092 is( "Hi!\nHoo\n", file_to_string( 'W/tmp/tests/tests_teelaunch.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch.txt is Hi!\nHoo\n' ) ;
18093
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018094 # closing so tee won't be happy
18095 close $mysync->{logfile_handle} ;
18096 is( undef, print( $tee "Argh1\n" ), 'teelaunch: write Argh1') ;
18097 is( undef, print( $tee "Argh2\n" ), 'teelaunch: write Argh2') ;
18098 # write not done
18099 is( "Hi!\nHoo\n", file_to_string( 'W/tmp/tests/tests_teelaunch.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch.txt is still Hi!\nHoo\n' ) ;
18100 print join( ' ', $tee->handles ), "\n";
18101 is( 2, scalar $tee->handles, 'teelaunch: 2 handles') ;
18102 shift @{*{$tee}};
18103 print join(' ', $tee->handles), "\n" ;
18104 is( 1, scalar $tee->handles, 'teelaunch: 1 handle') ;
18105 is( 1, print( $tee "Argh3\n" ), 'teelaunch: write Argh3 yeah') ;
18106
18107 shift @{*{$tee}};
18108 # will not print anything now
18109 is( 0, scalar $tee->handles, 'teelaunch: 0 handle') ;
18110 is( 1, print( $tee "Argh 4\n" ), 'teelaunch: write Argh4 no') ;
18111
18112 # Second time, lesson learnt IO::Tee
18113 $mysync->{logfile} = 'W/tmp/tests/tests_teelaunch2.txt' ;
18114 isa_ok( $tee = teelaunch( $mysync ), 'IO::Tee' , 'teelaunch: logfile W/tmp/tests/tests_teelaunch2.txt' ) ;
18115 is( 1, print( $tee "Hi!\n" ), 'teelaunch: write Hi!') ;
18116 is( "Hi!\n", file_to_string( 'W/tmp/tests/tests_teelaunch2.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch2.txt is Hi!\n' ) ;
18117 is( 1, print( $tee "Hoo\n" ), 'teelaunch: write Hoo') ;
18118 is( "Hi!\nHoo\n", file_to_string( 'W/tmp/tests/tests_teelaunch2.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch2.txt is Hi!\nHoo\n' ) ;
18119
18120 is( 1, teefinish( $mysync ), 'teefinish: return 1') ;
18121 is( 1, print( $tee "Argh1\n" ), 'teelaunch: write Argh1') ;
18122 is( 1, print( $tee "Argh2\n" ), 'teelaunch: write Argh2') ;
18123 is( "Hi!\nHoo\n", file_to_string( 'W/tmp/tests/tests_teelaunch2.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch2.txt is still Hi!\nHoo\n' ) ;
18124 is( 1, teefinish( $mysync ), 'teefinish: still return 1') ;
18125
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018126 note( 'Leaving tests_teelaunch()' ) ;
18127 return ;
18128}
18129
18130sub teelaunch
18131{
18132 my $mysync = shift ;
18133
18134 if ( ! defined( $mysync ) )
18135 {
18136 return ;
18137 }
18138
18139 my $logfile = $mysync->{logfile} ;
18140
18141 if ( ! $logfile )
18142 {
18143 return ;
18144 }
18145
18146 logfileprepa( $logfile ) || croak "Error no valid directory to write log file $logfile : $OS_ERROR" ;
18147
18148 # This is a log file opened during the whole sync
18149 ## no critic (InputOutput::RequireBriefOpen)
18150 open my $logfile_handle, '>', $logfile
18151 or croak( "Can not open $logfile for write: $OS_ERROR" ) ;
18152 binmode $logfile_handle, ":encoding(UTF-8)" ;
18153 my $tee = IO::Tee->new( $logfile_handle, \*STDOUT ) ;
18154 $tee->autoflush( 1 ) ;
18155 $mysync->{logfile_handle} = $logfile_handle ;
18156 $mysync->{tee} = $tee ;
18157 return $tee ;
18158}
18159
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018160sub teefinish
18161{
18162 my $mysync = shift ;
18163
18164 if ( ! defined( $mysync ) ) { return ; }
18165
18166 my $tee = $mysync->{tee} ;
18167
18168 if ( ! defined( $tee ) ) { return ; }
18169
18170 if ( 2 == scalar $tee->handles )
18171 {
18172 shift @{*{$tee}};
18173 }
18174 else
18175 {
18176 # nothing
18177 }
18178 return scalar $tee->handles ;
18179}
18180
18181
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018182sub getpwuid_any_os
18183{
18184 my $uid = shift ;
18185
18186 return( scalar getlogin ) if ( 'MSWin32' eq $OSNAME ) ; # Windows system
18187 return( scalar getpwuid $uid ) ; # Unix system
18188
18189
18190}
18191
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018192
18193
18194sub abortifneeded
18195{
18196 my $mysync = shift ;
18197 if ( -e $mysync->{ abortfile } )
18198 {
18199 myprint( "Asked to terminate by file $mysync->{ abortfile }\n" ) ;
18200 do_and_print_stats( $mysync ) ;
18201 myprint( "You should resynchronize those accounts by running a sync again,\n",
18202 "since some messages and entire folders might still be missing on host2.\n"
18203 ) ;
18204 exit_clean( $mysync, $EXIT_BY_FILE ) ;
18205 return ;
18206 }
18207 else
18208 {
18209 return ;
18210 }
18211}
18212
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018213sub simulong
18214{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018215 my $mysync = shift ;
18216
18217 my $max_seconds = $mysync->{ simulong } ;
18218
18219 if ( ! $max_seconds ) { return ; }
18220
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018221 my $division = 5 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018222 my $last_count = int( $division * $max_seconds ) ;
18223 $mysync->{ debug } and myprint "last_count $last_count = int( division $division * max_seconds $max_seconds)\n" ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018224 foreach my $i ( 1 .. ( $last_count ) ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018225 myprint( "Are you still here ETA: " . ( $last_count - $i ) . "/$last_count msgs left\n" ) ;
18226 #this one is for testing huge page behavior
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018227 #myprint( "Are you still here ETA: " . ($last_count - $i) . "/$last_count msgs left\n" . ( "Ah" x 40 . "\n") x 4000 ) ;
18228 sleep( 1 / $division ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018229 abortifneeded( $mysync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018230 }
18231
18232 return ;
18233}
18234
18235
18236
18237sub printenv
18238{
18239 myprint( "Environment variables listing:\n",
18240 ( map { "$_ => $ENV{$_}\n" } sort keys %ENV),
18241 "Environment variables listing end\n" ) ;
18242 return ;
18243}
18244
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018245
18246sub unittestssuite
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018247{
18248 my $mysync = shift ;
18249 if ( ! ( $mysync->{ tests } or $mysync->{ testsdebug } or $mysync->{ testsunit } ) ) {
18250 return ;
18251 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018252
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018253 my $test_builder = Test::More->builder ;
18254 tests( $mysync ) ;
18255 testsdebug( $mysync ) ;
18256 testunitsession( $mysync ) ;
18257
18258 my @summary = $test_builder->summary() ;
18259 my @details = $test_builder->details() ;
18260 my $nb_tests_run = scalar( @summary ) ;
18261 my $nb_tests_expected = $test_builder->expected_tests() ;
18262 my $nb_tests_failed = count_0s( @summary ) ;
18263 my $tests_failed = report_failures( @details ) ;
18264 if ( $nb_tests_failed or ( $nb_tests_run != $nb_tests_expected ) ) {
18265 #$test_builder->reset( ) ;
18266 myprint( "Summary of tests: failed $nb_tests_failed tests, run $nb_tests_run tests, expected to run $nb_tests_expected tests.\n",
18267 "List of failed tests:\n", $tests_failed ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018268 return $EXIT_TESTS_FAILED ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018269 }
18270
18271 cleanup_mess_from_tests( ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018272
18273 return 0 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018274}
18275
18276sub cleanup_mess_from_tests
18277{
18278 undef @pipemess ;
18279 return ;
18280}
18281
18282sub after_get_options
18283{
18284 my $mysync = shift ;
18285 my $numopt = shift ;
18286
18287
18288 # exit with --help option or no option at all
18289 $mysync->{ debug } and myprint( "numopt:$numopt\n" ) ;
18290
18291 if ( $help or not $numopt ) {
18292 myprint( usage( $mysync ) ) ;
18293 exit ;
18294 }
18295
18296 return ;
18297}
18298
18299sub tests_remove_edging_blanks
18300{
18301 note( 'Entering tests_remove_edging_blanks()' ) ;
18302
18303 is( undef, remove_edging_blanks( ), 'remove_edging_blanks: no args => undef' ) ;
18304 is( 'abcd', remove_edging_blanks( 'abcd' ), 'remove_edging_blanks: abcd => abcd' ) ;
18305 is( 'ab cd', remove_edging_blanks( ' ab cd ' ), 'remove_edging_blanks: " ab cd " => "ab cd"' ) ;
18306
18307 note( 'Leaving tests_remove_edging_blanks()' ) ;
18308 return ;
18309}
18310
18311
18312
18313sub remove_edging_blanks
18314{
18315 my $string = shift ;
18316 if ( ! defined $string )
18317 {
18318 return ;
18319 }
18320 $string =~ s,^ +| +$,,g ;
18321 return $string ;
18322}
18323
18324
18325sub tests_sanitize
18326{
18327 note( 'Entering tests_remove_edging_blanks()' ) ;
18328
18329 is( undef, sanitize( ), 'sanitize: no args => undef' ) ;
18330 my $mysync = {} ;
18331
18332 $mysync->{ host1 } = ' example.com ' ;
18333 $mysync->{ user1 } = ' to to ' ;
18334 $mysync->{ password1 } = ' sex is good! ' ;
18335 is( undef, sanitize( $mysync ), 'sanitize: => undef' ) ;
18336 is( 'example.com', $mysync->{ host1 }, 'sanitize: host1 " example.com " => "example.com"' ) ;
18337 is( 'to to', $mysync->{ user1 }, 'sanitize: user1 " to to " => "to to"' ) ;
18338 is( 'sex is good!', $mysync->{ password1 }, 'sanitize: password1 " sex is good! " => "sex is good!"' ) ;
18339 note( 'Leaving tests_remove_edging_blanks()' ) ;
18340 return ;
18341}
18342
18343
18344sub sanitize
18345{
18346 my $mysync = shift ;
18347 if ( ! defined $mysync )
18348 {
18349 return ;
18350 }
18351
18352 foreach my $parameter ( qw( host1 host2 user1 user2 password1 password2 ) )
18353 {
18354 $mysync->{ $parameter } = remove_edging_blanks( $mysync->{ $parameter } ) ;
18355 }
18356 return ;
18357}
18358
18359sub easyany
18360{
18361 my $mysync = shift ;
18362
18363 # Gmail
18364 if ( $mysync->{gmail1} and $mysync->{gmail2} ) {
18365 $mysync->{ debug } and myprint( "gmail1 gmail2\n") ;
18366 gmail12( $mysync ) ;
18367 return ;
18368 }
18369 if ( $mysync->{gmail1} ) {
18370 $mysync->{ debug } and myprint( "gmail1\n" ) ;
18371 gmail1( $mysync ) ;
18372 }
18373 if ( $mysync->{gmail2} ) {
18374 $mysync->{ debug } and myprint( "gmail2\n" ) ;
18375 gmail2( $mysync ) ;
18376 }
18377 # Office 365
18378 if ( $mysync->{office1} ) {
18379 office1( $mysync ) ;
18380 }
18381
18382 if ( $mysync->{office2} ) {
18383 office2( $mysync ) ;
18384 }
18385
18386 # Exchange
18387 if ( $mysync->{exchange1} ) {
18388 exchange1( $mysync ) ;
18389 }
18390
18391 if ( $mysync->{exchange2} ) {
18392 exchange2( $mysync ) ;
18393 }
18394
18395
18396 # Domino
18397 if ( $mysync->{domino1} ) {
18398 domino1( $mysync ) ;
18399 }
18400
18401 if ( $mysync->{domino2} ) {
18402 domino2( $mysync ) ;
18403 }
18404
18405 return ;
18406}
18407
18408# From and for https://imapsync.lamiral.info/FAQ.d/FAQ.Gmail.txt
18409sub gmail12
18410{
18411 my $mysync = shift ;
18412 # Gmail at host1 and host2
18413 $mysync->{host1} ||= 'imap.gmail.com' ;
18414 $mysync->{ssl1} = ( defined $mysync->{ssl1} ) ? $mysync->{ssl1} : 1 ;
18415 $mysync->{host2} ||= 'imap.gmail.com' ;
18416 $mysync->{ssl2} = ( defined $mysync->{ssl2} ) ? $mysync->{ssl2} : 1 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018417 $mysync->{maxbytespersecond} ||= 20_000 ; # should be less than 10_000 when computed from Gmail documentation
18418 $mysync->{maxbytesafter} ||= 1_000_000_000 ; # In fact it is documented as half: 500_000_000
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018419 $mysync->{automap} = ( defined $mysync->{automap} ) ? $mysync->{automap} : 1 ;
18420 $mysync->{maxsleep} = ( defined $mysync->{maxsleep} ) ? $mysync->{maxsleep} : $MAX_SLEEP ; ;
18421 $skipcrossduplicates = ( defined $skipcrossduplicates ) ? $skipcrossduplicates : 0 ;
18422 $mysync->{ synclabels } = ( defined $mysync->{ synclabels } ) ? $mysync->{ synclabels } : 1 ;
18423 $mysync->{ resynclabels } = ( defined $mysync->{ resynclabels } ) ? $mysync->{ resynclabels } : 1 ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010018424 push @useheader, 'X-Gmail-Received', 'Message-Id' ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018425 push @exclude, '\[Gmail\]$' ;
18426 push @folderlast, '[Gmail]/All Mail' ;
18427 return ;
18428}
18429
18430
18431sub gmail1
18432{
18433 my $mysync = shift ;
18434 # Gmail at host2
18435 $mysync->{host1} ||= 'imap.gmail.com' ;
18436 $mysync->{ssl1} = ( defined $mysync->{ssl1} ) ? $mysync->{ssl1} : 1 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018437 $mysync->{maxbytespersecond} ||= 40_000 ; # should be 30_000 computed from by Gmail documentation
18438 $mysync->{maxbytesafter} ||= 3_000_000_000 ; #
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018439 $mysync->{automap} = ( defined $mysync->{automap} ) ? $mysync->{automap} : 1 ;
18440 $mysync->{maxsleep} = ( defined $mysync->{maxsleep} ) ? $mysync->{maxsleep} : $MAX_SLEEP ; ;
18441 $skipcrossduplicates = ( defined $skipcrossduplicates ) ? $skipcrossduplicates : 1 ;
18442
18443 push @useheader, 'X-Gmail-Received', 'Message-Id' ;
18444 push @{ $mysync->{ regextrans2 } }, 's,\[Gmail\].,,' ;
18445 push @folderlast, '[Gmail]/All Mail' ;
18446 return ;
18447}
18448
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010018449sub gmail2
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018450{
18451 my $mysync = shift ;
18452 # Gmail at host2
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018453 $mysync->{ host2 } ||= 'imap.gmail.com' ;
18454 $mysync->{ ssl2 } = ( defined $mysync->{ ssl2 } ) ? $mysync->{ ssl2 } : 1 ;
18455 $mysync->{ maxbytespersecond } ||= 20_000 ; # should be less than 10_000 computed from by Gmail documentation
18456 $mysync->{ maxbytesafter } ||= 1_000_000_000 ; # In fact it is documented as half: 500_000_000
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018457
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018458 $mysync->{ automap } = ( defined $mysync->{ automap } ) ? $mysync->{ automap } : 1 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018459 #$skipcrossduplicates = ( defined $skipcrossduplicates ) ? $skipcrossduplicates : 1 ;
18460 $mysync->{ expunge1 } = ( defined $mysync->{ expunge1 } ) ? $mysync->{ expunge1 } : 1 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018461 $mysync->{ addheader } = ( defined $mysync->{ addheader } ) ? $mysync->{ addheader } : 1 ;
18462 $mysync->{ maxsleep } = ( defined $mysync->{ maxsleep } ) ? $mysync->{ maxsleep } : $MAX_SLEEP ; ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018463
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018464 #$mysync->{ maxsize } = ( defined $mysync->{ maxsize } ) ? $mysync->{ maxsize } : $GMAIL_MAXSIZE ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018465
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018466 if ( ! $mysync->{ noexclude } ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018467 push @exclude, '\[Gmail\]$' ;
18468 }
18469 push @useheader, 'Message-Id' ;
18470 push @{ $mysync->{ regextrans2 } }, 's,\[Gmail\].,,' ;
18471
18472 # push @{ $mysync->{ regextrans2 } }, 's/[ ]+/_/g' ; # is now replaced
18473 # by the two more specific following regexes,
18474 # they remove just the beginning and trailing blanks, not all.
18475 push @{ $mysync->{ regextrans2 } }, 's,^ +| +$,,g' ;
18476 push @{ $mysync->{ regextrans2 } }, 's,/ +| +/,/,g' ;
18477 #
18478 push @{ $mysync->{ regextrans2 } }, q{s/['\\^"]/_/g} ; # Verified this
18479 push @folderlast, '[Gmail]/All Mail' ;
18480 return ;
18481}
18482
18483
18484# From https://imapsync.lamiral.info/FAQ.d/FAQ.Exchange.txt
18485sub office1
18486{
18487 # Office 365 at host1
18488 my $mysync = shift ;
18489
18490 output( $mysync, q{Option --office1 is like: --host1 outlook.office365.com --ssl1 --exclude "^Files$"} . "\n" ) ;
18491 output( $mysync, "Option --office1 (cont) : unless overrided with --host1 otherhost --nossl1 --noexclude\n" ) ;
18492 $mysync->{host1} ||= 'outlook.office365.com' ;
18493 $mysync->{ssl1} = ( defined $mysync->{ssl1} ) ? $mysync->{ssl1} : 1 ;
18494 if ( ! $mysync->{noexclude} ) {
18495 push @exclude, '^Files$' ;
18496 }
18497 return ;
18498}
18499
18500
18501sub office2
18502{
18503 # Office 365 at host2
18504 my $mysync = shift ;
18505 output( $mysync, qq{Option --office2 is like: --host2 outlook.office365.com --ssl2 --maxsize 45_000_000 --maxmessagespersecond 4\n} ) ;
18506 output( $mysync, qq{Option --office2 (cont) : --disarmreadreceipts --regexmess "wrap 10500" --f1f2 "Files=Files_renamed_by_imapsync"\n} ) ;
18507 output( $mysync, qq{Option --office2 (cont) : unless overrided with --host2 otherhost --nossl2 ... --nodisarmreadreceipts --noregexmess\n} ) ;
18508 output( $mysync, qq{Option --office2 (cont) : and --nof1f2 to avoid Files folder renamed to Files_renamed_by_imapsync\n} ) ;
18509 $mysync->{host2} ||= 'outlook.office365.com' ;
18510 $mysync->{ssl2} = ( defined $mysync->{ssl2} ) ? $mysync->{ssl2} : 1 ;
18511 $mysync->{ maxsize } ||= 45_000_000 ;
18512 $mysync->{maxmessagespersecond} ||= 4 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018513 #push @{ $mysync->{ regexflag } }, 's/\\\\Flagged//g' ; # No problem without! tested 2018_09_10
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018514 $disarmreadreceipts = ( defined $disarmreadreceipts ) ? $disarmreadreceipts : 1 ;
18515 # I dislike double negation but here is one
18516 if ( ! $mysync->{noregexmess} )
18517 {
18518 push @regexmess, 's,(.{10239}),$1\r\n,g' ;
18519 }
18520 # and another...
18521 if ( ! $mysync->{nof1f2} )
18522 {
18523 push @{ $mysync->{f1f2} }, 'Files=Files_renamed_by_imapsync' ;
18524 }
18525 return ;
18526}
18527
18528sub exchange1
18529{
18530 # Exchange 2010/2013 at host1
18531 my $mysync = shift ;
18532 output( $mysync, "Option --exchange1 does nothing (except printing this line...)\n" ) ;
18533 # Well nothing to do so far
18534 return ;
18535}
18536
18537sub exchange2
18538{
18539 # Exchange 2010/2013 at host2
18540 my $mysync = shift ;
18541 output( $mysync, "Option --exchange2 is like: --maxsize 10_000_000 --maxmessagespersecond 4 --disarmreadreceipts\n" ) ;
18542 output( $mysync, "Option --exchange2 (cont) : --regexflag del Flagged --regexmess wrap 10500\n" ) ;
18543 output( $mysync, "Option --exchange2 (cont) : unless overrided with --maxsize xxx --nodisarmreadreceipts --noregexflag --noregexmess\n" ) ;
18544 $mysync->{ maxsize } ||= 10_000_000 ;
18545 $mysync->{maxmessagespersecond} ||= 4 ;
18546 $disarmreadreceipts = ( defined $disarmreadreceipts ) ? $disarmreadreceipts : 1 ;
18547 # I dislike double negation but here are two
18548 if ( ! $mysync->{noregexflag} ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018549 push @{ $mysync->{ regexflag } }, 's/\\\\Flagged//g' ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018550 }
18551 if ( ! $mysync->{noregexmess} ) {
18552 push @regexmess, 's,(.{10239}),$1\r\n,g' ;
18553 }
18554 return ;
18555}
18556
18557sub domino1
18558{
18559 # Domino at host1
18560 my $mysync = shift ;
18561
18562 $mysync->{ sep1 } = q{\\} ;
18563 $prefix1 = q{} ;
18564 $messageidnodomain = ( defined $messageidnodomain ) ? $messageidnodomain : 1 ;
18565 return ;
18566}
18567
18568sub domino2
18569{
18570 # Domino at host1
18571 my $mysync = shift ;
18572
18573 $mysync->{ sep2 } = q{\\} ;
18574 $prefix2 = q{} ;
18575 $messageidnodomain = ( defined $messageidnodomain ) ? $messageidnodomain : 1 ;
18576 push @{ $mysync->{ regextrans2 } }, 's,^Inbox\\\\(.*),$1,i' ;
18577 return ;
18578}
18579
18580
18581sub tests_resolv
18582{
18583 note( 'Entering tests_resolv()' ) ;
18584
18585 # is( , resolv( ), 'resolv: => ' ) ;
18586 is( undef, resolv( ), 'resolv: no args => undef' ) ;
18587 is( undef, resolv( q{} ), 'resolv: empty string => undef' ) ;
18588 is( undef, resolv( 'hostnotexist' ), 'resolv: hostnotexist => undef' ) ;
18589 is( '127.0.0.1', resolv( '127.0.0.1' ), 'resolv: 127.0.0.1 => 127.0.0.1' ) ;
18590 is( '127.0.0.1', resolv( 'localhost' ), 'resolv: localhost => 127.0.0.1' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018591 is( '2001:41d0:2:84e0::1', resolv( 'imapsync.lamiral.info' ), 'resolv: imapsync.lamiral.info => 2001:41d0:2:84e0::1' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018592
18593 # ip6-localhost ( in /etc/hosts )
18594 is( '::1', resolv( 'ip6-localhost' ), 'resolv: ip6-localhost => ::1' ) ;
18595 is( '::1', resolv( '::1' ), 'resolv: ::1 => ::1' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018596 # ks2ipv6 now has CNAME ks6ipv6
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018597 is( '2001:41d0:8:d8b6::1', resolv( '2001:41d0:8:d8b6::1' ), 'resolv: 2001:41d0:8:d8b6::1 => 2001:41d0:8:d8b6::1' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018598 is( '2001:41d0:8:9951::1', resolv( 'ks6ipv6.lamiral.info' ), 'resolv: ks6ipv6.lamiral.info => 2001:41d0:8:9951::1' ) ;
18599 # ks6
18600 is( '2001:41d0:8:9951::1', resolv( '2001:41d0:8:9951::1' ), 'resolv: 2001:41d0:8:9951::1 => 2001:41d0:8:9951::1' ) ;
18601 is( '2001:41d0:8:9951::1', resolv( 'ks6ipv6.lamiral.info' ), 'resolv: ks6ipv6.lamiral.info => 2001:41d0:8:9951::1' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018602 # ks3
18603 is( '2001:41d0:8:bebd::1', resolv( '2001:41d0:8:bebd::1' ), 'resolv: 2001:41d0:8:bebd::1 => 2001:41d0:8:bebd::1' ) ;
18604 is( '2001:41d0:8:bebd::1', resolv( 'ks3ipv6.lamiral.info' ), 'resolv: ks3ipv6.lamiral.info => 2001:41d0:8:bebd::1' ) ;
18605
18606
18607 note( 'Leaving tests_resolv()' ) ;
18608 return ;
18609}
18610
18611
18612
18613sub resolv
18614{
18615 my $host = shift @ARG ;
18616
18617 if ( ! $host ) { return ; }
18618 my $addr ;
18619 if ( defined &Socket::getaddrinfo ) {
18620 $addr = resolv_with_getaddrinfo( $host ) ;
18621 return( $addr ) ;
18622 }
18623
18624
18625
18626 my $iaddr = inet_aton( $host ) ;
18627 if ( ! $iaddr ) { return ; }
18628 $addr = inet_ntoa( $iaddr ) ;
18629
18630 return $addr ;
18631}
18632
18633sub resolv_with_getaddrinfo
18634{
18635 my $host = shift @ARG ;
18636
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018637 $sync->{ debug } and myprint( "Entering resolv_with_getaddrinfo( $host )\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018638 if ( ! $host ) { return ; }
18639
18640 my ( $err_getaddrinfo, @res ) = Socket::getaddrinfo( $host, "", { socktype => Socket::SOCK_RAW } ) ;
18641 if ( $err_getaddrinfo ) {
18642 myprint( "Cannot getaddrinfo of $host: $err_getaddrinfo\n" ) ;
18643 return ;
18644 }
18645
18646 my @addr ;
18647 while( my $ai = shift @res ) {
18648 my ( $err_getnameinfo, $ipaddr ) = Socket::getnameinfo( $ai->{addr}, Socket::NI_NUMERICHOST(), Socket::NIx_NOSERV() ) ;
18649 if ( $err_getnameinfo ) {
18650 myprint( "Cannot getnameinfo of $host: $err_getnameinfo\n" ) ;
18651 return ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018652 }else{
18653 $sync->{ debug } and myprint( "$host => $ipaddr\n" ) ;
18654 push @addr, $ipaddr ;
18655 my $reverse ;
18656 ( $err_getnameinfo, $reverse ) = Socket::getnameinfo( $ai->{addr}, 0, Socket::NIx_NOSERV() ) ;
18657 $sync->{ debug } and myprint( "$host => $ipaddr => $reverse\n" ) ;
18658 }
18659 $sync->{ debug } and myprint( "$host => $ipaddr\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018660
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018661 }
18662 $sync->{ debug } and myprint( "Leaving resolv_with_getaddrinfo( $host => $addr[0])\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018663 return $addr[0] ;
18664}
18665
18666sub tests_resolvrev
18667{
18668 note( 'Entering tests_resolvrev()' ) ;
18669
18670 # is( , resolvrev( ), 'resolvrev: => ' ) ;
18671 is( undef, resolvrev( ), 'resolvrev: no args => undef' ) ;
18672 is( undef, resolvrev( q{} ), 'resolvrev: empty string => undef' ) ;
18673 is( undef, resolvrev( 'hostnotexist' ), 'resolvrev: hostnotexist => undef' ) ;
18674 is( 'localhost', resolvrev( '127.0.0.1' ), 'resolvrev: 127.0.0.1 => localhost' ) ;
18675 is( 'localhost', resolvrev( 'localhost' ), 'resolvrev: localhost => localhost' ) ;
18676 is( 'ks.lamiral.info', resolvrev( 'imapsync.lamiral.info' ), 'resolvrev: imapsync.lamiral.info => ks.lamiral.info' ) ;
18677
18678 # ip6-localhost ( in /etc/hosts )
18679 is( 'ip6-localhost', resolvrev( 'ip6-localhost' ), 'resolvrev: ip6-localhost => ip6-localhost' ) ;
18680 is( 'ip6-localhost', resolvrev( '::1' ), 'resolvrev: ::1 => ip6-localhost' ) ;
18681 # ks2
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018682 is( 'ks6ipv6.lamiral.info', resolvrev( '2001:41d0:8:d8b6::1' ), 'resolvrev: 2001:41d0:8:d8b6::1 => ks6ipv6.lamiral.info' ) ;
18683 is( 'ks6ipv6.lamiral.info', resolvrev( 'ks6ipv6.lamiral.info' ), 'resolvrev: ks6ipv6.lamiral.info => ks6ipv6.lamiral.info' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018684 # ks3
18685 is( 'ks3ipv6.lamiral.info', resolvrev( '2001:41d0:8:bebd::1' ), 'resolvrev: 2001:41d0:8:bebd::1 => ks3ipv6.lamiral.info' ) ;
18686 is( 'ks3ipv6.lamiral.info', resolvrev( 'ks3ipv6.lamiral.info' ), 'resolvrev: ks3ipv6.lamiral.info => ks3ipv6.lamiral.info' ) ;
18687
18688
18689 note( 'Leaving tests_resolvrev()' ) ;
18690 return ;
18691}
18692
18693sub resolvrev
18694{
18695 my $host = shift @ARG ;
18696
18697 if ( ! $host ) { return ; }
18698
18699 if ( defined &Socket::getaddrinfo ) {
18700 my $name = resolvrev_with_getaddrinfo( $host ) ;
18701 return( $name ) ;
18702 }
18703
18704 return ;
18705}
18706
18707sub resolvrev_with_getaddrinfo
18708{
18709 my $host = shift @ARG ;
18710
18711 if ( ! $host ) { return ; }
18712
18713 my ( $err, @res ) = Socket::getaddrinfo( $host, "", { socktype => Socket::SOCK_RAW } ) ;
18714 if ( $err ) {
18715 myprint( "Cannot getaddrinfo of $host: $err\n" ) ;
18716 return ;
18717 }
18718
18719 my @name ;
18720 while( my $ai = shift @res ) {
18721 my ( $err, $reverse ) = Socket::getnameinfo( $ai->{addr}, 0, Socket::NIx_NOSERV() ) ;
18722 if ( $err ) {
18723 myprint( "Cannot getnameinfo of $host: $err\n" ) ;
18724 return ;
18725 }
18726 $sync->{ debug } and myprint( "$host => $reverse\n" ) ;
18727 push @name, $reverse ;
18728 }
18729
18730 return $name[0] ;
18731}
18732
18733
18734
18735sub tests_imapsping
18736{
18737 note( 'Entering tests_imapsping()' ) ;
18738
18739 is( undef, imapsping( ), 'imapsping: no args => undef' ) ;
18740 is( undef, imapsping( 'hostnotexist' ), 'imapsping: hostnotexist => undef' ) ;
18741 is( 1, imapsping( 'imapsync.lamiral.info' ), 'imapsping: imapsync.lamiral.info => 1' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018742 is( 1, imapsping( 'ks6ipv6.lamiral.info' ), 'imapsping: ks6ipv6.lamiral.info => 1' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018743 note( 'Leaving tests_imapsping()' ) ;
18744 return ;
18745}
18746
18747sub imapsping
18748{
18749 my $host = shift ;
18750 return tcpping( $host, $IMAP_SSL_PORT ) ;
18751}
18752
18753sub tests_tcpping
18754{
18755 note( 'Entering tests_tcpping()' ) ;
18756
18757 is( undef, tcpping( ), 'tcpping: no args => undef' ) ;
18758 is( undef, tcpping( 'hostnotexist' ), 'tcpping: one arg => undef' ) ;
18759 is( undef, tcpping( undef, 888 ), 'tcpping: arg undef, port => undef' ) ;
18760 is( undef, tcpping( 'hostnotexist', 993 ), 'tcpping: hostnotexist 993 => undef' ) ;
18761 is( undef, tcpping( 'hostnotexist', 888 ), 'tcpping: hostnotexist 888 => undef' ) ;
18762 is( 1, tcpping( 'imapsync.lamiral.info', 993 ), 'tcpping: imapsync.lamiral.info 993 => 1' ) ;
18763 is( 0, tcpping( 'imapsync.lamiral.info', 888 ), 'tcpping: imapsync.lamiral.info 888 => 0' ) ;
18764 is( 1, tcpping( '5.135.158.182', 993 ), 'tcpping: 5.135.158.182 993 => 1' ) ;
18765 is( 0, tcpping( '5.135.158.182', 888 ), 'tcpping: 5.135.158.182 888 => 0' ) ;
18766
18767 # Net::Ping supports ipv6 only after release 1.50
18768 # http://cpansearch.perl.org/src/RURBAN/Net-Ping-2.59/Changes
18769 # Anyway I plan to avoid Net-Ping for that too long standing feature
18770 # Net-Ping is integrated in Perl itself, who knows ipv6 for a long time
18771 is( 1, tcpping( '2001:41d0:8:d8b6::1', 993 ), 'tcpping: 2001:41d0:8:d8b6::1 993 => 1' ) ;
18772 is( 0, tcpping( '2001:41d0:8:d8b6::1', 888 ), 'tcpping: 2001:41d0:8:d8b6::1 888 => 0' ) ;
18773
18774 note( 'Leaving tests_tcpping()' ) ;
18775 return ;
18776}
18777
18778sub tcpping
18779{
18780 if ( 2 != scalar( @ARG ) ) {
18781 return ;
18782 }
18783 my ( $host, $port ) = @ARG ;
18784 if ( ! $host ) { return ; }
18785 if ( ! $port ) { return ; }
18786
18787 my $mytimeout = $TCP_PING_TIMEOUT ;
18788 require Net::Ping ;
18789 #my $p = Net::Ping->new( 'tcp' ) ;
18790 my $p = Net::Ping->new( ) ;
18791 $p->{port_num} = $port ;
18792 $p->service_check( 1 ) ;
18793 $p->hires( 1 ) ;
18794 my ($ping_ok, $rtt, $ip ) = $p->ping( $host, $mytimeout ) ;
18795 if ( ! defined $ping_ok ) { return ; }
18796 my $rtt_approx = sprintf( "%.3f", $rtt ) ;
18797 $sync->{ debug } and myprint( "Host $host timeout $mytimeout port $port ok $ping_ok ip $ip acked in $rtt_approx s\n" ) ;
18798 $p->close( ) ;
18799 if( $ping_ok ) {
18800 return 1 ;
18801 }else{
18802 return 0 ;
18803 }
18804}
18805
18806sub tests_sslcheck
18807{
18808 note( 'Entering tests_sslcheck()' ) ;
18809
18810 my $mysync ;
18811
18812 is( undef, sslcheck( $mysync ), 'sslcheck: no sslcheck => undef' ) ;
18813
18814 $mysync = {
18815 sslcheck => 1,
18816 } ;
18817
18818 is( 0, sslcheck( $mysync ), 'sslcheck: no host => 0' ) ;
18819
18820 $mysync = {
18821 sslcheck => 1,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018822 host1 => 'test1.lamiral.info',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018823 tls1 => 1,
18824 } ;
18825
18826 is( 0, sslcheck( $mysync ), 'sslcheck: tls1 => 0' ) ;
18827
18828 $mysync = {
18829 sslcheck => 1,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018830 host1 => 'test1.lamiral.info',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018831 } ;
18832
18833
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018834 is( 1, sslcheck( $mysync ), 'sslcheck: test1.lamiral.info => 1' ) ;
18835 is( 1, $mysync->{ssl1}, 'sslcheck: test1.lamiral.info => ssl1 1' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018836
18837 $mysync->{sslcheck} = 0 ;
18838 is( undef, sslcheck( $mysync ), 'sslcheck: sslcheck off => undef' ) ;
18839
18840 $mysync = {
18841 sslcheck => 1,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018842 host1 => 'test1.lamiral.info',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018843 host2 => 'test2.lamiral.info',
18844 } ;
18845
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018846 is( 2, sslcheck( $mysync ), 'sslcheck: test1.lamiral.info + test2.lamiral.info => 2' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018847
18848 $mysync = {
18849 sslcheck => 1,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018850 host1 => 'test1.lamiral.info',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018851 host2 => 'test2.lamiral.info',
18852 tls1 => 1,
18853 } ;
18854
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018855 is( 1, sslcheck( $mysync ), 'sslcheck: test1.lamiral.info + test2.lamiral.info + tls1 => 1' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018856
18857 note( 'Leaving tests_sslcheck()' ) ;
18858 return ;
18859}
18860
18861sub sslcheck
18862{
18863 my $mysync = shift ;
18864
18865 if ( ! $mysync->{sslcheck} ) {
18866 return ;
18867 }
18868 my $nb_on = 0 ;
18869 $mysync->{ debug } and myprint( "sslcheck\n" ) ;
18870 if (
18871 ( ! defined $mysync->{port1} )
18872 and
18873 ( ! defined $mysync->{tls1} )
18874 and
18875 ( ! defined $mysync->{ssl1} )
18876 and
18877 ( defined $mysync->{host1} )
18878 ) {
18879 myprint( "Host1: probing ssl on port $IMAP_SSL_PORT ( use --nosslcheck to avoid this ssl probe ) \n" ) ;
18880 if ( probe_imapssl( $mysync->{host1} ) ) {
18881 $mysync->{ssl1} = 1 ;
18882 myprint( "Host1: sslcheck detected open ssl port $IMAP_SSL_PORT so turning ssl on (use --nossl1 --notls1 to turn off SSL and TLS wizardry)\n" ) ;
18883 $nb_on++ ;
18884 }else{
18885 myprint( "Host1: sslcheck did not detected open ssl port $IMAP_SSL_PORT. Will use standard $IMAP_PORT port.\n" ) ;
18886 }
18887 }
18888
18889 if (
18890 ( ! defined $mysync->{port2} )
18891 and
18892 ( ! defined $mysync->{tls2} )
18893 and
18894 ( ! defined $mysync->{ssl2} )
18895 and
18896 ( defined $mysync->{host2} )
18897 ) {
18898 myprint( "Host2: probing ssl on port $IMAP_SSL_PORT ( use --nosslcheck to avoid this ssl probe ) \n" ) ;
18899 if ( probe_imapssl( $mysync->{host2} ) ) {
18900 $mysync->{ssl2} = 1 ;
18901 myprint( "Host2: sslcheck detected open ssl port $IMAP_SSL_PORT so turning ssl on (use --nossl2 --notls2 to turn off SSL and TLS wizardry)\n" ) ;
18902 $nb_on++ ;
18903 }else{
18904 myprint( "Host2: sslcheck did not detected open ssl port $IMAP_SSL_PORT. Will use standard $IMAP_PORT port.\n" ) ;
18905 }
18906 }
18907 return $nb_on ;
18908}
18909
18910
18911sub testslive_init
18912{
18913 my $mysync = shift ;
18914 $mysync->{host1} ||= 'test1.lamiral.info' ;
18915 $mysync->{user1} ||= 'test1' ;
18916 $mysync->{password1} ||= 'secret1' ;
18917 $mysync->{host2} ||= 'test2.lamiral.info' ;
18918 $mysync->{user2} ||= 'test2' ;
18919 $mysync->{password2} ||= 'secret2' ;
18920 return ;
18921}
18922
18923sub testslive6_init
18924{
18925 my $mysync = shift ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018926 $mysync->{host1} ||= 'ks6ipv6.lamiral.info' ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018927 $mysync->{user1} ||= 'test1' ;
18928 $mysync->{password1} ||= 'secret1' ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018929 $mysync->{host2} ||= 'ks6ipv6.lamiral.info' ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018930 $mysync->{user2} ||= 'test2' ;
18931 $mysync->{password2} ||= 'secret2' ;
18932 return ;
18933}
18934
18935
18936sub tests_backslash_caret
18937{
18938 note( 'Entering tests_backslash_caret()' ) ;
18939
18940 is( "lalala", backslash_caret( "lalala" ), 'backslash_caret: lalala => lalala' ) ;
18941 is( "lalala\n", backslash_caret( "lalala\n" ), 'backslash_caret: lalala => lalala 2nd' ) ;
18942 is( '^', backslash_caret( '\\' ), 'backslash_caret: \\ => ^' ) ;
18943 is( "^\n", backslash_caret( "\\\n" ), 'backslash_caret: \\ => ^' ) ;
18944 is( "\\lalala", backslash_caret( "\\lalala" ), 'backslash_caret: \\lalala => \\lalala' ) ;
18945 is( "\\lal\\ala", backslash_caret( "\\lal\\ala" ), 'backslash_caret: \\lal\\ala => \\lal\\ala' ) ;
18946 is( "\\lalala\n", backslash_caret( "\\lalala\n" ), 'backslash_caret: \\lalala => \\lalala 2nd' ) ;
18947 is( "lalala^\n", backslash_caret( "lalala\\\n" ), 'backslash_caret: lalala\\\n => lalala^\n' ) ;
18948 is( "lalala^\nlalala^\n", backslash_caret( "lalala\\\nlalala\\\n" ), 'backslash_caret: lalala\\\nlalala\\\n => lalala^\nlalala^\n' ) ;
18949 is( "lal\\ala^\nlalala^\n", backslash_caret( "lal\\ala\\\nlalala\\\n" ), 'backslash_caret: lal\\ala\\\nlalala\\\n => lal\\ala^\nlalala^\n' ) ;
18950
18951 note( 'Leaving tests_backslash_caret()' ) ;
18952 return ;
18953}
18954
18955sub backslash_caret
18956{
18957 my $string = shift ;
18958
18959 $string =~ s{\\ $ }{^}gxms ;
18960
18961 return $string ;
18962}
18963
18964sub tests_split_around_equal
18965{
18966 note( 'Entering tests_split_around_equal()' ) ;
18967
18968 is( undef, split_around_equal( ), 'split_around_equal: no args => undef' ) ;
18969 is_deeply( { toto => 'titi' }, { split_around_equal( 'toto=titi' ) }, 'split_around_equal: toto=titi => toto => titi' ) ;
18970 is_deeply( { A => 'B', C => 'D' }, { split_around_equal( 'A=B=C=D' ) }, 'split_around_equal: toto=titi => toto => titi' ) ;
18971 is_deeply( { A => 'B', C => 'D' }, { split_around_equal( 'A=B', 'C=D' ) }, 'split_around_equal: A=B C=D => A => B, C=>D' ) ;
18972
18973 note( 'Leaving tests_split_around_equal()' ) ;
18974 return ;
18975}
18976
18977sub split_around_equal
18978{
18979 if ( ! @ARG ) { return ; } ;
18980 return map { split /=/mxs, $_ } @ARG ;
18981
18982}
18983
18984
18985
18986sub tests_sig_install
18987{
18988 note( 'Entering tests_sig_install()' ) ;
18989
18990 my $mysync ;
18991 is( undef, sig_install( ), 'sig_install: no args => undef' ) ;
18992 is( undef, sig_install( $mysync ), 'sig_install: arg undef => undef' ) ;
18993 $mysync = { } ;
18994 is( undef, sig_install( $mysync ), 'sig_install: empty hash => undef' ) ;
18995
18996 SKIP: {
18997 Readonly my $SKIP_15 => 15 ;
18998 if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests only for Unix', $SKIP_15 ) ; }
18999 # Default to ignore USR1 USR2 in case future install fails
19000 local $SIG{ USR1 } = local $SIG{ USR2 } = sub { } ;
19001 kill( 'USR1', $PROCESS_ID ) ;
19002
19003 $mysync->{ debugsig } = 1 ;
19004 # Assign USR1 to call sub tototo
19005 # Surely a better value than undef should be returned when doing real signal stuff
19006 is( undef, sig_install( $mysync, 'tototo', 'USR1' ), 'sig_install: USR1 tototo' ) ;
19007
19008 is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 1' ) ;
19009 is( 1, $mysync->{ tototo_calls }, 'sig_install: tototo call nb 1' ) ;
19010
19011 #return ;
19012 # Assign USR2 to call sub tototo
19013 is( undef, sig_install( $mysync, 'tototo', 'USR2' ), 'sig_install: USR2 tototo' ) ;
19014
19015 is( 1, kill( 'USR2', $PROCESS_ID ), 'sig_install: kill USR2 myself 1' ) ;
19016 is( 2, $mysync->{ tototo_calls }, 'sig_install: tototo call nb 2' ) ;
19017
19018 is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 2' ) ;
19019 is( 3, $mysync->{ tototo_calls }, 'sig_install: tototo call nb 3' ) ;
19020
19021
19022 local $SIG{ USR1 } = local $SIG{ USR2 } = sub { } ;
19023 is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 3' ) ;
19024 is( 3, $mysync->{ tototo_calls }, 'sig_install: tototo call still nb 3' ) ;
19025
19026 # Assign USR1 + USR2 to call sub tototo
19027 is( undef, sig_install( $mysync, 'tototo', 'USR1', 'USR2' ), 'sig_install: USR1 USR2 tototo' ) ;
19028 is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 4' ) ;
19029 is( 4, $mysync->{ tototo_calls }, 'sig_install: tototo call now nb 4' ) ;
19030
19031 is( 1, kill( 'USR2', $PROCESS_ID ), 'sig_install: kill USR1 myself 2' ) ;
19032 is( 5, $mysync->{ tototo_calls }, 'sig_install: tototo call now nb 5' ) ;
19033 }
19034
19035
19036 note( 'Leaving tests_sig_install()' ) ;
19037 return ;
19038}
19039
19040
19041#
19042sub sig_install
19043{
19044 my $mysync = shift ;
19045 if ( ! $mysync ) { return ; }
19046 my $mysubname = shift ;
19047 if ( ! $mysubname ) { return ; }
19048
19049 if ( ! @ARG ) { return ; }
19050
19051 my @signals = @ARG ;
19052
19053 my $mysub = \&$mysubname ;
19054 #$mysync->{ debugsig } = 1 ;
19055 $mysync->{ debugsig } and myprint( "In sig_install with sub $mysubname and signal @ARG\n" ) ;
19056
19057 my $subsignal = sub {
19058 my $signame = shift ;
19059 $mysync->{ debugsig } and myprint( "In subsignal with $signame and $mysubname\n" ) ;
19060 &$mysub( $mysync, $signame ) ;
19061 } ;
19062
19063 foreach my $signal ( @signals ) {
19064 $mysync->{ debugsig } and myprint( "Installing signal $signal to call sub $mysubname\n") ;
19065 output( $mysync, "kill -$signal $PROCESS_ID # special behavior: call to sub $mysubname\n" ) ;
19066 ## no critic (RequireLocalizedPunctuationVars)
19067 $SIG{ $signal } = $subsignal ;
19068 }
19069 return ;
19070}
19071
19072
19073sub tototo
19074{
19075 my $mysync = shift ;
19076 myprint("In tototo with @ARG\n" ) ;
19077 $mysync->{ tototo_calls } += 1 ;
19078 return ;
19079}
19080
19081sub mygetppid
19082{
19083 if ( 'MSWin32' eq $OSNAME ) {
19084 return( 'unknown under MSWin32 (too complicated)' ) ;
19085 } else {
19086 # Unix
19087 return( getppid( ) ) ;
19088 }
19089}
19090
19091
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019092sub tests_toggle_sleep
19093{
19094 note( 'Entering tests_toggle_sleep()' ) ;
19095
19096 is( undef, toggle_sleep( ), 'toggle_sleep: no args => undef' ) ;
19097 my $mysync ;
19098 is( undef, toggle_sleep( $mysync ), 'toggle_sleep: undef => undef' ) ;
19099 $mysync = { } ;
19100 is( undef, toggle_sleep( $mysync ), 'toggle_sleep: no maxsleep => undef' ) ;
19101
19102 $mysync->{maxsleep} = 3 ;
19103 is( 0, toggle_sleep( $mysync ), 'toggle_sleep: 3 => 0' ) ;
19104
19105 is( $MAX_SLEEP, toggle_sleep( $mysync ), "toggle_sleep: 0 => $MAX_SLEEP" ) ;
19106 is( 0, toggle_sleep( $mysync ), "toggle_sleep: $MAX_SLEEP => 0" ) ;
19107 is( $MAX_SLEEP, toggle_sleep( $mysync ), "toggle_sleep: 0 => $MAX_SLEEP" ) ;
19108 is( 0, toggle_sleep( $mysync ), "toggle_sleep: $MAX_SLEEP => 0" ) ;
19109
19110 SKIP: {
19111 Readonly my $SKIP_9 => 9 ;
19112 if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests only for Unix', $SKIP_9 ) ; }
19113 # Default to ignore USR1 USR2 in case future install fails
19114 local $SIG{ USR1 } = sub { } ;
19115 kill( 'USR1', $PROCESS_ID ) ;
19116
19117 $mysync->{ debugsig } = 1 ;
19118 # Assign USR1 to call sub toggle_sleep
19119 is( undef, sig_install( $mysync, \&toggle_sleep, 'USR1' ), 'toggle_sleep: install USR1 toggle_sleep' ) ;
19120
19121
19122 $mysync->{maxsleep} = 4 ;
19123 is( 1, kill( 'USR1', $PROCESS_ID ), 'toggle_sleep: kill USR1 myself' ) ;
19124 is( 0, $mysync->{ maxsleep }, 'toggle_sleep: toggle_sleep called => sleeps are 0s' ) ;
19125
19126 is( 1, kill( 'USR1', $PROCESS_ID ), 'toggle_sleep: kill USR1 myself again' ) ;
19127 is( $MAX_SLEEP, $mysync->{ maxsleep }, "toggle_sleep: toggle_sleep called => sleeps are ${MAX_SLEEP}s" ) ;
19128
19129 is( 1, kill( 'USR1', $PROCESS_ID ), 'toggle_sleep: kill USR1 myself' ) ;
19130 is( 0, $mysync->{ maxsleep }, 'toggle_sleep: toggle_sleep called => sleeps are 0s' ) ;
19131
19132 is( 1, kill( 'USR1', $PROCESS_ID ), 'toggle_sleep: kill USR1 myself again' ) ;
19133 is( $MAX_SLEEP, $mysync->{ maxsleep }, "toggle_sleep: toggle_sleep called => sleeps are ${MAX_SLEEP}s" ) ;
19134 }
19135
19136 note( 'Leaving tests_toggle_sleep()' ) ;
19137 return ;
19138}
19139
19140
19141sub toggle_sleep
19142{
19143 my $mysync = shift ;
19144
19145 myprint("In toggle_sleep with @ARG\n" ) ;
19146
19147 if ( !defined( $mysync ) ) { return ; }
19148 if ( !defined( $mysync->{maxsleep} ) ) { return ; }
19149
19150 $mysync->{ maxsleep } = max( 0, $MAX_SLEEP - $mysync->{maxsleep} ) ;
19151 myprint("Resetting maxsleep to ", $mysync->{maxsleep}, "s\n" ) ;
19152 return $mysync->{maxsleep} ;
19153}
19154
19155sub mypod2usage
19156{
19157 my $fh_pod2usage = shift ;
19158
19159 pod2usage(
19160 -exitval => 'NOEXIT',
19161 -noperldoc => 1,
19162 -verbose => 99,
19163 -sections => [ qw(NAME VERSION USAGE OPTIONS) ],
19164 -indent => 1,
19165 -loose => 1,
19166 -output => $fh_pod2usage,
19167 ) ;
19168
19169 return ;
19170}
19171
19172sub usage
19173{
19174 my $mysync = shift ;
19175
19176 if ( ! defined $mysync ) { return ; }
19177
19178 my $usage = q{} ;
19179 my $usage_from_pod ;
19180 my $usage_footer = usage_footer( $mysync ) ;
19181
19182 # pod2usage writes on a filehandle only and I want a variable
19183 open my $fh_pod2usage, ">", \$usage_from_pod
19184 or do { warn $OS_ERROR ; return ; } ;
19185 mypod2usage( $fh_pod2usage ) ;
19186 close $fh_pod2usage ;
19187
19188 if ( 'MSWin32' eq $OSNAME ) {
19189 $usage_from_pod = backslash_caret( $usage_from_pod ) ;
19190 }
19191 $usage = join( q{}, $usage_from_pod, $usage_footer ) ;
19192
19193 return( $usage ) ;
19194}
19195
19196sub tests_usage
19197{
19198 note( 'Entering tests_usage()' ) ;
19199
19200 my $usage ;
19201 like( $usage = usage( $sync ), qr/Name:/, 'usage: contains Name:' ) ;
19202 myprint( $usage ) ;
19203 like( $usage, qr/Version:/, 'usage: contains Version:' ) ;
19204 like( $usage, qr/Usage:/, 'usage: contains Usage:' ) ;
19205 like( $usage, qr/imapsync/, 'usage: contains imapsync' ) ;
19206
19207 is( undef, usage( ), 'usage: no args => undef' ) ;
19208
19209 note( 'Leaving tests_usage()' ) ;
19210 return ;
19211}
19212
19213
19214sub usage_footer
19215{
19216 my $mysync = shift ;
19217
19218 my $footer = q{} ;
19219
19220 my $localhost_info = localhost_info( $mysync ) ;
19221 my $rcs = $mysync->{rcs} ;
19222 my $homepage = homepage( ) ;
19223
19224 my $imapsync_release = $STR_use_releasecheck ;
19225
19226 if ( $mysync->{releasecheck} ) {
19227 $imapsync_release = check_last_release( ) ;
19228 }
19229
19230 $footer = qq{$localhost_info
19231$rcs
19232$imapsync_release
19233$homepage
19234} ;
19235 return( $footer ) ;
19236}
19237
19238
19239
19240sub usage_complete
19241{
19242 # Unused, I guess this function could be deleted
19243 my $usage = <<'EOF' ;
19244--skipheader reg : Don't take into account header keyword
19245 matching reg ex: --skipheader 'X.*'
19246
19247--skipsize : Don't take message size into account to compare
19248 messages on both sides. On by default.
19249 Use --no-skipsize for using size comparaison.
19250--allowsizemismatch : allow RFC822.SIZE != fetched msg size
19251 consider also --skipsize to avoid duplicate messages
19252 when running syncs more than one time per mailbox
19253
19254--reconnectretry1 int : reconnect to host1 if connection is lost up to
19255 int times per imap command (default is 3)
19256--reconnectretry2 int : same as --reconnectretry1 but for host2
19257--split1 int : split the requests in several parts on host1.
19258 int is the number of messages handled per request.
19259 default is like --split1 100.
19260--split2 int : same thing on host2.
19261--nofixInboxINBOX : Don't fix Inbox INBOX mapping.
19262EOF
19263 return( $usage ) ;
19264}
19265
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019266
19267
19268
19269sub setvalfromcgikey
19270{
19271 my ( $mysync, $mycgi, $key, $val ) = @ARG ;
19272
19273 my $badthings = 0 ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010019274
19275
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019276 my ( $name, $type, $struct ) ;
19277 if ( $key !~ m/^([\w\d\|]+)([=:][isf])?([\+!\@\%])?$/mxs )
19278 {
19279 $badthings++ ;
19280 next ; # Unknown item
19281 }
19282 else
19283 {
19284 $name = [ split '|', $1, 1 ]->[0] ; # option name ab|cd|ef => keep only ab
19285 $type = $2 ; # = or : followed by i or s or f
19286 $struct = $3 ; # + or ! or @ or %
19287 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010019288
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019289 if ( ( $struct || q{} ) eq '+' )
19290 {
19291 ${$val} = $mycgi->param( $name ) ; # "Incremental" integer
19292 }
19293 elsif ( $type )
19294 {
19295 my @values = $mycgi->multi_param( $name ) ;
19296
19297 #myprint( "type[$type]values[@values]\$struct[", $struct || q{}, "]val[$val]ref(val)[", ref($val), "]\n" ) ;
19298 if ( ( $struct || q{} ) eq '%' or ref( $val ) eq 'HASH' )
19299 {
19300 setvalfromhash( $val, $type, @values ) ;
19301 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010019302 else
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019303 {
19304 setvalfromlist( $mysync, $val, $name, $type, $struct, @values ) ;
19305 }
19306 }
19307 else
19308 {
19309 setvalfromcheckbox( $mysync, $mycgi, $key, $name, $val ) ;
19310 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010019311
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019312 return $badthings ;
19313}
19314
19315sub setvalfromlist
19316{
19317 my ( $mysync, $val, $name, $type, $struct, @values ) = @ARG ;
19318 if ( $type =~ m/i$/mxs )
19319 {
19320 @values = map { q{} ne $_ ? int $_ : undef } @values ;
19321 }
19322 elsif ( $type =~ m/f$/mxs )
19323 {
19324 @values = map { 0 + $_ } @values ;
19325 }
19326
19327 if ( ( $struct || q{} ) eq '@' )
19328 {
19329 @{ ${$val} } = @values ;
19330 my @option = map { +( "--$name", "$_" ) } @values ;
19331 push @{ $mysync->{ cmdcgi } }, @option ;
19332 }
19333 elsif ( ref( $val ) eq 'ARRAY' )
19334 {
19335 @{$val} = @values ;
19336 }
19337 elsif ( my $value = $values[0] )
19338 {
19339 ${$val} = $value ;
19340 push @{ $mysync->{ cmdcgi } }, "--$name", $value ;
19341 }
19342 else
19343 {
19344 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010019345
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019346 return ;
19347}
19348sub setvalfromhash
19349{
19350 my ( $val, $type, @values ) = @ARG ;
19351
19352 my %values = map { split /=/mxs, $_ } @values ;
19353
19354 if ( $type =~ m/i$/mxs )
19355 {
19356 foreach my $k ( keys %values )
19357 {
19358 $values{$k} = int $values{$k} ;
19359 }
19360 }
19361 elsif ( $type =~ m/f$/mxs )
19362 {
19363 foreach my $k ( keys %values ) {
19364 $values{$k} = 0 + $values{$k};
19365 }
19366 }
19367
19368 if ( 'REF' eq ref $val )
19369 {
19370 %{ ${$val} } = %values ;
19371 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010019372 else
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019373 {
19374 %{$val} = %values ;
19375 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010019376
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019377 return ;
19378}
19379
19380
19381sub setvalfromcheckbox
19382{
19383 my ( $mysync, $mycgi, $key, $name, $val ) = @ARG ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010019384
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019385 # Checkbox
19386 # --noname is set by name=0 or name=
19387 my $value = $mycgi->param( $name ) ;
19388 if ( defined $value )
19389 {
19390 ${$val} = $value ;
19391 if ( $value )
19392 {
19393 push @{ $mysync->{ cmdcgi } }, "--$name" ;
19394 }
19395 else
19396 {
19397 push @{ $mysync->{ cmdcgi } }, "--no$name" ;
19398 }
19399 }
19400 else
19401 {
19402 ${$val} = undef ;
19403 }
19404 return ;
19405}
19406
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019407sub myGetOptions
19408{
19409
19410 # Started as a copy of Luke Ross Getopt::Long::CGI
19411 # https://metacpan.org/release/Getopt-Long-CGI
19412 # So this sub function is under the same license as Getopt-Long-CGI Luke Ross wants it,
19413 # which was Perl 5.6 or later licenses at the date of the copy.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019414 # It also applies for the sub functions called from this one.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019415
19416 my $mysync = shift @ARG ;
19417 my $arguments_ref = shift @ARG ;
19418 my %options = @ARG ;
19419
19420 my $mycgi = $mysync->{cgi} ;
19421
19422 if ( not under_cgi_context() ) {
19423
19424 # Not CGI - pass upstream for normal command line handling
19425 return Getopt::Long::GetOptionsFromArray( $arguments_ref, %options ) ;
19426 }
19427
19428 # We must be in CGI context now
19429 if ( ! defined( $mycgi ) ) { return ; }
19430
19431 my $badthings = 0 ;
19432 foreach my $key ( sort keys %options ) {
19433 my $val = $options{$key} ;
19434
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019435 $badthings += setvalfromcgikey( $mysync, $mycgi, $key, $val ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019436
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019437 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010019438
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019439 if ( $badthings ) {
19440 return ; # undef or ()
19441 }
19442 else {
19443 return ( 1 ) ;
19444 }
19445}
19446
19447
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019448
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010019449sub tests_get_options_extra
19450{
19451 note( 'Entering tests_get_options_extra()' ) ;
19452
19453 is( undef, get_options_extra( ), 'get_options_extra: no args => undef' ) ;
19454
19455 my $mysync = { } ;
19456 is( undef, get_options_extra( $mysync ), 'get_options_extra: undef => undef' ) ;
19457
19458 my $cwd_save = getcwd( ) ;
19459
19460 ok( (-d 'W/tmp/tests/options_extra/' or mkpath( 'W/tmp/tests/options_extra/' )), 'get_options_extra: mkpath W/tmp/tests/options_extra/' ) ;
19461
19462 chdir 'W/tmp/tests/options_extra/' ;
19463
19464 is( '--debugimap1', string_to_file( '--debugimap1', 'options_extra.txt' ), 'get_options_extra: string_to_file filling options_extra.txt with --debugimap1' ) ;
19465
19466 is( '--debugimap1', file_to_string( 'options_extra.txt' ), 'get_options_extra: reading options_extra.txt is --debugimap1' ) ;
19467
19468 is( '', get_options_extra( $mysync ), 'get_options_extra: --debugimap1 in options_extra.txt => nothing left, empty string return' ) ;
19469
19470 is( 1, $mysync->{ acc1 }->{ debugimap }, 'get_options_extra: --debugimap1 in options_extra.txt => ok, acc1->debugimap = 1' ) ;
19471
19472 is( '--tls1 proutcaca', string_to_file( '--tls1 proutcaca', 'options_extra.txt' ), 'get_options_extra: string_to_file filling options_extra.txt with --tls1 proutcaca' ) ;
19473
19474 is( 'proutcaca', get_options_extra( $mysync ), 'get_options_extra: --tls1 proutcaca in options_extra.txt => proutcaca left, proutcaca return' ) ;
19475
19476 chdir $cwd_save ;
19477
19478 note( 'Leaving tests_get_options_extra()' ) ;
19479 return ;
19480}
19481
19482sub get_options_extra
19483{
19484 my $mysync = shift @ARG ;
19485
19486 if ( ! defined $mysync ) { return ; }
19487
19488 if ( -f -r 'options_extra.txt' )
19489 {
19490 my $cwd = getcwd( ) ;
19491 my $string = firstline( 'options_extra.txt' ) ;
19492 my $rest = get_options_from_string( $mysync, $string ) ;
19493 output( $mysync, "Reading extra options from file options_extra.txt (cwd: $cwd) : $string\n" ) ;
19494 return $rest ;
19495 }
19496 else
19497 {
19498 return ;
19499 }
19500}
19501
19502
19503sub tests_get_options_from_string
19504{
19505 note( 'Entering tests_get_options_from_string()' ) ;
19506
19507 is( undef, get_options_from_string( ), 'get_options_from_string: no args => undef' ) ;
19508 my $mysync = { } ;
19509 is( undef, get_options_from_string( $mysync ), 'get_options_from_string: undef => undef' ) ;
19510
19511 is( '', get_options_from_string( $mysync, '--debugimap1' ),
19512 'get_options_from_string: --debugimap1 => ok, nothing left, empty string return' ) ;
19513 is( 1, $mysync->{ acc1 }->{ debugimap }, 'get_options_from_string: --debugimap1 => ok, acc1->debugimap = 1' ) ;
19514
19515 $mysync = { } ; # reset
19516 is( 'caca', get_options_from_string( $mysync, '--debugimap1 caca' ),
19517 'get_options_from_string: --debugimap1 caca => ok, caca left, caca return' ) ;
19518 is( 1, $mysync->{ acc1 }->{ debugimap }, 'get_options_from_string: --debugimap1 => ok, acc1->debugimap = 1' ) ;
19519
19520 is( 'popo roro', get_options_from_string( $mysync, '--debugimap2 popo roro' ),
19521 'get_options_from_string: --debugimap1 popo roro => ok, popo roro left, popo roro return' ) ;
19522 is( 1, $mysync->{ acc2 }->{ debugimap }, 'get_options_from_string: --debugimap2 popo roro => ok, acc2->debugimap = 1' ) ;
19523 is( 1, $mysync->{ acc1 }->{ debugimap }, 'get_options_from_string: acc1->debugimap = 1 still' ) ;
19524
19525 is( '', get_options_from_string( $mysync, '--nodebugimap1 --debugflags --errorsmax 2' ),
19526 'get_options_from_string: --nodebugimap1 --debugflags --errorsmax 2 => ok, empty string return' ) ;
19527
19528 is( 0, $mysync->{ acc1 }->{ debugimap }, 'get_options_from_string: acc1->debugimap = 0 now' ) ;
19529 is( 1, $debugflags, 'get_options_from_string: debugflags = 1 now' ) ;
19530 is( 2, $mysync->{ errorsmax }, 'get_options_from_string: mysync->errorsmax = 2 now' ) ;
19531
19532 is( '', get_options_from_string( $mysync, '--folder "IN BOX" --folder JOE' ),
19533 'get_options_from_string: --folder "IN BOX" --folder JOE => ok, empty string return' ) ;
19534
19535 is_deeply( [ 'IN BOX', 'JOE' ], [@{$mysync->{ folder }}], 'get_options_from_string: "IN BOX" "JOE"' ) ;
19536
19537 is( '', get_options_from_string( $mysync, '--debugflags --koko' ),
19538 'get_options_from_string: --debugflags --koko => ok, empty string return, with "Unknown option: koko" on STDERR' ) ;
19539
19540 note( 'Leaving tests_get_options_from_string()' ) ;
19541 return ;
19542}
19543
19544sub get_options_from_string
19545{
19546 my $mysync = shift @ARG ;
19547 my $mystring = shift @ARG ;
19548
19549 if ( ! defined $mystring ) { return ; }
19550
19551 my ( $ret, $args ) = Getopt::Long::GetOptionsFromString( $mystring,
19552 'debugimap!' => \$mysync->{ debugimap },
19553 'debugimap1!' => \$mysync->{ acc1 }->{ debugimap },
19554 'debugimap2!' => \$mysync->{ acc2 }->{ debugimap },
19555 'debugflags!' => \$debugflags,
19556 'debugsleep=f' => \$mysync->{ debugsleep },
19557 'errorsmax=i' => \$mysync->{ errorsmax },
19558 'folder=s@' => \$mysync->{ folder },
19559 'timeout=f' => \$mysync->{ timeout },
19560 'timeout1=f' => \$mysync->{ acc1 }->{ timeout },
19561 'timeout2=f' => \$mysync->{ acc2 }->{ timeout },
19562 'keepalive1!' => \$mysync->{ acc1 }->{ keepalive },
19563 'keepalive2!' => \$mysync->{ acc2 }->{ keepalive },
19564 'reconnectretry1=i' => \$mysync->{ acc1 }->{ reconnectretry },
19565 'reconnectretry2=i' => \$mysync->{ acc2 }->{ reconnectretry },
19566 'ssl1!' => \$mysync->{ ssl1 },
19567 'ssl2!' => \$mysync->{ ssl2 },
19568 'tls1!' => \$mysync->{ tls1 },
19569 'tls2!' => \$mysync->{ tls2 },
19570 'compress1!' => \$mysync->{ acc1 }->{ compress },
19571 'compress2!' => \$mysync->{ acc2 }->{ compress },
19572 ) ;
19573 my $left = join( ' ', @$args ) ;
19574 return $left ;
19575}
19576
19577
19578
19579
19580
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019581
19582sub tests_get_options_cgi_context
19583{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019584 note( 'Entering tests_get_options_cgi_context()' ) ;
19585
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019586
19587# Temporary, have to think harder about testing CGI context in command line --tests
19588 # API:
19589 # * input arguments: two ways, command line or CGI
19590 # * the program arguments
19591 # * QUERY_STRING env variable
19592 # * return
19593 # * QUERY_STRING length
19594
19595 # CGI context
19596 local $ENV{SERVER_SOFTWARE} = 'Votre serviteur' ;
19597
19598 # Real full test
19599 # = 'host1=test1.lamiral.info&user1=test1&password1=secret1&host2=test2.lamiral.info&user2=test2&password2=secret2&debugenv=on'
19600 my $mysync ;
19601 is( undef, get_options( $mysync ), 'get_options cgi context: no CGI module => undef' ) ;
19602
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019603 # skip all next tests if the CGI module is not available
19604
19605 SKIP: {
19606 if ( ! eval { require CGI ; } ) {
19607 skip( "CGI Perl module is not installed", 19 ) ;
19608 }
19609
19610 CGI->import( qw( -no_debug -utf8 ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019611
19612 is( undef, get_options( $mysync ), 'get_options cgi context: no CGI param => undef' ) ;
19613 # Testing boolean
19614 $mysync->{cgi} = CGI->new( 'version=on&debugenv=on' ) ;
19615 local $ENV{'QUERY_STRING'} = 'version=on&debugenv=on' ;
19616 is( 22, get_options( $mysync ), 'get_options cgi context: QUERY_STRING => 22' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019617 is( 'on', $mysync->{ version }, 'get_options cgi context: --version => on' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019618 # debugenv is not allowed in cgi context
19619 is( undef, $mysync->{debugenv}, 'get_options cgi context: $mysync->{debugenv} => undef' ) ;
19620
19621 # QUERY_STRING in this test is only for return value of get_options
19622 # Have to think harder, GET/POST context, is this return value a good thing?
19623 local $ENV{'QUERY_STRING'} = 'host1=test1.lamiral.info&user1=test1' ;
19624 $mysync->{cgi} = CGI->new( 'host1=test1.lamiral.info&user1=test1' ) ;
19625 is( 36, get_options( $mysync, ), 'get_options cgi context: QUERY_STRING => 36' ) ;
19626 is( 'test1', $mysync->{user1}, 'get_options cgi context: $mysync->{user1} => test1' ) ;
19627 #local $ENV{'QUERY_STRING'} = undef ;
19628
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019629 # Testing s@ as ref
19630 $mysync->{cgi} = CGI->new( 'folder=fd1' ) ;
19631 get_options( $mysync ) ;
19632 is_deeply( [ 'fd1' ], $mysync->{ folder }, 'get_options cgi context: $mysync->{ folder } => fd1' ) ;
19633 $mysync->{cgi} = CGI->new( 'folder=fd1&folder=fd2' ) ;
19634 get_options( $mysync ) ;
19635 is_deeply( [ 'fd1', 'fd2' ], $mysync->{ folder }, 'get_options cgi context: $mysync->{ folder } => fd1, fd2' ) ;
19636
19637 # Testing %
19638 $mysync->{cgi} = CGI->new( 'f1f2h=s1=d1&f1f2h=s2=d2&f1f2h=s3=d3' ) ;
19639 get_options( $mysync ) ;
19640
19641 is_deeply( { 's1' => 'd1', 's2' => 'd2', 's3' => 'd3' },
19642 $mysync->{f1f2h}, 'get_options cgi context: f1f2h => s1=d1 s2=d2 s3=d3' ) ;
19643
19644 # Testing boolean ! with --noxxx, doesnot work
19645 $mysync->{cgi} = CGI->new( 'nodry=on' ) ;
19646 get_options( $mysync ) ;
19647 is( undef, $mysync->{dry}, 'get_options cgi context: --nodry => $mysync->{dry} => undef' ) ;
19648
19649 $mysync->{cgi} = CGI->new( 'host1=example.com' ) ;
19650 get_options( $mysync ) ;
19651 is( 'example.com', $mysync->{host1}, 'get_options cgi context: --host1=example.com => $mysync->{host1} => example.com' ) ;
19652
19653 #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
19654 $mysync->{cgi} = CGI->new( 'simulong=' ) ;
19655 get_options( $mysync ) ;
19656 is( undef, $mysync->{simulong}, 'get_options cgi context: --simulong= => $mysync->{simulong} => undef' ) ;
19657
19658 $mysync->{cgi} = CGI->new( 'simulong' ) ;
19659 get_options( $mysync ) ;
19660 is( undef, $mysync->{simulong}, 'get_options cgi context: --simulong => $mysync->{simulong} => undef' ) ;
19661
19662 $mysync->{cgi} = CGI->new( 'simulong=4' ) ;
19663 get_options( $mysync ) ;
19664 is( 4, $mysync->{simulong}, 'get_options cgi context: --simulong=4 => $mysync->{simulong} => 4' ) ;
19665 is( undef, $mysync->{ folder }, 'get_options cgi context: --simulong=4 => $mysync->{ folder } => undef' ) ;
19666 #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
19667
19668 $mysync ={} ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019669 $mysync->{cgi} = CGI->new( 'testslive=on' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019670 get_options( $mysync ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019671 is( 'on', $mysync->{ testslive }, 'get_options cgi context: --testslive=on => testslive => on' ) ;
19672 #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
19673
19674 $mysync ={} ;
19675 $mysync->{cgi} = CGI->new( 'log=0' ) ;
19676 get_options( $mysync ) ;
19677 is( 0, $mysync->{ log }, 'get_options cgi context: --log=0 => log => 0' ) ;
19678 #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
19679
19680
19681 # What is this fucked up indentation?
19682 }
19683
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019684
19685 note( 'Leaving tests_get_options_cgi_context()' ) ;
19686 return ;
19687}
19688
19689
19690
19691sub get_options_cgi
19692{
19693 # In CGI context arguments are not in @ARGV but in QUERY_STRING variable (with GET).
19694 my $mysync = shift @ARG ;
19695 $mysync->{cgi} || return ;
19696 my @arguments = @ARG ;
19697 # final 0 is used to print usage when no option is given
19698 my $numopt = length $ENV{'QUERY_STRING'} || 1 ;
19699 $mysync->{f1f2h} = {} ;
19700 my $opt_ret = myGetOptions(
19701 $mysync,
19702 \@arguments,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019703 'abort' => \$mysync->{ abort },
19704 'abortbyfile' => \$mysync->{ abortbyfile },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019705 'host1=s' => \$mysync->{ host1 },
19706 'host2=s' => \$mysync->{ host2 },
19707 'user1=s' => \$mysync->{ user1 },
19708 'user2=s' => \$mysync->{ user2 },
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019709 'password1=s' => \$mysync->{ password1 },
19710 'password2=s' => \$mysync->{ password2 },
19711 'dry!' => \$mysync->{ dry },
19712 'dry1!' => \$mysync->{ dry1 },
19713 'version' => \$mysync->{ version },
19714 'ssl1!' => \$mysync->{ ssl1 },
19715 'ssl2!' => \$mysync->{ ssl2 },
19716 'tls1!' => \$mysync->{ tls1 },
19717 'tls2!' => \$mysync->{ tls2 },
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010019718 'compress1!' => \$mysync->{ acc1 }->{ compress },
19719 'compress2!' => \$mysync->{ acc2 }->{ compress },
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019720 'justbanner!' => \$mysync->{ justbanner },
19721 'justlogin!' => \$mysync->{ justlogin },
19722 'justconnect!' => \$mysync->{ justconnect },
19723 'addheader!' => \$mysync->{ addheader },
19724 'automap!' => \$mysync->{ automap },
19725 'justautomap!' => \$mysync->{ justautomap },
19726 'gmail1' => \$mysync->{ gmail1 },
19727 'gmail2' => \$mysync->{ gmail2 },
19728 'office1' => \$mysync->{ office1 },
19729 'office2' => \$mysync->{ office2 },
19730 'exchange1' => \$mysync->{ exchange1 },
19731 'exchange2' => \$mysync->{ exchange2 },
19732 'domino1' => \$mysync->{ domino1 },
19733 'domino2' => \$mysync->{ domino2 },
19734 'f1f2=s@' => \$mysync->{ f1f2 },
19735 'f1f2h=s%' => \$mysync->{ f1f2h },
19736 'folder=s@' => \$mysync->{ folder },
19737 'testslive!' => \$mysync->{ testslive },
19738 'testslive6!' => \$mysync->{ testslive6 },
19739 'releasecheck!' => \$mysync->{ releasecheck },
19740 'simulong=f' => \$mysync->{ simulong },
19741 'debugsleep=f' => \$mysync->{ debugsleep },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019742 'subfolder1=s' => \$mysync->{ subfolder1 },
19743 'subfolder2=s' => \$mysync->{ subfolder2 },
19744 'justfolders!' => \$mysync->{ justfolders },
19745 'justfoldersizes!' => \$mysync->{ justfoldersizes },
19746 'delete1!' => \$mysync->{ delete1 },
19747 'delete2!' => \$mysync->{ delete2 },
19748 'delete2duplicates!' => \$mysync->{ delete2duplicates },
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019749 'tail!' => \$mysync->{ tail },
19750 'tmphash=s' => \$mysync->{ tmphash },
19751 'exitwhenover=i' => \$mysync->{ exitwhenover },
19752 'syncduplicates!' => \$mysync->{ syncduplicates },
19753 'log!' => \$mysync->{ log },
19754 'loglogfile!' => \$mysync->{ loglogfile },
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010019755
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019756
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019757# f1f2h=s% could be removed but
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019758# tests_get_options_cgi() should be split before
19759# with a sub tests_myGetOptions()
19760 ) ;
19761
19762 $mysync->{ debug } and output( $mysync, "get options: [$opt_ret][$numopt]\n" ) ;
19763
19764 if ( ! $opt_ret ) {
19765 return ;
19766 }
19767 return $numopt ;
19768}
19769
19770sub get_options_cmd
19771{
19772 my $mysync = shift @ARG ;
19773 my @arguments = @ARG ;
19774 my $mycgi = $mysync->{cgi} ;
19775 # final 0 is used to print usage when no option is given on command line
19776 my $numopt = scalar @arguments || 0 ;
19777 my $argv = join "\x00", @arguments ;
19778
19779 if ( $argv =~ m/-delete\x002/x ) {
19780 output( $mysync, "May be you mean --delete2 instead of --delete 2\n" ) ;
19781 return ;
19782 }
19783 $mysync->{f1f2h} = {} ;
19784 my $opt_ret = myGetOptions(
19785 $mysync,
19786 \@arguments,
19787 'debug!' => \$mysync->{ debug },
19788 'debuglist!' => \$debuglist,
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010019789 'debugcontent!' => \$mysync->{ debugcontent },
19790 'debugsleep=f' => \$mysync->{ debugsleep },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019791 'debugflags!' => \$debugflags,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019792 'debugimap!' => \$mysync->{ debugimap },
19793 'debugimap1!' => \$mysync->{ acc1 }->{ debugimap },
19794 'debugimap2!' => \$mysync->{ acc2 }->{ debugimap },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019795 'debugdev!' => \$debugdev,
19796 'debugmemory!' => \$mysync->{debugmemory},
19797 'debugfolders!' => \$mysync->{debugfolders},
19798 'debugssl=i' => \$mysync->{debugssl},
19799 'debugcgi!' => \$debugcgi,
19800 'debugenv!' => \$mysync->{debugenv},
19801 'debugsig!' => \$mysync->{debugsig},
19802 'debuglabels!' => \$mysync->{debuglabels},
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019803
19804 'simulong=f' => \$mysync->{simulong},
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019805 'abort' => \$mysync->{abort},
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019806 'abortbyfile' => \$mysync->{abortbyfile},
19807
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019808 'host1=s' => \$mysync->{ host1 },
19809 'host2=s' => \$mysync->{ host2 },
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010019810 'port1=i' => \$mysync->{ port1 },
19811 'port2=i' => \$mysync->{ port2 },
19812 'inet4|ipv4' => \$mysync->{ inet4 },
19813 'inet6|ipv6' => \$mysync->{ inet6 },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019814 'user1=s' => \$mysync->{ user1 },
19815 'user2=s' => \$mysync->{ user2 },
19816 'gmail1' => \$mysync->{gmail1},
19817 'gmail2' => \$mysync->{gmail2},
19818 'office1' => \$mysync->{office1},
19819 'office2' => \$mysync->{office2},
19820 'exchange1' => \$mysync->{exchange1},
19821 'exchange2' => \$mysync->{exchange2},
19822 'domino1' => \$mysync->{domino1},
19823 'domino2' => \$mysync->{domino2},
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019824 'domain1=s' => \$mysync->{ acc1 }->{ domain },
19825 'domain2=s' => \$mysync->{ acc2 }->{ domain },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019826 'password1=s' => \$mysync->{password1},
19827 'password2=s' => \$mysync->{password2},
19828 'passfile1=s' => \$mysync->{ passfile1 },
19829 'passfile2=s' => \$mysync->{ passfile2 },
19830 'authmd5!' => \$authmd5,
19831 'authmd51!' => \$authmd51,
19832 'authmd52!' => \$authmd52,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019833
19834 'trylogin!' => \$mysync->{ trylogin },
19835
19836 'oauthdirect1=s' => \$mysync->{ acc1 }->{ oauthdirect },
19837 'oauthdirect2=s' => \$mysync->{ acc2 }->{ oauthdirect },
19838
19839 'oauthaccesstoken1=s' => \$mysync->{ acc1 }->{ oauthaccesstoken },
19840 'oauthaccesstoken2=s' => \$mysync->{ acc2 }->{ oauthaccesstoken },
19841
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019842 'sep1=s' => \$mysync->{ sep1 },
19843 'sep2=s' => \$mysync->{ sep2 },
19844 'sanitize!' => \$mysync->{ sanitize },
19845 'folder=s@' => \$mysync->{ folder },
19846 'folderrec=s' => \@folderrec,
19847 'include=s' => \@include,
19848 'exclude=s' => \@exclude,
19849 'noexclude' => \$mysync->{noexclude},
19850 'folderfirst=s' => \@folderfirst,
19851 'folderlast=s' => \@folderlast,
19852 'prefix1=s' => \$prefix1,
19853 'prefix2=s' => \$prefix2,
19854 'subfolder1=s' => \$mysync->{ subfolder1 },
19855 'subfolder2=s' => \$mysync->{ subfolder2 },
19856 'fixslash2!' => \$mysync->{ fixslash2 },
19857 'fixInboxINBOX!' => \$fixInboxINBOX,
19858 'regextrans2=s@' => \$mysync->{ regextrans2 },
19859 'mixfolders!' => \$mixfolders,
19860 'skipemptyfolders!' => \$mysync->{ skipemptyfolders },
19861 'regexmess=s' => \@regexmess,
19862 'noregexmess' => \$mysync->{noregexmess},
19863 'skipmess=s' => \@skipmess,
19864 'pipemess=s' => \@pipemess,
19865 'pipemesscheck!' => \$pipemesscheck,
19866 'disarmreadreceipts!' => \$disarmreadreceipts,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019867 'regexflag=s@' => \$mysync->{ regexflag },
19868 'noregexflag' => \$mysync->{ noregexflag },
19869 'filterflags!' => \$mysync->{ filterflags },
19870 'filterbuggyflags!' => \$mysync->{ filterbuggyflags },
19871 'flagscase!' => \$mysync->{ flagscase },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019872 'syncflagsaftercopy!' => \$syncflagsaftercopy,
19873 'resyncflags!' => \$mysync->{ resyncflags },
19874 'synclabels!' => \$mysync->{ synclabels },
19875 'resynclabels!' => \$mysync->{ resynclabels },
19876 'delete|delete1!' => \$mysync->{ delete1 },
19877 'delete2!' => \$mysync->{ delete2 },
19878 'delete2duplicates!' => \$mysync->{ delete2duplicates },
19879 'delete2folders!' => \$delete2folders,
19880 'delete2foldersonly=s' => \$delete2foldersonly,
19881 'delete2foldersbutnot=s' => \$delete2foldersbutnot,
19882 'syncinternaldates!' => \$syncinternaldates,
19883 'idatefromheader!' => \$idatefromheader,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019884 'syncacls!' => \$mysync->{ syncacls },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019885 'maxsize=i' => \$mysync->{ maxsize },
19886 'appendlimit=i' => \$mysync->{ appendlimit },
19887 'truncmess=i' => \$mysync->{ truncmess },
19888 'minsize=i' => \$minsize,
19889 'maxage=f' => \$maxage,
19890 'minage=f' => \$minage,
19891 'search=s' => \$search,
19892 'search1=s' => \$mysync->{ search1 },
19893 'search2=s' => \$mysync->{ search2 },
19894 'foldersizes!' => \$mysync->{ foldersizes },
19895 'foldersizesatend!' => \$mysync->{ foldersizesatend },
19896 'dry!' => \$mysync->{dry},
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019897 'dry1!' => \$mysync->{dry1},
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019898 'expunge1|expunge!' => \$mysync->{ expunge1 },
19899 'expunge2!' => \$mysync->{ expunge2 },
19900 'uidexpunge2!' => \$mysync->{ uidexpunge2 },
19901 'subscribed' => \$subscribed,
19902 'subscribe!' => \$subscribe,
19903 'subscribeall|subscribe_all!' => \$subscribeall,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019904 'justbanner!' => \$mysync->{ justbanner },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019905 'justfolders!'=> \$mysync->{ justfolders },
19906 'justfoldersizes!' => \$mysync->{ justfoldersizes },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019907 'version' => \$mysync->{version},
19908 'help' => \$help,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019909 'timeout=f' => \$mysync->{timeout},
19910 'timeout1=f' => \$mysync->{ acc1 }->{timeout},
19911 'timeout2=f' => \$mysync->{ acc2 }->{timeout},
19912 'skipheader=s' => \$mysync->{ skipheader },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019913 'useheader=s' => \@useheader,
19914 'wholeheaderifneeded!' => \$wholeheaderifneeded,
19915 'messageidnodomain!' => \$messageidnodomain,
19916 'skipsize!' => \$skipsize,
19917 'allowsizemismatch!' => \$allowsizemismatch,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019918 'fastio1!' => \$mysync->{ acc1 }->{ fastio },
19919 'fastio2!' => \$mysync->{ acc2 }->{ fastio },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019920 'sslcheck!' => \$mysync->{sslcheck},
19921 'ssl1!' => \$mysync->{ssl1},
19922 'ssl2!' => \$mysync->{ssl2},
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019923 'ssl1_ssl_version=s' => \$mysync->{ acc1 }->{sslargs}->{SSL_version},
19924 'ssl2_ssl_version=s' => \$mysync->{ acc2 }->{sslargs}->{SSL_version},
19925 'sslargs1=s%' => \$mysync->{ acc1 }->{sslargs},
19926 'sslargs2=s%' => \$mysync->{ acc2 }->{sslargs},
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019927 'tls1!' => \$mysync->{tls1},
19928 'tls2!' => \$mysync->{tls2},
19929 'uid1!' => \$uid1,
19930 'uid2!' => \$uid2,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019931 'authmech1=s' => \$mysync->{ acc1 }->{ authmech },
19932 'authmech2=s' => \$mysync->{ acc2 }->{ authmech },
19933 'authuser1=s' => \$mysync->{ acc1 }->{ authuser },
19934 'authuser2=s' => \$mysync->{ acc2 }->{ authuser },
19935 'proxyauth1' => \$mysync->{ acc1 }->{ proxyauth },
19936 'proxyauth2' => \$mysync->{ acc2 }->{ proxyauth },
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010019937 'compress1!' => \$mysync->{ acc1 }->{ compress },
19938 'compress2!' => \$mysync->{ acc2 }->{ compress },
19939 'keepalive1!' => \$mysync->{ acc1 }->{ keepalive },
19940 'keepalive2!' => \$mysync->{ acc2 }->{ keepalive },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019941 'split1=i' => \$split1,
19942 'split2=i' => \$split2,
19943 'buffersize=i' => \$buffersize,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019944 'reconnectretry1=i' => \$mysync->{ acc1 }->{ reconnectretry },
19945 'reconnectretry2=i' => \$mysync->{ acc2 }->{ reconnectretry },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019946 'tests!' => \$mysync->{ tests },
19947 'testsdebug|tests_debug!' => \$mysync->{ testsdebug },
19948 'testsunit=s@' => \$mysync->{testsunit},
19949 'testslive!' => \$mysync->{testslive},
19950 'testslive6!' => \$mysync->{testslive6},
19951 'justlogin!' => \$mysync->{justlogin},
19952 'justconnect!' => \$mysync->{justconnect},
19953 'tmpdir=s' => \$mysync->{ tmpdir },
19954 'pidfile=s' => \$mysync->{pidfile},
19955 'pidfilelocking!' => \$mysync->{pidfilelocking},
19956 'sigexit=s@' => \$mysync->{ sigexit },
19957 'sigreconnect=s@' => \$mysync->{ sigreconnect },
19958 'sigignore=s@' => \$mysync->{ sigignore },
19959 'releasecheck!' => \$mysync->{releasecheck},
19960 'modulesversion|modules_version!' => \$modulesversion,
19961 'usecache!' => \$usecache,
19962 'cacheaftercopy!' => \$cacheaftercopy,
19963 'debugcache!' => \$debugcache,
19964 'useuid!' => \$useuid,
19965 'addheader!' => \$mysync->{addheader},
19966 'exitwhenover=i' => \$mysync->{ exitwhenover },
19967 'checkselectable!' => \$mysync->{ checkselectable },
19968 'checkfoldersexist!' => \$mysync->{ checkfoldersexist },
19969 'checkmessageexists!' => \$checkmessageexists,
19970 'expungeaftereach!' => \$mysync->{ expungeaftereach },
19971 'abletosearch!' => \$mysync->{abletosearch},
19972 'abletosearch1!' => \$mysync->{abletosearch1},
19973 'abletosearch2!' => \$mysync->{abletosearch2},
19974 'showpasswords!' => \$mysync->{showpasswords},
19975 'maxlinelength=i' => \$maxlinelength,
19976 'maxlinelengthcmd=s' => \$maxlinelengthcmd,
19977 'minmaxlinelength=i' => \$minmaxlinelength,
19978 'debugmaxlinelength!' => \$debugmaxlinelength,
19979 'fixcolonbug!' => \$fixcolonbug,
19980 'create_folder_old!' => \$create_folder_old,
19981 'maxmessagespersecond=f' => \$mysync->{maxmessagespersecond},
19982 'maxbytespersecond=i' => \$mysync->{maxbytespersecond},
19983 'maxbytesafter=i' => \$mysync->{maxbytesafter},
19984 'maxsleep=f' => \$mysync->{maxsleep},
19985 'skipcrossduplicates!' => \$skipcrossduplicates,
19986 'debugcrossduplicates!' => \$debugcrossduplicates,
19987 'log!' => \$mysync->{log},
19988 'tail!' => \$mysync->{tail},
19989 'logfile=s' => \$mysync->{logfile},
19990 'logdir=s' => \$mysync->{logdir},
19991 'errorsmax=i' => \$mysync->{errorsmax},
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010019992 'errorsdump!' => \$mysync->{ errorsdump },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019993 'fetch_hash_set=s' => \$fetch_hash_set,
19994 'automap!' => \$mysync->{automap},
19995 'justautomap!' => \$mysync->{justautomap},
19996 'id!' => \$mysync->{id},
19997 'f1f2=s@' => \$mysync->{f1f2},
19998 'nof1f2' => \$mysync->{nof1f2},
19999 'f1f2h=s%' => \$mysync->{f1f2h},
20000 'justfolderlists!' => \$mysync->{justfolderlists},
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020001 'delete1emptyfolders' => \$mysync->{delete1emptyfolders},
20002 'checknoabletosearch!' => \$mysync->{checknoabletosearch},
20003 'syncduplicates!' => \$mysync->{ syncduplicates },
20004 'dockercontext!' => \$mysync->{ dockercontext },
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010020005
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020006
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010020007 ) ;
20008 #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
20009 $mysync->{ debug } and output( $mysync, "get options: [$opt_ret][$numopt]\n" ) ;
20010 my $numopt_after = scalar @arguments ;
20011 #myprint( "get options: [$opt_ret][$numopt][$numopt_after]\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020012
20013 # The $arguments[0] test is just because parallel adds "" when it is
20014 # used with {=7=} in sync_parallel_unix.sh
20015 if ( $numopt_after and $arguments[0] ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010020016 myprint(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020017 "Found ", scalar( @arguments ), " extra arguments : [@arguments]\n",
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010020018 "It usually means a quoting issue in the command line ",
20019 "or some misspelling options.\n",
20020 ) ;
20021 return ;
20022 }
20023 if ( ! $opt_ret ) {
20024 return ;
20025 }
20026 return $numopt ;
20027}
20028
20029
20030
20031sub tests_get_options
20032{
20033 note( 'Entering tests_get_options()' ) ;
20034
20035 # CAVEAT: still setting global variables, be careful
20036 # with tests, the context increases! $debug stays on for example.
20037 # API:
20038 # * input arguments: two ways, command line or CGI
20039 # * the program arguments
20040 # * QUERY_STRING env variable
20041 # * return
20042 # * undef if bad things happened like
20043 # * options not known
20044 # * --delete 2 input
20045 # * number of arguments or QUERY_STRING length
20046 my $mysync = { } ;
20047 is( undef, get_options( $mysync, qw( --noexist ) ), 'get_options: --noexist => undef' ) ;
20048 is( undef, $mysync->{ noexist }, 'get_options: --noexist => undef' ) ;
20049 $mysync = { } ;
20050 is( undef, get_options( $mysync, qw( --lalala --noexist --version ) ), 'get_options: --lalala --noexist --version => undef' ) ;
20051 is( 1, $mysync->{ version }, 'get_options: --version => 1' ) ;
20052 is( undef, $mysync->{ noexist }, 'get_options: --noexist => undef' ) ;
20053 $mysync = { } ;
20054 is( 1, get_options( $mysync, qw( --delete2 ) ), 'get_options: --delete2 => 1' ) ;
20055 is( 1, $mysync->{ delete2 }, 'get_options: --delete2 => var delete2 = 1' ) ;
20056 $mysync = { } ;
20057 is( undef, get_options( $mysync, qw( --delete 2 ) ), 'get_options: --delete 2 => var undef' ) ;
20058 is( undef, $mysync->{ delete1 }, 'get_options: --delete 2 => var still undef ; good!' ) ;
20059 $mysync = { } ;
20060 is( undef, get_options( $mysync, "--delete 2" ), 'get_options: --delete 2 => undef' ) ;
20061
20062 is( 1, get_options( $mysync, "--version" ), 'get_options: --version => 1' ) ;
20063 is( 1, get_options( $mysync, "--help" ), 'get_options: --help => 1' ) ;
20064
20065 is( undef, get_options( $mysync, qw( --noexist --version ) ), 'get_options: --debug --noexist --version => undef' ) ;
20066 is( 1, get_options( $mysync, qw( --version ) ), 'get_options: --version => 1' ) ;
20067 is( undef, get_options( $mysync, qw( extra ) ), 'get_options: extra => undef' ) ;
20068 is( undef, get_options( $mysync, qw( extra1 --version extra2 ) ), 'get_options: extra1 --version extra2 => undef' ) ;
20069
20070 $mysync = { } ;
20071 is( 2, get_options( $mysync, qw( --host1 HOST_01) ), 'get_options: --host1 HOST_01 => 1' ) ;
20072 is( 'HOST_01', $mysync->{ host1 }, 'get_options: --host1 HOST_01 => HOST_01' ) ;
20073 #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
20074
20075 note( 'Leaving tests_get_options()' ) ;
20076 return ;
20077}
20078
20079
20080
20081sub get_options
20082{
20083 my $mysync = shift @ARG ;
20084 my @arguments = @ARG ;
20085 #myprint( "1 mysync: ", Data::Dumper->Dump( [ $mysync ] ) ) ;
20086 my $ret ;
20087 if ( under_cgi_context( ) ) {
20088 # CGI context
20089 $ret = get_options_cgi( $mysync, @arguments ) ;
20090 }else{
20091 # Command line context ;
20092 $ret = get_options_cmd( $mysync, @arguments ) ;
20093 } ;
20094 #myprint( "2 mysync: ", Data::Dumper->Dump( [ $mysync ] ) ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010020095
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010020096 foreach my $key ( sort keys %{ $mysync } ) {
20097 if ( ! defined $mysync->{$key} ) {
20098 delete $mysync->{$key} ;
20099 next ;
20100 }
20101 if ( 'ARRAY' eq ref( $mysync->{$key} )
20102 and 0 == scalar( @{ $mysync->{$key} } ) ) {
20103 delete $mysync->{$key} ;
20104 }
20105 }
20106 return $ret ;
20107}
20108
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020109
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010020110sub tests_infos
20111{
20112 note( 'Entering tests_infos()' ) ;
20113 note( "OSNAME=$OSNAME" ) ;
20114 note( "hostname=". hostname() ) ;
20115 note( "cwd=" . getcwd( ) ) ;
20116 note( "PROGRAM_NAME=$PROGRAM_NAME" ) ;
20117 my $stat = stat("$PROGRAM_NAME") ;
20118 my $perms = sprintf( "%04o\n", $stat->mode & oct($PERMISSION_FILTER) ) ;
20119 note( "permissions=$perms" ) ;
20120 note( "PROCESS_ID=$PROCESS_ID" ) ;
20121 note( "REAL_USER_ID=$REAL_USER_ID" ) ;
20122 note( "EFFECTIVE_USER_ID=$EFFECTIVE_USER_ID" ) ;
20123 note( "context: " . imapsync_context( $sync ) ) ;
20124 note( "memory_consumption: " . memory_consumption() . " bytes aka " . bytes_display_string_dec( memory_consumption() ) ) ;
20125 cpu_number
20126 note( "cpu_number: " . cpu_number() ) ;
20127 note( $sync->{rcs} ) ;
20128
20129 note( 'Leaving tests_infos()' ) ;
20130 return ;
20131}
20132
20133
20134
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020135sub condition_to_leave_after_tests
20136{
20137 my $mysync = shift ;
20138 if ( $mysync->{ testslive } or $mysync->{ testslive6 } )
20139 {
20140 return 0 ;
20141 }
20142
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010020143 if ( $mysync->{ tests }
20144 or $mysync->{ testsdebug }
20145 or $mysync->{ testsunit }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020146 )
20147 {
20148 return 1 ;
20149 }
20150}
20151
20152
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010020153sub testunitsession
20154{
20155 my $mysync = shift ;
20156
20157 if ( ! $mysync ) { return ; }
20158 if ( ! $mysync->{ testsunit } ) { return ; }
20159
20160 my @functions = @{ $mysync->{ testsunit } } ;
20161
20162 if ( ! @functions ) { return ; }
20163
20164 SKIP: {
20165 if ( ! @functions ) { skip 'No test in normal run' ; }
20166 testsunit( @functions ) ;
20167 done_testing( ) ;
20168 }
20169 return ;
20170}
20171
20172sub tests_count_0s
20173{
20174 note( 'Entering tests_count_zeros()' ) ;
20175 is( 0, count_0s( ), 'count_0s: no parameters => 0' ) ;
20176 is( 1, count_0s( 0 ), 'count_0s: 0 => 1' ) ;
20177 is( 0, count_0s( 1 ), 'count_0s: 1 => 0' ) ;
20178 is( 1, count_0s( 1, 0, 1 ), 'count_0s: 1, 0, 1 => 1' ) ;
20179 is( 2, count_0s( 1, 0, 1, 0 ), 'count_0s: 1, 0, 1, 0 => 2' ) ;
20180 note( 'Leaving tests_count_zeros()' ) ;
20181 return ;
20182}
20183sub count_0s
20184{
20185 my @array = @ARG ;
20186
20187 if ( ! @array ) { return 0 ; }
20188 my $nb_zeros = 0 ;
20189 map { $_ == 0 and $nb_zeros += 1 } @array ;
20190 return $nb_zeros ;
20191}
20192
20193sub tests_report_failures
20194{
20195 note( 'Entering tests_report_failures()' ) ;
20196
20197 is( undef, report_failures( ), 'report_failures: no parameters => undef' ) ;
20198 is( "nb 1 - first\n", report_failures( ({'ok' => 0, name => 'first'}) ), 'report_failures: "first" failed => nb 1 - first' ) ;
20199 is( q{}, report_failures( ( {'ok' => 1, name => 'first'} ) ), 'report_failures: "first" success =>' ) ;
20200 is( "nb 2 - second\n", report_failures( ( {'ok' => 1, name => 'second'}, {'ok' => 0, name => 'second'} ) ), 'report_failures: "second" failed => nb 2 - second' ) ;
20201 is( "nb 1 - first\nnb 2 - second\n", report_failures( ( {'ok' => 0, name => 'first'}, {'ok' => 0, name => 'second'} ) ), 'report_failures: both failed => nb 1 - first nb 2 - second' ) ;
20202 note( 'Leaving tests_report_failures()' ) ;
20203 return ;
20204}
20205
20206sub report_failures
20207{
20208 my @details = @ARG ;
20209
20210 if ( ! @details ) { return ; }
20211
20212 my $counter = 1 ;
20213 my $report = q{} ;
20214 foreach my $details ( @details ) {
20215 if ( ! $details->{ 'ok' } ) {
20216 my $name = $details->{ 'name' } || 'NONAME' ;
20217 $report .= "nb $counter - $name\n" ;
20218 }
20219 $counter += 1 ;
20220 }
20221 return $report ;
20222
20223}
20224
20225sub tests_true
20226{
20227 note( 'Entering tests_true()' ) ;
20228
20229 is( 1, 1, 'true: 1 is 1' ) ;
20230 note( 'Leaving tests_true()' ) ;
20231 return ;
20232}
20233
20234sub tests_testsunit
20235{
20236 note( 'Entering tests_testunit()' ) ;
20237
20238 is( undef, testsunit( ), 'testsunit: no parameters => undef' ) ;
20239 is( undef, testsunit( undef ), 'testsunit: an undef parameter => undef' ) ;
20240 is( undef, testsunit( q{} ), 'testsunit: an empty parameter => undef' ) ;
20241 is( undef, testsunit( 'idonotexist' ), 'testsunit: a do not exist function as parameter => undef' ) ;
20242 is( undef, testsunit( 'tests_true' ), 'testsunit: tests_true => undef' ) ;
20243 note( 'Leaving tests_testunit()' ) ;
20244 return ;
20245}
20246
20247sub testsunit
20248{
20249 my @functions = @ARG ;
20250
20251 if ( ! @functions ) { #
20252 myprint( "testsunit warning: no argument given\n" ) ;
20253 return ;
20254 }
20255
20256 foreach my $function ( @functions ) {
20257 if ( ! $function ) {
20258 myprint( "testsunit warning: argument is empty\n" ) ;
20259 next ;
20260 }
20261 if ( ! exists &$function ) {
20262 myprint( "testsunit warning: function $function does not exist\n" ) ;
20263 next ;
20264 }
20265 if ( ! defined &$function ) {
20266 myprint( "testsunit warning: function $function is not defined\n" ) ;
20267 next ;
20268 }
20269 my $function_ref = \&{ $function } ;
20270 &$function_ref() ;
20271 }
20272 return ;
20273}
20274
20275sub testsdebug
20276{
20277 # Now a little obsolete since there is
20278 # imapsync ... --testsunit "anyfunction"
20279 my $mysync = shift ;
20280 if ( ! $mysync->{ testsdebug } ) { return ; }
20281 SKIP: {
20282 if ( ! $mysync->{ testsdebug } ) {
20283 skip 'No test in normal run' ;
20284 }
20285
20286 note( 'Entering testsdebug()' ) ;
20287 #ok( ( ( not -d 'W/tmp/tests' ) or rmtree( 'W/tmp/tests/' ) ), 'testsdebug: rmtree W/tmp/tests' ) ;
20288 #tests_check_binary_embed_all_dyn_libs( ) ;
20289 #tests_killpid_by_parent( ) ;
20290 #tests_killpid_by_brother( ) ;
20291 #tests_kill_zero( ) ;
20292 #tests_connect_socket( ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020293 #tests_probe_imapssl( ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010020294 #tests_cpu_number( ) ;
20295 #tests_mailimapclient_connect( ) ;
20296 tests_loadavg( ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010020297 #tests_always_fail( ) ;
20298
20299 note( 'Leaving testsdebug()' ) ;
20300 done_testing( ) ;
20301 }
20302 return ;
20303}
20304
20305
20306sub tests
20307{
20308 my $mysync = shift ;
20309 if ( ! $mysync->{ tests } ) { return ; }
20310
20311 SKIP: {
20312 skip 'No test in normal run' if ( ! $mysync->{ tests } ) ;
20313 note( 'Entering tests()' ) ;
20314 tests_folder_routines( ) ;
20315 tests_compare_lists( ) ;
20316 tests_regexmess( ) ;
20317 tests_skipmess( ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020318 tests_regexflags( );
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010020319 tests_ucsecond( ) ;
20320 tests_permanentflags();
20321 tests_flags_filter( ) ;
20322 tests_separator_invert( ) ;
20323 tests_imap2_folder_name( ) ;
20324 tests_command_line_nopassword( ) ;
20325 tests_good_date( ) ;
20326 tests_max( ) ;
20327 tests_remove_not_num();
20328 tests_memory_consumption( ) ;
20329 tests_is_a_release_number();
20330 tests_imapsync_basename();
20331 tests_list_keys_in_2_not_in_1();
20332 tests_convert_sep_to_slash( ) ;
20333 tests_match_a_cache_file( ) ;
20334 tests_cache_map( ) ;
20335 tests_get_cache( ) ;
20336 tests_clean_cache( ) ;
20337 tests_clean_cache_2( ) ;
20338 tests_touch( ) ;
20339 tests_flagscase( ) ;
20340 tests_mkpath( ) ;
20341 tests_extract_header( ) ;
20342 tests_decompose_header( ) ;
20343 tests_epoch( ) ;
20344 tests_add_header( ) ;
20345 tests_cache_dir_fix( ) ;
20346 tests_cache_dir_fix_win( ) ;
20347 tests_filter_forbidden_characters( ) ;
20348 tests_cache_folder( ) ;
20349 tests_time_remaining( ) ;
20350 tests_decompose_regex( ) ;
20351 tests_backtick( ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010020352 tests_bytes_display_string_bin( ) ;
20353 tests_bytes_display_string_dec( ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010020354 tests_header_line_normalize( ) ;
20355 tests_fix_Inbox_INBOX_mapping( ) ;
20356 tests_max_line_length( ) ;
20357 tests_subject( ) ;
20358 tests_msgs_from_maxmin( ) ;
20359 tests_tmpdir_has_colon_bug( ) ;
20360 tests_sleep_max_messages( ) ;
20361 tests_sleep_max_bytes( ) ;
20362 tests_logfile( ) ;
20363 tests_setlogfile( ) ;
20364 tests_jux_utf8_old( ) ;
20365 tests_jux_utf8( ) ;
20366 tests_pipemess( ) ;
20367 tests_jux_utf8_list( ) ;
20368 tests_guess_prefix( ) ;
20369 tests_guess_separator( ) ;
20370 tests_format_for_imap_arg( ) ;
20371 tests_imapsync_id( ) ;
20372 tests_date_from_rcs( ) ;
20373 tests_quota_extract_storage_limit_in_bytes( ) ;
20374 tests_quota_extract_storage_current_in_bytes( ) ;
20375 tests_guess_special( ) ;
20376 tests_do_valid_directory( ) ;
20377 tests_delete1emptyfolders( ) ;
20378 tests_message_for_host2( ) ;
20379 tests_length_ref( ) ;
20380 tests_firstline( ) ;
20381 tests_diff_or_NA( ) ;
20382 tests_match_number( ) ;
20383 tests_all_defined( ) ;
20384 tests_special_from_folders_hash( ) ;
20385 tests_notmatch( ) ;
20386 tests_match( ) ;
20387 tests_get_options( ) ;
20388 tests_get_options_cgi_context( ) ;
20389 tests_rand32( ) ;
20390 tests_hashsynclocal( ) ;
20391 tests_hashsync( ) ;
20392 tests_output( ) ;
20393 tests_output_reset_with( ) ;
20394 tests_output_start( ) ;
20395 tests_check_last_release( ) ;
20396 tests_loadavg( ) ;
20397 tests_cpu_number( ) ;
20398 tests_load_and_delay( ) ;
20399 #tests_imapsping( ) ;
20400 #tests_tcpping( ) ;
20401 tests_sslcheck( ) ;
20402 tests_not_long_imapsync_version_public( ) ;
20403 tests_reconnect_if_needed( ) ;
20404 tests_reconnect_12_if_needed( ) ;
20405 tests_sleep_if_needed( ) ;
20406 tests_string_to_file( ) ;
20407 tests_file_to_string( ) ;
20408 tests_under_cgi_context( ) ;
20409 tests_umask( ) ;
20410 tests_umask_str( ) ;
20411 tests_set_umask( ) ;
20412 tests_createhashfileifneeded( ) ;
20413 tests_slash_to_underscore( ) ;
20414 tests_testsunit( ) ;
20415 tests_count_0s( ) ;
20416 tests_report_failures( ) ;
20417 tests_min( ) ;
20418 #tests_connect_socket( ) ;
20419 #tests_resolvrev( ) ;
20420 tests_usage( ) ;
20421 tests_version_from_rcs( ) ;
20422 tests_backslash_caret( ) ;
20423 #tests_mailimapclient_connect_bug( ) ; # it fails with Mail-IMAPClient <= 3.39
20424 tests_write_pidfile( ) ;
20425 tests_remove_pidfile_not_running( ) ;
20426 tests_match_a_pid_number( ) ;
20427 tests_prefix_seperator_invertion( ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020428 tests_is_integer( ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010020429 tests_integer_or_1( ) ;
20430 tests_is_number( ) ;
20431 tests_sig_install( ) ;
20432 tests_template( ) ;
20433 tests_split_around_equal( ) ;
20434 tests_toggle_sleep( ) ;
20435 tests_labels( ) ;
20436 tests_synclabels( ) ;
20437 tests_uidexpunge_or_expunge( ) ;
20438 tests_appendlimit_from_capability( ) ;
20439 tests_maxsize_setting( ) ;
20440 tests_mock_capability( ) ;
20441 tests_appendlimit( ) ;
20442 tests_capability_of( ) ;
20443 tests_search_in_array( ) ;
20444 tests_operators_and_exclam_precedence( ) ;
20445 tests_teelaunch( ) ;
20446 tests_logfileprepa( ) ;
20447 tests_useheader_suggestion( ) ;
20448 tests_nb_messages_in_2_not_in_1( ) ;
20449 tests_labels_add_subfolder2( ) ;
20450 tests_labels_remove_subfolder1( ) ;
20451 tests_resynclabels( ) ;
20452 tests_labels_remove_special( ) ;
20453 tests_uniq( ) ;
20454 tests_remove_from_requested_folders( ) ;
20455 tests_errors_log( ) ;
20456 tests_add_subfolder1_to_folderrec( ) ;
20457 tests_sanitize_subfolder( ) ;
20458 tests_remove_edging_blanks( ) ;
20459 tests_sanitize( ) ;
20460 tests_remove_last_char_if_is( ) ;
20461 tests_check_binary_embed_all_dyn_libs( ) ;
20462 tests_nthline( ) ;
20463 tests_secondline( ) ;
20464 tests_tail( ) ;
20465 tests_truncmess( ) ;
20466 tests_eta( ) ;
20467 tests_timesince( ) ;
20468 tests_timenext( ) ;
20469 tests_foldersize( ) ;
20470 tests_imapsync_context( ) ;
20471 tests_abort( ) ;
20472 tests_probe_imapssl( ) ;
20473 tests_mailimapclient_connect( ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020474 tests_checknoabletosearch( ) ;
20475 tests_errorsdump( ) ;
20476 tests_errorsanalyse( ) ;
20477 tests_most_common_error( ) ;
20478 tests_errorclassify( ) ;
20479 tests_error_type( ) ;
20480 tests_sanitize_host( ) ;
20481 tests_hmac_sha1_hex( ) ;
20482 tests_total_bytes_max_reached( ) ;
20483 tests_header_construct( ) ;
20484 tests_remove_doublequotes_if_any( ) ;
20485 tests_login_imap( ) ;
20486 tests_login_imap_oauth( ) ;
20487 tests_skipmess_neg( ) ;
20488 tests_localtimez( ) ;
20489 tests_file_to_array( ) ;
20490 tests_cpu_time( ) ;
20491 tests_cpu_percent( ) ;
20492 tests_cpu_percent_global( ) ;
20493 tests_flags_for_host2( ) ;
20494 tests_under_docker_context( ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010020495 tests_exit_value( ) ;
20496 tests_comment_of_error_type( ) ;
20497 tests_debugcontent( ) ;
20498 tests_compress_ssl( ) ;
20499 tests_compress( ) ;
20500 tests_get_options_extra( ) ;
20501 tests_get_options_from_string( ) ;
20502 tests_infos( ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010020503 #tests_resolv( ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020504
20505 # Those three are for later use, when webserver will be inside imapsync
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010020506 # or will be deleted them if I abandon the project.
20507 #tests_killpid_by_parent( ) ;
20508 #tests_killpid_by_brother( ) ;
20509 #tests_kill_zero( ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020510
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010020511 #tests_always_fail( ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010020512 done_testing( 1860 ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010020513 note( 'Leaving tests()' ) ;
20514 }
20515 return ;
20516}
20517
20518sub tests_template
20519{
20520 note( 'Entering tests_template()' ) ;
20521
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020522 is( undef, template( ), 'template: no args => undef' ) ;
20523 my $mysync = { } ;
20524 is( undef, template( $mysync ), 'template: undef => undef' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010020525 is_deeply( {}, {}, 'template: a hash is a hash' ) ;
20526 is_deeply( [], [], 'template: an array is an array' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020527
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010020528 note( 'Leaving tests_template()' ) ;
20529 return ;
20530}
20531
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020532sub template
20533{
20534 my $mysync = shift @ARG ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010020535
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020536 return ;
20537}