blob: 07cf58e7b36810dfc7d031c9ea04e1d8bb6b2e34 [file] [log] [blame]
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001#!/usr/bin/env perl
2
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003# $Id: imapsync,v 2.148 2021/07/22 14:21:09 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 Benkard7b2a3a12021-08-16 10:57:25 +020028This documentation refers to Imapsync $Revision: 2.148 $
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 Benkard7b2a3a12021-08-16 10:57:25 +0200215 --timeout1 flo : Connection timeout in seconds for host1.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100216 Default is 120 and 0 means no timeout at all.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200217 --timeout2 flo : Connection timeout in seconds for host2.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100218 Default is 120 and 0 means no timeout at all.
219
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200220 Caveat, under CGI context, you may encounter a timeout
221 from the webserver, killing imapsync and the imap connexions.
222 See the document INSTALL.OnlineUI.txt and search
223 for "Timeout" for how to deal with this issue.
224
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100225
226=head2 OPTIONS/authentication
227
228 --authmech1 str : Auth mechanism to use with host1:
229 PLAIN, LOGIN, CRAM-MD5 etc. Use UPPERCASE.
230 --authmech2 str : Auth mechanism to use with host2. See --authmech1
231
232 --authuser1 str : User to auth with on host1 (admin user).
233 Avoid using --authmech1 SOMETHING with --authuser1.
234 --authuser2 str : User to auth with on host2 (admin user).
235 --proxyauth1 : Use proxyauth on host1. Requires --authuser1.
236 Required by Sun/iPlanet/Netscape IMAP servers to
237 be able to use an administrative user.
238 --proxyauth2 : Use proxyauth on host2. Requires --authuser2.
239
240 --authmd51 : Use MD5 authentication for host1.
241 --authmd52 : Use MD5 authentication for host2.
242 --domain1 str : Domain on host1 (NTLM authentication).
243 --domain2 str : Domain on host2 (NTLM authentication).
244
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200245 --oauthaccesstoken1 str : The access token to authenticate with OAUTH2.
246 It will be combined with the --user1 value to form the
247 string to pass with XOAUTH2 authentication.
248 The password given by --password1 or --passfile1
249 is ignored.
250 Instead of the access token itself, the value can be a
251 file containing the access token on the first line.
252 If the value is a file, imapsync reads its first line
253 and take this line as the access token. The advantage
254 of the file is that if the access token changes then
255 imapsync can read it again when it needs to reconnect
256 during a run.
257
258
259 --oauthaccesstoken2 str : same thing as --oauthaccesstoken1
260
261 --oauthdirect1 str : The direct string to pass with XOAUTH2 authentication.
262 The password given by --password1 or --passfile1 and
263 the user given by --user1 are ignored.
264
265 --oauthdirect2 str : same thing as oauthdirect1
266
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100267
268=head2 OPTIONS/folders
269
270
271 --folder str : Sync this folder.
272 --folder str : and this one, etc.
273 --folderrec str : Sync this folder recursively.
274 --folderrec str : and this one, etc.
275
276 --folderfirst str : Sync this folder first. Ex. --folderfirst "INBOX"
277 --folderfirst str : then this one, etc.
278 --folderlast str : Sync this folder last. --folderlast "[Gmail]/All Mail"
279 --folderlast str : then this one, etc.
280
281 --nomixfolders : Do not merge folders when host1 is case-sensitive
282 while host2 is not (like Exchange). Only the first
283 similar folder is synced (example: with folders
284 "Sent", "SENT" and "sent"
285 on host1 only "Sent" will be synced to host2).
286
287 --skipemptyfolders : Empty host1 folders are not created on host2.
288
289 --include reg : Sync folders matching this regular expression
290 --include reg : or this one, etc.
291 If both --include --exclude options are used, then
292 include is done before.
293 --exclude reg : Skips folders matching this regular expression
294 Several folders to avoid:
295 --exclude 'fold1|fold2|f3' skips fold1, fold2 and f3.
296 --exclude reg : or this one, etc.
297
298 --automap : guesses folders mapping, for folders well known as
299 "Sent", "Junk", "Drafts", "All", "Archive", "Flagged".
300
301 --f1f2 str1=str2 : Force folder str1 to be synced to str2,
302 --f1f2 overrides --automap and --regextrans2.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200303 Use several --f1f2 options to map several folders.
304 Option --f1f2 is a one to one only folder mapping,
305 str1 and str2 have to be full path folder names.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100306
307 --subfolder2 str : Syncs the whole host1 folders hierarchy under the
308 host2 folder named str.
309 It does it internally by adding three
310 --regextrans2 options before all others.
311 Add --debug to see what's really going on.
312
313 --subfolder1 str : Syncs the host1 folders hierarchy which is under folder
314 str to the root hierarchy of host2.
315 It's the couterpart of a sync done by --subfolder2
316 when doing it in the reverse order.
317 Backup/Restore scenario:
318 Use --subfolder2 str for a backup to the folder str
319 on host2. Then use --subfolder1 str for restoring
320 from the folder str, after inverting
321 host1/host2 user1/user2 values.
322
323
324 --subscribed : Transfers subscribed folders.
325 --subscribe : Subscribe to the folders transferred on the
326 host2 that are subscribed on host1. On by default.
327 --subscribeall : Subscribe to the folders transferred on the
328 host2 even if they are not subscribed on host1.
329
330 --prefix1 str : Remove prefix str to all destination folders,
331 usually "INBOX." or "INBOX/" or an empty string "".
332 imapsync guesses the prefix if host1 imap server
333 does not have NAMESPACE capability. So this option
334 should not be used most of the time.
335 --prefix2 str : Add prefix to all host2 folders. See --prefix1
336
337 --sep1 str : Host1 separator. This option should not be used
338 most of the time.
339 Imapsync gets the separator from the server itself,
340 by using NAMESPACE, or it tries to guess it
341 from the folders listing (it counts
342 characters / . \\ \ in folder names and choose the
343 more frequent, or finally / if nothing is found.
344 --sep2 str : Host2 separator. See --sep1
345
346 --regextrans2 reg : Apply the whole regex to each destination folders.
347 --regextrans2 reg : and this one. etc.
348 When you play with the --regextrans2 option, first
349 add also the safe options --dry --justfolders
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200350 Then, when happy, remove --dry for a run, then
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100351 remove --justfolders for the next ones.
352 Have in mind that --regextrans2 is applied after
353 the automatic prefix and separator inversion.
354 For examples see:
355 https://imapsync.lamiral.info/FAQ.d/FAQ.Folders_Mapping.txt
356
357=head2 OPTIONS/folders sizes
358
359 --nofoldersizes : Do not calculate the size of each folder at the
360 beginning of the sync. Default is to calculate them.
361 --nofoldersizesatend: Do not calculate the size of each folder at the
362 end of the sync. Default is to calculate them.
363 --justfoldersizes : Exit after having printed the initial folder sizes.
364
365
366=head2 OPTIONS/tmp
367
368
369 --tmpdir str : Where to store temporary files and subdirectories.
370 Will be created if it doesn't exist.
371 Default is system specific, Unix is /tmp but
372 /tmp is often too small and deleted at reboot.
373 --tmpdir /var/tmp should be better.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200374
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100375 --pidfile str : The file where imapsync pid is written,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200376 it can be dirname/filename complete path.
377 The default name is imapsync.pid in tmpdir.
378
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100379 --pidfilelocking : Abort if pidfile already exists. Useful to avoid
380 concurrent transfers on the same mailbox.
381
382
383=head2 OPTIONS/log
384
385 --nolog : Turn off logging on file
386 --logfile str : Change the default log filename (can be dirname/filename).
387 --logdir str : Change the default log directory. Default is LOG_imapsync/
388
389The default logfile name is for example
390
391 LOG_imapsync/2019_12_22_23_57_59_532_user1_user2.txt
392
393where:
394
395 2019_12_22_23_57_59_532 is nearly the date of the start
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200396 YYYY_MM_DD_HH_MM_SS_mmm
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100397 year_month_day_hour_minute_seconde_millisecond
398
399and user1 user2 are the --user1 --user2 values.
400
401=head2 OPTIONS/messages
402
403 --skipmess reg : Skips messages matching the regex.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200404 Example: 'm/[\x80-\xff]/' # to avoid 8bits messages.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100405 --skipmess is applied before --regexmess
406 --skipmess reg : or this one, etc.
407
408 --skipcrossduplicates : Avoid copying messages that are already copied
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200409 in another folder, good from Gmail to XYZ when
410 XYZ is not also Gmail.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100411 Activated with --gmail1 unless --noskipcrossduplicates
412
413 --debugcrossduplicates : Prints which messages (UIDs) are skipped with
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200414 --skipcrossduplicates and in what other folders
415 they are.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100416
417 --pipemess cmd : Apply this cmd command to each message content
418 before the copy.
419 --pipemess cmd : and this one, etc.
420 With several --pipemess, the output of each cmd
421 command (STDOUT) is given to the input (STDIN)
422 of the next command.
423 For example,
424 --pipemess cmd1 --pipemess cmd2 --pipemess cmd3
425 is like a Unix pipe:
426 "cat message | cmd1 | cmd2 | cmd3"
427
428 --disarmreadreceipts : Disarms read receipts (host2 Exchange issue)
429
430 --regexmess reg : Apply the whole regex to each message before transfer.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200431 Example: 's/\000/ /g' # to replace null characters
432 by spaces.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100433 --regexmess reg : and this one, etc.
434
435=head2 OPTIONS/labels
436
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200437Gmail present labels as folders in imap. Imapsync can accelerate the sync
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100438by syncing X-GM-LABELS, it will avoid to transfer messages when they are
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200439already on host2 in another folder.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100440
441
442 --synclabels : Syncs also Gmail labels when a message is copied to host2.
443 Activated by default with --gmail1 --gmail2 unless
444 --nosynclabels is added.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200445
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100446 --resynclabels : Resyncs Gmail labels when a message is already on host2.
447 Activated by default with --gmail1 --gmail2 unless
448 --noresynclabels is added.
449
450For Gmail syncs, see also:
451https://imapsync.lamiral.info/FAQ.d/FAQ.Gmail.txt
452
453=head2 OPTIONS/flags
454
455 If you encounter flag problems see also:
456 https://imapsync.lamiral.info/FAQ.d/FAQ.Flags.txt
457
458 --regexflag reg : Apply the whole regex to each flags list.
459 Example: 's/"Junk"//g' # to remove "Junk" flag.
460 --regexflag reg : then this one, etc.
461
462 --resyncflags : Resync flags for already transferred messages.
463 On by default.
464 --noresyncflags : Do not resync flags for already transferred messages.
465 May be useful when a user has already started to play
466 with its host2 account.
467
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200468 --filterbuggyflags : Filter flags known to be buggy and generators of errors
469 "BAD Invalid system flag" or "NO APPEND Invalid flag list".
470
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100471=head2 OPTIONS/deletions
472
473 --delete1 : Deletes messages on host1 server after a successful
474 transfer. Option --delete1 has the following behavior:
475 it marks messages as deleted with the IMAP flag
476 \Deleted, then messages are really deleted with an
477 EXPUNGE IMAP command. If expunging after each message
478 slows down too much the sync then use
479 --noexpungeaftereach to speed up, expunging will then be
480 done only twice per folder, one at the beginning and
481 one at the end of a folder sync.
482
483 --expunge1 : Expunge messages on host1 just before syncing a folder.
484 Expunge is done per folder.
485 Expunge aims is to really delete messages marked deleted.
486 An expunge is also done after each message copied
487 if option --delete1 is set (unless --noexpungeaftereach).
488
489 --noexpunge1 : Do not expunge messages on host1.
490
491 --delete1emptyfolders : Deletes empty folders on host1, INBOX excepted.
492 Useful with --delete1 since what remains on host1
493 is only what failed to be synced.
494
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200495 --delete2 : Delete messages in the host2 account that are not in
496 the host1 account. Useful for backup or pre-sync.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100497 --delete2 implies --uidexpunge2
498
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200499 --delete2duplicates : Deletes messages in host2 that are duplicates in host2.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100500 Works only without --useuid since duplicates are
501 detected with an header part of each message.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200502 NB: --delete2duplicates is far less violent than --delete2
503 since it removes only duplicates.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100504
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200505 --delete2folders : Delete folders in host2 that are not in host1.
506 For safety, first try it like this, it is safe:
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100507 --delete2folders --dry --justfolders --nofoldersizes
508 and see what folders will be deleted.
509
510 --delete2foldersonly reg : Delete only folders matching the regex reg.
511 Example: --delete2foldersonly "/^Junk$|^INBOX.Junk$/"
512 This option activates --delete2folders
513
514 --delete2foldersbutnot reg : Do not delete folders matching the regex rex.
515 Example: --delete2foldersbutnot "/Tasks$|Contacts$|Foo$/"
516 This option activates --delete2folders
517
518 --noexpunge2 : Do not expunge messages on host2.
519 --nouidexpunge2 : Do not uidexpunge messages on the host2 account
520 that are not on the host1 account.
521
522
523=head2 OPTIONS/dates
524
525 If you encounter problems with dates, see also:
526 https://imapsync.lamiral.info/FAQ.d/FAQ.Dates.txt
527
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200528 --syncinternaldates : Sets the internal dates on host2 as the same as host1.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100529 Turned on by default. Internal date is the date
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200530 a message arrived on a host (Unix mtime usually).
531 --idatefromheader : Sets the internal dates on host2 as same as the
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100532 ones in "Date:" headers.
533
534
535
536=head2 OPTIONS/message selection
537
538 --maxsize int : Skip messages larger (or equal) than int bytes
539 --minsize int : Skip messages smaller (or equal) than int bytes
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200540
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100541 --maxage int : Skip messages older than int days.
542 final stats (skipped) don't count older messages
543 see also --minage
544 --minage int : Skip messages newer than int days.
545 final stats (skipped) don't count newer messages
546 You can do (+ zone are the messages selected):
547 past|----maxage+++++++++++++++>now
548 past|+++++++++++++++minage---->now
549 past|----maxage+++++minage---->now (intersection)
550 past|++++minage-----maxage++++>now (union)
551
552 --search str : Selects only messages returned by this IMAP SEARCH
553 command. Applied on both sides.
554 For a complete set of what can be search see
555 https://imapsync.lamiral.info/FAQ.d/FAQ.Messages_Selection.txt
556
557 --search1 str : Same as --search but for selecting host1 messages only.
558 --search2 str : Same as --search but for selecting host2 messages only.
559 So --search CRIT equals --search1 CRIT --search2 CRIT
560
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200561 --noabletosearch : Makes --minage and --maxage options use the internal
562 dates given by a FETCH imap command instead of the
563 "Date:" header. Internal date is the arrival date
564 in the mailbox.
565 --noabletosearch equals --noabletosearch1 --noabletosearch2
566
567 --noabletosearch1 : Like --noabletosearch but for host1 only.
568 --noabletosearch2 : Like --noabletosearch but for host2 only.
569
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100570 --maxlinelength int : skip messages with a line length longer than int bytes.
571 RFC 2822 says it must be no more than 1000 bytes but
572 real life servers and email clients do more.
573
574
575 --useheader str : Use this header to compare messages on both sides.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200576 Example: "Message-Id" or "Received" or "Date".
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100577 --useheader str and this one, etc.
578
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200579 --syncduplicates : Sync also duplicates. Off by default.
580
581 --usecache : Use cache to speed up next syncs. Off by default.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100582 --nousecache : Do not use cache. Caveat: --useuid --nousecache creates
583 duplicates on multiple runs.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200584
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100585 --useuid : Use UIDs instead of headers as a criterion to recognize
586 messages. Option --usecache is then implied unless
587 --nousecache is used.
588
589
590=head2 OPTIONS/miscellaneous
591
592 --syncacls : Synchronizes acls (Access Control Lists).
593 Acls in IMAP are not standardized, be careful
594 since one acl code on one side may signify something
595 else on the other one.
596 --nosyncacls : Does not synchronize acls. This is the default.
597
598 --addheader : When a message has no headers to be identified,
599 --addheader adds a "Message-Id" header,
600 like "Message-Id: 12345@imapsync", where 12345
601 is the imap UID of the message on the host1 folder.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200602 Useful to sync folders "Sent" or "Draft".
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100603
604
605=head2 OPTIONS/debugging
606
607 --debug : Debug mode.
608 --debugfolders : Debug mode for the folders part only.
609 --debugcontent : Debug content of the messages transferred. Huge output.
610 --debugflags : Debug mode for flags.
611 --debugimap1 : IMAP debug mode for host1. Very verbose.
612 --debugimap2 : IMAP debug mode for host2. Very verbose.
613 --debugimap : IMAP debug mode for host1 and host2. Twice very verbose.
614 --debugmemory : Debug mode showing memory consumption after each copy.
615
616 --errorsmax int : Exit when int number of errors is reached. Default is 50.
617
618 --tests : Run local non-regression tests. Exit code 0 means all ok.
619 --testslive : Run a live test with test1.lamiral.info imap server.
620 Useful to check the basics. Needs internet connection.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200621 --testslive6 : Run a live test with ks6ipv6.lamiral.info imap server.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100622 Useful to check the ipv6 connectivity. Needs internet.
623
624
625=head2 OPTIONS/specific
626
627 --gmail1 : sets --host1 to Gmail and other options. See FAQ.Gmail.txt
628 --gmail2 : sets --host2 to Gmail and other options. See FAQ.Gmail.txt
629
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200630 --office1 : sets --host1 to Office365 and other options. See FAQ.Office365.txt
631 --office2 : sets --host2 to Office365 and other options. See FAQ.Office365.txt
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100632
633 --exchange1 : sets options for Exchange. See FAQ.Exchange.txt
634 --exchange2 : sets options for Exchange. See FAQ.Exchange.txt
635
636 --domino1 : sets options for Domino. See FAQ.Domino.txt
637 --domino2 : sets options for Domino. See FAQ.Domino.txt
638
639
640=head2 OPTIONS/behavior
641
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200642 --maxmessagespersecond flo : limits the average number of messages
643 transferred per second.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100644
645 --maxbytespersecond int : limits the average transfer rate per second.
646 --maxbytesafter int : starts --maxbytespersecond limitation only after
647 --maxbytesafter amount of data transferred.
648
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200649 --maxsleep flo : do not sleep more than int seconds.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100650 On by default, 2 seconds max, like --maxsleep 2
651
652 --abort : terminates a previous call still running.
653 It uses the pidfile to know what process to abort.
654
655 --exitwhenover int : Stop syncing and exits when int total bytes
656 transferred is reached.
657
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200658 --version : Print only the software version.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100659 --noreleasecheck : Do not check for any new imapsync release.
660 --releasecheck : Check for new imapsync release.
661 it's an http request to
662 http://imapsync.lamiral.info/prj/imapsync/VERSION
663
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200664 --noid : Do not send/receive IMAP "ID" command to imap servers.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100665
666 --justconnect : Just connect to both servers and print useful
667 information. Need only --host1 and --host2 options.
668 Obsolete since "imapsync --host1 imaphost" alone
669 implies --justconnect
670
671 --justlogin : Just login to both host1 and host2 with users
672 credentials, then exit.
673
674 --justfolders : Do only things about folders (ignore messages).
675
676 --help : print this help.
677
678 Example: to synchronize imap account "test1" on "test1.lamiral.info"
679 to imap account "test2" on "test2.lamiral.info"
680 with test1 password "secret1"
681 and test2 password "secret2"
682
683 imapsync \
684 --host1 test1.lamiral.info --user1 test1 --password1 secret1 \
685 --host2 test2.lamiral.info --user2 test2 --password2 secret2
686
687
688=cut
689# comment
690
691=pod
692
693
694
695=head1 SECURITY
696
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200697You can use --passfile1 instead of --password1 to mention the
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100698password since it is safer. With --password1 option, on Linux,
699any user on your host can see the password by using the 'ps auxwwww'
700command. Using a variable (like IMAPSYNC_PASSWORD1) is also
701dangerous because of the 'ps auxwwwwe' command. So, saving
702the password in a well protected file (600 or rw-------) is
703the best solution.
704
705Imapsync activates ssl or tls encryption by default, if possible.
706
707What detailed behavior is under this "if possible"?
708
709Imapsync activates ssl if the well known port imaps port (993) is open
710on the imap servers. If the imaps port is closed then it open a
711normal (clear) connection on port 143 but it looks for TLS support
712in the CAPABILITY list of the servers. If TLS is supported
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200713then imapsync goes to encryption with STARTTLS.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100714
715If the automatic ssl and the tls detections fail then imapsync will
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200716not protect against sniffing activities on the network, especially
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100717for passwords.
718
719If you want to force ssl or tls just use --ssl1 --ssl2 or --tls1 --tls2
720
721See also the document FAQ.Security.txt in the FAQ.d/ directory
722or at https://imapsync.lamiral.info/FAQ.d/FAQ.Security.txt
723
724=head1 EXIT STATUS
725
726Imapsync will exit with a 0 status (return code) if everything went good.
727Otherwise, it exits with a non-zero status. That's classical Unix behavior.
728Here is the list of the exit code values (an integer between 0 and 255).
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200729In Bourne Shells, this exit code value can be retrieved within the variable
730value "$?" if you read it just after the imapsync call.
731
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100732The names reflect their meaning:
733
734=for comment
735egrep '^Readonly my.*\$EX' imapsync | egrep -o 'EX.*' | sed 's_^_ _'
736
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100737 EX_OK => 0 ; #/* successful termination */
738 EX_USAGE => 64 ; #/* command line usage error */
739 EX_NOINPUT => 66 ; #/* cannot open input */
740 EX_UNAVAILABLE => 69 ; #/* service unavailable */
741 EX_SOFTWARE => 70 ; #/* internal software error */
742 EXIT_CATCH_ALL => 1 ; # Any other error
743 EXIT_BY_SIGNAL => 6 ; # Should be 128+n where n is the sig_num
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200744 EXIT_BY_FILE => 7 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100745 EXIT_PID_FILE_ERROR => 8 ;
746 EXIT_CONNECTION_FAILURE => 10 ;
747 EXIT_TLS_FAILURE => 12 ;
748 EXIT_AUTHENTICATION_FAILURE => 16 ;
749 EXIT_SUBFOLDER1_NO_EXISTS => 21 ;
750 EXIT_WITH_ERRORS => 111 ;
751 EXIT_WITH_ERRORS_MAX => 112 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200752 EXIT_OVERQUOTA => 113 ;
753 EXIT_ERR_APPEND => 114 ;
754 EXIT_ERR_FETCH => 115 ;
755 EXIT_ERR_CREATE => 116 ;
756 EXIT_ERR_SELECT => 117 ;
757 EXIT_TRANSFER_EXCEEDED => 118 ;
758 EXIT_ERR_APPEND_VIRUS => 119 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100759 EXIT_TESTS_FAILED => 254 ; # Like Test::More API
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200760 EXIT_CONNECTION_FAILURE_HOST1 => 101 ;
761 EXIT_CONNECTION_FAILURE_HOST2 => 102 ;
762 EXIT_AUTHENTICATION_FAILURE_USER1 => 161 ;
763 EXIT_AUTHENTICATION_FAILURE_USER2 => 162 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100764
765
766=head1 LICENSE AND COPYRIGHT
767
768Imapsync is free, open, public but not always gratis software
769cover by the NOLIMIT Public License, now called NLPL.
770See the LICENSE file included in the distribution or just read this
771simple sentence as it IS the licence text:
772
773 "No limits to do anything with this work and this license."
774
775In case it is not long enough, I repeat:
776
777 "No limits to do anything with this work and this license."
778
779Look at https://imapsync.lamiral.info/LICENSE
780
781=head1 AUTHOR
782
783Gilles LAMIRAL <gilles@lamiral.info>
784
785Good feedback is always welcome.
786Bad feedback is very often welcome.
787
788Gilles LAMIRAL earns his living by writing, installing,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200789configuring and sometimes teaching free, open and often gratis
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100790software. Imapsync used to be "always gratis" but now it is
791only "often gratis" because imapsync is sold by its author,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200792your servitor, a good way to maintain and support free open public
793software tools over decades.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100794
795=head1 BUGS AND LIMITATIONS
796
797See https://imapsync.lamiral.info/FAQ.d/FAQ.Reporting_Bugs.txt
798
799=head1 IMAP SERVERS supported
800
801See https://imapsync.lamiral.info/S/imapservers.shtml
802
803=head1 HUGE MIGRATION
804
805If you have many mailboxes to migrate think about a little
806shell program. Write a file called file.txt (for example)
807containing users and passwords.
808The separator used in this example is ';'
809
810The file.txt file contains:
811
812user001_1;password001_1;user001_2;password001_2
813user002_1;password002_1;user002_2;password002_2
814user003_1;password003_1;user003_2;password003_2
815user004_1;password004_1;user004_2;password004_2
816user005_1;password005_1;user005_2;password005_2
817...
818
819On Unix the shell program can be:
820
821 { while IFS=';' read u1 p1 u2 p2; do
822 imapsync --host1 imap.side1.org --user1 "$u1" --password1 "$p1" \
823 --host2 imap.side2.org --user2 "$u2" --password2 "$p2" ...
824 done ; } < file.txt
825
826On Windows the batch program can be:
827
828 FOR /F "tokens=1,2,3,4 delims=; eol=#" %%G IN (file.txt) DO imapsync ^
829 --host1 imap.side1.org --user1 %%G --password1 %%H ^
830 --host2 imap.side2.org --user2 %%I --password2 %%J ...
831
832The ... have to be replaced by nothing or any imapsync option.
833Welcome in shell or batch programming !
834
835You will find already written scripts at
836https://imapsync.lamiral.info/examples/
837
838=head1 INSTALL
839
840 Imapsync works under any Unix with Perl.
841
842 Imapsync works under most Windows (2000, XP, Vista, Seven, Eight, Ten
843 and all Server releases 2000, 2003, 2008 and R2, 2012 and R2, 2016)
844 as a standalone binary software called imapsync.exe,
845 usually launched from a batch file in order to avoid always typing
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200846 the options. There is also a 32bit binary called imapsync_32bit.exe
847
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100848 Imapsync works under OS X as a standalone binary
849 software called imapsync_bin_Darwin
850
851 Purchase latest imapsync at
852 https://imapsync.lamiral.info/
853
854 You'll receive a link to a compressed tarball called imapsync-x.xx.tgz
855 where x.xx is the version number. Untar the tarball where
856 you want (on Unix):
857
858 tar xzvf imapsync-x.xx.tgz
859
860 Go into the directory imapsync-x.xx and read the INSTALL file.
861 As mentioned at https://imapsync.lamiral.info/#install
862 the INSTALL file can also be found at
863 https://imapsync.lamiral.info/INSTALL.d/INSTALL.ANY.txt
864 It is now split in several files for each system
865 https://imapsync.lamiral.info/INSTALL.d/
866
867=head1 CONFIGURATION
868
869There is no specific configuration file for imapsync,
870everything is specified by the command line parameters
871and the default behavior.
872
873
874=head1 HACKING
875
876Feel free to hack imapsync as the NOLIMIT license permits it.
877
878
879=head1 SIMILAR SOFTWARE
880
881 See also https://imapsync.lamiral.info/S/external.shtml
882 for a better up to date list.
883
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200884List verified on Friday July 1, 2021.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100885
886 imapsync: https://github.com/imapsync/imapsync (this is an imapsync copy, sometimes delayed, with --noreleasecheck by default since release 1.592, 2014/05/22)
887 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
888 imaputils: https://github.com/mtsatsenko/imaputils (very old imap_tools fork)
889 Doveadm-Sync: https://wiki2.dovecot.org/Tools/Doveadm/Sync ( Dovecot sync tool )
890 davmail: http://davmail.sourceforge.net/
891 offlineimap: http://offlineimap.org/
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200892 fdm: https://github.com/nicm/fdm
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100893 mbsync: http://isync.sourceforge.net/
894 mailsync: http://mailsync.sourceforge.net/
895 mailutil: https://www.washington.edu/imap/ part of the UW IMAP toolkit. (well, seems abandoned now)
896 imaprepl: https://bl0rg.net/software/ http://freecode.com/projects/imap-repl/
897 imapcopy (Pascal): http://www.ardiehl.de/imapcopy/
898 imapcopy (Java): https://code.google.com/archive/p/imapcopy/
899 imapsize: http://www.broobles.com/imapsize/
900 migrationtool: http://sourceforge.net/projects/migrationtool/
901 imapmigrate: http://sourceforge.net/projects/cyrus-utils/
902 larch: https://github.com/rgrove/larch (derived from wonko_imapsync, good at Gmail)
903 wonko_imapsync: http://wonko.com/article/554 (superseded by larch)
904 pop2imap: http://www.linux-france.org/prj/pop2imap/ (I wrote that too)
905 exchange-away: http://exchange-away.sourceforge.net/
906 SyncBackPro: http://www.2brightsparks.com/syncback/sbpro.html
907 ImapSyncClient: https://github.com/ridaamirini/ImapSyncClient
908 MailStore: https://www.mailstore.com/en/products/mailstore-home/
909 mnIMAPSync: https://github.com/manusa/mnIMAPSync
910 imap-upload: http://imap-upload.sourceforge.net/ (A tool for uploading a local mbox file to IMAP4 server)
911 imapbackup: https://github.com/rcarmo/imapbackup (A Python script for incremental backups of IMAP mailboxes)
912 BitRecover email-backup 99 USD, 299 USD https://www.bitrecover.com/email-backup/.
913 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 +0200914 rximapmail: https://sourceforge.net/projects/rximapmail/
915 CodeTwo: https://www.codetwo.com/ but CodeTwo does imap source to Office365 only.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100916
917=head1 HISTORY
918
919I initially wrote imapsync in July 2001 because an enterprise,
920called BaSystemes, paid me to install a new imap server
921without losing huge old mailboxes located in a far
922away remote imap server, accessible by an
923often broken low-bandwidth ISDN link.
924
925I had to verify every mailbox was well transferred, all folders, all messages,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200926without wasting bandwidth or creating duplicates upon resyncs. The imapsync
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100927design was made with the beautiful rsync command in mind.
928
929Imapsync started its life as a patch of the copy_folder.pl
930script. The script copy_folder.pl comes from the Mail-IMAPClient-2.1.3 perl
931module tarball source (more precisely in the examples/ directory of the
932Mail-IMAPClient tarball).
933
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200934So many changes happened since then that I wonder
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100935if it remains any lines of the original
936copy_folder.pl in imapsync source code.
937
938
939=cut
940
941
942# use pragmas
943#
944
945use strict ;
946use warnings ;
947use Carp ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200948use Cwd ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100949use Data::Dumper ;
950use Digest::HMAC_SHA1 qw( hmac_sha1 hmac_sha1_hex ) ;
951use Digest::MD5 qw( md5 md5_hex md5_base64 ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200952use Encode ;
953use Encode::IMAPUTF7 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100954use English qw( -no_match_vars ) ;
955use Errno qw(EAGAIN EPIPE ECONNRESET) ;
956use Fcntl ;
957use File::Basename ;
958use File::Copy::Recursive ;
959use File::Glob qw( :glob ) ;
960use File::Path qw( mkpath rmtree ) ;
961use File::Spec ;
962use File::stat ;
963use Getopt::Long ( ) ;
964use IO::File ;
965use IO::Socket qw( :crlf SOL_SOCKET SO_KEEPALIVE ) ;
966use IO::Socket::INET6 ;
967use IO::Socket::SSL ;
968use IO::Tee ;
969use IPC::Open3 'open3' ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200970#use locale ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100971use Mail::IMAPClient 3.30 ;
972use MIME::Base64 ;
973use Pod::Usage qw(pod2usage) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200974use POSIX qw( uname SIGALRM :sys_wait_h ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100975use Sys::Hostname ;
976use Term::ReadKey ;
977use Test::More ;
978use Time::HiRes qw( time sleep ) ;
979use Time::Local ;
980use Unicode::String ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100981use Readonly ;
982use Sys::MemInfo ;
983use Regexp::Common ;
984use Text::ParseWords ; # for quotewords()
985use File::Tail ;
986
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100987
988
989local $OUTPUT_AUTOFLUSH = 1 ;
990
991# constants
992
993# Let us do like sysexits.h
994# /usr/include/sysexits.h
995# and https://www.tldp.org/LDP/abs/html/exitcodes.html
996
997# Should avoid 2 126 127 128..128+64=192 255
998# Should use 0 1 3..125 193..254
999
1000Readonly my $EX_OK => 0 ; #/* successful termination */
1001Readonly my $EX_USAGE => 64 ; #/* command line usage error */
1002#Readonly my $EX_DATAERR => 65 ; #/* data format error */
1003Readonly my $EX_NOINPUT => 66 ; #/* cannot open input */
1004#Readonly my $EX_NOUSER => 67 ; #/* addressee unknown */
1005#Readonly my $EX_NOHOST => 68 ; #/* host name unknown */
1006Readonly my $EX_UNAVAILABLE => 69 ; #/* service unavailable */
1007Readonly my $EX_SOFTWARE => 70 ; #/* internal software error */
1008#Readonly my $EX_OSERR => 71 ; #/* system error (e.g., can't fork) */
1009#Readonly my $EX_OSFILE => 72 ; #/* critical OS file missing */
1010#Readonly my $EX_CANTCREAT => 73 ; #/* can't create (user) output file */
1011#Readonly my $EX_IOERR => 74 ; #/* input/output error */
1012#Readonly my $EX_TEMPFAIL => 75 ; #/* temp failure; user is invited to retry */
1013#Readonly my $EX_PROTOCOL => 76 ; #/* remote error in protocol */
1014#Readonly my $EX_NOPERM => 77 ; #/* permission denied */
1015#Readonly my $EX_CONFIG => 78 ; #/* configuration error */
1016
1017# Mine
1018Readonly my $EXIT_CATCH_ALL => 1 ; # Any other error
1019Readonly my $EXIT_BY_SIGNAL => 6 ; # Should be 128+n where n is the sig_num
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001020Readonly my $EXIT_BY_FILE => 7 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001021Readonly my $EXIT_PID_FILE_ERROR => 8 ;
1022Readonly my $EXIT_CONNECTION_FAILURE => 10 ;
1023Readonly my $EXIT_TLS_FAILURE => 12 ;
1024Readonly my $EXIT_AUTHENTICATION_FAILURE => 16 ;
1025Readonly my $EXIT_SUBFOLDER1_NO_EXISTS => 21 ;
1026Readonly my $EXIT_WITH_ERRORS => 111 ;
1027Readonly my $EXIT_WITH_ERRORS_MAX => 112 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001028Readonly my $EXIT_OVERQUOTA => 113 ;
1029Readonly my $EXIT_ERR_APPEND => 114 ;
1030Readonly my $EXIT_ERR_FETCH => 115 ;
1031Readonly my $EXIT_ERR_CREATE => 116 ;
1032Readonly my $EXIT_ERR_SELECT => 117 ;
1033Readonly my $EXIT_TRANSFER_EXCEEDED => 118 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001034
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001035Readonly my $EXIT_ERR_APPEND_VIRUS => 119 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001036
1037Readonly my $EXIT_TESTS_FAILED => 254 ; # Like Test::More API
1038
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001039Readonly my $EXIT_CONNECTION_FAILURE_HOST1 => 101 ;
1040Readonly my $EXIT_CONNECTION_FAILURE_HOST2 => 102 ;
1041Readonly my $EXIT_AUTHENTICATION_FAILURE_USER1 => 161 ;
1042Readonly my $EXIT_AUTHENTICATION_FAILURE_USER2 => 162 ;
1043
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001044
1045Readonly my %EXIT_TXT => (
1046 $EX_OK => 'EX_OK: successful termination',
1047 $EX_USAGE => 'EX_USAGE: command line usage error',
1048 $EX_NOINPUT => 'EX_NOINPUT: cannot open input',
1049 $EX_UNAVAILABLE => 'EX_UNAVAILABLE: service unavailable',
1050 $EX_SOFTWARE => 'EX_SOFTWARE: internal software error',
1051
1052 $EXIT_CATCH_ALL => 'EXIT_CATCH_ALL',
1053 $EXIT_BY_SIGNAL => 'EXIT_BY_SIGNAL',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001054 $EXIT_BY_FILE => 'EXIT_BY_FILE',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001055 $EXIT_PID_FILE_ERROR => 'EXIT_PID_FILE_ERROR' ,
1056 $EXIT_CONNECTION_FAILURE => 'EXIT_CONNECTION_FAILURE',
1057 $EXIT_TLS_FAILURE => 'EXIT_TLS_FAILURE',
1058 $EXIT_AUTHENTICATION_FAILURE => 'EXIT_AUTHENTICATION_FAILURE',
1059 $EXIT_SUBFOLDER1_NO_EXISTS => 'EXIT_SUBFOLDER1_NO_EXISTS',
1060 $EXIT_WITH_ERRORS => 'EXIT_WITH_ERRORS',
1061 $EXIT_WITH_ERRORS_MAX => 'EXIT_WITH_ERRORS_MAX',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001062 $EXIT_OVERQUOTA => 'EXIT_OVERQUOTA',
1063 $EXIT_ERR_APPEND => 'EXIT_ERR_APPEND',
1064 $EXIT_ERR_APPEND_VIRUS => 'EXIT_ERR_APPEND_VIRUS',
1065 $EXIT_ERR_FETCH => 'EXIT_ERR_FETCH',
1066 $EXIT_ERR_CREATE => 'EXIT_ERR_CREATE',
1067 $EXIT_ERR_SELECT => 'EXIT_ERR_SELECT',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001068 $EXIT_TESTS_FAILED => 'EXIT_TESTS_FAILED',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001069 $EXIT_TRANSFER_EXCEEDED => 'EXIT_TRANSFER_EXCEEDED',
1070 $EXIT_CONNECTION_FAILURE_HOST1 => 'EXIT_CONNECTION_FAILURE_HOST1',
1071 $EXIT_CONNECTION_FAILURE_HOST2 => 'EXIT_CONNECTION_FAILURE_HOST2',
1072 $EXIT_AUTHENTICATION_FAILURE_USER1 => 'EXIT_AUTHENTICATION_FAILURE_USER1',
1073 $EXIT_AUTHENTICATION_FAILURE_USER2 => 'EXIT_AUTHENTICATION_FAILURE_USER2',
1074) ;
1075
1076
1077Readonly my %EXIT_VALUE_OF_ERR_TYPE => (
1078 ERR_APPEND_SIZE => $EXIT_ERR_APPEND,
1079 ERR_OVERQUOTA => $EXIT_OVERQUOTA,
1080 ERR_APPEND => $EXIT_ERR_APPEND,
1081 ERR_APPEND_VIRUS => $EXIT_ERR_APPEND_VIRUS,
1082 ERR_CREATE => $EXIT_ERR_CREATE,
1083 ERR_SELECT => $EXIT_ERR_SELECT,
1084 ERR_Host1_FETCH => $EXIT_ERR_FETCH,
1085 ERR_UNCLASSIFIED => $EXIT_WITH_ERRORS,
1086 ERR_NOTHING_REPORTED => $EXIT_WITH_ERRORS,
1087 ERR_TRANSFER_EXCEEDED => $EXIT_TRANSFER_EXCEEDED,
1088 ERR_CONNECTION_FAILURE_HOST1 => $EXIT_CONNECTION_FAILURE_HOST1,
1089 ERR_CONNECTION_FAILURE_HOST2 => $EXIT_CONNECTION_FAILURE_HOST2,
1090 ERR_AUTHENTICATION_FAILURE_USER1 => $EXIT_AUTHENTICATION_FAILURE_USER1,
1091 ERR_AUTHENTICATION_FAILURE_USER2 => $EXIT_AUTHENTICATION_FAILURE_USER2,
1092 ERR_EXIT_TLS_FAILURE => $EXIT_TLS_FAILURE,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001093) ;
1094
1095
1096Readonly my $DEFAULT_LOGDIR => 'LOG_imapsync' ;
1097
1098Readonly my $ERRORS_MAX => 50 ; # exit after 50 errors.
1099Readonly my $ERRORS_MAX_CGI => 20 ; # exit after 20 errors in CGI context.
1100
1101
1102
1103Readonly my $INTERVAL_TO_EXIT => 2 ; # interval max to exit instead of reconnect
1104
1105Readonly my $SPLIT => 100 ; # By default, 100 at a time, not more.
1106Readonly my $SPLIT_FACTOR => 10 ; # init_imap() calls Maxcommandlength( $SPLIT_FACTOR * $split )
1107 # which means default Maxcommandlength is 10*100 = 1000 characters ;
1108
1109Readonly my $IMAP_PORT => 143 ; # Well know port for IMAP
1110Readonly my $IMAP_SSL_PORT => 993 ; # Well know port for IMAP over SSL
1111
1112Readonly my $LAST => -1 ;
1113Readonly my $MINUS_ONE => -1 ;
1114Readonly my $MINUS_TWO => -2 ;
1115
1116Readonly my $RELEASE_NUMBER_EXAMPLE_1 => '1.351' ;
1117Readonly my $RELEASE_NUMBER_EXAMPLE_2 => 42.4242 ;
1118
1119Readonly my $TCP_PING_TIMEOUT => 5 ;
1120Readonly my $DEFAULT_TIMEOUT => 120 ;
1121Readonly my $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND => 3 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001122
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001123Readonly my $DEFAULT_BUFFER_SIZE => 4096 ;
1124
1125Readonly my $MAX_SLEEP => 2 ; # 2 seconds max for limiting too long sleeps from --maxbytespersecond and --maxmessagespersecond
1126
1127Readonly my $DEFAULT_EXPIRATION_TIME_OAUTH2_PK12 => 3600 ;
1128
1129Readonly my $PERMISSION_FILTER => 7777 ;
1130
1131Readonly my $KIBI => 1024 ;
1132
1133Readonly my $NUMBER_10 => 10 ;
1134Readonly my $NUMBER_42 => 42 ;
1135Readonly my $NUMBER_100 => 100 ;
1136Readonly my $NUMBER_200 => 200 ;
1137Readonly my $NUMBER_300 => 300 ;
1138Readonly my $NUMBER_123456 => 123_456 ;
1139Readonly my $NUMBER_654321 => 654_321 ;
1140
1141Readonly my $NUMBER_20_000 => 20_000 ;
1142
1143Readonly my $QUOTA_PERCENT_LIMIT => 90 ;
1144
1145Readonly my $NUMBER_104_857_600 => 104_857_600 ;
1146
1147Readonly my $SIZE_MAX_STR => 64 ;
1148
1149Readonly my $NB_SECONDS_IN_A_DAY => 86_400 ;
1150
1151Readonly my $STD_CHAR_PER_LINE => 80 ;
1152
1153Readonly my $TRUE => 1 ;
1154Readonly my $FALSE => 0 ;
1155
1156Readonly my $LAST_RESSORT_SEPARATOR => q{/} ;
1157
1158Readonly my $CGI_TMPDIR_TOP => '/var/tmp/imapsync_cgi' ;
1159Readonly my $CGI_HASHFILE => '/var/tmp/imapsync_hash' ;
1160Readonly my $UMASK_PARANO => '0077' ;
1161
1162Readonly my $STR_use_releasecheck => q{Check if a new imapsync release is available by adding --releasecheck} ;
1163
1164Readonly my $GMAIL_MAXSIZE => 35_651_584 ;
1165
1166Readonly my $FORCE => 1 ;
1167
1168# if ( 'MSWin32' eq $OSNAME )
1169# if ( 'darwin' eq $OSNAME )
1170# if ( 'linux' eq $OSNAME )
1171
1172
1173
1174# global variables
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001175# Currently working to finish with only $sync, $acc1, $acc2
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001176# Not finished yet...
1177
1178my(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001179 $sync, $acc1, $acc2,
1180 $debugcontent, $debugflags,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001181 $debuglist, $debugdev, $debugmaxlinelength, $debugcgi,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001182
1183 @include, @exclude, @folderrec,
1184 @folderfirst, @folderlast,
1185 @h1_folders_all, %h1_folders_all,
1186 @h2_folders_all, %h2_folders_all,
1187 @h2_folders_from_1_wanted, %h2_folders_from_1_all,
1188 %requested_folder,
1189 $h1_folders_wanted_nb, $h1_folders_wanted_ct,
1190 @h2_folders_not_in_1,
1191 %h1_subscribed_folder, %h2_subscribed_folder,
1192 %h2_folders_from_1_wanted,
1193 %h2_folders_from_1_several,
1194
1195 $prefix1, $prefix2,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001196 @regexmess, @skipmess, @pipemess, $pipemesscheck,
1197 $syncflagsaftercopy,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001198 $syncinternaldates,
1199 $idatefromheader,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001200
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001201 $minsize, $maxage, $minage,
1202 $search,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001203 @useheader, %useheader,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001204 $skipsize, $allowsizemismatch, $buffersize,
1205
1206
1207 $authmd5, $authmd51, $authmd52,
1208 $subscribed, $subscribe, $subscribeall,
1209 $help,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001210
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001211 $fast,
1212
1213 $nb_msg_skipped_dry_mode,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001214
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001215 $h2_nb_msg_noheader,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001216
1217 $h1_bytes_processed,
1218
1219 $h1_nb_msg_end, $h1_bytes_end,
1220 $h2_nb_msg_end, $h2_bytes_end,
1221
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001222 $timestart_int,
1223
1224 $uid1, $uid2,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001225
1226
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001227 $split1, $split2,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001228
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001229 $modulesversion,
1230 $delete2folders, $delete2foldersonly, $delete2foldersbutnot,
1231 $usecache, $debugcache, $cacheaftercopy,
1232 $wholeheaderifneeded, %h1_msgs_copy_by_uid, $useuid, $h2_uidguess,
1233 $checkmessageexists,
1234 $messageidnodomain,
1235 $fixInboxINBOX,
1236 $maxlinelength, $maxlinelengthcmd,
1237 $minmaxlinelength,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001238 $fixcolonbug,
1239 $create_folder_old,
1240 $skipcrossduplicates, $debugcrossduplicates,
1241 $disarmreadreceipts,
1242 $mixfolders,
1243 $fetch_hash_set,
1244 $cgidir,
1245 %month_abrev,
1246 $SSL_VERIFY_POLICY,
1247 $warn_release,
1248) ;
1249
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001250single_sync( $sync, $acc1, $acc2 );
1251
1252
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001253
1254sub single_sync
1255{
1256
1257# main program
1258# global variables initialization
1259
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001260# I'm currently removing all global variables except $sync $acc1 $acc2
1261# passing each of them under
1262# $sync->{variable_name}
1263# or $acc1->{variable_name}
1264# or $acc1->{variable_name}
1265
1266#
1267$acc1 = {} ;
1268$acc2 = {} ;
1269$sync->{ acc1 } = $acc1 ;
1270$sync->{ acc2 } = $acc2 ;
1271
1272$acc1->{ Side } = 'Host1' ;
1273$acc2->{ Side } = 'Host2' ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001274
1275$sync->{timestart} = time ; # Is a float because of use Time::HiRres
1276
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001277$sync->{rcs} = q{$Id: imapsync,v 2.148 2021/07/22 14:21:09 gilles Exp gilles $} ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001278
1279$sync->{ memory_consumption_at_start } = memory_consumption( ) || 0 ;
1280
1281
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001282
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001283my @loadavg = loadavg( ) ;
1284
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001285$sync->{ cpu_number } = cpu_number( ) ;
1286$sync->{ loaddelay } = load_and_delay( $sync->{ cpu_number }, @loadavg ) ;
1287$sync->{ loaddelay } = 0 ;
1288
1289$sync->{ loadavg } = join( q{ }, $loadavg[ 0 ] )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001290 . " on $sync->{cpu_number} cores and "
1291 . ram_memory_info( ) ;
1292
1293
1294
1295$sync->{ total_bytes_transferred } = 0 ;
1296$sync->{ total_bytes_skipped } = 0 ;
1297$sync->{ nb_msg_transferred } = 0 ;
1298$sync->{ nb_msg_skipped } = $nb_msg_skipped_dry_mode = 0 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001299
1300$sync->{ acc1 }->{ nb_msg_deleted } = 0 ;
1301$sync->{ acc2 }->{ nb_msg_deleted } = 0 ;
1302
1303$sync->{ acc1 }->{ nb_msg_duplicate } = 0 ;
1304$sync->{ acc2 }->{ nb_msg_duplicate } = 0 ;
1305
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001306$sync->{ h1_nb_msg_noheader } = 0 ;
1307$h2_nb_msg_noheader = 0 ;
1308
1309
1310$sync->{ h1_nb_msg_start } = 0 ;
1311$sync->{ h1_bytes_start } = 0 ;
1312$sync->{ h2_nb_msg_start } = 0 ;
1313$sync->{ h2_bytes_start } = 0 ;
1314$sync->{ h1_nb_msg_processed } = $h1_bytes_processed = 0 ;
1315
1316$sync->{ h2_nb_msg_crossdup } = 0 ;
1317
1318#$h1_nb_msg_end = $h1_bytes_end = 0 ;
1319#$h2_nb_msg_end = $h2_bytes_end = 0 ;
1320
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001321$sync->{ nb_errors } = 0;
1322$sync->{ biggest_message_transferred } = 0;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001323
1324%month_abrev = (
1325 Jan => '00',
1326 Feb => '01',
1327 Mar => '02',
1328 Apr => '03',
1329 May => '04',
1330 Jun => '05',
1331 Jul => '06',
1332 Aug => '07',
1333 Sep => '08',
1334 Oct => '09',
1335 Nov => '10',
1336 Dec => '11',
1337);
1338
1339
1340
1341# Just create a CGI object if under cgi context only.
1342# Needed for the get_options() call
1343cgibegin( $sync ) ;
1344
1345# In cgi context, printing must start by the header so we delay other prints by using output() storage
1346my $options_good = get_options( $sync, @ARGV ) ;
1347# Is it the first myprint?
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001348cgibuildheader( $sync ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001349docker_context( $sync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001350
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001351print_output_if_needed( $sync ) ;
1352
1353
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001354output_reset_with( $sync ) ;
1355
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001356# don't go on if options are not all known.
1357if ( ! defined $options_good ) { exit $EX_USAGE ; }
1358
1359# If you want releasecheck not to be done by default (like the github maintainer),
1360# then just uncomment the first "$sync->{releasecheck} =" line, the line ending with "0 ;",
1361# the second line (ending with "1 ;") can then stay active or be commented,
1362# the result will be the same: no releasecheck by default (because 0 is then the defined value).
1363
1364#$sync->{releasecheck} = defined $sync->{releasecheck} ? $sync->{releasecheck} : 0 ;
1365$sync->{releasecheck} = defined $sync->{releasecheck} ? $sync->{releasecheck} : 1 ;
1366
1367# just the version
1368if ( $sync->{ version } ) {
1369 myprint( imapsync_version( $sync ), "\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001370 return 0 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001371}
1372
1373#$sync->{debugenv} = 1 ;
1374$sync->{debugenv} and printenv( $sync ) ; # if option --debugenv
1375load_modules( ) ;
1376
1377# after_get_options call usage and exit if --help or options were not well got
1378after_get_options( $sync, $options_good ) ;
1379
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001380#local $ENV{TZ} = 'GMT' if ( under_cgi_context( $sync ) and 'MSWin32' ne $OSNAME ) ;
1381#output( $sync, localtime(time) . " " . gmtime(time) . "\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001382
1383# Under CGI environment, fix caveat emptor potential issues
1384cgisetcontext( $sync ) ;
1385
1386# --gmail --gmail --exchange --office etc.
1387easyany( $sync ) ;
1388
1389$sync->{ sanitize } = defined $sync->{ sanitize } ? $sync->{ sanitize } : 1 ;
1390sanitize( $sync ) ;
1391
1392$sync->{ tmpdir } ||= File::Spec->tmpdir( ) ;
1393
1394# Unit tests
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001395my $unittestssuite = unittestssuite( $sync ) ;
1396
1397
1398if ( condition_to_leave_after_tests( $sync ) )
1399{
1400 return $unittestssuite ;
1401}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001402
1403# init live varaiables
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001404
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001405if ( $sync->{ testslive } )
1406{
1407 testslive_init( $sync ) ;
1408}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001409
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001410if ( $sync->{ testslive6 } )
1411{
1412 testslive6_init( $sync ) ;
1413}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001414
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001415define_pidfile( $sync ) ;
1416if ( $sync->{ abortbyfile } ) { $sync->{ abort } = 1 ; }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001417
1418install_signals( $sync ) ;
1419
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001420$sync->{ log } = defined $sync->{ log } ? $sync->{ log } : 1 ;
1421$sync->{ errorsdump } = defined $sync->{ errorsdump } ? $sync->{ errorsdump } : 1 ;
1422$sync->{ errorsmax } = defined $sync->{ errorsmax } ? $sync->{ errorsmax } : $ERRORS_MAX ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001423
1424# log and output
1425binmode STDOUT, ":encoding(UTF-8)" ;
1426
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001427
1428if ( $sync->{ log } ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001429 setlogfile( $sync ) ;
1430 teelaunch( $sync ) ;
1431 # now $sync->{tee} is a filehandle to STDOUT and the logfile
1432}
1433
1434#binmode STDERR, ":encoding(UTF-8)" ;
1435# STDERR goes to the same place: LOG and STDOUT (if logging is on)
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001436# Useful only for --debugssl
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001437$sync->{tee} and local *STDERR = *${$sync->{tee}}{IO} ;
1438
1439
1440
1441$timestart_int = int( $sync->{timestart} ) ;
1442$sync->{timebefore} = $sync->{timestart} ;
1443
1444
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001445$sync->{ timestart_str } = localtimez( $sync->{timestart} ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001446
1447# The prints in the log starts here
1448
1449myprint( localhost_info( $sync ), "\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001450myprint( "Transfer started at $sync->{ timestart_str }\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001451myprint( "PID is $PROCESS_ID my PPID is ", mygetppid( ), "\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001452announcelogfile( $sync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001453myprint( "Load is " . ( join( q{ }, loadavg( ) ) || 'unknown' ), " on $sync->{cpu_number} cores\n" ) ;
1454#myprintf( "Memory consumption so far: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI ) ;
1455myprint( 'Current directory is ' . getcwd( ) . "\n" ) ;
1456myprint( 'Real user id is ' . getpwuid_any_os( $REAL_USER_ID ) . " (uid $REAL_USER_ID)\n" ) ;
1457myprint( 'Effective user id is ' . getpwuid_any_os( $EFFECTIVE_USER_ID ). " (euid $EFFECTIVE_USER_ID)\n" ) ;
1458
1459$modulesversion = defined $modulesversion ? $modulesversion : 1 ;
1460
1461$warn_release = ( $sync->{releasecheck} ) ? check_last_release( ) : $STR_use_releasecheck ;
1462
1463
1464$wholeheaderifneeded = defined $wholeheaderifneeded ? $wholeheaderifneeded : 1;
1465
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001466# Activate --usecache if --useuid is set and there is no --nousecache
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001467$usecache = 1 if ( $useuid and ( ! defined $usecache ) ) ;
1468$cacheaftercopy = 1 if ( $usecache and ( ! defined $cacheaftercopy ) ) ;
1469
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001470
1471
1472
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001473$sync->{ checkfoldersexist } = defined $sync->{ checkfoldersexist } ? $sync->{ checkfoldersexist } : 1 ;
1474$checkmessageexists = defined $checkmessageexists ? $checkmessageexists : 0 ;
1475$sync->{ expungeaftereach } = defined $sync->{ expungeaftereach } ? $sync->{ expungeaftereach } : 1 ;
1476
1477# abletosearch is on by default
1478$sync->{abletosearch} = defined $sync->{abletosearch} ? $sync->{abletosearch} : 1 ;
1479$sync->{abletosearch1} = defined $sync->{abletosearch1} ? $sync->{abletosearch1} : $sync->{abletosearch} ;
1480$sync->{abletosearch2} = defined $sync->{abletosearch2} ? $sync->{abletosearch2} : $sync->{abletosearch} ;
1481$checkmessageexists = 0 if ( not $sync->{abletosearch1} ) ;
1482
1483
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001484$sync->{ trylogin } = defined $sync->{ trylogin } ? $sync->{ trylogin } : 1 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001485$sync->{showpasswords} = defined $sync->{showpasswords} ? $sync->{showpasswords} : 0 ;
1486$sync->{ fixslash2 } = defined $sync->{ fixslash2 } ? $sync->{ fixslash2 } : 1 ;
1487$fixInboxINBOX = defined $fixInboxINBOX ? $fixInboxINBOX : 1 ;
1488$create_folder_old = defined $create_folder_old ? $create_folder_old : 0 ;
1489$mixfolders = defined $mixfolders ? $mixfolders : 1 ;
1490$sync->{automap} = defined $sync->{automap} ? $sync->{automap} : 0 ;
1491
1492$sync->{ delete2duplicates } = 1 if ( $sync->{ delete2 } and ( ! defined $sync->{ delete2duplicates } ) ) ;
1493
1494$sync->{maxmessagespersecond} = defined $sync->{maxmessagespersecond} ? $sync->{maxmessagespersecond} : 0 ;
1495$sync->{maxbytespersecond} = defined $sync->{maxbytespersecond} ? $sync->{maxbytespersecond} : 0 ;
1496
1497$sync->{sslcheck} = defined $sync->{sslcheck} ? $sync->{sslcheck} : 1 ;
1498
1499myprint( banner_imapsync( $sync, @ARGV ) ) ;
1500
1501myprint( "Temp directory is $sync->{ tmpdir } ( to change it use --tmpdir dirpath )\n" ) ;
1502
1503myprint( output( $sync ) ) ;
1504output_reset_with( $sync ) ;
1505
1506do_valid_directory( $sync->{ tmpdir } ) || croak "Error creating tmpdir $sync->{ tmpdir } : $OS_ERROR" ;
1507
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001508remove_pidfile_not_running( $sync->{ pidfile } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001509
1510# if another imapsync is running then tail -f its logfile and exit
1511# useful in cgi context
1512if ( $sync->{ tail } and tail( $sync ) )
1513{
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001514 exit_clean( $sync, $EX_OK, "Tail -f finished. Now finishing myself processus $PROCESS_ID\n" ) ;
1515 exit $EX_OK ;
1516}
1517
1518if ( ! write_pidfile( $sync ) ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001519 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 +01001520 exit $EXIT_PID_FILE_ERROR ;
1521}
1522
1523
1524# New place for abort
1525# abort before simulong in order to be able to abort a simulong sync
1526if ( $sync->{ abort } )
1527{
1528 abort( $sync ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001529 # well, the abort job is done, because even when not succeeded
1530 # in aborting another run, this run has to end without doing any
1531 # thing else
1532
1533 exit $EX_OK ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001534}
1535
1536# simulong is just a loop printing some lines for xx seconds with option "--simulong xx".
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001537simulong( $sync ) ;
1538
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001539
1540
1541# New place for cgiload 2019_03_03
1542# because I want to log it
1543# Can break here if load is too heavy
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001544# Have in mind the CGI header has already a 503 Service Unavailable
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001545cgiload( $sync ) ;
1546
1547
1548$fixcolonbug = defined $fixcolonbug ? $fixcolonbug : 1 ;
1549
1550if ( $usecache and $fixcolonbug ) { tmpdir_fix_colon_bug( $sync ) } ;
1551
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001552$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 +01001553
1554
1555check_lib_version( $sync ) or
1556 croak "imapsync needs perl lib Mail::IMAPClient release 3.30 or superior.\n";
1557
1558
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001559
1560if ( $sync->{ justbanner } )
1561{
1562 myprint( "Exiting because of --justbanner\n" ) ;
1563 exit_clean( $sync, $EX_OK ) ;
1564}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001565
1566# turn on RFC standard flags correction like \SEEN -> \Seen
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001567$sync->{ flagscase } = defined $sync->{ flagscase } ? $sync->{ flagscase } : 1 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001568
1569# Use PERMANENTFLAGS if available
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001570$sync->{ filterflags } = defined $sync->{ filterflags } ? $sync->{ filterflags } : 1 ;
1571
1572filterbuggyflags( $sync ) ;
1573
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001574
1575# sync flags just after an APPEND, some servers ignore the flags given in the APPEND
1576# like MailEnable IMAP server.
1577# Off by default since it takes time.
1578$syncflagsaftercopy = defined $syncflagsaftercopy ? $syncflagsaftercopy : 0 ;
1579
1580# update flags on host2 for already transferred messages
1581$sync->{resyncflags} = defined $sync->{resyncflags} ? $sync->{resyncflags} : 1 ;
1582if ( $sync->{resyncflags} ) {
1583 myprint( "Info: will resync flags for already transferred messages. Use --noresyncflags to not resync flags.\n" ) ;
1584}else{
1585 myprint( "Info: will not resync flags for already transferred messages. Use --resyncflags to resync flags.\n" ) ;
1586}
1587
1588
1589sslcheck( $sync ) ;
1590#print Data::Dumper->Dump( [ \$sync ] ) ;
1591
1592$split1 ||= $SPLIT ;
1593$split2 ||= $SPLIT ;
1594
1595#$sync->{host1} || missing_option( $sync, '--host1' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001596$sync->{host1} = sanitize_host( $sync->{host1} ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001597$sync->{port1} ||= ( $sync->{ssl1} ) ? $IMAP_SSL_PORT : $IMAP_PORT ;
1598
1599#$sync->{host2} || missing_option( $sync, '--host2' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001600$sync->{host2} = sanitize_host( $sync->{host2} ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001601$sync->{port2} ||= ( $sync->{ssl2} ) ? $IMAP_SSL_PORT : $IMAP_PORT ;
1602
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001603
1604$acc1->{ debugimap } = $acc2->{ debugimap } = 1 if ( $sync->{ debugimap } ) ;
1605# Set on debug mode if one of the imap dialogs are in debug.
1606# imap dialog without the debug mode is scary and useless.
1607$sync->{ debug } = 1 if ( $acc1->{ debugimap } or $acc2->{ debugimap } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001608
1609# By default, don't take size to compare
1610$skipsize = (defined $skipsize) ? $skipsize : 1;
1611
1612$uid1 = defined $uid1 ? $uid1 : 1;
1613$uid2 = defined $uid2 ? $uid2 : 1;
1614
1615$subscribe = defined $subscribe ? $subscribe : 1;
1616
1617# Allow size mismatch by default
1618$allowsizemismatch = defined $allowsizemismatch ? $allowsizemismatch : 1;
1619
1620
1621if ( defined $delete2foldersbutnot or defined $delete2foldersonly ) {
1622 $delete2folders = 1 ;
1623}
1624
1625
1626my %SSL_VERIFY_STR ;
1627
1628Readonly $SSL_VERIFY_POLICY => IO::Socket::SSL::SSL_VERIFY_NONE( ) ;
1629Readonly %SSL_VERIFY_STR => (
1630 IO::Socket::SSL::SSL_VERIFY_NONE( ) => 'SSL_VERIFY_NONE, ie, do not check the certificate server.' ,
1631 IO::Socket::SSL::SSL_VERIFY_PEER( ) => 'SSL_VERIFY_PEER, ie, check the certificate server' ,
1632) ;
1633
1634$IO::Socket::SSL::DEBUG = defined( $sync->{debugssl} ) ? $sync->{debugssl} : 1 ;
1635
1636
1637if ( $sync->{ssl1} or $sync->{ssl2} or $sync->{tls1} or $sync->{tls2}) {
1638 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" ) ;
1639}
1640
1641if ( $sync->{ssl1} ) {
1642 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} ) ;
1643 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 +02001644 # $sync->{ acc1 }->{sslargs}->{SSL_verify_mode}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001645}
1646
1647if ( $sync->{ssl2} ) {
1648 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} ) ;
1649 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" ) ;
1650}
1651
1652# ID on by default since 1.832
1653$sync->{id} = defined $sync->{id} ? $sync->{id} : 1 ;
1654
1655if ( $sync->{justconnect}
1656 or not $sync->{user1}
1657 or not $sync->{user2}
1658 or not $sync->{host1}
1659 or not $sync->{host2}
1660 )
1661{
1662 my $justconnect = justconnect( $sync ) ;
1663
1664 myprint( debugmemory( $sync, " after justconnect() call" ) ) ;
1665 exit_clean( $sync, $EX_OK,
1666 "Exiting after a justconnect on host(s): $justconnect\n"
1667 ) ;
1668}
1669
1670
1671#$sync->{user1} || missing_option( $sync, '--user1' ) ;
1672#$sync->{user2} || missing_option( $sync, '--user2' ) ;
1673
1674$syncinternaldates = defined $syncinternaldates ? $syncinternaldates : 1;
1675
1676# Turn on expunge if there is not explicit option --noexpunge1 and option
1677# --delete1 is given.
1678# Done because --delete1 --noexpunge1 is very dangerous on the second run:
1679# the Deleted flag is then synced to all previously transferred messages.
1680# So --delete1 implies --expunge1 is a better usability default behavior.
1681if ( $sync->{ delete1 } ) {
1682 if ( ! defined $sync->{ expunge1 } ) {
1683 myprint( "Info: turning on --expunge1 because --delete1 --noexpunge1 is very dangerous on the second run.\n" ) ;
1684 $sync->{ expunge1 } = 1 ;
1685 }
1686 myprint( "Info: if expunging after each message slows down too much the sync then use --noexpungeaftereach to speed up\n" ) ;
1687}
1688
1689if ( $sync->{ uidexpunge2 } and not Mail::IMAPClient->can( 'uidexpunge' ) ) {
1690 myprint( "Failure: uidexpunge not supported (IMAPClient release < 3.17), use nothing or --expunge2 instead\n" ) ;
1691 $sync->{nb_errors}++ ;
1692 exit_clean( $sync, $EX_SOFTWARE ) ;
1693}
1694
1695if ( ( $sync->{ delete2 } or $sync->{ delete2duplicates } ) and not defined $sync->{ uidexpunge2 } ) {
1696 if ( Mail::IMAPClient->can( 'uidexpunge' ) ) {
1697 myprint( "Info: will act as --uidexpunge2\n" ) ;
1698 $sync->{ uidexpunge2 } = 1 ;
1699 }elsif ( not defined $sync->{ expunge2 } ) {
1700 myprint( "Info: will act as --expunge2 (no uidexpunge support)\n" ) ;
1701 $sync->{ expunge2 } = 1 ;
1702 }
1703}
1704
1705if ( $sync->{ delete1 } and $sync->{ delete2 } ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001706 myprint( "Warning: using --delete1 and --delete2 together is almost always a bad idea. "
1707 . "You should probably launch two runs, the first with --delete2 for a strict sync, "
1708 . "then the second with --delete1 to remove messages from the source account. "
1709 . "Exiting imapsync.\n" ) ;
1710 $sync->{ nb_errors }++ ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001711 exit_clean( $sync, $EX_USAGE ) ;
1712}
1713
1714if ( $idatefromheader ) {
1715 myprint( 'Turned ON idatefromheader, ',
1716 "will set the internal dates on host2 from the 'Date:' header line.\n" ) ;
1717 $syncinternaldates = 0 ;
1718}
1719
1720if ( $syncinternaldates ) {
1721 myprint( 'Info: turned ON syncinternaldates, ',
1722 "will set the internal dates (arrival dates) on host2 same as host1.\n" ) ;
1723}else{
1724 myprint( "Info: turned OFF syncinternaldates\n" ) ;
1725}
1726
1727if ( defined $authmd5 and $authmd5 ) {
1728 $authmd51 = 1 ;
1729 $authmd52 = 1 ;
1730}
1731
1732if ( defined $authmd51 and $authmd51 ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001733 $acc1->{ authmech } ||= 'CRAM-MD5' ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001734}
1735else{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001736 $acc1->{ authmech } ||= $acc1->{ authuser } ? 'PLAIN' : 'LOGIN' ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001737}
1738
1739if ( defined $authmd52 and $authmd52 ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001740 $acc2->{ authmech } ||= 'CRAM-MD5';
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001741}
1742else{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001743 $acc2->{ authmech } ||= $acc2->{ authuser } ? 'PLAIN' : 'LOGIN';
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001744}
1745
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001746$acc1->{ authmech } = uc $acc1->{ authmech } ;
1747$acc2->{ authmech } = uc $acc2->{ authmech } ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001748
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001749if ( defined $acc1->{ proxyauth } && !$acc1->{ authuser } )
1750{
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001751 missing_option( $sync, 'With --proxyauth1, --authuser1' ) ;
1752}
1753
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001754if ( defined $acc2->{ proxyauth } && !$acc2->{ authuser } )
1755{
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001756 missing_option( $sync, 'With --proxyauth2, --authuser2' ) ;
1757}
1758
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001759myprint( "Host1: will try to use $acc1->{ authmech } authentication on host1\n") ;
1760myprint( "Host2: will try to use $acc2->{ authmech } authentication on host2\n") ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001761
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001762$sync->{ timeout } = defined $sync->{ timeout } ?$sync->{ timeout } : $DEFAULT_TIMEOUT ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001763
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001764$sync->{ acc1 }->{timeout} = defined $sync->{ acc1 }->{timeout} ? $sync->{ acc1 }->{timeout} : $sync->{ timeout } ;
1765myprint( "Host1: imap connection timeout is $sync->{ acc1 }->{timeout} seconds\n") ;
1766$sync->{ acc2 }->{timeout} = defined $sync->{ acc2 }->{timeout} ? $sync->{ acc2 }->{timeout} : $sync->{ timeout } ;
1767myprint( "Host2: imap connection timeout is $sync->{ acc2 }->{timeout} seconds\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001768
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001769if ( under_cgi_context( $sync ) )
1770{
1771 myprint( "Under CGI context, a timeout can occur from the webserver, see https://imapsync.lamiral.info/INSTALL.d/INSTALL.OnlineUI.txt\n" ) ;
1772}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001773
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001774$sync->{ syncacls } = defined $sync->{ syncacls } ? $sync->{ syncacls } : 0 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001775
1776# No folders sizes if --justfolders, unless really wanted.
1777if (
1778 $sync->{ justfolders }
1779 and not defined $sync->{ foldersizes }
1780 and not $sync->{ justfoldersizes } )
1781{
1782 $sync->{ foldersizes } = 0 ;
1783 $sync->{ foldersizesatend } = 1 ;
1784}
1785
1786$sync->{ foldersizes } = ( defined $sync->{ foldersizes } ) ? $sync->{ foldersizes } : 1 ;
1787$sync->{ foldersizesatend } = ( defined $sync->{ foldersizesatend } ) ? $sync->{ foldersizesatend } : $sync->{ foldersizes } ;
1788
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001789$sync->{ checknoabletosearch } = ( defined $sync->{ checknoabletosearch } ) ? $sync->{ checknoabletosearch } : 1 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001790
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001791
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001792$acc1->{ fastio } = defined $acc1->{ fastio } ? $acc1->{ fastio } : 0 ;
1793$acc2->{ fastio } = defined $acc2->{ fastio } ? $acc2->{ fastio } : 0 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001794
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001795
1796$acc1->{ reconnectretry } = defined $acc1->{ reconnectretry } ? $acc1->{ reconnectretry } : $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ;
1797$acc2->{ reconnectretry } = defined $acc2->{ reconnectretry } ? $acc2->{ reconnectretry } : $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ;
1798
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001799
1800if ( ! @useheader ) { @useheader = qw( Message-Id Received ) ; }
1801
1802# Make a hash %useheader of each --useheader 'key' in uppercase
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001803for ( @useheader ) { $sync->{useheader}->{ uc $_ } = undef } ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001804
1805#myprint( Data::Dumper->Dump( [ \%useheader ] ) ) ;
1806#exit ;
1807
1808myprint( "Host1: IMAP server [$sync->{host1}] port [$sync->{port1}] user [$sync->{user1}]\n" ) ;
1809myprint( "Host2: IMAP server [$sync->{host2}] port [$sync->{port2}] user [$sync->{user2}]\n" ) ;
1810
1811get_password1( $sync ) ;
1812get_password2( $sync ) ;
1813
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001814# --dry1 make imapsync not fetching messages from host1, it is on when --dry is on.
1815# Use --dry --nodry1 to make imapsync fetching messages from host1,
1816# It is useful when debugging transformation options like --pipemess or --regexmess
1817$sync->{dry1} = defined $sync->{dry1} ? $sync->{dry1} : $sync->{dry} ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001818
1819$sync->{dry_message} = q{} ;
1820if( $sync->{dry} ) {
1821 $sync->{dry_message} = "\t(not really since --dry mode)" ;
1822}
1823
1824$sync->{ search1 } ||= $search if ( $search ) ;
1825$sync->{ search2 } ||= $search if ( $search ) ;
1826
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001827if ( $disarmreadreceipts )
1828{
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001829 push @regexmess, q{s{\A((?:[^\n]+\r\n)+|)(^Disposition-Notification-To:[^\n]*\n)(\r?\n|.*\n\r?\n)}{$1X-$2$3}ims} ;
1830}
1831
1832$pipemesscheck = ( defined $pipemesscheck ) ? $pipemesscheck : 1 ;
1833
1834if ( @pipemess and $pipemesscheck ) {
1835 myprint( 'Checking each --pipemess command, '
1836 . join( q{, }, @pipemess )
1837 . ", with an space string. ( Can avoid this check with --nopipemesscheck )\n" ) ;
1838 my $string = pipemess( q{ }, @pipemess ) ;
1839 # string undef means something was bad.
1840 if ( not ( defined $string ) ) {
1841 $sync->{nb_errors}++ ;
1842 exit_clean( $sync, $EX_USAGE,
1843 "Error: one of --pipemess command is bad, check it\n"
1844 ) ;
1845 }
1846 myprint( "Ok with each --pipemess @pipemess\n" ) ;
1847}
1848
1849if ( $maxlinelengthcmd ) {
1850 myprint( "Checking --maxlinelengthcmd command,
1851 $maxlinelengthcmd, with an space string.\n"
1852 ) ;
1853 my $string = pipemess( q{ }, $maxlinelengthcmd ) ;
1854 # string undef means something was bad.
1855 if ( not ( defined $string ) ) {
1856 $sync->{nb_errors}++ ;
1857 exit_clean( $sync, $EX_USAGE,
1858 "Error: --maxlinelengthcmd command is bad, check it\n"
1859 ) ;
1860 }
1861 myprint( "Ok with --maxlinelengthcmd $maxlinelengthcmd\n" ) ;
1862}
1863
1864if ( @regexmess ) {
1865 my $string = regexmess( q{ } ) ;
1866 myprint( "Checking each --regexmess command with an space string.\n" ) ;
1867 # string undef means one of the eval regex was bad.
1868 if ( not ( defined $string ) ) {
1869 #errors_incr( $sync, 'Warning: one of --regexmess option may be bad, check them' ) ;
1870 exit_clean( $sync, $EX_USAGE,
1871 "Error: one of --regexmess option is bad, check it\n"
1872 ) ;
1873 }
1874 myprint( "Ok with each --regexmess\n" ) ;
1875}
1876
1877if ( @skipmess ) {
1878 myprint( "Checking each --skipmess command with an space string.\n" ) ;
1879 my $match = skipmess( q{ } ) ;
1880 # match undef means one of the eval regex was bad.
1881 if ( not ( defined $match ) ) {
1882 $sync->{nb_errors}++ ;
1883 exit_clean( $sync, $EX_USAGE,
1884 "Error: one of --skipmess option is bad, check it\n"
1885 ) ;
1886 }
1887 myprint( "Ok with each --skipmess\n" ) ;
1888}
1889
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001890if ( $sync->{ regexflag } ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001891 myprint( "Checking each --regexflag command with an space string.\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001892 my $string = regexflags( $sync, q{ } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001893 # string undef means one of the eval regex was bad.
1894 if ( not ( defined $string ) ) {
1895 $sync->{nb_errors}++ ;
1896 exit_clean( $sync, $EX_USAGE,
1897 "Error: one of --regexflag option is bad, check it\n"
1898 ) ;
1899 }
1900 myprint( "Ok with each --regexflag\n" ) ;
1901}
1902
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001903$sync->{imap1} = login_imap( $sync->{host1}, $sync->{port1}, $sync->{user1}, $sync->{password1},
1904 $sync->{ssl1}, $sync->{tls1},
1905 $uid1, $split1, $sync->{ acc1 }, $sync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001906
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001907$sync->{imap2} = login_imap( $sync->{host2}, $sync->{port2}, $sync->{user2}, $sync->{password2},
1908 $sync->{ssl2}, $sync->{tls2},
1909 $uid2, $split2, $sync->{ acc2 }, $sync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001910
1911
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001912$sync->{ debug } and $sync->{imap1} and myprint( 'Host1 Buffer I/O: ', $sync->{imap1}->Buffer(), "\n" ) ;
1913$sync->{ debug } and $sync->{imap2} and myprint( 'Host2 Buffer I/O: ', $sync->{imap2}->Buffer(), "\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001914
1915
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001916if ( ! $sync->{imap1} || ! $sync->{imap2} )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001917{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001918 exit_most_errors( $sync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001919}
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001920
1921
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001922myprint( "Host1: state Authenticated\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001923myprint( "Host2: state Authenticated\n" ) ;
1924
1925myprint( 'Host1 capability once authenticated: ', join(q{ }, @{ $sync->{imap1}->capability() || [] }), "\n" ) ;
1926
1927#myprint( Data::Dumper->Dump( [ $sync->{imap1} ] ) ) ;
1928#myprint( "imap4rev1: " . $sync->{imap1}->imap4rev1() . "\n" ) ;
1929
1930myprint( 'Host2 capability once authenticated: ', join(q{ }, @{ $sync->{imap2}->capability() || [] }), "\n" ) ;
1931
1932imap_id_stuff( $sync ) ;
1933
1934#quota( $sync, $sync->{imap1}, 'h1' ) ; # quota on host1 is useless and pollute host2 output.
1935quota( $sync, $sync->{imap2}, 'h2' ) ;
1936
1937maxsize_setting( $sync ) ;
1938
1939if ( $sync->{ justlogin } ) {
1940 $sync->{imap1}->logout( ) ;
1941 $sync->{imap2}->logout( ) ;
1942 exit_clean( $sync, $EX_OK, "Exiting because of --justlogin\n" ) ;
1943}
1944
1945
1946#
1947# Folder stuff
1948#
1949
1950$h1_folders_wanted_nb = 0 ; # counter of folders to be done.
1951$h1_folders_wanted_ct = 0 ; # counter of folders done.
1952
1953# All folders on host1 and host2
1954
1955@h1_folders_all = sort $sync->{imap1}->folders( ) ;
1956@h2_folders_all = sort $sync->{imap2}->folders( ) ;
1957
1958myprint( 'Host1: found ', scalar @h1_folders_all , " folders.\n" ) ;
1959myprint( 'Host2: found ', scalar @h2_folders_all , " folders.\n" ) ;
1960
1961foreach my $f ( @h1_folders_all )
1962{
1963 $h1_folders_all{ $f } = 1
1964}
1965
1966foreach my $f ( @h2_folders_all )
1967{
1968 $h2_folders_all{ $f } = 1 ;
1969 $sync->{h2_folders_all_UPPER}{ uc $f } = 1 ;
1970}
1971
1972$sync->{h1_folders_all} = \%h1_folders_all ;
1973$sync->{h2_folders_all} = \%h2_folders_all ;
1974
1975
1976private_folders_separators_and_prefixes( ) ;
1977
1978
1979# Make a hash of subscribed folders in both servers.
1980
1981for ( $sync->{imap1}->subscribed( ) ) { $h1_subscribed_folder{ $_ } = 1 } ;
1982for ( $sync->{imap2}->subscribed( ) ) { $h2_subscribed_folder{ $_ } = 1 } ;
1983
1984
1985if ( defined $sync->{ subfolder1 } ) {
1986 subfolder1( $sync ) ;
1987}
1988
1989
1990
1991
1992if ( defined $sync->{ subfolder2 } ) {
1993 subfolder2( $sync ) ;
1994}
1995
1996if ( $fixInboxINBOX and ( my $reg = fix_Inbox_INBOX_mapping( \%h1_folders_all, \%h2_folders_all ) ) ) {
1997 push @{ $sync->{ regextrans2 } }, $reg ;
1998}
1999
2000
2001
2002if ( ( $sync->{ folder } and scalar @{ $sync->{ folder } } )
2003 or $subscribed
2004 or scalar @folderrec )
2005{
2006 # folders given by option --folder
2007 if ( $sync->{ folder } and scalar @{ $sync->{ folder } } ) {
2008 add_to_requested_folders( @{ $sync->{ folder } } ) ;
2009 }
2010
2011 # option --subscribed
2012 if ( $subscribed ) {
2013 add_to_requested_folders( keys %h1_subscribed_folder ) ;
2014 }
2015
2016 # option --folderrec
2017 if ( scalar @folderrec ) {
2018 foreach my $folderrec ( @folderrec ) {
2019 add_to_requested_folders( $sync->{imap1}->folders( $folderrec ) ) ;
2020 }
2021 }
2022}
2023else
2024{
2025 # no include, no folder/subscribed/folderrec options => all folders
2026 if ( not scalar @include ) {
2027 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" ) ;
2028 add_to_requested_folders( @h1_folders_all ) ;
2029 }
2030}
2031
2032
2033# consider (optional) includes and excludes
2034if ( scalar @include ) {
2035 foreach my $include ( @include ) {
2036 # No, do not add /x after the regex, never.
2037 # Users would kill you!
2038 my @included_folders = grep { /$include/ } @h1_folders_all ;
2039 add_to_requested_folders( @included_folders ) ;
2040 myprint( "Including folders matching pattern $include\n" . jux_utf8_list( @included_folders ) . "\n" ) ;
2041 }
2042}
2043
2044if ( scalar @exclude ) {
2045 foreach my $exclude ( @exclude ) {
2046 my @requested_folder = sort keys %requested_folder ;
2047 # No, do not add /x after the regex, never.
2048 # Users would kill you!
2049 my @excluded_folders = grep { /$exclude/ } @requested_folder ;
2050 remove_from_requested_folders( @excluded_folders ) ;
2051 myprint( "Excluding folders matching pattern $exclude\n" . jux_utf8_list( @excluded_folders ) . "\n" ) ;
2052 }
2053}
2054
2055
2056# sort before is not very powerful
2057# it adds --folderfirst and --folderlast even if they don't exist on host1
2058#@h1_folders_wanted = sort_requested_folders( ) ;
2059$sync->{h1_folders_wanted} = [ sort_requested_folders( ) ] ;
2060
2061# Remove no selectable folders
2062
2063
2064if ( $sync->{ checkfoldersexist } ) {
2065 my @h1_folders_wanted_exist ;
2066 myprint( "Host1: Checking wanted folders exist. Use --nocheckfoldersexist to avoid this check (shared of public namespace targeted).\n" ) ;
2067 foreach my $folder ( @{ $sync->{h1_folders_wanted} } ) {
2068 ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "Checking $folder exists on host1\n" ) ;
2069 if ( ! exists $h1_folders_all{ $folder } ) {
2070 myprint( "Host1: warning! ignoring folder $folder because it is not in host1 whole folders list.\n" ) ;
2071 next ;
2072 }else{
2073 push @h1_folders_wanted_exist, $folder ;
2074 }
2075 }
2076 @{ $sync->{h1_folders_wanted} } = @h1_folders_wanted_exist ;
2077}else{
2078 myprint( "Host1: Not checking that wanted folders exist. Remove --nocheckfoldersexist to get this check.\n" ) ;
2079}
2080
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002081setcheckselectable( $sync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002082
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002083checkselectable( $sync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002084
2085
2086
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002087# Bugfix OpenFind folders named like "kk \*123" are in fact "kk *123" (no \)
2088#foreach my $folder ( @{ $sync->{ h1_folders_wanted } } )
2089#{
2090# $folder =~ s{ \\\*}{ *}g ;
2091#}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002092
2093
2094# this hack is because LWP post does not pass well a hash in the $form parameter
2095# but it does pass well an array
2096%{ $sync->{f1f2h} } = split_around_equal( @{ $sync->{f1f2} } ) ;
2097
2098automap( $sync ) ;
2099
2100
2101foreach my $h1_fold ( @{ $sync->{h1_folders_wanted} } ) {
2102 my $h2_fold ;
2103 $h2_fold = imap2_folder_name( $sync, $h1_fold ) ;
2104 $h2_folders_from_1_wanted{ $h2_fold }++ ;
2105 if ( 1 < $h2_folders_from_1_wanted{ $h2_fold } ) {
2106 $h2_folders_from_1_several{ $h2_fold }++ ;
2107 }
2108}
2109
2110@h2_folders_from_1_wanted = sort keys %h2_folders_from_1_wanted;
2111
2112
2113foreach my $h1_fold ( @h1_folders_all ) {
2114 my $h2_fold ;
2115 $h2_fold = imap2_folder_name( $sync, $h1_fold ) ;
2116 $h2_folders_from_1_all{ $h2_fold }++ ;
2117 # Follows a fix to avoid deleting folder $sync->{ subfolder2 }
2118 # because it usually does not exist on host1.
2119 if ( $sync->{ subfolder2 } )
2120 {
2121 $h2_folders_from_1_all{ $sync->{ h2_prefix } . $sync->{ subfolder2 } }++ ;
2122 $h2_folders_from_1_all{ $sync->{ subfolder2 } }++ ;
2123 }
2124}
2125
2126
2127
2128myprint( << 'END_LISTING' ) ;
2129
2130++++ Listing folders
2131All foldernames are presented between brackets like [X] where X is the foldername.
2132When a foldername contains non-ASCII characters it is presented in the form
2133[X] = [Y] where
2134X is the imap foldername you have to use in command line options and
2135Y is the utf8 output just printed for convenience, to recognize it.
2136
2137END_LISTING
2138
2139myprint(
2140 "Host1: folders list (first the raw imap format then the [X] = [Y]):\n",
2141 $sync->{imap1}->list( ),
2142 "\n",
2143 jux_utf8_list( @h1_folders_all ),
2144 "\n",
2145 "Host2: folders list (first the raw imap format then the [X] = [Y]):\n",
2146 $sync->{imap2}->list( ),
2147 "\n",
2148 jux_utf8_list( @h2_folders_all ),
2149 "\n",
2150 q{}
2151) ;
2152
2153if ( $subscribed ) {
2154 myprint(
2155 'Host1 subscribed folders list: ',
2156 jux_utf8_list( sort keys %h1_subscribed_folder ), "\n",
2157 ) ;
2158}
2159
2160
2161
2162@h2_folders_not_in_1 = list_folders_in_2_not_in_1( ) ;
2163
2164if ( @h2_folders_not_in_1 ) {
2165 myprint( "Folders in host2 not in host1:\n",
2166 jux_utf8_list( @h2_folders_not_in_1 ), "\n" ) ;
2167}
2168
2169
2170if ( keys %{ $sync->{f1f2auto} } ) {
2171 myprint( "Folders mapping from --automap feature (use --f1f2 to override any mapping):\n" ) ;
2172 foreach my $h1_fold ( keys %{ $sync->{f1f2auto} } ) {
2173 my $h2_fold = $sync->{f1f2auto}{$h1_fold} ;
2174 myprintf( "%-40s -> %-40s\n",
2175 jux_utf8( $h1_fold ), jux_utf8( $h2_fold ) ) ;
2176 }
2177 myprint( "\n" ) ;
2178}
2179
2180if ( keys %{ $sync->{f1f2h} } ) {
2181 myprint( "Folders mapping from --f1f2 options, it overrides --automap:\n" ) ;
2182 foreach my $h1_fold ( keys %{ $sync->{f1f2h} } ) {
2183 my $h2_fold = $sync->{f1f2h}{$h1_fold} ;
2184 my $warn = q{} ;
2185 if ( not exists $h1_folders_all{ $h1_fold } ) {
2186 $warn = "BUT $h1_fold does NOT exist on host1!" ;
2187 }
2188 myprintf( "%-40s -> %-40s %s\n",
2189 jux_utf8( $h1_fold ), jux_utf8( $h2_fold ), $warn ) ;
2190 }
2191 myprint( "\n" ) ;
2192}
2193
2194exit_clean( $sync, $EX_OK, "Exiting because of --justfolderlists\n" ) if ( $sync->{ justfolderlists } ) ;
2195exit_clean( $sync, $EX_OK, "Exiting because of --justautomap\n" ) if ( $sync->{ justautomap } ) ;
2196
2197debugsleep( $sync ) ;
2198
2199if ( $sync->{ skipemptyfolders } )
2200{
2201 myprint( "Host1: will not syncing empty folders on host1. Use --noskipemptyfolders to create them anyway on host2\n") ;
2202}
2203
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002204if ( $sync->{ checknoabletosearch } )
2205{
2206 myprint( "Checking SEARCH ALL works on both accounts. To avoid that check, use --nochecknoabletosearch\n" ) ;
2207 my $check1 = checknoabletosearch( $sync, $sync->{ imap1 }, 'INBOX', 'Host1' ) ;
2208 my $check2 = checknoabletosearch( $sync, $sync->{ imap2 }, 'INBOX', 'Host2' ) ;
2209 if ( $check1 or $check2 )
2210 {
2211 myprint( "At least one account can not SEARCH ALL. So acting like --noabletosearch\n" ) ;
2212 $sync->{abletosearch} = 0 ;
2213 $sync->{abletosearch1} = 0 ;
2214 $sync->{abletosearch2} = 0 ;
2215 }
2216 else
2217 {
2218 myprint( "Good! SEARCH ALL works on both accounts.\n" ) ;
2219 }
2220}
2221
2222
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002223
2224if ( $sync->{ foldersizes } ) {
2225
2226 foldersizes_at_the_beggining( $sync ) ;
2227 #foldersizes_at_the_beggining_old( $sync ) ;
2228}
2229
2230
2231
2232if ( $sync->{ justfoldersizes } )
2233{
2234 exit_clean( $sync, $EX_OK, "Exiting because of --justfoldersizes\n" ) ;
2235}
2236
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002237$sync->{can_do_stats} = 1 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002238
2239if ( $sync->{ delete1emptyfolders } ) {
2240 delete1emptyfolders( $sync ) ;
2241}
2242
2243delete_folders_in_2_not_in_1( ) if $delete2folders ;
2244
2245# folder loop
2246$h1_folders_wanted_nb = scalar @{ $sync->{h1_folders_wanted} } ;
2247
2248myprint( "++++ Looping on each one of $h1_folders_wanted_nb folders to sync\n" ) ;
2249
2250$sync->{begin_transfer_time} = time ;
2251
2252my %uid_candidate_for_deletion ;
2253my %uid_candidate_no_deletion ;
2254
2255$sync->{ h2_folders_of_md5 } = { } ;
2256
2257
2258FOLDER: foreach my $h1_fold ( @{ $sync->{h1_folders_wanted} } )
2259{
2260 $sync->{ h1_current_folder } = $h1_fold ;
2261 eta_print( $sync ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002262 abortifneeded( $sync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002263 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2264
2265 my $h2_fold = imap2_folder_name( $sync, $h1_fold ) ;
2266 $sync->{ h2_current_folder } = $h2_fold ;
2267
2268 $h1_folders_wanted_ct++ ;
2269 myprintf( "Folder %7s %-35s -> %-35s\n", "$h1_folders_wanted_ct/$h1_folders_wanted_nb",
2270 jux_utf8( $h1_fold ), jux_utf8( $h2_fold ) ) ;
2271 myprint( debugmemory( $sync, " at folder loop" ) ) ;
2272
2273 # host1 can not be fetched read only, select is needed because of expunge.
2274 select_folder( $sync, $sync->{imap1}, $h1_fold, 'Host1' ) or next FOLDER ;
2275
2276 debugsleep( $sync ) ;
2277
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002278 my $h1_msgs_all_hash_ref ;
2279 my @h1_msgs ;
2280 my $h1_msgs_nb ;
2281 my $h1_msgs_nb_from_select ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002282
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002283 $h1_msgs_nb_from_select = count_from_select( $sync->{imap1}->History ) ;
2284 myprint( "Host1: folder [$h1_fold] has $h1_msgs_nb_from_select messages in total (mentioned by SELECT)\n" ) ;
2285
2286 if ( $sync->{ skipemptyfolders } and 0 == $h1_msgs_nb_from_select ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002287 myprint( "Host1: skipping empty host1 folder [$h1_fold]\n" ) ;
2288 next FOLDER ;
2289 }
2290
2291 # Code added from https://github.com/imapsync/imapsync/issues/95
2292 # Thanks jh1995
2293 # Goal: do not create folder if --search or --max/minage return 0 message.
2294 # 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 +02002295 if ( $sync->{ skipemptyfolders } or $sync->{ dry } )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002296 {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002297 $h1_msgs_all_hash_ref = { } ;
2298 @h1_msgs = select_msgs( $sync->{imap1}, $h1_msgs_all_hash_ref, $sync->{ search1 }, $sync->{abletosearch1}, $h1_fold ) ;
2299
2300 $h1_msgs_nb = scalar( @h1_msgs ) ;
2301 if ( 0 == $h1_msgs_nb and $sync->{ skipemptyfolders } ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002302 myprint( "Host1: skipping empty host1 folder [$h1_fold] (0 message found by SEARCH)\n" ) ;
2303 next FOLDER ;
2304 }
2305 }
2306
2307 if ( ! exists $h2_folders_all{ $h2_fold } ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002308 # In --dry mode I could count the messages to be transfered instead of 0
2309 # Messages transferred : 0 (could be 0 without dry mode)
2310 if ( ! create_folder( $sync, $sync->{imap2}, $h2_fold, $h1_fold ) )
2311 {
2312 if ( $sync->{ dry } )
2313 {
2314 $nb_msg_skipped_dry_mode += $h1_msgs_nb ;
2315 }
2316 next FOLDER ;
2317 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002318 }
2319
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002320 acls_sync( $sync, $h1_fold, $h2_fold ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002321
2322 # Sometimes the folder on host2 is listed (it exists) but is
2323 # not selectable but becomes selectable by a create (Gmail)
2324 select_folder( $sync, $sync->{imap2}, $h2_fold, 'Host2' )
2325 or ( create_folder( $sync, $sync->{imap2}, $h2_fold, $h1_fold )
2326 and select_folder( $sync, $sync->{imap2}, $h2_fold, 'Host2' ) )
2327 or next FOLDER ;
2328 my @select_results = $sync->{imap2}->Results( ) ;
2329
2330 my $h2_fold_nb_messages = count_from_select( @select_results ) ;
2331 myprint( "Host2: folder [$h2_fold] has $h2_fold_nb_messages messages in total (mentioned by SELECT)\n" ) ;
2332
2333 my $permanentflags2 = permanentflags( @select_results ) ;
2334 myprint( "Host2: folder [$h2_fold] permanentflags: $permanentflags2\n" ) ;
2335
2336 if ( $sync->{ expunge1 } )
2337 {
2338 myprint( "Host1: Expunging $h1_fold $sync->{dry_message}\n" ) ;
2339 if ( ! $sync->{dry} )
2340 {
2341 $sync->{imap1}->expunge( ) ;
2342 }
2343 }
2344
2345 if ( ( ( $subscribe and exists $h1_subscribed_folder{ $h1_fold } ) or $subscribeall )
2346 and not exists $h2_subscribed_folder{ $h2_fold } )
2347 {
2348 myprint( "Host2: Subscribing to folder $h2_fold\n" ) ;
2349 if ( ! $sync->{dry} ) { $sync->{imap2}->subscribe( $h2_fold ) } ;
2350 }
2351
2352 next FOLDER if ( $sync->{ justfolders } ) ;
2353
2354 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2355
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002356
2357 if ( ! defined $h1_msgs_nb )
2358 {
2359 $h1_msgs_all_hash_ref = { } ;
2360 @h1_msgs = select_msgs( $sync->{imap1}, $h1_msgs_all_hash_ref, $sync->{ search1 }, $sync->{abletosearch1}, $h1_fold );
2361 $h1_msgs_nb = scalar @h1_msgs ;
2362 }else{
2363 # select_msgs already done.
2364 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002365
2366 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2367
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002368 myprint( "Host1: folder [$h1_fold] considering $h1_msgs_nb messages\n" ) ;
2369 ( $sync->{ debug } or $debuglist ) and myprint( "Host1: folder [$h1_fold] considering $h1_msgs_nb messages, LIST gives: @h1_msgs\n" ) ;
2370 $sync->{ debug } and myprint( "Host1: selecting messages of folder [$h1_fold] took ", timenext( $sync ), " s\n" ) ;
2371
2372 my $h2_msgs_all_hash_ref = { } ;
2373 my @h2_msgs = select_msgs( $sync->{imap2}, $h2_msgs_all_hash_ref, $sync->{ search2 }, $sync->{abletosearch2}, $h2_fold ) ;
2374
2375 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2376
2377 my $h2_msgs_nb = scalar @h2_msgs ;
2378
2379 myprint( "Host2: folder [$h2_fold] considering $h2_msgs_nb messages\n" ) ;
2380 ( $sync->{ debug } or $debuglist ) and myprint( "Host2: folder [$h2_fold] considering $h2_msgs_nb messages, LIST gives: @h2_msgs\n" ) ;
2381 $sync->{ debug } and myprint( "Host2: selecting messages of folder [$h2_fold] took ", timenext( $sync ), " s\n" ) ;
2382
2383 my $cache_base = "$sync->{ tmpdir }/imapsync_cache/" ;
2384 my $cache_dir = cache_folder( $cache_base,
2385 "$sync->{host1}/$sync->{user1}/$sync->{host2}/$sync->{user2}", $h1_fold, $h2_fold ) ;
2386 my ( $cache_1_2_ref, $cache_2_1_ref ) = ( {}, {} ) ;
2387
2388 my $h1_uidvalidity = $sync->{imap1}->uidvalidity( ) || q{} ;
2389 my $h2_uidvalidity = $sync->{imap2}->uidvalidity( ) || q{} ;
2390
2391 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2392
2393 if ( $usecache ) {
2394 myprint( "Local cache directory: $cache_dir ( " . length( $cache_dir ) . " characters long )\n" ) ;
2395 mkpath( "$cache_dir" ) ;
2396 ( $cache_1_2_ref, $cache_2_1_ref )
2397 = get_cache( $cache_dir, \@h1_msgs, \@h2_msgs, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) ;
2398 myprint( 'CACHE h1 h2: ', scalar keys %{ $cache_1_2_ref } , " files\n" ) ;
2399 $sync->{ debug } and myprint( '[',
2400 map ( { "$_->$cache_1_2_ref->{$_} " } keys %{ $cache_1_2_ref } ), " ]\n" ) ;
2401 }
2402
2403 my %h1_hash = ( ) ;
2404 my %h2_hash = ( ) ;
2405
2406 my ( %h1_msgs, %h2_msgs ) ;
2407 @h1_msgs{ @h1_msgs } = ( ) ;
2408 @h2_msgs{ @h2_msgs } = ( ) ;
2409
2410 my @h1_msgs_in_cache = sort { $a <=> $b } keys %{ $cache_1_2_ref } ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002411 my @h2_msgs_in_cache = sort { $a <=> $b } keys %{ $cache_2_1_ref } ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002412
2413 my ( %h1_msgs_not_in_cache, %h2_msgs_not_in_cache ) ;
2414 %h1_msgs_not_in_cache = %h1_msgs ;
2415 %h2_msgs_not_in_cache = %h2_msgs ;
2416 delete @h1_msgs_not_in_cache{ @h1_msgs_in_cache } ;
2417 delete @h2_msgs_not_in_cache{ @h2_msgs_in_cache } ;
2418
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002419 my @h1_msgs_not_in_cache = sort { $a <=> $b } keys %h1_msgs_not_in_cache ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002420 #myprint( "h1_msgs_not_in_cache: [@h1_msgs_not_in_cache]\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002421 my @h2_msgs_not_in_cache = sort { $a <=> $b } keys %h2_msgs_not_in_cache ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002422
2423 my @h2_msgs_delete2_not_in_cache = () ;
2424 %h1_msgs_copy_by_uid = ( ) ;
2425
2426 if ( $useuid ) {
2427 # use uid so we have to avoid getting header
2428 @h1_msgs_copy_by_uid{ @h1_msgs_not_in_cache } = ( ) ;
2429 @h2_msgs_delete2_not_in_cache = @h2_msgs_not_in_cache if $usecache ;
2430 @h1_msgs_not_in_cache = ( ) ;
2431 @h2_msgs_not_in_cache = ( ) ;
2432
2433 #myprint( "delete2: @h2_msgs_delete2_not_in_cache\n" ) ;
2434 }
2435
2436 $sync->{ debug } and myprint( "Host1: parsing headers of folder [$h1_fold]\n" ) ;
2437
2438 my ($h1_heads_ref, $h1_fir_ref) = ({}, {});
2439 $h1_heads_ref = $sync->{imap1}->parse_headers([@h1_msgs_not_in_cache], @useheader) if (@h1_msgs_not_in_cache);
2440 $sync->{ debug } and myprint( "Host1: parsing headers of folder [$h1_fold] took ", timenext( $sync ), " s\n" ) ;
2441
2442 @{ $h1_fir_ref }{@h1_msgs} = ( undef ) ;
2443
2444 $sync->{ debug } and myprint( "Host1: getting flags idate and sizes of folder [$h1_fold]\n" ) ;
2445
2446 my @h1_common_fetch_param = ( 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE' ) ;
2447 if ( $sync->{ synclabels } or $sync->{ resynclabels } ) { push @h1_common_fetch_param, 'X-GM-LABELS' ; }
2448
2449 if ( $sync->{abletosearch1} )
2450 {
2451 $h1_fir_ref = $sync->{imap1}->fetch_hash( \@h1_msgs, @h1_common_fetch_param, $h1_fir_ref )
2452 if ( @h1_msgs ) ;
2453 }
2454 else
2455 {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002456 my $fetch_hash_uids = $fetch_hash_set || "1:*" ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002457 $h1_fir_ref = $sync->{imap1}->fetch_hash( $fetch_hash_uids, @h1_common_fetch_param, $h1_fir_ref )
2458 if ( @h1_msgs ) ;
2459 }
2460
2461 $sync->{ debug } and myprint( "Host1: getting flags idate and sizes of folder [$h1_fold] took ", timenext( $sync ), " s\n" ) ;
2462 if ( ! $h1_fir_ref )
2463 {
2464 my $error = join( q{}, "Host1: folder $h1_fold : Could not fetch_hash ",
2465 scalar @h1_msgs, ' msgs: ', $sync->{imap1}->LastError || q{}, "\n" ) ;
2466 errors_incr( $sync, $error ) ;
2467 next FOLDER ;
2468 }
2469
2470 my @h1_msgs_duplicate;
2471 foreach my $m ( @h1_msgs_not_in_cache )
2472 {
2473 my $rc = parse_header_msg( $sync, $sync->{imap1}, $m, $h1_heads_ref, $h1_fir_ref, 'Host1', \%h1_hash ) ;
2474 if ( ! defined $rc )
2475 {
2476 my $h1_size = $h1_fir_ref->{$m}->{'RFC822.SIZE'} || 0;
2477 myprint( "Host1: $h1_fold/$m size $h1_size ignored (no wanted headers so we ignore this message. To solve this: use --addheader)\n" ) ;
2478 $sync->{ total_bytes_skipped } += $h1_size ;
2479 $sync->{ nb_msg_skipped } += 1 ;
2480 $sync->{ h1_nb_msg_noheader } +=1 ;
2481 $sync->{ h1_nb_msg_processed } +=1 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002482 } elsif( 0 == $rc )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002483 {
2484 # duplicate
2485 push @h1_msgs_duplicate, $m;
2486 # duplicate, same id same size?
2487 my $h1_size = $h1_fir_ref->{$m}->{'RFC822.SIZE'} || 0;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002488
2489 $sync->{ acc1 }->{ nb_msg_duplicate } += 1;
2490 if ( ! $sync->{ syncduplicates } ) {
2491 $sync->{ nb_msg_skipped } += 1 ;
2492 $sync->{ h1_nb_msg_processed } +=1 ;
2493 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002494 }
2495 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002496
2497
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002498 my $h1_msgs_duplicate_nb = scalar @h1_msgs_duplicate ;
2499
2500 myprint( "Host1: folder [$h1_fold] selected $h1_msgs_nb messages, duplicates $h1_msgs_duplicate_nb\n" ) ;
2501
2502 $sync->{ debug } and myprint( 'Host1: whole time parsing headers took ', timenext( $sync ), " s\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002503
2504
2505
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002506 # Getting headers and metada can be so long that host2 might be disconnected here
2507 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2508
2509
2510 $sync->{ debug } and myprint( "Host2: parsing headers of folder [$h2_fold]\n" ) ;
2511
2512 my ($h2_heads_ref, $h2_fir_ref) = ( {}, {} );
2513 $h2_heads_ref = $sync->{imap2}->parse_headers([@h2_msgs_not_in_cache], @useheader) if (@h2_msgs_not_in_cache);
2514 $sync->{ debug } and myprint( "Host2: parsing headers of folder [$h2_fold] took ", timenext( $sync ), " s\n" ) ;
2515
2516 $sync->{ debug } and myprint( "Host2: getting flags idate and sizes of folder [$h2_fold]\n" ) ;
2517 @{ $h2_fir_ref }{@h2_msgs} = ( ); # fetch_hash can select by uid with last arg as ref
2518
2519
2520 my @h2_common_fetch_param = ( 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE' ) ;
2521 if ( $sync->{ synclabels } or $sync->{ resynclabels } ) { push @h2_common_fetch_param, 'X-GM-LABELS' ; }
2522
2523 if ( $sync->{abletosearch2} and scalar( @h2_msgs ) ) {
2524 $h2_fir_ref = $sync->{imap2}->fetch_hash( \@h2_msgs, @h2_common_fetch_param, $h2_fir_ref) ;
2525 }else{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002526 my $fetch_hash_uids = $fetch_hash_set || "1:*" ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002527 $h2_fir_ref = $sync->{imap2}->fetch_hash( $fetch_hash_uids, @h2_common_fetch_param, $h2_fir_ref )
2528 if ( @h2_msgs ) ;
2529 }
2530
2531 $sync->{ debug } and myprint( "Host2: getting flags idate and sizes of folder [$h2_fold] took ", timenext( $sync ), " s\n" ) ;
2532
2533 my @h2_msgs_duplicate;
2534 foreach my $m (@h2_msgs_not_in_cache) {
2535 my $rc = parse_header_msg( $sync, $sync->{imap2}, $m, $h2_heads_ref, $h2_fir_ref, 'Host2', \%h2_hash ) ;
2536 my $h2_size = $h2_fir_ref->{$m}->{'RFC822.SIZE'} || 0 ;
2537 if (! defined $rc ) {
2538 myprint( "Host2: $h2_fold/$m size $h2_size ignored (no wanted headers so we ignore this message)\n" ) ;
2539 $h2_nb_msg_noheader += 1 ;
2540 } elsif( 0 == $rc ) {
2541 # duplicate
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002542 $sync->{ acc2 }->{ nb_msg_duplicate } += 1 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002543 push @h2_msgs_duplicate, $m ;
2544 }
2545 }
2546
2547 # %h2_folders_of_md5
2548 foreach my $md5 ( keys %h2_hash ) {
2549 $sync->{ h2_folders_of_md5 }->{ $md5 }->{ $h2_fold } ++ ;
2550 }
2551 # %h1_folders_of_md5
2552 foreach my $md5 ( keys %h1_hash ) {
2553 $sync->{ h1_folders_of_md5 }->{ $md5 }->{ $h2_fold } ++ ;
2554 }
2555
2556
2557 my $h2_msgs_duplicate_nb = scalar @h2_msgs_duplicate ;
2558
2559 myprint( "Host2: folder [$h2_fold] selected $h2_msgs_nb messages, duplicates $h2_msgs_duplicate_nb\n" ) ;
2560
2561 $sync->{ debug } and myprint( 'Host2 whole time parsing headers took ', timenext( $sync ), " s\n" ) ;
2562
2563 $sync->{ debug } and myprint( "++++ Verifying [$h1_fold] -> [$h2_fold]\n" ) ;
2564 # messages in host1 that are not in host2
2565
2566 my @h1_hash_keys_sorted_by_uid
2567 = sort {$h1_hash{$a}{'m'} <=> $h1_hash{$b}{'m'}} keys %h1_hash;
2568
2569 #myprint( map { $h1_hash{$_}{'m'} . q{ }} @h1_hash_keys_sorted_by_uid ) ;
2570
2571 my @h2_hash_keys_sorted_by_uid
2572 = sort {$h2_hash{$a}{'m'} <=> $h2_hash{$b}{'m'}} keys %h2_hash;
2573
2574 # Deletions on account2.
2575
2576 if( $sync->{ delete2duplicates } and not exists $h2_folders_from_1_several{ $h2_fold } ) {
2577 my @h2_expunge ;
2578
2579 foreach my $h2_msg ( @h2_msgs_duplicate ) {
2580 myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted [duplicate] on host2 $sync->{dry_message}\n" ) ;
2581 push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 } ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002582 if ( ! $sync->{ dry } ) {
2583 $sync->{ imap2 }->delete_message( $h2_msg ) ;
2584 $sync->{ acc2 }->{ nb_msg_deleted } += 1 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002585 }
2586 }
2587 my $cnt = scalar @h2_expunge ;
2588 if( @h2_expunge and not $sync->{ expunge2 } ) {
2589 myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n" ) ;
2590 $sync->{imap2}->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ;
2591 }
2592 if ( $sync->{ expunge2 } ){
2593 myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n" ) ;
2594 $sync->{imap2}->expunge( ) if ! $sync->{dry} ;
2595 }
2596 }
2597
2598 if( $sync->{ delete2 } and not exists $h2_folders_from_1_several{ $h2_fold } ) {
2599 # No host1 folders f1a f1b ... going all to same f2 (via --regextrans2)
2600 my @h2_expunge;
2601 foreach my $m_id (@h2_hash_keys_sorted_by_uid) {
2602 #myprint( "$m_id " ) ;
2603 if ( ! exists $h1_hash{$m_id} ) {
2604 my $h2_msg = $h2_hash{$m_id}{'m'};
2605 my $h2_flags = $h2_hash{$m_id}{'F'} || q{};
2606 my $isdel = $h2_flags =~ /\B\\Deleted\b/x ? 1 : 0;
2607 myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted on host2 [$m_id] $sync->{dry_message}\n" )
2608 if ! $isdel;
2609 push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 };
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002610 if ( ! ( $sync->{ dry } or $isdel ) ) {
2611 $sync->{ imap2 }->delete_message( $h2_msg );
2612 $sync->{ acc2 }->{ nb_msg_deleted } += 1;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002613 }
2614 }
2615 }
2616 foreach my $h2_msg ( @h2_msgs_delete2_not_in_cache ) {
2617 myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted [not in cache] on host2 $sync->{dry_message}\n" ) ;
2618 push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 };
2619 if ( ! $sync->{dry} ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002620 $sync->{ imap2 }->delete_message( $h2_msg );
2621 $sync->{ acc2 }->{ nb_msg_deleted } += 1;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002622 }
2623 }
2624 my $cnt = scalar @h2_expunge ;
2625
2626 if( @h2_expunge and not $sync->{ expunge2 } ) {
2627 myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n" ) ;
2628 $sync->{imap2}->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ;
2629 }
2630 if ( $sync->{ expunge2 } ) {
2631 myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n" ) ;
2632 $sync->{imap2}->expunge( ) if ! $sync->{dry} ;
2633 }
2634 }
2635
2636 if( $sync->{ delete2 } and exists $h2_folders_from_1_several{ $h2_fold } ) {
2637 myprint( "Host2: folder $h2_fold $h2_folders_from_1_several{ $h2_fold } folders left to sync there\n" ) ;
2638 my @h2_expunge;
2639 foreach my $m_id ( @h2_hash_keys_sorted_by_uid ) {
2640 my $h2_msg = $h2_hash{ $m_id }{ 'm' } ;
2641 if ( ! exists $h1_hash{ $m_id } ) {
2642 my $h2_flags = $h2_hash{ $m_id }{ 'F' } || q{} ;
2643 my $isdel = $h2_flags =~ /\B\\Deleted\b/x ? 1 : 0 ;
2644 if ( ! $isdel ) {
2645 $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion [$m_id]\n" ) ;
2646 $uid_candidate_for_deletion{ $h2_fold }{ $h2_msg }++ ;
2647 }
2648 }else{
2649 $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [$m_id]\n" ) ;
2650 $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
2651 }
2652 }
2653 foreach my $h2_msg ( @h2_msgs_delete2_not_in_cache ) {
2654 myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion [not in cache]\n" ) ;
2655 $uid_candidate_for_deletion{ $h2_fold }{ $h2_msg }++ ;
2656 }
2657
2658 foreach my $h2_msg ( @h2_msgs_in_cache ) {
2659 myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [in cache]\n" ) ;
2660 $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
2661 }
2662
2663
2664 if ( 0 == $h2_folders_from_1_several{ $h2_fold } ) {
2665 # last host1 folder going to $h2_fold
2666 myprint( "Last host1 folder going to $h2_fold\n" ) ;
2667 foreach my $h2_msg ( keys %{ $uid_candidate_for_deletion{ $h2_fold } } ) {
2668 $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion\n" ) ;
2669 if ( exists $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg } ) {
2670 $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg canceled deletion\n" ) ;
2671 }else{
2672 myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted $sync->{dry_message}\n" ) ;
2673 push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 } ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002674 if ( ! $sync->{ dry} ) {
2675 $sync->{ imap2 }->delete_message( $h2_msg ) ;
2676 $sync->{ acc2 }->{ nb_msg_deleted } += 1 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002677 }
2678 }
2679 }
2680 }
2681
2682 my $cnt = scalar @h2_expunge ;
2683 if( @h2_expunge and not $sync->{ expunge2 } ) {
2684 myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n" ) ;
2685 $sync->{imap2}->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ;
2686 }
2687 if ( $sync->{ expunge2 } ) {
2688 myprint( "Host2: Expunging host2 folder $h2_fold $sync->{dry_message}\n" ) ;
2689 $sync->{imap2}->expunge( ) if ! $sync->{dry} ;
2690 }
2691
2692 $h2_folders_from_1_several{ $h2_fold }-- ;
2693 }
2694
2695 my $h2_uidnext = $sync->{imap2}->uidnext( $h2_fold ) ;
2696 $sync->{ debug } and myprint( "Host2: uidnext is $h2_uidnext\n" ) ;
2697 $h2_uidguess = $h2_uidnext ;
2698
2699 # Getting host2 headers, metada and delete2 stuff can be so long that host1 might be disconnected here
2700 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2701
2702 my @h1_msgs_to_delete ;
2703 MESS: foreach my $m_id (@h1_hash_keys_sorted_by_uid) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002704 abortifneeded( $sync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002705 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2706
2707 #myprint( "h1_nb_msg_processed: $sync->{ h1_nb_msg_processed }\n" ) ;
2708 my $h1_size = $h1_hash{$m_id}{'s'};
2709 my $h1_msg = $h1_hash{$m_id}{'m'};
2710 my $h1_idate = $h1_hash{$m_id}{'D'};
2711
2712 #my $labels = labels( $sync->{imap1}, $h1_msg ) ;
2713 #print "LABELS: $labels\n" ;
2714
2715 if ( ( not exists $h2_hash{ $m_id } )
2716 and ( not ( exists $sync->{ h2_folders_of_md5 }->{ $m_id } )
2717 or not $skipcrossduplicates ) )
2718 {
2719 # copy
2720 my $h2_msg = copy_message( $sync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) ;
2721 if ( $h2_msg and $sync->{ delete1 } and not $sync->{ expungeaftereach } ) {
2722 # not expunged
2723 push @h1_msgs_to_delete, $h1_msg ;
2724 }
2725
2726 # A bug here with imapsync 1.920, fixed in 1.921
2727 # Added $h2_msg in the condition. Errors of APPEND were not counted as missing messages on host2!
2728 if ( $h2_msg and not $sync->{ dry } )
2729 {
2730 $sync->{ h2_folders_of_md5 }->{ $m_id }->{ $h2_fold } ++ ;
2731 }
2732
2733 #
2734 if( $sync->{ delete2 } and ( exists $h2_folders_from_1_several{ $h2_fold } ) and $h2_msg ) {
2735 myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [fresh copy] on host2\n" ) ;
2736 $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
2737 }
2738
2739 if ( total_bytes_max_reached( $sync ) ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002740 # Still a bug when using --delete1 --noexpungeaftereach
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002741 # same thing below on all total_bytes_max_reached!
2742 last FOLDER ;
2743 }
2744 next MESS;
2745 }
2746 else
2747 {
2748 # already on host2
2749 if ( exists $h2_hash{ $m_id } )
2750 {
2751 my $h2_msg = $h2_hash{$m_id}{'m'} ;
2752 $sync->{ debug } and myprint( "Host1: found that msg $h1_fold/$h1_msg equals Host2 $h2_fold/$h2_msg\n" ) ;
2753 if ( $usecache )
2754 {
2755 $debugcache and myprint( "touch $cache_dir/${h1_msg}_$h2_msg\n" ) ;
2756 touch( "$cache_dir/${h1_msg}_$h2_msg" )
2757 or croak( "Couldn't touch $cache_dir/${h1_msg}_$h2_msg" ) ;
2758 }
2759 }
2760 elsif( exists $sync->{ h2_folders_of_md5 }->{ $m_id } )
2761 {
2762 my @folders_dup = keys %{ $sync->{ h2_folders_of_md5 }->{ $m_id } } ;
2763 ( $sync->{ debug } or $debugcrossduplicates ) and myprint( "Host1: found that msg $h1_fold/$h1_msg is also in Host2 folders @folders_dup\n" ) ;
2764 $sync->{ h2_nb_msg_crossdup } +=1 ;
2765 }
2766 $sync->{ total_bytes_skipped } += $h1_size ;
2767 $sync->{ nb_msg_skipped } += 1 ;
2768 $sync->{ h1_nb_msg_processed } +=1 ;
2769 }
2770
2771 if ( exists $h2_hash{ $m_id } ) {
2772 #$debug and myprint( "MESSAGE $m_id\n" ) ;
2773 my $h2_msg = $h2_hash{$m_id}{'m'};
2774 if ( $sync->{resyncflags} ) {
2775 sync_flags_fir( $sync, $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) ;
2776 }
2777 # Good
2778 my $h2_size = $h2_hash{$m_id}{'s'};
2779 $sync->{ debug } and myprint(
2780 "Host1: size msg $h1_fold/$h1_msg = $h1_size <> $h2_size = Host2 $h2_fold/$h2_msg\n" ) ;
2781
2782 if ( $sync->{ resynclabels } )
2783 {
2784 resynclabels( $sync, $h1_msg, $h2_msg, $h1_fir_ref, $h2_fir_ref, $h1_fold )
2785 }
2786 }
2787
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002788 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002789
2790 if ( $sync->{ delete1 } ) {
2791 push @h1_msgs_to_delete, $h1_msg ;
2792 }
2793 }
2794 # END MESS: loop
2795
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002796 # @h1_msgs_in_cache are already synced too.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002797 delete_message_on_host1( $sync, $h1_fold, $sync->{ expunge1 }, @h1_msgs_to_delete, @h1_msgs_in_cache ) ;
2798
2799 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2800
2801 # MESS_IN_CACHE:
2802 if ( ! $sync->{ delete1 } )
2803 {
2804 foreach my $h1_msg ( @h1_msgs_in_cache )
2805 {
2806 my $h2_msg = $cache_1_2_ref->{ $h1_msg } ;
2807 $debugcache and myprint( "cache messages update flags $h1_msg->$h2_msg\n" ) ;
2808 if ( $sync->{resyncflags} )
2809 {
2810 sync_flags_fir( $sync, $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) ;
2811 }
2812 my $h1_size = $h1_fir_ref->{ $h1_msg }->{ 'RFC822.SIZE' } || 0 ;
2813 $sync->{ total_bytes_skipped } += $h1_size;
2814 $sync->{ nb_msg_skipped } += 1;
2815 $sync->{ h1_nb_msg_processed } +=1 ;
2816 }
2817 }
2818
2819 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2820
2821 @h1_msgs_to_delete = ( ) ;
2822 #myprint( "Messages by uid: ", map { "$_ " } keys %h1_msgs_copy_by_uid, "\n" ) ;
2823 # MESS_BY_UID:
2824 foreach my $h1_msg ( sort { $a <=> $b } keys %h1_msgs_copy_by_uid )
2825 {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002826 abortifneeded( $sync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002827 $sync->{ debug } and myprint( "Copy by uid $h1_fold/$h1_msg\n" ) ;
2828 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2829
2830 my $h2_msg = copy_message( $sync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) ;
2831 if( $sync->{ delete2 } and exists $h2_folders_from_1_several{ $h2_fold } and $h2_msg ) {
2832 myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [fresh copy] on host2\n" ) ;
2833 $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
2834 }
2835 last FOLDER if total_bytes_max_reached( $sync ) ;
2836 }
2837
2838 if ( $sync->{ expunge1 } ){
2839 myprint( "Host1: Expunging folder $h1_fold $sync->{dry_message}\n" ) ;
2840 if ( ! $sync->{dry} ) { $sync->{imap1}->expunge( ) } ;
2841 }
2842 if ( $sync->{ expunge2 } ){
2843 myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n" ) ;
2844 if ( ! $sync->{dry} ) { $sync->{imap2}->expunge( ) } ;
2845 }
2846 $sync->{ debug } and myprint( 'Time: ', timenext( $sync ), " s\n" ) ;
2847}
2848
2849eta_print( $sync ) ;
2850
2851myprint( "++++ End looping on each folder\n" ) ;
2852
2853if ( $sync->{ delete1 } and $sync->{ delete1emptyfolders } ) {
2854 delete1emptyfolders( $sync ) ;
2855}
2856
2857( $sync->{ debug } or $sync->{debugfolders} ) and myprint( 'Time: ', timenext( $sync ), " s\n" ) ;
2858
2859
2860if ( $sync->{ foldersizesatend } ) {
2861 myprint( << 'END_SIZE' ) ;
2862
2863Folders sizes after the synchronization.
2864You can remove this foldersizes listing by using "--nofoldersizesatend"
2865END_SIZE
2866
2867 foldersizesatend( $sync ) ;
2868}
2869
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002870#$sync->{imap1}->State( 0 ); # Unconnected
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002871if ( ! lost_connection( $sync, $sync->{imap1}, "for host1 [$sync->{host1}]" ) ) { $sync->{imap1}->logout( ) ; }
2872if ( ! lost_connection( $sync, $sync->{imap2}, "for host2 [$sync->{host2}]" ) ) { $sync->{imap2}->logout( ) ; }
2873
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002874do_and_print_stats( $sync ) ;
2875
2876
2877if ( $sync->{errorsdump} and $sync->{nb_errors} )
2878{
2879 myprint( errors_listing( $sync ) ) ;
2880}
2881
2882
2883if ( $sync->{testslive} or $sync->{testslive6} )
2884{
2885 tests_live_result( $sync->{nb_errors} ) ;
2886}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002887
2888
2889
2890if ( $sync->{nb_errors} )
2891{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002892 my $exit_value = $EXIT_VALUE_OF_ERR_TYPE{ $sync->{most_common_error} } || $EXIT_CATCH_ALL ;
2893 exit_clean( $sync, $exit_value ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002894}
2895else
2896{
2897 exit_clean( $sync, $EX_OK ) ;
2898}
2899
2900return ;
2901}
2902
2903# END of sub single_sync
2904
2905
2906# subroutines
2907sub myprint
2908{
2909 #print @ARG ;
2910 print { $sync->{ tee } || \*STDOUT } @ARG ;
2911 return ;
2912}
2913
2914sub myprintf
2915{
2916 printf { $sync->{ tee } || \*STDOUT } @ARG ;
2917 return ;
2918}
2919
2920sub mysprintf
2921{
2922 my( $format, @list ) = @ARG ;
2923 return sprintf $format, @list ;
2924}
2925
2926sub output_start
2927{
2928 my $mysync = shift @ARG ;
2929
2930 if ( not $mysync ) { return ; }
2931
2932 my @output = @ARG ;
2933 $mysync->{ output } = join( q{}, @output ) . ( $mysync->{ output } || q{} ) ;
2934 return $mysync->{ output } ;
2935}
2936
2937
2938sub tests_output_start
2939{
2940 note( 'Entering tests_output_start()' ) ;
2941
2942 my $mysync = { } ;
2943
2944 is( undef, output_start( ), 'output_start: no args => undef' ) ;
2945 is( q{}, output_start( $mysync ), 'output_start: one arg => ""' ) ;
2946 is( 'rrrr', output_start( $mysync, 'rrrr' ), 'output_start: rrrr => rrrr' ) ;
2947 is( 'aaaarrrr', output_start( $mysync, 'aaaa' ), 'output_start: aaaa => aaaarrrr' ) ;
2948 is( "\naaaarrrr", output_start( $mysync, "\n" ), 'output_start: \n => \naaaarrrr' ) ;
2949 is( "ABC\naaaarrrr", output_start( $mysync, 'A', 'B', 'C' ), 'output_start: A B C => ABC\naaaarrrr' ) ;
2950
2951 note( 'Leaving tests_output_start()' ) ;
2952 return ;
2953}
2954
2955sub tests_output
2956{
2957 note( 'Entering tests_output()' ) ;
2958
2959 my $mysync = { } ;
2960
2961 is( undef, output( ), 'output: no args => undef' ) ;
2962 is( q{}, output( $mysync ), 'output: one arg => ""' ) ;
2963 is( 'rrrr', output( $mysync, 'rrrr' ), 'output: rrrr => rrrr' ) ;
2964 is( 'rrrraaaa', output( $mysync, 'aaaa' ), 'output: aaaa => rrrraaaa' ) ;
2965 is( "rrrraaaa\n", output( $mysync, "\n" ), 'output: \n => rrrraaaa\n' ) ;
2966 is( "rrrraaaa\nABC", output( $mysync, 'A', 'B', 'C' ), 'output: A B C => rrrraaaaABC\n' ) ;
2967
2968 note( 'Leaving tests_output()' ) ;
2969 return ;
2970}
2971
2972sub output
2973{
2974 my $mysync = shift @ARG ;
2975
2976 if ( not $mysync ) { return ; }
2977
2978 my @output = @ARG ;
2979 $mysync->{ output } .= join( q{}, @output ) ;
2980 return $mysync->{ output } ;
2981}
2982
2983
2984
2985sub tests_output_reset_with
2986{
2987 note( 'Entering tests_output_reset_with()' ) ;
2988
2989 my $mysync = { } ;
2990
2991 is( undef, output_reset_with( ), 'output_reset_with: no args => undef' ) ;
2992 is( q{}, output_reset_with( $mysync ), 'output_reset_with: one arg => ""' ) ;
2993 is( 'rrrr', output_reset_with( $mysync, 'rrrr' ), 'output_reset_with: rrrr => rrrr' ) ;
2994 is( 'aaaa', output_reset_with( $mysync, 'aaaa' ), 'output_reset_with: aaaa => aaaa' ) ;
2995 is( "\n", output_reset_with( $mysync, "\n" ), 'output_reset_with: \n => \n' ) ;
2996
2997 note( 'Leaving tests_output_reset_with()' ) ;
2998 return ;
2999}
3000
3001sub output_reset_with
3002{
3003 my $mysync = shift @ARG ;
3004
3005 if ( not $mysync ) { return ; }
3006
3007 my @output = @ARG ;
3008 $mysync->{ output } = join( q{}, @output ) ;
3009 return $mysync->{ output } ;
3010}
3011
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003012
3013sub tests_print_output_if_needed
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003014{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003015 note( 'Entering tests_print_output_if_needed()' ) ;
3016
3017 is( undef, print_output_if_needed( ), 'print_output_if_needed: no args => undef' ) ;
3018 my $mysync = { } ;
3019 is( q{}, print_output_if_needed( $mysync ), 'print_output_if_needed: undef => undef' ) ;
3020
3021 output( $mysync, "Hello\n" ) ;
3022 is( "Hello\n", print_output_if_needed( $mysync ), 'print_output_if_needed: Hello => Hello' ) ;
3023
3024 $mysync->{ dockercontext } = 1 ;
3025 is( "Hello\n", print_output_if_needed( $mysync ), 'print_output_if_needed: dockercontext + Hello => Hello' ) ;
3026
3027 $mysync->{ version } = 1 ;
3028 is( q{}, print_output_if_needed( $mysync ), 'print_output_if_needed: dockercontext + Hello + --version => ""' ) ;
3029
3030 $mysync->{ dockercontext } = 0 ;
3031 is( "Hello\n", print_output_if_needed( $mysync ), 'print_output_if_needed: Hello + --version => Hello' ) ;
3032
3033 note( 'Leaving tests_print_output_if_needed()' ) ;
3034 return ;
3035}
3036
3037
3038sub print_output_if_needed
3039{
3040
3041 my $mysync = shift @ARG ;
3042 if ( ! defined $mysync ) { return ; }
3043 my $output = output( $mysync ) ;
3044
3045 if ( $mysync->{ version } && under_docker_context( $mysync ) )
3046 {
3047 return q{} ;
3048 }
3049 else
3050 {
3051 myprint( $output ) ;
3052 return $output ;
3053 }
3054
3055}
3056
3057
3058
3059sub define_pidfile
3060{
3061 my $mysync = shift @ARG ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003062
3063 $mysync->{ pidfilelocking } = defined $mysync->{ pidfilelocking } ? $mysync->{ pidfilelocking } : 0 ;
3064
3065 my $host1 = $mysync->{ host1 } || q{} ;
3066 my $user1 = $mysync->{ user1 } || q{} ;
3067 my $host2 = $mysync->{ host2 } || q{} ;
3068 my $user2 = $mysync->{ user2 } || q{} ;
3069
3070 my $account1_filtered = filter_forbidden_characters( slash_to_underscore( $host1 . '_' . $user1 ) ) || q{} ;
3071 my $account2_filtered = filter_forbidden_characters( slash_to_underscore( $host2 . '_' . $user2 ) ) || q{} ;
3072
3073 my $pidfile_basename ;
3074
3075 if ( $ENV{ 'NET_SERVER_SOFTWARE' } and ( $ENV{ 'NET_SERVER_SOFTWARE' } =~ /Net::Server::HTTP/ ) )
3076 {
3077 # under local webserver
3078 $pidfile_basename = 'imapsync' . '_' . $account1_filtered . '_' . $account2_filtered . '.pid' ;
3079 }
3080 else
3081 {
3082 $pidfile_basename = 'imapsync.pid' ;
3083 }
3084
3085 $mysync->{ pidfile } = defined $mysync->{ pidfile } ? $mysync-> { pidfile } : $mysync->{ tmpdir } . "/$pidfile_basename" ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003086 $mysync->{ abortfile } = abortfile( $mysync, $PROCESS_ID ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003087 return ;
3088}
3089
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003090sub abortfile
3091{
3092 my $mysync = shift @ARG ;
3093 my $pid = shift @ARG ;
3094
3095 my $abortfile ;
3096 if ( $mysync->{ abort } )
3097 {
3098 $abortfile = $mysync->{ pidfile } . "abort$pid" ;
3099 }
3100 else
3101 {
3102 $abortfile = $mysync->{ pidfile } . "abort$PROCESS_ID" ;
3103 }
3104 return $abortfile ;
3105}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003106
3107sub tests_kill_zero
3108{
3109 note( 'Entering tests_kill_zero()' ) ;
3110
3111
3112
3113 SKIP: {
3114 if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests tests_kill_zero avoided on Windows', 8 ) ; }
3115
3116
3117 is( 1, kill( 'ZERO', $PROCESS_ID ), "kill ZERO : myself $PROCESS_ID => 1" ) ;
3118 is( 2, kill( 'ZERO', $PROCESS_ID, $PROCESS_ID ), "kill ZERO : myself $PROCESS_ID $PROCESS_ID => 2" ) ;
3119
3120 if ( (-e '/.dockerenv' ) or ( 0 == $EFFECTIVE_USER_ID) )
3121 {
3122 is( 1, kill( 'ZERO', 1 ), "kill ZERO : pid 1 => 1 (docker context or root)" ) ;
3123 is( 2, kill( 'ZERO', $PROCESS_ID, 1 ), "kill ZERO : myself + pid 1, $PROCESS_ID 1 => 2 (docker context or root)" ) ;
3124 }
3125 else
3126 {
3127 is( 0, kill( 'ZERO', 1 ), "kill ZERO : pid 1 => 0 (non root)" ) ;
3128 is( 1, kill( 'ZERO', $PROCESS_ID, 1 ), "kill ZERO : myself + pid 1, $PROCESS_ID 1 => 1 (one is non root)" ) ;
3129
3130 }
3131
3132
3133 my $pid_1 = fork( ) ;
3134 if ( $pid_1 )
3135 {
3136 # parent
3137 }
3138 else
3139 {
3140 # child
3141 sleep 3 ;
3142 exit ;
3143 }
3144
3145 my $pid_2 ;
3146 $pid_2 = fork( ) ;
3147 if ( $pid_2 )
3148 {
3149 # I am the parent
3150 ok( defined( $pid_2 ), "kill_zero: initial fork ok. I am the parent $PROCESS_ID" ) ;
3151 ok( $pid_2 , "kill_zero: initial fork ok, child pid is $pid_2" ) ;
3152 is( 3, kill( 'ZERO', $PROCESS_ID, $pid_2, $pid_1 ), "kill ZERO : myself $PROCESS_ID and child $pid_2 and brother $pid_1 => 3" ) ;
3153
3154 is( $pid_2, waitpid( $pid_2, 0 ), "kill_zero: child $pid_2 no more there => waitpid return $pid_2" ) ;
3155 }
3156 else
3157 {
3158 # I am the child
3159 note( 'This one fails under Windows, kill ZERO returns 0 instead of 2' ) ;
3160 is( 2, kill( 'ZERO', $PROCESS_ID, $pid_1 ), "kill ZERO : myself child $PROCESS_ID brother $pid_1 => 2" ) ;
3161 myprint( "I am the child pid $PROCESS_ID, Exiting\n" ) ;
3162 exit ;
3163 }
3164 wait( ) ;
3165
3166 # End of SKIP block
3167 }
3168
3169 note( 'Leaving tests_kill_zero()' ) ;
3170 return ;
3171}
3172
3173
3174
3175
3176sub tests_killpid_by_parent
3177{
3178 note( 'Entering tests_killpid_by_parent()' ) ;
3179
3180 SKIP: {
3181 if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests tests_killpid_by_parent avoided on Windows', 7 ) ; }
3182
3183 is( undef, killpid( ), 'killpid: no args => undef' ) ;
3184 note( "killpid: trying to kill myself pid $PROCESS_ID, hope I will not succeed" ) ;
3185 is( undef, killpid( $PROCESS_ID ), 'killpid: myself => undef' ) ;
3186
3187 local $SIG{'QUIT'} = sub { myprint "GOT SIG QUIT! I am PID $PROCESS_ID. Exiting\n" ; exit ; } ;
3188
3189 my $pid ;
3190 $pid = fork( ) ;
3191 if ( $pid )
3192 {
3193 # I am the parent
3194 ok( defined( $pid ), "killpid: initial fork ok. I am the parent $PROCESS_ID" ) ;
3195 ok( $pid , "killpid: initial fork ok, child pid is $pid" ) ;
3196
3197 is( 2, kill( 'ZERO', $PROCESS_ID, $pid ), "kill ZERO : myself $PROCESS_ID and child $pid => 2" ) ;
3198 is( 1, killpid( $pid ), "killpid: child $pid killed => 1" ) ;
3199 is( -1, waitpid( $pid, 0 ), "killpid: child $pid no more there => waitpid return -1" ) ;
3200 }
3201 else
3202 {
3203 # I am the child
3204 myprint( "I am the child pid $PROCESS_ID, sleeping 1 + 3 seconds then kill myself\n" ) ;
3205 sleep 1 ;
3206 myprint( "I am the child pid $PROCESS_ID, slept 1 second, should be killed by my parent now, PPID " . mygetppid( ) . "\n" ) ;
3207 sleep 3 ;
3208 # this test should not be run. If it happens => failure.
3209 ok( 0 == 1, "killpid: child pid $PROCESS_ID not dead => failure" ) ;
3210 myprint( "I am the child pid $PROCESS_ID, killing myself failure... Exiting\n" ) ;
3211 exit ;
3212 }
3213
3214 # End of SKIP block
3215 }
3216 note( 'Leaving tests_killpid_by_parent()' ) ;
3217 return ;
3218}
3219
3220sub tests_killpid_by_brother
3221{
3222 note( 'Entering tests_killpid_by_brother()' ) ;
3223
3224
3225 SKIP: {
3226 if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests tests_killpid_by_brother avoided on Windows', 2 ) ; }
3227
3228 local $SIG{'QUIT'} = sub { myprint "GOT SIG QUIT! I am PID $PROCESS_ID. Exiting\n" ; exit ; } ;
3229
3230 my $pid_parent = $PROCESS_ID ;
3231 myprint( "I am the parent pid $pid_parent\n" ) ;
3232 my $pid_1 = fork( ) ;
3233 if ( $pid_1 )
3234 {
3235 # parent
3236 }
3237 else
3238 {
3239 # child
3240 #while ( 1 ) { } ;
3241 sleep 2 ;
3242 sleep 2 ;
3243 # this test should not be run. If it happens => failure.
3244 # Well under Windows this always fails, shit!
3245 ok( 0 == 1 or ( 'MSWin32' eq $OSNAME ) , "killpid: child pid $PROCESS_ID killing by brother but not dead => failure" ) ;
3246 myprint( "I am the child pid $PROCESS_ID, killing by brother failed... Exiting\n" ) ;
3247 exit ;
3248 }
3249
3250 my $pid_2 ;
3251 $pid_2 = fork( ) ;
3252 if ( $pid_2 )
3253 {
3254 # parent
3255 }
3256 else
3257 {
3258 # I am the child
3259 myprint( "I am the child pid $PROCESS_ID, my brother has pid $pid_1\n" ) ;
3260 is( 1, killpid( $pid_1 ), "killpid: brother $pid_1 killed => 1" ) ;
3261 sleep 2 ;
3262 exit ;
3263 }
3264
3265 #sleep 1 ;
3266 is( $pid_1, waitpid( $pid_1, 0), "I am the parent $PROCESS_ID waitpid _1( $pid_1 )" ) ;
3267 is( $pid_2, waitpid( $pid_2, 0 ), "I am the parent $PROCESS_ID waitpid _2( $pid_2 )" ) ;
3268
3269
3270 # End of SKIP block
3271 }
3272
3273 note( 'Leaving tests_killpid_by_brother()' ) ;
3274 return ;
3275}
3276
3277
3278sub killpid
3279{
3280 my $pidtokill = shift ;
3281
3282 if ( ! $pidtokill ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003283 myprint( "No process to kill.\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003284 return ;
3285 }
3286
3287 if ( $PROCESS_ID == $pidtokill ) {
3288 myprint( "I will not kill myself pid $PROCESS_ID via killpid. Sractch it!\n" ) ;
3289 return ;
3290 }
3291
3292
3293 # First ask for suicide
3294 if ( kill( 'ZERO', $pidtokill ) or ( 'MSWin32' eq $OSNAME ) ) {
3295 myprint( "Sending signal QUIT to PID $pidtokill \n" ) ;
3296 kill 'QUIT', $pidtokill ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003297 sleep 3 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003298 waitpid( $pidtokill, WNOHANG) ;
3299 }else{
3300 myprint( "Can not send signal kill ZERO to PID $pidtokill.\n" ) ;
3301 return ;
3302 }
3303
3304 #while ( waitpid( $pidtokill, WNOHANG) > 0 ) { } ;
3305
3306 # Then murder
3307 if ( kill( 'ZERO', $pidtokill ) or ( 'MSWin32' eq $OSNAME ) ) {
3308 myprint( "Sending signal KILL to PID $pidtokill \n" ) ;
3309 kill 'KILL', $pidtokill ;
3310 sleep 1 ;
3311 waitpid( $pidtokill, WNOHANG) ;
3312 }else{
3313 myprint( "Process PID $pidtokill ended.\n" ) ;
3314 return 1;
3315 }
3316 # Well ...
3317 if ( kill( 'ZERO', $pidtokill ) or ( 'xMSWin32' eq $OSNAME ) ) {
3318 myprint( "Process PID $pidtokill seems still there. Can not do much.\n" ) ;
3319 return ;
3320 }else{
3321 myprint( "Process PID $pidtokill ended.\n" ) ;
3322 return 1;
3323 }
3324
3325 return ;
3326}
3327
3328sub tests_abort
3329{
3330 note( 'Entering tests_abort()' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003331 # Well, the abort behavior is tested by test.sh
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003332 is( undef, abort( ), 'abort: no args => undef' ) ;
3333 note( 'Leaving tests_abort()' ) ;
3334 return ;
3335}
3336
3337
3338
3339
3340sub abort
3341{
3342 my $mysync = shift @ARG ;
3343
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003344 myprint( "In abort\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003345 if ( not $mysync ) { return ; }
3346
3347 if ( ! -r $mysync->{pidfile} ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003348 myprint( "In abort: Can not read pidfile $mysync->{pidfile}\n" ) ;
3349 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003350 }
3351 my $pidtokill = firstline( $mysync->{pidfile} ) ;
3352 if ( ! $pidtokill ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003353 myprint( "In abort: No process to abort in $mysync->{pidfile}\n" ) ;
3354 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003355 }
3356
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003357 if ( ! match_a_pid_number( $pidtokill ) )
3358 {
3359 myprint( "In abort: pid $pidtokill in $mysync->{pidfile} is not a pid number\n" ) ;
3360 return ;
3361 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003362
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003363
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003364 if ( $mysync->{abortbyfile} )
3365 {
3366 abortbyfile( $mysync, $pidtokill ) ;
3367 }
3368 else
3369 {
3370 killpid( $pidtokill ) ;
3371 }
3372 return ;
3373}
3374
3375sub abortbyfile
3376{
3377 my $mysync = shift @ARG ;
3378 my $pidtokill = shift @ARG ;
3379
3380 my $abortfile = abortfile( $mysync, $pidtokill ) ;
3381 myprint( "touching $abortfile\n" ) ;
3382 touch( $abortfile ) ;
3383 return ;
3384}
3385
3386
3387sub tests_under_docker_context
3388{
3389 note( 'Entering tests_under_docker_context()' ) ;
3390
3391 is( undef, under_docker_context( ), 'under_docker_context: no args => undef' ) ;
3392
3393 my $mysync = { } ;
3394 $mysync->{ dockercontext } = 1 ;
3395 is( 1, under_docker_context( $mysync ), 'under_docker_context: --dockercontext => 1' ) ;
3396 $mysync->{ dockercontext } = 0 ;
3397 is( 0, under_docker_context( $mysync ), 'under_docker_context: --nodockercontext => 0' ) ;
3398
3399 $mysync = { } ;
3400 # Is not it a stupid test?
3401 if ( under_docker_context( $mysync ) )
3402 {
3403 is( 1, under_docker_context( $mysync ), 'under_docker_context: docker context => 1' ) ;
3404 }
3405 else
3406 {
3407 is( 0, under_docker_context( $mysync ), 'under_docker_context: not docker context => 0' ) ;
3408 }
3409
3410 note( 'Leaving tests_under_docker_context()' ) ;
3411 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003412}
3413
3414
3415sub under_docker_context
3416{
3417 my $mysync = shift ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003418
3419 if ( ! defined $mysync ) { return ; }
3420
3421 if ( defined $mysync->{ dockercontext } )
3422 {
3423 return( $mysync->{ dockercontext } ) ;
3424 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003425
3426 if ( -e '/.dockerenv' )
3427 {
3428 return 1 ;
3429 }
3430 else
3431 {
3432 return 0 ;
3433 }
3434
3435 return ;
3436}
3437
3438
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003439sub docker_context
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003440{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003441 my $mysync = shift ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003442
3443 if ( ! under_docker_context( $mysync ) )
3444 {
3445 return ;
3446 }
3447
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003448 output( $mysync, "Docker context detected with the file /.dockerenv\n" ) ;
3449 # No pidfile by default
3450
3451 $mysync->{ pidfile } = defined( $mysync->{ pidfile } ) ? $mysync->{ pidfile } : q{} ;
3452 # No log by default
3453 if ( defined( $mysync->{ log } ) )
3454 {
3455 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" ) ;
3456 }
3457 else
3458 {
3459 output( $mysync, "No log by default in Docker context. Use --log to trigger logging to the logfile.\n" ) ;
3460 $mysync->{ log } = 0 ;
3461 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003462
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003463 # In case something is written relatively to .
3464 output( $mysync, "Changing current directory to /var/tmp/\n" ) ;
3465 chdir '/var/tmp/' ;
3466
3467 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003468}
3469
3470sub cgibegin
3471{
3472 my $mysync = shift ;
3473 if ( ! under_cgi_context( $mysync ) ) { return ; }
3474 require CGI ;
3475 CGI->import( qw( -no_debug -utf8 ) ) ;
3476 require CGI::Carp ;
3477 CGI::Carp->import( qw( fatalsToBrowser ) ) ;
3478 $mysync->{cgi} = CGI->new( ) ;
3479 return ;
3480}
3481
3482sub tests_under_cgi_context
3483{
3484 note( 'Entering tests_under_cgi_context()' ) ;
3485
3486 # $ENV{SERVER_SOFTWARE} = 'under imapsync' ;
3487 do {
3488 # Not in cgi context
3489 delete local $ENV{SERVER_SOFTWARE} ;
3490 is( undef, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE unset => not in cgi context' ) ;
3491 } ;
3492 do {
3493 # In cgi context
3494 local $ENV{SERVER_SOFTWARE} = 'under imapsync' ;
3495 is( 1, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE set => in cgi context' ) ;
3496 } ;
3497 do {
3498 # Not in cgi context
3499 delete local $ENV{SERVER_SOFTWARE} ;
3500 is( undef, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE unset => not in cgi context' ) ;
3501 } ;
3502 do {
3503 # In cgi context
3504 local $ENV{SERVER_SOFTWARE} = 'under imapsync' ;
3505 is( 1, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE set => in cgi context' ) ;
3506 } ;
3507 note( 'Leaving tests_under_cgi_context()' ) ;
3508 return ;
3509}
3510
3511
3512sub under_cgi_context
3513{
3514 my $mysync = shift ;
3515 # Under cgi context
3516 if ( $ENV{SERVER_SOFTWARE} ) {
3517 return 1 ;
3518 }
3519 # Not in cgi context
3520 return ;
3521}
3522
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003523sub cgibuildheader
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003524{
3525 my $mysync = shift ;
3526 if ( ! under_cgi_context( $mysync ) ) { return ; }
3527
3528 my $imapsync_runs = $mysync->{cgi}->cookie( 'imapsync_runs' ) || 0 ;
3529 my $cookie = $mysync->{cgi}->cookie(
3530 -name => 'imapsync_runs',
3531 -value => 1 + $imapsync_runs,
3532 -expires => '+20y',
3533 -path => '/cgi-bin/imapsync',
3534 ) ;
3535 my $httpheader ;
3536 if ( $mysync->{ abort } ) {
3537 $httpheader = $mysync->{cgi}->header(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003538 -type => 'text/plain; charset=UTF-8',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003539 -status => '200 OK to abort syncing IMAP boxes' . ". Here is " . hostname(),
3540 ) ;
3541 }elsif( $mysync->{ loaddelay } ) {
3542# https://tools.ietf.org/html/rfc2616#section-10.5.4
3543# 503 Service Unavailable
3544# The server is currently unable to handle the request due to a temporary overloading or maintenance of the server.
3545 $httpheader = $mysync->{cgi}->header(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003546 -type => 'text/plain; charset=UTF-8',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003547 -status => '503 Service Unavailable' . ". Be back in $mysync->{ loaddelay } min. Load on " . hostname() . " is $mysync->{ loadavg }",
3548 ) ;
3549 }else{
3550 $httpheader = $mysync->{cgi}->header(
3551 -type => 'text/plain; charset=UTF-8',
3552 -status => '200 OK to sync IMAP boxes' . ". Load on " . hostname() . " is $mysync->{ loadavg }",
3553 -cookie => $cookie,
3554 ) ;
3555 }
3556 output_start( $mysync, $httpheader ) ;
3557
3558 return ;
3559}
3560
3561sub cgiload
3562{
3563 # Exit on heavy load in CGI context
3564 my $mysync = shift ;
3565 if ( ! under_cgi_context( $mysync ) ) { return ; }
3566 if ( $mysync->{ abort } ) { return ; } # keep going to abort since some ressources will be free soon
3567 if ( $mysync->{ loaddelay } )
3568 {
3569 $mysync->{nb_errors}++ ;
3570 exit_clean( $mysync, $EX_UNAVAILABLE,
3571 "Server is on heavy load. Be back in $mysync->{ loaddelay } min. Load is $mysync->{ loadavg }\n"
3572 ) ;
3573 }
3574 return ;
3575}
3576
3577sub tests_set_umask
3578{
3579 note( 'Entering tests_set_umask()' ) ;
3580
3581 my $save_umask = umask ;
3582
3583 my $mysync = {} ;
3584 if ( 'MSWin32' eq $OSNAME ) {
3585 is( undef, set_umask( $mysync ), "set_umask: set failure to $UMASK_PARANO on MSWin32" ) ;
3586 }else{
3587 is( 1, set_umask( $mysync ), "set_umask: set to $UMASK_PARANO" ) ;
3588 }
3589
3590 umask $save_umask ;
3591 note( 'Leaving tests_set_umask()' ) ;
3592 return ;
3593}
3594
3595sub set_umask
3596{
3597 my $mysync = shift ;
3598 my $previous_umask = umask_str( ) ;
3599 my $new_umask = umask_str( $UMASK_PARANO ) ;
3600 output( $mysync, "Umask set with $new_umask (was $previous_umask)\n" ) ;
3601 if ( $new_umask eq $UMASK_PARANO ) {
3602 return 1 ;
3603 }
3604 return ;
3605}
3606
3607sub tests_umask_str
3608{
3609 note( 'Entering tests_umask_str()' ) ;
3610
3611 my $save_umask = umask ;
3612
3613 is( umask_str( ), umask_str( ), 'umask_str: no parameters => idopotent' ) ;
3614 is( my $save_umask_str = umask_str( ), umask_str( ), 'umask_str: no parameters => idopotent + save' ) ;
3615 is( '0000', umask_str( q{ } ), 'umask_str: q{ } => 0000' ) ;
3616 is( '0000', umask_str( q{} ), 'umask_str: q{} => 0000' ) ;
3617 is( '0000', umask_str( '0000' ), 'umask_str: 0000 => 0000' ) ;
3618 is( '0000', umask_str( '0' ), 'umask_str: 0 => 0000' ) ;
3619 is( '0200', umask_str( '0200' ), 'umask_str: 0200 => 0200' ) ;
3620 is( '0400', umask_str( '0400' ), 'umask_str: 0400 => 0400' ) ;
3621 is( '0600', umask_str( '0600' ), 'umask_str: 0600 => 0600' ) ;
3622
3623 SKIP: {
3624 if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests success only for Unix', 6 ) ; }
3625 is( '0100', umask_str( '0100' ), 'umask_str: 0100 => 0100' ) ;
3626 is( '0001', umask_str( '0001' ), 'umask_str: 0001 => 0001' ) ;
3627 is( '0777', umask_str( '0777' ), 'umask_str: 0777 => 0777' ) ;
3628 is( '0777', umask_str( '00777' ), 'umask_str: 00777 => 0777' ) ;
3629 is( '0777', umask_str( ' 777 ' ), 'umask_str: 777 => 0777' ) ;
3630 is( "$UMASK_PARANO", umask_str( $UMASK_PARANO ), "umask_str: UMASK_PARANO $UMASK_PARANO => $UMASK_PARANO" ) ;
3631 }
3632
3633 is( $save_umask_str, umask_str( $save_umask_str ), 'umask_str: restore with str' ) ;
3634 is( $save_umask, umask, 'umask_str: umask is restored, controlled by direct umask' ) ;
3635 is( $save_umask, umask $save_umask, 'umask_str: umask is restored by direct umask' ) ;
3636 is( $save_umask, umask, 'umask_str: umask initial controlled by direct umask' ) ;
3637
3638 note( 'Leaving tests_umask_str()' ) ;
3639 return ;
3640}
3641
3642sub umask_str
3643{
3644 my $value = shift ;
3645
3646 if ( defined $value ) {
3647 umask oct( $value ) ;
3648 }
3649 my $current = umask ;
3650
3651 return( sprintf( '%#04o', $current ) ) ;
3652}
3653
3654sub tests_umask
3655{
3656 note( 'Entering tests_umask()' ) ;
3657
3658 my $save_umask ;
3659 is( umask, umask, 'umask: umask is umask' ) ;
3660 is( $save_umask = umask, umask, "umask: umask is umask again + save it: $save_umask" ) ;
3661 is( $save_umask, umask oct(0000), 'umask: umask 0000' ) ;
3662 is( oct(0000), umask, 'umask: umask is now 0000' ) ;
3663 is( oct(0000), umask oct(777), 'umask: umask 0777 call, previous 0000' ) ;
3664
3665 SKIP: {
3666 if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests success only for Unix', 2 ) ; }
3667 is( oct(777), umask, 'umask: umask is now 0777' ) ;
3668 is( oct(777), umask $save_umask, "umask: umask $save_umask restore inital value, previous 0777" ) ;
3669 }
3670
3671 ok( defined umask $save_umask, "umask: umask $save_umask restore inital value, previous defined" ) ;
3672 is( $save_umask, umask, 'umask: umask is umask restored' ) ;
3673 note( 'Leaving tests_umask()' ) ;
3674
3675 return ;
3676}
3677
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003678sub buggyflagsregex
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003679{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003680 # From /X analyse
3681 # cut -d: -f1 Error_112_all_syncs.txt | xargs egrep -oih 'Invalid system flag [^( ]+' | sort | uniq -c | sort -g
3682 my @buggyflagsregex = ( 's/\\\\RECEIPTCHECKED|\\\\Indexed|\\\\X-EON-HAS-ATTACHMENT|\\\\UNSEEN|\\\\ATTACHED|\\\\X-HAS-ATTACH|\\\\FORWARDED|\\\\FORWARD|\\\\X-FORWARDED|\\\\\$FORWARDED|\\\\PRIORITY|\\\\READRCPT//g' ) ;
3683 return( @buggyflagsregex ) ;
3684}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003685
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003686sub cgisetcontext
3687{
3688 my $mysync = shift ;
3689 if ( ! under_cgi_context( $mysync ) ) { return ; }
3690
3691 output( $mysync, "Under cgi context\n" ) ;
3692
3693
3694 set_umask( $mysync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003695
3696 # Remove all content in unsafe evaled options
3697 @{ $mysync->{ regextrans2 } } = ( ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003698
3699 @{ $mysync->{ regexflag } } = buggyflagsregex( ) ;
3700
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003701 @regexmess = ( ) ;
3702 @skipmess = ( ) ;
3703 @pipemess = ( ) ;
3704 $delete2foldersonly = undef ;
3705 $delete2foldersbutnot = undef ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003706 $maxlinelengthcmd = undef ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003707
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003708 # Set safe default values (I hope...)
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003709
3710
3711 #$mysync->{pidfile} = 'imapsync.pid' ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003712 $mysync->{ pidfilelocking } = 1 ;
3713 $mysync->{ errorsmax } = $ERRORS_MAX_CGI ;
3714 $modulesversion = 0 ;
3715 $mysync->{ releasecheck } = defined $mysync->{ releasecheck } ? $mysync->{ releasecheck } : 1 ;
3716 $usecache = 0 ;
3717 $mysync->{ showpasswords } = 0 ;
3718 $mysync->{ acc1 }->{ debugimap } = 0 ;
3719 $mysync->{ acc2 }->{ debugimap } = 0 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003720
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003721 $mysync->{ acc1 }->{ reconnectretry } = $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ;
3722 $mysync->{ acc2 }->{ reconnectretry } = $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ;
3723
3724 $pipemesscheck = 0 ;
3725
3726 $mysync->{ hashfile } = $CGI_HASHFILE ;
3727 my $hashsynclocal = hashsynclocal( $mysync ) || die "Can not get hashsynclocal. Exiting\n" ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003728
3729 if ( $ENV{ 'NET_SERVER_SOFTWARE' } and ( $ENV{ 'NET_SERVER_SOFTWARE' } =~ /Net::Server::HTTP/ ) )
3730 {
3731 # under local webserver
3732 $cgidir = q{.} ;
3733 }
3734 else
3735 {
3736 $cgidir = $CGI_TMPDIR_TOP . '/' . $hashsynclocal ;
3737 }
3738 -d $cgidir or mkpath $cgidir or die "Can not create $cgidir: $OS_ERROR\n" ;
3739 $mysync->{ tmpdir } = $cgidir ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003740 $mysync->{ logdir } = '' ;
3741
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003742 chdir $cgidir or die "Can not cd to $cgidir: $OS_ERROR\n" ;
3743 cgioutputenvcontext( $mysync ) ;
3744 $mysync->{ debug } and output( $mysync, 'Current directory is ' . getcwd( ) . "\n" ) ;
3745 $mysync->{ debug } and output( $mysync, 'Real user id is ' . getpwuid_any_os( $REAL_USER_ID ) . " (uid $REAL_USER_ID)\n" ) ;
3746 $mysync->{ debug } and output( $mysync, 'Effective user id is ' . getpwuid_any_os( $EFFECTIVE_USER_ID ). " (euid $EFFECTIVE_USER_ID)\n" ) ;
3747
3748 $mysync->{ skipemptyfolders } = defined $mysync->{ skipemptyfolders } ? $mysync->{ skipemptyfolders } : 1 ;
3749
3750 # Out of memory with messages over 1 GB ?
3751 $mysync->{ maxsize } = defined $mysync->{ maxsize } ? $mysync->{ maxsize } : 1_000_000_000 ;
3752
3753 # tail -f behaviour on by default
3754 $mysync->{ tail } = defined $mysync->{ tail } ? $mysync->{ tail } : 1 ;
3755
3756 # not sure it's for good
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003757 @useheader = qw( Message-Id Received ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003758
3759 # addheader on by default
3760 $mysync->{ addheader } = defined $mysync->{ addheader } ? $mysync->{ addheader } : 1 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003761
3762 # sync duplicates by default in cgi context
3763 $mysync->{ syncduplicates } = defined $mysync->{ syncduplicates } ? $mysync->{ syncduplicates } : 1 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003764
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003765 # log the logfile name by default in cgi context
3766 $mysync->{ loglogfile } = defined $mysync->{ loglogfile } ? $mysync->{ loglogfile } : 1 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003767 return ;
3768}
3769
3770sub cgioutputenvcontext
3771{
3772 my $mysync = shift @ARG ;
3773
3774 for my $envvar ( qw( REMOTE_ADDR REMOTE_HOST HTTP_REFERER HTTP_USER_AGENT SERVER_SOFTWARE SERVER_PORT HTTP_COOKIE ) ) {
3775
3776 my $envval = $ENV{ $envvar } || q{} ;
3777 if ( $envval ) { output( $mysync, "$envvar is $envval\n" ) } ;
3778 }
3779
3780 return ;
3781}
3782
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003783sub announcelogfile
3784{
3785 my $mysync = shift ;
3786
3787 if ( $mysync->{ log } )
3788 {
3789 myprint( "Log file is $mysync->{ logfile } ( to change it, use --logfile path ; or use --nolog to turn off logging )\n" ) ;
3790 loglogfile( $mysync ) ;
3791 }
3792 else
3793 {
3794 myprint( "No log file because of option --nolog\n" ) ;
3795 }
3796 return ;
3797}
3798
3799
3800sub loglogfile
3801{
3802 my $mysync = shift ;
3803 if ( ! $mysync->{ loglogfile } ) { return ; }
3804 if ( ! $mysync->{ log } ) { return ; }
3805
3806 my $cwd = getcwd( ) ;
3807 my $absolutelogfilepath ;
3808 # Fixme: add case when the logfile name is already absolute
3809 $absolutelogfilepath = "$cwd/$mysync->{ logfile }" ;
3810 my $loglogfilename = '../list_all_logs_auto.txt' ;
3811 myprint( "Writing log file name $absolutelogfilepath to $loglogfilename\n" ) ;
3812 if ( open( my $fh, '>>', $loglogfilename ) )
3813 {
3814 print $fh "$absolutelogfilepath\n" ;
3815 close $fh ;
3816 }
3817 else
3818 {
3819 myprint( "Could not open loglogfile $loglogfilename $!\n" ) ;
3820 }
3821 return ;
3822}
3823
3824
3825sub checkselectable
3826{
3827 my $mysync = shift ;
3828
3829 if ( $mysync->{ checkselectable } ) {
3830 my @h1_folders_wanted_selectable ;
3831 myprint( "Host1: Checking wanted folders are selectable. Use --nocheckselectable to avoid this check.\n" ) ;
3832 foreach my $folder ( @{ $mysync->{ h1_folders_wanted } } )
3833 {
3834 ( $mysync->{ debug } or $mysync->{ debugfolders } ) and myprint( "Checking $folder is selectable on host1\n" ) ;
3835 # It does an imap command LIST "" $folder and then search for no \Noselect
3836 if ( ! $mysync->{ imap1 }->selectable( $folder ) )
3837 {
3838 myprint( "Host1: warning! ignoring folder $folder because it is not selectable\n" ) ;
3839 }else
3840 {
3841 push @h1_folders_wanted_selectable, $folder ;
3842 }
3843 }
3844 @{ $mysync->{ h1_folders_wanted } } = @h1_folders_wanted_selectable ;
3845 ( $mysync->{ debug } or $mysync->{ debugfolders } )
3846 and myprint( 'Host1: checking folders took ', timenext( $mysync ), " s\n" ) ;
3847 }
3848 else
3849 {
3850 myprint( "Host1: Not checking that wanted folders are selectable. Use --checkselectable to force this check.\n" ) ;
3851 }
3852 return ;
3853}
3854
3855sub setcheckselectable
3856{
3857 my $mysync = shift ;
3858
3859 my $h1_folders_wanted_nb = scalar @{ $mysync->{ h1_folders_wanted } } ;
3860 # 152 because 98% of host1 accounts have less than 152 folders on /X service.
3861 # command to get this value:
3862 # datamash_file_op_index G_Host1_Nb_folders.txt perc:98 4 %16.1f
3863 if ( ! defined $mysync->{ checkselectable } )
3864 {
3865 if ( 152 >= $h1_folders_wanted_nb )
3866 {
3867 $mysync->{ checkselectable } = 1 ;
3868 }else{
3869 myprint( "Host1: Not checking that $h1_folders_wanted_nb wanted folders are selectable. Use --checkselectable to force this check.\n" ) ;
3870 $mysync->{ checkselectable } = 0 ;
3871 }
3872 }
3873 return ;
3874}
3875
3876
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003877
3878sub debugsleep
3879{
3880 my $mysync = shift @ARG ;
3881 if ( defined $mysync->{debugsleep} ) {
3882 myprint( "Info: sleeping $mysync->{debugsleep}s\n" ) ;
3883 sleep $mysync->{debugsleep} ;
3884 }
3885 return ;
3886}
3887
3888sub tests_foldersize
3889{
3890 note( 'Entering tests_foldersize()' ) ;
3891
3892 is( undef, foldersize( ), 'foldersize: no args => undef' ) ;
3893
3894
3895 #is_deeply( {}, {}, 'foldersize: a hash is a hash' ) ;
3896 #is_deeply( [], [], 'foldersize: an array is an array' ) ;
3897 note( 'Leaving tests_foldersize()' ) ;
3898 return ;
3899}
3900
3901
3902
3903# Globals:
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003904# $fetch_hash_set
3905#
3906sub foldersize
3907{
3908 # size of one folder
3909 my ( $mysync, $side, $imap, $search_cmd, $abletosearch, $folder ) = @ARG ;
3910
3911 if ( ! all_defined( $mysync, $side, $imap, $folder ) )
3912 {
3913 return ;
3914 }
3915
3916 # FTGate is RFC buggy with EXAMINE it does not act as SELECT
3917 #if ( ! $imap->examine( $folder ) ) {
3918 if ( ! $imap->select( $folder ) ) {
3919 my $error = join q{},
3920 "$side Folder $folder: Could not select: ",
3921 $imap->LastError, "\n" ;
3922 errors_incr( $mysync, $error ) ;
3923 return ;
3924 }
3925
3926 if ( $imap->IsUnconnected( ) )
3927 {
3928 return ;
3929 }
3930
3931 my $hash_ref = { } ;
3932 my @msgs = select_msgs( $imap, undef, $search_cmd, $abletosearch, $folder ) ;
3933 my $nb_msgs = scalar @msgs ;
3934 my $biggest_in_folder = 0 ;
3935 @{ $hash_ref }{ @msgs } = ( undef ) if @msgs ;
3936
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003937
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003938 my $stot = 0 ;
3939
3940 if ( $imap->IsUnconnected( ) )
3941 {
3942 return ;
3943 }
3944
3945 if ( $nb_msgs > 0 and @msgs ) {
3946 if ( $abletosearch ) {
3947 if ( ! $imap->fetch_hash( \@msgs, 'RFC822.SIZE', $hash_ref) ) {
3948 my $error = "$side failure with fetch_hash: $EVAL_ERROR\n" ;
3949 errors_incr( $mysync, $error ) ;
3950 return ;
3951 }
3952 }
3953 else
3954 {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003955 my $fetch_hash_uids = $fetch_hash_set || "1:*" ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003956 if ( ! $imap->fetch_hash( $fetch_hash_uids, 'RFC822.SIZE', $hash_ref ) ) {
3957 my $error = "$side failure with fetch_hash: $EVAL_ERROR\n" ;
3958 errors_incr( $mysync, $error ) ;
3959 return ;
3960 }
3961 }
3962 for ( keys %{ $hash_ref } ) {
3963 my $size = $hash_ref->{ $_ }->{ 'RFC822.SIZE' } ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003964 if ( defined $size )
3965 {
3966 $stot += $size ;
3967 $biggest_in_folder = max( $biggest_in_folder, $size ) ;
3968 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003969 }
3970 }
3971 return( $stot, $nb_msgs, $biggest_in_folder ) ;
3972
3973}
3974
3975
3976# The old subroutine that performed just one side at a time.
3977# Still here for a while, until confident with sub foldersize_diff_compute()
3978sub foldersizes
3979{
3980 my ( $mysync, $side, $imap, $search_cmd, $abletosearch, @folders ) = @_ ;
3981 my $total_size = 0 ;
3982 my $total_nb = 0 ;
3983 my $biggest_in_all = 0 ;
3984
3985 my $nb_folders = scalar @folders ;
3986 my $ct_folders = 0 ; # folder counter.
3987 myprint( "++++ Calculating sizes of $nb_folders folders on $side\n" ) ;
3988 foreach my $folder ( @folders ) {
3989 my $stot = 0 ;
3990 my $nb_msgs = 0 ;
3991 my $biggest_in_folder = 0 ;
3992
3993 $ct_folders++ ;
3994 myprintf( "$side folder %7s %-35s", "$ct_folders/$nb_folders", jux_utf8( $folder ) ) ;
3995 if ( 'Host2' eq $side and not exists $mysync->{h2_folders_all_UPPER}{ uc $folder } ) {
3996 myprint( " does not exist yet\n") ;
3997 next ;
3998 }
3999 if ( 'Host1' eq $side and not exists $h1_folders_all{ $folder } ) {
4000 myprint( " does not exist\n" ) ;
4001 next ;
4002 }
4003
4004 last if $imap->IsUnconnected( ) ;
4005
4006 ( $stot, $nb_msgs, $biggest_in_folder ) = foldersize( $mysync, $side, $imap, $search_cmd, $abletosearch, $folder ) ;
4007
4008 myprintf( ' Size: %9s', $stot ) ;
4009 myprintf( ' Messages: %5s', $nb_msgs ) ;
4010 myprintf( " Biggest: %9s\n", $biggest_in_folder ) ;
4011 $total_size += $stot ;
4012 $total_nb += $nb_msgs ;
4013 $biggest_in_all = max( $biggest_in_all, $biggest_in_folder ) ;
4014 }
4015 myprintf( "%s Nb folders: %11s folders\n", $side, $nb_folders ) ;
4016 myprintf( "%s Nb messages: %11s messages\n", $side, $total_nb ) ;
4017 myprintf( "%s Total size: %11s bytes (%s)\n", $side, $total_size, bytes_display_string( $total_size ) ) ;
4018 myprintf( "%s Biggest message: %11s bytes (%s)\n", $side, $biggest_in_all, bytes_display_string( $biggest_in_all ) ) ;
4019 myprintf( "%s Time spent on sizing: %11.1f seconds\n", $side, timenext( $mysync ) ) ;
4020 return( $total_nb, $total_size ) ;
4021}
4022
4023
4024sub foldersize_diff_present
4025{
4026 my $mysync = shift ;
4027 my $folder1 = shift ;
4028 my $folder2 = shift ;
4029 my $counter_str = shift ;
4030 my $force = shift ;
4031
4032 my $values1_str ;
4033 my $values2_str ;
4034
4035 if ( ! defined $mysync->{ folder1 }->{ $folder1 }->{ size } || $force )
4036 {
4037 foldersize_diff_compute( $mysync, $folder1, $folder2, $force ) ;
4038 }
4039
4040 # again, but this time it means no availaible data.
4041 if ( defined $mysync->{ folder1 }->{ $folder1 }->{ size } )
4042 {
4043 $values1_str = sprintf( "Size: %9s Messages: %5s Biggest: %9s\n",
4044 $mysync->{ folder1 }->{ $folder1 }->{ size },
4045 $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs },
4046 $mysync->{ folder1 }->{ $folder1 }->{ biggest },
4047 ) ;
4048 }
4049 else
4050 {
4051 $values1_str = " does not exist\n" ;
4052 }
4053
4054 if ( defined $mysync->{ folder2 }->{ $folder2 }->{ size } )
4055 {
4056 $values2_str = sprintf( "Size: %9s Messages: %5s Biggest: %9s\n",
4057 $mysync->{ folder2 }->{ $folder2 }->{ size },
4058 $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs },
4059 $mysync->{ folder2 }->{ $folder2 }->{ biggest },
4060 ) ;
4061 }
4062 else
4063 {
4064 $values2_str = " does not exist yet\n" ;
4065 }
4066
4067 myprintf( "Host1 folder %7s %-35s %s",
4068 "$counter_str",
4069 jux_utf8( $folder1 ),
4070 $values1_str
4071 ) ;
4072
4073 myprintf( "Host2 folder %7s %-35s %s",
4074 "$counter_str",
4075 jux_utf8( $folder2 ),
4076 $values2_str
4077 ) ;
4078
4079 myprintf( "Host2-Host1 %7s %-35s %9s %5s %9s\n\n",
4080 "",
4081 "",
4082 $mysync->{ folder1 }->{ $folder1 }->{ size_diff },
4083 $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs_diff },
4084 $mysync->{ folder1 }->{ $folder1 }->{ biggest_diff },
4085
4086 ) ;
4087
4088
4089
4090
4091 return ;
4092}
4093
4094sub foldersize_diff_compute
4095{
4096 my $mysync = shift ;
4097 my $folder1 = shift ;
4098 my $folder2 = shift ;
4099 my $force = shift ;
4100
4101
4102
4103 my ( $size_1, $nb_msgs_1, $biggest_1 ) ;
4104 # memoization
4105 if (
4106 exists $h1_folders_all{ $folder1 }
4107 &&
4108 (
4109 ! defined $mysync->{ folder1 }->{ $folder1 }->{ size }
4110 || $force
4111 )
4112 )
4113 {
4114 #myprint( "foldersize folder1 $h1_folders_all{ $folder1 }\n" ) ;
4115 ( $size_1, $nb_msgs_1, $biggest_1 ) =
4116 foldersize( $mysync,
4117 'Host1',
4118 $mysync->{ imap1 },
4119 $mysync->{ search1 },
4120 $mysync->{ abletosearch1 },
4121 $folder1
4122 ) ;
4123 $mysync->{ folder1 }->{ $folder1 }->{ size } = $size_1 ;
4124 $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs } = $nb_msgs_1 ;
4125 $mysync->{ folder1 }->{ $folder1 }->{ biggest } = $biggest_1 ;
4126 }
4127 else
4128 {
4129 $size_1 = $mysync->{ folder1 }->{ $folder1 }->{ size } ;
4130 $nb_msgs_1 = $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs } ;
4131 $biggest_1 = $mysync->{ folder1 }->{ $folder1 }->{ biggest } ;
4132
4133 }
4134
4135
4136 my ( $size_2, $nb_msgs_2, $biggest_2 ) ;
4137 if (
4138 exists $mysync->{ h2_folders_all_UPPER }{ uc $folder2 }
4139 &&
4140 (
4141 ! defined $mysync->{ folder2 }->{ $folder2 }->{ size }
4142 || $force
4143 )
4144 )
4145 {
4146 #myprint( "foldersize folder2\n" ) ;
4147 ( $size_2, $nb_msgs_2, $biggest_2 ) =
4148 foldersize( $mysync,
4149 'Host2',
4150 $mysync->{ imap2 },
4151 $mysync->{ search2 },
4152 $mysync->{ abletosearch2 },
4153 $folder2
4154 ) ;
4155
4156 $mysync->{ folder2 }->{ $folder2 }->{ size } = $size_2 ;
4157 $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs } = $nb_msgs_2 ;
4158 $mysync->{ folder2 }->{ $folder2 }->{ biggest } = $biggest_2 ;
4159 }
4160 else
4161 {
4162 $size_2 = $mysync->{ folder2 }->{ $folder2 }->{ size } ;
4163 $nb_msgs_2 = $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs } ;
4164 $biggest_2 = $mysync->{ folder2 }->{ $folder2 }->{ biggest } ;
4165
4166 }
4167
4168
4169 my $size_diff = diff( $size_2, $size_1 ) ;
4170 my $nb_msgs_diff = diff( $nb_msgs_2, $nb_msgs_1 ) ;
4171 my $biggest_diff = diff( $biggest_2, $biggest_1 ) ;
4172
4173 $mysync->{ folder1 }->{ $folder1 }->{ size_diff } = $size_diff ;
4174 $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs_diff } = $nb_msgs_diff ;
4175 $mysync->{ folder1 }->{ $folder1 }->{ biggest_diff } = $biggest_diff ;
4176
4177 # It's redundant but easier to access later
4178 $mysync->{ folder2 }->{ $folder2 }->{ size_diff } = $size_diff ;
4179 $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs_diff } = $nb_msgs_diff ;
4180 $mysync->{ folder2 }->{ $folder2 }->{ biggest_diff } = $biggest_diff ;
4181
4182 return ;
4183}
4184
4185sub diff
4186{
4187 my $x = shift ;
4188 my $y = shift ;
4189
4190 $x ||= 0 ;
4191 $y ||= 0 ;
4192
4193 return $x - $y ;
4194}
4195
4196sub add
4197{
4198 my $x = shift ;
4199 my $y = shift ;
4200
4201 $x ||= 0 ;
4202 $y ||= 0 ;
4203
4204 return $x + $y ;
4205}
4206
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02004207sub tests_checknoabletosearch
4208{
4209 note( 'Entering checknoabletosearch()' ) ;
4210
4211 is( undef, checknoabletosearch( ), 'checknoabletosearch: no args => undef' ) ;
4212
4213 note( 'Leaving checknoabletosearch()' ) ;
4214 return ;
4215}
4216
4217
4218
4219
4220sub checknoabletosearch
4221{
4222 # call example: checknoabletosearch( $sync, $sync->{ imap1 }, 'INBOX', 'Host1' ) ;
4223 # output:
4224 # * undef if something is not ok to decide
4225 # * 1 if SEARCH ALL failed
4226
4227 my( $mysync, $imap, $folder, $HostX ) = @ARG ;
4228
4229 if ( ! all_defined( $mysync, $imap, $folder, $HostX ) )
4230 {
4231 return ;
4232 }
4233
4234 myprint( "$HostX: checking if SEARCH ALL works on $folder\n" ) ;
4235 if ( ! select_folder( $mysync, $imap, $folder, $HostX ) )
4236 {
4237 myprint( "$HostX: can not SELECT folder [$folder]\n" ) ;
4238 return ;
4239 }
4240 my $count_from_select = count_from_select( $imap->History ) ;
4241 myprint( "$HostX: folder [$folder] has $count_from_select messages mentioned by SELECT\n" ) ;
4242
4243 my $msgs_all = $imap->messages( ) ;
4244 if ( ! $msgs_all )
4245 {
4246 myprint( "$HostX: can not SEARCH ALL folder [$folder]\n" ) ;
4247 myprint( "$HostX: ", $imap->LastError(), "\n" ) ;
4248 return 1 ;
4249 }
4250
4251 my $count_from_search_all = scalar( @{ $msgs_all } ) ;
4252 myprint( "$HostX: folder [$folder] has $count_from_search_all messages found by SEARCH ALL\n" ) ;
4253
4254 if ( $count_from_select == $count_from_search_all )
4255 {
4256 myprint( "$HostX: folder [$folder] has the same messages count ($count_from_select) by SELECT and SEARCH ALL\n" ) ;
4257 }
4258 else
4259 {
4260 myprint( "$HostX: Warning, folder [$folder] has not the same count by SELECT ($count_from_select) and SEARCH ALL ($count_from_search_all)\n" ) ;
4261 return 1 ;
4262 }
4263
4264 return ;
4265}
4266
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004267
4268sub foldersizes_diff_list
4269{
4270 my $mysync = shift ;
4271 my $force = shift ;
4272
4273 my @folders = @{ $mysync->{h1_folders_wanted} } ;
4274 my $nb_folders = scalar @folders ;
4275 my $ct_folders = 0 ; # folder counter.
4276
4277 foreach my $folder1 ( @folders )
4278 {
4279 $ct_folders++ ;
4280 my $counter_str = "$ct_folders/$nb_folders" ;
4281 my $folder2 = imap2_folder_name( $mysync, $folder1 ) ;
4282 foldersize_diff_present( $mysync, $folder1, $folder2, $counter_str, $force ) ;
4283 }
4284
4285 return ;
4286}
4287
4288sub foldersizes_total
4289{
4290 my $mysync = shift ;
4291
4292 my @folders_1 = @{ $mysync->{h1_folders_wanted} } ;
4293 my @folders_2 = @h2_folders_from_1_wanted ;
4294
4295 my $nb_folders_1 = scalar( @folders_1 ) ;
4296 my $nb_folders_2 = scalar( @folders_2 ) ;
4297
4298 my ( $total_size_1, $total_nb_1, $biggest_in_all_1 ) = ( 0, 0, 0 ) ;
4299 my ( $total_size_2, $total_nb_2, $biggest_in_all_2 ) = ( 0, 0, 0 ) ;
4300
4301 foreach my $folder1 ( @folders_1 )
4302 {
4303 $total_size_1 = add( $total_size_1, $mysync->{ folder1 }->{ $folder1 }->{ size } ) ;
4304 $total_nb_1 = add( $total_nb_1, $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs } ) ;
4305 $biggest_in_all_1 = max( $biggest_in_all_1 , $mysync->{ folder1 }->{ $folder1 }->{ biggest } ) ;
4306 }
4307
4308 foreach my $folder2 ( @folders_2 )
4309 {
4310 $total_size_2 = add( $total_size_2, $mysync->{ folder2 }->{ $folder2 }->{ size } ) ;
4311 $total_nb_2 = add( $total_nb_2, $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs } ) ;
4312 $biggest_in_all_2 = max( $biggest_in_all_2 , $mysync->{ folder2 }->{ $folder2 }->{ biggest } ) ;
4313
4314 }
4315
4316 myprintf( "Host1 Nb folders: %11s folders\n", $nb_folders_1 ) ;
4317 myprintf( "Host2 Nb folders: %11s folders\n", $nb_folders_2 ) ;
4318 myprint( "\n" ) ;
4319 myprintf( "Host1 Nb messages: %11s messages\n", $total_nb_1 ) ;
4320 myprintf( "Host2 Nb messages: %11s messages\n", $total_nb_2 ) ;
4321 myprint( "\n" ) ;
4322 myprintf( "Host1 Total size: %11s bytes (%s)\n", $total_size_1, bytes_display_string( $total_size_1 ) ) ;
4323 myprintf( "Host2 Total size: %11s bytes (%s)\n", $total_size_2, bytes_display_string( $total_size_2 ) ) ;
4324 myprint( "\n" ) ;
4325 myprintf( "Host1 Biggest message: %11s bytes (%s)\n", $biggest_in_all_1, bytes_display_string( $biggest_in_all_1 ) ) ;
4326 myprintf( "Host2 Biggest message: %11s bytes (%s)\n", $biggest_in_all_2, bytes_display_string( $biggest_in_all_2 ) ) ;
4327 myprint( "\n" ) ;
4328 myprintf( "Time spent on sizing: %11.1f seconds\n", timenext( $mysync ) ) ;
4329
4330 my @total_1_2 = ( $total_nb_1, $total_size_1, $total_nb_2, $total_size_2 ) ;
4331 return @total_1_2 ;
4332}
4333
4334sub foldersizesatend_old
4335{
4336 my $mysync = shift ;
4337 timenext( $mysync ) ;
4338 return if ( $mysync->{imap1}->IsUnconnected( ) ) ;
4339 return if ( $mysync->{imap2}->IsUnconnected( ) ) ;
4340 # Get all folders on host2 again since new were created
4341 @h2_folders_all = sort $mysync->{imap2}->folders();
4342 for ( @h2_folders_all ) {
4343 $h2_folders_all{ $_ } = 1 ;
4344 $mysync->{h2_folders_all_UPPER}{ uc $_ } = 1 ;
4345 } ;
4346 ( $h1_nb_msg_end, $h1_bytes_end ) = foldersizes( $mysync, 'Host1', $mysync->{imap1}, $mysync->{ search1 }, $mysync->{abletosearch1}, @{ $mysync->{h1_folders_wanted} } ) ;
4347 ( $h2_nb_msg_end, $h2_bytes_end ) = foldersizes( $mysync, 'Host2', $mysync->{imap2}, $mysync->{ search2 }, $mysync->{abletosearch2}, @h2_folders_from_1_wanted ) ;
4348 if ( not all_defined( $h1_nb_msg_end, $h1_bytes_end, $h2_nb_msg_end, $h2_bytes_end ) ) {
4349 my $error = "Failure getting foldersizes, final differences will not be calculated\n" ;
4350 errors_incr( $mysync, $error ) ;
4351 }
4352 return ;
4353}
4354
4355sub foldersizesatend
4356{
4357 my $mysync = shift ;
4358 timenext( $mysync ) ;
4359 return if ( $mysync->{imap1}->IsUnconnected( ) ) ;
4360 return if ( $mysync->{imap2}->IsUnconnected( ) ) ;
4361 # Get all folders on host2 again since new were created
4362 @h2_folders_all = sort $mysync->{imap2}->folders();
4363 for ( @h2_folders_all ) {
4364 $h2_folders_all{ $_ } = 1 ;
4365 $mysync->{h2_folders_all_UPPER}{ uc $_ } = 1 ;
4366 } ;
4367
4368
4369 foldersizes_diff_list( $mysync, $FORCE ) ;
4370
4371 ( $h1_nb_msg_end, $h1_bytes_end, $h2_nb_msg_end, $h2_bytes_end )
4372 = foldersizes_total( $mysync ) ;
4373
4374
4375 if ( not all_defined( $h1_nb_msg_end, $h1_bytes_end, $h2_nb_msg_end, $h2_bytes_end ) ) {
4376 my $error = "Failure getting foldersizes, final differences will not be calculated\n" ;
4377 errors_incr( $mysync, $error ) ;
4378 }
4379 return ;
4380}
4381
4382
4383sub foldersizes_at_the_beggining
4384{
4385 my $mysync = shift ;
4386
4387 myprint( << 'END_SIZE' ) ;
4388
4389Folders sizes before the synchronization.
4390You can remove foldersizes listings by using "--nofoldersizes" and "--nofoldersizesatend"
4391but then you will also lose the ETA (Estimation Time of Arrival) given after each message copy.
4392END_SIZE
4393
4394 foldersizes_diff_list( $mysync ) ;
4395
4396 ( $mysync->{ h1_nb_msg_start }, $mysync->{ h1_bytes_start },
4397 $mysync->{ h2_nb_msg_start }, $mysync->{ h2_bytes_start } )
4398 = foldersizes_total( $mysync ) ;
4399
4400
4401 if ( not all_defined(
4402 $mysync->{ h1_nb_msg_start },
4403 $mysync->{ h1_bytes_start },
4404 $mysync->{ h2_nb_msg_start },
4405 $mysync->{ h2_bytes_start } ) )
4406 {
4407 my $error = "Failure getting foldersizes, ETA and final diff will not be displayed\n" ;
4408 errors_incr( $mysync, $error ) ;
4409 $mysync->{ foldersizes } = 0 ;
4410 $mysync->{ foldersizesatend } = 0 ;
4411 return ;
4412 }
4413
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02004414 my $h2_bytes_limit = $mysync->{ acc2 }->{quota_limit_bytes} || 0 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004415 if ( $h2_bytes_limit and ( $h2_bytes_limit < $mysync->{ h1_bytes_start } ) )
4416 {
4417 my $quota_percent = mysprintf( '%.0f', $NUMBER_100 * $mysync->{ h1_bytes_start } / $h2_bytes_limit ) ;
4418 my $error = "Host2: Quota limit will be exceeded! Over $quota_percent % ( $mysync->{ h1_bytes_start } bytes / $h2_bytes_limit bytes )\n" ;
4419 errors_incr( $mysync, $error ) ;
4420 }
4421 return ;
4422}
4423
4424
4425# Globals:
4426# @h2_folders_from_1_wanted
4427
4428sub foldersizes_at_the_beggining_old
4429{
4430 my $mysync = shift ;
4431
4432 myprint( << 'END_SIZE' ) ;
4433
4434Folders sizes before the synchronization.
4435You can remove foldersizes listings by using "--nofoldersizes" and "--nofoldersizesatend"
4436but then you will also lose the ETA (Estimation Time of Arrival) given after each message copy.
4437END_SIZE
4438
4439 ( $mysync->{ h1_nb_msg_start }, $mysync->{ h1_bytes_start } ) =
4440 foldersizes( $mysync, 'Host1', $mysync->{imap1}, $mysync->{ search1 },
4441 $mysync->{abletosearch1}, @{ $mysync->{h1_folders_wanted} } ) ;
4442 ( $mysync->{ h2_nb_msg_start }, $mysync->{ h2_bytes_start } ) =
4443 foldersizes( $mysync, 'Host2', $mysync->{imap2}, $mysync->{ search2 },
4444 $mysync->{abletosearch2}, @h2_folders_from_1_wanted ) ;
4445
4446 if ( not all_defined( $mysync->{ h1_nb_msg_start },
4447 $mysync->{ h1_bytes_start }, $mysync->{ h2_nb_msg_start }, $mysync->{ h2_bytes_start } ) )
4448 {
4449 my $error = "Failure getting foldersizes, ETA and final diff will not be displayed\n" ;
4450 errors_incr( $mysync, $error ) ;
4451 $mysync->{ foldersizes } = 0 ;
4452 $mysync->{ foldersizesatend } = 0 ;
4453 return ;
4454 }
4455
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02004456 my $h2_bytes_limit = $mysync->{ acc2 }->{quota_limit_bytes} || 0 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004457 if ( $h2_bytes_limit and ( $h2_bytes_limit < $mysync->{ h1_bytes_start } ) )
4458 {
4459 my $quota_percent = mysprintf( '%.0f', $NUMBER_100 * $mysync->{ h1_bytes_start } / $h2_bytes_limit ) ;
4460 my $error = "Host2: Quota limit will be exceeded! Over $quota_percent % ( $mysync->{ h1_bytes_start } bytes / $h2_bytes_limit bytes )\n" ;
4461 errors_incr( $mysync, $error ) ;
4462 }
4463 return ;
4464}
4465
4466
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02004467sub tests_total_bytes_max_reached
4468{
4469 note( 'Entering tests_total_bytes_max_reached()' ) ;
4470
4471 is( undef, total_bytes_max_reached( ), 'total_bytes_max_reached: no args => undef' ) ;
4472
4473 my $mysync = {} ;
4474 is( undef, total_bytes_max_reached( $mysync ), 'total_bytes_max_reached: no exitwhenover => undef' ) ;
4475
4476 $mysync->{ exitwhenover } = 300 ;
4477 is( undef, total_bytes_max_reached( $mysync ), 'total_bytes_max_reached: exitwhenover 300 but no total_bytes_transferred => undef' ) ;
4478
4479 $mysync->{ total_bytes_transferred } = 200 ;
4480 is( undef, total_bytes_max_reached( $mysync ), 'total_bytes_max_reached: exitwhenover 300 but total_bytes_transferred 200 => undef' ) ;
4481
4482 $mysync->{ total_bytes_transferred } = 400 ;
4483 is( 1, total_bytes_max_reached( $mysync ), 'total_bytes_max_reached: exitwhenover 300 but total_bytes_transferred 400 => 1' ) ;
4484
4485
4486
4487 note( 'Leaving tests_total_bytes_max_reached()' ) ;
4488 return ;
4489}
4490
4491
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004492sub total_bytes_max_reached
4493{
4494 my $mysync = shift ;
4495
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02004496 if ( ! defined $mysync ) { return ; }
4497
4498 if ( ! $mysync->{ exitwhenover } )
4499 {
4500 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004501 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02004502
4503 if ( ! $mysync->{ total_bytes_transferred } )
4504 {
4505 return ;
4506 }
4507
4508 if ( $mysync->{ total_bytes_transferred } >= $mysync->{ exitwhenover } )
4509 {
4510 my $error = "Maximum bytes transferred reached, $mysync->{total_bytes_transferred} >= $mysync->{ exitwhenover }, ending sync\n" ;
4511 errors_incr( $mysync, $error ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004512 return( 1 ) ;
4513 }
4514 return ;
4515}
4516
4517
4518sub tests_mock_capability
4519{
4520 note( 'Entering tests_mock_capability()' ) ;
4521
4522 my $myimap ;
4523 ok( $myimap = mock_capability( ),
4524 'mock_capability: (1) no args => a Test::MockObject'
4525 ) ;
4526 ok( $myimap->isa( 'Test::MockObject' ),
4527 'mock_capability: (2) no args => a Test::MockObject'
4528 ) ;
4529
4530 is( undef, $myimap->capability( ),
4531 'mock_capability: (3) no args => capability undef'
4532 ) ;
4533
4534 ok( mock_capability( $myimap ),
4535 'mock_capability: (1) one arg => MockObject'
4536 ) ;
4537
4538 is( undef, $myimap->capability( ),
4539 'mock_capability: (2) one arg OO style => capability undef'
4540 ) ;
4541
4542 ok( mock_capability( $myimap, $NUMBER_123456 ),
4543 'mock_capability: (1) two args 123456 => capability 123456'
4544 ) ;
4545
4546 is( $NUMBER_123456, $myimap->capability( ),
4547 'mock_capability: (2) two args 123456 => capability 123456'
4548 ) ;
4549
4550 ok( mock_capability( $myimap, 'ABCD' ),
4551 'mock_capability: (1) two args ABCD => capability ABCD'
4552 ) ;
4553 is( 'ABCD', $myimap->capability( ),
4554 'mock_capability: (2) two args ABCD => capability ABCD'
4555 ) ;
4556
4557 ok( mock_capability( $myimap, [ 'ABCD' ] ),
4558 'mock_capability: (1) two args [ ABCD ] => capability [ ABCD ]'
4559 ) ;
4560 is_deeply( [ 'ABCD' ], $myimap->capability( ),
4561 'mock_capability: (2) two args [ ABCD ] => capability [ ABCD ]'
4562 ) ;
4563
4564 ok( mock_capability( $myimap, [ 'ABC', 'DEF' ] ),
4565 'mock_capability: (1) two args [ ABC, DEF ] => capability [ ABC, DEF ]'
4566 ) ;
4567 is_deeply( [ 'ABC', 'DEF' ], $myimap->capability( ),
4568 'mock_capability: (2) two args [ ABC, DEF ] => capability capability [ ABC, DEF ]'
4569 ) ;
4570
4571 ok( mock_capability( $myimap, 'ABC', 'DEF' ),
4572 'mock_capability: (1) two args ABC, DEF => capability [ ABC, DEF ]'
4573 ) ;
4574 is_deeply( [ 'ABC', 'DEF' ], [ $myimap->capability( ) ],
4575 'mock_capability: (2) two args ABC, DEF => capability capability [ ABC, DEF ]'
4576 ) ;
4577
4578 ok( mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ),
4579 'mock_capability: (1) two args IMAP4rev1, APPENDLIMIT=123456 => capability [ IMAP4rev1, APPENDLIMIT=123456 ]'
4580 ) ;
4581 is_deeply( [ 'IMAP4rev1', 'APPENDLIMIT=123456' ], [ $myimap->capability( ) ],
4582 'mock_capability: (2) two args IMAP4rev1, APPENDLIMIT=123456 => capability capability [ IMAP4rev1, APPENDLIMIT=123456 ]'
4583 ) ;
4584
4585 note( 'Leaving tests_mock_capability()' ) ;
4586 return ;
4587}
4588
4589sub sig_install_toggle_sleep
4590{
4591 my $mysync = shift ;
4592 if ( 'MSWin32' ne $OSNAME ) {
4593 #myprint( "sig_install( $mysync, \&toggle_sleep, 'USR1' )\n" ) ;
4594 sig_install( $mysync, 'toggle_sleep', 'USR1' ) ;
4595 }
4596 #myprint( "Leaving sig_install_toggle_sleep\n" ) ;
4597 return ;
4598}
4599
4600
4601sub mock_capability
4602{
4603 my $myimap = shift ;
4604 my @has_capability_value = @ARG ;
4605 my ( $has_capability_value ) = @has_capability_value ;
4606
4607 if ( ! $myimap )
4608 {
4609 require_ok( "Test::MockObject" ) ;
4610 $myimap = Test::MockObject->new( ) ;
4611 }
4612
4613 $myimap->mock(
4614 'capability',
4615 sub { return wantarray ?
4616 @has_capability_value
4617 : $has_capability_value ;
4618 }
4619 ) ;
4620
4621 return $myimap ;
4622}
4623
4624
4625sub tests_capability_of
4626{
4627 note( 'Entering tests_capability_of()' ) ;
4628
4629 is( undef, capability_of( ),
4630 'capability_of: no args => undef' ) ;
4631
4632 my $myimap ;
4633 is( undef, capability_of( $myimap ),
4634 'capability_of: undef => undef' ) ;
4635
4636
4637 $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ;
4638
4639 is( undef, capability_of( $myimap, 'CACA' ),
4640 'capability_of: two args unknown capability => undef' ) ;
4641
4642
4643 is( $NUMBER_123456, capability_of( $myimap, 'APPENDLIMIT' ),
4644 'capability_of: two args APPENDLIMIT 123456 => 123456 yeah!' ) ;
4645
4646 note( 'Leaving tests_capability_of()' ) ;
4647 return ;
4648}
4649
4650
4651sub capability_of
4652{
4653 my $imap = shift || return ;
4654 my $capability_keyword = shift || return ;
4655
4656 my @capability = $imap->capability ;
4657
4658 if ( ! @capability ) { return ; }
4659 my $capability_value = search_in_array( $capability_keyword, @capability ) ;
4660
4661 return $capability_value ;
4662}
4663
4664
4665sub tests_search_in_array
4666{
4667 note( 'Entering tests_search_in_array()' ) ;
4668
4669 is( undef, search_in_array( 'KA' ),
4670 'search_in_array: no array => undef ' ) ;
4671
4672 is( 'VA', search_in_array( 'KA', ( 'KA=VA' ) ),
4673 'search_in_array: KA KA=VA => VA ' ) ;
4674
4675 is( 'VA', search_in_array( 'KA', ( 'KA=VA', 'KB=VB' ) ),
4676 'search_in_array: KA KA=VA KB=VB => VA ' ) ;
4677
4678 is( 'VB', search_in_array( 'KB', ( 'KA=VA', 'KB=VB' ) ),
4679 'search_in_array: KA=VA KB=VB => VB ' ) ;
4680
4681 note( 'Leaving tests_search_in_array()' ) ;
4682 return ;
4683}
4684
4685sub search_in_array
4686{
4687 my ( $key, @array ) = @ARG ;
4688
4689 foreach my $item ( @array )
4690 {
4691
4692 if ( $item =~ /([^=]+)=(.*)/ )
4693 {
4694 if ( $1 eq $key )
4695 {
4696 return $2 ;
4697 }
4698 }
4699 }
4700
4701 return ;
4702}
4703
4704
4705
4706
4707sub tests_appendlimit_from_capability
4708{
4709 note( 'Entering tests_appendlimit_from_capability()' ) ;
4710
4711 is( undef, appendlimit_from_capability( ),
4712 'appendlimit_from_capability: no args => undef'
4713 ) ;
4714
4715 my $myimap ;
4716 is( undef, appendlimit_from_capability( $myimap ),
4717 'appendlimit_from_capability: undef arg => undef'
4718 ) ;
4719
4720
4721 $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ;
4722
4723 # Normal behavior
4724 is( $NUMBER_123456, appendlimit_from_capability( $myimap ),
4725 'appendlimit_from_capability: APPENDLIMIT=123456 => 123456'
4726 ) ;
4727
4728 # Not a number
4729 $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=ABC' ) ;
4730
4731 is( undef, appendlimit_from_capability( $myimap ),
4732 'appendlimit_from_capability: not a number => undef'
4733 ) ;
4734
4735 note( 'Leaving tests_appendlimit_from_capability()' ) ;
4736 return ;
4737}
4738
4739
4740sub appendlimit_from_capability
4741{
4742 my $myimap = shift ;
4743 if ( ! $myimap )
4744 {
4745 myprint( "Warn: no imap with call to appendlimit_from_capability\n" ) ;
4746 return ;
4747 }
4748
4749 #myprint( Data::Dumper->Dump( [ \$myimap ] ) ) ;
4750 my $appendlimit = capability_of( $myimap, 'APPENDLIMIT' ) ;
4751 #myprint( "has_capability APPENDLIMIT $appendlimit\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02004752 if ( is_integer( $appendlimit ) )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004753 {
4754 return $appendlimit ;
4755 }
4756 return ;
4757}
4758
4759
4760sub tests_appendlimit
4761{
4762 note( 'Entering tests_appendlimit()' ) ;
4763
4764 is( undef, appendlimit( ),
4765 'appendlimit: no args => undef'
4766 ) ;
4767
4768 my $mysync = { } ;
4769
4770 is( undef, appendlimit( $mysync ),
4771 'appendlimit: no imap2 => undef'
4772 ) ;
4773
4774 my $myimap ;
4775 $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ;
4776
4777 $mysync->{ imap2 } = $myimap ;
4778
4779 is( 123456, appendlimit( $mysync ),
4780 'appendlimit: imap2 with APPENDLIMIT=123456 => 123456'
4781 ) ;
4782
4783 note( 'Leaving tests_appendlimit()' ) ;
4784 return ;
4785}
4786
4787sub appendlimit
4788{
4789 my $mysync = shift || return ;
4790 my $myimap = $mysync->{ imap2 } ;
4791
4792 my $appendlimit = appendlimit_from_capability( $myimap ) ;
4793 if ( defined $appendlimit )
4794 {
4795 myprint( "Host2: found APPENDLIMIT=$appendlimit in CAPABILITY (use --appendlimit xxxx to override this automatic setting)\n" ) ;
4796 return $appendlimit ;
4797 }
4798 return ;
4799
4800}
4801
4802
4803sub tests_maxsize_setting
4804{
4805 note( 'Entering tests_maxsize_setting()' ) ;
4806
4807 is( undef, maxsize_setting( ),
4808 'maxsize_setting: no args => undef'
4809 ) ;
4810
4811 my $mysync ;
4812
4813 is( undef, maxsize_setting( $mysync ),
4814 'maxsize_setting: undef arg => undef'
4815 ) ;
4816
4817 $mysync = { } ;
4818 $mysync->{ maxsize } = $NUMBER_123456 ;
4819
4820 # --maxsize alone
4821 is( $NUMBER_123456, maxsize_setting( $mysync ),
4822 'maxsize_setting: --maxsize 123456 alone => 123456'
4823 ) ;
4824
4825
4826 $mysync = { } ;
4827 my $myimap ;
4828
4829 $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=654321' ) ;
4830 $mysync->{ imap2 } = $myimap ;
4831
4832 # APPENDLIMIT alone
4833 is( $NUMBER_654321, maxsize_setting( $mysync ),
4834 'maxsize_setting: APPENDLIMIT 654321 alone => 654321'
4835 ) ;
4836
4837 is( $NUMBER_654321, $mysync->{ maxsize },
4838 'maxsize_setting: APPENDLIMIT 654321 alone => maxsize 654321'
4839 ) ;
4840
4841 # APPENDLIMIT with --appendlimit => --appendlimit wins
4842 $mysync->{ appendlimit } = $NUMBER_123456 ;
4843
4844 is( $NUMBER_123456, maxsize_setting( $mysync ),
4845 'maxsize_setting: APPENDLIMIT 654321 + --appendlimit 123456 => 123456'
4846 ) ;
4847
4848 is( $NUMBER_123456, $mysync->{ maxsize },
4849 'maxsize_setting: APPENDLIMIT 654321 + --appendlimit 123456 => maxsize 123456'
4850 ) ;
4851
4852 # Fresh
4853 $mysync = { } ;
4854 $mysync->{ imap2 } = $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=654321' ) ;
4855
4856 # Case: "APPENDLIMIT >= --maxsize" => maxsize.
4857 $mysync->{ maxsize } = $NUMBER_123456 ;
4858
4859 is( $NUMBER_123456, maxsize_setting( $mysync ),
4860 'maxsize_setting: APPENDLIMIT 654321 --maxsize 123456 => 123456'
4861 ) ;
4862
4863 # Case: "APPENDLIMIT < --maxsize" => APPENDLIMIT.
4864
4865
4866 # Fresh
4867 $mysync = { } ;
4868 $mysync->{ imap2 } = $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ;
4869 $mysync->{ maxsize } = $NUMBER_654321 ;
4870
4871 is( $NUMBER_123456, maxsize_setting( $mysync ),
4872 'maxsize_setting: APPENDLIMIT 123456 --maxsize 654321 => 123456 '
4873 ) ;
4874
4875 # Now --truncmess stuff
4876
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004877 note( 'Leaving tests_maxsize_setting()' ) ;
4878
4879 return ;
4880}
4881
4882# Three variables to take account of
4883# appendlimit (given by --appendlimit or CAPABILITY...)
4884# maxsize
4885# truncmess
4886
4887sub maxsize_setting
4888{
4889 my $mysync = shift || return ;
4890
4891 if ( defined $mysync->{ appendlimit } )
4892 {
4893 myprint( "Host2: Getting appendlimit from --appendlimit $mysync->{ appendlimit }\n" ) ;
4894 }
4895 else
4896 {
4897 $mysync->{ appendlimit } = appendlimit( $mysync ) ;
4898 }
4899
4900
4901 if ( all_defined( $mysync->{ appendlimit }, $mysync->{ maxsize } ) )
4902 {
4903 my $min_maxsize_appendlimit = min( $mysync->{ maxsize }, $mysync->{ appendlimit } ) ;
4904 myprint( "Host2: Setting maxsize to $min_maxsize_appendlimit (min of --maxsize $mysync->{ maxsize } and appendlimit $mysync->{ appendlimit }\n" ) ;
4905 $mysync->{ maxsize } = $min_maxsize_appendlimit ;
4906 return $mysync->{ maxsize } ;
4907 }
4908 elsif ( defined $mysync->{ appendlimit } )
4909 {
4910 myprint( "Host2: Setting maxsize to appendlimit $mysync->{ appendlimit }\n" ) ;
4911 $mysync->{ maxsize } = $mysync->{ appendlimit } ;
4912 return $mysync->{ maxsize } ;
4913 }elsif ( defined $mysync->{ maxsize } )
4914 {
4915 return $mysync->{ maxsize } ;
4916 }else
4917 {
4918 return ;
4919 }
4920}
4921
4922
4923
4924
4925sub all_defined
4926{
4927 if ( not @ARG ) {
4928 return 0 ;
4929 }
4930 foreach my $elem ( @ARG ) {
4931 if ( not defined $elem ) {
4932 return 0 ;
4933 }
4934 }
4935 return 1 ;
4936}
4937
4938sub tests_all_defined
4939{
4940 note( 'Entering tests_all_defined()' ) ;
4941
4942 is( 0, all_defined( ), 'all_defined: no param => 0' ) ;
4943 is( 0, all_defined( () ), 'all_defined: void list => 0' ) ;
4944 is( 0, all_defined( undef ), 'all_defined: undef => 0' ) ;
4945 is( 0, all_defined( undef, undef ), 'all_defined: undef => 0' ) ;
4946 is( 0, all_defined( 1, undef ), 'all_defined: 1 undef => 0' ) ;
4947 is( 0, all_defined( undef, 1 ), 'all_defined: undef 1 => 0' ) ;
4948 is( 1, all_defined( 1, 1 ), 'all_defined: 1 1 => 1' ) ;
4949 is( 1, all_defined( (1, 1) ), 'all_defined: (1 1) => 1' ) ;
4950
4951 note( 'Leaving tests_all_defined()' ) ;
4952 return ;
4953}
4954
4955
4956sub tests_hashsynclocal
4957{
4958 note( 'Entering tests_hashsynclocal()' ) ;
4959
4960 my $mysync = {
4961 host1 => q{},
4962 user1 => q{},
4963 password1 => q{},
4964 host2 => q{},
4965 user2 => q{},
4966 password2 => q{},
4967 } ;
4968
4969 is( undef, hashsynclocal( $mysync ), 'hashsynclocal: no hashfile name' ) ;
4970
4971 $mysync->{ hashfile } = q{} ;
4972 is( undef, hashsynclocal( $mysync ), 'hashsynclocal: empty hashfile name' ) ;
4973
4974 $mysync->{ hashfile } = './noexist/rrr' ;
4975 is( undef, hashsynclocal( $mysync ), 'hashsynclocal: no exists hashfile dir' ) ;
4976
4977 SKIP: {
4978 if ( 'MSWin32' eq $OSNAME or '0' eq $EFFECTIVE_USER_ID ) { skip( 'Tests only for non-root Unix', 1 ) ; }
4979 $mysync->{ hashfile } = '/rrr' ;
4980 is( undef, hashsynclocal( $mysync ), 'hashsynclocal: permission denied' ) ;
4981 }
4982 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'hashsynclocal: mkpath W/tmp/tests/' ) ;
4983 $mysync->{ hashfile } = 'W/tmp/tests/imapsync_hash' ;
4984
4985 ok( ! -e 'W/tmp/tests/imapsync_hash' || unlink 'W/tmp/tests/imapsync_hash', 'hashsynclocal: unlink W/tmp/tests/imapsync_hash' ) ;
4986 ok( ! -e 'W/tmp/tests/imapsync_hash', 'hashsynclocal: verify there is no W/tmp/tests/imapsync_hash' ) ;
4987 is( 'ecdeb4ede672794d173da4e08c52b8ee19b7d252', hashsynclocal( $mysync, 'mukksyhpmbixkxkpjlqivmlqsulpictj' ), 'hashsynclocal: creating/reading W/tmp/tests/imapsync_hash' ) ;
4988 # A second time now
4989 is( 'ecdeb4ede672794d173da4e08c52b8ee19b7d252', hashsynclocal( $mysync ), 'hashsynclocal: reading W/tmp/tests/imapsync_hash second time => same' ) ;
4990
4991 note( 'Leaving tests_hashsynclocal()' ) ;
4992 return ;
4993}
4994
4995sub hashsynclocal
4996{
4997 my $mysync = shift ;
4998 my $hashkey = shift ; # Optional, only there for tests
4999 my $hashfile = $mysync->{ hashfile } ;
5000 $hashfile = createhashfileifneeded( $hashfile, $hashkey ) ;
5001 if ( ! $hashfile ) {
5002 return ;
5003 }
5004 $hashkey = firstline( $hashfile ) ;
5005 if ( ! $hashkey ) {
5006 myprint( "No hashkey!\n" ) ;
5007 return ;
5008 }
5009 my $hashsynclocal = hashsync( $mysync, $hashkey ) ;
5010 return( $hashsynclocal ) ;
5011
5012}
5013
5014sub tests_hashsync
5015{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02005016 note( 'Entering tests_hashsync()' ) ;
5017
5018 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hashsync( ), 'hashsync: no args' ) ;
5019
5020 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hashsync( {}, q{} ), 'hashsync: empty args' ) ;
5021 my $mysync ;
5022 $mysync->{ host1 } = 'zzz' ;
5023 is( 'e86a28a3611c1e7bbaf8057cd00ae122781a11fe', hashsync( $mysync, q{} ), 'hashsync: host1 zzz => ' ) ;
5024 is( '6a7b451ac99eab1531ad8e6cd544b32420c552ac', hashsync( $mysync, q{A} ), 'hashsync: host1 zzz => ' ) ;
5025 $mysync->{ host2 } = 'zzz' ;
5026 is( '15959573e4a86763253a7aedb1a2b0c60d133dc2', hashsync( $mysync, q{} ), 'hashsync: + host2 zzz => ' ) ;
5027 is( 'b8d4ab541b209c75928528020ca28ee43488bd8f', hashsync( $mysync, 'A' ), 'hashsync: + hashkey A => ' ) ;
5028
5029 $mysync = undef ;
5030 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hashsync( $mysync, q{} ), 'hashsync: undef $mysync' ) ;
5031 $mysync->{ password1 } = 'abcd' ;
5032 is( 'afa29ab8534495251ac8346a985717c54bc49c26', hashsync( $mysync, q{} ), 'hashsync: password1: abcd' ) ;
5033
5034 # A user reported a massive failure on /X (Thomas V. 21/04/2020 Ã 21:41 Subject: Error)
5035 # "Wide character in subroutine entry at /usr/local/lib/perl5/site_perl/Digest/HMAC.pm"
5036 # I can reproduce it now
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005037
5038
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02005039 # The eval is there to avoid a complete crash
5040 # this one is fatal so it is commented
5041 # is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', 1 / 0 , 'hashsync: 1 / 0 fatal' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005042
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02005043 my $eval ;
5044 # this one is not fatal
5045 is( undef, $eval = eval { 1 / 0 } , 'hashsync: 1/0 not fatal' ) ;
5046 # this one neither
5047 $mysync->{ password1 } = 'Ö' ;
5048 is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', $eval = eval { hashsync( $mysync, q{} ) } , 'hashsync: password1: Ö with eval' ) ;
5049
5050 $mysync->{ password1 } = 'Ö' ;
5051 is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hashsync( $mysync, q{} ), 'hashsync: password1: Ö without eval' ) ;
5052
5053 $mysync->{ password1 } = qq{\x{00D6}} ;
5054 is( 'bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a', $eval = eval { hashsync( $mysync, q{} ) }, 'hashsync: password1: \x{00D6}' ) ; #
5055
5056 print qq{1 00D6:Ö\n} ;
5057 print encode_utf8( qq{2 00D6:Ö\n} ) ;
5058 print qq{3 00D6:\x{00D6}\n} ;
5059 print encode_utf8( qq{4 00D6:\x{00D6}\n} ) ;
5060
5061
5062 print qq{5 6536:收\n} ;
5063 print encode_utf8( qq{6 6536:收\n} ) ;
5064 # the next one prints "Wide character in print at ./imapsync line xxxx"
5065 print qq{7 6536:\x{6536}\n} ;
5066 print encode_utf8( qq{8 6536:\x{6536}\n} ) ;
5067
5068 $mysync->{ password1 } = qq{收} ;
5069 is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hashsync( $mysync, q{} ), 'hashsync: password1: 收' ) ;
5070
5071 $mysync->{ password1 } = qq{\x{6536}} ;
5072 is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', $eval = eval{ hashsync( $mysync, q{} ) }, 'hashsync: password1: \x{6536} with eval' ) ;
5073
5074 # No side effect.
5075 $mysync->{ password1 } = 'abcd' ;
5076 is( 'afa29ab8534495251ac8346a985717c54bc49c26', hashsync( $mysync, q{} ), 'hashsync: password1: abcd again' ) ;
5077
5078 note( 'Leaving tests_hashsync()' ) ;
5079 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005080}
5081
5082sub hashsync
5083{
5084 my $mysync = shift ;
5085 my $hashkey = shift ;
5086
5087 my $mystring = join( q{},
5088 $mysync->{ host1 } || q{},
5089 $mysync->{ user1 } || q{},
5090 $mysync->{ password1 } || q{},
5091 $mysync->{ host2 } || q{},
5092 $mysync->{ user2 } || q{},
5093 $mysync->{ password2 } || q{},
5094 ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02005095 #my $hashsync = hmac_sha1_hex( $mystring, $hashkey ) ;
5096 my $hashsync = hmac_sha1_hex_robust( $mystring, $hashkey ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005097 #myprint( "$hashsync\n" ) ;
5098 return( $hashsync ) ;
5099}
5100
5101
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02005102sub tests_hmac_sha1_hex
5103{
5104 note( 'Entering tests_hmac_sha1_hex()' ) ;
5105
5106 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex( ), 'hmac_sha1_hex: no args => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
5107 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex( '' ), 'hmac_sha1_hex: empty string => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
5108 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex( '', '' ), 'hmac_sha1_hex: empty strings => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
5109 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex( '', '', 'caca' ), 'hmac_sha1_hex: empty strings + caca => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
5110
5111 # Good
5112 is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hmac_sha1_hex( 'Ö' ), 'hmac_sha1_hex: Ö => f1a3f3dac3f137fd658027c11678b895f773ce55' ) ;
5113 is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hmac_sha1_hex( encode_utf8(qq{\x{00D6}}) ), 'hmac_sha1_hex: encode_utf8 \x{00D6} => f1a3f3dac3f137fd658027c11678b895f773ce55' ) ;
5114 # Bad
5115 is( 'fe8dc3b9ba3e8850bb4a7b070b2279e911003af2', hmac_sha1_hex( encode_utf8( 'Ö' ) ), 'hmac_sha1_hex: encode_utf8 Ö => fe8dc3b9ba3e8850bb4a7b070b2279e911003af2' ) ;
5116 is( 'bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a', hmac_sha1_hex( qq{\x{00D6}} ), 'hmac_sha1_hex: qq{\x{00D6}} => bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a' ) ;
5117
5118 # Good
5119 is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex( 'A' ), 'hmac_sha1_hex: A => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
5120 is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex( encode_utf8(qq{\x{0041}}) ), 'hmac_sha1_hex: encode_utf8 \x{0041} => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
5121 is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex( encode_utf8( 'A' ) ), 'hmac_sha1_hex: encode_utf8 A => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
5122 is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex( qq{\x{0041}} ), 'hmac_sha1_hex: \x{0041} => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
5123
5124 # Good
5125 is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex( 'A', 'B' ), 'hmac_sha1_hex: A B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
5126 is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex( encode_utf8(qq{\x{0041}}), 'B' ), 'hmac_sha1_hex: encode_utf8 \x{0041} B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
5127 is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex( encode_utf8( 'A' ), 'B' ), 'hmac_sha1_hex: encode_utf8 A B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
5128 is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex( qq{\x{0041}}, 'B' ), 'hmac_sha1_hex: \x{0041} B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
5129
5130 # http://unicode.scarfboy.com/?s=U%2B6536
5131 # Good
5132 is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex( '收' ), 'hmac_sha1_hex: 收 => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ;
5133 is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex( encode_utf8(qq{\x{6536}}) ), 'hmac_sha1_hex: encode_utf8 \x{6536} => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ;
5134 # Bad
5135 is( 'e82217119628ad03e659cc89671d05ea4cee7238', hmac_sha1_hex( encode_utf8( '收' ) ), 'hmac_sha1_hex: encode_utf8 收 => e82217119628ad03e659cc89671d05ea4cee7238' ) ;
5136 # Very very bad, perl dies...
5137 #is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex( qq{\x{6536}} ), 'hmac_sha1_hex: \x{6536} => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ;
5138 # Ok but well, bad indeed
5139 is( undef, my $eval = eval{ hmac_sha1_hex( qq{\x{6536}} ) }, 'hmac_sha1_hex: \x{6536} => undef' ) ;
5140
5141
5142 note( 'Leaving tests_hmac_sha1_hex()' ) ;
5143 return ;
5144}
5145
5146sub tests_hmac_sha1_hex_robust
5147{
5148 note( 'Entering tests_hmac_sha1_hex_robust()' ) ;
5149
5150 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex_robust( ), 'hmac_sha1_hex_robust: no args => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
5151 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex_robust( '' ), 'hmac_sha1_hex_robust: empty string => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
5152 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex_robust( '', '' ), 'hmac_sha1_hex_robust: empty strings => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
5153 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex_robust( '', '', 'caca' ), 'hmac_sha1_hex_robust: empty strings + caca => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
5154
5155 # Good
5156 is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hmac_sha1_hex_robust( 'Ö' ), 'hmac_sha1_hex_robust: Ö => f1a3f3dac3f137fd658027c11678b895f773ce55' ) ;
5157 is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hmac_sha1_hex_robust( encode_utf8(qq{\x{00D6}}) ), 'hmac_sha1_hex_robust: encode_utf8 \x{00D6} => f1a3f3dac3f137fd658027c11678b895f773ce55' ) ;
5158 # Bad
5159 is( 'fe8dc3b9ba3e8850bb4a7b070b2279e911003af2', hmac_sha1_hex_robust( encode_utf8( 'Ö' ) ), 'hmac_sha1_hex_robust: encode_utf8 Ö => fe8dc3b9ba3e8850bb4a7b070b2279e911003af2' ) ;
5160 is( 'bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a', hmac_sha1_hex_robust( qq{\x{00D6}} ), 'hmac_sha1_hex_robust: qq{\x{00D6}} => bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a' ) ;
5161
5162 # Good
5163 is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex_robust( 'A' ), 'hmac_sha1_hex_robust: A => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
5164 is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex_robust( encode_utf8(qq{\x{0041}}) ), 'hmac_sha1_hex_robust: encode_utf8 \x{0041} => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
5165 is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex_robust( encode_utf8( 'A' ) ), 'hmac_sha1_hex_robust: encode_utf8 A => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
5166 is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex_robust( qq{\x{0041}} ), 'hmac_sha1_hex_robust: \x{0041} => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
5167
5168 # Good
5169 is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex_robust( 'A', 'B' ), 'hmac_sha1_hex_robust: A B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
5170 is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex_robust( encode_utf8(qq{\x{0041}}), 'B' ), 'hmac_sha1_hex_robust: encode_utf8 \x{0041} B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
5171 is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex_robust( encode_utf8( 'A' ), 'B' ), 'hmac_sha1_hex_robust: encode_utf8 A B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
5172 is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex_robust( qq{\x{0041}}, 'B' ), 'hmac_sha1_hex_robust: \x{0041} B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
5173
5174 # http://unicode.scarfboy.com/?s=U%2B6536
5175 # Good
5176 is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex_robust( '收' ), 'hmac_sha1_hex_robust: 收 => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ;
5177 is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex_robust( encode_utf8(qq{\x{6536}}) ), 'hmac_sha1_hex_robust: encode_utf8 \x{6536} => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ;
5178 # Bad
5179 is( 'e82217119628ad03e659cc89671d05ea4cee7238', hmac_sha1_hex_robust( encode_utf8( '收' ) ), 'hmac_sha1_hex_robust: encode_utf8 收 => e82217119628ad03e659cc89671d05ea4cee7238' ) ;
5180 # Good
5181 is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex_robust( qq{\x{6536}} ), 'hmac_sha1_hex_robust: \x{6536} => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ;
5182 # Good again
5183 is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', my $eval = eval{ hmac_sha1_hex_robust( qq{\x{6536}} ) }, 'hmac_sha1_hex_robust: \x{6536} => undef' ) ;
5184
5185 note( 'Leaving tests_hmac_sha1_hex_robust()' ) ;
5186 return ;
5187}
5188
5189
5190sub hmac_sha1_hex_robust
5191{
5192 my $string = shift ;
5193 my $val ;
5194 if ( defined( $val = eval{ hmac_sha1_hex( $string, @ARG ) } ) )
5195 {
5196 return $val ;
5197 }
5198 elsif( defined( $val = eval{ hmac_sha1_hex( encode_utf8( $string ), @ARG ) } ) )
5199 {
5200 return $val ;
5201 }
5202 else
5203 {
5204 return ;
5205 }
5206}
5207
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005208sub tests_createhashfileifneeded
5209{
5210 note( 'Entering tests_createhashfileifneeded()' ) ;
5211
5212 is( undef, createhashfileifneeded( ), 'createhashfileifneeded: no parameters => undef' ) ;
5213
5214 note( 'Leaving tests_createhashfileifneeded()' ) ;
5215 return ;
5216}
5217
5218sub createhashfileifneeded
5219{
5220 my $hashfile = shift ;
5221 my $hashkey = shift || rand32( ) ;
5222
5223 # no name
5224 if ( ! $hashfile ) {
5225 return ;
5226 }
5227 # already there
5228 if ( -e -r $hashfile ) {
5229 return $hashfile ;
5230 }
5231 # not creatable
5232 if ( ! -w dirname( $hashfile ) ) {
5233 return ;
5234 }
5235 # creatable
5236 open my $FILE_HANDLE, '>', $hashfile
5237 or do {
5238 myprint( "Could not open $hashfile for writing. Check permissions or disk space." ) ;
5239 return ;
5240 } ;
5241 myprint( "Writing random hashkey in $hashfile, once for all times\n" ) ;
5242 print $FILE_HANDLE $hashkey ;
5243 close $FILE_HANDLE ;
5244 # Should be there now
5245 if ( -e -r $hashfile ) {
5246 return $hashfile ;
5247 }
5248 # unknown failure
5249 return ;
5250}
5251
5252sub tests_rand32
5253{
5254 note( 'Entering tests_rand32()' ) ;
5255
5256 my $string = rand32( ) ;
5257 myprint( "$string\n" ) ;
5258 is( 32, length( $string ), 'rand32: 32 characters long' ) ;
5259 is( 32, length( rand32( ) ), 'rand32: 32 characters long, another one' ) ;
5260
5261 note( 'Leaving tests_rand32()' ) ;
5262 return ;
5263}
5264
5265sub rand32
5266{
5267 my @chars = ( "a".."z" ) ;
5268 my $string;
5269 $string .= $chars[rand @chars] for 1..32 ;
5270 return $string ;
5271}
5272
5273sub imap_id_stuff
5274{
5275 my $mysync = shift ;
5276
5277 if ( not $mysync->{id} ) { return ; } ;
5278
5279 $mysync->{h1_imap_id} = imap_id( $mysync, $mysync->{imap1}, 'Host1' ) ;
5280 #myprint( 'Host1: ' . $mysync->{h1_imap_id} ) ;
5281 $mysync->{h2_imap_id} = imap_id( $mysync, $mysync->{imap2}, 'Host2' ) ;
5282 #myprint( 'Host2: ' . $mysync->{h2_imap_id} ) ;
5283
5284 return ;
5285}
5286
5287sub imap_id
5288{
5289 my ( $mysync, $imap, $Side ) = @_ ;
5290
5291 if ( not $mysync->{id} ) { return q{} ; } ;
5292
5293 $Side ||= q{} ;
5294 my $imap_id_response = q{} ;
5295
5296 if ( not $imap->has_capability( 'ID' ) ) {
5297 $imap_id_response = 'No ID capability' ;
5298 myprint( "$Side: No ID capability\n" ) ;
5299 }else{
5300 my $id_inp = imapsync_id( $mysync, { side => lc $Side } ) ;
5301 myprint( "\n$Side: found ID capability. Sending/receiving ID, presented in raw IMAP for now.\n"
5302 . "In order to avoid sending/receiving ID, use option --noid\n" ) ;
5303 my $debug_before = $imap->Debug( ) ;
5304 $imap->Debug( 1 ) ;
5305 my $id_out = $imap->tag_and_run( 'ID ' . $id_inp ) ;
5306 #my $id_out = $imap->tag_and_run( 'ID NIL' ) ;
5307 myprint( "\n" ) ;
5308 $imap->Debug( $debug_before ) ;
5309 #$imap_id_response = Data::Dumper->Dump( [ $id_out ], [ 'IMAP_ID' ] ) ;
5310 }
5311 return( $imap_id_response ) ;
5312}
5313
5314sub imapsync_id
5315{
5316 my $mysync = shift ;
5317 my $overhashref = shift ;
5318 # See http://tools.ietf.org/html/rfc2971.html
5319
5320 my $imapsync_id = { } ;
5321
5322 my $imapsync_id_lamiral = {
5323 name => 'imapsync',
5324 version => imapsync_version( $mysync ),
5325 os => $OSNAME,
5326 vendor => 'Gilles LAMIRAL',
5327 'support-url' => 'https://imapsync.lamiral.info/',
5328 # Example of date-time: 19-Sep-2015 08:56:07
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02005329 date => date_from_rcs( q{$Date: 2021/07/22 14:21:09 $ } ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005330 } ;
5331
5332 my $imapsync_id_github = {
5333 name => 'imapsync',
5334 version => imapsync_version( $mysync ),
5335 os => $OSNAME,
5336 vendor => 'github',
5337 'support-url' => 'https://github.com/imapsync/imapsync',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02005338 date => date_from_rcs( q{$Date: 2021/07/22 14:21:09 $ } ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005339 } ;
5340
5341 $imapsync_id = $imapsync_id_lamiral ;
5342 #$imapsync_id = $imapsync_id_github ;
5343 my %mix = ( %{ $imapsync_id }, %{ $overhashref } ) ;
5344 my $imapsync_id_str = format_for_imap_arg( \%mix ) ;
5345 #myprint( "$imapsync_id_str\n" ) ;
5346 return( $imapsync_id_str ) ;
5347}
5348
5349sub tests_imapsync_id
5350{
5351 note( 'Entering tests_imapsync_id()' ) ;
5352
5353 my $mysync ;
5354 ok( '("name" "imapsync" "version" "111" "os" "beurk" "vendor" "Gilles LAMIRAL" "support-url" "https://imapsync.lamiral.info/" "date" "22-12-1968" "side" "host1")'
5355 eq imapsync_id( $mysync,
5356 {
5357 version => 111,
5358 os => 'beurk',
5359 date => '22-12-1968',
5360 side => 'host1'
5361 }
5362 ),
5363 'tests_imapsync_id override'
5364 ) ;
5365
5366 note( 'Leaving tests_imapsync_id()' ) ;
5367 return ;
5368}
5369
5370sub format_for_imap_arg
5371{
5372 my $ref = shift ;
5373
5374 my $string = q{} ;
5375 my %terms = %{ $ref } ;
5376 my @terms = ( ) ;
5377 if ( not ( %terms ) ) { return( 'NIL' ) } ;
5378 # sort like in RFC then add extra key/values
5379 foreach my $key ( qw( name version os os-version vendor support-url address date command arguments environment) ) {
5380 if ( $terms{ $key } ) {
5381 push @terms, $key, $terms{ $key } ;
5382 delete $terms{ $key } ;
5383 }
5384 }
5385 push @terms, %terms ;
5386 $string = '(' . ( join q{ }, map { '"' . $_ . '"' } @terms ) . ')' ;
5387 return( $string ) ;
5388}
5389
5390
5391
5392sub tests_format_for_imap_arg
5393{
5394 note( 'Entering tests_format_for_imap_arg()' ) ;
5395
5396 ok( 'NIL' eq format_for_imap_arg( { } ), 'format_for_imap_arg empty hash ref' ) ;
5397 ok( '("name" "toto")' eq format_for_imap_arg( { name => 'toto' } ), 'format_for_imap_arg { name => toto }' ) ;
5398 ok( '("name" "toto" "key" "val")' eq format_for_imap_arg( { name => 'toto', key => 'val' } ), 'format_for_imap_arg 2 x key val' ) ;
5399
5400 note( 'Leaving tests_format_for_imap_arg()' ) ;
5401 return ;
5402}
5403
5404sub quota
5405{
5406 my ( $mysync, $imap, $side ) = @_ ;
5407
5408 my %side = (
5409 h1 => 'Host1',
5410 h2 => 'Host2',
5411 ) ;
5412 my $Side = $side{ $side } ;
5413 my $debug_before = $imap->Debug( ) ;
5414 $imap->Debug( 1 ) ;
5415 if ( not $imap->has_capability( 'QUOTA' ) ) {
5416 $imap->Debug( $debug_before ) ;
5417 return ;
5418 } ;
5419 myprint( "\n$Side: found quota, presented in raw IMAP\n" ) ;
5420 my $getquotaroot = $imap->getquotaroot( 'INBOX' ) ;
5421 # Gmail INBOX quotaroot is "" but with it Mail::IMAPClient does a literal GETQUOTA {2} \n ""
5422 #$imap->quota( 'ROOT' ) ;
5423 #$imap->quota( '""' ) ;
5424 myprint( "\n" ) ;
5425 $imap->Debug( $debug_before ) ;
5426 my $quota_limit_bytes = quota_extract_storage_limit_in_bytes( $mysync, $getquotaroot ) ;
5427 my $quota_current_bytes = quota_extract_storage_current_in_bytes( $mysync, $getquotaroot ) ;
5428 $mysync->{$side}->{quota_limit_bytes} = $quota_limit_bytes ;
5429 $mysync->{$side}->{quota_current_bytes} = $quota_current_bytes ;
5430 my $quota_percent ;
5431 if ( $quota_limit_bytes > 0 ) {
5432 $quota_percent = mysprintf( '%.2f', $NUMBER_100 * $quota_current_bytes / $quota_limit_bytes ) ;
5433 }else{
5434 $quota_percent = 0 ;
5435 }
5436 myprint( "$Side: Quota current storage is $quota_current_bytes bytes. Limit is $quota_limit_bytes bytes. So $quota_percent % full\n" ) ;
5437 if ( $QUOTA_PERCENT_LIMIT < $quota_percent ) {
5438 my $error = "$Side: $quota_percent % full: it is time to find a bigger place! ( $quota_current_bytes bytes / $quota_limit_bytes bytes )\n" ;
5439 errors_incr( $mysync, $error ) ;
5440 }
5441 return ;
5442}
5443
5444sub tests_quota_extract_storage_limit_in_bytes
5445{
5446 note( 'Entering tests_quota_extract_storage_limit_in_bytes()' ) ;
5447
5448 my $mysync = {} ;
5449 my $imap_output = [
5450 '* QUOTAROOT "INBOX" "Storage quota" "Messages quota"',
5451 '* QUOTA "Storage quota" (STORAGE 1 104857600)',
5452 '* QUOTA "Messages quota" (MESSAGE 2 100000)',
5453 '5 OK Getquotaroot completed.'
5454 ] ;
5455 ok( $NUMBER_104_857_600 * $KIBI == quota_extract_storage_limit_in_bytes( $mysync, $imap_output ), 'quota_extract_storage_limit_in_bytes ') ;
5456
5457 note( 'Leaving tests_quota_extract_storage_limit_in_bytes()' ) ;
5458 return ;
5459}
5460
5461sub quota_extract_storage_limit_in_bytes
5462{
5463 my $mysync = shift ;
5464 my $imap_output = shift ;
5465
5466 my $limit_kb ;
5467 $limit_kb = ( map { /.*\(\s*STORAGE\s+\d+\s+(\d+)\s*\)/x ? $1 : () } @{ $imap_output } )[0] ;
5468 $limit_kb ||= 0 ;
5469 $mysync->{ debug } and myprint( "storage_limit_kb = $limit_kb\n" ) ;
5470 return( $KIBI * $limit_kb ) ;
5471}
5472
5473
5474sub tests_quota_extract_storage_current_in_bytes
5475{
5476 note( 'Entering tests_quota_extract_storage_current_in_bytes()' ) ;
5477
5478 my $mysync = {} ;
5479 my $imap_output = [
5480 '* QUOTAROOT "INBOX" "Storage quota" "Messages quota"',
5481 '* QUOTA "Storage quota" (STORAGE 1 104857600)',
5482 '* QUOTA "Messages quota" (MESSAGE 2 100000)',
5483 '5 OK Getquotaroot completed.'
5484 ] ;
5485 ok( 1*$KIBI == quota_extract_storage_current_in_bytes( $mysync, $imap_output ), 'quota_extract_storage_current_in_bytes: 1 => 1024 ') ;
5486
5487 note( 'Leaving tests_quota_extract_storage_current_in_bytes()' ) ;
5488 return ;
5489}
5490
5491sub quota_extract_storage_current_in_bytes
5492{
5493 my $mysync = shift ;
5494 my $imap_output = shift ;
5495
5496 my $current_kb ;
5497 $current_kb = ( map { /.*\(\s*STORAGE\s+(\d+)\s+\d+\s*\)/x ? $1 : () } @{ $imap_output } )[0] ;
5498 $current_kb ||= 0 ;
5499 $mysync->{ debug } and myprint( "storage_current_kb = $current_kb\n" ) ;
5500 return( $KIBI * $current_kb ) ;
5501
5502}
5503
5504
5505sub automap
5506{
5507 my ( $mysync ) = @_ ;
5508
5509 if ( $mysync->{automap} ) {
5510 myprint( "Turned on automapping folders ( use --noautomap to turn off automapping )\n" ) ;
5511 }else{
5512 myprint( "Turned off automapping folders ( use --automap to turn on automapping )\n" ) ;
5513 return ;
5514 }
5515
5516 $mysync->{h1_special} = special_from_folders_hash( $mysync, $mysync->{imap1}, 'Host1' ) ;
5517 $mysync->{h2_special} = special_from_folders_hash( $mysync, $mysync->{imap2}, 'Host2' ) ;
5518
5519 build_possible_special( $mysync ) ;
5520 build_guess_special( $mysync ) ;
5521 build_automap( $mysync ) ;
5522
5523 return ;
5524}
5525
5526
5527
5528
5529sub build_guess_special
5530{
5531 my ( $mysync ) = shift ;
5532
5533 foreach my $h1_fold ( sort keys %{ $mysync->{h1_folders_all} } ) {
5534 my $special = guess_special( $h1_fold, $mysync->{possible_special}, $mysync->{h1_prefix} ) ;
5535 if ( $special ) {
5536 $mysync->{h1_special_guessed}{$h1_fold} = $special ;
5537 my $already_guessed = $mysync->{h1_special_guessed}{$special} ;
5538 if ( $already_guessed ) {
5539 myprint( "Host1: $h1_fold not $special because set to $already_guessed\n" ) ;
5540 }else{
5541 $mysync->{h1_special_guessed}{$special} = $h1_fold ;
5542 }
5543 }
5544 }
5545 foreach my $h2_fold ( sort keys %{ $mysync->{h2_folders_all} } ) {
5546 my $special = guess_special( $h2_fold, $mysync->{possible_special}, $mysync->{h2_prefix} ) ;
5547 if ( $special ) {
5548 $mysync->{h2_special_guessed}{$h2_fold} = $special ;
5549 my $already_guessed = $mysync->{h2_special_guessed}{$special} ;
5550 if ( $already_guessed ) {
5551 myprint( "Host2: $h2_fold not $special because set to $already_guessed\n" ) ;
5552 }else{
5553 $mysync->{h2_special_guessed}{$special} = $h2_fold ;
5554 }
5555 }
5556 }
5557 return ;
5558}
5559
5560sub guess_special
5561{
5562 my( $folder, $possible_special_ref, $prefix ) = @_ ;
5563
5564 my $folder_no_prefix = $folder ;
5565 $folder_no_prefix =~ s/\Q${prefix}\E//xms ;
5566 #$debug and myprint( "folder_no_prefix: $folder_no_prefix\n" ) ;
5567
5568 my $guess_special = $possible_special_ref->{ $folder }
5569 || $possible_special_ref->{ $folder_no_prefix }
5570 || q{} ;
5571
5572 return( $guess_special ) ;
5573}
5574
5575sub tests_guess_special
5576{
5577 note( 'Entering tests_guess_special()' ) ;
5578
5579 my $possible_special_ref = build_possible_special( my $mysync ) ;
5580 ok( '\Sent' eq guess_special( 'Sent', $possible_special_ref, q{} ) ,'guess_special: Sent => \Sent' ) ;
5581 ok( q{} eq guess_special( 'Blabla', $possible_special_ref, q{} ) ,'guess_special: Blabla => q{}' ) ;
5582 ok( '\Sent' eq guess_special( 'INBOX.Sent', $possible_special_ref, 'INBOX.' ) ,'guess_special: INBOX.Sent => \Sent' ) ;
5583 ok( '\Sent' eq guess_special( 'IN BOX.Sent', $possible_special_ref, 'IN BOX.' ) ,'guess_special: IN BOX.Sent => \Sent' ) ;
5584
5585 note( 'Leaving tests_guess_special()' ) ;
5586 return ;
5587}
5588
5589sub build_automap
5590{
5591 my $mysync = shift ;
5592 $mysync->{ debug } and myprint( "Entering build_automap\n" ) ;
5593 foreach my $h1_fold ( @{ $mysync->{h1_folders_wanted} } ) {
5594 my $h2_fold ;
5595 my $h1_special = $mysync->{h1_special}{$h1_fold} ;
5596 my $h1_special_guessed = $mysync->{h1_special_guessed}{$h1_fold} ;
5597
5598 # Case 1: special on both sides.
5599 if ( $h1_special
5600 and exists $mysync->{h2_special}{$h1_special} ) {
5601 $h2_fold = $mysync->{h2_special}{$h1_special} ;
5602 $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ;
5603 next ;
5604 }
5605 # Case 2: special on host1, not on host2
5606 if ( $h1_special
5607 and ( not exists $mysync->{h2_special}{$h1_special} )
5608 and ( exists $mysync->{h2_special_guessed}{$h1_special} )
5609 ) {
5610 # special_guessed on host2
5611 $h2_fold = $mysync->{h2_special_guessed}{$h1_special} ;
5612 $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ;
5613 next ;
5614 }
5615 # Case 3: no special on host1, special on host2
5616 if ( ( not $h1_special )
5617 and ( $h1_special_guessed )
5618 and ( exists $mysync->{h2_special}{$h1_special_guessed} )
5619 ) {
5620 $h2_fold = $mysync->{h2_special}{$h1_special_guessed} ;
5621 $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ;
5622 next ;
5623 }
5624 # Case 4: no special on both sides.
5625 if ( ( not $h1_special )
5626 and ( $h1_special_guessed )
5627 and ( not exists $mysync->{h2_special}{$h1_special_guessed} )
5628 and ( exists $mysync->{h2_special_guessed}{$h1_special_guessed} )
5629 ) {
5630 $h2_fold = $mysync->{h2_special_guessed}{$h1_special_guessed} ;
5631 $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ;
5632 next ;
5633 }
5634 }
5635 return( $mysync->{f1f2auto} ) ;
5636}
5637
5638# I will not add what there is at:
5639# http://stackoverflow.com/questions/2185391/localized-gmail-imap-folders/2185548#2185548
5640# because it works well without
5641sub build_possible_special
5642{
5643 my $mysync = shift ;
5644 my $possible_special = { } ;
5645 # All|Archive|Drafts|Flagged|Junk|Sent|Trash
5646
5647 $possible_special->{'\All'} = [ 'All', 'All Messages', '&BBIEQQQ1-' ] ;
5648 $possible_special->{'\Archive'} = [ 'Archive', 'Archives', '&BBAEQARFBDgEMg-' ] ;
5649 $possible_special->{'\Drafts'} = [ 'Drafts', 'DRAFTS', '&BCcENQRABD0EPgQyBDgEOgQ4-', 'Szkice', 'Wersje robocze' ] ;
5650 $possible_special->{'\Flagged'} = [ 'Flagged', 'Starred', '&BB8EPgQ8BDUERwQ1BD0EPQRLBDU-' ] ;
5651 $possible_special->{'\Junk'} = [ 'Junk', 'junk', 'Spam', 'SPAM', '&BCEEPwQwBDw-',
5652 'Potwierdzony spam', 'Wiadomo&AVs-ci-&AVs-mieci',
5653 'Junk E-Mail', 'Junk Email'] ;
5654 $possible_special->{'\Sent'} = [ 'Sent', 'Sent Messages', 'Sent Items',
5655 'Gesendete Elemente', 'Gesendete Objekte',
5656 '&AMk-l&AOk-ments envoy&AOk-s', 'Envoy&AOk-', 'Objets envoy&AOk-s',
5657 'Elementos enviados',
5658 '&kAFP4W4IMH8wojCkMMYw4A-',
5659 '&BB4EQgQ,BEAEMAQyBDsENQQ9BD0ESwQ1-',
5660 'Elementy wys&AUI-ane'] ;
5661 $possible_special->{'\Trash'} = [ 'Trash', 'TRASH',
5662 '&BCMENAQwBDsENQQ9BD0ESwQ1-', '&BBoEPgRABDcEOAQ9BDA-',
5663 'Kosz',
5664 'Deleted Items', 'Deleted Messages' ] ;
5665
5666
5667 foreach my $special ( qw( \All \Archive \Drafts \Flagged \Junk \Sent \Trash ) ){
5668 foreach my $possible_folder ( @{ $possible_special->{$special} } ) {
5669 $possible_special->{ $possible_folder } = $special ;
5670 } ;
5671 }
5672 $mysync->{possible_special} = $possible_special ;
5673 $mysync->{ debug } and myprint( Data::Dumper->Dump( [ $possible_special ], [ 'possible_special' ] ) ) ;
5674 return( $possible_special ) ;
5675}
5676
5677sub tests_special_from_folders_hash
5678{
5679 note( 'Entering tests_special_from_folders_hash()' ) ;
5680
5681 my $mysync = {} ;
5682 require_ok( "Test::MockObject" ) ;
5683 my $imapT = Test::MockObject->new( ) ;
5684
5685 is( undef, special_from_folders_hash( ), 'special_from_folders_hash: no args' ) ;
5686 is( undef, special_from_folders_hash( $mysync ), 'special_from_folders_hash: undef args' ) ;
5687 is_deeply( {}, special_from_folders_hash( $mysync, $imapT ), 'special_from_folders_hash: $imap void' ) ;
5688
5689 $imapT->mock( 'folders_hash', sub { return( [ { name => 'Sent', attrs => [ '\Sent' ] } ] ) } ) ;
5690
5691 is_deeply( { Sent => '\Sent', '\Sent' => 'Sent' },
5692 special_from_folders_hash( $mysync, $imapT ), 'special_from_folders_hash: $imap \Sent' ) ;
5693
5694 note( 'Leaving tests_special_from_folders_hash()' ) ;
5695 return( ) ;
5696}
5697
5698sub special_from_folders_hash
5699{
5700 my ( $mysync, $imap, $side ) = @_ ;
5701 my %special = ( ) ;
5702
5703 if ( ! defined $imap ) { return ; }
5704 $side = defined $side ? $side : 'Host?' ;
5705
5706 if ( ! $imap->can( 'folders_hash' ) ) {
5707 my $error = "$side: To have automagic rfc6154 folder mapping, upgrade Mail::IMAPClient >= 3.34\n" ;
5708 errors_incr( $mysync, $error ) ;
5709 return( \%special ) ; # empty hash ref
5710 }
5711 my $folders_hash = $imap->folders_hash( ) ;
5712 foreach my $fhash (@{ $folders_hash } ) {
5713 my @special = grep { /\\(?:All|Archive|Drafts|Flagged|Junk|Sent|Trash)/x } @{ $fhash->{attrs} } ;
5714 if ( @special ) {
5715 my $special = $special[0] ; # keep first one. Could be not very good.
5716 if ( exists $special{ $special } ) {
5717 myprintf( "%s: special %-20s = %s already assigned to %s\n",
5718 $side, $fhash->{name}, join( q{ }, @special ), $special{ $special } ) ;
5719 }else{
5720 myprintf( "%s: special %-20s = %s\n",
5721 $side, $fhash->{name}, join( q{ }, @special ) ) ;
5722 $special{ $special } = $fhash->{name} ;
5723 $special{ $fhash->{name} } = $special ; # double entry value => key
5724 }
5725 }
5726 }
5727 myprint( "\n" ) if ( %special ) ;
5728 return( \%special ) ;
5729}
5730
5731sub errors_incr
5732{
5733 my ( $mysync, @error ) = @ARG ;
5734 $mysync->{nb_errors}++ ;
5735
5736 if ( @error ) {
5737 errors_log( $mysync, @error ) ;
5738 myprint( @error ) ;
5739 }
5740
5741 $mysync->{errorsmax} ||= $ERRORS_MAX ;
5742 if ( $mysync->{nb_errors} >= $mysync->{errorsmax} ) {
5743 myprint( "Maximum number of errors $mysync->{errorsmax} reached ( you can change $mysync->{errorsmax} to any value, for example 100 with --errorsmax 100 ). Exiting.\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02005744 my $most_common_error = errorsanalyse( errors_log( $mysync ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005745 if ( $mysync->{errorsdump} ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02005746 myprint( errorsdump( errors_log( $mysync ) ) ) ;
5747 myprint( "The most frequent error is $most_common_error\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005748 # again since errorsdump( ) can be very verbose and masquerade previous warning
5749 myprint( "Maximum number of errors $mysync->{errorsmax} reached ( you can change $mysync->{errorsmax} to any value, for example 100 with --errorsmax 100 ). Exiting.\n" ) ;
5750 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02005751 my $exit_value = $EXIT_VALUE_OF_ERR_TYPE{ $most_common_error } || $EXIT_CATCH_ALL ;
5752 #exit_clean( $mysync, $EXIT_WITH_ERRORS_MAX ) ;
5753 exit_clean( $mysync, $exit_value ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005754 }
5755 return ;
5756}
5757
5758sub tests_errors_log
5759{
5760 note( 'Entering tests_errors_log()' ) ;
5761 is( undef, errors_log( ), 'errors_log: no args => undef' ) ;
5762 my $mysync = {} ;
5763 is( undef, errors_log( $mysync ), 'errors_log: empty => undef' ) ;
5764 is_deeply( [ 'aieaie' ], [ errors_log( $mysync, 'aieaie' ) ], 'errors_log: aieaie => aieaie' ) ;
5765 # cumulative
5766 is_deeply( [ 'aieaie' ], [ errors_log( $mysync ) ], 'errors_log: nothing more => aieaie' ) ;
5767 is_deeply( [ 'aieaie', 'ouille' ], [ errors_log( $mysync, 'ouille' ) ], 'errors_log: ouille => aieaie ouille' ) ;
5768 is_deeply( [ 'aieaie', 'ouille' ], [ errors_log( $mysync ) ], 'errors_log: nothing more => aieaie ouille' ) ;
5769 note( 'Leaving tests_errors_log()' ) ;
5770 return ;
5771}
5772
5773sub errors_log
5774{
5775 my ( $mysync, @error ) = @ARG ;
5776
5777 if ( ! $mysync->{errors_log} ) {
5778 $mysync->{errors_log} = [] ;
5779 }
5780
5781 if ( @error ) {
5782 push @{ $mysync->{errors_log} }, join( q{}, @error ) ;
5783 }
5784 if ( @{ $mysync->{errors_log} } ) {
5785 return @{ $mysync->{errors_log} } ;
5786 }
5787 else {
5788 return ;
5789 }
5790}
5791
5792
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02005793sub tests_error_type
5794{
5795 note( 'Entering tests_error_type()' ) ;
5796
5797 is( 'ERR_NOTHING_REPORTED', error_type( ), 'error_type: no args => ERR_NOTHING_REPORTED' ) ;
5798 is( 'ERR_NOTHING_REPORTED', error_type( '' ), 'error_type: empty string => ERR_NOTHING_REPORTED' ) ;
5799
5800 is( 'ERR_UNCLASSIFIED', error_type( 'ERR_UNCLASSIFIED' ), 'error_type: ERR_UNCLASSIFIED => ERR_UNCLASSIFIED' ) ;
5801 is( 'ERR_UNCLASSIFIED', error_type( 'aie' ), 'error_type: aie => ERR_UNCLASSIFIED' ) ;
5802 is( 'ERR_UNCLASSIFIED', error_type( 'ouille' ), 'error_type: ouille => ERR_UNCLASSIFIED' ) ;
5803
5804 is( 'ERR_Host1_FETCH', error_type( 'Message xxx could not be fetched: blabla' ),
5805 'error_type: could not be fetched => ERR_Host1_FETCH'
5806 ) ;
5807
5808 is( 'ERR_APPEND_SIZE',
5809 error_type( 'could not append message xxx: BAD maximum message size exceeded' ),
5810 'error_type: could not append message xxx: BAD maximum message size exceeded => ERR_APPEND_SIZE'
5811 ) ;
5812
5813 is( 'ERR_OVERQUOTA',
5814 error_type( 'Quota limit will be exceeded' ),
5815 'error_type: Quota limit will be exceeded => ERR_OVERQUOTA'
5816 ) ;
5817
5818 is( 'ERR_APPEND', error_type( 'could not append' ), 'error_type: could not append => ERR_APPEND' ) ;
5819
5820 is( 'ERR_CREATE',
5821 error_type( 'Could not create folder' ),
5822 'error_type: Could not create folder => ERR_CREATE'
5823 ) ;
5824
5825 is( 'ERR_SELECT',
5826 error_type( 'Could not select: blabla' ),
5827 'error_type: Could not select: blabla => ERR_SELECT'
5828 ) ;
5829
5830
5831 #
5832 #Maximum bytes transferred reached, 423 >= 100, ending sync
5833 is( 'ERR_TRANSFER_EXCEEDED',
5834 error_type( 'Maximum bytes transferred reached, blabla' ),
5835 'error_type: Maximum bytes transferred reached, blabla => ERR_TRANSFER_EXCEEDED'
5836 ) ;
5837
5838 #
5839 is( 'ERR_CONNECTION_FAILURE_HOST1',
5840 error_type( 'Host1 failure: can not open imap connection on host1 [badhostkaka] with user [tata]: Unable to connect to badhostkaka: Invalid argument' ),
5841 'error_type: can not open imap connection on host1 => ERR_CONNECTION_FAILURE_HOST1'
5842 ) ;
5843
5844 is( 'ERR_CONNECTION_FAILURE_HOST2',
5845 error_type( 'Host2 failure: can not open imap connection on host2 [badhostkiki] with user [titi]: Unable to connect to badhostkiki: Invalid argument' ),
5846 'error_type: can not open imap connection on host2 => ERR_CONNECTION_FAILURE_HOST2'
5847 ) ;
5848
5849 is( 'ERR_APPEND_VIRUS',
5850 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' ),
5851 'error_type: could not append ... virus => ERR_APPEND_VIRUS'
5852 ) ;
5853
5854 note( 'Leaving tests_error_type()' ) ;
5855 return ;
5856}
5857
5858
5859
5860# Could be implemented with https://metacpan.org/pod/Tie::RegexpHash
5861# with just a hash of error regexes as keys and types as values.
5862
5863sub error_type
5864{
5865 my $error = shift ;
5866
5867 if ( ! defined $error ) { return 'ERR_NOTHING_REPORTED' ; }
5868 if ( ! $error ) { return 'ERR_NOTHING_REPORTED' ; }
5869
5870 #
5871 if ( $error =~ m{Host1 failure: Error login on} ) { return 'ERR_AUTHENTICATION_FAILURE_USER1' } ;
5872 if ( $error =~ m{Host2 failure: Error login on} ) { return 'ERR_AUTHENTICATION_FAILURE_USER2' } ;
5873
5874 if ( $error =~ m{Host. failure: Can not go to tls encryption on host.} ) { return 'ERR_EXIT_TLS_FAILURE' } ;
5875 #
5876
5877 if ( $error =~ m{could not be fetched:} ) { return 'ERR_Host1_FETCH' } ;
5878
5879 # could not append .*BAD maximum message size exceeded
5880 # could not append.*Maximum size of appendable message has been exceeded
5881 if ( $error =~ m{could not append .*BAD maximum message size exceeded} )
5882 { return 'ERR_APPEND_SIZE' ; } ;
5883
5884 if ( $error =~ m{could not append.*Maximum size of appendable message has been exceeded} )
5885 { return 'ERR_APPEND_SIZE' ; } ;
5886
5887 # Could not create folder *[OVERQUOTA] Not enough disk quota
5888 # could not append .*[OVERQUOTA] Not enough disk quota
5889 # could not append .*[OVERQUOTA] Mailbox is full / Blocks limit exceeded / Inode limit exceeded
5890 if ( $error =~ m{OVERQUOTA} ) { return 'ERR_OVERQUOTA' ; } ;
5891 if ( $error =~ m{Quota limit will be exceeded} ) { return 'ERR_OVERQUOTA' ; } ;
5892 if ( $error =~ m{full: it is time to find a bigger place} ) { return 'ERR_OVERQUOTA' ; } ;
5893
5894 # could not append ... to folder INBOX: 276 NO Message refused because it contains a virus
5895 if ( $error =~ m{could not append.*virus} )
5896 { return 'ERR_APPEND_VIRUS' ; } ;
5897
5898 # could not append .*Write failed 'Broken pipe'
5899 # could not append .*timeout waiting .* for data from server
5900 # could not append .*BAD Invalid Arguments: Unable to parse message
5901 # could not append .*BAD Command Argument Error. 11
5902 # could not append .*NO header limit reached
5903 if ( $error =~ m{could not append} ) { return 'ERR_APPEND' ; } ;
5904
5905 # Could not create folder .*Invalid mailbox name
5906 if ( $error =~ m{Could not create folder} ) { return 'ERR_CREATE' ; } ;
5907
5908
5909 # Could not select:.*NO [NOPERM] Permission denied
5910 # Could not select:.*NO Mailbox doesn't exist
5911 # Could not select:.*NO [SERVERBUG] Internal error occurred.
5912 # Could not select:.*[CANNOT] Mailbox isn't a valid mbox file
5913 if ( $error =~ m{Could not select:} ) { return 'ERR_SELECT' ; } ;
5914
5915 #Maximum bytes transferred reached, 423 >= 100, ending sync
5916 if ( $error =~ m{Maximum bytes transferred reached} ) { return 'ERR_TRANSFER_EXCEEDED' ; } ;
5917
5918 if ( $error =~ m{can not open imap connection on host1} ) { return 'ERR_CONNECTION_FAILURE_HOST1' ; } ;
5919 if ( $error =~ m{can not open imap connection on host2} ) { return 'ERR_CONNECTION_FAILURE_HOST2' ; } ;
5920
5921 # Default is ERR_UNCLASSIFIED
5922 return 'ERR_UNCLASSIFIED' ;
5923
5924}
5925
5926sub tests_errorclassify
5927{
5928 note( 'Entering tests_errorclassify()' ) ;
5929
5930 is( undef, errorclassify( ), 'errorclassify: no args => undef' ) ;
5931
5932 is_deeply( { 'ERR_UNCLASSIFIED' => 1 }, errorclassify( 'aie' ), 'errorclassify: aie => { ERR_UNCLASSIFIED => 1 }' ) ;
5933 is_deeply( { 'ERR_UNCLASSIFIED' => 2 }, errorclassify( 'aie', 'ouille' ), 'errorclassify: aie ouille => { ERR_UNCLASSIFIED => 2 }' ) ;
5934 is_deeply( { 'ERR_UNCLASSIFIED' => 2, 'ERR_NOTHING_REPORTED' => 1 }, errorclassify( 'aie', 'ouille', '' ), 'errorclassify: aie ouille "" => { ERR_UNCLASSIFIED => 2 }' ) ;
5935 is_deeply( { 'ERR_UNCLASSIFIED' => 3 }, errorclassify( 'aie', 'ouille', 'aie' ), 'errorclassify: aie ouille aie => { ERR_UNCLASSIFIED => 3 }' ) ;
5936 is_deeply( { 'ERR_UNCLASSIFIED' => 1, 'ERR_OVERQUOTA' => 2 }, errorclassify( 'aie', 'OVERQUOTA pipi', 'OVERQUOTA caca' ), 'errorclassify: aie OVERQUOTA OVERQUOTA' ) ;
5937 is_deeply( { 'ERR_NOTHING_REPORTED' => 1 }, errorclassify( '' ), 'errorclassify: "" => { ERR_NOTHING_REPORTED => 1 }' ) ;
5938 is_deeply( { 'ERR_NOTHING_REPORTED' => 2 }, errorclassify( '', '' ), 'errorclassify: "", "" => { ERR_NOTHING_REPORTED => 1 }' ) ;
5939
5940 note( 'Leaving tests_errorclassify()' ) ;
5941 return ;
5942}
5943
5944
5945
5946sub errorclassify
5947{
5948 my @errors = @ARG ;
5949
5950 if ( ! @errors ) { return ; } ;
5951
5952 my $error_type_count = { } ;
5953 foreach my $error ( @errors )
5954 {
5955 my $error_type = error_type( $error ) ;
5956 $error_type_count->{ $error_type }++ ;
5957 }
5958
5959 return $error_type_count ;
5960}
5961
5962sub tests_most_common_error
5963{
5964 note( 'Entering tests_most_common_error()' ) ;
5965
5966 is( 'ERR_NOTHING_REPORTED', most_common_error( ), 'most_common_error: no args => ERR_NOTHING_REPORTED' ) ;
5967 is( 'ERR_NOTHING_REPORTED', most_common_error( {} ), 'most_common_error: empty hash ref => ERR_NOTHING_REPORTED' ) ;
5968 is( 'ERR_NOTHING_REPORTED', most_common_error( 'blabla' ), 'most_common_error: not a hash ref => ERR_NOTHING_REPORTED' ) ;
5969
5970 is( 'ERR_FOO', most_common_error( { ERR_FOO => 1 } ), 'most_common_error: { ERR_FOO => 1 } => ERR_FOO' ) ;
5971 is( 'ERR_BAR', most_common_error( { ERR_FOO => 1, ERR_BAR => 2 } ), 'most_common_error: { ERR_FOO => 1, ERR_BAR => 2 } => ERR_BAR' ) ;
5972 is( 'ERR_FOO', most_common_error( { ERR_FOO => 2, ERR_BAR => 1 } ), 'most_common_error: { ERR_FOO => 2, ERR_BAR => 1 } => ERR_FOO' ) ;
5973 # exaequo => first lexical wins. ERR_BAR <= ERR_FOO
5974 is( 'ERR_BAR', most_common_error( { ERR_FOO => 2, ERR_BAR => 2 } ), 'most_common_error: { ERR_FOO => 2, ERR_BAR => 2 } => ERR_BAR' ) ;
5975
5976 is( 'A', most_common_error( { A => 5, B => 5, C => 5 } ), 'most_common_error: { A => 5, B => 5, C => 5 } => A' ) ;
5977 is( 'B', most_common_error( { A => 5, B => 6, C => 6 } ), 'most_common_error: { A => 5, B => 6, C => 6 } => B' ) ;
5978 is( 'C', most_common_error( { A => 5, B => 5, C => 7 } ), 'most_common_error: { A => 5, B => 5, C => 7 } => C' ) ;
5979 is( 'C', most_common_error( { A => 5, B => 6, C => 7 } ), 'most_common_error: { A => 5, B => 5, C => 7 } => C' ) ;
5980
5981 note( 'Leaving tests_most_common_error()' ) ;
5982 return ;
5983}
5984
5985
5986
5987sub most_common_error
5988{
5989 my $errors_counted_ref = shift ;
5990
5991 if ( ! defined $errors_counted_ref ) { return 'ERR_NOTHING_REPORTED' ; }
5992
5993 if ( 'HASH' ne ref $errors_counted_ref ) { return 'ERR_NOTHING_REPORTED' ; }
5994
5995 # empty hash
5996 if ( !%{ $errors_counted_ref } ) { return 'ERR_NOTHING_REPORTED' ; }
5997
5998 # non empty hash
5999 my $most_common_error = ( sort
6000 {
6001 $errors_counted_ref->{$b} <=> $errors_counted_ref->{$a}
6002 || $a cmp $b
6003 } keys %{$errors_counted_ref} )[0] ;
6004
6005 return $most_common_error ;
6006
6007}
6008
6009
6010
6011sub tests_errorsanalyse
6012{
6013 note( 'Entering tests_errorsanalyse()' ) ;
6014
6015 is( 'ERR_NOTHING_REPORTED', errorsanalyse( ), 'errorsanalyse: no args => ERR_NOTHING_REPORTED' ) ;
6016 is( 'ERR_NOTHING_REPORTED', errorsanalyse( ( ) ), 'errorsanalyse: empty list => ERR_NOTHING_REPORTED' ) ;
6017 is( 'ERR_UNCLASSIFIED', errorsanalyse( 'aie' ), 'errorsanalyse: aie => ERR_UNCLASSIFIED' ) ;
6018
6019 # in case of equality, empty wins
6020 is( 'ERR_NOTHING_REPORTED', errorsanalyse( 'aie', '' ), 'errorsanalyse: aie => ERR_UNCLASSIFIED' ) ;
6021 is( 'ERR_NOTHING_REPORTED', errorsanalyse( '', 'aie' ), 'errorsanalyse: aie => ERR_UNCLASSIFIED' ) ;
6022
6023
6024 is( 'ERR_UNCLASSIFIED', errorsanalyse( 'aie', 'ouille' ), 'errorsanalyse: aie, ouille => ERR_UNCLASSIFIED' ) ;
6025 is( 'ERR_UNCLASSIFIED', errorsanalyse( 'aie', 'ouille', '' ), 'errorsanalyse: aie, ouille, "" => ERR_UNCLASSIFIED' ) ;
6026 is( 'ERR_UNCLASSIFIED', errorsanalyse( '', 'aie', 'ouille' ), 'errorsanalyse: aie, ouille, "" => ERR_UNCLASSIFIED' ) ;
6027
6028 is( 'ERR_NOTHING_REPORTED', errorsanalyse( '' ), 'errorsanalyse: "" => ERR_NOTHING_REPORTED' ) ;
6029 is( 'ERR_NOTHING_REPORTED', errorsanalyse( ( '' ) ), 'errorsanalyse: ( "" ) => ERR_NOTHING_REPORTED' ) ;
6030 is( 'ERR_NOTHING_REPORTED', errorsanalyse( ( '', '' ) ), 'errorsanalyse: ( "", "" ) => ERR_NOTHING_REPORTED' ) ;
6031
6032 note( 'Leaving tests_errorsanalyse()' ) ;
6033 return ;
6034}
6035
6036
6037
6038sub errorsanalyse
6039{
6040 my @errors = @ARG ;
6041 my $errors_types_counted = errorclassify( @errors ) ;
6042
6043 my $most_common_error = most_common_error( $errors_types_counted ) ;
6044
6045 return $most_common_error ;
6046}
6047
6048
6049
6050sub tests_errorsdump
6051{
6052 note( 'Entering tests_errorsdump()' ) ;
6053
6054 is( undef, errorsdump( ), 'errorsdump: no args => undef' ) ;
6055 is( undef, errorsdump( ( ) ), 'errorsdump: empty list => undef' ) ;
6056 is( "Err 1/1: ", errorsdump( '' ), 'errorsdump: one empty string => "Err 1/1: "' ) ;
6057 is( "Err 1/1: aieaieaie", errorsdump( 'aieaieaie' ), 'errorsdump: aieaieaie => "Err 1/1: aieaieaie"' ) ;
6058 is( "Err 1/2: Aie Err 2/2: Ouille", errorsdump( 'Aie ', 'Ouille' ), 'errorsdump: Aie Ouille => "Err 1/2: Aie Err 2/2: Ouille"' ) ;
6059 note( 'Leaving tests_errorsdump()' ) ;
6060 return ;
6061}
6062
6063
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006064sub errorsdump
6065{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006066 if ( ! @ARG ) { return ; }
6067
6068 my @errors_log = @ARG ;
6069 my $nb_errors = @errors_log ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006070 my $error_num = 0 ;
6071 my $errors_list = q{} ;
6072 if ( @errors_log ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006073 foreach my $error ( @errors_log )
6074 {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006075 $error_num++ ;
6076 $errors_list .= "Err $error_num/$nb_errors: $error" ;
6077 }
6078 }
6079 return( $errors_list ) ;
6080}
6081
6082
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006083
6084sub errors_listing
6085{
6086 my $mysync = shift ;
6087 $mysync->{most_common_error} = errorsanalyse( errors_log( $sync ) ) ;
6088
6089 my $errors_listing = join( '',
6090 "++++ Listing $mysync->{nb_errors} errors encountered during the sync ( avoid this listing with --noerrorsdump ).\n",
6091 errorsdump( errors_log( $mysync ) ),
6092 "The most frequent error is $mysync->{most_common_error}\n",
6093 ) ;
6094 return $errors_listing ;
6095}
6096
6097
6098
6099
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006100sub tests_live_result
6101{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006102 note( 'Entering tests_live_result()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006103
6104 my $nb_errors = shift ;
6105 if ( $nb_errors ) {
6106 myprint( "Live tests failed with $nb_errors errors\n" ) ;
6107 } else {
6108 myprint( "Live tests ended successfully\n" ) ;
6109 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006110 note( 'Leaving tests_live_result()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006111 return ;
6112}
6113
6114
6115sub size_filtered_flag
6116{
6117 my $mysync = shift ;
6118 my $h1_size = shift ;
6119
6120 if ( defined $mysync->{ maxsize } and $h1_size >= $mysync->{ maxsize } ) {
6121 return( 1 ) ;
6122 }
6123 if ( defined $minsize and $h1_size <= $minsize ) {
6124 return( 1 ) ;
6125 }
6126 return( 0 ) ;
6127}
6128
6129sub sync_flags_fir
6130{
6131 my ( $mysync, $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) = @_ ;
6132
6133 if ( not defined $h1_msg ) { return } ;
6134 if ( not defined $h2_msg ) { return } ;
6135
6136 my $h1_size = $h1_fir_ref->{$h1_msg}->{'RFC822.SIZE'} ;
6137 return if size_filtered_flag( $mysync, $h1_size ) ;
6138
6139 # used cached flag values for efficiency
6140 my $h1_flags = $h1_fir_ref->{ $h1_msg }->{ 'FLAGS' } || q{} ;
6141 my $h2_flags = $h2_fir_ref->{ $h2_msg }->{ 'FLAGS' } || q{} ;
6142
6143 sync_flags( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) ;
6144
6145 return ;
6146}
6147
6148sub sync_flags_after_copy
6149{
6150 # Activated with option --syncflagsaftercopy
6151 my( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $permanentflags2 ) = @_ ;
6152
6153 if ( my @h2_flags = $mysync->{imap2}->flags( $h2_msg ) ) {
6154 my $h2_flags = "@h2_flags" ;
6155 ( $mysync->{ debug } or $debugflags ) and myprint( "Host2: msg $h2_fold/$h2_msg flags before sync flags after copy ( $h2_flags )\n" ) ;
6156 sync_flags( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) ;
6157 }else{
6158 myprint( "Host2: msg $h2_fold/$h2_msg could not get its flags for sync flags after copy\n" ) ;
6159 }
6160 return ;
6161}
6162
6163# Globals
6164# $debug
6165# $debugflags
6166# $permanentflags2
6167
6168
6169sub sync_flags
6170{
6171 my( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) = @_ ;
6172
6173 ( $mysync->{ debug } or $debugflags ) and
6174 myprint( "Host1: flags init msg $h1_fold/$h1_msg flags( $h1_flags ) Host2 msg $h2_fold/$h2_msg flags( $h2_flags )\n" ) ;
6175
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006176 $h1_flags = flags_for_host2( $mysync, $h1_flags, $permanentflags2 ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006177
6178 $h2_flags = flagscase( $h2_flags ) ;
6179
6180 ( $mysync->{ debug } or $debugflags ) and
6181 myprint( "Host1: flags filt msg $h1_fold/$h1_msg flags( $h1_flags ) Host2 msg $h2_fold/$h2_msg flags( $h2_flags )\n" ) ;
6182
6183
6184 # compare flags - set flags if there a difference
6185 my @h1_flags = sort split(q{ }, $h1_flags );
6186 my @h2_flags = sort split(q{ }, $h2_flags );
6187 my $diff = compare_lists( \@h1_flags, \@h2_flags );
6188
6189 $diff and ( $mysync->{ debug } or $debugflags )
6190 and myprint( "Host2: flags msg $h2_fold/$h2_msg replacing h2 flags( $h2_flags ) with h1 flags( $h1_flags )\n" ) ;
6191
6192 # This sets flags exactly. So flags can be removed with this.
6193 # When you remove a \Seen flag on host1 you want it
6194 # to be removed on host2. Just add flags is not what
6195 # we need most of the time, so no + like in "+FLAGS.SILENT".
6196
6197 if ( not $mysync->{dry} and $diff and not $mysync->{imap2}->store( $h2_msg, "FLAGS.SILENT (@h1_flags)" ) ) {
6198 my $error_msg = join q{}, "Host2: flags msg $h2_fold/$h2_msg could not add flags [@h1_flags]: ",
6199 $mysync->{imap2}->LastError || q{}, "\n" ;
6200 errors_incr( $mysync, $error_msg ) ;
6201 }
6202
6203 return ;
6204}
6205
6206
6207
6208sub _filter
6209{
6210 my $mysync = shift ;
6211 my $str = shift or return q{} ;
6212 my $sz = $SIZE_MAX_STR ;
6213 my $len = length $str ;
6214 if ( not $mysync->{ debug } and $len > $sz*2 ) {
6215 my $beg = substr $str, 0, $sz ;
6216 my $end = substr $str, -$sz, $sz ;
6217 $str = $beg . '...' . $end ;
6218 }
6219 $str =~ s/\012?\015$//x ;
6220 return "(len=$len) " . $str ;
6221}
6222
6223
6224
6225sub lost_connection
6226{
6227 my( $mysync, $imap, $error_message ) = @_;
6228 if ( $imap->IsUnconnected( ) ) {
6229 $mysync->{nb_errors}++ ;
6230 my $lcomm = $imap->LastIMAPCommand || q{} ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006231
6232 my $einfo = imap_last_error( $imap ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006233
6234 # if string is long try reduce to a more reasonable size
6235 $lcomm = _filter( $mysync, $lcomm ) ;
6236 $einfo = _filter( $mysync, $einfo ) ;
6237 myprint( "Failure: last command: $lcomm\n") if ( $mysync->{ debug } && $lcomm) ;
6238 myprint( "Failure: lost connection $error_message: ", $einfo, "\n") ;
6239 return( 1 ) ;
6240 }
6241 else{
6242 return( 0 ) ;
6243 }
6244}
6245
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006246sub imap_last_error
6247{
6248 my $imap = shift ;
6249 my $einfo = $imap->LastError || @{$imap->History}[$LAST] || q{} ;
6250 chomp( $einfo ) ;
6251 return( $einfo ) ;
6252}
6253
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006254sub tests_max
6255{
6256 note( 'Entering tests_max()' ) ;
6257
6258 is( 0, max( 0 ), 'max 0 => 0' ) ;
6259 is( 1, max( 1 ), 'max 1 => 1' ) ;
6260 is( $MINUS_ONE, max( $MINUS_ONE ), 'max -1 => -1') ;
6261 is( undef, max( ), 'max no arg => undef' ) ;
6262 is( undef, max( undef ), 'undef => undef' ) ;
6263 is( undef, max( undef, undef ), 'undef, undef => undef' ) ;
6264
6265 is( $NUMBER_100, max( 1, $NUMBER_100 ), 'max 1 100 => 100' ) ;
6266 is( $NUMBER_100, max( $NUMBER_100, 1 ), 'max 100 1 => 100' ) ;
6267 is( $NUMBER_100, max( $NUMBER_100, $NUMBER_42, 1 ), 'max 100 42 1 => 100' ) ;
6268 is( $NUMBER_100, max( $NUMBER_100, '42', 1 ), 'max 100 42 1 => 100' ) ;
6269 is( $NUMBER_100, max( '100', '42', 1 ), 'max 100 42 1 => 100' ) ;
6270 is( $NUMBER_100, max( $NUMBER_100, 'haha', 1 ), 'max 100 haha 1 => 100') ;
6271 is( $NUMBER_100, max( 'bb', $NUMBER_100, 'haha' ), 'max bb 100 haha => 100') ;
6272 is( $MINUS_ONE, max( q{}, $MINUS_ONE, 'haha' ), 'max "" -1 haha => -1') ;
6273 is( $MINUS_ONE, max( q{}, $MINUS_ONE, $MINUS_TWO ), 'max "" -1 -2 => -1') ;
6274 is( $MINUS_ONE, max( 'haha', $MINUS_ONE, $MINUS_TWO ), 'max haha -1 -2 => -1') ;
6275 is( 1, max( $MINUS_ONE, 1 ), 'max -1 1 => 1') ;
6276 is( 1, max( undef, 1 ), 'max undef 1 => 1' ) ;
6277 is( 0, max( undef, 0 ), 'max undef 0 => 0' ) ;
6278 is( 'haha', max( 'haha' ), 'max haha => haha') ;
6279 is( 'bb', max( 'aa', 'bb' ), 'max aa bb => bb') ;
6280 is( 'bb', max( 'bb', 'aa' ), 'max bb aa => bb') ;
6281 is( 'bb', max( 'bb', 'aa', 'bb' ), 'max bb aa bb => bb') ;
6282 note( 'Leaving tests_max()' ) ;
6283 return ;
6284}
6285
6286sub max
6287{
6288 my @list = @_ ;
6289 return( undef ) if ( 0 == scalar @list ) ;
6290
6291 my( @numbers, @notnumbers ) ;
6292 foreach my $item ( @list )
6293 {
6294 if ( is_number( $item ) )
6295 {
6296 push @numbers, $item ;
6297 }
6298 elsif ( defined $item )
6299 {
6300 push @notnumbers, $item ;
6301 }
6302 }
6303
6304 my @sorted ;
6305
6306 if ( @numbers )
6307 {
6308 @sorted = sort { $a <=> $b } @numbers ;
6309 }
6310 elsif ( @notnumbers )
6311 {
6312 @sorted = sort { $a cmp $b } @notnumbers ;
6313 }
6314 else
6315 {
6316 return ;
6317 }
6318
6319 return( pop @sorted ) ;
6320}
6321
6322sub tests_is_number
6323{
6324 note( 'Entering tests_is_number()' ) ;
6325
6326 is( undef, is_number( ), 'is_number: no args => undef ' ) ;
6327 is( undef, is_number( undef ), 'is_number: undef => undef ' ) ;
6328 ok( is_number( 1 ), 'is_number: 1 => 1' ) ;
6329 ok( is_number( 1.1 ), 'is_number: 1.1 => 1' ) ;
6330 ok( is_number( 0 ), 'is_number: 0 => 1' ) ;
6331 ok( is_number( -1 ), 'is_number: -1 => 1' ) ;
6332 ok( ! is_number( 1.1.1 ), 'is_number: 1.1.1 => no' ) ;
6333 ok( ! is_number( q{} ), 'is_number: q{} => no' ) ;
6334 ok( ! is_number( 'haha' ), 'is_number: haha => no' ) ;
6335 ok( ! is_number( '0haha' ), 'is_number: 0haha => no' ) ;
6336 ok( ! is_number( '2haha' ), 'is_number: 2haha => no' ) ;
6337 ok( ! is_number( 'haha2' ), 'is_number: haha2 => no' ) ;
6338
6339 note( 'Leaving tests_is_number()' ) ;
6340 return ;
6341}
6342
6343
6344
6345sub is_number
6346{
6347 my $item = shift ;
6348
6349 if ( ! defined $item ) { return ; }
6350
6351 if ( $item =~ /\A$RE{num}{real}\Z/ ) {
6352 return 1 ;
6353 }
6354 return ;
6355}
6356
6357sub tests_min
6358{
6359 note( 'Entering tests_min()' ) ;
6360
6361 is( 0, min( 0 ), 'min 0 => 0' ) ;
6362 is( 1, min( 1 ), 'min 1 => 1' ) ;
6363 is( $MINUS_ONE, min( $MINUS_ONE ), 'min -1 => -1' ) ;
6364 is( undef, min( ), 'min no arg => undef' ) ;
6365 is( 1, min( 1, $NUMBER_100 ), 'min 1 100 => 1' ) ;
6366 is( 1, min( $NUMBER_100, 1 ), 'min 100 1 => 1' ) ;
6367 is( 1, min( $NUMBER_100, $NUMBER_42, 1 ), 'min 100 42 1 => 1' ) ;
6368 is( 1, min( $NUMBER_100, '42', 1 ), 'min 100 42 1 => 1' ) ;
6369 is( 1, min( '100', '42', 1 ), 'min 100 42 1 => 1' ) ;
6370 is( 1, min( $NUMBER_100, 'haha', 1 ), 'min 100 haha 1 => 1') ;
6371 is( $MINUS_ONE, min( $MINUS_ONE, 1 ), 'min -1 1 => -1') ;
6372
6373 is( 1, min( undef, 1 ), 'min undef 1 => 1' ) ;
6374 is( 0, min( undef, 0 ), 'min undef 0 => 0' ) ;
6375 is( 1, min( undef, 1 ), 'min undef 1 => 1' ) ;
6376 is( 0, min( undef, 2, 0, 1 ), 'min undef, 2, 0, 1 => 0' ) ;
6377
6378 is( 'haha', min( 'haha' ), 'min haha => haha') ;
6379 is( 'aa', min( 'aa', 'bb' ), 'min aa bb => aa') ;
6380 is( 'aa', min( 'bb', 'aa' ), 'min bb aa bb => aa') ;
6381 is( 'aa', min( 'bb', 'aa', 'bb' ), 'min bb aa bb => aa') ;
6382
6383 note( 'Leaving tests_min()' ) ;
6384 return ;
6385}
6386
6387
6388sub min
6389{
6390 my @list = @_ ;
6391 return( undef ) if ( 0 == scalar @list ) ;
6392
6393 my( @numbers, @notnumbers ) ;
6394 foreach my $item ( @list ) {
6395 if ( is_number( $item ) ) {
6396 push @numbers, $item ;
6397 }else{
6398 push @notnumbers, $item ;
6399 }
6400 }
6401
6402 my @sorted ;
6403 if ( @numbers ) {
6404 @sorted = sort { $a <=> $b } @numbers ;
6405 }elsif( @notnumbers ) {
6406 @sorted = sort { $a cmp $b } @notnumbers ;
6407 }else{
6408 return ;
6409 }
6410
6411 return( shift @sorted ) ;
6412}
6413
6414
6415sub check_lib_version
6416{
6417 my $mysync = shift ;
6418 $mysync->{ debug } and myprint( "IMAPClient $Mail::IMAPClient::VERSION\n" ) ;
6419 if ( '2.2.9' eq $Mail::IMAPClient::VERSION ) {
6420 myprint( "imapsync no longer supports Mail::IMAPClient 2.2.9, upgrade it\n" ) ;
6421 return 0 ;
6422 }
6423 else{
6424 # 3.x.x is no longer buggy with imapsync.
6425 # 3.30 or currently superior is imposed in the Perl "use Mail::IMAPClient line".
6426 return 1 ;
6427 }
6428 return ;
6429}
6430
6431sub module_version_str
6432{
6433 my( $module_name, $module_version ) = @_ ;
6434 my $str = mysprintf( "%-20s %s\n", $module_name, $module_version ) ;
6435 return( $str ) ;
6436}
6437
6438sub modulesversion
6439{
6440
6441 my @list_version;
6442
6443 my %modulesversion = (
6444 'Authen::NTLM' => sub { $Authen::NTLM::VERSION },
6445 'CGI' => sub { $CGI::VERSION },
6446 'Compress::Zlib' => sub { $Compress::Zlib::VERSION },
6447 'Crypt::OpenSSL::RSA' => sub { $Crypt::OpenSSL::RSA::VERSION },
6448 'Data::Uniqid' => sub { $Data::Uniqid::VERSION },
6449 'Digest::HMAC_MD5' => sub { $Digest::HMAC_MD5::VERSION },
6450 'Digest::HMAC_SHA1' => sub { $Digest::HMAC_SHA1::VERSION },
6451 'Digest::MD5' => sub { $Digest::MD5::VERSION },
6452 'Encode' => sub { $Encode::VERSION },
6453 'Encode::IMAPUTF7' => sub { $Encode::IMAPUTF7::VERSION },
6454 'File::Copy::Recursive' => sub { $File::Copy::Recursive::VERSION },
6455 'File::Spec' => sub { $File::Spec::VERSION },
6456 'Getopt::Long' => sub { $Getopt::Long::VERSION },
6457 'HTML::Entities' => sub { $HTML::Entities::VERSION },
6458 'IO::Socket' => sub { $IO::Socket::VERSION },
6459 'IO::Socket::INET' => sub { $IO::Socket::INET::VERSION },
6460 'IO::Socket::INET6' => sub { $IO::Socket::INET6::VERSION },
6461 'IO::Socket::IP' => sub { $IO::Socket::IP::VERSION },
6462 'IO::Socket::SSL' => sub { $IO::Socket::SSL::VERSION },
6463 'IO::Tee' => sub { $IO::Tee::VERSION },
6464 'JSON' => sub { $JSON::VERSION },
6465 'JSON::WebToken' => sub { $JSON::WebToken::VERSION },
6466 'LWP' => sub { $LWP::VERSION },
6467 'Mail::IMAPClient' => sub { $Mail::IMAPClient::VERSION },
6468 'MIME::Base64' => sub { $MIME::Base64::VERSION },
6469 'Net::Ping' => sub { $Net::Ping::VERSION },
6470 'Net::SSLeay' => sub { $Net::SSLeay::VERSION },
6471 'Term::ReadKey' => sub { $Term::ReadKey::VERSION },
6472 'Test::MockObject' => sub { $Test::MockObject::VERSION },
6473 'Time::HiRes' => sub { $Time::HiRes::VERSION },
6474 'Unicode::String' => sub { $Unicode::String::VERSION },
6475 'URI::Escape' => sub { $URI::Escape::VERSION },
6476 #'Lalala' => sub { $Lalala::VERSION },
6477 ) ;
6478
6479 foreach my $module_name ( sort keys %modulesversion ) {
6480 # trick from http://www.perlmonks.org/?node_id=152122
6481 my $file_name = $module_name . '.pm' ;
6482 $file_name =~s,::,/,xmgs; # Foo::Bar::Baz => Foo/Bar/Baz.pm
6483 my $v ;
6484 eval {
6485 require $file_name ;
6486 $v = defined $modulesversion{ $module_name } ? $modulesversion{ $module_name }->() : q{?} ;
6487 } or $v = q{Not installed} ;
6488
6489 push @list_version, module_version_str( $module_name, $v ) ;
6490 }
6491 return( @list_version ) ;
6492}
6493
6494
6495sub tests_command_line_nopassword
6496{
6497 note( 'Entering tests_command_line_nopassword()' ) ;
6498
6499 ok( q{} eq command_line_nopassword(), 'command_line_nopassword void' );
6500 my $mysync = {} ;
6501 ok( '--blabla' eq command_line_nopassword( $mysync, '--blabla' ), 'command_line_nopassword --blabla' );
6502 #myprint( command_line_nopassword((qw{ --password1 secret1 })), "\n" ) ;
6503 ok( '--password1 MASKED' eq command_line_nopassword( $mysync, qw{ --password1 secret1}), 'command_line_nopassword --password1' );
6504 ok( '--blabla --password1 MASKED --blibli'
6505 eq command_line_nopassword( $mysync, qw{ --blabla --password1 secret1 --blibli } ), 'command_line_nopassword --password1 --blibli' );
6506 $mysync->{showpasswords} = 1 ;
6507 ok( q{} eq command_line_nopassword(), 'command_line_nopassword void' );
6508 ok( '--blabla' eq command_line_nopassword( $mysync, '--blabla'), 'command_line_nopassword --blabla' );
6509 #myprint( command_line_nopassword((qw{ --password1 secret1 })), "\n" ) ;
6510 ok( '--password1 secret1' eq command_line_nopassword( $mysync, qw{ --password1 secret1} ), 'command_line_nopassword --password1' );
6511 ok( '--blabla --password1 secret1 --blibli'
6512 eq command_line_nopassword( $mysync, qw{ --blabla --password1 secret1 --blibli } ), 'command_line_nopassword --password1 --blibli' );
6513
6514 note( 'Leaving tests_command_line_nopassword()' ) ;
6515 return ;
6516}
6517
6518# Construct a command line copy with passwords replaced by MASKED.
6519sub command_line_nopassword
6520{
6521 my $mysync = shift @ARG ;
6522 my @argv = @ARG ;
6523 my @argv_nopassword ;
6524
6525 if ( $mysync->{ cmdcgi } ) {
6526 @argv_nopassword = mask_password_value( @{ $mysync->{ cmdcgi } } ) ;
6527 return( "@argv_nopassword" ) ;
6528 }
6529
6530 if ( $mysync->{showpasswords} )
6531 {
6532 return( "@argv" ) ;
6533 }
6534
6535 @argv_nopassword = mask_password_value( @argv ) ;
6536 return("@argv_nopassword") ;
6537}
6538
6539sub mask_password_value
6540{
6541 my @argv = @ARG ;
6542 my @argv_nopassword ;
6543 while ( @argv ) {
6544 my $arg = shift @argv ; # option name or value
6545 if ( $arg =~ m/-password[12]/x ) {
6546 shift @argv ; # password value
6547 push @argv_nopassword, $arg, 'MASKED' ; # option name and fake value
6548 }else{
6549 push @argv_nopassword, $arg ; # same option or value
6550 }
6551 }
6552 return @argv_nopassword ;
6553}
6554
6555
6556sub tests_get_stdin_masked
6557{
6558 note( 'Entering tests_get_stdin_masked()' ) ;
6559
6560 is( q{}, get_stdin_masked( ), 'get_stdin_masked: no args' ) ;
6561 is( q{}, get_stdin_masked( 'Please ENTER: ' ), 'get_stdin_masked: ENTER' ) ;
6562
6563 note( 'Leaving tests_get_stdin_masked()' ) ;
6564 return ;
6565}
6566
6567#######################################################
6568# The issue is that prompt() does not prompt the prompt
6569# when the program is used like
6570# { sleep 2 ; echo blablabla ; } | ./imapsync ...--host1 lo --user1 tata --host2 lo --user2 titi
6571
6572# use IO::Prompter ;
6573sub get_stdin_masked
6574{
6575 my $prompt = shift || 'Say something: ' ;
6576 local @ARGV = () ;
6577 my $input = prompt(
6578 -prompt => $prompt,
6579 -echo => '*',
6580 ) ;
6581 #myprint( "You said: $input\n" ) ;
6582 return $input ;
6583}
6584
6585sub ask_for_password_new
6586{
6587 my $prompt = shift ;
6588 my $password = get_stdin_masked( $prompt ) ;
6589 return $password ;
6590}
6591#########################################################
6592
6593
6594sub ask_for_password
6595{
6596 my $prompt = shift ;
6597 myprint( $prompt ) ;
6598 Term::ReadKey::ReadMode( 2 ) ;
6599 ## no critic (InputOutput::ProhibitExplicitStdin)
6600 my $password = <STDIN> ;
6601 chomp $password ;
6602 myprint( "\nGot it\n" ) ;
6603 Term::ReadKey::ReadMode( 0 ) ;
6604 return $password ;
6605}
6606
6607# Have to refactor get_password1() get_password2()
6608# to have only get_password() and two calls
6609sub get_password1
6610{
6611
6612 my $mysync = shift ;
6613
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006614 $mysync->{ password1 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006615 || $mysync->{ passfile1 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006616 || 'PREAUTH' eq $mysync->{ acc1 }->{ authmech }
6617 || 'EXTERNAL' eq $mysync->{ acc1 }->{ authmech }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006618 || $ENV{IMAPSYNC_PASSWORD1}
6619 || do
6620 {
6621 myprint( << 'FIN_PASSFILE' ) ;
6622
6623If you are afraid of giving password on the command line arguments, you can put the
6624password of user1 in a file named file1 and use "--passfile1 file1" instead of typing it.
6625Then give this file restrictive permissions with the command "chmod 600 file1".
6626An other solution is to set the environment variable IMAPSYNC_PASSWORD1
6627FIN_PASSFILE
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006628 my $user = $mysync->{ acc1 }->{ authuser } || $mysync->{ user1 } ;
6629 my $host = $mysync->{ host1 } ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006630 my $prompt = "What's the password for $user" . ' at ' . "$host? (not visible while you type, then enter RETURN) " ;
6631 $mysync->{password1} = ask_for_password( $prompt ) ;
6632 } ;
6633
6634 if ( defined $mysync->{ passfile1 } ) {
6635 if ( ! -e -r $mysync->{ passfile1 } ) {
6636 myprint( "Failure: file from parameter --passfile1 $mysync->{ passfile1 } does not exist or is not readable\n" ) ;
6637 $mysync->{nb_errors}++ ;
6638 exit_clean( $mysync, $EX_NOINPUT ) ;
6639 }
6640 # passfile1 readable
6641 $mysync->{password1} = firstline ( $mysync->{ passfile1 } ) ;
6642 return ;
6643 }
6644 if ( $ENV{IMAPSYNC_PASSWORD1} ) {
6645 $mysync->{password1} = $ENV{IMAPSYNC_PASSWORD1} ;
6646 return ;
6647 }
6648 return ;
6649}
6650
6651sub get_password2
6652{
6653
6654 my $mysync = shift ;
6655
6656 $mysync->{password2}
6657 || $mysync->{ passfile2 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006658 || 'PREAUTH' eq $mysync->{ acc2 }->{ authmech }
6659 || 'EXTERNAL' eq $mysync->{ acc2 }->{ authmech }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006660 || $ENV{IMAPSYNC_PASSWORD2}
6661 || do
6662 {
6663 myprint( << 'FIN_PASSFILE' ) ;
6664
6665If you are afraid of giving password on the command line arguments, you can put the
6666password of user2 in a file named file2 and use "--passfile2 file2" instead of typing it.
6667Then give this file restrictive permissions with the command "chmod 600 file2".
6668An other solution is to set the environment variable IMAPSYNC_PASSWORD2
6669FIN_PASSFILE
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006670 my $user = $mysync->{ acc2 }->{ authuser } || $mysync->{ user2 } ;
6671 my $host = $mysync->{ host2 } ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006672 my $prompt = "What's the password for $user" . ' at ' . "$host? (not visible while you type, then enter RETURN) " ;
6673 $mysync->{password2} = ask_for_password( $prompt ) ;
6674 } ;
6675
6676
6677 if ( defined $mysync->{ passfile2 } ) {
6678 if ( ! -e -r $mysync->{ passfile2 } ) {
6679 myprint( "Failure: file from parameter --passfile2 $mysync->{ passfile2 } does not exist or is not readable\n" ) ;
6680 $mysync->{nb_errors}++ ;
6681 exit_clean( $mysync, $EX_NOINPUT ) ;
6682 }
6683 # passfile2 readable
6684 $mysync->{password2} = firstline ( $mysync->{ passfile2 } ) ;
6685 return ;
6686 }
6687 if ( $ENV{IMAPSYNC_PASSWORD2} ) {
6688 $mysync->{password2} = $ENV{IMAPSYNC_PASSWORD2} ;
6689 return ;
6690 }
6691 return ;
6692}
6693
6694
6695
6696
6697sub remove_tmp_files
6698{
6699 my $mysync = shift or return ;
6700 $mysync->{pidfile} or return ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006701
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006702 if ( -e $mysync->{pidfile} ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006703 myprint( "Removing pidfile $mysync->{pidfile}\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006704 unlink $mysync->{pidfile} ;
6705 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006706 if ( -e $mysync->{abortfile} ) {
6707 myprint( "Removing pidfile $mysync->{abortfile}\n" ) ;
6708 unlink $mysync->{abortfile} ;
6709 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006710 return ;
6711}
6712
6713sub cleanup_before_exit
6714{
6715 my $mysync = shift ;
6716 remove_tmp_files( $mysync ) ;
6717 if ( $mysync->{imap1} and $mysync->{imap1}->IsConnected() )
6718 {
6719 myprint( "Disconnecting from host1 $mysync->{ host1 } user1 $mysync->{ user1 }\n" ) ;
6720 $mysync->{imap1}->logout( ) ;
6721 }
6722 if ( $mysync->{imap2} and $mysync->{imap2}->IsConnected() )
6723 {
6724 myprint( "Disconnecting from host2 $mysync->{ host2 } user2 $mysync->{ user2 }\n" ) ;
6725 $mysync->{imap2}->logout( ) ;
6726 }
6727 if ( $mysync->{log} ) {
6728 myprint( "Log file is $mysync->{logfile} ( to change it, use --logfile filepath ; or use --nolog to turn off logging )\n" ) ;
6729 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006730 else
6731 {
6732 myprint( "No log file because of option --nolog\n" ) ;
6733 }
6734
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006735 if ( $mysync->{log} and $mysync->{logfile_handle} ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006736 #print( "Closing $mysync->{ logfile }\n" ) ;
6737 teefinish( $mysync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006738 }
6739 return ;
6740}
6741
6742
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006743sub exit_most_errors
6744{
6745 my $mysync = shift @ARG ;
6746
6747 myprint( errors_listing( $mysync ) ) ;
6748 my $exit_value = $EXIT_VALUE_OF_ERR_TYPE{ $mysync->{most_common_error} } || $EXIT_CATCH_ALL ;
6749 exit_clean( $mysync, $exit_value ) ;
6750 return ;
6751}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006752
6753sub exit_clean
6754{
6755 my $mysync = shift @ARG ;
6756 my $status = shift @ARG ;
6757 my @messages = @ARG ;
6758 if ( @messages )
6759 {
6760 myprint( @messages ) ;
6761 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006762 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 +01006763 cleanup_before_exit( $mysync ) ;
6764
6765 exit $status ;
6766}
6767
6768sub missing_option
6769{
6770 my $mysync = shift ;
6771 my $option = shift ;
6772 $mysync->{nb_errors}++ ;
6773 exit_clean( $mysync, $EX_USAGE, "$option option is mandatory, for help run $PROGRAM_NAME --help\n" ) ;
6774 return ;
6775}
6776
6777
6778sub catch_ignore
6779{
6780 my $mysync = shift ;
6781 my $signame = shift ;
6782
6783 my $sigcounter = ++$mysync->{ sigcounter }{ $signame } ;
6784 myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ),
6785 "). Received $sigcounter $signame signals so far. Thanks!\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006786 do_and_print_stats( $mysync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006787 return ;
6788}
6789
6790
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006791
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006792sub catch_exit
6793{
6794 my $mysync = shift ;
6795 my $signame = shift || q{} ;
6796 if ( $signame ) {
6797 myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ),
6798 "). Asked to terminate\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006799 if ( $mysync->{can_do_stats} ) {
6800 do_and_print_stats( $mysync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006801 myprint( "Ended by a signal $signame (my PID is $PROCESS_ID my PPID is ",
6802 getppid( ), "). I am asked to terminate immediately.\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006803 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006804 myprint( "You should resynchronize those accounts by running a sync again,\n",
6805 "since some messages and entire folders might still be missing on host2.\n"
6806 ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006807 ## no critic (RequireLocalizedPunctuationVars)
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006808 # Well, restore default action does not work well
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006809 $SIG{ $signame } = 'DEFAULT'; # restore default action
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006810 #$SIG{ 'TERM' } = 'DEFAULT'; # restore default action
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006811 # kill myself with $signame
6812 # https://www.cons.org/cracauer/sigint.html
6813 myprint( "Killing myself with signal $signame\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006814 #cleanup_before_exit( $mysync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006815 kill( $signame, $PROCESS_ID ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006816 #kill( 'TERM', $PROCESS_ID ) ;
6817 #sleep 1 ;
6818 #while ( 1 ) { } ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006819 $mysync->{nb_errors}++ ;
6820 exit_clean( $mysync, $EXIT_BY_SIGNAL,
6821 "Still there after killing myself with signal $signame...\n"
6822 ) ;
6823 }
6824 else
6825 {
6826 $mysync->{nb_errors}++ ;
6827 exit_clean( $mysync, $EXIT_BY_SIGNAL, "Exiting in catch_exit with no signal...\n" ) ;
6828 }
6829 return ;
6830}
6831
6832
6833sub catch_print
6834{
6835 my $mysync = shift ;
6836 my $signame = shift ;
6837
6838 my $sigcounter = ++$mysync->{ sigcounter }{ $signame } ;
6839 myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ),
6840 "). Received $sigcounter $signame signals so far. Thanks!\n" ) ;
6841 return ;
6842}
6843
6844sub here_twice
6845{
6846 my $mysync = shift ;
6847 my $now = time ;
6848 my $previous = $mysync->{lastcatch} || 0 ;
6849 $mysync->{lastcatch} = $now ;
6850
6851 if ( $INTERVAL_TO_EXIT >= $now - $previous ) {
6852 return $TRUE ;
6853 }else{
6854 return $FALSE ;
6855 }
6856}
6857
6858
6859sub catch_reconnect
6860{
6861 my $mysync = shift ;
6862 my $signame = shift ;
6863 if ( here_twice( $mysync ) ) {
6864 myprint( "Got two signals $signame within $INTERVAL_TO_EXIT seconds. Exiting...\n" ) ;
6865 catch_exit( $mysync, $signame ) ;
6866 }else{
6867 myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ), ")\n",
6868 "Hit 2 ctr-c within 2 seconds to exit the program\n",
6869 "Hit only 1 ctr-c to reconnect to both imap servers\n",
6870 ) ;
6871 myprint( "For now only one signal $signame within $INTERVAL_TO_EXIT seconds.\n" ) ;
6872
6873 if ( ! defined $mysync->{imap1} ) { return ; }
6874 if ( ! defined $mysync->{imap2} ) { return ; }
6875
6876 myprint( "Info: reconnecting to host1 imap server $mysync->{host1}\n" ) ;
6877 $mysync->{imap1}->State( Mail::IMAPClient::Unconnected ) ;
6878 $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT} += 1 ;
6879 if ( $mysync->{imap1}->reconnect( ) )
6880 {
6881 myprint( "Info: reconnected to host1 imap server $mysync->{host1}\n" ) ;
6882 }
6883 else
6884 {
6885 $mysync->{nb_errors}++ ;
6886 exit_clean( $mysync, $EXIT_CONNECTION_FAILURE ) ;
6887 }
6888 myprint( "Info: reconnecting to host2 imap server\n" ) ;
6889 $mysync->{imap2}->State( Mail::IMAPClient::Unconnected ) ;
6890 $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT} += 1 ;
6891 if ( $mysync->{imap2}->reconnect( ) )
6892 {
6893 myprint( "Info: reconnected to host2 imap server $mysync->{host2}\n" ) ;
6894 }
6895 else
6896 {
6897 $mysync->{nb_errors}++ ;
6898 exit_clean( $mysync, $EXIT_CONNECTION_FAILURE ) ;
6899 }
6900 myprint( "Info: reconnected to both imap servers\n" ) ;
6901 }
6902 return ;
6903}
6904
6905sub install_signals
6906{
6907 my $mysync = shift ;
6908
6909 if ( under_docker_context( $mysync ) )
6910 {
6911 # output( $mysync, "Under docker context so leaving signals as they are\n" ) ;
6912 output( $mysync, "Under docker context so installing only signals to exit\n" ) ;
6913 @{ $mysync->{ sigexit } } = ( defined( $mysync->{ sigexit } ) ) ? @{ $mysync->{ sigexit } } : ( 'INT', 'QUIT', 'TERM' ) ;
6914 sig_install( $mysync, 'catch_exit', @{ $mysync->{ sigexit } } ) ;
6915 }
6916 else
6917 {
6918 # Unix signals
6919 @{ $mysync->{ sigexit } } = ( defined( $mysync->{ sigexit } ) ) ? @{ $mysync->{ sigexit } } : ( 'QUIT', 'TERM' ) ;
6920 @{ $mysync->{ sigreconnect } } = ( defined( $mysync->{ sigreconnect } ) ) ? @{ $mysync->{ sigreconnect } } : ( 'INT' ) ;
6921 @{ $mysync->{ sigprint } } = ( defined( $mysync->{ sigprint } ) ) ? @{ $mysync->{ sigprint } } : ( 'HUP' ) ;
6922 @{ $mysync->{ sigignore } } = ( defined( $mysync->{ sigignore } ) ) ? @{ $mysync->{ sigignore } } : ( ) ;
6923
6924 #local %SIG = %SIG ;
6925 sig_install( $mysync, 'catch_exit', @{ $mysync->{ sigexit } } ) ;
6926 sig_install( $mysync, 'catch_reconnect', @{ $mysync->{ sigreconnect } } ) ;
6927 sig_install( $mysync, 'catch_print', @{ $mysync->{ sigprint } } ) ;
6928 # --sigignore can override sigexit, sigreconnect and sigprint (for the same signals only)
6929 sig_install( $mysync, 'catch_ignore', @{ $mysync->{ sigignore } } ) ;
6930
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006931 # remove/add sleeping mechanism when receiving USR1 signal (except on Win32)
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006932 sig_install_toggle_sleep( $mysync ) ;
6933 }
6934
6935 return ;
6936}
6937
6938
6939
6940sub tests_reconnect_12_if_needed
6941{
6942 note( 'Entering tests_reconnect_12_if_needed()' ) ;
6943
6944 my $mysync ;
6945
6946 $mysync->{imap1} = Mail::IMAPClient->new( ) ;
6947 $mysync->{imap2} = Mail::IMAPClient->new( ) ;
6948 $mysync->{imap1}->Server( 'test1.lamiral.info' ) ;
6949 $mysync->{imap2}->Server( 'test2.lamiral.info' ) ;
6950 is( 2, reconnect_12_if_needed( $mysync ), 'reconnect_12_if_needed: test1&test2 .lamiral.info => 1' ) ;
6951 is( 1, $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_12_if_needed: test1.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ;
6952 is( 1, $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_12_if_needed: test2.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ;
6953
6954 note( 'Leaving tests_reconnect_12_if_needed()' ) ;
6955 return ;
6956}
6957
6958sub reconnect_12_if_needed
6959{
6960 my $mysync = shift ;
6961 #return 2 ;
6962 if ( ! reconnect_if_needed( $mysync->{imap1} ) ) {
6963 return ;
6964 }
6965 if ( ! reconnect_if_needed( $mysync->{imap2} ) ) {
6966 return ;
6967 }
6968 # both were good
6969 return 2 ;
6970}
6971
6972
6973sub tests_reconnect_if_needed
6974{
6975 note( 'Entering tests_reconnect_if_needed()' ) ;
6976
6977
6978 my $myimap ;
6979
6980 is( undef, reconnect_if_needed( ), 'reconnect_if_needed: no args => undef' ) ;
6981 is( undef, reconnect_if_needed( $myimap ), 'reconnect_if_needed: undef arg => undef' ) ;
6982
6983 $myimap = Mail::IMAPClient->new( ) ;
6984 $myimap->Debug( 1 ) ;
6985 is( undef, reconnect_if_needed( $myimap ), 'reconnect_if_needed: empty new Mail::IMAPClient => undef' ) ;
6986 $myimap->Server( 'test.lamiral.info' ) ;
6987 is( 1, reconnect_if_needed( $myimap ), 'reconnect_if_needed: test.lamiral.info => 1' ) ;
6988 is( 1, $myimap->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_if_needed: test.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ;
6989
6990 note( 'Leaving tests_reconnect_if_needed()' ) ;
6991 return ;
6992}
6993
6994sub reconnect_if_needed
6995{
6996 # return undef upon failure.
6997 # return 1 upon connection success, with or without reconnection.
6998
6999 my $imap = shift ;
7000
7001 if ( ! defined $imap ) { return ; }
7002 if ( ! $imap->Server( ) ) { return ; }
7003
7004 if ( $imap->IsUnconnected( ) ) {
7005 $imap->{IMAPSYNC_RECONNECT_COUNT} += 1 ;
7006 if ( $imap->reconnect( ) ) {
7007 return 1 ;
7008 }
7009 }else{
7010 return 1 ;
7011 }
7012
7013 # A last forced one
7014 $imap->State( Mail::IMAPClient::Unconnected ) ;
7015 $imap->reconnect( ) ;
7016 $imap->{IMAPSYNC_RECONNECT_COUNT} += 1 ;
7017 if ( $imap->noop ) {
7018 # NOOP is ok
7019 return 1 ;
7020 }
7021
7022 return ;
7023}
7024
7025
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007026sub justconnect
7027{
7028 my $mysync = shift ;
7029 my $justconnect1 = justconnect1( $sync ) ;
7030 my $justconnect2 = justconnect2( $sync ) ;
7031 return "$justconnect1 $justconnect2";
7032}
7033
7034sub justconnect1
7035{
7036 my $mysync = shift ;
7037 if ( $mysync->{host1} )
7038 {
7039 myprint( "Host1: Will just connect to $mysync->{host1} without login\n" ) ;
7040 $mysync->{imap1} = connect_imap(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007041 $mysync->{host1}, $mysync->{port1},
7042 $mysync->{ssl1}, $mysync->{tls1},
7043 $mysync->{ acc1 } ) ;
7044
7045 imap_id( $mysync, $mysync->{imap1}, $mysync->{ acc1 }->{ Side } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007046 $mysync->{imap1}->logout( ) ;
7047 return $mysync->{host1} ;
7048 }
7049
7050 return q{} ;
7051}
7052
7053sub justconnect2
7054{
7055 my $mysync = shift ;
7056 if ( $mysync->{host2} )
7057 {
7058 myprint( "Host2: Will just connect to $mysync->{host2} without login\n" ) ;
7059 $mysync->{imap2} = connect_imap(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007060 $mysync->{host2}, $mysync->{port2},
7061 $mysync->{ssl2}, $mysync->{tls2},
7062 $mysync->{ acc2 } ) ;
7063
7064 imap_id( $mysync, $mysync->{imap2}, $mysync->{ acc2 }->{ Side } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007065 $mysync->{imap2}->logout( ) ;
7066 return $mysync->{host2} ;
7067 }
7068
7069 return q{} ;
7070}
7071
7072sub skip_macosx
7073{
7074 #return ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007075 # hostname used to be macosx.polarhome.com
7076 return( 'macosx' eq hostname( ) && ( 'darwin' eq $OSNAME ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007077}
7078
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007079sub skip_macosx_binary
7080{
7081 #return ;
7082 return( skip_macosx( ) && ( $PROGRAM_NAME =~ m{imapsync_bin_Darwin} ) ) ;
7083}
7084
7085
7086
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007087sub tests_mailimapclient_connect
7088{
7089 note( 'Entering tests_mailimapclient_connect()' ) ;
7090
7091 my $imap ;
7092 # ipv4
7093 ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect ipv4: new' ) ;
7094 is( 'Mail::IMAPClient', ref( $imap ), 'mailimapclient_connect ipv4: ref is Mail::IMAPClient' ) ;
7095
7096 # Mail::IMAPClient 3.40 die on this... So we skip it, thanks to "mature" IO::Socket::IP
7097 # Mail::IMAPClient 3.42 is ok so this test is back.
7098 is( undef, $imap->connect( ), 'mailimapclient_connect ipv4: connect with no server => failure' ) ;
7099
7100
7101 is( 'test.lamiral.info', $imap->Server( 'test.lamiral.info' ), 'mailimapclient_connect ipv4: setting Server(test.lamiral.info)' ) ;
7102 is( 1, $imap->Debug( 1 ), 'mailimapclient_connect ipv4: setting Debug( 1 )' ) ;
7103 is( 143, $imap->Port( 143 ), 'mailimapclient_connect ipv4: setting Port( 143 )' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007104 is( 10, $imap->Timeout( 10 ), 'mailimapclient_connect ipv4: setting Timeout( 10 )' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007105 like( ref( $imap->connect( ) ), qr/IO::Socket::INET|IO::Socket::IP/, 'mailimapclient_connect ipv4: connect to test.lamiral.info' ) ;
7106 like( $imap->logout( ), qr/Mail::IMAPClient/, 'mailimapclient_connect ipv4: logout' ) ;
7107 is( undef, undef $imap, 'mailimapclient_connect ipv4: free variable' ) ;
7108
7109 # ipv4 + ssl
7110 ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect ipv4 + ssl: new' ) ;
7111 is( 'test.lamiral.info', $imap->Server( 'test.lamiral.info' ), 'mailimapclient_connect ipv4 + ssl: setting Server(test.lamiral.info)' ) ;
7112 is( 1, $imap->Debug( 1 ), 'mailimapclient_connect ipv4 + ssl: setting Debug( 1 )' ) ;
7113 ok( $imap->Ssl( [ SSL_verify_mode => SSL_VERIFY_NONE, SSL_cipher_list => 'DEFAULT:!DH' ] ), 'mailimapclient_connect ipv4 + ssl: setting Ssl( SSL_VERIFY_NONE )' ) ;
7114 is( 993, $imap->Port( 993 ), 'mailimapclient_connect ipv4 + ssl: setting Port( 993 )' ) ;
7115 like( ref( $imap->connect( ) ), qr/IO::Socket::SSL/, 'mailimapclient_connect ipv4 + ssl: connect to test.lamiral.info' ) ;
7116 like( $imap->logout( ), qr/Mail::IMAPClient/, 'mailimapclient_connect ipv4 + ssl: logout in ssl does not cause failure' ) ;
7117 is( undef, undef $imap, 'mailimapclient_connect ipv4 + ssl: free variable' ) ;
7118
7119 # ipv6 + ssl
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007120
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007121 ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect ipv6 + ssl: new' ) ;
7122 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 +02007123 is( 10, $imap->Timeout( 10 ), 'mailimapclient_connect ipv6: setting Timeout( 10 )' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007124 ok( $imap->Ssl( [ SSL_verify_mode => SSL_VERIFY_NONE, SSL_cipher_list => 'DEFAULT:!DH' ] ), 'mailimapclient_connect ipv6 + ssl: setting Ssl( SSL_VERIFY_NONE )' ) ;
7125 is( 993, $imap->Port( 993 ), 'mailimapclient_connect ipv6 + ssl: setting Port( 993 )' ) ;
7126 SKIP: {
7127 if (
7128 'CUILLERE' eq hostname()
7129 or
7130 skip_macosx()
7131 or
7132 -e '/.dockerenv'
7133 or
7134 'pcHPDV7-HP' eq hostname()
7135 )
7136 {
7137 skip( 'Tests avoided on CUILLERE/pcHPDV7-HP/macosx.polarhome.com/docker cannot do ipv6', 4 ) ;
7138 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007139
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007140 is( 1, $imap->Debug( 1 ), 'mailimapclient_connect ipv4 + ssl: setting Debug( 1 )' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007141
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007142 # It sounds stupid but it avoids failures on the next test about $imap->connect
7143 is( '2a01:e34:ecde:70d0:223:54ff:fec2:36d7', resolv( 'petiteipv6.lamiral.info' ), 'resolv: petiteipv6.lamiral.info => 2001:41d0:8:bebd::1' ) ;
7144
7145 like( ref( $imap->connect( ) ), qr/IO::Socket::SSL/, 'mailimapclient_connect ipv6 + ssl: connect to petiteipv6.lamiral.info' ) ;
7146 # This one is ok on petite, not on ks2, do not know why, so commented.
7147 like( ref( $imap->logout( ) ), qr/Mail::IMAPClient/, 'mailimapclient_connect ipv6 + ssl: logout in ssl is ok on petiteipv6.lamiral.info' ) ;
7148 }
7149
7150 is( undef, undef $imap, 'mailimapclient_connect ipv6 + ssl: free variable' ) ;
7151
7152
7153 note( 'Leaving tests_mailimapclient_connect()' ) ;
7154 return ;
7155}
7156
7157
7158sub tests_mailimapclient_connect_bug
7159{
7160 note( 'Entering tests_mailimapclient_connect_bug()' ) ;
7161
7162 my $imap ;
7163
7164 # ipv6
7165 ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect_bug ipv6: new' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007166 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 +01007167 is( 143, $imap->Port( 143 ), 'mailimapclient_connect_bug ipv6: setting Port( 993 )' ) ;
7168
7169 SKIP: {
7170 if (
7171 'CUILLERE' eq hostname()
7172 or
7173 skip_macosx()
7174 or
7175 -e '/.dockerenv'
7176 or
7177 'pcHPDV7-HP' eq hostname()
7178 )
7179 {
7180 skip( 'Tests avoided on CUILLERE/pcHPDV7-HP/macosx.polarhome.com/docker cannot do ipv6', 1 ) ;
7181 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007182 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 +01007183 or diag( 'mailimapclient_connect_bug ipv6: ', $imap->LastError( ), $!, ) ;
7184 }
7185 #is( $imap->logout( ), undef, 'mailimapclient_connect_bug ipv6: logout in ssl causes failure' ) ;
7186 is( undef, undef $imap, 'mailimapclient_connect_bug ipv6: free variable' ) ;
7187
7188 note( 'Leaving tests_mailimapclient_connect_bug()' ) ;
7189 return ;
7190}
7191
7192
7193
7194sub tests_connect_socket
7195{
7196 note( 'Entering tests_connect_socket()' ) ;
7197
7198 is( undef, connect_socket( ), 'connect_socket: no args' ) ;
7199
7200 my $socket ;
7201 my $imap ;
7202 SKIP: {
7203 if (
7204 'CUILLERE' eq hostname()
7205 or
7206 skip_macosx()
7207 or
7208 -e '/.dockerenv'
7209 or
7210 'pcHPDV7-HP' eq hostname()
7211 )
7212 {
7213 skip( 'Tests avoided on CUILLERE/pcHPDV7-HP/macosx.polarhome.com/docker cannot do ipv6', 2 ) ;
7214 }
7215
7216 $socket = IO::Socket::INET6->new(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007217 PeerAddr => 'ks6ipv6.lamiral.info',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007218 PeerPort => 143,
7219 ) ;
7220
7221
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007222 ok( $imap = connect_socket( $socket ), 'connect_socket: ks6ipv6.lamiral.info port 143 IO::Socket::INET6' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007223 #$imap->Debug( 1 ) ;
7224 # myprint( $imap->capability( ) ) ;
7225 if ( $imap ) {
7226 $imap->logout( ) ;
7227 }
7228
7229 $IO::Socket::SSL::DEBUG = 4 ;
7230 $socket = IO::Socket::SSL->new(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007231 PeerHost => 'ks6ipv6.lamiral.info',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007232 PeerPort => 993,
7233 SSL_verify_mode => SSL_VERIFY_NONE,
7234 SSL_cipher_list => 'DEFAULT:!DH',
7235 ) ;
7236 # myprint( $socket ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007237 ok( $imap = connect_socket( $socket ), 'connect_socket: ks6ipv6.lamiral.info port 993 IO::Socket::SSL' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007238 #$imap->Debug( 1 ) ;
7239 # myprint( $imap->capability( ) ) ;
7240 # $socket->close( ) ;
7241 if ( $imap ) {
7242 $socket->close( ) ;
7243 }
7244 #$socket->close(SSL_no_shutdown => 1) ;
7245 #$imap->logout( ) ;
7246 #myprint( "\n" ) ;
7247 #$imap->logout( ) ;
7248 }
7249 note( 'Leaving tests_connect_socket()' ) ;
7250 return ;
7251}
7252
7253sub connect_socket
7254{
7255 my( $socket ) = @ARG ;
7256
7257 if ( ! defined $socket ) { return ; }
7258
7259 my $host = $socket->peerhost( ) ;
7260 my $port = $socket->peerport( ) ;
7261 #print "socket->peerhost: ", $socket->peerhost( ), "\n" ;
7262 #print "socket->peerport: ", $socket->peerport( ), "\n" ;
7263 my $imap = Mail::IMAPClient->new( ) ;
7264 $imap->Socket( $socket ) ;
7265 my $banner = $imap->Results()->[0] ;
7266 #myprint( "banner: $banner" ) ;
7267 return $imap ;
7268}
7269
7270
7271sub tests_probe_imapssl
7272{
7273 note( 'Entering tests_probe_imapssl()' ) ;
7274
7275 is( undef, probe_imapssl( ), 'probe_imapssl: no args => undef' ) ;
7276 is( undef, probe_imapssl( 'unknown' ), 'probe_imapssl: unknown => undef' ) ;
7277
7278 note( "hostname is: ", hostname() ) ;
7279 SKIP: {
7280 if (
7281 'CUILLERE' eq hostname()
7282 or
7283 skip_macosx()
7284 or
7285 -e '/.dockerenv'
7286 or
7287 'pcHPDV7-HP' eq hostname()
7288 )
7289 {
7290 skip( 'Tests avoided on CUILLERE or pcHPDV7-HP or Mac or docker: cannot do ipv6', 0 ) ;
7291 }
7292 # fed up with this one
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007293 #like( probe_imapssl( 'ks6ipv6.lamiral.info' ), qr/^\* OK/, 'probe_imapssl: ks6ipv6.lamiral.info matches "* OK"' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007294 } ;
7295
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007296
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007297 # It sounds stupid but it avoids failures on the next test about $imap->connect
7298 ok( resolv( 'imap.gmail.com' ), 'resolv: imap.gmail.com => something' ) ;
7299 like( probe_imapssl( 'imap.gmail.com' ), qr/^\* OK/, 'probe_imapssl: imap.gmail.com matches "* OK"' ) ;
7300
7301 like( probe_imapssl( 'test1.lamiral.info' ), qr/^\* OK/, 'probe_imapssl: test1.lamiral.info matches "* OK"' ) ;
7302
7303 note( 'Leaving tests_probe_imapssl()' ) ;
7304 return ;
7305}
7306
7307
7308sub probe_imapssl
7309{
7310 my $host = shift ;
7311
7312 if ( ! $host ) { return ; }
7313 $sync->{ debug } and $IO::Socket::SSL::DEBUG = 4 ;
7314 my $socket = IO::Socket::SSL->new(
7315 PeerHost => $host,
7316 PeerPort => $IMAP_SSL_PORT,
7317 SSL_verifycn_scheme => 'imap',
7318 SSL_verify_mode => $SSL_VERIFY_POLICY,
7319 SSL_cipher_list => 'DEFAULT:!DH',
7320 ) ;
7321 if ( ! $socket ) { return ; }
7322 $sync->{ debug } and print "socket: $socket\n" ;
7323
7324 my $banner ;
7325 $socket->sysread( $banner, 65_536 ) ;
7326 $sync->{ debug } and print "banner: $banner" ;
7327 $socket->close( ) ;
7328 return $banner ;
7329
7330}
7331
7332sub connect_imap
7333{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007334 my( $host, $port, $ssl, $tls, $acc ) = @_ ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007335 my $imap = Mail::IMAPClient->new( ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007336
7337 if ( $ssl ) { set_ssl( $imap, $acc ) }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007338 $imap->Server( $host ) ;
7339 $imap->Port( $port ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007340 $imap->Debug( $acc->{ debugimap } ) ;
7341 $imap->Timeout( $acc->{ timeout } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007342
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007343 my $side = lc $acc->{ Side } ;
7344
7345 myprint( "$acc->{ Side }: connecting on $side [$host] port [$port]\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007346
7347 if ( ! $imap->connect( ) )
7348 {
7349 $sync->{nb_errors}++ ;
7350 exit_clean( $sync, $EXIT_CONNECTION_FAILURE,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007351 "$acc->{ Side }: Can not open imap connection on [$host]: ",
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007352 $imap->LastError,
7353 " $OS_ERROR\n"
7354 ) ;
7355 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007356 myprint( "$acc->{ Side } IP address: ", $imap->Socket->peerhost(), "\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007357
7358 my $banner = $imap->Results()->[0] ;
7359
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007360 myprint( "$acc->{ Side } banner: $banner" ) ;
7361 myprint( "$acc->{ Side } capability: ", join(q{ }, @{ $imap->capability() || [] }), "\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007362
7363 if ( $tls ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007364 set_tls( $imap, $acc ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007365 if ( ! $imap->starttls( ) )
7366 {
7367 $sync->{nb_errors}++ ;
7368 exit_clean( $sync, $EXIT_TLS_FAILURE,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007369 "$acc->{ Side }: Can not go to tls encryption on $side [$host]:",
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007370 $imap->LastError, "\n"
7371 ) ;
7372 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007373 myprint( "$acc->{ Side }: Socket successfully converted to SSL\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007374 }
7375 return( $imap ) ;
7376}
7377
7378
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007379
7380sub tests_login_imap
7381{
7382 note( 'Entering tests_login_imap()' ) ;
7383
7384 is( undef, login_imap( ), 'login_imap: no args => undef' ) ;
7385
7386 SKIP: {
7387 if ( skip_macosx_binary( ) )
7388 {
7389 skip( 'Tests avoided only on binary on host polarhome macosx, no clue "ssl3_get_server_certificate:certificate verify failed"', 11 ) ;
7390 }
7391 else{
7392
7393 my $myimap ;
7394 my $acc = {} ;
7395 $acc->{ Side } = 'HostK' ;
7396 $acc->{ authmech } = 'LOGIN' ;
7397 #$IO::Socket::SSL::DEBUG = 4 ;
7398 # Each month (trimester?):
7399 # echo | openssl s_client -crlf -connect test1.lamiral.info:993
7400 # ...
7401 # certificate has expired
7402 # Fix:
7403 # ssh root@test1.lamiral.info 'apt update && apt upgrade && /etc/init.d/dovecot restart'
7404 ok(
7405 $myimap = login_imap( 'test1.lamiral.info', 993, 'test1', 'secret1',
7406 1, undef,
7407 1, 100, $acc, {},
7408 ), 'login_imap: test1.lamiral.info test1 ssl' ) ;
7409 ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: test1.lamiral.info test1 ssl IsAuthenticated' ) ;
7410
7411 ok(
7412 $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1',
7413 0, undef,
7414 1, 100, $acc, {},
7415 ), 'login_imap: test1.lamiral.info test1 tls' ) ;
7416 ok( $myimap && $myimap->IsAuthenticated( ), 'login_imap: test1.lamiral.info test1 tls IsAuthenticated' ) ;
7417
7418 #$IO::Socket::SSL::DEBUG = 4 ;
7419 $acc->{sslargs} = { SSL_version => 'SSLv2' } ;
7420 # SSLv2 not supported
7421 is(
7422 undef, $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1',
7423 0, undef,
7424 1, 100, $acc, {},
7425 ), 'login_imap: test1.lamiral.info test1 tls SSLv2 not supported' ) ;
7426#SSL_verify_mode => 1
7427#SSL_version => 'TLSv1_1'
7428
7429
7430
7431 # I have left ? exit_clean to be replaced by errors_incr( $mysync, 'error message' )
7432 # 1 in login_imap()
7433
7434
7435 my $mysync = {} ;
7436 $acc = {} ;
7437 $acc->{ Side } = 'Host2' ;
7438 $acc->{ authmech } = 'LOGIN' ;
7439 is(
7440 undef, login_imap( 'noresol.lamiral.info', 143, 'test1', 'secret1',
7441 0, undef,
7442 1, 100, $acc, $mysync,
7443 ), 'login_imap: noresol.lamiral.info undef' ) ;
7444
7445 is( 'ERR_CONNECTION_FAILURE_HOST2', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host2 noresol.lamiral.info => ERR_CONNECTION_FAILURE_HOST2' ) ;
7446
7447 # authentication failure for user2
7448 $mysync = {} ;
7449 is(
7450 undef, login_imap( 'test1.lamiral.info', 143, 'test1', 'Ce crétin',
7451 0, undef,
7452 1, 100, $acc, $mysync,
7453 ), 'login_imap: user2 bad passord => undef' ) ;
7454
7455 is( 'ERR_AUTHENTICATION_FAILURE_USER2', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host2 bad password => ERR_AUTHENTICATION_FAILURE_USER2' ) ;
7456
7457 # authentication failure for user1
7458 $mysync = {} ;
7459 $acc = {} ;
7460 $acc->{ Side } = 'Host1' ;
7461 $acc->{ authmech } = 'LOGIN' ;
7462 is(
7463 undef, login_imap( 'test1.lamiral.info', 143, 'test1', 'Ce crétin',
7464 0, undef,
7465 1, 100, $acc, $mysync,
7466 ), 'login_imap: user1 bad passord => undef' ) ;
7467
7468 is( 'ERR_AUTHENTICATION_FAILURE_USER1', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host1 bad password => ERR_AUTHENTICATION_FAILURE_USER1' ) ;
7469
7470 }
7471 }
7472
7473 note( 'Leaving tests_login_imap()' ) ;
7474 return ;
7475}
7476
7477sub oauthgenerateaccess
7478{
7479 if ( "petite" eq hostname() )
7480 {
7481 myprint( "oauthgenerateaccess\n" ) ;
7482 my @output = backtick( 'cd oauth2 && pwd && ./generate_gmail_token imapsync.gl0@gmail.com' ) ;
7483 myprint( @output ) ;
7484 }
7485 return ;
7486}
7487
7488sub tests_login_imap_oauth
7489{
7490 note( 'Entering tests_login_imap_oauth()' ) ;
7491
7492 oauthgenerateaccess() ;
7493
7494 SKIP: {
7495 if ( skip_macosx_binary( ) )
7496 {
7497 skip( 'Tests avoided only on binary on host polarhome macosx, no clue "ssl3_get_server_certificate:certificate verify failed"', 6 ) ;
7498 }
7499 else
7500 {
7501
7502 my $mysync ;
7503 my $acc ;
7504 # oauthdirect authentication failure for user2
7505 $mysync = {} ;
7506 $acc = {} ;
7507 $acc->{ oauthdirect } = 'caca2' ;
7508 $acc->{ debugimap } = 1 ;
7509 $mysync->{ showpasswords } = 1 ;
7510 $acc->{ Side } = 'Host2' ;
7511 $acc->{ authmech } = 'QQQ' ;
7512 is(
7513 undef, login_imap( 'imap.gmail.com', 993, 'test1', 'Ce crétin',
7514 1, undef,
7515 1, 100, $acc, $mysync,
7516 ), 'login_imap: user2 bad oauthdirect => undef' ) ;
7517
7518 is( 'ERR_AUTHENTICATION_FAILURE_USER2', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host2 bad oauthdirect => ERR_AUTHENTICATION_FAILURE_USER2' ) ;
7519
7520 # oauthdirect authentication failure for user1
7521 $mysync = {} ;
7522 $acc = {} ;
7523 $acc->{ Side } = 'Host1' ;
7524 $acc->{ oauthdirect } = 'caca1' ;
7525 $acc->{ debugimap } = 1 ;
7526 $mysync->{ showpasswords } = 1 ;
7527 $acc->{ authmech } = 'QQQ' ;
7528 is(
7529 undef, login_imap( 'imap.gmail.com', 993, 'test1', 'Ce crétin',
7530 1, undef,
7531 1, 100, $acc, $mysync,
7532 ), 'login_imap: user1 bad oauthdirect => undef' ) ;
7533
7534 is( 'ERR_AUTHENTICATION_FAILURE_USER1', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host1 bad oauthdirect => ERR_AUTHENTICATION_FAILURE_USER1' ) ;
7535
7536 # oauthdirect authentication failure for user1
7537 $mysync = {} ;
7538 $acc = {} ;
7539 $acc->{ Side } = 'Host1' ;
7540 $acc->{ oauthdirect } = '' ;
7541 $acc->{ debugimap } = 1 ;
7542 $mysync->{ showpasswords } = 1 ;
7543 $acc->{ authmech } = 'QQQ' ;
7544 is(
7545 undef, login_imap( 'imap.gmail.com', 993, 'test1', 'Ce crétin',
7546 1, undef,
7547 1, 100, $acc, $mysync,
7548 ), 'login_imap: user1 bad oauthdirect => undef' ) ;
7549
7550 is( 'ERR_AUTHENTICATION_FAILURE_USER1', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host1 no oauthdirect value => ERR_AUTHENTICATION_FAILURE_USER1' ) ;
7551
7552 }
7553 }
7554
7555 # oauthdirect authentication success for user1
7556 SKIP: {
7557 if ( ! -r 'oauth2/D_oauth2_oauthdirect_imapsync.gl0@gmail.com.txt' )
7558 {
7559 skip( 'oauthdirect: no oauthdirect file', 2 ) ;
7560 }
7561 my $myimap ;
7562 my $mysync = {} ;
7563 my $acc = {} ;
7564 $acc->{ Side } = 'Host1' ;
7565 $acc->{ oauthdirect } = 'oauth2/D_oauth2_oauthdirect_imapsync.gl0@gmail.com.txt' ;
7566 $acc->{ debugimap } = 1 ;
7567 $mysync->{ showpasswords } = 1 ;
7568 $acc->{ authmech } = 'QQQ' ;
7569 isa_ok(
7570 $myimap = login_imap( 'imap.gmail.com', 993, 'user_useless', 'password_useless',
7571 1, undef,
7572 1, 100, $acc, $mysync,
7573 ), 'Mail::IMAPClient', 'login_imap: user1 good oauthdirect => Mail::IMAPClient' ) ;
7574
7575 ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 IsAuthenticated' ) ;
7576 }
7577
7578 # oauthaccesstoken authentication success for user1
7579 SKIP: {
7580 if ( ! -r 'oauth2/D_oauth2_access_token_imapsync.gl0@gmail.com.txt' )
7581 {
7582 skip( 'oauthaccesstoken: no access_token file', 2 ) ;
7583 }
7584 my $myimap ;
7585 my $mysync = {} ;
7586 my $acc = {} ;
7587 $acc->{ Side } = 'Host1' ;
7588 $acc->{ oauthaccesstoken } = 'oauth2/D_oauth2_access_token_imapsync.gl0@gmail.com.txt' ;
7589 $acc->{ debugimap } = 1 ;
7590 $mysync->{ showpasswords } = 1 ;
7591 $acc->{ authmech } = 'QQQ' ;
7592 isa_ok(
7593 $myimap = login_imap( 'imap.gmail.com', 993, 'imapsync.gl0@gmail.com', 'password_useless',
7594 1, undef,
7595 1, 100, $acc, $mysync,
7596 ), 'Mail::IMAPClient', 'login_imap: user1 good oauthaccesstoken => Mail::IMAPClient' ) ;
7597
7598 ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthaccesstoken IsAuthenticated' ) ;
7599
7600 }
7601
7602
7603 note( 'Leaving tests_login_imap_oauth()' ) ;
7604 return ;
7605}
7606
7607
7608
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007609sub login_imap
7610{
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007611 my @allargs = @_ ;
7612 my(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007613 $host, $port, $user, $password,
7614 $ssl, $tls,
7615 $uid, $split, $acc, $mysync ) = @allargs ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007616
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007617 if ( ! all_defined( $host, $port, $user, $acc->{ Side } ) )
7618 {
7619 return ;
7620 }
7621
7622 my $side = lc $acc->{ Side } ;
7623 myprint( "$acc->{ Side }: connecting and login on $side [$host] port [$port] with user [$user]\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007624
7625 my $imap = init_imap( @allargs ) ;
7626
7627 if ( ! $imap->connect() )
7628 {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007629 my $error = "$acc->{ Side } failure: can not open imap connection on $side [$host] with user [$user]: "
7630 . $imap->LastError . " $OS_ERROR\n" ;
7631 errors_incr( $mysync, $error ) ;
7632 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007633 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007634 myprint( "$acc->{ Side } IP address: ", $imap->Socket->peerhost(), "\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007635 my $banner = $imap->Results()->[0] ;
7636
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007637 myprint( "$acc->{ Side } banner: $banner" ) ;
7638 myprint( "$acc->{ Side } capability before authentication: ", join(q{ }, @{ $imap->capability() || [] }), "\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007639
7640 if ( (! $ssl) and (! defined $tls ) and $imap->has_capability( 'STARTTLS' ) ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007641 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 +01007642 $tls = 1 ;
7643 }
7644
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007645
7646 #myprint( Data::Dumper->Dump( [ @allargs ] ) ) ;
7647 if ( $tls ) {
7648 set_tls( $imap, $acc ) ;
7649
7650 if ( ! $imap->starttls( ) )
7651 {
7652 my $error = "$acc->{ Side } failure: Can not go to tls encryption on $side [$host]: "
7653 . $imap->LastError . "\n" ;
7654
7655 errors_incr( $mysync, $error ) ;
7656 return ;
7657 }
7658 myprint( "$acc->{ Side }: Socket successfully converted to SSL\n" ) ;
7659 }
7660
7661 if ( $acc->{ authmech } eq 'PREAUTH' ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007662 if ( $imap->IsAuthenticated( ) ) {
7663 $imap->Socket ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007664 myprintf("%s: Assuming PREAUTH for %s\n", $acc->{ Side }, $imap->Server ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007665 }else{
7666 $mysync->{nb_errors}++ ;
7667 exit_clean(
7668 $mysync, $EXIT_AUTHENTICATION_FAILURE,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007669 "$acc->{ Side } failure: error login on $side [$host] with user [$user] auth [PREAUTH]\n"
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007670 ) ;
7671 }
7672 }
7673
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007674
7675
7676 if ( authenticate_imap( $imap, @allargs ) )
7677 {
7678 myprint( "$acc->{ Side }: success login on [$host] with user [$user] auth [$acc->{ authmech }] or [LOGIN]\n" ) ;
7679 return( $imap ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007680 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007681 else
7682 {
7683 # The errors are already printed
7684 myprint( "$acc->{ Side }: failed login on [$host] with user [$user] auth [$acc->{ authmech }]\n" ) ;
7685 return ;
7686 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007687}
7688
7689
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007690
7691sub init_imap
7692{
7693 my(
7694 $host, $port, $user, $password,
7695 $ssl, $tls,
7696 $uid, $split, $acc, $mysync ) = @_ ;
7697
7698 my ( $imap ) ;
7699
7700 $imap = Mail::IMAPClient->new() ;
7701
7702 if ( $mysync->{ tee } )
7703 {
7704 # Well, it does not change anything, does it?
7705 # It does when suppressing the hack with *STDERR
7706 $imap->Debug_fh( $mysync->{ tee } ) ;
7707 }
7708
7709 if ( $ssl ) { set_ssl( $imap, $acc ) }
7710 if ( $tls ) { } # can not do set_tls() here because connect() will directly do a STARTTLS
7711 $imap->Clear( 1 ) ;
7712 $imap->Server( $host ) ;
7713 $imap->Port( $port ) ;
7714 $imap->Fast_io( $acc->{ fastio } ) ;
7715 $imap->Buffer( $buffersize || $DEFAULT_BUFFER_SIZE ) ;
7716 $imap->Uid( $uid ) ;
7717
7718
7719 $imap->Peek( 1 ) ;
7720 $imap->Debug( $acc->{ debugimap } ) ;
7721 if ( $mysync->{ showpasswords } ) {
7722 $imap->Showcredentials( 1 ) ;
7723 }
7724
7725 defined $acc->{ timeout } and $imap->Timeout( $acc->{ timeout } ) ;
7726
7727 if ( defined $acc->{ reconnectretry } )
7728 {
7729 $imap->Reconnectretry( $acc->{ reconnectretry } ) ;
7730 }
7731 $imap->{IMAPSYNC_RECONNECT_COUNT} = 0 ;
7732 $imap->Ignoresizeerrors( $allowsizemismatch ) ;
7733 $split and $imap->Maxcommandlength( $SPLIT_FACTOR * $split ) ;
7734
7735
7736 return( $imap ) ;
7737
7738}
7739
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007740sub authenticate_imap
7741{
7742 my( $imap,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007743 $host, $port, $user, $password,
7744 $ssl, $tls,
7745 $uid, $split, $acc, $mysync ) = @_ ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007746
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007747 check_capability( $imap, $acc->{ authmech }, $acc->{ Side } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007748 $imap->User( $user ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007749
7750 if ( defined $acc->{ domain } )
7751 {
7752 $imap->Domain( $acc->{ domain } ) ;
7753 $mysync->{ debug } and myprint( "Domain: $acc->{ domain }\n" ) ;
7754 }
7755
7756 $imap->Authuser( $acc->{ authuser } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007757 $imap->Password( $password ) ;
7758
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007759 if ( 'X-MASTERAUTH' eq $acc->{ authmech } )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007760 {
7761 xmasterauth( $imap ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007762 return 1 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007763 }
7764
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007765
7766 if ( defined $acc->{ oauthdirect } )
7767 {
7768 $acc->{ authmech } = 'XOAUTH2 direct' ;
7769 return( oauthdirect( $mysync, $acc, $imap, $host, $user ) ) ;
7770 }
7771
7772
7773 if ( defined $acc->{ oauthaccesstoken } )
7774 {
7775 $acc->{ authmech } = 'XOAUTH2 accesstoken' ;
7776 return( oauthaccesstoken( $mysync, $acc, $imap, $host, $user ) ) ;
7777 }
7778
7779
7780
7781
7782 if ( $acc->{ proxyauth } ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007783 $imap->Authmechanism(q{}) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007784 $imap->User( $acc->{ authuser } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007785 } else {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007786 $imap->Authmechanism( $acc->{ authmech } ) unless ( $acc->{ authmech } eq 'LOGIN' or $acc->{ authmech } eq 'PREAUTH' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007787 }
7788
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007789 $imap->Authcallback(\&xoauth2) if ( 'XOAUTH2' eq $acc->{ authmech } ) ;
7790 $imap->Authcallback(\&plainauth) if ( ( 'PLAIN' eq $acc->{ authmech } ) or ( 'EXTERNAL' eq $acc->{ authmech } ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007791
7792
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007793 unless ( $acc->{ authmech } eq 'PREAUTH' or $imap->login( ) ) {
7794 my $info = "$acc->{ Side } failure: Error login on [$host] with user [$user] auth" ;
7795 my $einfo = imap_last_error( $imap ) ;
7796 my $error = "$info [$acc->{ authmech }]: $einfo\n" ;
7797
7798
7799 if ( ( $acc->{ authmech } eq 'LOGIN' ) or $imap->IsUnconnected( ) or $acc->{ authuser } ) {
7800 $acc->{ authuser } ||= "" ;
7801 myprint( "$acc->{ Side } info: authmech [$acc->{ authmech }] user [$user] authuser [$acc->{ authuser }] IsUnconnected [", $imap->IsUnconnected( ), "]\n" ) ;
7802 errors_incr( $mysync, $error ) ;
7803 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007804 }else{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007805 errors_incr( $mysync, $error ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007806 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007807
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007808 # It is not secure to try plain text LOGIN when another authmech failed
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007809 # but I do it anyway. This behavior is optional as option --notrylogin will skip it.
7810 if ( $mysync->{ trylogin } )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007811 {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007812 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" ) ;
7813 $imap->Authmechanism(q{}) ;
7814 if ( ! $imap->login( ) )
7815 {
7816 failure_login( $mysync, $acc, 'LOGIN', $imap, $host, $user ) ;
7817 return ;
7818 }
7819 else
7820 {
7821 myprint( "$acc->{ Side }: success login on [$host] with user [$user] auth [LOGIN] after [$acc->{ authmech }] failure\n" ) ;
7822 }
7823 }
7824 else
7825 {
7826 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" ) ;
7827 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007828 }
7829 }
7830
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007831 if ( $acc->{ proxyauth } ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007832 if ( ! $imap->proxyauth( $user ) ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007833 failure_proxyauth( $mysync, $acc, $acc->{ authmech }, $imap, $host, $user ) ;
7834 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007835 }
7836 }
7837
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007838 return 1;
7839}
7840
7841
7842sub failure_login
7843{
7844 my( $mysync, $acc, $authmech, $imap, $host, $user ) = @ARG ;
7845 my $info = "$acc->{ Side } failure: Error login on [$host] with user [$user] auth" ;
7846 my $einfo = imap_last_error( $imap ) ;
7847 my $error = "$info [$authmech]: $einfo\n" ;
7848 errors_incr( $mysync, $error ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007849 return ;
7850}
7851
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007852# failure_login and failure_proxyauth function are similar but
7853# variable $error so no factoring
7854sub failure_proxyauth
7855{
7856 my( $mysync, $acc, $authmech, $imap, $host, $user ) = @ARG ;
7857 my $info = "$acc->{ Side } failure: Error login on [$host] with user [$user] auth" ;
7858 my $einfo = imap_last_error( $imap ) ;
7859 my $error = "$info [$authmech] using proxy-login as [$acc->{ authuser }]: $einfo\n" ;
7860 errors_incr( $mysync, $error ) ;
7861 return ;
7862}
7863
7864
7865
7866
7867sub oauthdirect
7868{
7869 my( $mysync, $acc, $imap, $host, $user ) = @_ ;
7870
7871 my $oauthdirect_str ;
7872 if ( -f -r $acc->{ oauthdirect } )
7873 {
7874 $oauthdirect_str = firstline( $acc->{ oauthdirect } ) ;
7875 }
7876 else
7877 {
7878 $oauthdirect_str = $acc->{ oauthdirect } || 'Please define oauthdirect value' ;
7879 }
7880 if ( $imap->authenticate('XOAUTH2', sub { return $oauthdirect_str } ) )
7881 {
7882 return 1 ;
7883 }
7884 else
7885 {
7886 failure_login( $mysync, $acc, $acc->{ authmech }, $imap, $host, $user ) ;
7887 return ;
7888 }
7889 return ;
7890}
7891
7892
7893
7894
7895sub oauthaccesstoken
7896{
7897 my( $mysync, $acc, $imap, $host, $user ) = @_ ;
7898
7899 my $oauthaccesstoken_str ;
7900 if ( -f -r $acc->{ oauthaccesstoken } )
7901 {
7902 $oauthaccesstoken_str = firstline( $acc->{ oauthaccesstoken } ) ;
7903 }
7904 else
7905 {
7906 $oauthaccesstoken_str = $acc->{ oauthaccesstoken } || 'Please define oauthaccesstoken value' ;
7907 }
7908
7909 my $oauth_string = "user=" . $user . "\x01auth=Bearer ". $oauthaccesstoken_str . "\x01\x01" ;
7910 #myprint "oauth_string: $oauth_string\n" ;
7911
7912 my $oauth_string_base64 = encode_base64( $oauth_string , '' ) ;
7913 #myprint "oauth_string_base64: $oauth_string_base64\n" ;
7914
7915 my $oauthdirect_str = $oauth_string_base64 ;
7916
7917 if ( $imap->authenticate('XOAUTH2', sub { return $oauthdirect_str } ) )
7918 {
7919 return 1 ;
7920 }
7921 else
7922 {
7923 failure_login( $mysync, $acc, $acc->{ authmech }, $imap, $host, $user ) ;
7924 return ;
7925 }
7926 return ;
7927}
7928
7929
7930
7931
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007932sub check_capability
7933{
7934
7935 my( $imap, $authmech, $Side ) = @_ ;
7936
7937
7938 if ( $imap->has_capability( "AUTH=$authmech" )
7939 or $imap->has_capability( $authmech ) )
7940 {
7941 myprintf("%s: %s says it has CAPABILITY for AUTHENTICATE %s\n",
7942 $Side, $imap->Server, $authmech) ;
7943 return ;
7944 }
7945
7946 if ( $authmech eq 'LOGIN' )
7947 {
7948 # Well, the warning is so common and useless that I prefer to remove it
7949 # No more "... says it has NO CAPABILITY for AUTHENTICATE LOGIN"
7950 return ;
7951 }
7952
7953
7954 myprintf( "%s: %s says it has NO CAPABILITY for AUTHENTICATE %s\n",
7955 $Side, $imap->Server, $authmech ) ;
7956
7957 if ( $authmech eq 'PLAIN' )
7958 {
7959 myprint( "$Side: frequently PLAIN is only supported with SSL, try --ssl or --tls options\n" ) ;
7960 }
7961
7962 return ;
7963}
7964
7965sub set_ssl
7966{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007967 my ( $imap, $acc ) = @_ ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007968 # SSL_version can be
7969 # SSLv3 SSLv2 SSLv23 SSLv23:!SSLv2 (last one is the default in IO-Socket-SSL-1.953)
7970 #
7971
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007972 my $sslargs_hash = $acc->{sslargs} ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007973
7974 my $sslargs_default = {
7975 SSL_verify_mode => $SSL_VERIFY_POLICY,
7976 SSL_verifycn_scheme => 'imap',
7977 SSL_cipher_list => 'DEFAULT:!DH',
7978 } ;
7979
7980 # initiate with default values
7981 my %sslargs_mix = %{ $sslargs_default } ;
7982 # now override with passed values
7983 @sslargs_mix{ keys %{ $sslargs_hash } } = values %{ $sslargs_hash } ;
7984 # remove keys with undef values
7985 foreach my $key ( keys %sslargs_mix ) {
7986 delete $sslargs_mix{ $key } if ( not defined $sslargs_mix{ $key } ) ;
7987 }
7988 # back to an ARRAY
7989 my @sslargs_mix = %sslargs_mix ;
7990 #myprint( Data::Dumper->Dump( [ $sslargs_hash, $sslargs_default, \%sslargs_mix, \@sslargs_mix ] ) ) ;
7991 $imap->Ssl( \@sslargs_mix ) ;
7992 return ;
7993}
7994
7995sub set_tls
7996{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007997 my ( $imap, $acc ) = @_ ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007998
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007999 my $sslargs_hash = $acc->{sslargs} ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008000
8001 my $sslargs_default = {
8002 SSL_verify_mode => $SSL_VERIFY_POLICY,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008003 SSL_cipher_list => 'DEFAULT:!DH',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008004 } ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008005 #myprint( Data::Dumper->Dump( [ $acc, $sslargs_hash, $sslargs_default ] ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008006
8007 # initiate with default values
8008 my %sslargs_mix = %{ $sslargs_default } ;
8009 # now override with passed values
8010 @sslargs_mix{ keys %{ $sslargs_hash } } = values %{ $sslargs_hash } ;
8011 # remove keys with undef values
8012 foreach my $key ( keys %sslargs_mix ) {
8013 delete $sslargs_mix{ $key } if ( not defined $sslargs_mix{ $key } ) ;
8014 }
8015 # back to an ARRAY
8016 my @sslargs_mix = %sslargs_mix ;
8017
8018 $imap->Starttls( \@sslargs_mix ) ;
8019 return ;
8020}
8021
8022
8023
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008024sub plainauth
8025{
8026 my $code = shift;
8027 my $imap = shift;
8028
8029 my $string = mysprintf("%s\x00%s\x00%s", $imap->User,
8030 $imap->Authuser, $imap->Password);
8031 return encode_base64("$string", q{});
8032}
8033
8034# Copy from https://github.com/imapsync/imapsync/pull/25/files
8035# Changes "use" pragmas to "require".
8036# The openssl system call shall be replaced by pure Perl and
8037# https://metacpan.org/pod/Crypt::OpenSSL::PKCS12
8038
8039# Now the Joaquin Lopez code:
8040#
8041# Used this as an example: https://gist.github.com/gsainio/6322375
8042#
8043# And this as a reference: https://developers.google.com/accounts/docs/OAuth2ServiceAccount
8044# (note there is an http/rest tab, where the real info is hidden away... went on a witch hunt
8045# until I noticed that...)
8046#
8047# This is targeted at gmail to maintain compatibility after google's oauth1 service is deactivated
8048# on May 5th, 2015: https://developers.google.com/gmail/oauth_protocol
8049# If there are other oauth2 implementations out there, this would need to be modified to be
8050# compatible
8051#
8052# This is a good guide on setting up the google api/apps side of the equation:
8053# http://www.limilabs.com/blog/oauth2-gmail-imap-service-account
8054#
8055# 2016/05/27: Updated to support oauth/key data in the .json files Google now defaults to
8056# when creating gmail service accounts. They're easier to work with since they neither
8057# requiring decrypting nor specifying the oauth2 client id separately.
8058#
8059# If the password arg ends in .json, it will assume this new json method, otherwise it
8060# will fallback to the "oauth client id;.p12" format it was previously using.
8061sub xoauth2
8062{
8063 require JSON::WebToken ;
8064 require LWP::UserAgent ;
8065 require HTML::Entities ;
8066 require JSON ;
8067 require JSON::WebToken::Crypt::RSA ;
8068 require Crypt::OpenSSL::RSA ;
8069 require Encode::Byte ;
8070 require IO::Socket::SSL ;
8071
8072 my $code = shift;
8073 my $imap = shift;
8074
8075 my ($iss,$key);
8076
8077 if( $imap->Password =~ /^(.*\.json)$/x )
8078 {
8079 my $json = JSON->new( ) ;
8080 my $filename = $1;
8081 $sync->{ debug } and myprint( "XOAUTH2 json file: $filename\n" ) ;
8082 my $FILE ;
8083 if ( ! open( $FILE, '<', $filename ) )
8084 {
8085 $sync->{nb_errors}++ ;
8086 exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE,
8087 "error [$filename]: $OS_ERROR\n"
8088 ) ;
8089 }
8090 my $jsonfile = $json->decode( join q{}, <$FILE> ) ;
8091 close $FILE ;
8092
8093 $iss = $jsonfile->{client_id};
8094 $key = $jsonfile->{private_key};
8095 $sync->{ debug } and myprint( "Service account: $iss\n");
8096 $sync->{ debug } and myprint( "Private key:\n$key\n");
8097 }
8098 else
8099 {
8100 # Get iss (service account address), keyfile name, and keypassword if necessary
8101 ( $iss, my $keyfile, my $keypass ) = $imap->Password =~ /([\-\d\w\@\.]+);([a-zA-Z0-9 \_\-\.\/]+);?(.*)?/x ;
8102
8103 # Assume key password is google default if not provided
8104 $keypass = 'notasecret' if not $keypass;
8105
8106 $sync->{ debug } and myprint( "Service account: $iss\nKey file: $keyfile\nKey password: $keypass\n");
8107
8108 # Get private key from p12 file (would be better in perl...)
8109 $key = `openssl pkcs12 -in "$keyfile" -nodes -nocerts -passin pass:$keypass -nomacver`;
8110
8111 $sync->{ debug } and myprint( "Private key:\n$key\n");
8112 }
8113
8114 # Create jwt of oauth2 request
8115 my $time = time ;
8116 my $jwt = JSON::WebToken->encode( {
8117 'iss' => $iss, # service account
8118 'scope' => 'https://mail.google.com/',
8119 'aud' => 'https://www.googleapis.com/oauth2/v3/token',
8120 'exp' => $time + $DEFAULT_EXPIRATION_TIME_OAUTH2_PK12,
8121 'iat' => $time,
8122 'prn' => $imap->User # user to auth as
8123 },
8124 $key, 'RS256', {'typ' => 'JWT'} ); # Crypt::OpenSSL::RSA needed here.
8125
8126 # Post oauth2 request
8127 my $ua = LWP::UserAgent->new( ) ;
8128 $ua->env_proxy( ) ;
8129
8130 my $response = $ua->post('https://www.googleapis.com/oauth2/v3/token',
8131 { grant_type => HTML::Entities::encode_entities('urn:ietf:params:oauth:grant-type:jwt-bearer'),
8132 assertion => $jwt } ) ;
8133
8134 unless( $response->is_success( ) ) {
8135 $sync->{nb_errors}++ ;
8136 exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE,
8137 $response->code, "\n", $response->content, "\n"
8138 ) ;
8139 }else{
8140 $sync->{ debug } and myprint( $response->content ) ;
8141 }
8142
8143 # access_token in response is what we need
8144 my $data = JSON::decode_json( $response->content ) ;
8145
8146 # format as oauth2 auth data
8147 my $xoauth2_string = encode_base64( 'user=' . $imap->User . "\1auth=Bearer " . $data->{access_token} . "\1\1", q{} ) ;
8148
8149 $sync->{ debug } and myprint( "XOAUTH2 String: $xoauth2_string\n");
8150 return($xoauth2_string);
8151}
8152
8153
8154
8155
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008156sub xmasterauth
8157{
8158 # This is Kerio auth admin
8159 # This code comes from
8160 # https://github.com/imapsync/imapsync/pull/53/files
8161
8162 my $imap = shift ;
8163
8164 my $user = $imap->User( ) ;
8165 my $password = $imap->Password( ) ;
8166 my $authmech = 'X-MASTERAUTH' ;
8167
8168 my @challenge = $imap->tag_and_run( $authmech, "+" ) ;
8169 if ( not defined $challenge[0] )
8170 {
8171 $sync->{nb_errors}++ ;
8172 exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE,
8173 "Failure authenticate with $authmech: ",
8174 $imap->LastError, "\n"
8175 ) ;
8176 return ; # hahaha!
8177 }
8178 $sync->{ debug } and myprint( "X-MASTERAUTH challenge: [@challenge]\n" ) ;
8179
8180 $challenge[1] =~ s/^\+ |^\s+|\s+$//g ;
8181 if ( ! $imap->_imap_command( { addcrlf => 1, addtag => 0, tag => $imap->Count }, md5_hex( $challenge[1] . $password ) ) )
8182 {
8183 $sync->{nb_errors}++ ;
8184 exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE,
8185 "Failure authenticate with $authmech: ",
8186 $imap->LastError, "\n"
8187 ) ;
8188 }
8189
8190 if ( ! $imap->tag_and_run( 'X-SETUSER ' . $user ) )
8191 {
8192 $sync->{nb_errors}++ ;
8193 exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE,
8194 "Failure authenticate with $authmech: ",
8195 "X-SETUSER ", $imap->LastError, "\n"
8196 ) ;
8197 }
8198
8199 $imap->State( Mail::IMAPClient::Authenticated ) ;
8200 # I comment this state because "Selected" state is usually done by SELECT or EXAMINE imap commands
8201 # $imap->State( Mail::IMAPClient::Selected ) ;
8202
8203 return ;
8204}
8205
8206
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008207
8208sub banner_imapsync
8209{
8210 my $mysync = shift @ARG ;
8211 my @argv = @ARG ;
8212
8213 my $banner_imapsync = join q{},
8214 q{$RCSfile: imapsync,v $ },
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008215 q{$Revision: 2.148 $ },
8216 q{$Date: 2021/07/22 14:21:09 $ },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008217 "\n",
8218 "Command line used, run by $EXECUTABLE_NAME:\n",
8219 "$PROGRAM_NAME ", command_line_nopassword( $mysync, @argv ), "\n" ;
8220
8221 return( $banner_imapsync ) ;
8222}
8223
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008224sub tests_do_valid_directory
8225{
8226 note( 'Entering tests_do_valid_directory()' ) ;
8227
8228 is( 1, do_valid_directory( '.'), 'do_valid_directory: . good' ) ;
8229 is( 1, do_valid_directory( './W/tmp/tests/valid/sub'), 'do_valid_directory: ./W/tmp/tests/valid/sub good' ) ;
8230
8231 Readonly my $NB_UNIX_tests_do_valid_directory_non_root => 2 ;
8232 diag( "OSNAME=$OSNAME EFFECTIVE_USER_ID=$EFFECTIVE_USER_ID" ) ;
8233
8234 SKIP: {
8235 skip( 'Tests only for non roor user', $NB_UNIX_tests_do_valid_directory_non_root ) if ( '0' eq $EFFECTIVE_USER_ID ) ;
8236 diag( 'The "Error / is not writable" is on purpose' ) ;
8237 ok( 0 == do_valid_directory( '/'), 'do_valid_directory: / bad' ) ;
8238 diag( 'The "Error permission denied" on /noway is on purpose' ) ;
8239 ok( 0 == do_valid_directory( '/noway'), 'do_valid_directory: /noway bad' ) ;
8240 }
8241
8242
8243 note( 'Leaving tests_do_valid_directory()' ) ;
8244 return ;
8245}
8246
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008247sub do_valid_directory
8248{
8249 my $dir = shift @ARG ;
8250
8251 # all good => return ok.
8252 return( 1 ) if ( -d $dir and -r _ and -w _ ) ;
8253
8254 # exist but bad
8255 if ( -e $dir and not -d _ ) {
8256 myprint( "Error: $dir exists but is not a directory\n" ) ;
8257 return( 0 ) ;
8258 }
8259 if ( -e $dir and not -w _ ) {
8260 my $sb = stat $dir ;
8261 myprintf( "Error: directory %s is not writable for user %s, permissions are %04o and owner is %s ( uid %s )\n",
8262 $dir, getpwuid_any_os( $EFFECTIVE_USER_ID ), ($sb->mode & oct($PERMISSION_FILTER) ), getpwuid_any_os( $sb->uid ), $sb->uid( ) ) ;
8263 return( 0 ) ;
8264 }
8265 # Trying to create it
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008266 myprint( "Creating directory $dir (current directory is " . getcwd( ) . ")\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008267 if ( ! eval { mkpath( $dir ) } ) {
8268 myprint( "$EVAL_ERROR" ) if ( $EVAL_ERROR ) ;
8269 }
8270 return( 1 ) if ( -d $dir and -r _ and -w _ ) ;
8271 return( 0 ) ;
8272}
8273
8274
8275sub tests_match_a_pid_number
8276{
8277 note( 'Entering tests_match_a_pid_number()' ) ;
8278
8279 is( undef, match_a_pid_number( ), 'match_a_pid_number: no args => undef' ) ;
8280 is( undef, match_a_pid_number( q{} ), 'match_a_pid_number: "" => undef' ) ;
8281 is( undef, match_a_pid_number( 'lalala' ), 'match_a_pid_number: lalala => undef' ) ;
8282 is( 1, match_a_pid_number( 1 ), 'match_a_pid_number: 1 => 1' ) ;
8283 is( 1, match_a_pid_number( 123 ), 'match_a_pid_number: 123 => 1' ) ;
8284 is( 1, match_a_pid_number( -123 ), 'match_a_pid_number: -123 => 1' ) ;
8285 is( 1, match_a_pid_number( '123' ), 'match_a_pid_number: "123" => 1' ) ;
8286 is( 1, match_a_pid_number( '-123' ), 'match_a_pid_number: "-123" => 1' ) ;
8287 is( undef, match_a_pid_number( 'a123' ), 'match_a_pid_number: a123 => undef' ) ;
8288 is( undef, match_a_pid_number( '-a123' ), 'match_a_pid_number: -a123 => undef' ) ;
8289 is( 1, match_a_pid_number( 99999 ), 'match_a_pid_number: 99999 => 1' ) ;
8290 is( 1, match_a_pid_number( -99999 ), 'match_a_pid_number: -99999 => 1' ) ;
8291 is( undef, match_a_pid_number( 0 ), 'match_a_pid_number: 0 => undef' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008292 is( 1, match_a_pid_number( 100000 ), 'match_a_pid_number: 100000 => 1' ) ;
8293 is( 1, match_a_pid_number( 123456 ), 'match_a_pid_number: 123456 => 1' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008294 is( undef, match_a_pid_number( '-0' ), 'match_a_pid_number: "-0" => undef' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008295 is( 1, match_a_pid_number( -100000 ), 'match_a_pid_number: -100000 => 1' ) ;
8296 is( 1, match_a_pid_number( -123456 ), 'match_a_pid_number: -123456 => 1' ) ;
8297 is( 1, match_a_pid_number( 2**22 ), 'match_a_pid_number: 2**22 => 1' ) ;
8298 is( undef, match_a_pid_number( 2**22 + 1 ), 'match_a_pid_number: 2**22 + 1 => undef' ) ;
8299 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 +01008300
8301 note( 'Leaving tests_match_a_pid_number()' ) ;
8302 return ;
8303}
8304
8305sub match_a_pid_number
8306{
8307 my $pid = shift @ARG ;
8308 if ( ! defined $pid ) { return ; }
8309 #print "$pid\n" ;
8310 if ( ! match( $pid, '^-?\d+$' ) ) { return ; }
8311 #print "$pid\n" ;
8312 # can be negative on Windows
8313 #if ( 0 > $pid ) { return ; }
8314 #if ( 65535 < $pid ) { return ; }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008315 if ( 2**22 < abs( $pid ) ) { return ; }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008316 if ( 0 == abs( $pid ) ) { return ; }
8317 return 1 ;
8318}
8319
8320sub tests_remove_pidfile_not_running
8321{
8322 note( 'Entering tests_remove_pidfile_not_running()' ) ;
8323
8324 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'remove_pidfile_not_running: mkpath W/tmp/tests/' ) ;
8325 is( undef, remove_pidfile_not_running( ), 'remove_pidfile_not_running: no args => undef' ) ;
8326 is( undef, remove_pidfile_not_running( './W' ), 'remove_pidfile_not_running: a dir => undef' ) ;
8327 is( undef, remove_pidfile_not_running( 'noexists' ), 'remove_pidfile_not_running: noexists => undef' ) ;
8328 is( 1, touch( 'W/tmp/tests/empty.pid' ), 'remove_pidfile_not_running: prepa empty W/tmp/tests/empty.pid' ) ;
8329 is( undef, remove_pidfile_not_running( 'W/tmp/tests/empty.pid' ), 'remove_pidfile_not_running: W/tmp/tests/empty.pid => undef' ) ;
8330 is( 'lalala', string_to_file( 'lalala', 'W/tmp/tests/lalala.pid' ), 'remove_pidfile_not_running: prepa W/tmp/tests/lalala.pid' ) ;
8331 is( undef, remove_pidfile_not_running( 'W/tmp/tests/lalala.pid' ), 'remove_pidfile_not_running: W/tmp/tests/lalala.pid => undef' ) ;
8332 is( '55555', string_to_file( '55555', 'W/tmp/tests/notrunning.pid' ), 'remove_pidfile_not_running: prepa W/tmp/tests/notrunning.pid' ) ;
8333 is( 1, remove_pidfile_not_running( 'W/tmp/tests/notrunning.pid' ), 'remove_pidfile_not_running: W/tmp/tests/notrunning.pid => 1' ) ;
8334 is( $PROCESS_ID, string_to_file( $PROCESS_ID, 'W/tmp/tests/running.pid' ), 'remove_pidfile_not_running: prepa W/tmp/tests/running.pid' ) ;
8335 is( undef, remove_pidfile_not_running( 'W/tmp/tests/running.pid' ), 'remove_pidfile_not_running: W/tmp/tests/running.pid => undef' ) ;
8336
8337 note( 'Leaving tests_remove_pidfile_not_running()' ) ;
8338 return ;
8339}
8340
8341sub remove_pidfile_not_running
8342{
8343 #
8344 my $pid_filename = shift @ARG ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008345
8346 #myprint( "In remove_pidfile_not_running $pid_filename\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008347 if ( ! $pid_filename ) { myprint( "No variable pid_filename\n" ) ; return } ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008348 if ( ! -e $pid_filename )
8349 {
8350 myprint( "File $pid_filename does not exist\n" ) ;
8351 return ;
8352 }
8353 #myprint( "Still In remove_pidfile_not_running $pid_filename\n" ) ;
8354
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008355 if ( ! -f $pid_filename ) { myprint( "File $pid_filename is not a file\n" ) ; return } ;
8356
8357 my $pid = firstline( $pid_filename ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008358 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 +01008359 # can't kill myself => do nothing
8360 if ( ! kill 'ZERO', $PROCESS_ID ) { myprint( "Can not kill ZERO myself $PROCESS_ID\n" ) ; return } ;
8361
8362 # can't kill ZERO the pid => it is gone or own by another user => remove pidfile
8363 if ( ! kill 'ZERO', $pid ) {
8364 myprint( "Removing old $pid_filename since its PID $pid is not running anymore (oo-killed?)\n" ) ;
8365 if ( unlink $pid_filename ) {
8366 myprint( "Removed old $pid_filename\n" ) ;
8367 return 1 ;
8368 }else{
8369 myprint( "Could not remove old $pid_filename because $!\n" ) ;
8370 return ;
8371 }
8372 }
8373 myprint( "Another imapsync process $pid is running as says pidfile $pid_filename\n" ) ;
8374 return ;
8375}
8376
8377
8378sub tests_tail
8379{
8380 note( 'Entering tests_tail()' ) ;
8381
8382 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'tail: mkpath W/tmp/tests/' ) ;
8383 ok( ( ! -e 'W/tmp/tests/tail.pid' || unlink 'W/tmp/tests/tail.pid' ), 'tail: unlink W/tmp/tests/tail.pid' ) ;
8384 ok( ( ! -e 'W/tmp/tests/tail.txt' || unlink 'W/tmp/tests/tail.txt' ), 'tail: unlink W/tmp/tests/tail.txt' ) ;
8385
8386 is( undef, tail( ), 'tail: no args => undef' ) ;
8387 my $mysync ;
8388 is( undef, tail( $mysync ), 'tail: no pidfile => undef' ) ;
8389
8390 $mysync->{pidfile} = 'W/tmp/tests/tail.pid' ;
8391 is( undef, tail( $mysync ), 'tail: no pidfilelocking => undef' ) ;
8392
8393 $mysync->{pidfilelocking} = 1 ;
8394 is( undef, tail( $mysync ), 'tail: pidfile no exists => undef' ) ;
8395
8396
8397 my $pidandlog = "33333\nW/tmp/tests/tail.txt\n" ;
8398 is( $pidandlog, string_to_file( $pidandlog, $mysync->{pidfile} ), 'tail: put pid 33333 and tail.txt in pidfile' ) ;
8399 is( undef, tail( $mysync ), 'tail: logfile to tail no exists => undef' ) ;
8400
8401 my $tailcontent = "L1\nL2\nL3\nL4\nL5\n" ;
8402 is( $tailcontent, string_to_file( $tailcontent, 'W/tmp/tests/tail.txt' ),
8403 'tail: put L1\nL2\nL3\nL4\nL5\n in W/tmp/tests/tail.txt' ) ;
8404
8405 is( undef, tail( $mysync ), 'tail: fake pid in pidfile + tail off => 1' ) ;
8406
8407 $mysync->{ tail } = 1 ;
8408 is( 1, tail( $mysync ), 'tail: fake pid in pidfile + tail on=> 1' ) ;
8409
8410 # put my own pid, won't do tail
8411 $pidandlog = "$PROCESS_ID\nW/tmp/tests/tail.txt\n" ;
8412 is( $pidandlog, string_to_file( $pidandlog, $mysync->{pidfile} ), 'tail: put my own PID in pidfile' ) ;
8413 is( undef, tail( $mysync ), 'tail: my own pid in pidfile => undef' ) ;
8414
8415 note( 'Leaving tests_tail()' ) ;
8416 return ;
8417}
8418
8419
8420
8421sub tail
8422{
8423 # return undef on failures
8424 # return 1 on success
8425
8426 my $mysync = shift ;
8427
8428 # no tail when aborting!
8429 if ( $mysync->{ abort } ) { return ; }
8430
8431 my $pidfile = $mysync->{pidfile} ;
8432 my $lock = $mysync->{pidfilelocking} ;
8433 my $tail = $mysync->{tail} ;
8434
8435 if ( ! $pidfile ) { return ; }
8436 if ( ! $lock ) { return ; }
8437 if ( ! $tail ) { return ; }
8438
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008439 if ( ! -e $pidfile ) { return ; }
8440
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008441 my $pidtotail = firstline( $pidfile ) ;
8442 if ( ! $pidtotail ) { return ; }
8443
8444
8445
8446 # It should not happen but who knows...
8447 if ( $pidtotail eq $PROCESS_ID ) { return ; }
8448
8449
8450 my $filetotail = secondline( $pidfile ) ;
8451 if ( ! $filetotail ) { return ; }
8452
8453 if ( ! -r $filetotail )
8454 {
8455 #myprint( "Error: can not read $filetotail\n" ) ;
8456 return ;
8457 }
8458
8459 myprint( "Doing a tail -f on $filetotail for processus pid $pidtotail until it is finished.\n" ) ;
8460 my $file = File::Tail->new(
8461 name => $filetotail,
8462 nowait => 1,
8463 interval => 1,
8464 tail => 1,
8465 adjustafter => 2
8466 );
8467
8468 my $moretimes = 200 ;
8469 # print one line at least
8470 my $line = $file->read ;
8471 myprint( $line ) ;
8472 while ( isrunning( $pidtotail, \$moretimes ) and defined( $line = $file->read ) )
8473 {
8474 myprint( $line );
8475 sleep( 0.02 ) ;
8476 }
8477
8478 return 1 ;
8479}
8480
8481sub isrunning
8482{
8483 my $pidtocheck = shift ;
8484 my $moretimes_ref = shift ;
8485
8486 if ( kill 'ZERO', $pidtocheck )
8487 {
8488 #myprint( "$pidtocheck running\n" ) ;
8489 return 1 ;
8490 }
8491 elsif ( $$moretimes_ref >= 0 )
8492 {
8493 # continue to consider it running
8494 $$moretimes_ref-- ;
8495 return 1 ;
8496 }
8497 else
8498 {
8499 myprint( "Tailed processus $pidtocheck ended\n" ) ;
8500 return ;
8501 }
8502}
8503
8504sub tests_write_pidfile
8505{
8506 note( 'Entering tests_write_pidfile()' ) ;
8507
8508 my $mysync ;
8509
8510 is( 1, write_pidfile( ), 'write_pidfile: no args => 1' ) ;
8511
8512 # no pidfile => ok
8513 $mysync->{pidfile} = q{} ;
8514 is( 1, write_pidfile( $mysync ), 'write_pidfile: no pidfile => undef' ) ;
8515
8516 # The pidfile path is bad => failure
8517 $mysync->{pidfile} = '/no/no/no.pid' ;
8518 is( undef, write_pidfile( $mysync ), 'write_pidfile: no permission for /no/no/no.pid, no lock => undef' ) ;
8519
8520 $mysync->{pidfilelocking} = 1 ;
8521 is( undef, write_pidfile( $mysync ), 'write_pidfile: no permission for /no/no/no.pid + lock => undef' ) ;
8522
8523 $mysync->{pidfile} = 'W/tmp/tests/test.pid' ;
8524 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'write_pidfile: mkpath W/tmp/tests/' ) ;
8525 is( 1, touch( $mysync->{pidfile} ), 'write_pidfile: lock prepa' ) ;
8526
8527 $mysync->{pidfilelocking} = 0 ;
8528 is( 1, write_pidfile( $mysync ), 'write_pidfile: W/tmp/tests/test.pid + no lock => 1' ) ;
8529 is( $PROCESS_ID, firstline( 'W/tmp/tests/test.pid' ), "write_pidfile: W/tmp/tests/test.pid contains $PROCESS_ID" ) ;
8530 is( q{}, secondline( 'W/tmp/tests/test.pid' ), "write_pidfile: W/tmp/tests/test.pid contains no second line" ) ;
8531
8532 $mysync->{pidfilelocking} = 1 ;
8533 is( undef, write_pidfile( $mysync ), 'write_pidfile: W/tmp/tests/test.pid + lock => undef' ) ;
8534
8535
8536 $mysync->{pidfilelocking} = 0 ;
8537 $mysync->{ logfile } = 'rrrr.txt' ;
8538 is( 1, write_pidfile( $mysync ), 'write_pidfile: W/tmp/tests/test.pid + no lock + logfile => 1' ) ;
8539 is( $PROCESS_ID, firstline( 'W/tmp/tests/test.pid' ), "write_pidfile: + no lock + logfile W/tmp/tests/test.pid contains $PROCESS_ID" ) ;
8540 is( q{rrrr.txt}, secondline( 'W/tmp/tests/test.pid' ), "write_pidfile: + no lock + logfile W/tmp/tests/test.pid contains rrrr.txt" ) ;
8541
8542
8543 note( 'Leaving tests_write_pidfile()' ) ;
8544 return ;
8545}
8546
8547
8548
8549sub write_pidfile
8550{
8551 # returns undef if something is considered fatal
8552 # returns 1 otherwise
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008553
8554 #myprint( "In write_pidfile\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008555 if ( ! @ARG ) { return 1 ; }
8556
8557 my $mysync = shift @ARG ;
8558
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008559 # Do not write the pid file if the current process goal is to abort the process designed by the pid file
8560 if ( $mysync->{ abort } ) { return 1 ; }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008561
8562 #
8563 my $pid_filename = $mysync->{ pidfile } ;
8564 my $lock = $mysync->{ pidfilelocking } ;
8565
8566 if ( ! $pid_filename )
8567 {
8568 myprint( "PID file is unset ( to set it, use --pidfile filepath ; to avoid it use --pidfile \"\" )\n" ) ;
8569 return( 1 ) ;
8570 }
8571
8572 myprint( "PID file is $pid_filename ( to change it, use --pidfile filepath ; to avoid it use --pidfile \"\" )\n" ) ;
8573 if ( -e $pid_filename and $lock ) {
8574 myprint( "$pid_filename already exists, another imapsync may be curently running. Aborting imapsync.\n" ) ;
8575 return ;
8576
8577 }
8578
8579 if ( -e $pid_filename ) {
8580 myprint( "$pid_filename already exists, overwriting it ( use --pidfilelocking to avoid concurrent runs )\n" ) ;
8581 }
8582
8583 my $pid_string = "$PROCESS_ID\n" ;
8584 my $pid_message = "Writing my PID $PROCESS_ID in $pid_filename\n" ;
8585
8586 if ( $mysync->{ logfile } )
8587 {
8588 $pid_string .= "$mysync->{ logfile }\n" ;
8589 $pid_message .= "Writing also my logfile name in $pid_filename : $mysync->{ logfile }\n" ;
8590 }
8591
8592 if ( open my $FILE_HANDLE, '>', $pid_filename ) {
8593 myprint( $pid_message ) ;
8594 print $FILE_HANDLE $pid_string ;
8595 close $FILE_HANDLE ;
8596 return( 1 ) ;
8597 }
8598 else
8599 {
8600 myprint( "Could not open $pid_filename for writing. Check permissions or disk space: $OS_ERROR\n" ) ;
8601 return ;
8602 }
8603}
8604
8605
8606sub fix_Inbox_INBOX_mapping
8607{
8608 my( $h1_all, $h2_all ) = @_ ;
8609
8610 my $regex = q{} ;
8611 SWITCH: {
8612 if ( exists $h1_all->{INBOX} and exists $h2_all->{INBOX} ) { $regex = q{} ; last SWITCH ; } ;
8613 if ( exists $h1_all->{Inbox} and exists $h2_all->{Inbox} ) { $regex = q{} ; last SWITCH ; } ;
8614 if ( exists $h1_all->{INBOX} and exists $h2_all->{Inbox} ) { $regex = q{s/^INBOX$/Inbox/x} ; last SWITCH ; } ;
8615 if ( exists $h1_all->{Inbox} and exists $h2_all->{INBOX} ) { $regex = q{s/^Inbox$/INBOX/x} ; last SWITCH ; } ;
8616 } ;
8617 return( $regex ) ;
8618}
8619
8620sub tests_fix_Inbox_INBOX_mapping
8621{
8622 note( 'Entering tests_fix_Inbox_INBOX_mapping()' ) ;
8623
8624
8625 my( $h1_all, $h2_all ) ;
8626
8627 $h1_all = { 'INBOX' => q{} } ;
8628 $h2_all = { 'INBOX' => q{} } ;
8629 ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX INBOX' ) ;
8630
8631 $h1_all = { 'Inbox' => q{} } ;
8632 $h2_all = { 'Inbox' => q{} } ;
8633 ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: Inbox Inbox' ) ;
8634
8635 $h1_all = { 'INBOX' => q{} } ;
8636 $h2_all = { 'Inbox' => q{} } ;
8637 ok( q{s/^INBOX$/Inbox/x} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX Inbox' ) ;
8638
8639 $h1_all = { 'Inbox' => q{} } ;
8640 $h2_all = { 'INBOX' => q{} } ;
8641 ok( q{s/^Inbox$/INBOX/x} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: Inbox INBOX' ) ;
8642
8643 $h1_all = { 'INBOX' => q{} } ;
8644 $h2_all = { 'rrrrr' => q{} } ;
8645 ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX rrrrrr' ) ;
8646
8647 $h1_all = { 'rrrrr' => q{} } ;
8648 $h2_all = { 'Inbox' => q{} } ;
8649 ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: rrrrr Inbox' ) ;
8650
8651 note( 'Leaving tests_fix_Inbox_INBOX_mapping()' ) ;
8652 return ;
8653}
8654
8655
8656sub jux_utf8_list
8657{
8658 my @s_inp = @_ ;
8659 my $s_out = q{} ;
8660 foreach my $s ( @s_inp ) {
8661 $s_out .= jux_utf8( $s ) . "\n" ;
8662 }
8663 return( $s_out ) ;
8664}
8665
8666sub tests_jux_utf8_list
8667{
8668 note( 'Entering tests_jux_utf8_list()' ) ;
8669
8670 use utf8 ;
8671 is( q{}, jux_utf8_list( ), 'jux_utf8_list: void' ) ;
8672 is( "[]\n", jux_utf8_list( q{} ), 'jux_utf8_list: empty string' ) ;
8673 is( "[INBOX]\n", jux_utf8_list( 'INBOX' ), 'jux_utf8_list: INBOX' ) ;
8674 is( "[&ANY-] = [Ö]\n", jux_utf8_list( '&ANY-' ), 'jux_utf8_list: [&ANY-] = [Ö]' ) ;
8675
8676 note( 'Leaving tests_jux_utf8_list()' ) ;
8677 return( 0 ) ;
8678}
8679
8680# editing utf8 can be tricky without an utf8 editor
8681sub tests_jux_utf8_old
8682{
8683 note( 'Entering tests_jux_utf8_old()' ) ;
8684
8685 no utf8 ;
8686
8687 is( '[]', jux_utf8_old( q{} ), 'jux_utf8_old: void => []' ) ;
8688 is( '[INBOX]', jux_utf8_old( 'INBOX'), 'jux_utf8_old: INBOX => [INBOX]' ) ;
8689 is( '[&ZTZO9nux-] = [收件箱]', jux_utf8_old( '&ZTZO9nux-'), 'jux_utf8_old: => [&ZTZO9nux-] = [收件箱]' ) ;
8690 is( '[&ANY-] = [Ö]', jux_utf8_old( '&ANY-'), 'jux_utf8_old: &ANY- => [&ANY-] = [Ö]' ) ;
8691 # +BD8EQAQ1BDQEOwQ+BDM- SHOULD stay as is!
8692 is( '[+BD8EQAQ1BDQEOwQ+BDM-] = [предлог]', jux_utf8_old( '+BD8EQAQ1BDQEOwQ+BDM-' ), 'jux_utf8_old: => [+BD8EQAQ1BDQEOwQ+BDM-] = [предлог]' ) ;
8693 is( '[&BB8EQAQ+BDUEOgRC-] = [Проект]', jux_utf8_old( '&BB8EQAQ+BDUEOgRC-' ), 'jux_utf8_old: => [&BB8EQAQ+BDUEOgRC-] = [Проект]' ) ;
8694
8695 note( 'Leaving tests_jux_utf8_old()' ) ;
8696 return ;
8697}
8698
8699sub jux_utf8_old
8700{
8701 # juxtapose utf8 at the right if different
8702 my ( $s_utf7 ) = shift ;
8703 my ( $s_utf8 ) = imap_utf7_decode_old( $s_utf7 ) ;
8704
8705 if ( $s_utf7 eq $s_utf8 ) {
8706 #myprint( "[$s_utf7]\n" ) ;
8707 return( "[$s_utf7]" ) ;
8708 }else{
8709 #myprint( "[$s_utf7] = [$s_utf8]\n" ) ;
8710 return( "[$s_utf7] = [$s_utf8]" ) ;
8711 }
8712}
8713
8714# Copied from http://cpansearch.perl.org/src/FABPOT/Unicode-IMAPUtf7-2.01/lib/Unicode/IMAPUtf7.pm
8715# and then fixed with
8716# https://rt.cpan.org/Public/Bug/Display.html?id=11172
8717sub imap_utf7_decode_old
8718{
8719 my ( $s ) = shift ;
8720
8721 # Algorithm
8722 # On remplace , par / dans les BASE 64 (, entre & et -)
8723 # On remplace les &, non suivi d'un - par +
8724 # On remplace les &- par &
8725 $s =~ s/&([^,&\-]*),([^,\-&]*)\-/&$1\/$2\-/xg ;
8726 $s =~ s/&(?!\-)/\+/xg ;
8727 $s =~ s/&\-/&/xg ;
8728 return( Unicode::String::utf7( $s )->utf8 ) ;
8729}
8730
8731
8732
8733
8734
8735sub tests_jux_utf8
8736{
8737 note( 'Entering tests_jux_utf8()' ) ;
8738 #no utf8 ;
8739 use utf8 ;
8740
8741 #binmode STDOUT, ":encoding(UTF-8)" ;
8742 binmode STDERR, ":encoding(UTF-8)" ;
8743
8744 # This test is because the binary can fail on it, a PAR.pm issue.
8745 # The failure was with the underlying Encode::IMAPUTF7 module line 66 release 1.05
8746 # Was solved by including Encode in imapsync and using "pp -x".
8747 ok( find_encoding( "UTF-16BE"), 'jux_utf8: Encode::find_encoding: UTF-16BE' ) ;
8748
8749 #
8750 is( '[]', jux_utf8( q{} ), 'jux_utf8: void => []' ) ;
8751 is( '[INBOX]', jux_utf8( 'INBOX'), 'jux_utf8: INBOX => [INBOX]' ) ;
8752 is( '[&ANY-] = [Ö]', jux_utf8( '&ANY-'), 'jux_utf8: &ANY- => [&ANY-] = [Ö]' ) ;
8753 # +BD8EQAQ1BDQEOwQ+BDM- must stay as is
8754 is( '[+BD8EQAQ1BDQEOwQ+BDM-]', jux_utf8( '+BD8EQAQ1BDQEOwQ+BDM-' ), 'jux_utf8: => [+BD8EQAQ1BDQEOwQ+BDM-] = [+BD8EQAQ1BDQEOwQ+BDM-]' ) ;
8755 is( '[&BB8EQAQ+BDUEOgRC-] = [Проект]', jux_utf8( '&BB8EQAQ+BDUEOgRC-' ), 'jux_utf8: => [&BB8EQAQ+BDUEOgRC-] = [Проект]' ) ;
8756
8757 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]' ) ;
8758 my $str = Encode::IMAPUTF7::encode("IMAP-UTF-7", 'Réponses 1200+1201+1202' ) ;
8759 is( '[R&AOk-ponses 1200+1201+1202] = [Réponses 1200+1201+1202]', jux_utf8( $str ), "jux_utf8: [$str] = [Réponses 1200+1201+1202]" ) ;
8760
8761 is( '[INBOX.&AOkA4ADnAPk-&-*] = [INBOX.éà çù&*]', jux_utf8( 'INBOX.&AOkA4ADnAPk-&-*' ), "jux_utf8: [INBOX.&AOkA4ADnAPk-&-*] = [INBOX.éà çù&*]" ) ;
8762
8763 is( '[&ZTZO9nux-] = [收件箱]', jux_utf8( '&ZTZO9nux-'), 'jux_utf8: => [&ZTZO9nux-] = [收件箱]' ) ;
8764 #
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008765 #
8766 is( '[!Old Emails]', jux_utf8( '!Old Emails'), 'jux_utf8: !Old Emails => [!Old Emails]' ) ;
8767 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 +01008768 note( 'Leaving tests_jux_utf8()' ) ;
8769 return ;
8770}
8771
8772sub jux_utf8
8773{
8774 #use utf8 ;
8775 # juxtapose utf8 at the right if different
8776 my ( $s_utf7 ) = shift ;
8777 my ( $s_utf8 ) = imap_utf7_decode( $s_utf7 ) ;
8778
8779 if ( $s_utf7 eq $s_utf8 ) {
8780 #myprint( "[$s_utf7]\n" ) ;
8781 return( "[$s_utf7]" ) ;
8782 }else{
8783 #myprint( "[$s_utf7] = [$s_utf8]\n" ) ;
8784 return( "[$s_utf7] = [$s_utf8]" ) ;
8785 }
8786}
8787
8788sub imap_utf7_decode
8789{
8790 #use utf8 ;
8791 my ( $s ) = shift ;
8792 return( Encode::IMAPUTF7::decode("IMAP-UTF-7", $s ) ) ;
8793}
8794
8795sub imap_utf7_encode
8796{
8797 #use utf8 ;
8798 my ( $s ) = shift ;
8799 return( Encode::IMAPUTF7::encode("IMAP-UTF-7", $s ) ) ;
8800}
8801
8802
8803
8804sub imap_utf7_encode_old
8805{
8806 my ( $s ) = @_ ;
8807
8808 $s = Unicode::String::utf8( $s )->utf7 ;
8809
8810 $s =~ s/\+([^\/&\-]*)\/([^\/\-&]*)\-/\+$1,$2\-/xg ;
8811 $s =~ s/&/&\-/xg ;
8812 $s =~ s/\+([^+\-]+)?\-/&$1\-/xg ;
8813 return( $s ) ;
8814}
8815
8816
8817
8818
8819sub select_folder
8820{
8821 my ( $mysync, $imap, $folder, $hostside ) = @_ ;
8822 if ( ! $imap->select( $folder ) ) {
8823 my $error = join q{},
8824 "$hostside folder $folder: Could not select: ",
8825 $imap->LastError, "\n" ;
8826 errors_incr( $mysync, $error ) ;
8827 return( 0 ) ;
8828 }else{
8829 # ok select succeeded
8830 return( 1 ) ;
8831 }
8832}
8833
8834sub examine_folder
8835{
8836 my ( $mysync, $imap, $folder, $hostside ) = @_ ;
8837 if ( ! $imap->examine( $folder ) ) {
8838 my $error = join q{},
8839 "$hostside folder $folder: Could not examine: ",
8840 $imap->LastError, "\n" ;
8841 errors_incr( $mysync, $error ) ;
8842 return( 0 ) ;
8843 }else{
8844 # ok select succeeded
8845 return( 1 ) ;
8846 }
8847}
8848
8849
8850sub count_from_select
8851{
8852 my @lines = @ARG ;
8853 my $count ;
8854 foreach my $line ( @lines ) {
8855 #myprint( "line = [$line]\n" ) ;
8856 if ( $line =~ m/^\*\s+(\d+)\s+EXISTS/x ) {
8857 $count = $1 ;
8858 return( $count ) ;
8859 }
8860 }
8861 return( undef ) ;
8862}
8863
8864
8865
8866sub create_folder_old
8867{
8868 my $mysync = shift @ARG ;
8869 my( $imap, $h2_fold, $h1_fold ) = @ARG ;
8870
8871 myprint( "Creating (old way) folder [$h2_fold] on host2\n" ) ;
8872 if ( ( 'INBOX' eq uc $h2_fold )
8873 and ( $imap->exists( $h2_fold ) ) ) {
8874 myprint( "Folder [$h2_fold] already exists\n" ) ;
8875 return( 1 ) ;
8876 }
8877 if ( ! $mysync->{dry} ){
8878 if ( ! $imap->create( $h2_fold ) ) {
8879 my $error = join q{},
8880 "Could not create folder [$h2_fold] from [$h1_fold]: ",
8881 $imap->LastError( ), "\n" ;
8882 errors_incr( $mysync, $error ) ;
8883 # success if folder exists ("already exists" error)
8884 return( 1 ) if $imap->exists( $h2_fold ) ;
8885 # failure since create failed
8886 return( 0 ) ;
8887 }else{
8888 #create succeeded
8889 myprint( "Created ( the old way ) folder [$h2_fold] on host2\n" ) ;
8890 return( 1 ) ;
8891 }
8892 }else{
8893 # dry mode, no folder so many imap will fail, assuming failure
8894 myprint( "Created ( the old way ) folder [$h2_fold] on host2 $mysync->{dry_message}\n" ) ;
8895 return( 0 ) ;
8896 }
8897}
8898
8899
8900sub create_folder
8901{
8902 my $mysync = shift @ARG ;
8903 my( $myimap2 , $h2_fold , $h1_fold ) = @ARG ;
8904 my( @parts , $parent ) ;
8905
8906 if ( $myimap2->IsUnconnected( ) ) {
8907 myprint( "Host2: Unconnected state\n" ) ;
8908 return( 0 ) ;
8909 }
8910
8911 if ( $create_folder_old ) {
8912 return( create_folder_old( $mysync, $myimap2 , $h2_fold , $h1_fold ) ) ;
8913 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008914
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008915 myprint( "Creating folder [$h2_fold] on host2\n" ) ;
8916 if ( ( 'INBOX' eq uc $h2_fold )
8917 and ( $myimap2->exists( $h2_fold ) ) ) {
8918 myprint( "Folder [$h2_fold] already exists\n" ) ;
8919 return( 1 ) ;
8920 }
8921
8922 if ( $mixfolders and $myimap2->exists( $h2_fold ) ) {
8923 myprint( "Folder [$h2_fold] already exists (--nomixfolders is not set)\n" ) ;
8924 return( 1 ) ;
8925 }
8926
8927
8928 if ( ( not $mixfolders ) and ( $myimap2->exists( $h2_fold ) ) ) {
8929 myprint( "Folder [$h2_fold] already exists and --nomixfolders is set\n" ) ;
8930 return( 0 ) ;
8931 }
8932
8933 @parts = split /\Q$mysync->{ h2_sep }\E/x, $h2_fold ;
8934 pop @parts ;
8935 $parent = join $mysync->{ h2_sep }, @parts ;
8936 $parent =~ s/^\s+|\s+$//xg ;
8937 if ( ( $parent ne q{} ) and ( ! $myimap2->exists( $parent ) ) ) {
8938 create_folder( $mysync, $myimap2 , $parent , $h1_fold ) ;
8939 }
8940
8941 if ( ! $mysync->{dry} ) {
8942 if ( ! $myimap2->create( $h2_fold ) ) {
8943 my $error = join q{},
8944 "Could not create folder [$h2_fold] from [$h1_fold]: " ,
8945 $myimap2->LastError( ), "\n" ;
8946 errors_incr( $mysync, $error ) ;
8947 # success if folder exists ("already exists" error)
8948 return( 1 ) if $myimap2->exists( $h2_fold ) ;
8949 # failure since create failed
8950 return( 0 ) ;
8951 }else{
8952 #create succeeded
8953 myprint( "Created folder [$h2_fold] on host2\n" ) ;
8954 return( 1 ) ;
8955 }
8956 }else{
8957 # dry mode, no folder so many imap will fail, assuming failure
8958 myprint( "Created folder [$h2_fold] on host2 $mysync->{dry_message}\n" ) ;
8959 if ( ! $mysync->{ justfolders } ) {
8960 myprint( "Since --dry mode is on and folder [$h2_fold] on host2 does not exist yet, syncing messages will not be simulated.\n"
8961 . "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 +02008962 # The messages that could be transferred are counted and the number is given at the end.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008963 }
8964 return( 0 ) ;
8965 }
8966}
8967
8968
8969
8970sub tests_folder_routines
8971{
8972 note( 'Entering tests_folder_routines()' ) ;
8973
8974 ok( !is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 1' );
8975 ok( add_to_requested_folders('folder_foo'), 'add_to_requested_folders folder_foo' );
8976 ok( is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 2' );
8977 ok( !is_requested_folder('folder_NO_EXIST'), 'is_requested_folder folder_NO_EXIST' );
8978
8979 is_deeply( [ 'folder_foo' ], [ remove_from_requested_folders( 'folder_foo' ) ], 'removed folder_foo => folder_foo' ) ;
8980 ok( !is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 3' );
8981 my @f ;
8982 ok( @f = add_to_requested_folders('folder_bar', 'folder_toto'), "add result: @f" );
8983 ok( is_requested_folder('folder_bar'), 'is_requested_folder 4' );
8984 ok( is_requested_folder('folder_toto'), 'is_requested_folder 5' );
8985 ok( remove_from_requested_folders('folder_toto'), 'remove_from_requested_folders: ' );
8986 ok( !is_requested_folder('folder_toto'), 'is_requested_folder 6' );
8987
8988 is_deeply( [ 'folder_bar' ], [ remove_from_requested_folders('folder_bar') ], 'remove_from_requested_folders: empty' ) ;
8989
8990 ok( 0 == compare_lists( [ sort_requested_folders( ) ], [] ), 'sort_requested_folders: all empty' ) ;
8991 ok( add_to_requested_folders( 'A_99', 'M_55', 'Z_11' ), 'add_to_requested_folders M_55 Z_11' );
8992 ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'A_99', 'M_55', 'Z_11' ] ), 'sort_requested_folders: middle' ) ;
8993
8994
8995 @folderfirst = ( 'Z_11' ) ;
8996
8997 ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'A_99', 'M_55' ] ), 'sort_requested_folders: first+middle' ) ;
8998
8999 is_deeply( [ 'Z_11', 'A_99', 'M_55' ], [ sort_requested_folders( ) ], 'sort_requested_folders: first+middle is_deeply' ) ;
9000
9001 @folderlast = ( 'A_99' ) ;
9002 ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'M_55', 'A_99' ] ), 'sort_requested_folders: first+middle+last 1' ) ;
9003
9004 ok( add_to_requested_folders('M_55', 'M_44',), 'add_to_requested_folders M_55 M_44' ) ;
9005
9006 ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'M_44', 'M_55', 'A_99'] ), 'sort_requested_folders: first+middle+last 2' ) ;
9007
9008
9009 ok( add_to_requested_folders('A_88', 'Z_22',), 'add_to_requested_folders A_88 Z_22' ) ;
9010 @folderfirst = qw( Z_22 Z_11 ) ;
9011 @folderlast = qw( A_99 A_88 ) ;
9012 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' ) ;
9013 undef @folderfirst ;
9014 undef @folderlast ;
9015
9016 note( 'Leaving tests_folder_routines()' ) ;
9017 return ;
9018}
9019
9020
9021sub sort_requested_folders
9022{
9023 my @requested_folders_sorted = () ;
9024
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009025 $sync->{ debug } and myprint "folderfirst: @folderfirst\n" ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009026 my @folderfirst_requested = remove_from_requested_folders( @folderfirst ) ;
9027 #myprint "folderfirst_requested: @folderfirst_requested\n" ;
9028
9029 my @folderlast_requested = remove_from_requested_folders( @folderlast ) ;
9030
9031 my @middle = sort keys %requested_folder ;
9032
9033 @requested_folders_sorted = ( @folderfirst_requested, @middle, @folderlast_requested ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009034 $sync->{ debug } and myprint "requested_folders_sorted: @requested_folders_sorted\n" ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009035 add_to_requested_folders( @requested_folders_sorted ) ;
9036
9037 return( @requested_folders_sorted ) ;
9038}
9039
9040sub is_requested_folder
9041{
9042 my ( $folder ) = @_;
9043
9044 return( defined $requested_folder{ $folder } ) ;
9045}
9046
9047
9048sub add_to_requested_folders
9049{
9050 my @wanted_folders = @_ ;
9051
9052 foreach my $folder ( @wanted_folders ) {
9053 ++$requested_folder{ $folder } ;
9054 }
9055 return( keys %requested_folder ) ;
9056}
9057
9058sub tests_remove_from_requested_folders
9059{
9060 note( 'Entering tests_remove_from_requested_folders()' ) ;
9061
9062 is( undef, undef, 'remove_from_requested_folders: undef is undef' ) ;
9063 is_deeply( [], [ remove_from_requested_folders( ) ], 'remove_from_requested_folders: no args' ) ;
9064 %requested_folder = (
9065 'F1' => 1,
9066 ) ;
9067 is_deeply( [], [ remove_from_requested_folders( ) ], 'remove_from_requested_folders: remove nothing among F1 => nothing' ) ;
9068 is_deeply( [], [ remove_from_requested_folders( 'Fno' ) ], 'remove_from_requested_folders: remove Fno among F1 => nothing' ) ;
9069 is_deeply( [ 'F1' ], [ remove_from_requested_folders( 'F1' ) ], 'remove_from_requested_folders: remove F1 among F1 => F1' ) ;
9070 is_deeply( { }, { %requested_folder }, 'remove_from_requested_folders: remove F1 among F1 => %requested_folder emptied' ) ;
9071
9072 %requested_folder = (
9073 'F1' => 1,
9074 'F2' => 1,
9075 ) ;
9076 is_deeply( [], [ remove_from_requested_folders( ) ], 'remove_from_requested_folders: remove nothing among F1 F2 => nothing' ) ;
9077 is_deeply( [], [ remove_from_requested_folders( 'Fno' ) ], 'remove_from_requested_folders: remove Fno among F1 F2 => nothing' ) ;
9078 is_deeply( [ 'F1' ], [ remove_from_requested_folders( 'F1' ) ], 'remove_from_requested_folders: remove F1 among F1 F2 => F1' ) ;
9079 is_deeply( { 'F2' => 1 }, { %requested_folder }, 'remove_from_requested_folders: remove F1 among F1 F2 => %requested_folder F2' ) ;
9080
9081 is_deeply( [], [ remove_from_requested_folders( 'F1' ) ], 'remove_from_requested_folders: remove F1 among F2 => nothing' ) ;
9082 is_deeply( [ 'F2' ], [ remove_from_requested_folders( 'F1', 'F2' ) ], 'remove_from_requested_folders: remove F1 F2 among F2 => F2' ) ;
9083 is_deeply( {}, { %requested_folder }, 'remove_from_requested_folders: remove F1 among F1 F2 => %requested_folder F2' ) ;
9084
9085 %requested_folder = (
9086 'F1' => 1,
9087 'F2' => 1,
9088 'F3' => 1,
9089 ) ;
9090 is_deeply( [ 'F1', 'F2' ], [ remove_from_requested_folders( 'F1', 'F2' ) ], 'remove_from_requested_folders: remove F1 F2 among F1 F2 F3 => F1 F2' ) ;
9091 is_deeply( { 'F3' => 1 }, { %requested_folder }, 'remove_from_requested_folders: remove F1 F2 among F1 F2 F3 => %requested_folder F3' ) ;
9092
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009093 undef %requested_folder ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009094
9095 note( 'Leaving tests_remove_from_requested_folders()' ) ;
9096 return ;
9097}
9098
9099
9100sub remove_from_requested_folders
9101{
9102 my @unwanted_folders = @_ ;
9103
9104 my @removed_folders = () ;
9105 foreach my $folder ( @unwanted_folders ) {
9106 if ( exists $requested_folder{ $folder } )
9107 {
9108 delete $requested_folder{ $folder } ;
9109 push @removed_folders, $folder ;
9110 }
9111 }
9112 return( @removed_folders ) ;
9113}
9114
9115sub compare_lists
9116{
9117 my ($list_1_ref, $list_2_ref) = @_;
9118
9119 return($MINUS_ONE) if ((not defined $list_1_ref) and defined $list_2_ref);
9120 return(0) if ((not defined $list_1_ref) and not defined $list_2_ref); # end if no list
9121 return(1) if (not defined $list_2_ref); # end if only one list
9122
9123 if (not ref $list_1_ref ) {$list_1_ref = [$list_1_ref]};
9124 if (not ref $list_2_ref ) {$list_2_ref = [$list_2_ref]};
9125
9126
9127 my $last_used_indice = $MINUS_ONE;
9128
9129
9130 ELEMENT:
9131 foreach my $indice ( 0 .. $#{ $list_1_ref } ) {
9132 $last_used_indice = $indice ;
9133
9134 # End of list_2
9135 return 1 if ($indice > $#{ $list_2_ref } ) ;
9136
9137 my $element_list_1 = $list_1_ref->[$indice] ;
9138 my $element_list_2 = $list_2_ref->[$indice] ;
9139 my $balance = $element_list_1 cmp $element_list_2 ;
9140 next ELEMENT if ($balance == 0) ;
9141 return $balance ;
9142 }
9143 # each element equal until last indice of list_1
9144 return $MINUS_ONE if ($last_used_indice < $#{ $list_2_ref } ) ;
9145
9146 # same size, each element equal
9147 return 0 ;
9148}
9149
9150sub tests_compare_lists
9151{
9152 note( 'Entering tests_compare_lists()' ) ;
9153
9154 my $empty_list_ref = [];
9155
9156 ok( 0 == compare_lists() , 'compare_lists, no args');
9157 ok( 0 == compare_lists(undef) , 'compare_lists, undef = nothing');
9158 ok( 0 == compare_lists(undef, undef) , 'compare_lists, undef = undef');
9159 ok($MINUS_ONE == compare_lists(undef , []) , 'compare_lists, undef < []');
9160 ok($MINUS_ONE == compare_lists(undef , [1]) , 'compare_lists, undef < [1]');
9161 ok($MINUS_ONE == compare_lists(undef , [0]) , 'compare_lists, undef < [0]');
9162 ok(+1 == compare_lists([]) , 'compare_lists, [] > nothing');
9163 ok(+1 == compare_lists([], undef) , 'compare_lists, [] > undef');
9164 ok( 0 == compare_lists([] , []) , 'compare_lists, [] = []');
9165
9166 ok($MINUS_ONE == compare_lists([] , [1]) , 'compare_lists, [] < [1]');
9167 ok(+1 == compare_lists([1] , []) , 'compare_lists, [1] > []');
9168
9169
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009170 ok( 0 == compare_lists( [1], 1 ) , 'compare_lists, [1] = 1 ') ;
9171 ok( 0 == compare_lists( 1 , [1] ) , 'compare_lists, 1 = [1]') ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009172 ok( 0 == compare_lists( 1 , 1 ) , 'compare_lists, 1 = 1 ') ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009173 ok( $MINUS_ONE == compare_lists( 0 , 1 ) , 'compare_lists, 0 < 1 ') ;
9174 ok( $MINUS_ONE == compare_lists( $MINUS_ONE , 0 ) , 'compare_lists, -1 < 0 ') ;
9175 ok( $MINUS_ONE == compare_lists( 1 , 2 ) , 'compare_lists, 1 < 2 ') ;
9176 ok( +1 == compare_lists( 2 , 1 ) , 'compare_lists, 2 > 1 ') ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009177
9178
9179 ok( 0 == compare_lists([1,2], [1,2]) , 'compare_lists, [1,2] = [1,2]' ) ;
9180 ok($MINUS_ONE == compare_lists([1], [1,2]) , 'compare_lists, [1] < [1,2]' ) ;
9181 ok(+1 == compare_lists([2], [1,2]) , 'compare_lists, [2] > [1,2]' ) ;
9182 ok($MINUS_ONE == compare_lists([1], [1,1]) , 'compare_lists, [1] < [1,1]' ) ;
9183 ok(+1 == compare_lists([1, 1], [1]) , 'compare_lists, [1, 1] > [1]' ) ;
9184 ok( 0 == compare_lists([1 .. $NUMBER_20_000] , [1 .. $NUMBER_20_000])
9185 , 'compare_lists, [1..20_000] = [1..20_000]' ) ;
9186 ok($MINUS_ONE == compare_lists([1], [2]) , 'compare_lists, [1] < [2]') ;
9187 ok( 0 == compare_lists([2], [2]) , 'compare_lists, [0] = [2]') ;
9188 ok(+1 == compare_lists([2], [1]) , 'compare_lists, [2] > [1]') ;
9189
9190 ok($MINUS_ONE == compare_lists(['a'], ['b']) , 'compare_lists, ["a"] < ["b"]') ;
9191 ok( 0 == compare_lists(['a'], ['a']) , 'compare_lists, ["a"] = ["a"]') ;
9192 ok( 0 == compare_lists(['ab'], ['ab']) , 'compare_lists, ["ab"] = ["ab"]') ;
9193 ok(+1 == compare_lists(['b'], ['a']) , 'compare_lists, ["b"] > ["a"]') ;
9194 ok($MINUS_ONE == compare_lists(['a'], ['aa']) , 'compare_lists, ["a"] < ["aa"]') ;
9195 ok($MINUS_ONE == compare_lists(['a'], ['a', 'a']), 'compare_lists, ["a"] < ["a", "a"]') ;
9196 ok( 0 == compare_lists([split q{ }, 'a b' ], ['a', 'b']), 'compare_lists, split') ;
9197 ok( 0 == compare_lists([sort split q{ }, 'b a' ], ['a', 'b']), 'compare_lists, sort split') ;
9198
9199 note( 'Leaving tests_compare_lists()' ) ;
9200 return ;
9201}
9202
9203
9204sub guess_prefix
9205{
9206 my @foldernames = @_ ;
9207
9208 my $prefix_guessed = q{} ;
9209 foreach my $folder ( @foldernames ) {
9210 next if ( $folder =~ m{^INBOX$}xi ) ; # no guessing from INBOX
9211 if ( $folder !~ m{^INBOX}xi ) {
9212 $prefix_guessed = q{} ; # prefix empty guessed
9213 last ;
9214 }
9215 if ( $folder =~ m{^(INBOX(?:\.|\/))}xi ) {
9216 $prefix_guessed = $1 ; # prefix Inbox/ or INBOX. guessed
9217 }
9218 }
9219 return( $prefix_guessed ) ;
9220}
9221
9222sub tests_guess_prefix
9223{
9224 note( 'Entering tests_guess_prefix()' ) ;
9225
9226 is( guess_prefix( ), q{}, 'guess_prefix: no args => empty string' ) ;
9227 is( q{} , guess_prefix( 'INBOX' ), 'guess_prefix: INBOX alone' ) ;
9228 is( q{} , guess_prefix( 'Inbox' ), 'guess_prefix: Inbox alone' ) ;
9229 is( q{} , guess_prefix( 'INBOX' ), 'guess_prefix: INBOX alone' ) ;
9230 is( 'INBOX/' , guess_prefix( 'INBOX', 'INBOX/Junk' ), 'guess_prefix: INBOX INBOX/Junk' ) ;
9231 is( 'INBOX.' , guess_prefix( 'INBOX', 'INBOX.Junk' ), 'guess_prefix: INBOX INBOX.Junk' ) ;
9232 is( 'Inbox/' , guess_prefix( 'Inbox', 'Inbox/Junk' ), 'guess_prefix: Inbox Inbox/Junk' ) ;
9233 is( 'Inbox.' , guess_prefix( 'Inbox', 'Inbox.Junk' ), 'guess_prefix: Inbox Inbox.Junk' ) ;
9234 is( 'INBOX/' , guess_prefix( 'INBOX', 'INBOX/Junk', 'INBOX/rrr' ), 'guess_prefix: INBOX INBOX/Junk INBOX/rrr' ) ;
9235 is( q{} , guess_prefix( 'INBOX', 'INBOX/Junk', 'INBOX/rrr', 'zzz' ), 'guess_prefix: INBOX INBOX/Junk INBOX/rrr zzz' ) ;
9236 is( q{} , guess_prefix( 'INBOX', 'Junk' ), 'guess_prefix: INBOX Junk' ) ;
9237 is( q{} , guess_prefix( 'INBOX', 'Junk' ), 'guess_prefix: INBOX Junk' ) ;
9238
9239 note( 'Leaving tests_guess_prefix()' ) ;
9240 return ;
9241}
9242
9243sub get_prefix
9244{
9245 my( $imap, $prefix_in, $prefix_opt, $Side, $folders_ref ) = @_ ;
9246 my( $prefix_out, $prefix_guessed ) ;
9247
9248 ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "$Side: Getting prefix\n" ) ;
9249 $prefix_guessed = guess_prefix( @{ $folders_ref } ) ;
9250 myprint( "$Side: guessing prefix from folder listing: [$prefix_guessed]\n" ) ;
9251 ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "$Side: Calling namespace capability\n" ) ;
9252 if ( $imap->has_capability( 'namespace' ) ) {
9253 my $r_namespace = $imap->namespace( ) ;
9254 $prefix_out = $r_namespace->[0][0][0] ;
9255 myprint( "$Side: prefix given by NAMESPACE: [$prefix_out]\n" ) ;
9256 if ( defined $prefix_in ) {
9257 myprint( "$Side: but using [$prefix_in] given by $prefix_opt\n" ) ;
9258 $prefix_out = $prefix_in ;
9259 return( $prefix_out ) ;
9260 }else{
9261 # all good
9262 return( $prefix_out ) ;
9263 }
9264 }
9265 else{
9266 if ( defined $prefix_in ) {
9267 myprint( "$Side: using [$prefix_in] given by $prefix_opt\n" ) ;
9268 $prefix_out = $prefix_in ;
9269 return( $prefix_out ) ;
9270 }else{
9271 myprint(
9272 "$Side: No NAMESPACE capability so using guessed prefix [$prefix_guessed]\n",
9273 help_to_guess_prefix( $imap, $prefix_opt ) ) ;
9274 return( $prefix_guessed ) ;
9275 }
9276 }
9277 return ;
9278}
9279
9280
9281sub guess_separator
9282{
9283 my @foldernames = @_ ;
9284
9285 #return( undef ) unless ( @foldernames ) ;
9286
9287 my $sep_guessed ;
9288 my %counter ;
9289 foreach my $folder ( @foldernames ) {
9290 $counter{'/'}++ while ( $folder =~ m{/}xg ) ; # count /
9291 $counter{'.'}++ while ( $folder =~ m{\.}xg ) ; # count .
9292 $counter{'\\\\'}++ while ( $folder =~ m{(\\){2}}xg ) ; # count \\
9293 $counter{'\\'}++ while ( $folder =~ m{[^\\](\\){1}(?=[^\\])}xg ) ; # count \
9294 }
9295 my @race_sorted = sort { $counter{ $b } <=> $counter{ $a } } keys %counter ;
9296 $sync->{ debug } and myprint( "@foldernames\n@race_sorted\n", %counter, "\n" ) ;
9297 $sep_guessed = shift @race_sorted || $LAST_RESSORT_SEPARATOR ; # / when nothing found.
9298 return( $sep_guessed ) ;
9299}
9300
9301sub tests_guess_separator
9302{
9303 note( 'Entering tests_guess_separator()' ) ;
9304
9305 ok( '/' eq guess_separator( ), 'guess_separator: no args' ) ;
9306 ok( '/' eq guess_separator( 'abcd' ), 'guess_separator: abcd' ) ;
9307 ok( '/' eq guess_separator( 'a/b/c.d' ), 'guess_separator: a/b/c.d' ) ;
9308 ok( '.' eq guess_separator( 'a.b/c.d' ), 'guess_separator: a.b/c.d' ) ;
9309 ok( '\\\\' eq guess_separator( 'a\\\\b\\\\c.c\\\\d/e/f' ), 'guess_separator: a\\\\b\\\\c.c\\\\d/e/f' ) ;
9310 ok( '\\' eq guess_separator( 'a\\b\\c.c\\d/e/f' ), 'guess_separator: a\\b\\c.c\\d/e/f' ) ;
9311 ok( '\\' eq guess_separator( 'a\\b' ), 'guess_separator: a\\b' ) ;
9312 ok( '\\' eq guess_separator( 'a\\b\\c' ), 'guess_separator: a\\b\\c' ) ;
9313
9314 note( 'Leaving tests_guess_separator()' ) ;
9315 return ;
9316}
9317
9318sub get_separator
9319{
9320 my( $imap, $sep_in, $sep_opt, $Side, $folders_ref ) = @_ ;
9321 my( $sep_out, $sep_guessed ) ;
9322
9323 ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "$Side: Getting separator\n" ) ;
9324 $sep_guessed = guess_separator( @{ $folders_ref } ) ;
9325 myprint( "$Side: guessing separator from folder listing: [$sep_guessed]\n" ) ;
9326
9327 ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "$Side: calling namespace capability\n" ) ;
9328 if ( $imap->has_capability( 'namespace' ) )
9329 {
9330 $sep_out = $imap->separator( ) ;
9331 if ( defined $sep_out ) {
9332 myprint( "$Side: separator given by NAMESPACE: [$sep_out]\n" ) ;
9333 if ( defined $sep_in ) {
9334 myprint( "$Side: but using [$sep_in] given by $sep_opt\n" ) ;
9335 $sep_out = $sep_in ;
9336 return( $sep_out ) ;
9337 }else{
9338 return( $sep_out ) ;
9339 }
9340 }else{
9341 if ( defined $sep_in ) {
9342 myprint( "$Side: NAMESPACE request failed but using [$sep_in] given by $sep_opt\n" ) ;
9343 $sep_out = $sep_in ;
9344 return( $sep_out ) ;
9345 }else{
9346 myprint(
9347 "$Side: NAMESPACE request failed so using guessed separator [$sep_guessed]\n",
9348 help_to_guess_sep( $imap, $sep_opt ) ) ;
9349 return( $sep_guessed ) ;
9350 }
9351 }
9352 }
9353 else
9354 {
9355 if ( defined $sep_in ) {
9356 myprint( "$Side: No NAMESPACE capability but using [$sep_in] given by $sep_opt\n" ) ;
9357 $sep_out = $sep_in ;
9358 return( $sep_out ) ;
9359 }else{
9360 myprint(
9361 "$Side: No NAMESPACE capability, so using guessed separator [$sep_guessed]\n",
9362 help_to_guess_sep( $imap, $sep_opt ) ) ;
9363 return( $sep_guessed ) ;
9364 }
9365 }
9366 return ;
9367}
9368
9369sub help_to_guess_sep
9370{
9371 my( $imap, $sep_opt ) = @_ ;
9372
9373 my $help_to_guess_sep = "You can set the separator character with the $sep_opt option,\n"
9374 . "the complete listing of folders may help you to find it\n"
9375 . folders_list_to_help( $imap ) ;
9376
9377 return( $help_to_guess_sep ) ;
9378}
9379
9380sub help_to_guess_prefix
9381{
9382 my( $imap, $prefix_opt ) = @_ ;
9383
9384 my $help_to_guess_prefix = "You can set the prefix namespace with the $prefix_opt option,\n"
9385 . "the folowing listing of folders may help you to find it:\n"
9386 . folders_list_to_help( $imap ) ;
9387
9388 return( $help_to_guess_prefix ) ;
9389}
9390
9391
9392sub folders_list_to_help
9393{
9394 my( $imap ) = shift ;
9395
9396 my @folders = $imap->folders ;
9397 my $listing = join q{}, map { "[$_]\n" } @folders ;
9398 return( $listing ) ;
9399}
9400
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009401# Globals are $sync @h1_folders_all @h2_folders_all $prefix1 $prefix2
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009402sub private_folders_separators_and_prefixes
9403{
9404# what are the private folders separators and prefixes for each server ?
9405
9406 ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "Getting separators\n" ) ;
9407 $sync->{ h1_sep } = get_separator( $sync->{imap1}, $sync->{ sep1 }, '--sep1', 'Host1', \@h1_folders_all ) ;
9408 $sync->{ h2_sep } = get_separator( $sync->{imap2}, $sync->{ sep2 }, '--sep2', 'Host2', \@h2_folders_all ) ;
9409
9410
9411 $sync->{ h1_prefix } = get_prefix( $sync->{imap1}, $prefix1, '--prefix1', 'Host1', \@h1_folders_all ) ;
9412 $sync->{ h2_prefix } = get_prefix( $sync->{imap2}, $prefix2, '--prefix2', 'Host2', \@h2_folders_all ) ;
9413
9414 myprint( "Host1: separator and prefix: [$sync->{ h1_sep }][$sync->{ h1_prefix }]\n" ) ;
9415 myprint( "Host2: separator and prefix: [$sync->{ h2_sep }][$sync->{ h2_prefix }]\n" ) ;
9416 return ;
9417}
9418
9419
9420sub subfolder1
9421{
9422 my $mysync = shift ;
9423 my $subfolder1 = sanitize_subfolder( $mysync->{ subfolder1 } ) ;
9424
9425 if ( $subfolder1 )
9426 {
9427 # turns off automap
9428 myprint( "Turning off automapping folders because of --subfolder1\n" ) ;
9429 $mysync->{ automap } = undef ;
9430 myprint( "Sanitizing subfolder1: [$mysync->{ subfolder1 }] => [$subfolder1]\n" ) ;
9431 $mysync->{ subfolder1 } = $subfolder1 ;
9432 if ( ! add_subfolder1_to_folderrec( $mysync ) )
9433 {
9434 $mysync->{nb_errors}++ ;
9435 exit_clean( $mysync, $EXIT_SUBFOLDER1_NO_EXISTS, "subfolder1 $subfolder1 does not exist\n" ) ;
9436 }
9437 }
9438 else
9439 {
9440 $mysync->{ subfolder1 } = undef ;
9441 }
9442
9443 return ;
9444}
9445
9446sub subfolder2
9447{
9448 my $mysync = shift ;
9449 my $subfolder2 = sanitize_subfolder( $mysync->{ subfolder2 } ) ;
9450 if ( $subfolder2 )
9451 {
9452 # turns off automap
9453 myprint( "Turning off automapping folders because of --subfolder2\n" ) ;
9454 $mysync->{ automap } = undef ;
9455 myprint( "Sanitizing subfolder2: [$mysync->{ subfolder2 }] => [$subfolder2]\n" ) ;
9456 $mysync->{ subfolder2 } = $subfolder2 ;
9457 set_regextrans2_for_subfolder2( $mysync ) ;
9458 }
9459 else
9460 {
9461 $mysync->{ subfolder2 } = undef ;
9462 }
9463
9464 return ;
9465}
9466
9467sub tests_sanitize_subfolder
9468{
9469 note( 'Entering tests_sanitize_subfolder()' ) ;
9470
9471 is( undef, sanitize_subfolder( ), 'sanitize_subfolder: no args => undef' ) ;
9472 is( undef, sanitize_subfolder( q{} ), 'sanitize_subfolder: empty => undef' ) ;
9473 is( undef, sanitize_subfolder( ' ' ), 'sanitize_subfolder: blank => undef' ) ;
9474 is( undef, sanitize_subfolder( ' ' ), 'sanitize_subfolder: blanks => undef' ) ;
9475 is( 'abcd', sanitize_subfolder( 'abcd' ), 'sanitize_subfolder: abcd => abcd' ) ;
9476 is( 'ab cd', sanitize_subfolder( ' ab cd ' ), 'sanitize_subfolder: " ab cd " => "ab cd"' ) ;
9477 is( 'abcd', sanitize_subfolder( q{a&~b#\\c[]=d;} ), 'sanitize_subfolder: "a&~b#\\c[]=d;" => "abcd"' ) ;
9478 is( 'aA.b-_ 8c/dD', sanitize_subfolder( 'aA.b-_ 8c/dD' ), 'sanitize_subfolder: aA.b-_ 8c/dD => aA.b-_ 8c/dD' ) ;
9479 note( 'Leaving tests_sanitize_subfolder()' ) ;
9480 return ;
9481}
9482
9483
9484sub sanitize_subfolder
9485{
9486 my $subfolder = shift ;
9487
9488 if ( ! $subfolder )
9489 {
9490 return ;
9491 }
9492 # Remove edging blanks
9493 $subfolder =~ s,^ +| +$,,g ;
9494 # Keep only abcd...ABCD...0123... and -_./
9495 $subfolder =~ tr,-_a-zA-Z0-9./ ,,cd ;
9496
9497 # A blank subfolder is not a subfolder
9498 if ( ! $subfolder )
9499 {
9500 return ;
9501 }
9502 else
9503 {
9504 return $subfolder ;
9505 }
9506}
9507
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009508sub tests_sanitize_host
9509{
9510 note( 'Entering tests_sanitize_host()' ) ;
9511
9512 is( undef, sanitize_host( ), 'sanitize_host: no args => undef' ) ;
9513 is( '', sanitize_host( '' ), 'sanitize_host: empty => empty' ) ;
9514 is( 'imap.example.org', sanitize_host( 'imap.example.org' ), 'sanitize_host: imap.example.org => imap.example.org' ) ;
9515 is( 'imap.example.org', sanitize_host( ' imap.example.org' ), 'sanitize_host: imap.example.org 1 => imap.example.org' ) ;
9516 is( 'imap.example.org', sanitize_host( 'imap.example.org ' ), 'sanitize_host: imap.example.org 2 => imap.example.org' ) ;
9517 is( 'imap.example.org', sanitize_host( 'imap.exam ple.org' ), 'sanitize_host: imap.example.org 3 => imap.example.org' ) ;
9518 is( 'imap.example.org', sanitize_host( ' imap.exam ple.org ' ), 'sanitize_host: imap.example.org 4 => imap.example.org' ) ;
9519 is( 'imap.example.org', sanitize_host( 'imap.exa/mple.org/' ), 'sanitize_host: imap.example.org/ => imap.example.org' ) ;
9520
9521 note( 'Leaving tests_sanitize_host()' ) ;
9522 return ;
9523}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009524
9525
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009526sub sanitize_host
9527{
9528 my $host = shift ;
9529 if ( ! defined $host ) { return ; }
9530
9531 $host =~ tr{ /}{}d ;
9532 return $host ;
9533}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009534
9535
9536sub tests_add_subfolder1_to_folderrec
9537{
9538 note( 'Entering tests_add_subfolder1_to_folderrec()' ) ;
9539
9540 is( undef, add_subfolder1_to_folderrec( ), 'add_subfolder1_to_folderrec: undef => undef' ) ;
9541 is_deeply( [], [ add_subfolder1_to_folderrec( ) ], 'add_subfolder1_to_folderrec: no args => empty array' ) ;
9542 @folderrec = () ;
9543 my $mysync = {} ;
9544 is_deeply( [ ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: empty => empty array' ) ;
9545 is_deeply( [ ], [ @folderrec ], 'add_subfolder1_to_folderrec: empty => empty folderrec' ) ;
9546 $mysync->{ subfolder1 } = 'SUBI' ;
9547 $h1_folders_all{ 'SUBI' } = 1 ;
9548 $mysync->{ h1_prefix } = 'INBOX/' ;
9549 is_deeply( [ 'SUBI' ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: SUBI => SUBI' ) ;
9550 is_deeply( [ 'SUBI' ], [ @folderrec ], 'add_subfolder1_to_folderrec: SUBI => folderrec SUBI ' ) ;
9551
9552 @folderrec = () ;
9553 $mysync->{ subfolder1 } = 'SUBO' ;
9554 is_deeply( [ ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: SUBO no exists => empty array' ) ;
9555 is_deeply( [ ], [ @folderrec ], 'add_subfolder1_to_folderrec: SUBO no exists => empty folderrec' ) ;
9556 $h1_folders_all{ 'INBOX/SUBO' } = 1 ;
9557 is_deeply( [ 'INBOX/SUBO' ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: SUBO + INBOX/SUBO exists => INBOX/SUBO' ) ;
9558 is_deeply( [ 'INBOX/SUBO' ], [ @folderrec ], 'add_subfolder1_to_folderrec: SUBO + INBOX/SUBO exists => INBOX/SUBO folderrec' ) ;
9559
9560 note( 'Leaving tests_add_subfolder1_to_folderrec()' ) ;
9561 return ;
9562}
9563
9564
9565sub add_subfolder1_to_folderrec
9566{
9567 my $mysync = shift ;
9568 if ( ! $mysync || ! $mysync->{ subfolder1 } )
9569 {
9570 return ;
9571 }
9572
9573 my $subfolder1 = $mysync->{ subfolder1 } ;
9574 my $subfolder1_extended = $mysync->{ h1_prefix } . $subfolder1 ;
9575
9576 if ( exists $h1_folders_all{ $subfolder1 } )
9577 {
9578 myprint( qq{Acting like --folderrec "$subfolder1"\n} ) ;
9579 push @folderrec, $subfolder1 ;
9580 }
9581 elsif ( exists $h1_folders_all{ $subfolder1_extended } )
9582 {
9583 myprint( qq{Acting like --folderrec "$subfolder1_extended"\n} ) ;
9584 push @folderrec, $subfolder1_extended ;
9585 }
9586 else
9587 {
9588 myprint( qq{Nor folder "$subfolder1" nor "$subfolder1_extended" exists on host1\n} ) ;
9589 }
9590 return @folderrec ;
9591}
9592
9593sub set_regextrans2_for_subfolder2
9594{
9595 my $mysync = shift ;
9596
9597
9598 unshift @{ $mysync->{ regextrans2 } },
9599 q(s,^$mysync->{ h2_prefix }(.*),$mysync->{ h2_prefix }$mysync->{ subfolder2 }$mysync->{ h2_sep }$1,),
9600 q(s,^INBOX$,$mysync->{ h2_prefix }$mysync->{ subfolder2 }$mysync->{ h2_sep }INBOX,),
9601 q(s,^($mysync->{ h2_prefix }){2},$mysync->{ h2_prefix },);
9602
9603 #myprint( "@{ $mysync->{ regextrans2 } }\n" ) ;
9604 return ;
9605}
9606
9607
9608
9609# Looks like no globals here
9610
9611sub tests_imap2_folder_name
9612{
9613 note( 'Entering tests_imap2_folder_name()' ) ;
9614
9615 my $mysync = {} ;
9616 $mysync->{ h1_prefix } = q{} ;
9617 $mysync->{ h2_prefix } = q{} ;
9618 $mysync->{ h1_sep } = '/';
9619 $mysync->{ h2_sep } = '.';
9620
9621 $mysync->{ debug } and myprint( <<"EOS"
9622prefix1: [$mysync->{ h1_prefix }]
9623prefix2: [$mysync->{ h2_prefix }]
9624sep1: [$sync->{ h1_sep }]
9625sep2: [$sync->{ h2_sep }]
9626EOS
9627) ;
9628
9629 $mysync->{ fixslash2 } = 0 ;
9630 is( q{INBOX}, imap2_folder_name( $mysync, q{} ), 'imap2_folder_name: empty string' ) ;
9631 is( 'blabla', imap2_folder_name( $mysync, 'blabla' ), 'imap2_folder_name: blabla' ) ;
9632 is('spam.spam', imap2_folder_name( $mysync, 'spam/spam' ), 'imap2_folder_name: spam/spam' ) ;
9633
9634 is( 'spam/spam', imap2_folder_name( $mysync, 'spam.spam' ), 'imap2_folder_name: spam.spam') ;
9635 is( 'spam.spam/spam', imap2_folder_name( $mysync, 'spam/spam.spam' ), 'imap2_folder_name: spam/spam.spam' ) ;
9636 is( 's pam.spam/sp am', imap2_folder_name( $mysync, 's pam/spam.sp am' ), 'imap2_folder_name: s pam/spam.sp am' ) ;
9637
9638 $mysync->{f1f2h}{ 'auto' } = 'moto' ;
9639 is( 'moto', imap2_folder_name( $mysync, 'auto' ), 'imap2_folder_name: auto' ) ;
9640 $mysync->{f1f2h}{ 'auto/auto' } = 'moto x 2' ;
9641 is( 'moto x 2', imap2_folder_name( $mysync, 'auto/auto' ), 'imap2_folder_name: auto/auto' ) ;
9642
9643 @{ $mysync->{ regextrans2 } } = ( 's,/,X,g' ) ;
9644 is( q{INBOX}, imap2_folder_name( $mysync, q{} ), 'imap2_folder_name: empty string [s,/,X,g]' ) ;
9645 is( 'blabla', imap2_folder_name( $mysync, 'blabla' ), 'imap2_folder_name: blabla [s,/,X,g]' ) ;
9646 is('spam.spam', imap2_folder_name( $mysync, 'spam/spam'), 'imap2_folder_name: spam/spam [s,/,X,g]');
9647 is('spamXspam', imap2_folder_name( $mysync, 'spam.spam'), 'imap2_folder_name: spam.spam [s,/,X,g]');
9648 is('spam.spamXspam', imap2_folder_name( $mysync, 'spam/spam.spam'), 'imap2_folder_name: spam/spam.spam [s,/,X,g]');
9649
9650 @{ $mysync->{ regextrans2 } } = ( 's, ,_,g' ) ;
9651 is('blabla', imap2_folder_name( $mysync, 'blabla'), 'imap2_folder_name: blabla [s, ,_,g]');
9652 is('bla_bla', imap2_folder_name( $mysync, 'bla bla'), 'imap2_folder_name: blabla [s, ,_,g]');
9653
9654 @{ $mysync->{ regextrans2 } } = ( q{s,(.*),\U$1,} ) ;
9655 is( 'BLABLA', imap2_folder_name( $mysync, 'blabla' ), q{imap2_folder_name: blabla [s,\U(.*)\E,$1,]} ) ;
9656
9657 $mysync->{ fixslash2 } = 1 ;
9658 @{ $mysync->{ regextrans2 } } = ( ) ;
9659 is(q{INBOX}, imap2_folder_name( $mysync, q{}), 'imap2_folder_name: empty string');
9660 is('blabla', imap2_folder_name( $mysync, 'blabla'), 'imap2_folder_name: blabla');
9661 is('spam.spam', imap2_folder_name( $mysync, 'spam/spam'), 'imap2_folder_name: spam/spam -> spam.spam');
9662 is('spam_spam', imap2_folder_name( $mysync, 'spam.spam'), 'imap2_folder_name: spam.spam -> spam_spam');
9663 is('spam.spam_spam', imap2_folder_name( $mysync, 'spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam_spam');
9664 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');
9665
9666 $mysync->{ h1_sep } = '.';
9667 $mysync->{ h2_sep } = '/';
9668 is( q{INBOX}, imap2_folder_name( $mysync, q{}), 'imap2_folder_name: empty string');
9669 is('blabla', imap2_folder_name( $mysync, 'blabla'), 'imap2_folder_name: blabla');
9670 is('spam.spam', imap2_folder_name( $mysync, 'spam/spam'), 'imap2_folder_name: spam/spam -> spam.spam');
9671 is('spam/spam', imap2_folder_name( $mysync, 'spam.spam'), 'imap2_folder_name: spam.spam -> spam/spam');
9672 is('spam.spam/spam', imap2_folder_name( $mysync, 'spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam');
9673
9674
9675
9676 $mysync->{ fixslash2 } = 0 ;
9677 $mysync->{ h1_prefix } = q{ };
9678
9679 is( 'spam.spam/spam', imap2_folder_name( $mysync, 'spam/spam.spam' ), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam' ) ;
9680 is( 'spam.spam/spam', imap2_folder_name( $mysync, ' spam/spam.spam' ), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam' ) ;
9681
9682 $mysync->{ h1_sep } = '.' ;
9683 $mysync->{ h2_sep } = '/' ;
9684 $mysync->{ h1_prefix } = 'INBOX.' ;
9685 $mysync->{ h2_prefix } = q{} ;
9686 @{ $mysync->{ regextrans2 } } = ( q{s,(.*),\U$1,} ) ;
9687 is( 'BLABLA', imap2_folder_name( $mysync, 'blabla' ), 'imap2_folder_name: blabla' ) ;
9688 is( 'TEST/TEST/TEST/TEST', imap2_folder_name( $mysync, 'INBOX.TEST.test.Test.tesT' ), 'imap2_folder_name: INBOX.TEST.test.Test.tesT' ) ;
9689 @{ $mysync->{ regextrans2 } } = ( q{s,(.*),\L$1,} ) ;
9690 is( 'test/test/test/test', imap2_folder_name( $mysync, 'INBOX.TEST.test.Test.tesT' ), 'imap2_folder_name: INBOX.TEST.test.Test.tesT' ) ;
9691
9692 # INBOX
9693 $mysync = {} ;
9694 $mysync->{ h1_prefix } = q{Pf1.} ;
9695 $mysync->{ h2_prefix } = q{Pf2/} ;
9696 $mysync->{ h1_sep } = '.';
9697 $mysync->{ h2_sep } = '/';
9698
9699 #
9700 #$mysync->{ debug } = 1 ;
9701 is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'F1.F2.F3' ), 'imap2_folder_name: F1.F2.F3 -> Pf2/F1/F2/F3' ) ;
9702 is( 'Pf2/F1/INBOX', imap2_folder_name( $mysync, 'F1.INBOX' ), 'imap2_folder_name: F1.INBOX -> Pf2/F1/INBOX' ) ;
9703 is( 'INBOX', imap2_folder_name( $mysync, 'INBOX' ), 'imap2_folder_name: INBOX -> INBOX' ) ;
9704
9705 is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'Pf1.F1.F2.F3' ), 'imap2_folder_name: Pf1.F1.F2.F3 -> Pf2/F1/F2/F3' ) ;
9706 is( 'Pf2/F1/INBOX', imap2_folder_name( $mysync, 'Pf1.F1.INBOX' ), 'imap2_folder_name: Pf1.F1.INBOX -> Pf2/F1/INBOX' ) ;
9707 is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.INBOX' ), 'imap2_folder_name: Pf1.INBOX -> INBOX' ) ; # not Pf2/INBOX: Yes I can!
9708
9709
9710
9711 # subfolder2
9712 $mysync = {} ;
9713 $mysync->{ h1_prefix } = q{} ;
9714 $mysync->{ h2_prefix } = q{} ;
9715 $mysync->{ h1_sep } = '/';
9716 $mysync->{ h2_sep } = '.';
9717
9718
9719 set_regextrans2_for_subfolder2( $mysync ) ;
9720 $mysync->{ subfolder2 } = 'S1.S2' ;
9721 is( 'S1.S2.F1.F2.F3', imap2_folder_name( $mysync, 'F1/F2/F3' ), 'imap2_folder_name: F1/F2/F3 -> S1.S2.F1.F2.F3' ) ;
9722 is( 'S1.S2.INBOX', imap2_folder_name( $mysync, 'INBOX' ), 'imap2_folder_name: F1/F2/F3 -> S1.S2.INBOX' ) ;
9723
9724 $mysync = {} ;
9725 $mysync->{ h1_prefix } = q{Pf1/} ;
9726 $mysync->{ h2_prefix } = q{Pf2.} ;
9727 $mysync->{ h1_sep } = '/';
9728 $mysync->{ h2_sep } = '.';
9729 #$mysync->{ debug } = 1 ;
9730
9731 set_regextrans2_for_subfolder2( $mysync ) ;
9732 $mysync->{ subfolder2 } = 'Pf2.S1.S2' ;
9733 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' ) ;
9734 is( 'Pf2.S1.S2.INBOX', imap2_folder_name( $mysync, 'INBOX' ), 'imap2_folder_name: INBOX -> Pf2.S1.S2.INBOX' ) ;
9735 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' ) ;
9736 is( 'Pf2.S1.S2.INBOX', imap2_folder_name( $mysync, 'Pf1/INBOX' ), 'imap2_folder_name: INBOX -> Pf2.S1.S2.INBOX' ) ;
9737
9738 # subfolder1
9739 # scenario as the reverse of the previous tests, separators point of vue
9740 $mysync = {} ;
9741 $mysync->{ h1_prefix } = q{Pf1.} ;
9742 $mysync->{ h2_prefix } = q{Pf2/} ;
9743 $mysync->{ h1_sep } = '.';
9744 $mysync->{ h2_sep } = '/';
9745 #$mysync->{ debug } = 1 ;
9746
9747 $mysync->{ subfolder1 } = 'S1.S2' ;
9748 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' ) ;
9749 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' ) ;
9750
9751 is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.INBOX' ), 'imap2_folder_name: S1.S2.INBOX -> INBOX' ) ;
9752 is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2' ), 'imap2_folder_name: S1.S2 -> INBOX' ) ;
9753 is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.' ), 'imap2_folder_name: S1.S2. -> INBOX' ) ;
9754
9755 is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.INBOX' ), 'imap2_folder_name: Pf1.S1.S2.INBOX -> INBOX' ) ;
9756 is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2' ), 'imap2_folder_name: Pf1.S1.S2 -> INBOX' ) ;
9757 is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.' ), 'imap2_folder_name: Pf1.S1.S2. -> INBOX' ) ;
9758
9759
9760 $mysync->{ subfolder1 } = 'S1.S2.' ;
9761 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' ) ;
9762 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' ) ;
9763
9764 is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.INBOX' ), 'imap2_folder_name: S1.S2.INBOX -> INBOX' ) ;
9765 is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2' ), 'imap2_folder_name: S1.S2 -> INBOX' ) ;
9766 is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.' ), 'imap2_folder_name: S1.S2. -> INBOX' ) ;
9767
9768 is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.INBOX' ), 'imap2_folder_name: Pf1.S1.S2.INBOX -> INBOX' ) ;
9769 is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2' ), 'imap2_folder_name: Pf1.S1.S2 -> INBOX' ) ;
9770 is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.' ), 'imap2_folder_name: Pf1.S1.S2. -> INBOX' ) ;
9771
9772
9773 # subfolder1
9774 # scenario as Gmail
9775 $mysync = {} ;
9776 $mysync->{ h1_prefix } = q{} ;
9777 $mysync->{ h2_prefix } = q{} ;
9778 $mysync->{ h1_sep } = '/';
9779 $mysync->{ h2_sep } = '/';
9780 #$mysync->{ debug } = 1 ;
9781
9782 $mysync->{ subfolder1 } = 'S1/S2' ;
9783 is( 'F1/F2/F3', imap2_folder_name( $mysync, 'S1/S2/F1/F2/F3' ), 'imap2_folder_name: S1/S2/F1/F2/F3 -> F1/F2/F3' ) ;
9784 is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/INBOX' ), 'imap2_folder_name: S1/S2/INBOX -> INBOX' ) ;
9785 is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2' ), 'imap2_folder_name: S1/S2 -> INBOX' ) ;
9786 is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/' ), 'imap2_folder_name: S1/S2/ -> INBOX' ) ;
9787
9788 $mysync->{ subfolder1 } = 'S1/S2/' ;
9789 is( 'F1/F2/F3', imap2_folder_name( $mysync, 'S1/S2/F1/F2/F3' ), 'imap2_folder_name: S1/S2/F1/F2/F3 -> F1/F2/F3' ) ;
9790 is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/INBOX' ), 'imap2_folder_name: S1/S2/INBOX -> INBOX' ) ;
9791 is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2' ), 'imap2_folder_name: S1/S2 -> INBOX' ) ;
9792 is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/' ), 'imap2_folder_name: S1/S2/ -> INBOX' ) ;
9793
9794
9795 note( 'Leaving tests_imap2_folder_name()' ) ;
9796 return ;
9797}
9798
9799
9800# Global variables to remove:
9801# None?
9802
9803
9804sub imap2_folder_name
9805{
9806 my $mysync = shift ;
9807 my ( $h1_fold ) = shift ;
9808 my ( $h2_fold ) ;
9809 if ( $mysync->{f1f2h}{ $h1_fold } ) {
9810 $h2_fold = $mysync->{f1f2h}{ $h1_fold } ;
9811 ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "f1f2 [$h1_fold] -> [$h2_fold]\n" ) ;
9812 return( $h2_fold ) ;
9813 }
9814 if ( $mysync->{f1f2auto}{ $h1_fold } ) {
9815 $h2_fold = $mysync->{f1f2auto}{ $h1_fold } ;
9816 ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "automap [$h1_fold] -> [$h2_fold]\n" ) ;
9817 return( $h2_fold ) ;
9818 }
9819
9820 if ( $mysync->{ subfolder1 } )
9821 {
9822 my $esc_h1_sep = "\\" . $mysync->{ h1_sep } ;
9823 # case where subfolder1 has the sep1 at the end, then remove it
9824 my $part_to_removed = remove_last_char_if_is( $mysync->{ subfolder1 }, $mysync->{ h1_sep } ) ;
9825 # remove the subfolder1 part and the sep1 if present after
9826 $h1_fold =~ s{$part_to_removed($esc_h1_sep)?}{} ;
9827 #myprint( "h1_fold=$h1_fold\n" ) ;
9828 }
9829
9830 if ( ( q{} eq $h1_fold ) or ( $mysync->{ h1_prefix } eq $h1_fold ) )
9831 {
9832 $h1_fold = 'INBOX' ;
9833 }
9834
9835 $h2_fold = prefix_seperator_invertion( $mysync, $h1_fold ) ;
9836 $h2_fold = regextrans2( $mysync, $h2_fold ) ;
9837 return( $h2_fold ) ;
9838}
9839
9840
9841sub tests_remove_last_char_if_is
9842{
9843 note( 'Entering tests_remove_last_char_if_is()' ) ;
9844
9845 is( undef, remove_last_char_if_is( ), 'remove_last_char_if_is: no args => undef' ) ;
9846 is( q{}, remove_last_char_if_is( q{} ), 'remove_last_char_if_is: empty => empty' ) ;
9847 is( q{}, remove_last_char_if_is( q{}, 'Z' ), 'remove_last_char_if_is: empty Z => empty' ) ;
9848 is( q{}, remove_last_char_if_is( 'Z', 'Z' ), 'remove_last_char_if_is: Z Z => empty' ) ;
9849 is( 'abc', remove_last_char_if_is( 'abcZ', 'Z' ), 'remove_last_char_if_is: abcZ Z => abc' ) ;
9850 is( 'abcY', remove_last_char_if_is( 'abcY', 'Z' ), 'remove_last_char_if_is: abcY Z => abcY' ) ;
9851 note( 'Leaving tests_remove_last_char_if_is()' ) ;
9852 return ;
9853}
9854
9855
9856
9857
9858sub remove_last_char_if_is
9859{
9860 my $string = shift ;
9861 my $char = shift ;
9862
9863 if ( ! defined $string )
9864 {
9865 return ;
9866 }
9867
9868 if ( ! defined $char )
9869 {
9870 return $string ;
9871 }
9872
9873 my $last_char = substr $string, -1 ;
9874 if ( $char eq $last_char )
9875 {
9876 chop $string ;
9877 return $string ;
9878 }
9879 else
9880 {
9881 return $string ;
9882 }
9883}
9884
9885sub tests_prefix_seperator_invertion
9886{
9887 note( 'Entering tests_prefix_seperator_invertion()' ) ;
9888
9889 is( undef, prefix_seperator_invertion( ), 'prefix_seperator_invertion: no args => undef' ) ;
9890 is( q{}, prefix_seperator_invertion( undef, q{} ), 'prefix_seperator_invertion: empty string => empty string' ) ;
9891 is( 'lalala', prefix_seperator_invertion( undef, 'lalala' ), 'prefix_seperator_invertion: lalala => lalala' ) ;
9892 is( 'lal/ala', prefix_seperator_invertion( undef, 'lal/ala' ), 'prefix_seperator_invertion: lal/ala => lal/ala' ) ;
9893 is( 'lal.ala', prefix_seperator_invertion( undef, 'lal.ala' ), 'prefix_seperator_invertion: lal.ala => lal.ala' ) ;
9894 is( '////', prefix_seperator_invertion( undef, '////' ), 'prefix_seperator_invertion: //// => ////' ) ;
9895 is( '.....', prefix_seperator_invertion( undef, '.....' ), 'prefix_seperator_invertion: ..... => .....' ) ;
9896
9897 my $mysync = {
9898 h1_prefix => q{},
9899 h2_prefix => q{},
9900 h1_sep => '/',
9901 h2_sep => '/',
9902 } ;
9903
9904 is( q{}, prefix_seperator_invertion( $mysync, q{} ), 'prefix_seperator_invertion: $mysync empty string => empty string' ) ;
9905 is( 'lalala', prefix_seperator_invertion( $mysync, 'lalala' ), 'prefix_seperator_invertion: $mysync lalala => lalala' ) ;
9906 is( 'lal/ala', prefix_seperator_invertion( $mysync, 'lal/ala' ), 'prefix_seperator_invertion: $mysync lal/ala => lal/ala' ) ;
9907 is( 'lal.ala', prefix_seperator_invertion( $mysync, 'lal.ala' ), 'prefix_seperator_invertion: $mysync lal.ala => lal.ala' ) ;
9908 is( '////', prefix_seperator_invertion( $mysync, '////' ), 'prefix_seperator_invertion: $mysync //// => ////' ) ;
9909 is( '.....', prefix_seperator_invertion( $mysync, '.....' ), 'prefix_seperator_invertion: $mysync ..... => .....' ) ;
9910
9911 $mysync = {
9912 h1_prefix => 'PPP',
9913 h2_prefix => 'QQQ',
9914 h1_sep => 's',
9915 h2_sep => 't',
9916 } ;
9917
9918 is( q{QQQ}, prefix_seperator_invertion( $mysync, q{} ), 'prefix_seperator_invertion: PPPQQQst empty string => QQQ' ) ;
9919 is( 'QQQlalala', prefix_seperator_invertion( $mysync, 'lalala' ), 'prefix_seperator_invertion: PPPQQQst lalala => QQQlalala' ) ;
9920 is( 'QQQlal/ala', prefix_seperator_invertion( $mysync, 'lal/ala' ), 'prefix_seperator_invertion: PPPQQQst lal/ala => QQQlal/ala' ) ;
9921 is( 'QQQlal.ala', prefix_seperator_invertion( $mysync, 'lal.ala' ), 'prefix_seperator_invertion: PPPQQQst lal.ala => QQQlal.ala' ) ;
9922 is( 'QQQ////', prefix_seperator_invertion( $mysync, '////' ), 'prefix_seperator_invertion: PPPQQQst //// => QQQ////' ) ;
9923 is( 'QQQ.....', prefix_seperator_invertion( $mysync, '.....' ), 'prefix_seperator_invertion: PPPQQQst ..... => QQQ.....' ) ;
9924
9925 is( 'QQQPlalala', prefix_seperator_invertion( $mysync, 'PPPPlalala' ), 'prefix_seperator_invertion: PPPQQQst PPPPlalala => QQQPlalala' ) ;
9926 is( 'QQQ', prefix_seperator_invertion( $mysync, 'PPP' ), 'prefix_seperator_invertion: PPPQQQst PPP => QQQ' ) ;
9927 is( 'QQQttt', prefix_seperator_invertion( $mysync, 'sss' ), 'prefix_seperator_invertion: PPPQQQst sss => QQQttt' ) ;
9928 is( 'QQQt', prefix_seperator_invertion( $mysync, 's' ), 'prefix_seperator_invertion: PPPQQQst s => QQQt' ) ;
9929 is( 'QQQtAAAtBBB', prefix_seperator_invertion( $mysync, 'PPPsAAAsBBB' ), 'prefix_seperator_invertion: PPPQQQst PPPsAAAsBBB => QQQtAAAtBBB' ) ;
9930
9931 note( 'Leaving tests_prefix_seperator_invertion()' ) ;
9932 return ;
9933}
9934
9935# Global variables to remove:
9936
9937
9938sub prefix_seperator_invertion
9939{
9940 my $mysync = shift ;
9941 my $h1_fold = shift ;
9942 my $h2_fold ;
9943
9944 if ( not defined $h1_fold ) { return ; }
9945
9946 my $my_h1_prefix = $mysync->{ h1_prefix } || q{} ;
9947 my $my_h2_prefix = $mysync->{ h2_prefix } || q{} ;
9948 my $my_h1_sep = $mysync->{ h1_sep } || '/' ;
9949 my $my_h2_sep = $mysync->{ h2_sep } || '/' ;
9950
9951 # first we remove the prefix
9952 $h1_fold =~ s/^\Q$my_h1_prefix\E//x ;
9953 ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "removed host1 prefix: [$h1_fold]\n" ) ;
9954 $h2_fold = separator_invert( $mysync, $h1_fold, $my_h1_sep, $my_h2_sep ) ;
9955 ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "inverted separators: [$h2_fold]\n" ) ;
9956
9957 # Adding the prefix supplied by namespace or the --prefix2 option
9958 # except for INBOX or Inbox
9959 if ( $h2_fold !~ m/^INBOX$/xi )
9960 {
9961 $h2_fold = $my_h2_prefix . $h2_fold ;
9962 }
9963
9964 ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "added host2 prefix: [$h2_fold]\n" ) ;
9965 return( $h2_fold ) ;
9966}
9967
9968sub tests_separator_invert
9969{
9970 note( 'Entering tests_separator_invert()' ) ;
9971
9972 my $mysync = {} ;
9973 $mysync->{ fixslash2 } = 0 ;
9974 ok( not( defined separator_invert( ) ), 'separator_invert: no args' ) ;
9975 ok( not( defined separator_invert( q{} ) ), 'separator_invert: not enough args' ) ;
9976 ok( not( defined separator_invert( q{}, q{} ) ), 'separator_invert: not enough args' ) ;
9977
9978 ok( q{} eq separator_invert( $mysync, q{}, q{}, q{} ), 'separator_invert: 3 empty strings' ) ;
9979 ok( 'lalala' eq separator_invert( $mysync, 'lalala', q{}, q{} ), 'separator_invert: empty separator' ) ;
9980 ok( 'lalala' eq separator_invert( $mysync, 'lalala', '/', '/' ), 'separator_invert: same separator /' ) ;
9981 ok( 'lal/ala' eq separator_invert( $mysync, 'lal/ala', '/', '/' ), 'separator_invert: same separator / 2' ) ;
9982 ok( 'lal.ala' eq separator_invert( $mysync, 'lal/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
9983 ok( 'lal/ala' eq separator_invert( $mysync, 'lal.ala', '.', '/' ), 'separator_invert: separators ./' ) ;
9984 ok( 'la.l/ala' eq separator_invert( $mysync, 'la/l.ala', '.', '/' ), 'separator_invert: separators ./' ) ;
9985
9986 ok( 'l/al.ala' eq separator_invert( $mysync, 'l.al/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
9987 $mysync->{ fixslash2 } = 1 ;
9988 ok( 'l_al.ala' eq separator_invert( $mysync, 'l.al/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
9989
9990 note( 'Leaving tests_separator_invert()' ) ;
9991 return ;
9992}
9993
9994# Global variables to remove:
9995#
9996sub separator_invert
9997{
9998 my( $mysync, $h1_fold, $h1_separator, $h2_separator ) = @_ ;
9999
10000 return( undef ) if ( not all_defined( $mysync, $h1_fold, $h1_separator, $h2_separator ) ) ;
10001 # The separator we hope we'll never encounter: 00000000 == 0x00
10002 my $o_sep = "\000" ;
10003
10004 my $h2_fold = $h1_fold ;
10005 $h2_fold =~ s,\Q$h2_separator,$o_sep,xg ;
10006 $h2_fold =~ s,\Q$h1_separator,$h2_separator,xg ;
10007 $h2_fold =~ s,\Q$o_sep,$h1_separator,xg ;
10008 $h2_fold =~ s,/,_,xg if( $mysync->{ fixslash2 } and '/' ne $h2_separator and '/' eq $h1_separator ) ;
10009 return( $h2_fold ) ;
10010}
10011
10012
10013sub regextrans2
10014{
10015 my( $mysync, $h2_fold ) = @_ ;
10016 # Transforming the folder name by the --regextrans2 option(s)
10017 foreach my $regextrans2 ( @{ $mysync->{ regextrans2 } } ) {
10018 my $h2_fold_before = $h2_fold ;
10019 my $ret = eval "\$h2_fold =~ $regextrans2 ; 1 " ;
10020 ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "[$h2_fold_before] -> [$h2_fold] using regextrans2 [$regextrans2]\n" ) ;
10021 if ( not ( defined $ret ) or $EVAL_ERROR ) {
10022 $mysync->{nb_errors}++ ;
10023 exit_clean( $mysync, $EX_USAGE,
10024 "error: eval regextrans2 '$regextrans2': $EVAL_ERROR\n"
10025 ) ;
10026 }
10027 }
10028 return( $h2_fold ) ;
10029}
10030
10031
10032sub tests_decompose_regex
10033{
10034 note( 'Entering tests_decompose_regex()' ) ;
10035
10036 ok( 1, 'decompose_regex 1' ) ;
10037 ok( 0 == compare_lists( [ q{}, q{} ], [ decompose_regex( q{} ) ] ), 'decompose_regex empty string' ) ;
10038 ok( 0 == compare_lists( [ '.*', 'lala' ], [ decompose_regex( 's/.*/lala/' ) ] ), 'decompose_regex s/.*/lala/' ) ;
10039
10040 note( 'Leaving tests_decompose_regex()' ) ;
10041 return ;
10042}
10043
10044sub decompose_regex
10045{
10046 my $regex = shift ;
10047 my( $left_part, $right_part ) ;
10048
10049 ( $left_part, $right_part ) = $regex =~ m{^s/((?:[^/]|\\/)+)/((?:[^/]|\\/)+)/}x;
10050 return( q{}, q{} ) if not $left_part ;
10051 return( $left_part, $right_part ) ;
10052}
10053
10054
10055
10056sub tests_timenext
10057{
10058 note( 'Entering tests_timenext()' ) ;
10059
10060 is( undef, timenext( ), 'timenext: no args => undef' ) ;
10061 my $mysync ;
10062 is( undef, timenext( $mysync ), 'timenext: undef => undef' ) ;
10063 $mysync = {} ;
10064 ok( time - timenext( $mysync ) <= 1e-02, 'timenext: defined first time => ~ time' ) ;
10065 ok( timenext( $mysync ) <= 1e-02, 'timenext: second time => less than 1e-02' ) ;
10066 ok( timenext( $mysync ) <= 1e-02, 'timenext: third time => less than 1e-02' ) ;
10067
10068 note( 'Leaving tests_timenext()' ) ;
10069 return ;
10070}
10071
10072
10073sub timenext
10074{
10075 my $mysync = shift ;
10076
10077 if ( ! defined $mysync )
10078 {
10079 return ;
10080 }
10081 my ( $timenow, $timediff ) ;
10082
10083 $mysync->{ timebefore } ||= 0; # epoch...
10084 $timenow = time ;
10085 $timediff = $timenow - $mysync->{ timebefore } ;
10086 $mysync->{ timebefore } = $timenow ;
10087 # myprint( "timenext: $timediff\n" ) ;
10088 return( $timediff ) ;
10089}
10090
10091
10092sub tests_timesince
10093{
10094 note( 'Entering tests_timesince()' ) ;
10095
10096 ok( timesince( time - 1 ) - 1 <= 1e-02, 'timesince: time - 1 => <= 1 + 1e-02' ) ;
10097 ok( timesince( time ) <= 1e-02, 'timesince: time => <= 1e-02' ) ;
10098 ok( timesince( ) - time <= 1e-02, 'timesince: no args => <= time + 1e-02' ) ;
10099 note( 'Leaving tests_timesince()' ) ;
10100 return ;
10101}
10102
10103
10104
10105sub timesince
10106{
10107 my $timeinit = shift || 0 ;
10108 my ( $timenow, $timediff ) ;
10109 $timenow = time ;
10110 $timediff = $timenow - $timeinit ;
10111 # Often used in a division so no 0 but a nano seconde.
10112 return( max( $timediff, min( 1e-09, $timediff ) ) ) ;
10113}
10114
10115
10116
10117
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010118sub tests_regexflags
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010119{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010120 note( 'Entering tests_regexflags()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010121
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010122 my $mysync = {} ;
10123
10124 ok( q{} eq regexflags( $mysync, q{} ), 'regexflags, null string q{}' ) ;
10125 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 +010010126
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010127 @{ $mysync->{ regexflag } } = ('I am BAD' ) ;
10128 ok( not ( defined regexflags( $mysync, q{} ) ), 'regexflags, bad regex' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010129
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010130 @{ $mysync->{ regexflag } } = ( 's/NonJunk//g' ) ;
10131 ok( q{\Seen $Spam} eq regexflags( $mysync, q{\Seen NonJunk $Spam} ), q{regexflags, remove NonJunk: 's/NonJunk//g'} ) ;
10132 @{ $mysync->{ regexflag } } = ( q{s/\$Spam//g} ) ;
10133 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 +010010134
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010135 @{ $mysync->{ regexflag } } = ( 's/\\\\Seen//g' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010136
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010137 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 +010010138
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010139 @{ $mysync->{ regexflag } } = ( 's/(\s|^)[^\\\\]\w+//g' ) ;
10140 ok( q{\Seen \Middle \End} eq regexflags( $mysync, q{\Seen NonJunk \Middle $Spam \End} ), q{regexflags: only \word among \Seen NonJunk \Middle $Spam \End} ) ;
10141 ok( q{ \Seen \Middle \End1} eq regexflags( $mysync, q{Begin \Seen NonJunk \Middle $Spam \End1 End} ),
10142 q{regexflags: only \word among Begin \Seen NonJunk \Middle $Spam \End1 End} ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010143
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010144 @{ $mysync->{ regexflag } } = ( q{s/.*?(Keep1|Keep2|Keep3)/$1 /g} ) ;
10145 ok( 'Keep1 Keep2 ReB' eq regexflags( $mysync, 'ReA Keep1 REM Keep2 ReB' ), 'Keep only regex' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010146
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010147 ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM REM Keep1 Keep2' ), 'Keep only regex' ) ;
10148 ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 REM REM Keep2' ), 'Keep only regex' ) ;
10149 ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM Keep1 REM REM Keep2' ), 'Keep only regex' ) ;
10150 ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 Keep2' ), 'Keep only regex' ) ;
10151 ok( 'Keep1 ' eq regexflags( $mysync, 'REM Keep1' ), 'Keep only regex' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010152
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010153 @{ $mysync->{ regexflag } } = ( q{s/(Keep1|Keep2|Keep3) (?!(Keep1|Keep2|Keep3)).*/$1 /g} ) ;
10154 ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 Keep2 ReB' ), 'Keep only regex' ) ;
10155 ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 Keep2 REM REM REM' ), 'Keep only regex' ) ;
10156 ok( 'Keep2 ' eq regexflags( $mysync, 'Keep2 REM REM REM' ), 'Keep only regex' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010157
10158
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010159 @{ $mysync->{ regexflag } } = ( q{s/.*?(Keep1|Keep2|Keep3)/$1 /g},
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010160 's/(Keep1|Keep2|Keep3) (?!(Keep1|Keep2|Keep3)).*/$1 /g' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010161 ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM Keep1 REM Keep2 REM' ), 'Keep only regex' ) ;
10162 ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 REM Keep2 REM' ), 'Keep only regex' ) ;
10163 ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM Keep1 Keep2 REM' ), 'Keep only regex' ) ;
10164 ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM Keep1 REM Keep2' ), 'Keep only regex' ) ;
10165 ok( 'Keep1 Keep2 Keep3 ' eq regexflags( $mysync, 'REM Keep1 REM Keep2 REM REM Keep3 REM' ), 'Keep only regex' ) ;
10166 ok( 'Keep1 ' eq regexflags( $mysync, 'REM REM Keep1 REM REM REM ' ), 'Keep only regex' ) ;
10167 ok( 'Keep1 Keep3 ' eq regexflags( $mysync, 'RE1 Keep1 RE2 Keep3 RE3 RE4 RE5 ' ), 'Keep only regex' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010168
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010169 @{ $mysync->{ regexflag } } = ( 's/(.*)/$1 jrdH8u/' ) ;
10170 ok('REM REM REM REM REM jrdH8u' eq regexflags( $mysync, 'REM REM REM REM REM' ), q{Add jrdH8u 's/(.*)/\$1 jrdH8u/'} ) ;
10171 @{ $mysync->{ regexflag } } = ('s/jrdH8u *//' );
10172 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 +010010173
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010174 @{ $mysync->{ regexflag } } = (
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010175 's/.*?(?:(\\\\(?:Answered|Flagged|Deleted|Seen|Draft)\s?)|$)/defined($1)?$1:q()/eg'
10176 );
10177
10178 ok( '\\Deleted \\Answered '
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010179 eq regexflags( $mysync, 'Blabla \$Junk \\Deleted machin \\Answered truc' ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010180 'Keep only regex: Exchange case (Phil)' ) ;
10181
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010182 ok( q{} eq regexflags( $mysync, q{} ), 'Keep only regex: Exchange case, null string (Phil)' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010183
10184 ok( q{}
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010185 eq regexflags( $mysync, 'Blabla $Junk machin truc' ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010186 'Keep only regex: Exchange case, no accepted flags (Phil)' ) ;
10187
10188 ok('\\Deleted \\Answered \\Draft \\Flagged '
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010189 eq regexflags( $mysync, '\\Deleted \\Answered \\Draft \\Flagged ' ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010190 'Keep only regex: Exchange case (Phil)' ) ;
10191
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010192 @{ $mysync->{ regexflag } } = ( 's/\\\\Flagged//g' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010193
10194 is('\Deleted \Answered \Draft ',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010195 regexflags( $mysync, '\\Deleted \\Answered \\Draft \\Flagged ' ),
10196 'regexflags: remove \Flagged 1' ) ;
10197
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010198 is('\\Deleted \\Answered \\Draft',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010199 regexflags( $mysync, '\\Deleted \\Flagged \\Answered \\Draft' ),
10200 'regexflags: remove \Flagged 2' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010201
10202 # I didn't understand why it gives \F
10203 # https://perldoc.perl.org/perlrebackslash.html
10204 # \F Foldcase till \E. Not in [].
10205 # https://perldoc.perl.org/functions/fc.html
10206
10207 # \F Not available in old Perl so I comment the test
10208
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010209 # @{ $mysync->{ regexflag } } = ( 's/\\Flagged/X/g' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010210 #is('\Deleted FX \Answered \FX \Draft \FX',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010211 #regexflags( '\Deleted Flagged \Answered \Flagged \Draft \Flagged' ),
10212 # 'regexflags: remove \Flagged 3 mistery...' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010213
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010214 $mysync->{ regexflag } = [ ] ;
10215 $mysync->{ filterbuggyflags } = 1 ;
10216 filterbuggyflags( $mysync ) ;
10217
10218 is( '\Deleted \Answered \Draft \Flagged',
10219 regexflags( $mysync, '\\Deleted \\Answered \\RECEIPTCHECKED \\Draft \\Indexed \\Flagged' ),
10220 'regexflags: remove famous /X 1' ) ;
10221
10222 is( '\\Deleted \\Flagged \\Answered \\Draft',
10223 regexflags( $mysync, '\\Deleted \\RECEIPTCHECKED \\Flagged \\Answered \\Indexed \\Draft' ),
10224 'regexflags: remove famous /X 2' ) ;
10225
10226 is( '\ ', '\\ ', 'regexflags: \ is \\ ' ) ;
10227 is( '\\ ', '\\ ', 'regexflags: \\ is \\ ' ) ;
10228 is( '\\ \ ', '\ \\ ', 'regexflags: \\ \ is \ \\ ' ) ;
10229 note( 'Leaving tests_regexflags()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010230 return ;
10231}
10232
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010233sub regexflags
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010234{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010235 my $mysync = shift ;
10236 my $flags = shift ;
10237
10238 foreach my $regexflag ( @{ $mysync->{ regexflag } } )
10239 {
10240 my $flags_orig = $flags ;
10241 $debugflags and myprint( "eval \$flags =~ $regexflag\n" ) ;
10242 my $ret = eval "\$flags =~ $regexflag ; 1 " ;
10243 $debugflags and myprint( "regexflag $regexflag [$flags_orig] -> [$flags]\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010244 if( not ( defined $ret ) or $EVAL_ERROR ) {
10245 myprint( "Error: eval regexflag '$regexflag': $EVAL_ERROR\n" ) ;
10246 return( undef ) ;
10247 }
10248 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010249 return( $flags ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010250}
10251
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010252
10253sub filterbuggyflags
10254{
10255 my $mysync = shift ;
10256 if ( $mysync->{ filterbuggyflags } )
10257 {
10258 unshift @{ $mysync->{ regexflag } }, buggyflagsregex( ) ;
10259 }
10260 return ;
10261}
10262
10263
10264sub tests_remove_doublequotes_if_any
10265{
10266 note( 'Entering tests_remove_doublequotes_if_any()' ) ;
10267 # the number of tests is stupid here
10268 is( undef, remove_doublequotes_if_any( ), 'remove_doublequotes_if_any: no args => undef' ) ;
10269 is( q{}, remove_doublequotes_if_any( q{} ), 'remove_doublequotes_if_any: empty string => empty string' ) ;
10270 is( q{}, remove_doublequotes_if_any( q{""} ), 'remove_doublequotes_if_any: double-quotes => empty string' ) ;
10271 is( q{}, remove_doublequotes_if_any( q{"""} ), 'remove_doublequotes_if_any: double-quotes => empty string' ) ;
10272 is( q{}, remove_doublequotes_if_any( q{"""} ), 'remove_doublequotes_if_any: double-quotes => empty string' ) ;
10273 is( q{toto}, remove_doublequotes_if_any( q{"toto"} ), 'remove_doublequotes_if_any: "toto" => toto' ) ;
10274 is( q{toto}, remove_doublequotes_if_any( q{toto} ), 'remove_doublequotes_if_any: toto => toto' ) ;
10275 is( q{toto}, remove_doublequotes_if_any( q{to"to} ), 'remove_doublequotes_if_any: to"to => toto' ) ;
10276 is( q{toto}, remove_doublequotes_if_any( q{toto"} ), 'remove_doublequotes_if_any: toto" => toto' ) ;
10277 is( q{toto}, remove_doublequotes_if_any( q{"toto} ), 'remove_doublequotes_if_any: "toto => toto' ) ;
10278 is( q{toto}, remove_doublequotes_if_any( q{"to"to} ), 'remove_doublequotes_if_any: "to"to => toto' ) ;
10279 is( q{toto}, remove_doublequotes_if_any( q{to"to"} ), 'remove_doublequotes_if_any: to"to" => toto' ) ;
10280
10281 is( q{toto}, remove_doublequotes_if_any( q{to\"to} ), 'remove_doublequotes_if_any: to\"to => toto' ) ;
10282 is( q{toto}, remove_doublequotes_if_any( q{toto\"} ), 'remove_doublequotes_if_any: toto\" => toto' ) ;
10283 is( q{toto}, remove_doublequotes_if_any( q{\"toto} ), 'remove_doublequotes_if_any: \"toto => toto' ) ;
10284 is( q{toto}, remove_doublequotes_if_any( q{\"to\"to} ), 'remove_doublequotes_if_any: \"to\"to => toto' ) ;
10285 is( q{toto}, remove_doublequotes_if_any( q{to\"to\"} ), 'remove_doublequotes_if_any: to\"to" => toto' ) ;
10286
10287
10288 note( 'Leaving tests_remove_doublequotes_if_any()' ) ;
10289 return ;
10290}
10291
10292
10293
10294sub remove_doublequotes_if_any
10295{
10296 my $string = shift ;
10297
10298 if ( ! defined $string ) { return ; }
10299 $string =~ s/\\\"//g ;
10300 $string =~ tr/"//d ;
10301 return $string ;
10302}
10303
10304
10305# No globals here
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010306sub acls_sync
10307{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010308# https://tools.ietf.org/html/rfc4314
10309# Standard Rights:
10310# https://tools.ietf.org/html/rfc4314#section-2.1
10311
10312 my( $mysync, $h1_fold, $h2_fold ) = @_ ;
10313 if ( $mysync->{ syncacls } ) {
10314 my $h1_hash = $mysync->{imap1}->getacl($h1_fold)
10315 or myprint( "Host1: Could not getacl for $h1_fold: $EVAL_ERROR\n" ) ;
10316 my $h2_hash = $mysync->{imap2}->getacl($h2_fold)
10317 or myprint( "Host2: Could not getacl for $h2_fold: $EVAL_ERROR\n" ) ;
10318
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010319 my %users = map { ($_, 1) } ( keys %{ $h1_hash} , keys %{ $h2_hash } ) ;
10320 foreach my $user (sort keys %users ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010321 my $h1_acl = remove_doublequotes_if_any( $h1_hash->{$user} ) || '' ;
10322 my $h2_acl = remove_doublequotes_if_any( $h2_hash->{$user} ) || '' ;
10323 myprint( "Host1: user $user has acl [$h1_acl] on host1\n" ) ;
10324 myprint( "Host2: user $user has acl [$h2_acl] on host2\n" ) ;
10325 # removes surrounding double-quotes if any
10326 my $user_no_quotes = remove_doublequotes_if_any( $user ) ;
10327
10328 if ( $h1_hash->{$user}
10329 && $h2_hash->{$user}
10330 && $h1_hash->{$user} eq $h2_hash->{$user} )
10331 {
10332 myprint( "Host2: user $user_no_quotes has already the same acl, no need to set it.\n" ) ;
10333 next ;
10334 }
10335 myprint( "Host2: setting acl for folder $h2_fold user $user_no_quotes acl $h1_acl $mysync->{dry_message}\n" ) ;
10336 unless ( $mysync->{dry} ) {
10337 $mysync->{imap2}->setacl( $h2_fold, $user_no_quotes, $h1_acl )
10338 or myprint( "Could not set acl for user $user_no_quotes on host2: $EVAL_ERROR\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010339 }
10340 }
10341 }
10342 return ;
10343}
10344
10345
10346sub tests_permanentflags
10347{
10348 note( 'Entering tests_permanentflags()' ) ;
10349
10350 my $string;
10351 ok(q{} eq permanentflags(' * OK [PERMANENTFLAGS (\* \Draft \Answered)] Limited'),
10352 'permanentflags \*');
10353 ok('\Draft \Answered' eq permanentflags(' * OK [PERMANENTFLAGS (\Draft \Answered)] Limited'),
10354 'permanentflags \Draft \Answered');
10355 ok('\Draft \Answered'
10356 eq permanentflags('Blabla',
10357 ' * OK [PERMANENTFLAGS (\Draft \Answered)] Limited',
10358 'Blabla'),
10359 'permanentflags \Draft \Answered'
10360 );
10361 ok(q{} eq permanentflags('Blabla'), 'permanentflags nothing');
10362
10363 note( 'Leaving tests_permanentflags()' ) ;
10364 return ;
10365}
10366
10367sub permanentflags
10368{
10369 my @lines = @_ ;
10370
10371 foreach my $line (@lines) {
10372 if ( $line =~ m{\[PERMANENTFLAGS\s\(([^)]+?)\)\]}x ) {
10373 ( $debugflags or $sync->{ debug } ) and myprint( "permanentflags: $line" ) ;
10374 my $permanentflags = $1 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010375 if ( $permanentflags =~ m{\\\*}x )
10376 {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010377 $permanentflags = q{} ;
10378 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010379 return( $permanentflags ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010380 } ;
10381 }
10382 return( q{} ) ;
10383}
10384
10385sub tests_flags_filter
10386{
10387 note( 'Entering tests_flags_filter()' ) ;
10388
10389 ok( '\Seen' eq flags_filter('\Seen', '\Draft \Seen \Answered'), 'flags_filter ' );
10390 ok( q{} eq flags_filter('\Seen', '\Draft \Answered'), 'flags_filter ' );
10391 ok( '\Seen' eq flags_filter('\Seen', '\Seen'), 'flags_filter ' );
10392 ok( '\Seen' eq flags_filter('\Seen', ' \Seen '), 'flags_filter ' );
10393 ok( '\Seen \Draft'
10394 eq flags_filter('\Seen \Draft', '\Draft \Seen \Answered'), 'flags_filter ' );
10395 ok( '\Seen \Draft'
10396 eq flags_filter('\Seen \Draft', ' \Draft \Seen \Answered '), 'flags_filter ' );
10397
10398 note( 'Leaving tests_flags_filter()' ) ;
10399 return ;
10400}
10401
10402sub flags_filter
10403{
10404 my( $flags, $allowed_flags ) = @_ ;
10405
10406 my @flags = split /\s+/x, $flags ;
10407 my %allowed_flags = map { $_ => 1 } split q{ }, $allowed_flags ;
10408 my @flags_out = map { exists $allowed_flags{$_} ? $_ : () } @flags ;
10409
10410 my $flags_out = join q{ }, @flags_out ;
10411
10412 return( $flags_out ) ;
10413}
10414
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010415sub tests_flagscase
10416{
10417 note( 'Entering tests_flagscase()' ) ;
10418
10419 ok( '\Seen' eq flagscase( '\Seen' ), 'flagscase: \Seen -> \Seen' ) ;
10420 ok( '\Seen' eq flagscase( '\SEEN' ), 'flagscase: \SEEN -> \Seen' ) ;
10421
10422 ok( '\Seen \Draft' eq flagscase( '\SEEN \DRAFT' ), 'flagscase: \SEEN \DRAFT -> \Seen \Draft' ) ;
10423 ok( '\Draft \Seen' eq flagscase( '\DRAFT \SEEN' ), 'flagscase: \DRAFT \SEEN -> \Draft \Seen' ) ;
10424
10425 ok( '\Draft LALA \Seen' eq flagscase( '\DRAFT LALA \SEEN' ), 'flagscase: \DRAFT LALA \SEEN -> \Draft LALA \Seen' ) ;
10426 ok( '\Draft lala \Seen' eq flagscase( '\DRAFT lala \SEEN' ), 'flagscase: \DRAFT lala \SEEN -> \Draft lala \Seen' ) ;
10427
10428 note( 'Leaving tests_flagscase()' ) ;
10429 return ;
10430}
10431
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010432sub flagscase
10433{
10434 my $flags = shift ;
10435
10436 my @flags = split /\s+/x, $flags ;
10437 my %rfc_flags = map { $_ => 1 } split q{ }, '\Answered \Flagged \Deleted \Seen \Draft' ;
10438 my @flags_out = map { exists $rfc_flags{ ucsecond( lc $_ ) } ? ucsecond( lc $_ ) : $_ } @flags ;
10439
10440 my $flags_out = join q{ }, @flags_out ;
10441
10442 return( $flags_out ) ;
10443}
10444
10445
10446
10447sub tests_flags_for_host2
10448{
10449 note( 'Entering tests_flags_for_host2()' ) ;
10450
10451 is( undef, flags_for_host2( ), 'flags_for_host2: no args => undef' ) ;
10452
10453 my $mysync ;
10454 is( undef, flags_for_host2( $mysync ), 'flags_for_host2: undef => undef' ) ;
10455
10456 $mysync = { } ;
10457 is( undef, flags_for_host2( $mysync ), 'flags_for_host2: nothing => undef' ) ;
10458
10459 is( q{}, flags_for_host2( $mysync, '' ), 'flags_for_host2: no flags => empty string' ) ;
10460
10461 is( q{}, flags_for_host2( $mysync, '\Recent' ), 'flags_for_host2: \Recent => empty string' ) ;
10462
10463 is( q{\Seen}, flags_for_host2( $mysync, '\Recent \Seen' ), 'flags_for_host2: \Recent \Seen => \Seen' ) ;
10464
10465 is( q{\Deleted \Seen}, flags_for_host2( $mysync, '\Deleted \Recent \Seen' ), 'flags_for_host2: \Deleted \Recent \Seen => \Deleted \Seen' ) ;
10466
10467 $mysync->{ flagscase } = 0 ;
10468 is( q{\DELETED \Seen}, flags_for_host2( $mysync, '\DELETED \Seen' ), 'flags_for_host2: flagscase = 0 \DELETED \Seen => \DELETED \Seen' ) ;
10469
10470 $mysync->{ flagscase } = 1 ;
10471 is( q{\Deleted \Seen}, flags_for_host2( $mysync, '\DELETED \Seen' ), 'flags_for_host2: flagscase = 1 \DELETED \Seen => \Deleted \Seen' ) ;
10472
10473 $mysync->{ filterflags } = 0 ;
10474 is( q{\Seen \Blabla}, flags_for_host2( $mysync, '\Seen \Blabla', '\Seen \Junk' ), 'flags_for_host2: filterflags = 0 \Seen \Blabla among \Seen \Junk => \Seen \Blabla' ) ;
10475
10476 $mysync->{ filterflags } = 1 ;
10477 is( q{\Seen}, flags_for_host2( $mysync, '\Seen \Blabla', '\Seen \Junk' ), 'flags_for_host2: filterflags = 1 \Seen \Blabla among \Seen \Junk => \Seen' ) ;
10478
10479 $mysync->{ filterflags } = 1 ;
10480 is( q{\Seen \Blabla}, flags_for_host2( $mysync, '\Seen \Blabla', '' ), 'flags_for_host2: filterflags = 1 \Seen \Blabla among "" => \Seen \Blabla' ) ;
10481
10482
10483 note( 'Leaving tests_flags_for_host2()' ) ;
10484 return ;
10485}
10486
10487
10488
10489
10490sub flags_for_host2
10491{
10492 my $mysync = shift ;
10493 my $h1_flags = shift ;
10494 my $permanentflags2 = shift ;
10495
10496 if ( ! all_defined( $mysync, $h1_flags ) ) { return ; } ;
10497
10498 # RFC 2060: This flag can not be altered by any client
10499 $h1_flags =~ s@\\Recent\s?@@xgi ;
10500
10501 my $h1_flags_re ;
10502 if ( $mysync->{ regexflag } and defined( $h1_flags_re = regexflags( $mysync, $h1_flags ) ) ) {
10503 $h1_flags = $h1_flags_re ;
10504 }
10505
10506 if ( $mysync->{ flagscase } )
10507 {
10508 $h1_flags = flagscase( $h1_flags ) ;
10509 }
10510
10511 if ( $permanentflags2 and $mysync->{ filterflags } )
10512 {
10513 $h1_flags = flags_filter( $h1_flags, $permanentflags2 ) ;
10514 }
10515
10516 return( $h1_flags ) ;
10517}
10518
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010519
10520
10521sub ucsecond
10522{
10523 my $string = shift ;
10524 my $output ;
10525
10526 return( $string ) if ( 1 >= length $string ) ;
10527
10528 $output = ( substr( $string, 0, 1) ) . ( uc substr $string, 1, 1 ) . ( substr $string, 2 ) ;
10529 #myprint( "UUU $string -> $output\n" ) ;
10530 return( $output ) ;
10531}
10532
10533
10534sub tests_ucsecond
10535{
10536 note( 'Entering tests_ucsecond()' ) ;
10537
10538 ok( 'aBcde' eq ucsecond( 'abcde' ), 'ucsecond: abcde -> aBcde' ) ;
10539 ok( 'ABCDE' eq ucsecond( 'ABCDE' ), 'ucsecond: ABCDE -> ABCDE' ) ;
10540 ok( 'ABCDE' eq ucsecond( 'AbCDE' ), 'ucsecond: AbCDE -> ABCDE' ) ;
10541 ok( 'ABCde' eq ucsecond( 'AbCde' ), 'ucsecond: AbCde -> ABCde' ) ;
10542 ok( 'A' eq ucsecond( 'A' ), 'ucsecond: A -> A' ) ;
10543 ok( 'AB' eq ucsecond( 'Ab' ), 'ucsecond: Ab -> AB' ) ;
10544 ok( '\B' eq ucsecond( '\b' ), 'ucsecond: \b -> \B' ) ;
10545 ok( '\Bcde' eq ucsecond( '\bcde' ), 'ucsecond: \bcde -> \Bcde' ) ;
10546
10547 note( 'Leaving tests_ucsecond()' ) ;
10548 return ;
10549}
10550
10551
10552sub select_msgs
10553{
10554 my ( $imap, $msgs_all_hash_ref, $search_cmd, $abletosearch, $folder ) = @_ ;
10555 my ( @msgs ) ;
10556
10557 if ( $abletosearch ) {
10558 @msgs = select_msgs_by_search( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) ;
10559 }else{
10560 @msgs = select_msgs_by_fetch( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) ;
10561 }
10562 return( @msgs ) ;
10563
10564}
10565
10566sub select_msgs_by_search
10567{
10568 my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ;
10569 my ( @msgs, @msgs_all ) ;
10570
10571 # Need to have the whole list in msgs_all_hash_ref
10572 # without calling messages() several times.
10573 # Need all messages list to avoid deleting useful cache part
10574 # in case of --search or --minage or --maxage
10575
10576 if ( ( defined $msgs_all_hash_ref and $usecache )
10577 or ( not defined $maxage and not defined $minage and not defined $search_cmd )
10578 ) {
10579
10580 $debugdev and myprint( "Calling messages()\n" ) ;
10581 @msgs_all = $imap->messages( ) ;
10582
10583 return if ( $#msgs_all == 0 && !defined $msgs_all[0] ) ;
10584
10585 if ( defined $msgs_all_hash_ref ) {
10586 @{ $msgs_all_hash_ref }{ @msgs_all } = () ;
10587 }
10588 # return all messages
10589 if ( not defined $maxage and not defined $minage and not defined $search_cmd ) {
10590 return( @msgs_all ) ;
10591 }
10592 }
10593
10594 if ( defined $search_cmd ) {
10595 @msgs = $imap->search( $search_cmd ) ;
10596 return( @msgs ) ;
10597 }
10598
10599 # we are here only if $maxage or $minage is defined
10600 @msgs = select_msgs_by_age( $imap ) ;
10601 return( @msgs );
10602}
10603
10604
10605sub select_msgs_by_fetch
10606{
10607 my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ;
10608 my ( @msgs, @msgs_all, %fetch ) ;
10609
10610 # Need to have the whole list in msgs_all_hash_ref
10611 # without calling messages() several times.
10612 # Need all messages list to avoid deleting useful cache part
10613 # in case of --search or --minage or --maxage
10614
10615
10616 $debugdev and myprint( "Calling fetch_hash()\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010617 my $fetch_hash_uids = $fetch_hash_set || "1:*" ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010618 %fetch = %{$imap->fetch_hash( $fetch_hash_uids, 'INTERNALDATE' ) } ;
10619
10620 @msgs_all = sort { $a <=> $b } keys %fetch ;
10621 $debugdev and myprint( "Done fetch_hash()\n" ) ;
10622
10623 return if ( $#msgs_all == 0 && !defined $msgs_all[0] ) ;
10624
10625 if ( defined $msgs_all_hash_ref ) {
10626 @{ $msgs_all_hash_ref }{ @msgs_all } = () ;
10627 }
10628 # return all messages
10629 if ( not defined $maxage and not defined $minage and not defined $search_cmd ) {
10630 return( @msgs_all ) ;
10631 }
10632
10633 if ( defined $search_cmd ) {
10634 myprint( "Warning: strange to see --search with --noabletosearch, an error can happen\n" ) ;
10635 @msgs = $imap->search( $search_cmd ) ;
10636 return( @msgs ) ;
10637 }
10638
10639 # we are here only if $maxage or $minage is defined
10640 my( @max, @min, $maxage_epoch, $minage_epoch ) ;
10641 if ( defined $maxage ) { $maxage_epoch = $timestart_int - $NB_SECONDS_IN_A_DAY * $maxage ; }
10642 if ( defined $minage ) { $minage_epoch = $timestart_int - $NB_SECONDS_IN_A_DAY * $minage ; }
10643 foreach my $msg ( @msgs_all ) {
10644 my $idate = $fetch{ $msg }->{'INTERNALDATE'} ;
10645 #myprint( "$idate\n" ) ;
10646 if ( defined $maxage and ( epoch( $idate ) >= $maxage_epoch ) ) {
10647 push @max, $msg ;
10648 }
10649 if ( defined $minage and ( epoch( $idate ) <= $minage_epoch ) ) {
10650 push @min, $msg ;
10651 }
10652 }
10653 @msgs = msgs_from_maxmin( \@max, \@min ) ;
10654 return( @msgs ) ;
10655}
10656
10657sub select_msgs_by_age
10658{
10659 my( $imap ) = @_ ;
10660
10661 my( @max, @min, @msgs, @inter, @union ) ;
10662
10663 if ( defined $maxage ) {
10664 @max = $imap->sentsince( $timestart_int - $NB_SECONDS_IN_A_DAY * $maxage ) ;
10665 }
10666 if ( defined $minage ) {
10667 @min = $imap->sentbefore( $timestart_int - $NB_SECONDS_IN_A_DAY * $minage ) ;
10668 }
10669
10670 @msgs = msgs_from_maxmin( \@max, \@min ) ;
10671 return( @msgs ) ;
10672}
10673
10674sub msgs_from_maxmin
10675{
10676 my( $max_ref, $min_ref ) = @_ ;
10677 my( @max, @min, @msgs, @inter, @union ) ;
10678
10679 @max = @{ $max_ref } ;
10680 @min = @{ $min_ref } ;
10681
10682 SWITCH: {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010683 if ( not ( defined $minage or defined $maxage ) )
10684 {
10685 return ;
10686 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010687 unless( defined $minage ) { @msgs = @max ; last SWITCH } ;
10688 unless( defined $maxage ) { @msgs = @min ; last SWITCH } ;
10689 my ( %union, %inter ) ;
10690 foreach my $m ( @min, @max ) { $union{ $m }++ && $inter{ $m }++ }
10691 @inter = sort { $a <=> $b } keys %inter ;
10692 @union = sort { $a <=> $b } keys %union ;
10693 # normal case
10694 if ( $minage <= $maxage ) { @msgs = @inter ; last SWITCH } ;
10695 # just exclude messages between
10696 if ( $minage > $maxage ) { @msgs = @union ; last SWITCH } ;
10697
10698 }
10699 return( @msgs ) ;
10700}
10701
10702sub tests_msgs_from_maxmin
10703{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010704 note( 'Entering tests_msgs_from_maxmin()' ) ;
10705
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010706
10707 my @msgs ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010708
10709 # no maxage nor minage
10710 @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
10711 is_deeply( [ ], \@msgs , 'msgs_from_maxmin: no maxage nor minage => empty result' ) ;
10712
10713 # maxage alone
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010714 $maxage = $NUMBER_200 ;
10715 @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010716 is_deeply( [ '1', '2' ], \@msgs , 'msgs_from_maxmin: maxage++' ) ;
10717
10718 # maxage > minage -> intersection
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010719 $minage = $NUMBER_100 ;
10720 @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010721 is_deeply( [ '2' ], \@msgs , 'msgs_from_maxmin: -maxage++minage-' ) ;
10722
10723 # maxage < minage -> union
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010724 $minage = $NUMBER_300 ;
10725 @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010726 is_deeply( [ '1', '2', '3' ], \@msgs, 'msgs_from_maxmin: ++maxage-minage++' ) ;
10727
10728
10729 # minage alone
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010730 $maxage = undef ;
10731 @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010732 is_deeply( [ '2', '3' ], \@msgs, 'msgs_from_maxmin: ++minage-' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010733
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010734
10735 note( 'Leaving tests_msgs_from_maxmin()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010736 return ;
10737}
10738
10739sub tests_info_date_from_uid
10740{
10741 note( 'Entering tests_info_date_from_uid()' ) ;
10742 note( 'Leaving tests_info_date_from_uid()' ) ;
10743
10744 return ;
10745}
10746
10747sub info_date_from_uid
10748{
10749
10750 #my $first_uid = $msgs_all[ 0 ] ;
10751 #my $first_idate = $fetch{ $first_uid }->{'INTERNALDATE'} ;
10752 #my $first_epoch = epoch( $first_idate ) ;
10753 #my $first_days = ( $timestart_int - $first_epoch ) / $NB_SECONDS_IN_A_DAY ;
10754 #myprint( "\nOldest msg has UID $first_uid INTERNALDATE $first_idate EPOCH $first_epoch DAYS AGO $first_days\n" ) ;
10755}
10756
10757
10758sub lastuid
10759{
10760 my $imap = shift ;
10761 my $folder = shift ;
10762 my $lastuid_guess = shift ;
10763 my $lastuid ;
10764
10765 # rfc3501: The only reliable way to identify recent messages is to
10766 # look at message flags to see which have the \Recent flag
10767 # set, or to do a SEARCH RECENT.
10768 # SEARCH RECENT doesn't work this way on courrier.
10769
10770 my @recent_messages ;
10771 # SEARCH RECENT for each transfer can be expensive with a big folder
10772 # Call commented for now
10773 #@recent_messages = $imap->recent( ) ;
10774 #myprint( "Recent: @recent_messages\n" ) ;
10775
10776 my $max_recent ;
10777 $max_recent = max( @recent_messages ) ;
10778
10779 if ( defined $max_recent and ($lastuid_guess <= $max_recent ) ) {
10780 $lastuid = $max_recent ;
10781 }else{
10782 $lastuid = $lastuid_guess
10783 }
10784 return( $lastuid ) ;
10785}
10786
10787sub size_filtered
10788{
10789 my( $h1_size, $h1_msg, $h1_fold, $h2_fold ) = @_ ;
10790
10791 $h1_size = 0 if ( ! $h1_size ) ; # null if empty or undef
10792 if ( defined $sync->{ maxsize } and $h1_size > $sync->{ maxsize } ) {
10793 myprint( "msg $h1_fold/$h1_msg skipped ($h1_size exceeds maxsize limit $sync->{ maxsize } bytes)\n" ) ;
10794 $sync->{ total_bytes_skipped } += $h1_size;
10795 $sync->{ nb_msg_skipped } += 1;
10796 return( 1 ) ;
10797 }
10798 if ( defined $minsize and $h1_size <= $minsize ) {
10799 myprint( "msg $h1_fold/$h1_msg skipped ($h1_size smaller than minsize $minsize bytes)\n" ) ;
10800 $sync->{ total_bytes_skipped } += $h1_size;
10801 $sync->{ nb_msg_skipped } += 1;
10802 return( 1 ) ;
10803 }
10804 return( 0 ) ;
10805}
10806
10807sub message_exists
10808{
10809 my( $imap, $msg ) = @_ ;
10810 return( 1 ) if not $imap->Uid( ) ;
10811
10812 my $search_uid ;
10813 ( $search_uid ) = $imap->search( "UID $msg" ) ;
10814 #myprint( "$search ? $msg\n" ) ;
10815 return( 1 ) if ( $search_uid eq $msg ) ;
10816 return( 0 ) ;
10817}
10818
10819
10820# Globals
10821# $sync->{ total_bytes_skipped }
10822# $sync->{ nb_msg_skipped }
10823# $mysync->{ h1_nb_msg_processed }
10824sub stats_update_skip_message
10825{
10826 my $mysync = shift ; # to be used
10827 my $h1_size = shift ;
10828
10829 $mysync->{ total_bytes_skipped } += $h1_size ;
10830 $mysync->{ nb_msg_skipped } += 1 ;
10831 $mysync->{ h1_nb_msg_processed } +=1 ;
10832 return ;
10833}
10834
10835sub copy_message
10836{
10837 # copy
10838
10839 my ( $mysync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) = @_ ;
10840 ( $mysync->{ debug } or $mysync->{dry} )
10841 and myprint( "msg $h1_fold/$h1_msg copying to $h2_fold $mysync->{dry_message} " . eta( $mysync ) . "\n" ) ;
10842
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010843 if ( $mysync->{dry1} )
10844 {
10845 $mysync->{ h1_nb_msg_processed } +=1 ;
10846 $nb_msg_skipped_dry_mode += 1 ;
10847 return ;
10848 }
10849
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010850 my $h1_size = $h1_fir_ref->{$h1_msg}->{'RFC822.SIZE'} || 0 ;
10851 my $h1_flags = $h1_fir_ref->{$h1_msg}->{'FLAGS'} || q{} ;
10852 my $h1_idate = $h1_fir_ref->{$h1_msg}->{'INTERNALDATE'} || q{} ;
10853
10854
10855 if ( size_filtered( $h1_size, $h1_msg, $h1_fold, $h2_fold ) ) {
10856 $mysync->{ h1_nb_msg_processed } +=1 ;
10857 return ;
10858 }
10859
10860 debugsleep( $mysync ) ;
10861 myprint( "- msg $h1_fold/$h1_msg S[$h1_size] F[$h1_flags] I[$h1_idate] has RFC822.SIZE null!\n" ) if ( ! $h1_size ) ;
10862
10863 if ( $checkmessageexists and not message_exists( $mysync->{imap1}, $h1_msg ) ) {
10864 stats_update_skip_message( $mysync, $h1_size ) ;
10865 return ;
10866 }
10867 myprint( debugmemory( $mysync, " at C1" ) ) ;
10868
10869 my ( $string, $string_len ) ;
10870 ( $string_len ) = message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, \$string ) ;
10871
10872 myprint( debugmemory( $mysync, " at C2" ) ) ;
10873
10874 # not defined or empty $string
10875 if ( ( not $string ) or ( not $string_len ) ) {
10876 myprint( "- msg $h1_fold/$h1_msg skipped.\n" ) ;
10877 stats_update_skip_message( $mysync, $h1_size ) ;
10878 return ;
10879 }
10880
10881 # Lines too long (or not enough) => do no copy or fix
10882 if ( ( defined $maxlinelength ) or ( defined $minmaxlinelength ) ) {
10883 $string = linelengthstuff( $string, $h1_fold, $h1_msg, $string_len, $h1_size, $h1_flags, $h1_idate ) ;
10884 if ( not defined $string ) {
10885 stats_update_skip_message( $mysync, $h1_size ) ;
10886 return ;
10887 }
10888 }
10889
10890 my $h1_date = date_for_host2( $h1_msg, $h1_idate ) ;
10891
10892 ( $mysync->{ debug } or $debugflags ) and
10893 myprint( "Host1: flags init msg $h1_fold/$h1_msg date [$h1_date] flags [$h1_flags] size [$h1_size]\n" ) ;
10894
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010895 $h1_flags = flags_for_host2( $mysync, $h1_flags, $permanentflags2 ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010896
10897 ( $mysync->{ debug } or $debugflags ) and
10898 myprint( "Host1: flags filt msg $h1_fold/$h1_msg date [$h1_date] flags [$h1_flags] size [$h1_size]\n" ) ;
10899
10900 $h1_date = undef if ( $h1_date eq q{} ) ;
10901
10902 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 ) ;
10903
10904
10905
10906 if ( $new_id and $syncflagsaftercopy ) {
10907 sync_flags_after_copy( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $new_id, $permanentflags2 ) ;
10908 }
10909
10910 myprint( debugmemory( $mysync, " at C3" ) ) ;
10911
10912 return $new_id ;
10913}
10914
10915
10916
10917sub linelengthstuff
10918{
10919 my( $string, $h1_fold, $h1_msg, $string_len, $h1_size, $h1_flags, $h1_idate ) = @_ ;
10920 my $maxlinelength_string = max_line_length( $string ) ;
10921 $debugmaxlinelength and myprint( "msg $h1_fold/$h1_msg maxlinelength: $maxlinelength_string\n" ) ;
10922
10923 if ( ( defined $minmaxlinelength ) and ( $maxlinelength_string <= $minmaxlinelength ) ) {
10924 my $subject = subject( $string ) ;
10925 $debugdev and myprint( "- msg $h1_fold/$h1_msg skipped S[$h1_size] F[$h1_flags] I[$h1_idate] "
10926 . "(Subject:[$subject]) (max line length under minmaxlinelength $minmaxlinelength bytes)\n" ) ;
10927 return ;
10928 }
10929
10930 if ( ( defined $maxlinelength ) and ( $maxlinelength_string > $maxlinelength ) ) {
10931 my $subject = subject( $string ) ;
10932 if ( $maxlinelengthcmd ) {
10933 $string = pipemess( $string, $maxlinelengthcmd ) ;
10934 # string undef means something was bad.
10935 if ( not ( defined $string ) ) {
10936 myprint( "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate] "
10937 . "(Subject:[$subject]) could not be successfully transformed by --maxlinelengthcmd option\n" ) ;
10938 return ;
10939 }else{
10940 return $string ;
10941 }
10942 }
10943 myprint( "- msg $h1_fold/$h1_msg skipped S[$h1_size] F[$h1_flags] I[$h1_idate] "
10944 . "(Subject:[$subject]) (line length exceeds maxlinelength $maxlinelength bytes)\n" ) ;
10945 return ;
10946 }
10947 return $string ;
10948}
10949
10950
10951sub message_for_host2
10952{
10953
10954# global variable list:
10955# @skipmess
10956# @regexmess
10957# @pipemess
10958# $debugcontent
10959# $debug
10960#
10961# API current
10962#
10963# at failure:
10964# * return nothing ( will then be undef or () )
10965# * $string_ref content is undef or empty
10966# at success:
10967# * return string length ($string_ref content length)
10968# * $string_ref content filled with message
10969
10970# API future
10971#
10972#
10973 my ( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ) = @_ ;
10974
10975 # abort when missing a parameter
10976 if ( ( ! $mysync ) or ( ! $h1_msg ) or ( ! $h1_fold ) or ( ! defined $h1_size )
10977 or ( ! defined $h1_flags) or ( ! defined $h1_idate )
10978 or ( ! $h1_fir_ref) or ( ! $string_ref ) )
10979 {
10980 return ;
10981 }
10982
10983 myprint( debugmemory( $mysync, " at M1" ) ) ;
10984
10985
10986 my $string_ok = $mysync->{imap1}->message_to_file( $string_ref, $h1_msg ) ;
10987
10988 myprint( debugmemory( $mysync, " at M2" ) ) ;
10989
10990 my $string_len = length_ref( $string_ref ) ;
10991
10992
10993 unless ( defined $string_ok and $string_len ) {
10994 # undef or 0 length
10995 my $error = join q{},
10996 "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate] could not be fetched: ",
10997 $mysync->{imap1}->LastError || q{}, "\n" ;
10998 errors_incr( $mysync, $error ) ;
10999 $mysync->{ h1_nb_msg_processed } +=1 ;
11000 return ;
11001 }
11002
11003 if ( @skipmess ) {
11004 my $match = skipmess( ${ $string_ref } ) ;
11005 # string undef means the eval regex was bad.
11006 if ( not ( defined $match ) ) {
11007 myprint(
11008 "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
11009 . " could not be skipped by --skipmess option, bad regex\n" ) ;
11010 return ;
11011 }
11012 if ( $match ) {
11013 my $subject = subject( ${ $string_ref } ) ;
11014 myprint( "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
11015 . " (Subject:[$subject]) skipped by --skipmess\n" ) ;
11016 return ;
11017 }
11018 }
11019
11020 if ( @regexmess ) {
11021 ${ $string_ref } = regexmess( ${ $string_ref } ) ;
11022 # string undef means the eval regex was bad.
11023 if ( not ( defined ${ $string_ref } ) ) {
11024 myprint(
11025 "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
11026 . " could not be transformed by --regexmess\n" ) ;
11027 return ;
11028 }
11029 }
11030
11031 if ( @pipemess ) {
11032 ${ $string_ref } = pipemess( ${ $string_ref }, @pipemess ) ;
11033 # string undef means something was bad.
11034 if ( not ( defined ${ $string_ref } ) ) {
11035 myprint(
11036 "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
11037 . " could not be successfully transformed by --pipemess option\n" ) ;
11038 return ;
11039 }
11040 }
11041
11042 if ( $mysync->{addheader} and defined $h1_fir_ref->{$h1_msg}->{'NO_HEADER'} ) {
11043 my $header = add_header( $h1_msg ) ;
11044 $mysync->{ debug } and myprint( "msg $h1_fold/$h1_msg adding custom header [$header]\n" ) ;
11045 ${ $string_ref } = $header . "\r\n" . ${ $string_ref } ;
11046 }
11047
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011048 if ( ( defined $mysync->{ truncmess } ) and is_integer( $mysync->{ truncmess } ) )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011049 {
11050 ${ $string_ref } = truncmess( ${ $string_ref }, $mysync->{ truncmess } ) ;
11051 }
11052
11053 $string_len = length_ref( $string_ref ) ;
11054
11055 $debugcontent and myprint(
11056 q{=} x $STD_CHAR_PER_LINE, "\n",
11057 "F message content begin next line ($string_len characters long)\n",
11058 ${ $string_ref },
11059 "\nF message content ended on previous line\n", q{=} x $STD_CHAR_PER_LINE, "\n" ) ;
11060
11061 myprint( debugmemory( $mysync, " at M3" ) ) ;
11062
11063 return $string_len ;
11064}
11065
11066sub tests_truncmess
11067{
11068 note( 'Entering tests_truncmess()' ) ;
11069
11070 is( undef, truncmess( ), 'truncmess: no args => undef' ) ;
11071 is( 'abc', truncmess( 'abc' ), 'truncmess: abc => abc' ) ;
11072 is( 'ab', truncmess( 'abc', 2 ), 'truncmess: abc 2 => ab' ) ;
11073 is( 'abc', truncmess( 'abc', 3 ), 'truncmess: abc 3 => abc' ) ;
11074 is( 'abc', truncmess( 'abc', 4 ), 'truncmess: abc 4 => abc' ) ;
11075 is( '12345', truncmess( "123456789\n", 5 ), 'truncmess: "123456789\n", 5 => 12345' ) ;
11076 is( "123456789\n" x 5000, truncmess( "123456789\n" x 100000, 50000 ), 'truncmess: "123456789\n" x 100000, 50000 => "123456789\n" x 5000' ) ;
11077 note( 'Leaving tests_truncmess()' ) ;
11078 return ;
11079}
11080
11081sub truncmess
11082{
11083 my $string = shift ;
11084 my $length = shift ;
11085
11086 if ( not defined $string ) { return ; }
11087 if ( not defined $length ) { return $string ; }
11088
11089 $string = substr $string, 0, $length ;
11090 return $string ;
11091}
11092
11093sub tests_message_for_host2
11094{
11095 note( 'Entering tests_message_for_host2()' ) ;
11096
11097
11098 my ( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ) ;
11099
11100 is( undef, message_for_host2( ), q{message_for_host2: no args} ) ;
11101 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} ) ;
11102
11103 require_ok( "Test::MockObject" ) ;
11104 my $imapT = Test::MockObject->new( ) ;
11105 $mysync->{imap1} = $imapT ;
11106 my $string ;
11107
11108 $h1_msg = 1 ;
11109 $h1_fold = 'FoldFoo';
11110 $h1_size = 9 ;
11111 $h1_flags = q{} ;
11112 $h1_idate = '10-Jul-2015 09:00:00 +0200' ;
11113 $h1_fir_ref = {} ;
11114 $string_ref = \$string ;
11115 $imapT->mock( 'message_to_file',
11116 sub {
11117 my ( $imap, $mystring_ref, $msg ) = @_ ;
11118 ${$mystring_ref} = 'blablabla' ;
11119 return length ${$mystring_ref} ;
11120 }
11121 ) ;
11122 is( 9, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
11123 q{message_for_host2: msg 1 == "blablabla", length} ) ;
11124 is( 'blablabla', $string, q{message_for_host2: msg 1 == "blablabla", value} ) ;
11125
11126 # so far so good
11127 # now the --pipemess stuff
11128
11129 SKIP: {
11130 Readonly my $NB_WIN_tests_message_for_host2 => 0 ;
11131 skip( 'Not on MSWin32', $NB_WIN_tests_message_for_host2 ) if ('MSWin32' ne $OSNAME) ;
11132 # Windows
11133 # "type" command does not accept redirection of STDIN with <
11134 # "sort" does
11135
11136 } ;
11137
11138 SKIP: {
11139 Readonly my $NB_UNX_tests_message_for_host2 => 6 ;
11140 skip( 'Not on Unix', $NB_UNX_tests_message_for_host2 ) if ('MSWin32' eq $OSNAME) ;
11141 # Unix
11142
11143 # no change by cat
11144 @pipemess = ( 'cat' ) ;
11145 is( 9, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
11146 q{message_for_host2: --pipemess 'cat', length} ) ;
11147 is( 'blablabla', $string, q{message_for_host2: --pipemess 'cat', value} ) ;
11148
11149
11150 # failure by false
11151 @pipemess = ( 'false' ) ;
11152 is( undef, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
11153 q{message_for_host2: --pipemess 'false', length} ) ;
11154 is( undef, $string, q{message_for_host2: --pipemess 'false', value} ) ;
11155
11156 # failure by true since no output
11157 @pipemess = ( 'true' ) ;
11158 is( undef, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
11159 q{message_for_host2: --pipemess 'true', length} ) ;
11160 is( undef, $string, q{message_for_host2: --pipemess 'true', value} ) ;
11161 }
11162
11163 note( 'Leaving tests_message_for_host2()' ) ;
11164 return ;
11165}
11166
11167sub tests_labels_remove_subfolder1
11168{
11169 note( 'Entering tests_labels_remove_subfolder1()' ) ;
11170 is( undef, labels_remove_subfolder1( ), 'labels_remove_subfolder1: no parameters => undef' ) ;
11171 is( 'Blabla', labels_remove_subfolder1( 'Blabla' ), 'labels_remove_subfolder1: one parameter Blabla => Blabla' ) ;
11172 is( 'Blan blue', labels_remove_subfolder1( 'Blan blue' ), 'labels_remove_subfolder1: one parameter Blan blue => Blan blue' ) ;
11173 is( '\Bla "Blan blan" Blabla', labels_remove_subfolder1( '\Bla "Blan blan" Blabla' ),
11174 'labels_remove_subfolder1: one parameter \Bla "Blan blan" Blabla => \Bla "Blan blan" Blabla' ) ;
11175
11176 is( 'Bla', labels_remove_subfolder1( 'Subf/Bla', 'Subf' ), 'labels_remove_subfolder1: Subf/Bla Subf => "Bla"' ) ;
11177
11178
11179 is( '"\\\\Bla"', labels_remove_subfolder1( '"\\\\Bla"', 'Subf' ), 'labels_remove_subfolder1: "\\\\Bla" Subf => "\\\\Bla"' ) ;
11180
11181 is( 'Bla Kii', labels_remove_subfolder1( 'Subf/Bla Subf/Kii', 'Subf' ),
11182 'labels_remove_subfolder1: Subf/Bla Subf/Kii, Subf => "Bla" "Kii"' ) ;
11183
11184 is( '"\\\\Bla" Kii', labels_remove_subfolder1( '"\\\\Bla" Subf/Kii', 'Subf' ),
11185 'labels_remove_subfolder1: "\\\\Bla" Subf/Kii Subf => "\\\\Bla" Kii' ) ;
11186
11187 is( '"Blan blan"', labels_remove_subfolder1( '"Subf/Blan blan"', 'Subf' ),
11188 'labels_remove_subfolder1: "Subf/Blan blan" Subf => "Blan blan"' ) ;
11189
11190 is( '"\\\\Loo" "Blan blan" Kii', labels_remove_subfolder1( '"\\\\Loo" "Subf/Blan blan" Subf/Kii', 'Subf' ),
11191 'labels_remove_subfolder1: "\\\\Loo" "Subf/Blan blan" Subf/Kii + Subf => "\\\\Loo" "Blan blan" Kii' ) ;
11192
11193 is( '"\\\\Inbox"', labels_remove_subfolder1( 'Subf/INBOX', 'Subf' ),
11194 'labels_remove_subfolder1: Subf/INBOX + Subf => "\\\\Inbox"' ) ;
11195
11196 is( '"\\\\Loo" "Blan blan" Kii "\\\\Inbox"', labels_remove_subfolder1( '"\\\\Loo" "Subf/Blan blan" Subf/Kii Subf/INBOX', 'Subf' ),
11197 'labels_remove_subfolder1: "\\\\Loo" "Subf/Blan blan" Subf/Kii Subf/INBOX + Subf => "\\\\Loo" "Blan blan" Kii "\\\\Inbox"' ) ;
11198
11199
11200 note( 'Leaving tests_labels_remove_subfolder1()' ) ;
11201 return ;
11202}
11203
11204
11205
11206sub labels_remove_subfolder1
11207{
11208 my $labels = shift ;
11209 my $subfolder1 = shift ;
11210
11211 if ( not defined $labels ) { return ; }
11212 if ( not defined $subfolder1 ) { return $labels ; }
11213
11214 my @labels = quotewords('\s+', 1, $labels ) ;
11215 #myprint( "@labels\n" ) ;
11216 my @labels_subfolder2 ;
11217
11218 foreach my $label ( @labels )
11219 {
11220 if ( $label =~ m{zzzzzzzzzz} )
11221 {
11222 # \Seen \Deleted ... stay the same
11223 push @labels_subfolder2, $label ;
11224 }
11225 else
11226 {
11227 # Remove surrounding quotes if any, to add them again in case of space
11228 $label = join( q{}, quotewords('\s+', 0, $label ) ) ;
11229 $label =~ s{$subfolder1/?}{} ;
11230 if ( 'INBOX' eq $label )
11231 {
11232 push @labels_subfolder2, q{"\\\\Inbox"} ;
11233 }
11234 elsif ( $label =~ m{\\} )
11235 {
11236 push @labels_subfolder2, qq{"\\$label"} ;
11237 }
11238 elsif ( $label =~ m{ } )
11239 {
11240 push @labels_subfolder2, qq{"$label"} ;
11241 }
11242 else
11243 {
11244 push @labels_subfolder2, $label ;
11245 }
11246 }
11247 }
11248
11249 my $labels_subfolder2 = join( ' ', sort uniq( @labels_subfolder2 ) ) ;
11250
11251 return $labels_subfolder2 ;
11252}
11253
11254sub tests_labels_remove_special
11255{
11256 note( 'Entering tests_labels_remove_special()' ) ;
11257
11258 is( undef, labels_remove_special( ), 'labels_remove_special: no parameters => undef' ) ;
11259 is( q{}, labels_remove_special( q{} ), 'labels_remove_special: empty string => empty string' ) ;
11260 is( q{}, labels_remove_special( '"\\\\Inbox"' ), 'labels_remove_special:"\\\\Inbox" => empty string' ) ;
11261 is( q{}, labels_remove_special( '"\\\\Inbox" "\\\\Starred"' ), 'labels_remove_special:"\\\\Inbox" "\\\\Starred" => empty string' ) ;
11262 is( 'Bar Foo', labels_remove_special( 'Foo Bar' ), 'labels_remove_special:Foo Bar => Bar Foo' ) ;
11263 is( 'Bar Foo', labels_remove_special( 'Foo Bar "\\\\Inbox"' ), 'labels_remove_special:Foo Bar "\\\\Inbox" => Bar Foo' ) ;
11264 note( 'Leaving tests_labels_remove_special()' ) ;
11265 return ;
11266}
11267
11268
11269
11270
11271sub labels_remove_special
11272{
11273 my $labels = shift ;
11274
11275 if ( not defined $labels ) { return ; }
11276
11277 my @labels = quotewords('\s+', 1, $labels ) ;
11278 myprint( "labels before remove_non_folded: @labels\n" ) ;
11279 my @labels_remove_special ;
11280
11281 foreach my $label ( @labels )
11282 {
11283 if ( $label =~ m{^\"\\\\} )
11284 {
11285 # not kept
11286 }
11287 else
11288 {
11289 push @labels_remove_special, $label ;
11290 }
11291 }
11292
11293 my $labels_remove_special = join( ' ', sort @labels_remove_special ) ;
11294
11295 return $labels_remove_special ;
11296}
11297
11298
11299sub tests_labels_add_subfolder2
11300{
11301 note( 'Entering tests_labels_add_subfolder2()' ) ;
11302 is( undef, labels_add_subfolder2( ), 'labels_add_subfolder2: no parameters => undef' ) ;
11303 is( 'Blabla', labels_add_subfolder2( 'Blabla' ), 'labels_add_subfolder2: one parameter Blabla => Blabla' ) ;
11304 is( 'Blan blue', labels_add_subfolder2( 'Blan blue' ), 'labels_add_subfolder2: one parameter Blan blue => Blan blue' ) ;
11305 is( '\Bla "Blan blan" Blabla', labels_add_subfolder2( '\Bla "Blan blan" Blabla' ),
11306 'labels_add_subfolder2: one parameter \Bla "Blan blan" Blabla => \Bla "Blan blan" Blabla' ) ;
11307
11308 is( 'Subf/Bla', labels_add_subfolder2( 'Bla', 'Subf' ), 'labels_add_subfolder2: Bla Subf => "Subf/Bla"' ) ;
11309
11310
11311 is( 'Subf/\Bla', labels_add_subfolder2( '\\\\Bla', 'Subf' ), 'labels_add_subfolder2: \Bla Subf => \Bla' ) ;
11312
11313 is( 'Subf/Bla Subf/Kii', labels_add_subfolder2( 'Bla Kii', 'Subf' ),
11314 'labels_add_subfolder2: Bla Kii Subf => "Subf/Bla" "Subf/Kii"' ) ;
11315
11316 is( 'Subf/Kii Subf/\Bla', labels_add_subfolder2( '\\\\Bla Kii', 'Subf' ),
11317 'labels_add_subfolder2: \Bla Kii Subf => \Bla Subf/Kii' ) ;
11318
11319 is( '"Subf/Blan blan"', labels_add_subfolder2( '"Blan blan"', 'Subf' ),
11320 'labels_add_subfolder2: "Blan blan" Subf => "Subf/Blan blan"' ) ;
11321
11322 is( '"Subf/Blan blan" Subf/Kii Subf/\Loo', labels_add_subfolder2( '\\\\Loo "Blan blan" Kii', 'Subf' ),
11323 'labels_add_subfolder2: \Loo "Blan blan" Kii + Subf => "Subf/Blan blan" Subf/Kii Subf/\Loo' ) ;
11324
11325 # "\\Inbox" is special, add to subfolder INBOX also because Gmail will but ...
11326 is( '"Subf/\\\\Inbox" Subf/INBOX', labels_add_subfolder2( '"\\\\Inbox"', 'Subf' ),
11327 'labels_add_subfolder2: "\\\\Inbox" Subf => "Subf/\\\\Inbox" Subf/INBOX' ) ;
11328
11329 # but not with INBOX folder
11330 is( '"Subf/\\\\Inbox"', labels_add_subfolder2( '"\\\\Inbox"', 'Subf', 'INBOX' ),
11331 'labels_add_subfolder2: "\\\\Inbox" Subf INBOX => "Subf/\\\\Inbox"' ) ;
11332
11333 # two times => one time
11334 is( '"Subf/\\\\Inbox" Subf/INBOX', labels_add_subfolder2( '"\\\\Inbox" "\\\\Inbox"', 'Subf' ),
11335 'labels_add_subfolder2: "\\\\Inbox" "\\\\Inbox" Subf => "Subf/\\\\Inbox"' ) ;
11336
11337 is( '"Subf/\\\\Starred"', labels_add_subfolder2( '"\\\\Starred"', 'Subf' ),
11338 'labels_add_subfolder2: "\\\\Starred" Subf => "Subf/\\\\Starred"' ) ;
11339
11340 note( 'Leaving tests_labels_add_subfolder2()' ) ;
11341 return ;
11342}
11343
11344sub labels_add_subfolder2
11345{
11346 my $labels = shift ;
11347 my $subfolder2 = shift ;
11348 my $h1_folder = shift || q{} ;
11349
11350 if ( not defined $labels ) { return ; }
11351 if ( not defined $subfolder2 ) { return $labels ; }
11352
11353 # Isn't it messy?
11354 if ( 'INBOX' eq $h1_folder )
11355 {
11356 $labels .= ' "\\\\Inbox"' ;
11357 }
11358
11359 my @labels = uniq( quotewords('\s+', 1, $labels ) ) ;
11360 myprint( "labels before subfolder2: @labels\n" ) ;
11361 my @labels_subfolder2 ;
11362
11363
11364 foreach my $label ( @labels )
11365 {
11366 # Isn't it more messy?
11367 if ( ( q{"\\\\Inbox"} eq $label ) and ( 'INBOX' ne $h1_folder ) )
11368 {
11369 if ( $subfolder2 =~ m{ } )
11370 {
11371 push @labels_subfolder2, qq{"$subfolder2/INBOX"} ;
11372 }
11373 else
11374 {
11375 push @labels_subfolder2, "$subfolder2/INBOX" ;
11376 }
11377 }
11378 if ( $label =~ m{^\"\\\\} )
11379 {
11380 # \Seen \Deleted ... stay the same
11381 #push @labels_subfolder2, $label ;
11382 # Remove surrounding quotes if any, to add them again
11383 $label = join( q{}, quotewords('\s+', 0, $label ) ) ;
11384 push @labels_subfolder2, qq{"$subfolder2/\\$label"} ;
11385
11386 }
11387 else
11388 {
11389 # Remove surrounding quotes if any, to add them again in case of space
11390 $label = join( q{}, quotewords('\s+', 0, $label ) ) ;
11391 if ( $label =~ m{ } )
11392 {
11393 push @labels_subfolder2, qq{"$subfolder2/$label"} ;
11394 }
11395 else
11396 {
11397 push @labels_subfolder2, "$subfolder2/$label" ;
11398 }
11399 }
11400 }
11401
11402 my $labels_subfolder2 = join( ' ', sort @labels_subfolder2 ) ;
11403
11404 return $labels_subfolder2 ;
11405}
11406
11407sub tests_labels
11408{
11409 note( 'Entering tests_labels()' ) ;
11410
11411 is( undef, labels( ), 'labels: no parameters => undef' ) ;
11412 is( undef, labels( undef ), 'labels: undef => undef' ) ;
11413 require_ok( "Test::MockObject" ) ;
11414 my $myimap = Test::MockObject->new( ) ;
11415
11416 $myimap->mock( 'fetch_hash',
11417 sub {
11418 return(
11419 { '1' => {
11420 'X-GM-LABELS' => '\Seen Blabla'
11421 }
11422 }
11423 ) ;
11424 }
11425 ) ;
11426 $myimap->mock( 'Debug' , sub { } ) ;
11427 $myimap->mock( 'Unescape', sub { return Mail::IMAPClient::Unescape( @_ ) } ) ; # real one
11428
11429 is( undef, labels( $myimap ), 'labels: one parameter => undef' ) ;
11430 is( '\Seen Blabla', labels( $myimap, '1' ), 'labels: $mysync UID_1 => \Seen Blabla' ) ;
11431
11432 note( 'Leaving tests_labels()' ) ;
11433 return ;
11434}
11435
11436sub labels
11437{
11438 my ( $myimap, $uid ) = @ARG ;
11439
11440 if ( not all_defined( $myimap, $uid ) ) {
11441 return ;
11442 }
11443
11444 my $hash = $myimap->fetch_hash( [ $uid ], 'X-GM-LABELS' ) ;
11445
11446 my $labels = $hash->{ $uid }->{ 'X-GM-LABELS' } ;
11447 #$labels = $myimap->Unescape( $labels ) ;
11448 return $labels ;
11449}
11450
11451sub tests_synclabels
11452{
11453 note( 'Entering tests_synclabels()' ) ;
11454
11455 is( undef, synclabels( ), 'synclabels: no parameters => undef' ) ;
11456 is( undef, synclabels( undef ), 'synclabels: undef => undef' ) ;
11457 my $mysync ;
11458 is( undef, synclabels( $mysync ), 'synclabels: var undef => undef' ) ;
11459
11460 require_ok( "Test::MockObject" ) ;
11461 $mysync = {} ;
11462
11463 my $myimap1 = Test::MockObject->new( ) ;
11464 $myimap1->mock( 'fetch_hash',
11465 sub {
11466 return(
11467 { '1' => {
11468 'X-GM-LABELS' => '\Seen Blabla'
11469 }
11470 }
11471 ) ;
11472 }
11473 ) ;
11474 $myimap1->mock( 'Debug', sub { } ) ;
11475 $myimap1->mock( 'Unescape', sub { return Mail::IMAPClient::Unescape( @_ ) } ) ; # real one
11476
11477 my $myimap2 = Test::MockObject->new( ) ;
11478
11479 $myimap2->mock( 'store',
11480 sub {
11481 return 1 ;
11482 }
11483 ) ;
11484
11485
11486 $mysync->{imap1} = $myimap1 ;
11487 $mysync->{imap2} = $myimap2 ;
11488
11489 is( undef, synclabels( $mysync ), 'synclabels: fresh $mysync => undef' ) ;
11490
11491 is( undef, synclabels( $mysync, '1' ), 'synclabels: $mysync UID_1 alone => undef' ) ;
11492 is( 1, synclabels( $mysync, '1', '2' ), 'synclabels: $mysync UID_1 UID_2 => 1' ) ;
11493
11494 note( 'Leaving tests_synclabels()' ) ;
11495 return ;
11496}
11497
11498
11499sub synclabels
11500{
11501 my( $mysync, $uid1, $uid2 ) = @ARG ;
11502
11503 if ( not all_defined( $mysync, $uid1, $uid2 ) ) {
11504 return ;
11505 }
11506 my $myimap1 = $mysync->{ 'imap1' } || return ;
11507 my $myimap2 = $mysync->{ 'imap2' } || return ;
11508
11509 $mysync->{debuglabels} and $myimap1->Debug( 1 ) ;
11510 my $labels1 = labels( $myimap1, $uid1 ) ;
11511 $mysync->{debuglabels} and $myimap1->Debug( 0 ) ;
11512 $mysync->{debuglabels} and myprint( "Host1 labels: $labels1\n" ) ;
11513
11514
11515
11516 if ( $mysync->{ subfolder1 } and $labels1 )
11517 {
11518 $labels1 = labels_remove_subfolder1( $labels1, $mysync->{ subfolder1 } ) ;
11519 $mysync->{debuglabels} and myprint( "Host1 labels with subfolder1: $labels1\n" ) ;
11520 }
11521
11522 if ( $mysync->{ subfolder2 } and $labels1 )
11523 {
11524 $labels1 = labels_add_subfolder2( $labels1, $mysync->{ subfolder2 } ) ;
11525 $mysync->{debuglabels} and myprint( "Host1 labels with subfolder2: $labels1\n" ) ;
11526 }
11527
11528 my $store ;
11529 if ( $labels1 and not $mysync->{ dry } )
11530 {
11531 $mysync->{ debuglabels } and $myimap2->Debug( 1 ) ;
11532 $store = $myimap2->store( $uid2, "X-GM-LABELS ($labels1)" ) ;
11533 $mysync->{ debuglabels } and $myimap2->Debug( 0 ) ;
11534 }
11535 return $store ;
11536}
11537
11538
11539sub tests_resynclabels
11540{
11541 note( 'Entering tests_resynclabels()' ) ;
11542
11543 is( undef, resynclabels( ), 'resynclabels: no parameters => undef' ) ;
11544 is( undef, resynclabels( undef ), 'resynclabels: undef => undef' ) ;
11545 my $mysync ;
11546 is( undef, resynclabels( $mysync ), 'resynclabels: var undef => undef' ) ;
11547
11548 my ( $h1_fir_ref, $h2_fir_ref ) ;
11549
11550 $mysync->{ debuglabels } = 1 ;
11551 $h1_fir_ref->{ 11 }->{ 'X-GM-LABELS' } = '\Seen Baa Kii' ;
11552 $h2_fir_ref->{ 22 }->{ 'X-GM-LABELS' } = '\Seen Baa Kii' ;
11553
11554 # labels are equal
11555 is( 1, resynclabels( $mysync, 11, 22, $h1_fir_ref, $h2_fir_ref ),
11556 'resynclabels: $mysync UID_1 UID_2 labels are equal => 1' ) ;
11557
11558 # labels are different
11559 $h2_fir_ref->{ 22 }->{ 'X-GM-LABELS' } = '\Seen Zuu' ;
11560 require_ok( "Test::MockObject" ) ;
11561 my $myimap2 = Test::MockObject->new( ) ;
11562 $myimap2->mock( 'store',
11563 sub {
11564 return 1 ;
11565 }
11566 ) ;
11567 $myimap2->mock( 'Debug', sub { } ) ;
11568 $mysync->{imap2} = $myimap2 ;
11569
11570 is( 1, resynclabels( $mysync, 11, 22, $h1_fir_ref, $h2_fir_ref ),
11571 'resynclabels: $mysync UID_1 UID_2 labels are not equal => store => 1' ) ;
11572
11573 note( 'Leaving tests_resynclabels()' ) ;
11574 return ;
11575}
11576
11577
11578
11579sub resynclabels
11580{
11581 my( $mysync, $uid1, $uid2, $h1_fir_ref, $h2_fir_ref, $h1_folder ) = @ARG ;
11582
11583 if ( not all_defined( $mysync, $uid1, $uid2, $h1_fir_ref, $h2_fir_ref ) ) {
11584 return ;
11585 }
11586
11587 my $labels1 = $h1_fir_ref->{ $uid1 }->{ 'X-GM-LABELS' } || q{} ;
11588 my $labels2 = $h2_fir_ref->{ $uid2 }->{ 'X-GM-LABELS' } || q{} ;
11589
11590 if ( $mysync->{ subfolder1 } and $labels1 )
11591 {
11592 $labels1 = labels_remove_subfolder1( $labels1, $mysync->{ subfolder1 } ) ;
11593 }
11594
11595 if ( $mysync->{ subfolder2 } and $labels1 )
11596 {
11597 $labels1 = labels_add_subfolder2( $labels1, $mysync->{ subfolder2 }, $h1_folder ) ;
11598 $labels2 = labels_remove_special( $labels2 ) ;
11599 }
11600 $mysync->{ debuglabels } and myprint( "Host1 labels fixed: $labels1\n" ) ;
11601 $mysync->{ debuglabels } and myprint( "Host2 labels : $labels2\n" ) ;
11602
11603 my $store ;
11604 if ( $labels1 eq $labels2 )
11605 {
11606 # no sync needed
11607 $mysync->{ debuglabels } and myprint( "Labels are already equal\n" ) ;
11608 return 1 ;
11609 }
11610 elsif ( not $mysync->{ dry } )
11611 {
11612 # sync needed
11613 $mysync->{debuglabels} and $mysync->{imap2}->Debug( 1 ) ;
11614 $store = $mysync->{imap2}->store( $uid2, "X-GM-LABELS ($labels1)" ) ;
11615 $mysync->{debuglabels} and $mysync->{imap2}->Debug( 0 ) ;
11616 }
11617
11618 return $store ;
11619}
11620
11621sub tests_uniq
11622{
11623 note( 'Entering tests_uniq()' ) ;
11624
11625 is( 0, uniq( ), 'uniq: undef => 0' ) ;
11626 is_deeply( [ 'one' ], [ uniq( 'one' ) ], 'uniq: one => one' ) ;
11627 is_deeply( [ 'one' ], [ uniq( 'one', 'one' ) ], 'uniq: one one => one' ) ;
11628 is_deeply( [ 'one', 'two' ], [ uniq( 'one', 'one', 'two', 'one', 'two' ) ], 'uniq: one one two one two => one two' ) ;
11629 note( 'Leaving tests_uniq()' ) ;
11630 return ;
11631}
11632
11633sub uniq
11634{
11635 my @list = @ARG ;
11636 my %seen = ( ) ;
11637 my @uniq = ( ) ;
11638 foreach my $item ( @list ) {
11639 if ( ! $seen{ $item } ) {
11640 $seen{ $item } = 1 ;
11641 push( @uniq, $item ) ;
11642 }
11643 }
11644 return @uniq ;
11645}
11646
11647
11648sub length_ref
11649{
11650 my $string_ref = shift ;
11651 my $string_len = defined ${ $string_ref } ? length( ${ $string_ref } ) : q{} ; # length or empty string
11652 return $string_len ;
11653}
11654
11655sub tests_length_ref
11656{
11657 note( 'Entering tests_length_ref()' ) ;
11658
11659 my $notdefined ;
11660 is( q{}, length_ref( \$notdefined ), q{length_ref: value not defined} ) ;
11661 my $notref ;
11662 is( q{}, length_ref( $notref ), q{length_ref: param not a ref} ) ;
11663
11664 my $lala = 'lala' ;
11665 is( 4, length_ref( \$lala ), q{length_ref: lala length == 4} ) ;
11666 is( 4, length_ref( \'lili' ), q{length_ref: lili length == 4} ) ;
11667
11668 note( 'Leaving tests_length_ref()' ) ;
11669 return ;
11670}
11671
11672sub date_for_host2
11673{
11674 my( $h1_msg, $h1_idate ) = @_ ;
11675
11676 my $h1_date = q{} ;
11677
11678 if ( $syncinternaldates ) {
11679 $h1_date = $h1_idate ;
11680 $sync->{ debug } and myprint( "internal date from host1: [$h1_date]\n" ) ;
11681 $h1_date = good_date( $h1_date ) ;
11682 $sync->{ debug } and myprint( "internal date from host1: [$h1_date] (fixed)\n" ) ;
11683 }
11684
11685 if ( $idatefromheader ) {
11686 $h1_date = $sync->{imap1}->get_header( $h1_msg, 'Date' ) ;
11687 $sync->{ debug } and myprint( "header date from host1: [$h1_date]\n" ) ;
11688 $h1_date = good_date( $h1_date ) ;
11689 $sync->{ debug } and myprint( "header date from host1: [$h1_date] (fixed)\n" ) ;
11690 }
11691
11692 return( $h1_date ) ;
11693}
11694
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011695
11696sub subject
11697{
11698 my $string = shift ;
11699 my $subject = q{} ;
11700
11701 my $header = extract_header( $string ) ;
11702
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011703 if( $header =~ m/^Subject:[ \t]*([^\n\r]*)\r?$/msx ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011704 #myprint( "MMM[$1]\n" ) ;
11705 $subject = $1 ;
11706 }
11707 return( $subject ) ;
11708}
11709
11710sub tests_subject
11711{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011712 note( 'Entering tests_subject()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011713
11714 ok( q{} eq subject( q{} ), 'subject: null') ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011715 is( '', subject( 'Subject:' ), 'Subject:') ;
11716 is( '', subject( "Subject:\r\n" ), 'Subject:\r\n') ;
11717 ok( 'toto le hero' eq subject( 'Subject: toto le hero' ), 'Subject: toto le hero') ;
11718 ok( 'toto le hero' eq subject( 'Subject:toto le hero' ), 'Subject:toto le hero') ;
11719 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 +010011720
11721 my $MESS ;
11722 $MESS = <<'EOF';
11723From: lalala
11724Subject: toto le hero
11725Date: zzzzzz
11726
11727Boogie boogie
11728EOF
11729 ok( 'toto le hero' eq subject( $MESS ), 'subject: toto le hero 2') ;
11730
11731 $MESS = <<'EOF';
11732Subject: toto le hero
11733From: lalala
11734Date: zzzzzz
11735
11736Boogie boogie
11737EOF
11738 ok( 'toto le hero' eq subject( $MESS ), 'subject: toto le hero 3') ;
11739
11740
11741 $MESS = <<'EOF';
11742From: lalala
11743Subject: cuicui
11744Date: zzzzzz
11745
11746Subject: toto le hero
11747EOF
11748 ok( 'cuicui' eq subject( $MESS ), 'subject: cuicui') ;
11749
11750 $MESS = <<'EOF';
11751From: lalala
11752Date: zzzzzz
11753
11754Subject: toto le hero
11755EOF
11756 ok( q{} eq subject( $MESS ), 'subject: null but body could') ;
11757
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011758
11759 $MESS = <<'EOF';
11760From: lalala
11761Subject:
11762Date: zzzzzz
11763
11764Subject: toto le hero
11765EOF
11766 is( '', subject( $MESS ), 'Subject:') ;
11767
11768
11769
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011770 note( 'Leaving tests_subject()' ) ;
11771 return ;
11772}
11773
11774
11775# GlobVar
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011776# $h2_uidguess
11777# ...
11778#
11779#
11780sub append_message_on_host2
11781{
11782 my( $mysync, $string_ref, $h1_fold, $h1_msg, $string_len, $h2_fold, $h1_size, $h1_flags, $h1_date, $cache_dir ) = @_ ;
11783 myprint( debugmemory( $mysync, " at A1" ) ) ;
11784
11785 my $new_id ;
11786 if ( ! $mysync->{dry} ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011787 $new_id = $mysync->{imap2}->append_string( $h2_fold, ${ $string_ref }, $h1_flags, $h1_date ) ;
11788 myprint( debugmemory( $mysync, " at A2" ) ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011789 if ( ! defined $new_id ){
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011790 my $subject = subject( ${ $string_ref } ) ;
11791 my $error_imap = $mysync->{imap2}->LastError || q{} ;
11792 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" ;
11793 errors_incr( $mysync, $error ) ;
11794 $mysync->{ h1_nb_msg_processed } +=1 ;
11795 return ;
11796 }
11797 else{
11798 # good
11799 # $new_id is an id if the IMAP server has the
11800 # UIDPLUS capability else just a ref
11801 if ( $new_id !~ m{^\d+$}x ) {
11802 $new_id = lastuid( $mysync->{imap2}, $h2_fold, $h2_uidguess ) ;
11803 }
11804 if ( $mysync->{ synclabels } ) { synclabels( $mysync, $h1_msg, $new_id ) }
11805 $h2_uidguess += 1 ;
11806 $mysync->{ total_bytes_transferred } += $string_len ;
11807 $mysync->{ nb_msg_transferred } += 1 ;
11808 $mysync->{ h1_nb_msg_processed } +=1 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011809 $mysync->{ biggest_message_transferred } = max( $string_len, $mysync->{ biggest_message_transferred } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011810
11811 my $time_spent = timesince( $mysync->{begin_transfer_time} ) ;
11812 my $rate = bytes_display_string( $mysync->{total_bytes_transferred} / $time_spent ) ;
11813 my $eta = eta( $mysync ) ;
11814 my $amount_transferred = bytes_display_string( $mysync->{total_bytes_transferred} ) ;
11815 myprintf( "msg %s/%-19s copied to %s/%-10s %.2f msgs/s %s/s %s copied %s\n",
11816 $h1_fold, "$h1_msg {$string_len}", $h2_fold, $new_id, $mysync->{nb_msg_transferred}/$time_spent, $rate,
11817 $amount_transferred,
11818 $eta );
11819 sleep_if_needed( $mysync ) ;
11820 if ( $usecache and $cacheaftercopy and $new_id =~ m{^\d+$}x ) {
11821 $debugcache and myprint( "touch $cache_dir/${h1_msg}_$new_id\n" ) ;
11822 touch( "$cache_dir/${h1_msg}_$new_id" )
11823 or croak( "Couldn't touch $cache_dir/${h1_msg}_$new_id" ) ;
11824 }
11825 if ( $mysync->{ delete1 } ) {
11826 delete_message_on_host1( $mysync, $h1_fold, $mysync->{ expungeaftereach }, $h1_msg ) ;
11827 }
11828 #myprint( "PRESS ENTER" ) and my $a = <> ;
11829
11830 return( $new_id ) ;
11831 }
11832 }
11833 else{
11834 $nb_msg_skipped_dry_mode += 1 ;
11835 $mysync->{ h1_nb_msg_processed } += 1 ;
11836 }
11837
11838 return ;
11839}
11840
11841
11842sub tests_sleep_if_needed
11843{
11844 note( 'Entering tests_sleep_if_needed()' ) ;
11845
11846 is( undef, sleep_if_needed( ), 'sleep_if_needed: no args => undef' ) ;
11847 my $mysync ;
11848 is( undef, sleep_if_needed( $mysync ), 'sleep_if_needed: arg undef => undef' ) ;
11849
11850 $mysync->{maxbytespersecond} = 1000 ;
11851 is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: maxbytespersecond only => no sleep => 0' ) ;
11852 $mysync->{begin_transfer_time} = time ; # now
11853 is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: begin_transfer_time now => no sleep => 0' ) ;
11854 $mysync->{begin_transfer_time} = time - 2 ; # 2 s before
11855 is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 0 => no sleep => 0' ) ;
11856
11857 $mysync->{total_bytes_transferred} = 2200 ;
11858 $mysync->{begin_transfer_time} = time - 2 ; # 2 s before
11859 is( '0.20', sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 2200 since 2s => sleep 0.2s' ) ;
11860 is( '0', sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 2200 since 2+2 == 4s => no sleep' ) ;
11861
11862 $mysync->{maxsleep} = 0.1 ;
11863 $mysync->{begin_transfer_time} = time - 2 ; # 2 s before again
11864 is( '0.10', sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 4000 since 2s but maxsleep 0.1s => sleep 0.1s' ) ;
11865
11866 $mysync->{maxbytesafter} = 4000 ;
11867 $mysync->{begin_transfer_time} = time - 2 ; # 2 s before again
11868 is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: maxbytesafter == total_bytes_transferred => no sleep => 0' ) ;
11869
11870 note( 'Leaving tests_sleep_if_needed()' ) ;
11871 return ;
11872}
11873
11874
11875sub sleep_if_needed
11876{
11877 my( $mysync ) = shift ;
11878
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011879 if ( ! $mysync ) {
11880 return ;
11881 }
11882 # No need to go further if there is no limit set
11883 if (
11884 not (
11885 $mysync->{maxmessagespersecond}
11886 or $mysync->{maxbytespersecond}
11887 )
11888 ) {
11889 return ;
11890 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011891
11892 $mysync->{maxsleep} = defined $mysync->{maxsleep} ? $mysync->{maxsleep} : $MAX_SLEEP ;
11893 # Must be positive
11894 $mysync->{maxsleep} = max( 0, $mysync->{maxsleep} ) ;
11895
11896 my $time_spent = timesince( $mysync->{begin_transfer_time} ) ;
11897 my $sleep_max_messages = sleep_max_messages( $mysync->{nb_msg_transferred}, $time_spent, $mysync->{maxmessagespersecond} ) ;
11898
11899 my $maxbytesafter = $mysync->{maxbytesafter} || 0 ;
11900 my $total_bytes_transferred = $mysync->{total_bytes_transferred} || 0 ;
11901 my $total_bytes_to_consider = $total_bytes_transferred - $maxbytesafter ;
11902
11903 #myprint( "maxbytesafter:$maxbytesafter\n" ) ;
11904 #myprint( "total_bytes_to_consider:$total_bytes_to_consider\n" ) ;
11905
11906 my $sleep_max_bytes = sleep_max_bytes( $total_bytes_to_consider, $time_spent, $mysync->{maxbytespersecond} ) ;
11907 my $sleep_max = min( $mysync->{maxsleep}, max( $sleep_max_messages, $sleep_max_bytes ) ) ;
11908 $sleep_max = mysprintf( "%.2f", $sleep_max ) ; # round with 2 decimals.
11909 if ( $sleep_max > 0 ) {
11910 myprint( "sleeping $sleep_max s\n" ) ;
11911 sleep $sleep_max ;
11912 # Slept
11913 return $sleep_max ;
11914 }
11915 # No sleep
11916 return 0 ;
11917}
11918
11919sub sleep_max_messages
11920{
11921 # how long we have to sleep to go under max_messages_per_second
11922 my( $nb_msg_transferred, $time_spent, $maxmessagespersecond ) = @_ ;
11923 if ( ( not defined $maxmessagespersecond ) or $maxmessagespersecond <= 0 ) { return( 0 ) } ;
11924 my $sleep = ( $nb_msg_transferred / $maxmessagespersecond ) - $time_spent ;
11925 # the sleep must be positive
11926 return( max( 0, $sleep ) ) ;
11927}
11928
11929
11930sub tests_sleep_max_messages
11931{
11932 note( 'Entering tests_sleep_max_messages()' ) ;
11933
11934 ok( 0 == sleep_max_messages( 4, 2, undef ), 'sleep_max_messages: maxmessagespersecond = undef') ;
11935 ok( 0 == sleep_max_messages( 4, 2, 0 ), 'sleep_max_messages: maxmessagespersecond = 0') ;
11936 ok( 0 == sleep_max_messages( 4, 2, $MINUS_ONE ), 'sleep_max_messages: maxmessagespersecond = -1') ;
11937 ok( 0 == sleep_max_messages( 4, 2, 2 ), 'sleep_max_messages: maxmessagespersecond = 2 max reached') ;
11938 ok( 2 == sleep_max_messages( 8, 2, 2 ), 'sleep_max_messages: maxmessagespersecond = 2 max over') ;
11939 ok( 0 == sleep_max_messages( 2, 2, 2 ), 'sleep_max_messages: maxmessagespersecond = 2 max not reached') ;
11940
11941 note( 'Leaving tests_sleep_max_messages()' ) ;
11942 return ;
11943}
11944
11945
11946sub sleep_max_bytes
11947{
11948 # how long we have to sleep to go under max_bytes_per_second
11949 my( $total_bytes_to_consider, $time_spent, $maxbytespersecond ) = @_ ;
11950 $total_bytes_to_consider ||= 0 ;
11951 $time_spent ||= 0 ;
11952
11953 if ( ( not defined $maxbytespersecond ) or $maxbytespersecond <= 0 ) { return( 0 ) } ;
11954 #myprint( "total_bytes_to_consider:$total_bytes_to_consider\n" ) ;
11955 my $sleep = ( $total_bytes_to_consider / $maxbytespersecond ) - $time_spent ;
11956 # the sleep must be positive
11957 return( max( 0, $sleep ) ) ;
11958}
11959
11960
11961sub tests_sleep_max_bytes
11962{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011963 note( 'Entering tests_sleep_max_bytes()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011964
11965 ok( 0 == sleep_max_bytes( 4000, 2, undef ), 'sleep_max_bytes: maxbytespersecond == undef => sleep 0' ) ;
11966 ok( 0 == sleep_max_bytes( 4000, 2, 0 ), 'sleep_max_bytes: maxbytespersecond = 0 => sleep 0') ;
11967 ok( 0 == sleep_max_bytes( 4000, 2, $MINUS_ONE ), 'sleep_max_bytes: maxbytespersecond = -1 => sleep 0') ;
11968 ok( 0 == sleep_max_bytes( 4000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max reached sharp => sleep 0') ;
11969 ok( 2 == sleep_max_bytes( 8000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max over => sleep a little') ;
11970 ok( 0 == sleep_max_bytes( -8000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max not reached => sleep 0') ;
11971 ok( 0 == sleep_max_bytes( 2000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max not reached => sleep 0') ;
11972 ok( 0 == sleep_max_bytes( -2000, 2, 1000 ), 'sleep_max_bytes: maxbytespersecond = 1k max not reached => sleep 0') ;
11973
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011974 note( 'Leaving tests_sleep_max_bytes()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011975 return ;
11976}
11977
11978
11979sub delete_message_on_host1
11980{
11981 my( $mysync, $h1_fold, $expunge, @h1_msg ) = @_ ;
11982 if ( ! $mysync->{ delete1 } ) { return ; }
11983 if ( ! @h1_msg ) { return ; }
11984 delete_messages_on_any(
11985 $mysync,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011986 $mysync->{ acc1 },
11987 $mysync->{ imap1 },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011988 "Host1: $h1_fold",
11989 $expunge,
11990 $split1,
11991 @h1_msg ) ;
11992 return ;
11993}
11994
11995sub tests_operators_and_exclam_precedence
11996{
11997 note( 'Entering tests_operators_and_exclam_precedence()' ) ;
11998
11999 is( 1, ! 0, 'tests_operators_and_exclam_precedence: ! 0 => 1' ) ;
12000 is( "", ! 1, 'tests_operators_and_exclam_precedence: ! 1 => ""' ) ;
12001 is( 1, not( 0 ), 'tests_operators_and_exclam_precedence: not( 0 ) => 1' ) ;
12002 is( "", not( 1 ), 'tests_operators_and_exclam_precedence: not( 1 ) => ""' ) ;
12003
12004 # I wrote those tests to avoid perlcrit "Mixed high and low-precedence booleans"
12005 # and change sub delete_messages_on_any() but got 4 more warnings... So now commented.
12006
12007 #is( 0, ( ! 0 and 0 ), 'tests_operators_and_exclam_precedence: ! 0 and 0 ) => 0' ) ;
12008 #is( 1, ( ! 0 and 1 ), 'tests_operators_and_exclam_precedence: ! 0 and 1 ) => 1' ) ;
12009 #is( "", ( ! 1 and 0 ), 'tests_operators_and_exclam_precedence: ! 1 and 0 ) => ""' ) ;
12010 #is( "", ( ! 1 and 1 ), 'tests_operators_and_exclam_precedence: ! 1 and 1 ) => ""' ) ;
12011
12012 is( 0, ( ! 0 && 0 ), 'tests_operators_and_exclam_precedence: ! 0 && 0 ) => 0' ) ;
12013 is( 1, ( ! 0 && 1 ), 'tests_operators_and_exclam_precedence: ! 0 && 1 ) => 1' ) ;
12014 is( "", ( ! 1 && 0 ), 'tests_operators_and_exclam_precedence: ! 1 && 0 ) => ""' ) ;
12015 is( "", ( ! 1 && 1 ), 'tests_operators_and_exclam_precedence: ! 1 && 1 ) => ""' ) ;
12016
12017 is( 2, ( ! 0 && 2 ), 'tests_operators_and_exclam_precedence: ! 0 && 2 ) => 1' ) ;
12018
12019 note( 'Leaving tests_operators_and_exclam_precedence()' ) ;
12020 return ;
12021}
12022
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012023
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012024sub delete_messages_on_any
12025{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012026 # $acc is not used yet,
12027 #
12028 my( $mysync, $acc, $imap, $hostX_folder, $expunge, $split, @messages ) = @_ ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012029 my $expunge_message = q{} ;
12030
12031 my $dry_message = $mysync->{ dry_message } ;
12032 $expunge_message = 'and expunged' if ( $expunge ) ;
12033 # "Host1: msg "
12034
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012035 # $imap->Debug( 1 ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012036
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012037 my @messages_to_mark_deleted = @messages ;
12038 while ( my @messages_part = splice @messages_to_mark_deleted, 0, $split )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012039 {
12040 foreach my $message ( @messages_part )
12041 {
12042 myprint( "$hostX_folder/$message marking deleted $expunge_message $dry_message\n" ) ;
12043 }
12044 if ( ! $mysync->{dry} && @messages_part )
12045 {
12046 my $nb_deleted = $imap->delete_message( $imap->Range( @messages_part ) ) ;
12047 if ( defined $nb_deleted )
12048 {
12049 # $nb_deleted is not accurate
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012050 $acc->{ nb_msg_deleted } += scalar @messages_part ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012051 }
12052 else
12053 {
12054 my $error_imap = $imap->LastError || q{} ;
12055 my $error = join( q{}, "$hostX_folder folder, could not delete ",
12056 scalar @messages_part, ' messages: ', $error_imap, "\n" ) ;
12057 errors_incr( $mysync, $error ) ;
12058 }
12059 }
12060 }
12061
12062 if ( $expunge ) {
12063 uidexpunge_or_expunge( $mysync, $imap, @messages ) ;
12064 }
12065
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012066 #$imap->Debug( 0 ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012067
12068 return ;
12069}
12070
12071
12072sub tests_uidexpunge_or_expunge
12073{
12074 note( 'Entering tests_uidexpunge_or_expunge()' ) ;
12075
12076
12077 is( undef, uidexpunge_or_expunge( ), 'uidexpunge_or_expunge: no args => undef' ) ;
12078 my $mysync ;
12079 is( undef, uidexpunge_or_expunge( $mysync ), 'uidexpunge_or_expunge: undef args => undef' ) ;
12080 $mysync = {} ;
12081 is( undef, uidexpunge_or_expunge( $mysync ), 'uidexpunge_or_expunge: arg empty => undef' ) ;
12082 my $imap ;
12083 is( undef, uidexpunge_or_expunge( $mysync, $imap ), 'uidexpunge_or_expunge: undef Mail-IMAPClient instance => undef' ) ;
12084
12085 require_ok( "Test::MockObject" ) ;
12086 $imap = Test::MockObject->new( ) ;
12087 is( undef, uidexpunge_or_expunge( $mysync, $imap ), 'uidexpunge_or_expunge: no message (1) to uidexpunge => undef' ) ;
12088
12089 my @messages = ( ) ;
12090 is( undef, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: no message (2) to uidexpunge => undef' ) ;
12091
12092 @messages = ( '2', '1' ) ;
12093 $imap->mock( 'uidexpunge', sub { return ; } ) ;
12094 $imap->mock( 'expunge', sub { return ; } ) ;
12095 is( undef, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: uidexpunge failure => expunge failure => undef' ) ;
12096
12097 $imap->mock( 'expunge', sub { return 1 ; } ) ;
12098 is( 1, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: uidexpunge failure => expunge ok => 1' ) ;
12099
12100 $imap->mock( 'uidexpunge', sub { return 1 ; } ) ;
12101 is( 1, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: messages to uidexpunge ok => 1' ) ;
12102
12103 note( 'Leaving tests_uidexpunge_or_expunge()' ) ;
12104 return ;
12105}
12106
12107sub uidexpunge_or_expunge
12108{
12109 my $mysync = shift ;
12110 my $imap = shift ;
12111 my @messages = @ARG ;
12112
12113 if ( ! $imap ) { return ; } ;
12114 if ( ! @messages ) { return ; } ;
12115
12116 # Doing uidexpunge
12117 my @uidexpunge_result = $imap->uidexpunge( @messages ) ;
12118 if ( @uidexpunge_result ) {
12119 return 1 ;
12120 }
12121 # Failure so doing expunge
12122 my $expunge_result = $imap->expunge( ) ;
12123 if ( $expunge_result ) {
12124 return 1 ;
12125 }
12126 # bad trip
12127 return ;
12128}
12129
12130sub eta_print
12131{
12132 my $mysync = shift ;
12133 if ( my $eta = eta( $mysync ) )
12134 {
12135 myprint( "$eta\n" ) ;
12136 }
12137 return ;
12138}
12139
12140sub tests_eta
12141{
12142 note( 'Entering tests_eta()' ) ;
12143
12144 is( q{}, eta( ), 'eta: no args => ""' ) ;
12145 is( q{}, eta( undef ), 'eta: undef => ""' ) ;
12146 my $mysync = {} ;
12147 # No foldersizes
12148 is( q{}, eta( $mysync ), 'eta: No foldersizes => ""' ) ;
12149
12150 $mysync->{ foldersizes } = 1 ;
12151
12152 $mysync->{ begin_transfer_time } = time ; # Now
12153 $mysync->{ h1_nb_msg_processed } = 0 ;
12154
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012155 is( "ETA: " . localtimez( time ) . " 0 s 0/0 msgs left",
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012156 eta( $mysync ),
12157 'eta: no args => ETA: "Now" 0 s 0/0 msgs left' ) ;
12158
12159 $mysync->{ h1_nb_msg_processed } = 1 ;
12160 $mysync->{ h1_nb_msg_start } = 2 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012161 is( "ETA: " . localtimez( time ) . " 0 s 1/2 msgs left",
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012162 eta( $mysync ),
12163 'eta: 1, 1, 2 => ETA: "Now" 0 s 1/2 msgs left' ) ;
12164
12165 note( 'Leaving tests_eta()' ) ;
12166 return ;
12167}
12168
12169
12170sub eta
12171{
12172 my( $mysync ) = shift ;
12173
12174 if ( ! $mysync )
12175 {
12176 return q{} ;
12177 }
12178
12179 return( q{} ) if not $mysync->{ foldersizes } ;
12180
12181 my $h1_nb_msg_start = $mysync->{ h1_nb_msg_start } ;
12182 my $h1_nb_processed = $mysync->{ h1_nb_msg_processed } ;
12183 my $nb_msg_transferred = ( $mysync->{dry} ) ? $mysync->{ h1_nb_msg_processed } : $mysync->{ nb_msg_transferred } ;
12184 my $time_spent = timesince( $mysync->{ begin_transfer_time } ) ;
12185 $h1_nb_processed ||= 0 ;
12186 $h1_nb_msg_start ||= 0 ;
12187 $time_spent ||= 0 ;
12188
12189 my $time_remaining = time_remaining( $time_spent, $h1_nb_processed, $h1_nb_msg_start, $nb_msg_transferred ) ;
12190 $mysync->{ debug } and myprint( "time_spent: $time_spent time_remaining: $time_remaining\n" ) ;
12191 my $nb_msg_remaining = $h1_nb_msg_start - $h1_nb_processed ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012192 my $eta_date = localtimez( time + $time_remaining ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012193 return( mysprintf( 'ETA: %s %1.0f s %s/%s msgs left',
12194 $eta_date, $time_remaining, $nb_msg_remaining, $h1_nb_msg_start ) ) ;
12195}
12196
12197
12198
12199
12200sub time_remaining
12201{
12202
12203 my( $my_time_spent, $h1_nb_processed, $h1_nb_msg_start, $nb_transferred ) = @_ ;
12204
12205 $nb_transferred ||= 1 ; # At least one is done (no division by zero)
12206 $h1_nb_processed ||= 0 ;
12207 $h1_nb_msg_start ||= $h1_nb_processed ;
12208 $my_time_spent ||= 0 ;
12209
12210 my $time_remaining = ( $my_time_spent / $nb_transferred ) * ( $h1_nb_msg_start - $h1_nb_processed ) ;
12211 return( $time_remaining ) ;
12212}
12213
12214
12215sub tests_time_remaining
12216{
12217 note( 'Entering tests_time_remaining()' ) ;
12218
12219 # time_spent, nb_processed, nb_to_do_total, nb_transferred
12220 is( 0, time_remaining( ), 'time_remaining: no args -> 0' ) ;
12221 is( 0, time_remaining( 0, 0, 0, 0 ), 'time_remaining: 0, 0, 0, 0 -> 0' ) ;
12222 is( 1, time_remaining( 1, 1, 2, 1 ), 'time_remaining: 1, 1, 2, 1 -> 1' ) ;
12223 is( 1, time_remaining( 9, 9, 10, 9 ), 'time_remaining: 9, 9, 10, 9 -> 1' ) ;
12224 is( 9, time_remaining( 1, 1, 10, 1 ), 'time_remaining: 1, 1, 10, 1 -> 9' ) ;
12225 is( 5, time_remaining( 5, 5, 10, 5 ), 'time_remaining: 5, 5, 10, 5 -> 5' ) ;
12226 is( 25, time_remaining( 5, 5, 10, 0 ), 'time_remaining: 5, 5, 10, 0 -> ( 5 / 1 ) * ( 10 - 5) = 25' ) ;
12227 is( 25, time_remaining( 5, 5, 10, 1 ), 'time_remaining: 5, 5, 10, 1 -> ( 5 / 1 ) * ( 10 - 5) = 25' ) ;
12228
12229 note( 'Leaving tests_time_remaining()' ) ;
12230 return ;
12231}
12232
12233
12234sub cache_map
12235{
12236 my ( $cache_files_ref, $h1_msgs_ref, $h2_msgs_ref ) = @_;
12237 my ( %map1_2, %map2_1, %done2 ) ;
12238
12239 my $h1_msgs_hash_ref = { } ;
12240 my $h2_msgs_hash_ref = { } ;
12241
12242 @{ $h1_msgs_hash_ref }{ @{ $h1_msgs_ref } } = ( ) ;
12243 @{ $h2_msgs_hash_ref }{ @{ $h2_msgs_ref } } = ( ) ;
12244
12245 foreach my $file ( sort @{ $cache_files_ref } ) {
12246 $debugcache and myprint( "C12: $file\n" ) ;
12247 ( $uid1, $uid2 ) = match_a_cache_file( $file ) ;
12248
12249 if ( exists( $h1_msgs_hash_ref->{ defined $uid1 ? $uid1 : q{} } )
12250 and exists( $h2_msgs_hash_ref->{ defined $uid2 ? $uid2 : q{} } ) ) {
12251 # keep only the greatest uid2
12252 # 130_2301 and
12253 # 130_231 => keep only 130 -> 2301
12254
12255 # keep only the greatest uid1
12256 # 1601_260 and
12257 # 161_260 => keep only 1601 -> 260
12258 my $max_uid2 = max( $uid2, $map1_2{ $uid1 } || $MINUS_ONE ) ;
12259 if ( exists $done2{ $max_uid2 } ) {
12260 if ( $done2{ $max_uid2 } < $uid1 ) {
12261 $map1_2{ $uid1 } = $max_uid2 ;
12262 delete $map1_2{ $done2{ $max_uid2 } } ;
12263 $done2{ $max_uid2 } = $uid1 ;
12264 }
12265 }else{
12266 $map1_2{ $uid1 } = $max_uid2 ;
12267 $done2{ $max_uid2 } = $uid1 ;
12268 }
12269 };
12270
12271 }
12272 %map2_1 = reverse %map1_2 ;
12273 return( \%map1_2, \%map2_1) ;
12274}
12275
12276sub tests_cache_map
12277{
12278 note( 'Entering tests_cache_map()' ) ;
12279
12280 #$debugcache = 1 ;
12281 my @cache_files = qw (
12282 100_200
12283 101_201
12284 120_220
12285 142_242
12286 143_243
12287 177_277
12288 177_278
12289 177_279
12290 155_255
12291 180_280
12292 181_280
12293 182_280
12294 130_231
12295 130_2301
12296 161_260
12297 1601_260
12298 ) ;
12299
12300 my $msgs_1 = [120, 142, 143, 144, 161, 1601, 177, 182, 130 ];
12301 my $msgs_2 = [ 242, 243, 260, 299, 377, 279, 255, 280, 231, 2301 ];
12302
12303 my( $c12, $c21 ) ;
12304 ok( ( $c12, $c21 ) = cache_map( \@cache_files, $msgs_1, $msgs_2 ), 'cache_map: 02' );
12305 my $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
12306 my $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
12307 ok( 0 == compare_lists( [ 130, 142, 143, 177, 182, 1601 ], $a1 ), 'cache_map: 03' );
12308 ok( 0 == compare_lists( [ 242, 243, 260, 279, 280, 2301 ], $a2 ), 'cache_map: 04' );
12309 ok( ! $c12->{161}, 'cache_map: ! 161 -> 260' );
12310 ok( 260 == $c12->{1601}, 'cache_map: 1601 -> 260' );
12311 ok( 2301 == $c12->{130}, 'cache_map: 130 -> 2301' );
12312 #myprint( $c12->{1601}, "\n" ) ;
12313
12314 note( 'Leaving tests_cache_map()' ) ;
12315 return ;
12316
12317}
12318
12319sub cache_dir_fix
12320{
12321 my $cache_dir = shift ;
12322 $cache_dir =~ s/([;<>\*\|`&\$!#\(\)\[\]\{\}:'"\\])/\\$1/xg ;
12323 #myprint( "cache_dir_fix: $cache_dir\n" ) ;
12324 return( $cache_dir ) ;
12325}
12326
12327sub tests_cache_dir_fix
12328{
12329 note( 'Entering tests_cache_dir_fix()' ) ;
12330
12331 ok( 'lalala' eq cache_dir_fix('lalala'), 'cache_dir_fix: lalala -> lalala' );
12332 ok( 'ii\\\\ii' eq cache_dir_fix('ii\ii'), 'cache_dir_fix: ii\ii -> ii\\\\ii' );
12333 ok( 'ii@ii' eq cache_dir_fix('ii@ii'), 'cache_dir_fix: ii@ii -> ii@ii' );
12334 ok( 'ii@ii\\:ii' eq cache_dir_fix('ii@ii:ii'), 'cache_dir_fix: ii@ii:ii -> ii@ii\\:ii' );
12335 ok( 'i\\\\i\\\\ii' eq cache_dir_fix('i\i\ii'), 'cache_dir_fix: i\i\ii -> i\\\\i\\\\ii' );
12336 ok( 'i\\\\ii' eq cache_dir_fix('i\\ii'), 'cache_dir_fix: i\\ii -> i\\\\\\\\ii' );
12337 ok( '\\\\ ' eq cache_dir_fix('\\ '), 'cache_dir_fix: \\ -> \\\\\ ' );
12338 ok( '\\\\ ' eq cache_dir_fix('\ '), 'cache_dir_fix: \ -> \\\\\ ' );
12339 ok( '\[bracket\]' eq cache_dir_fix('[bracket]'), 'cache_dir_fix: [bracket] -> \[bracket\]' );
12340
12341 note( 'Leaving tests_cache_dir_fix()' ) ;
12342 return ;
12343}
12344
12345sub cache_dir_fix_win
12346{
12347 my $cache_dir = shift ;
12348 $cache_dir =~ s/(\[|\])/[$1]/xg ;
12349 #myprint( "cache_dir_fix_win: $cache_dir\n" ) ;
12350 return( $cache_dir ) ;
12351}
12352
12353sub tests_cache_dir_fix_win
12354{
12355 note( 'Entering tests_cache_dir_fix_win()' ) ;
12356
12357 ok( 'lalala' eq cache_dir_fix_win('lalala'), 'cache_dir_fix_win: lalala -> lalala' );
12358 ok( '[[]bracket[]]' eq cache_dir_fix_win('[bracket]'), 'cache_dir_fix_win: [bracket] -> [[]bracket[]]' );
12359
12360 note( 'Leaving tests_cache_dir_fix_win()' ) ;
12361 return ;
12362}
12363
12364
12365
12366
12367sub get_cache
12368{
12369 my ( $cache_dir, $h1_msgs_ref, $h2_msgs_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) = @_;
12370
12371 $debugcache and myprint( "Entering get_cache\n" ) ;
12372
12373 -d $cache_dir or return( undef ); # exit if cache directory doesn't exist
12374 $debugcache and myprint( "cache_dir : $cache_dir\n" ) ;
12375
12376
12377 if ( 'MSWin32' ne $OSNAME ) {
12378 $cache_dir = cache_dir_fix( $cache_dir ) ;
12379 }else{
12380 $cache_dir = cache_dir_fix_win( $cache_dir ) ;
12381 }
12382
12383 $debugcache and myprint( "cache_dir_fix: $cache_dir\n" ) ;
12384
12385 my @cache_files = bsd_glob( "$cache_dir/*" ) ;
12386 #$debugcache and myprint( "cache_files: [@cache_files]\n" ) ;
12387
12388 $debugcache and myprint( 'cache_files: ', scalar @cache_files , " files found\n" ) ;
12389
12390 my( $cache_1_2_ref, $cache_2_1_ref )
12391 = cache_map( \@cache_files, $h1_msgs_ref, $h2_msgs_ref ) ;
12392
12393 clean_cache( \@cache_files, $cache_1_2_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) ;
12394
12395 $debugcache and myprint( "Exiting get_cache\n" ) ;
12396 return( $cache_1_2_ref, $cache_2_1_ref ) ;
12397}
12398
12399
12400sub tests_get_cache
12401{
12402 note( 'Entering tests_get_cache()' ) ;
12403
12404 ok( not( get_cache('/cache_no_exist') ), 'get_cache: /cache_no_exist' );
12405 ok( ( not -d 'W/tmp/cache/F1/F2' or rmtree( 'W/tmp/cache/F1/F2' ) ), 'get_cache: rmtree W/tmp/cache/F1/F2' ) ;
12406 ok( mkpath( 'W/tmp/cache/F1/F2' ), 'get_cache: mkpath W/tmp/cache/F1/F2' ) ;
12407
12408 my @test_files_cache = ( qw(
12409 W/tmp/cache/F1/F2/100_200
12410 W/tmp/cache/F1/F2/101_201
12411 W/tmp/cache/F1/F2/120_220
12412 W/tmp/cache/F1/F2/142_242
12413 W/tmp/cache/F1/F2/143_243
12414 W/tmp/cache/F1/F2/177_277
12415 W/tmp/cache/F1/F2/177_377
12416 W/tmp/cache/F1/F2/177_777
12417 W/tmp/cache/F1/F2/155_255
12418 ) ) ;
12419 ok( touch( @test_files_cache ), 'get_cache: touch W/tmp/cache/F1/F2/...' ) ;
12420
12421
12422 # on cache: 100_200 101_201 142_242 143_243 177_277 177_377 177_777 155_255
12423 # on live:
12424 my $msgs_1 = [120, 142, 143, 144, 177 ];
12425 my $msgs_2 = [ 242, 243, 299, 377, 777, 255 ];
12426
12427 my $msgs_all_1 = { 120 => 0, 142 => 0, 143 => 0, 144 => 0, 177 => 0 } ;
12428 my $msgs_all_2 = { 242 => 0, 243 => 0, 299 => 0, 377 => 0, 777 => 0, 255 => 0 } ;
12429
12430 my( $c12, $c21 ) ;
12431 ok( ( $c12, $c21 ) = get_cache( 'W/tmp/cache/F1/F2', $msgs_1, $msgs_2, $msgs_all_1, $msgs_all_2 ), 'get_cache: 02' );
12432 my $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
12433 my $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
12434 ok( 0 == compare_lists( [ 142, 143, 177 ], $a1 ), 'get_cache: 03' );
12435 ok( 0 == compare_lists( [ 242, 243, 777 ], $a2 ), 'get_cache: 04' );
12436 ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 142_242');
12437 ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 143_243');
12438 ok( ! -f 'W/tmp/cache/F1/F2/100_200', 'get_cache: file removed 100_200');
12439 ok( ! -f 'W/tmp/cache/F1/F2/101_201', 'get_cache: file removed 101_201');
12440
12441 # test clean_cache executed
12442 $maxage = 2 ;
12443 ok( touch(@test_files_cache), 'get_cache: touch W/tmp/cache/F1/F2/...' ) ;
12444 ok( ( $c12, $c21 ) = get_cache('W/tmp/cache/F1/F2', $msgs_1, $msgs_2, $msgs_all_1, $msgs_all_2 ), 'get_cache: 02' );
12445 ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 142_242');
12446 ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 143_243');
12447 ok( ! -f 'W/tmp/cache/F1/F2/100_200', 'get_cache: file NOT removed 100_200');
12448 ok( ! -f 'W/tmp/cache/F1/F2/101_201', 'get_cache: file NOT removed 101_201');
12449
12450
12451 # strange files
12452 #$debugcache = 1 ;
12453 $maxage = undef ;
12454 ok( ( not -d 'W/tmp/cache/rr\uee' or rmtree( 'W/tmp/cache/rr\uee' )), 'get_cache: rmtree W/tmp/cache/rr\uee' ) ;
12455 ok( mkpath( 'W/tmp/cache/rr\uee' ), 'get_cache: mkpath W/tmp/cache/rr\uee' ) ;
12456
12457 @test_files_cache = ( qw(
12458 W/tmp/cache/rr\uee/100_200
12459 W/tmp/cache/rr\uee/101_201
12460 W/tmp/cache/rr\uee/120_220
12461 W/tmp/cache/rr\uee/142_242
12462 W/tmp/cache/rr\uee/143_243
12463 W/tmp/cache/rr\uee/177_277
12464 W/tmp/cache/rr\uee/177_377
12465 W/tmp/cache/rr\uee/177_777
12466 W/tmp/cache/rr\uee/155_255
12467 ) ) ;
12468 ok( touch(@test_files_cache), 'get_cache: touch strange W/tmp/cache/...' ) ;
12469
12470 # on cache: 100_200 101_201 142_242 143_243 177_277 177_377 177_777 155_255
12471 # on live:
12472 $msgs_1 = [120, 142, 143, 144, 177 ] ;
12473 $msgs_2 = [ 242, 243, 299, 377, 777, 255 ] ;
12474
12475 $msgs_all_1 = { 120 => q{}, 142 => q{}, 143 => q{}, 144 => q{}, 177 => q{} } ;
12476 $msgs_all_2 = { 242 => q{}, 243 => q{}, 299 => q{}, 377 => q{}, 777 => q{}, 255 => q{} } ;
12477
12478 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' );
12479 $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
12480 $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
12481 ok( 0 == compare_lists( [ 142, 143, 177 ], $a1 ), 'get_cache: strange path 03' );
12482 ok( 0 == compare_lists( [ 242, 243, 777 ], $a2 ), 'get_cache: strange path 04' );
12483 ok( -f 'W/tmp/cache/rr\uee/142_242', 'get_cache: strange path file kept 142_242');
12484 ok( -f 'W/tmp/cache/rr\uee/142_242', 'get_cache: strange path file kept 143_243');
12485 ok( ! -f 'W/tmp/cache/rr\uee/100_200', 'get_cache: strange path file removed 100_200');
12486 ok( ! -f 'W/tmp/cache/rr\uee/101_201', 'get_cache: strange path file removed 101_201');
12487
12488 note( 'Leaving tests_get_cache()' ) ;
12489 return ;
12490}
12491
12492sub match_a_cache_file
12493{
12494 my $file = shift ;
12495 my ( $cache_uid1, $cache_uid2 ) ;
12496
12497 return( ( undef, undef ) ) if ( ! $file ) ;
12498 if ( $file =~ m{(?:^|/)(\d+)_(\d+)$}x ) {
12499 $cache_uid1 = $1 ;
12500 $cache_uid2 = $2 ;
12501 }
12502 return( $cache_uid1, $cache_uid2 ) ;
12503}
12504
12505sub tests_match_a_cache_file
12506{
12507 note( 'Entering tests_match_a_cache_file()' ) ;
12508
12509 my ( $tuid1, $tuid2 ) ;
12510 ok( ( $tuid1, $tuid2 ) = match_a_cache_file( ), 'match_a_cache_file: no arg' ) ;
12511 ok( ! defined $tuid1 , 'match_a_cache_file: no arg 1' ) ;
12512 ok( ! defined $tuid2 , 'match_a_cache_file: no arg 2' ) ;
12513
12514 ok( ( $tuid1, $tuid2 ) = match_a_cache_file( q{} ), 'match_a_cache_file: empty arg' ) ;
12515 ok( ! defined $tuid1 , 'match_a_cache_file: empty arg 1' ) ;
12516 ok( ! defined $tuid2 , 'match_a_cache_file: empty arg 2' ) ;
12517
12518 ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '000_000' ), 'match_a_cache_file: 000_000' ) ;
12519 ok( '000' eq $tuid1, 'match_a_cache_file: 000_000 1' ) ;
12520 ok( '000' eq $tuid2, 'match_a_cache_file: 000_000 2' ) ;
12521
12522 ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '123_456' ), 'match_a_cache_file: 123_456' ) ;
12523 ok( '123' eq $tuid1, 'match_a_cache_file: 123_456 1' ) ;
12524 ok( '456' eq $tuid2, 'match_a_cache_file: 123_456 2' ) ;
12525
12526 ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '/tmp/truc/123_456' ), 'match_a_cache_file: /tmp/truc/123_456' ) ;
12527 ok( '123' eq $tuid1, 'match_a_cache_file: /tmp/truc/123_456 1' ) ;
12528 ok( '456' eq $tuid2, 'match_a_cache_file: /tmp/truc/123_456 2' ) ;
12529
12530 ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '/lala123_456' ), 'match_a_cache_file: NO /lala123_456' ) ;
12531 ok( ! $tuid1, 'match_a_cache_file: /lala123_456 1' ) ;
12532 ok( ! $tuid2, 'match_a_cache_file: /lala123_456 2' ) ;
12533
12534 ok( ( $tuid1, $tuid2 ) = match_a_cache_file( 'la123_456' ), 'match_a_cache_file: NO la123_456' ) ;
12535 ok( ! $tuid1, 'match_a_cache_file: la123_456 1' ) ;
12536 ok( ! $tuid2, 'match_a_cache_file: la123_456 2' ) ;
12537
12538 note( 'Leaving tests_match_a_cache_file()' ) ;
12539 return ;
12540}
12541
12542sub clean_cache
12543{
12544 my ( $cache_files_ref, $cache_1_2_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) = @_ ;
12545
12546 $debugcache and myprint( "Entering clean_cache\n" ) ;
12547
12548 $debugcache and myprint( map { "$_ -> " . $cache_1_2_ref->{ $_ } . "\n" } keys %{ $cache_1_2_ref } ) ;
12549 foreach my $file ( @{ $cache_files_ref } ) {
12550 $debugcache and myprint( "$file\n" ) ;
12551 my ( $cache_uid1, $cache_uid2 ) = match_a_cache_file( $file ) ;
12552 $debugcache and myprint( "u1: $cache_uid1 u2: $cache_uid2 c12: ", $cache_1_2_ref->{ $cache_uid1 } || q{}, "\n") ;
12553# or ( ! exists( $cache_1_2_ref->{ $cache_uid1 } ) )
12554# or ( ! ( $cache_uid2 == $cache_1_2_ref->{ $cache_uid1 } ) )
12555 if ( ( not defined $cache_uid1 )
12556 or ( not defined $cache_uid2 )
12557 or ( not exists $h1_msgs_all_hash_ref->{ $cache_uid1 } )
12558 or ( not exists $h2_msgs_all_hash_ref->{ $cache_uid2 } )
12559 ) {
12560 $debugcache and myprint( "remove $file\n" ) ;
12561 unlink $file or myprint( "$OS_ERROR" ) ;
12562 }
12563 }
12564
12565 $debugcache and myprint( "Exiting clean_cache\n" ) ;
12566 return( 1 ) ;
12567}
12568
12569sub tests_clean_cache
12570{
12571 note( 'Entering tests_clean_cache()' ) ;
12572
12573 ok( ( not -d 'W/tmp/cache/G1/G2' or rmtree( 'W/tmp/cache/G1/G2' )), 'clean_cache: rmtree W/tmp/cache/G1/G2' ) ;
12574 ok( mkpath( 'W/tmp/cache/G1/G2' ), 'clean_cache: mkpath W/tmp/cache/G1/G2' ) ;
12575
12576 my @test_files_cache = ( qw(
12577 W/tmp/cache/G1/G2/100_200
12578 W/tmp/cache/G1/G2/101_201
12579 W/tmp/cache/G1/G2/120_220
12580 W/tmp/cache/G1/G2/142_242
12581 W/tmp/cache/G1/G2/143_243
12582 W/tmp/cache/G1/G2/177_277
12583 W/tmp/cache/G1/G2/177_377
12584 W/tmp/cache/G1/G2/177_777
12585 W/tmp/cache/G1/G2/155_255
12586 ) ) ;
12587 ok( touch(@test_files_cache), 'clean_cache: touch W/tmp/cache/G1/G2/...' ) ;
12588
12589 ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache: 100_200 before' );
12590 ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache: 142_242 before' );
12591 ok( -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache: 177_277 before' );
12592 ok( -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache: 177_377 before' );
12593 ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache: 177_777 before' );
12594 ok( -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache: 155_255 before' );
12595
12596 my $cache = {
12597 142 => 242,
12598 177 => 777,
12599 } ;
12600
12601 my $all_1 = {
12602 142 => q{},
12603 177 => q{},
12604 } ;
12605
12606 my $all_2 = {
12607 200 => q{},
12608 242 => q{},
12609 777 => q{},
12610 } ;
12611 ok( clean_cache( \@test_files_cache, $cache, $all_1, $all_2 ), 'clean_cache: ' ) ;
12612
12613 ok( ! -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache: 100_200 after' );
12614 ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache: 142_242 after' );
12615 ok( ! -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache: 177_277 after' );
12616 ok( ! -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache: 177_377 after' );
12617 ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache: 177_777 after' );
12618 ok( ! -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache: 155_255 after' );
12619
12620 note( 'Leaving tests_clean_cache()' ) ;
12621 return ;
12622}
12623
12624sub tests_clean_cache_2
12625{
12626 note( 'Entering tests_clean_cache_2()' ) ;
12627
12628 ok( ( not -d 'W/tmp/cache/G1/G2' or rmtree( 'W/tmp/cache/G1/G2' )), 'clean_cache_2: rmtree W/tmp/cache/G1/G2' ) ;
12629 ok( mkpath( 'W/tmp/cache/G1/G2' ), 'clean_cache_2: mkpath W/tmp/cache/G1/G2' ) ;
12630
12631 my @test_files_cache = ( qw(
12632 W/tmp/cache/G1/G2/100_200
12633 W/tmp/cache/G1/G2/101_201
12634 W/tmp/cache/G1/G2/120_220
12635 W/tmp/cache/G1/G2/142_242
12636 W/tmp/cache/G1/G2/143_243
12637 W/tmp/cache/G1/G2/177_277
12638 W/tmp/cache/G1/G2/177_377
12639 W/tmp/cache/G1/G2/177_777
12640 W/tmp/cache/G1/G2/155_255
12641 ) ) ;
12642 ok( touch(@test_files_cache), 'clean_cache_2: touch W/tmp/cache/G1/G2/...' ) ;
12643
12644 ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache_2: 100_200 before' );
12645 ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache_2: 142_242 before' );
12646 ok( -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache_2: 177_277 before' );
12647 ok( -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache_2: 177_377 before' );
12648 ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache_2: 177_777 before' );
12649 ok( -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache_2: 155_255 before' );
12650
12651 my $cache = {
12652 142 => 242,
12653 177 => 777,
12654 } ;
12655
12656 my $all_1 = {
12657 $NUMBER_100 => q{},
12658 142 => q{},
12659 177 => q{},
12660 } ;
12661
12662 my $all_2 = {
12663 200 => q{},
12664 242 => q{},
12665 777 => q{},
12666 } ;
12667
12668
12669
12670 ok( clean_cache( \@test_files_cache, $cache, $all_1, $all_2 ), 'clean_cache_2: ' ) ;
12671
12672 ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache_2: 100_200 after' );
12673 ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache_2: 142_242 after' );
12674 ok( ! -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache_2: 177_277 after' );
12675 ok( ! -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache_2: 177_377 after' );
12676 ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache_2: 177_777 after' );
12677 ok( ! -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache_2: 155_255 after' );
12678
12679 note( 'Leaving tests_clean_cache_2()' ) ;
12680 return ;
12681}
12682
12683
12684
12685sub tests_mkpath
12686{
12687 note( 'Entering tests_mkpath()' ) ;
12688
12689 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' )), 'mkpath: mkpath W/tmp/tests/' ) ;
12690
12691 SKIP: {
12692 skip( 'Tests only for Unix', 10 ) if ( 'MSWin32' eq $OSNAME ) ;
12693 my $long_path_unix = '123456789/' x 30 ;
12694 ok( ( -d "W/tmp/tests/long/$long_path_unix" or mkpath( "W/tmp/tests/long/$long_path_unix" ) ), 'mkpath: mkpath 300 char' ) ;
12695 ok( -d "W/tmp/tests/long/$long_path_unix", 'mkpath: mkpath > 300 char verified' ) ;
12696 ok( ( -d "W/tmp/tests/long/$long_path_unix" and rmtree( 'W/tmp/tests/long/' ) ), 'mkpath: rmtree 300 char' ) ;
12697 ok( ! -d "W/tmp/tests/long/$long_path_unix", 'mkpath: rmtree 300 char verified' ) ;
12698
12699 ok( ( -d 'W/tmp/tests/trailing_dots...' or mkpath( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: mkpath trailing_dots...' ) ;
12700 ok( -d 'W/tmp/tests/trailing_dots...', 'mkpath: mkpath trailing_dots... verified' ) ;
12701 ok( ( -d 'W/tmp/tests/trailing_dots...' and rmtree( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: rmtree trailing_dots...' ) ;
12702 ok( ! -d 'W/tmp/tests/trailing_dots...', 'mkpath: rmtree trailing_dots... verified' ) ;
12703
12704 eval { ok( 1 / 0, 'mkpath: divide by 0' ) ; } or ok( 1, 'mkpath: can not divide by 0' ) ;
12705 ok( 1, 'mkpath: still alive' ) ;
12706 } ;
12707
12708 SKIP: {
12709 skip( 'Tests only for MSWin32', 13 ) if ( 'MSWin32' ne $OSNAME ) ;
12710 my $long_path_2_prefix = ".\\imapsync_tests" || '\\\?\\E:\\TEMP\\imapsync_tests' ;
12711 myprint( "long_path_2_prefix: $long_path_2_prefix\n" ) ;
12712
12713 my $long_path_100 = $long_path_2_prefix . '\\' . '123456789\\' x 10 . 'END' ;
12714 my $long_path_300 = $long_path_2_prefix . '\\' . '123456789\\' x 30 . 'END' ;
12715
12716 #myprint( "$long_path_100\n" ) ;
12717
12718 ok( ( -d $long_path_2_prefix or mkpath( $long_path_2_prefix ) ), 'mkpath: -d mkpath small path' ) ;
12719 ok( ( -d $long_path_2_prefix ), 'mkpath: -d mkpath small path done' ) ;
12720 ok( ( -d $long_path_100 or mkpath( $long_path_100 ) ), 'mkpath: mkpath > 100 char' ) ;
12721 ok( ( -d $long_path_100 ), 'mkpath: -d mkpath > 200 char done' ) ;
12722 ok( ( -d $long_path_2_prefix and rmtree( $long_path_2_prefix ) ), 'mkpath: rmtree > 100 char' ) ;
12723 ok( (! -d $long_path_2_prefix ), 'mkpath: ! -d rmtree done' ) ;
12724
12725 # Without the eval the following mkpath 300 just kill the whole process without a whisper
12726 #myprint( "$long_path_300\n" ) ;
12727 eval { ok( ( -d $long_path_300 or mkpath( $long_path_300 ) ), 'mkpath: create a path with 300 characters' ) ; }
12728 or ok( 1, 'mkpath: can not create a path with 300 characters' ) ;
12729 ok( ( ( ! -d $long_path_300 ) or -d $long_path_300 and rmtree( $long_path_300 ) ), 'mkpath: rmtree the 300 character path' ) ;
12730 ok( 1, 'mkpath: still alive' ) ;
12731
12732 ok( ( -d 'W/tmp/tests/trailing_dots...' or mkpath( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: mkpath trailing_dots...' ) ;
12733 ok( -d 'W/tmp/tests/trailing_dots...', 'mkpath: mkpath trailing_dots... verified' ) ;
12734 ok( ( -d 'W/tmp/tests/trailing_dots...' and rmtree( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: rmtree trailing_dots...' ) ;
12735 ok( ! -d 'W/tmp/tests/trailing_dots...', 'mkpath: rmtree trailing_dots... verified' ) ;
12736
12737
12738 } ;
12739
12740 note( 'Leaving tests_mkpath()' ) ;
12741 # Keep this because of the eval used by the caller (failed badly?)
12742 return 1 ;
12743}
12744
12745sub tests_touch
12746{
12747 note( 'Entering tests_touch()' ) ;
12748
12749 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' )), 'touch: mkpath W/tmp/tests/' ) ;
12750 ok( 1 == touch( 'W/tmp/tests/lala'), 'touch: W/tmp/tests/lala') ;
12751 ok( 1 == touch( 'W/tmp/tests/\y'), 'touch: W/tmp/tests/\y') ;
12752 ok( 0 == touch( '/no/no/no/aaa'), 'touch: not /aaa') ;
12753 ok( 1 == touch( 'W/tmp/tests/lili', 'W/tmp/tests/lolo'), 'touch: 2 files') ;
12754 ok( 0 == touch( 'W/tmp/tests/\y', '/no/no/aaa'), 'touch: 2 files, 1 fails' ) ;
12755
12756 note( 'Leaving tests_touch()' ) ;
12757 return ;
12758}
12759
12760
12761sub touch
12762{
12763 my @files = @_ ;
12764 my $failures = 0 ;
12765
12766 foreach my $file ( @files ) {
12767 my $fh = IO::File->new ;
12768 if ( $fh->open(">> $file" ) ) {
12769 $fh->close ;
12770 }else{
12771 myprint( "Could not open file $file in write/append mode\n" ) ;
12772 $failures++ ;
12773 }
12774 }
12775 return( ! $failures );
12776}
12777
12778
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012779
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012780sub tests_tmpdir_has_colon_bug
12781{
12782 note( 'Entering tests_tmpdir_has_colon_bug()' ) ;
12783
12784 ok( 0 == tmpdir_has_colon_bug( q{} ), 'tmpdir_has_colon_bug: ' ) ;
12785 ok( 0 == tmpdir_has_colon_bug( '/tmp' ), 'tmpdir_has_colon_bug: /tmp' ) ;
12786 ok( 1 == tmpdir_has_colon_bug( 'C:' ), 'tmpdir_has_colon_bug: C:' ) ;
12787 ok( 1 == tmpdir_has_colon_bug( 'C:\temp' ), 'tmpdir_has_colon_bug: C:\temp' ) ;
12788
12789 note( 'Leaving tests_tmpdir_has_colon_bug()' ) ;
12790 return ;
12791}
12792
12793sub tmpdir_has_colon_bug
12794{
12795 my $path = shift ;
12796
12797 my $path_filtered = filter_forbidden_characters( $path ) ;
12798 if ( $path_filtered ne $path ) {
12799 ( -d $path_filtered ) and myprint( "Path $path was previously mistakely changed to $path_filtered\n" ) ;
12800 return( 1 ) ;
12801 }
12802 return( 0 ) ;
12803}
12804
12805sub tmpdir_fix_colon_bug
12806{
12807 my $mysync = shift ;
12808 my $err = 0 ;
12809 if ( not (-d $mysync->{ tmpdir } and -r _ and -w _) ) {
12810 myprint( "tmpdir $mysync->{ tmpdir } is not valid\n" ) ;
12811 return( 0 ) ;
12812 }
12813 my $cachedir_new = "$mysync->{ tmpdir }/imapsync_cache" ;
12814
12815 if ( not tmpdir_has_colon_bug( $cachedir_new ) ) { return( 0 ) } ;
12816
12817 # check if old cache directory already exists
12818 my $cachedir_old = filter_forbidden_characters( $cachedir_new ) ;
12819 if ( not ( -d $cachedir_old ) ) {
12820 myprint( "Old cache directory $cachedir_new no exists, nothing to do\n" ) ;
12821 return( 1 ) ;
12822 }
12823 # check if new cache directory already exists
12824 if ( -d $cachedir_new ) {
12825 myprint( "New fixed cache directory $cachedir_new already exists, not moving the old one $cachedir_old. Fix this manually.\n" ) ;
12826 return( 0 ) ;
12827 }else{
12828 # move the old one to the new place
12829 myprint( "Moving $cachedir_old to $cachedir_new Do not interrupt this task.\n" ) ;
12830 File::Copy::Recursive::rmove( $cachedir_old, $cachedir_new )
12831 or do {
12832 myprint( "Could not move $cachedir_old to $cachedir_new\n" ) ;
12833 $err++ ;
12834 } ;
12835 # check it succeeded
12836 if ( -d $cachedir_new and -r _ and -w _ ) {
12837 myprint( "New fixed cache directory $cachedir_new ok\n" ) ;
12838 }else{
12839 myprint( "New fixed cache directory $cachedir_new does not exist\n" ) ;
12840 $err++ ;
12841 }
12842 if ( -d $cachedir_old ) {
12843 myprint( "Old cache directory $cachedir_old still exists\n" ) ;
12844 $err++ ;
12845 }else{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012846 myprint( "Old cache directory $cachedir_old successfully moved\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012847 }
12848 }
12849 return( not $err ) ;
12850}
12851
12852
12853sub tests_cache_folder
12854{
12855 note( 'Entering tests_cache_folder()' ) ;
12856
12857 ok( '/path/fold1/fold2' eq cache_folder( q{}, '/path', 'fold1', 'fold2'), 'cache_folder: /path, fold1, fold2 -> /path/fold1/fold2' ) ;
12858 ok( '/pa_th/fold1/fold2' eq cache_folder( q{}, '/pa*th', 'fold1', 'fold2'), 'cache_folder: /pa*th, fold1, fold2 -> /path/fold1/fold2' ) ;
12859 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' ) ;
12860
12861 ok( 'D:/path/fold1/fold2' eq cache_folder( 'D:', '/path', 'fold1', 'fold2'), 'cache_folder: /path, fold1, fold2 -> /path/fold1/fold2' ) ;
12862 ok( 'D:/pa_th/fold1/fold2' eq cache_folder( 'D:', '/pa*th', 'fold1', 'fold2'), 'cache_folder: /pa*th, fold1, fold2 -> /path/fold1/fold2' ) ;
12863 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' ) ;
12864 ok( '//' eq cache_folder( q{}, q{}, q{}, q{}), 'cache_folder: -> //' ) ;
12865 ok( '//_______' eq cache_folder( q{}, q{}, q{}, '*|?:"<>'), 'cache_folder: *|?:"<> -> //_______' ) ;
12866
12867 note( 'Leaving tests_cache_folder()' ) ;
12868 return ;
12869}
12870
12871sub cache_folder
12872{
12873 my( $cache_base, $cache_dir, $h1_fold, $h2_fold ) = @_ ;
12874
12875 my $sep_1 = $sync->{ h1_sep } || '/';
12876 my $sep_2 = $sync->{ h2_sep } || '/';
12877
12878 #myprint( "$cache_dir h1_fold $h1_fold sep1 $sep_1 h2_fold $h2_fold sep2 $sep_2\n" ) ;
12879 $h1_fold = convert_sep_to_slash( $h1_fold, $sep_1 ) ;
12880 $h2_fold = convert_sep_to_slash( $h2_fold, $sep_2 ) ;
12881
12882 my $cache_folder = "$cache_base" . filter_forbidden_characters( "$cache_dir/$h1_fold/$h2_fold" ) ;
12883 #myprint( "cache_folder [$cache_folder]\n" ) ;
12884 return( $cache_folder ) ;
12885}
12886
12887sub tests_filter_forbidden_characters
12888{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012889 note( 'Entering tests_filter_forbidden_characters()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012890
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012891 is( undef , filter_forbidden_characters( ), 'filter_forbidden_characters: no args -> undef' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012892
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012893 is( 'a_b' , filter_forbidden_characters( 'a_b' ), 'filter_forbidden_characters: a_b -> a_b' ) ;
12894 is( 'a_b' , filter_forbidden_characters( 'a*b' ), 'filter_forbidden_characters: a*b -> a_b' ) ;
12895 is( 'a_b' , filter_forbidden_characters( 'a|b' ), 'filter_forbidden_characters: a|b -> a_b' ) ;
12896 is( 'a_b' , filter_forbidden_characters( 'a?b' ), 'filter_forbidden_characters: a?b -> a_b' ) ;
12897 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 +010012898
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012899
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012900 is( 'a_b_' , filter_forbidden_characters( 'a b ' ), 'filter_forbidden_characters: "a b " -> "a_b_"' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012901
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012902
12903 is( 'a_b' , filter_forbidden_characters( "a\tb" ), 'filter_forbidden_characters: a\tb -> a_b' ) ;
12904 is( "a_b" , filter_forbidden_characters( "a\rb" ), 'filter_forbidden_characters: a\rb -> a_b' ) ;
12905 is( "a_b" , filter_forbidden_characters( "a\nb" ), 'filter_forbidden_characters: a\nb -> a_b' ) ;
12906 is( "a_b" , filter_forbidden_characters( "a\\b" ), 'filter_forbidden_characters: a\b -> a_b' ) ;
12907
12908 is( 'a-b' , filter_forbidden_characters( 'a-b' ), 'filter_forbidden_characters: a-b -> a-b' ) ;
12909 is( 'a__-__-__-__-__b' , filter_forbidden_characters( 'aé-è-à -ç-Öb' ), 'filter_forbidden_characters: aé-è-à -ç-Öb -> a__-__-__-__-__b' ) ;
12910
12911 is( 'abcdABCDwxyzWXYZ012789' , filter_forbidden_characters( 'abcdABCDwxyzWXYZ012789' ),
12912 'filter_forbidden_characters: abcdABCDwxyzWXYZ012789 -> abcdABCDwxyzWXYZ012789' ) ;
12913
12914
12915 note( 'Leaving tests_filter_forbidden_characters()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012916 return ;
12917}
12918
12919sub filter_forbidden_characters
12920{
12921 my $string = shift ;
12922
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012923 if ( ! defined $string ) { return ; }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012924
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012925 $string =~ s{[\Q*|?:"<>' \E\t\r\n\\]}{_}xg ;
12926 # replace all non-ascii and control characters by _
12927 $string =~ s/[[:^ascii:][:cntrl:]]/_/xg ;
12928
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012929 #myprint( "[$string]\n" ) ;
12930 return( $string ) ;
12931}
12932
12933sub tests_convert_sep_to_slash
12934{
12935 note( 'Entering tests_convert_sep_to_slash()' ) ;
12936
12937
12938 ok(q{} eq convert_sep_to_slash(q{}, '/'), 'convert_sep_to_slash: no folder');
12939 ok('INBOX' eq convert_sep_to_slash('INBOX', '/'), 'convert_sep_to_slash: INBOX');
12940 ok('INBOX/foo' eq convert_sep_to_slash('INBOX/foo', '/'), 'convert_sep_to_slash: INBOX/foo');
12941 ok('INBOX/foo' eq convert_sep_to_slash('INBOX_foo', '_'), 'convert_sep_to_slash: INBOX_foo');
12942 ok('INBOX/foo/zob' eq convert_sep_to_slash('INBOX_foo_zob', '_'), 'convert_sep_to_slash: INBOX_foo_zob');
12943 ok('INBOX/foo' eq convert_sep_to_slash('INBOX.foo', '.'), 'convert_sep_to_slash: INBOX.foo');
12944 ok('INBOX/foo/hi' eq convert_sep_to_slash('INBOX.foo.hi', '.'), 'convert_sep_to_slash: INBOX.foo.hi');
12945
12946 note( 'Leaving tests_convert_sep_to_slash()' ) ;
12947 return ;
12948}
12949
12950sub convert_sep_to_slash
12951{
12952 my ( $folder, $sep ) = @_ ;
12953
12954 $folder =~ s{\Q$sep\E}{/}xg ;
12955 return( $folder ) ;
12956}
12957
12958
12959sub tests_regexmess
12960{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012961 note( 'Entering tests_regexmess()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012962
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012963 ok( 'blabla' eq regexmess( 'blabla' ), 'regexmess: no regexmess, nothing to do' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012964
12965 @regexmess = ( 'lalala' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012966 ok( not( defined regexmess( 'popopo' ) ), 'regexmess: bad regex lalala' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012967
12968 @regexmess = ( 's/p/Z/g' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012969 ok( 'ZoZoZo' eq regexmess( 'popopo' ), 'regexmess: s/p/Z/g' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012970
12971 @regexmess = ( 's{c}{C}gxms' ) ;
12972 ok("H1: abC\nH2: Cde\n\nBody abC"
12973 eq regexmess( "H1: abc\nH2: cde\n\nBody abc"),
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012974 'regexmess: c->C');
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012975
12976 @regexmess = ( 's{\AFrom\ }{From:}gxms' ) ;
12977 ok( q{}
12978 eq regexmess(q{}),
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012979 'regexmess: From mbox 1 add colon blank');
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012980
12981 ok( 'From:<tartanpion@machin.truc>'
12982 eq regexmess('From <tartanpion@machin.truc>'),
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012983 'regexmess: From mbox 2 add colo');
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012984
12985 ok( "\n" . 'From <tartanpion@machin.truc>'
12986 eq regexmess("\n" . 'From <tartanpion@machin.truc>'),
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012987 'regexmess: From mbox 3 add colo') ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012988
12989 ok( "From: zzz\n" . 'From <tartanpion@machin.truc>'
12990 eq regexmess("From zzz\n" . 'From <tartanpion@machin.truc>'),
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012991 'regexmess: From mbox 4 add colo') ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012992
12993 @regexmess = ( 's{\AFrom\ [^\n]*(\n)?}{}gxms' ) ;
12994 ok( q{}
12995 eq regexmess(q{}),
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012996 'regexmess: From mbox 1 remove, blank');
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012997
12998 ok( q{}
12999 eq regexmess('From <tartanpion@machin.truc>'),
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013000 'regexmess: From mbox 2 remove');
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013001
13002 ok( "\n" . 'From <tartanpion@machin.truc>'
13003 eq regexmess("\n" . 'From <tartanpion@machin.truc>'),
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013004 'regexmess: From mbox 3 remove');
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013005
13006 #myprint( "[", regexmess("From zzz\n" . 'From <tartanpion@machin.truc>'), "]" ) ;
13007 ok( q{} . 'From <tartanpion@machin.truc>'
13008 eq regexmess("From zzz\n" . 'From <tartanpion@machin.truc>'),
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013009 'regexmess: From mbox 4 remove');
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013010
13011
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013012 is(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013013<<'EOM'
13014Date: Sat, 10 Jul 2010 05:34:45 -0700
13015From:<tartanpion@machin.truc>
13016
13017Hello,
13018Bye.
13019EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013020 , regexmess(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013021<<'EOM'
13022From zzz
13023Date: Sat, 10 Jul 2010 05:34:45 -0700
13024From:<tartanpion@machin.truc>
13025
13026Hello,
13027Bye.
13028EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013029 ), 'regexmess: From mbox 5 remove');
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013030
13031
13032@regexmess = ( 's{\A((?:[^\n]+\n)+|)^Disposition-Notification-To:[^\n]*\n(\r?\n|.*\n\r?\n)}{$1$2}xms' ) ; # SUPER SUPER BEST!
13033 ok(
13034<<'EOM'
13035Date: Sat, 10 Jul 2010 05:34:45 -0700
13036From:<tartanpion@machin.truc>
13037
13038Hello,
13039Bye.
13040EOM
13041 eq regexmess(
13042<<'EOM'
13043Date: Sat, 10 Jul 2010 05:34:45 -0700
13044Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13045From:<tartanpion@machin.truc>
13046
13047Hello,
13048Bye.
13049EOM
13050 ),
13051 'regexmess: 1 Delete header Disposition-Notification-To:');
13052
13053 ok(
13054<<'EOM'
13055Date: Sat, 10 Jul 2010 05:34:45 -0700
13056From:<tartanpion@machin.truc>
13057
13058Hello,
13059Bye.
13060EOM
13061 eq regexmess(
13062<<'EOM'
13063Date: Sat, 10 Jul 2010 05:34:45 -0700
13064From:<tartanpion@machin.truc>
13065Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13066
13067Hello,
13068Bye.
13069EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013070 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013071 'regexmess: 2 Delete header Disposition-Notification-To:');
13072
13073 ok(
13074<<'EOM'
13075Date: Sat, 10 Jul 2010 05:34:45 -0700
13076From:<tartanpion@machin.truc>
13077
13078Hello,
13079Bye.
13080EOM
13081 eq regexmess(
13082<<'EOM'
13083Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13084Date: Sat, 10 Jul 2010 05:34:45 -0700
13085From:<tartanpion@machin.truc>
13086
13087Hello,
13088Bye.
13089EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013090 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013091 'regexmess: 3 Delete header Disposition-Notification-To:');
13092
13093 ok(
13094<<'EOM'
13095Date: Sat, 10 Jul 2010 05:34:45 -0700
13096From:<tartanpion@machin.truc>
13097
13098Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13099Bye.
13100EOM
13101 eq regexmess(
13102<<'EOM'
13103Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13104Date: Sat, 10 Jul 2010 05:34:45 -0700
13105From:<tartanpion@machin.truc>
13106
13107Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13108Bye.
13109EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013110 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013111 'regexmess: 4 Delete header Disposition-Notification-To:');
13112
13113
13114 ok(
13115<<'EOM'
13116Date: Sat, 10 Jul 2010 05:34:45 -0700
13117From:<tartanpion@machin.truc>
13118
13119Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13120Bye.
13121EOM
13122 eq regexmess(
13123<<'EOM'
13124Date: Sat, 10 Jul 2010 05:34:45 -0700
13125From:<tartanpion@machin.truc>
13126
13127Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13128Bye.
13129EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013130 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013131 'regexmess: 5 Delete header Disposition-Notification-To:');
13132
13133
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013134 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013135<<'EOM'
13136Date: Sat, 10 Jul 2010 05:34:45 -0700
13137From:<tartanpion@machin.truc>
13138
13139Hello,
13140Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13141Bye.
13142EOM
13143 eq regexmess(
13144<<'EOM'
13145Date: Sat, 10 Jul 2010 05:34:45 -0700
13146From:<tartanpion@machin.truc>
13147
13148Hello,
13149Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13150Bye.
13151EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013152 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013153 'regexmess: 6 Delete header Disposition-Notification-To:');
13154
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013155 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013156<<'EOM'
13157Date: Sat, 10 Jul 2010 05:34:45 -0700
13158From:<tartanpion@machin.truc>
13159
13160Hello,
13161Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13162
13163Bye.
13164EOM
13165 eq regexmess(
13166<<'EOM'
13167Date: Sat, 10 Jul 2010 05:34:45 -0700
13168From:<tartanpion@machin.truc>
13169
13170Hello,
13171Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13172
13173Bye.
13174EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013175 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013176 'regexmess: 7 Delete header Disposition-Notification-To:');
13177
13178
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013179 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013180<<'EOM'
13181Date: Sat, 10 Jul 2010 05:34:45 -0700
13182From:<tartanpion@machin.truc>
13183
13184Hello,
13185Bye.
13186EOM
13187 eq regexmess(
13188<<'EOM'
13189Date: Sat, 10 Jul 2010 05:34:45 -0700
13190From:<tartanpion@machin.truc>
13191
13192Hello,
13193Bye.
13194EOM
13195),
13196 'regexmess: 8 Delete header Disposition-Notification-To:');
13197
13198
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013199 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013200<<'EOM'
13201Date: Sat, 10 Jul 2010 05:34:45 -0700
13202From:<tartanpion@machin.truc>
13203
13204Hello,
13205Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13206Bye.
13207EOM
13208 eq regexmess(
13209<<'EOM'
13210Date: Sat, 10 Jul 2010 05:34:45 -0700
13211From:<tartanpion@machin.truc>
13212
13213Hello,
13214Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13215Bye.
13216EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013217 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013218 'regexmess: 9 Delete header Disposition-Notification-To:');
13219
13220
13221
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013222 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013223<<'EOM'
13224Date: Sat, 10 Jul 2010 05:34:45 -0700
13225From:<tartanpion@machin.truc>
13226
13227Hello,
13228Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13229
13230
13231Bye.
13232EOM
13233 eq regexmess(
13234<<'EOM'
13235Date: Sat, 10 Jul 2010 05:34:45 -0700
13236From:<tartanpion@machin.truc>
13237
13238Hello,
13239Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13240
13241
13242Bye.
13243EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013244 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013245 'regexmess: 10 Delete header Disposition-Notification-To:');
13246
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013247 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013248<<'EOM'
13249Date: Sat, 10 Jul 2010 05:34:45 -0700
13250From:<tartanpion@machin.truc>
13251
13252Hello,
13253
13254Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13255
13256Bye.
13257EOM
13258 eq regexmess(
13259<<'EOM'
13260Date: Sat, 10 Jul 2010 05:34:45 -0700
13261From:<tartanpion@machin.truc>
13262
13263Hello,
13264
13265Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13266
13267Bye.
13268EOM
13269),
13270 'regexmess: 11 Delete header Disposition-Notification-To:');
13271
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013272 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013273<<'EOM'
13274Date: Sat, 10 Jul 2010 05:34:45 -0700
13275From:<tartanpion@machin.truc>
13276
13277Hello,
13278
13279Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13280
13281Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13282
13283Bye.
13284EOM
13285 eq regexmess(
13286<<'EOM'
13287Date: Sat, 10 Jul 2010 05:34:45 -0700
13288From:<tartanpion@machin.truc>
13289
13290Hello,
13291
13292Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13293
13294Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13295
13296Bye.
13297EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013298 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013299 'regexmess: 12 Delete header Disposition-Notification-To:');
13300
13301
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013302 @regexmess = ( 's{\A(.*?(?! ^$))^Disposition-Notification-To:(.*?)$}{$1X-Disposition-Notification-To:$2}igxms' ) ; # BAD!
13303 @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 +010013304
13305
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013306 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013307<<'EOM'
13308Date: Sat, 10 Jul 2010 05:34:45 -0700
13309From:<tartanpion@machin.truc>
13310
13311Hello,
13312
13313Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13314
13315Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13316
13317Bye.
13318EOM
13319 eq regexmess(
13320<<'EOM'
13321Date: Sat, 10 Jul 2010 05:34:45 -0700
13322From:<tartanpion@machin.truc>
13323
13324Hello,
13325
13326Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13327
13328Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13329
13330Bye.
13331EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013332 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013333 'regexmess: 13 Delete header Disposition-Notification-To:');
13334
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013335 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013336<<'EOM'
13337Date: Sat, 10 Jul 2010 05:34:45 -0700
13338X-Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13339From:<tartanpion@machin.truc>
13340
13341Hello,
13342
13343Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13344
13345Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13346
13347Bye.
13348EOM
13349 eq regexmess(
13350<<'EOM'
13351Date: Sat, 10 Jul 2010 05:34:45 -0700
13352Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13353From:<tartanpion@machin.truc>
13354
13355Hello,
13356
13357Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13358
13359Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13360
13361Bye.
13362EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013363 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013364 'regexmess: 14 Delete header Disposition-Notification-To:');
13365
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013366 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013367<<'EOM'
13368Date: Sat, 10 Jul 2010 05:34:45 -0700
13369X-Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13370From:<tartanpion@machin.truc>
13371
13372Hello,
13373
13374Bye.
13375EOM
13376 eq regexmess(
13377<<'EOM'
13378Date: Sat, 10 Jul 2010 05:34:45 -0700
13379Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13380From:<tartanpion@machin.truc>
13381
13382Hello,
13383
13384Bye.
13385EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013386 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013387 'regexmess: 15 Delete header Disposition-Notification-To:');
13388
13389
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013390 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013391<<'EOM'
13392Date: Sat, 10 Jul 2010 05:34:45 -0700
13393From:<tartanpion@machin.truc>
13394X-Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13395
13396Hello,
13397
13398Bye.
13399EOM
13400 eq regexmess(
13401<<'EOM'
13402Date: Sat, 10 Jul 2010 05:34:45 -0700
13403From:<tartanpion@machin.truc>
13404Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13405
13406Hello,
13407
13408Bye.
13409EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013410 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013411 'regexmess: 16 Delete header Disposition-Notification-To:');
13412
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013413 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013414<<'EOM'
13415X-Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13416Date: Sat, 10 Jul 2010 05:34:45 -0700
13417From:<tartanpion@machin.truc>
13418
13419Hello,
13420
13421Bye.
13422EOM
13423 eq regexmess(
13424<<'EOM'
13425Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13426Date: Sat, 10 Jul 2010 05:34:45 -0700
13427From:<tartanpion@machin.truc>
13428
13429Hello,
13430
13431Bye.
13432EOM
13433),
13434 'regexmess: 17 Delete header Disposition-Notification-To:');
13435
13436 @regexmess = ( 's/.{11}\K.*//gs' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013437 is( "0123456789\n", regexmess( "0123456789\n" x 100 ), 'regexmess: truncate whole message after 11 characters' ) ;
13438 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 +010013439
13440 @regexmess = ( 's/.{10000}\K.*//gs' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013441 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 +010013442
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013443 @regexmess = ( 's/^(X-Ham-Report.*?\n)^X-/X-/sm' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013444
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013445 is(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013446<<'EOM'
13447X-Spam-Score: -1
13448X-Spam-Bar: /
13449X-Spam-Flag: NO
13450Date: Sat, 10 Jul 2010 05:34:45 -0700
13451From:<tartanpion@machin.truc>
13452
13453Hello,
13454
13455Bye.
13456EOM
13457,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013458 regexmess(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013459<<'EOM'
13460X-Spam-Score: -1
13461X-Spam-Bar: /
13462X-Ham-Report: =?utf-8?Q?Spam_detection_software=2C_running?=
13463 =?utf-8?Q?_on_the_system_=22ohp-ag006.int200?=
13464_has_NOT_identified_thi?=
13465 =?utf-8?Q?s_incoming_email_as_spam.__The_o?=
13466_message_has_been_attac?=
13467 =?utf-8?Q?hed_to_this_so_you_can_view_it_o?=
13468___________________________?=
13469 =?utf-8?Q?__author's_domain
13470X-Spam-Flag: NO
13471Date: Sat, 10 Jul 2010 05:34:45 -0700
13472From:<tartanpion@machin.truc>
13473
13474Hello,
13475
13476Bye.
13477EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013478 ),
13479 'regexmess: Delete header X-Ham-Report:');
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013480
13481
13482# regex to play with Date: from the FAQ
13483#@regexmess = 's{\A(.*?(?! ^$))^Date:(.*?)$}{$1Date:$2\nX-Date:$2}gxms'
13484
13485
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013486# Change 8bit characters in whole email to X characters
13487 @regexmess = ( 's{[\x80-\xff]}{X}gxms' ) ;
13488 is( 'X-8bit: kaka 1 XX kiki', regexmess('X-8bit: kaka 1 ¤ kiki'), 'regexmess: 1 Change 8bit characters in whole email to X characters');
13489
13490# Same change but using tr
13491 @regexmess = ( 'tr [\x80-\xff] [X]' ) ;
13492 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 +010013493
13494
13495
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013496# Add a final \r\n if missing
13497 @regexmess = ( 's{(?<![\n])\z}{\r\n}gxms' ) ;
13498 is( "\r\n", regexmess(""), 'regexmess: 1. Add a final \r\n if missing. Missing' ) ;
13499 is( "abc\r\n", regexmess("abc"), 'regexmess: 2. Add a final \r\n if missing. Missing' ) ;
13500 is( "abc\ndef\r\n", regexmess("abc\ndef"), 'regexmess: 3. Add a final \r\n if missing. Missing' ) ;
13501 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 +010013502
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013503 is( "\r\n", regexmess("\r\n"), 'regexmess: 3. Add a final \r\n if missing. Not missing' ) ;
13504 is( "abc\n", regexmess("abc\n"), 'regexmess: 4. Add a final \r\n if missing. Not missing' ) ;
13505 is( "abc\r\n", regexmess("abc\r\n"), 'regexmess: 4. Add a final \r\n if missing. Not missing' ) ;
13506 is( "abc\ndef\n", regexmess("abc\ndef\n"), 'regexmess: 4. Add a final \r\n if missing. Not missing' ) ;
13507 is( "abc\r\ndef\r\n", regexmess("abc\r\ndef\r\n"), 'regexmess: 4. Add a final \r\n if missing. Not missing' ) ;
13508
13509# Remove the fucking buggy X-Spam-Report: a bad header on several lines that can even begin without a space!
13510
13511 @regexmess = ( 's{X-Spam-Report:.*?\n(^[^\n]+:|^\r?\n)}{$1}xms' ) ;
13512 # Damien regexes:
13513 #@regexmess = ( 's{X-Spam-Report:.*?\n(^[a-zA-Z0-9\-]+:)}{$1}xms' ) ;
13514 #@regexmess = ( 's{X-Spam-Report:.*?\n(^[a-zA-Z0-9\-]+:|^\r?\n)}{$1}xms' ) ;
13515
13516 is(
13517<<'EOM'
13518Date: Sat, 10 Jul 2010 05:34:45 -0700
13519From:<tartanpion@machin.truc>
13520LaSuite: super
13521
13522Hello,
13523Bye.
13524EOM
13525 , regexmess(
13526<<'EOM'
13527Date: Sat, 10 Jul 2010 05:34:45 -0700
13528From:<tartanpion@machin.truc>
13529X-Spam-Report: caca
13530caca
13531 caca
13532caca
13533LaSuite: super
13534
13535Hello,
13536Bye.
13537EOM
13538 ), 'regexmess: 1 remove buggy X-Spam-Report: across several lines, not the final header');
13539
13540
13541 is(
13542<<'EOM'
13543Date: Sat, 10 Jul 2010 05:34:45 -0700
13544From:<tartanpion@machin.truc>
13545LaSuite: super
13546LaSuite2: super 2
13547
13548Hello,
13549Bye.
13550EOM
13551 , regexmess(
13552<<'EOM'
13553Date: Sat, 10 Jul 2010 05:34:45 -0700
13554From:<tartanpion@machin.truc>
13555X-Spam-Report: caca
13556caca
13557 caca
13558caca
13559LaSuite: super
13560LaSuite2: super 2
13561
13562Hello,
13563Bye.
13564EOM
13565 ), 'regexmess: 2 remove buggy X-Spam-Report: across several lines, not the final header');
13566
13567
13568 is(
13569<<'EOM'
13570Date: Sat, 10 Jul 2010 05:34:45 -0700
13571From:<tartanpion@machin.truc>
13572LaSuite: super
13573LaSuite2: super 2
13574
13575Hello,
13576Bye.
13577EOM
13578 , regexmess(
13579<<'EOM'
13580X-Spam-Report: caca
13581caca
13582 caca
13583caca
13584Date: Sat, 10 Jul 2010 05:34:45 -0700
13585From:<tartanpion@machin.truc>
13586LaSuite: super
13587LaSuite2: super 2
13588
13589Hello,
13590Bye.
13591EOM
13592 ), 'regexmess: 3 remove buggy X-Spam-Report: across several lines, first header');
13593
13594
13595
13596
13597 is(
13598<<'EOM'
13599Date: Sat, 10 Jul 2010 05:34:45 -0700
13600From:<tartanpion@machin.truc>
13601
13602Hello,
13603Bye.
13604EOM
13605 , regexmess(
13606<<'EOM'
13607Date: Sat, 10 Jul 2010 05:34:45 -0700
13608From:<tartanpion@machin.truc>
13609X-Spam-Report: caca
13610caca
13611 caca
13612caca
13613
13614Hello,
13615Bye.
13616EOM
13617 ), 'regexmess: 4 remove buggy X-Spam-Report: across several lines, final header');
13618
13619
13620 is(
13621<<'EOM'
13622Date: Sat, 10 Jul 2010 05:34:45 -0700
13623From:<tartanpion@machin.truc>
13624
13625Hello,
13626Bye.
13627EOM
13628 , regexmess(
13629<<'EOM'
13630Date: Sat, 10 Jul 2010 05:34:45 -0700
13631From:<tartanpion@machin.truc>
13632
13633Hello,
13634Bye.
13635EOM
13636 ), 'regexmess: 5 remove buggy X-Spam-Report: not there at all');
13637
13638
13639 is(
13640<<"EOM"
13641Date: Sat, 10 Jul 2010 05:34:45 -0700\r
13642From:<tartanpion>\r
13643LaSuite: super\r
13644LaSuite2: super 2\r
13645\r
13646Hello,\r
13647Bye.\r
13648EOM
13649 , regexmess(
13650<<"EOM"
13651X-Spam-Report: caca\r
13652caca\r
13653 caca\r
13654caca\r
13655Date: Sat, 10 Jul 2010 05:34:45 -0700\r
13656From:<tartanpion>\r
13657LaSuite: super\r
13658LaSuite2: super 2\r
13659\r
13660Hello,\r
13661Bye.\r
13662EOM
13663 ), 'regexmess: 6 remove buggy X-Spam-Report: across several lines, first header, with \r');
13664
13665
13666 is(
13667<<"EOM"
13668Date: Sat, 10 Jul 2010 05:34:45 -0700\r
13669From:<tartanpion>\r
13670LaSuite: super\r
13671LaSuite2: super 2\r
13672\r
13673Hello,\r
13674Bye.\r
13675EOM
13676 , regexmess(
13677<<"EOM"
13678Date: Sat, 10 Jul 2010 05:34:45 -0700\r
13679From:<tartanpion>\r
13680X-Spam-Report: caca\r
13681caca\r
13682 caca\r
13683caca\r
13684LaSuite: super\r
13685LaSuite2: super 2\r
13686\r
13687Hello,\r
13688Bye.\r
13689EOM
13690 ), 'regexmess: 7 remove buggy X-Spam-Report: across several lines, middle header, with \r');
13691
13692
13693 is(
13694<<"EOM"
13695Date: Sat, 10 Jul 2010 05:34:45 -0700\r
13696From:<tartanpion>\r
13697\r
13698Hello,\r
13699Bye.\r
13700EOM
13701 , regexmess(
13702<<"EOM"
13703Date: Sat, 10 Jul 2010 05:34:45 -0700\r
13704From:<tartanpion>\r
13705X-Spam-Report: caca\r
13706caca\r
13707 caca\r
13708caca\r
13709\r
13710Hello,\r
13711Bye.\r
13712EOM
13713 ), 'regexmess: 8 remove buggy X-Spam-Report: across several lines, final header, with \r');
13714
13715
13716 undef @regexmess ;
13717 note( 'Leaving tests_regexmess()' ) ;
13718 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013719}
13720
13721sub regexmess
13722{
13723 my ( $string ) = @_ ;
13724 foreach my $regexmess ( @regexmess ) {
13725 $sync->{ debug } and myprint( "eval \$string =~ $regexmess\n" ) ;
13726 my $ret = eval "\$string =~ $regexmess ; 1" ;
13727 #myprint( "eval [$ret]\n" ) ;
13728 if ( ( not $ret ) or $EVAL_ERROR ) {
13729 myprint( "Error: eval regexmess '$regexmess': $EVAL_ERROR" ) ;
13730 return( undef ) ;
13731 }
13732 }
13733 $sync->{ debug } and myprint( "$string\n" ) ;
13734 return( $string ) ;
13735}
13736
13737
13738sub tests_skipmess
13739{
13740 note( 'Entering tests_skipmess()' ) ;
13741
13742 ok( not( defined skipmess( 'blabla' ) ), 'skipmess, no skipmess, no skip' ) ;
13743
13744 @skipmess = ('[') ;
13745 ok( not( defined skipmess( 'popopo' ) ), 'skipmess, bad regex [' ) ;
13746
13747 @skipmess = ('lalala') ;
13748 ok( not( defined skipmess( 'popopo' ) ), 'skipmess, bad regex lalala' ) ;
13749
13750 @skipmess = ('/popopo/') ;
13751 ok( 1 == skipmess( 'popopo' ), 'skipmess, popopo match regex /popopo/' ) ;
13752
13753 @skipmess = ('/popopo/') ;
13754 ok( 0 == skipmess( 'rrrrrr' ), 'skipmess, rrrrrr does not match regex /popopo/' ) ;
13755
13756 @skipmess = ('m{^$}') ;
13757 ok( 1 == skipmess( q{} ), 'skipmess: empty string yes' ) ;
13758 ok( 0 == skipmess( 'Hi!' ), 'skipmess: empty string no' ) ;
13759
13760 @skipmess = ('m{i}') ;
13761 ok( 1 == skipmess( 'Hi!' ), 'skipmess: i string yes' ) ;
13762 ok( 0 == skipmess( 'Bye!' ), 'skipmess: i string no' ) ;
13763
13764 @skipmess = ('m{[\x80-\xff]}') ;
13765 ok( 0 == skipmess( 'Hi!' ), 'skipmess: i 8bit no' ) ;
13766 ok( 1 == skipmess( "\xff" ), 'skipmess: \xff 8bit yes' ) ;
13767
13768 @skipmess = ('m{A}', 'm{B}') ;
13769 ok( 0 == skipmess( 'Hi!' ), 'skipmess: A or B no' ) ;
13770 ok( 0 == skipmess( 'lala' ), 'skipmess: A or B no' ) ;
13771 ok( 0 == skipmess( "\xff" ), 'skipmess: A or B no' ) ;
13772 ok( 1 == skipmess( 'AB' ), 'skipmess: A or B yes' ) ;
13773 ok( 1 == skipmess( 'BA' ), 'skipmess: A or B yes' ) ;
13774 ok( 1 == skipmess( 'AA' ), 'skipmess: A or B yes' ) ;
13775 ok( 1 == skipmess( 'Ok Bye' ), 'skipmess: A or B yes' ) ;
13776
13777
13778 @skipmess = ( 'm#\A((?:[^\n]+\n)+|)^Content-Type: Message/Partial;[^\n]*\n(?:\n|.*\n\n)#ism' ) ; # SUPER BEST!
13779
13780
13781
13782 ok( 1 == skipmess(
13783<<'EOM'
13784Date: Sat, 10 Jul 2010 05:34:45 -0700
13785Content-Type: Message/Partial; blabla
13786From:<tartanpion@machin.truc>
13787
13788Hello!
13789Bye.
13790EOM
13791),
13792 'skipmess: 1 match Content-Type: Message/Partial' ) ;
13793
13794 ok( 0 == skipmess(
13795<<'EOM'
13796Date: Sat, 10 Jul 2010 05:34:45 -0700
13797From:<tartanpion@machin.truc>
13798
13799Hello!
13800Bye.
13801EOM
13802),
13803 'skipmess: 2 not match Content-Type: Message/Partial' ) ;
13804
13805
13806 ok( 1 == skipmess(
13807<<'EOM'
13808Date: Sat, 10 Jul 2010 05:34:45 -0700
13809From:<tartanpion@machin.truc>
13810Content-Type: Message/Partial; blabla
13811
13812Hello!
13813Bye.
13814EOM
13815),
13816 'skipmess: 3 match Content-Type: Message/Partial' ) ;
13817
13818 ok( 0 == skipmess(
13819<<'EOM'
13820Date: Sat, 10 Jul 2010 05:34:45 -0700
13821From:<tartanpion@machin.truc>
13822
13823Hello!
13824Content-Type: Message/Partial; blabla
13825Bye.
13826EOM
13827),
13828 'skipmess: 4 not match Content-Type: Message/Partial' ) ;
13829
13830
13831 ok( 0 == skipmess(
13832<<'EOM'
13833Date: Sat, 10 Jul 2010 05:34:45 -0700
13834From:<tartanpion@machin.truc>
13835
13836Hello!
13837Content-Type: Message/Partial; blabla
13838
13839Bye.
13840EOM
13841),
13842 'skipmess: 5 not match Content-Type: Message/Partial' ) ;
13843
13844
13845 ok( 1 == skipmess(
13846<<'EOM'
13847Date: Sat, 10 Jul 2010 05:34:45 -0700
13848Content-Type: Message/Partial; blabla
13849From:<tartanpion@machin.truc>
13850
13851Hello!
13852
13853Content-Type: Message/Partial; blabla
13854
13855Bye.
13856EOM
13857),
13858 'skipmess: 6 match Content-Type: Message/Partial' ) ;
13859
13860 ok( 1 == skipmess(
13861<<'EOM'
13862Date: Sat, 10 Jul 2010 05:34:45 -0700
13863Content-Type: Message/Partial;
13864From:<tartanpion@machin.truc>
13865
13866Hello!
13867Bye.
13868EOM
13869),
13870 'skipmess: 7 match Content-Type: Message/Partial' ) ;
13871
13872 ok( 1 == skipmess(
13873<<'EOM'
13874Date: Wed, 2 Jul 2014 02:26:40 +0000
13875MIME-Version: 1.0
13876Content-Type: message/partial;
13877 id="TAN_U_P<1404267997.00007489ed17>";
13878 number=3;
13879 total=3
13880
138816HQ6Hh3CdXj77qEGixerQ6zHx0OnQ/Cf5On4W0Y6vtU2crABZQtD46Hx1EOh8dDz4+OnTr1G
13882
13883
13884Hello!
13885Bye.
13886EOM
13887),
13888 'skipmess: 8 match Content-Type: Message/Partial' ) ;
13889
13890
13891ok( 1 == skipmess(
13892<<'EOM'
13893Return-Path: <gilles@lamiral.info>
13894Received: by lamiral.info (Postfix, from userid 1000)
13895 id 21EB12443BF; Mon, 2 Mar 2015 15:38:35 +0100 (CET)
13896Subject: test: aethaecohngiexao
13897To: <tata@petite.lamiral.info>
13898X-Mailer: mail (GNU Mailutils 2.2)
13899Message-Id: <20150302143835.21EB12443BF@lamiral.info>
13900Content-Type: message/partial;
13901 id="TAN_U_P<1404267997.00007489ed17>";
13902 number=3;
13903 total=3
13904Date: Mon, 2 Mar 2015 15:38:34 +0100 (CET)
13905From: gilles@lamiral.info (Gilles LAMIRAL)
13906
13907test: aethaecohngiexao
13908EOM
13909),
13910 'skipmess: 9 match Content-Type: Message/Partial' ) ;
13911
13912ok( 1 == skipmess(
13913<<'EOM'
13914Date: Mon, 2 Mar 2015 15:38:34 +0100 (CET)
13915From: gilles@lamiral.info (Gilles LAMIRAL)
13916Content-Type: message/partial;
13917 id="TAN_U_P<1404267997.00007489ed17>";
13918 number=3;
13919 total=3
13920
13921test: aethaecohngiexao
13922EOM
13923. "lalala\n" x 3_000_000
13924),
13925 'skipmess: 10 match Content-Type: Message/Partial' ) ;
13926
13927ok( 0 == skipmess(
13928<<'EOM'
13929Date: Mon, 2 Mar 2015 15:38:34 +0100 (CET)
13930From: gilles@lamiral.info (Gilles LAMIRAL)
13931
13932test: aethaecohngiexao
13933EOM
13934. "lalala\n" x 3_000_000
13935),
13936 'skipmess: 11 match Content-Type: Message/Partial' ) ;
13937
13938
13939ok( 0 == skipmess(
13940<<"EOM"
13941From: fff\r
13942To: fff\r
13943Subject: Testing imapsync --skipmess\r
13944Date: Mon, 22 Aug 2011 08:40:20 +0800\r
13945Mime-Version: 1.0\r
13946Content-Type: text/plain; charset=iso-8859-1\r
13947Content-Transfer-Encoding: 7bit\r
13948\r
13949EOM
13950. qq{!#"d%&'()*+,-./0123456789:;<=>?\@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefg\r\n } x 32_730
13951),
13952 'skipmess: 12 not match Content-Type: Message/Partial' ) ;
13953 # Complex regular subexpression recursion limit (32766) exceeded with more lines
13954 # exit;
13955
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013956
13957 undef @skipmess ;
13958 note( 'Leaving tests_skipmess()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013959 return ;
13960}
13961
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013962
13963sub tests_skipmess_neg
13964{
13965 note( 'Entering tests_skipmess_neg()' ) ;
13966
13967
13968 @skipmess = ('m{i}') ;
13969 ok( 1 == skipmess( 'Hi!' ), 'skipmess: i string yes' ) ;
13970 ok( 0 == skipmess( 'Ho!' ), 'skipmess: i string no' ) ;
13971
13972 @skipmess = ('m{\A(?!.*i)}') ;
13973 ok( 0 == skipmess( 'Hi!' ), 'skipmess: not i string no' ) ;
13974 ok( 1 == skipmess( 'Ho!' ), 'skipmess: not i string yes' ) ;
13975
13976
13977 @skipmess = ('m{\A(?!.*^From:[^\n]*tartanpion\@machin\.truc)}xms') ;
13978
13979 ok( 0 == skipmess(
13980<<'EOM'
13981Date: Sat, 10 Jul 2010 05:34:45 -0700
13982From: <tartanpion@machin.truc>
13983
13984Bye.
13985EOM
13986),
13987 'skipmess: 1 not From tartanpion@machin.truc' ) ;
13988
13989ok( 1 == skipmess(
13990<<'EOM'
13991Date: Sat, 10 Jul 2010 05:34:45 -0700
13992From: <kikiki@machin.truc>
13993
13994Bye.
13995EOM
13996),
13997 'skipmess: 2 not From tartanpion@machin.truc' ) ;
13998
13999
14000
14001
14002 ok( 0 == skipmess(
14003<<'EOM'
14004Date: Sat, 10 Jul 2010 05:34:45 -0700
14005From: <tartanpion@machin.truc>
14006
14007 From: <tartanpion@machin.truc>
14008Bye.
14009EOM
14010),
14011 'skipmess: 3 not From tartanpion@machin.truc' ) ;
14012
14013ok( 1 == skipmess(
14014<<'EOM'
14015Date: Sat, 10 Jul 2010 05:34:45 -0700
14016From: <kikiki@machin.truc>
14017
14018 From: <tartanpion@machin.truc>
14019Bye.
14020EOM
14021),
14022 'skipmess: 4 not From tartanpion@machin.truc' ) ;
14023
14024
14025
14026
14027 undef @skipmess ;
14028 note( 'Leaving tests_skipmess_neg()' ) ;
14029 return ;
14030}
14031
14032
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014033sub skipmess
14034{
14035 my ( $string ) = @_ ;
14036 my $match ;
14037 #myprint( "$string\n" ) ;
14038 foreach my $skipmess ( @skipmess ) {
14039 $sync->{ debug } and myprint( "eval \$match = \$string =~ $skipmess\n" ) ;
14040 my $ret = eval "\$match = \$string =~ $skipmess ; 1" ;
14041 #myprint( "eval [$ret]\n" ) ;
14042 $sync->{ debug } and myprint( "match [$match]\n" ) ;
14043 if ( ( not $ret ) or $EVAL_ERROR ) {
14044 myprint( "Error: eval skipmess '$skipmess': $EVAL_ERROR" ) ;
14045 return( undef ) ;
14046 }
14047 return( $match ) if ( $match ) ;
14048 }
14049 return( $match ) ;
14050}
14051
14052
14053
14054
14055sub tests_bytes_display_string
14056{
14057 note( 'Entering tests_bytes_display_string()' ) ;
14058
14059
14060 is( 'NA', bytes_display_string( ), 'bytes_display_string: no args => NA' ) ;
14061 is( 'NA', bytes_display_string( undef ), 'bytes_display_string: undef => NA' ) ;
14062 is( 'NA', bytes_display_string( 'blabla' ), 'bytes_display_string: blabla => NA' ) ;
14063
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014064 is( '0.000 KiB', bytes_display_string( 0 ), 'bytes_display_string: 0' ) ;
14065 is( '0.001 KiB', bytes_display_string( 1 ), 'bytes_display_string: 1' ) ;
14066 is( '0.010 KiB', bytes_display_string( 10 ), 'bytes_display_string: 10' ) ;
14067 is( '1.000 MiB', bytes_display_string( 1_048_575 ), 'bytes_display_string: 1_048_575' ) ;
14068 is( '1.000 MiB', bytes_display_string( 1_048_576 ), 'bytes_display_string: 1_048_576' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014069
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014070 is( '1.000 GiB', bytes_display_string( 1_073_741_823 ), 'bytes_display_string: 1_073_741_823 ' ) ;
14071 is( '1.000 GiB', bytes_display_string( 1_073_741_824 ), 'bytes_display_string: 1_073_741_824 ' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014072
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014073 is( '1.000 TiB', bytes_display_string( 1_099_511_627_775 ), 'bytes_display_string: 1_099_511_627_775' ) ;
14074 is( '1.000 TiB', bytes_display_string( 1_099_511_627_776 ), 'bytes_display_string: 1_099_511_627_776' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014075
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014076 is( '1.000 PiB', bytes_display_string( 1_125_899_906_842_623 ), 'bytes_display_string: 1_125_899_906_842_623' ) ;
14077 is( '1.000 PiB', bytes_display_string( 1_125_899_906_842_624 ), 'bytes_display_string: 1_125_899_906_842_624' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014078
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014079 is( '1024.000 PiB', bytes_display_string( 1_152_921_504_606_846_975 ), 'bytes_display_string: 1_152_921_504_606_846_975' ) ;
14080 is( '1024.000 PiB', bytes_display_string( 1_152_921_504_606_846_976 ), 'bytes_display_string: 1_152_921_504_606_846_976' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014081
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014082 is( '1048576.000 PiB', bytes_display_string( 1_180_591_620_717_411_303_424 ), 'bytes_display_string: 1_180_591_620_717_411_303_424' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014083
14084 #myprint( bytes_display_string( 1_180_591_620_717_411_303_424 ), "\n" ) ;
14085 note( 'Leaving tests_bytes_display_string()' ) ;
14086
14087 return ;
14088}
14089
14090sub bytes_display_string
14091{
14092 my ( $bytes ) = @_ ;
14093
14094 my $readable_value = q{} ;
14095
14096 if ( ! defined( $bytes ) ) {
14097 return( 'NA' ) ;
14098 }
14099
14100 if ( not match_number( $bytes ) ) {
14101 return( 'NA' ) ;
14102 }
14103
14104
14105
14106 SWITCH: {
14107 if ( abs( $bytes ) < ( 1000 * $KIBI ) ) {
14108 $readable_value = mysprintf( '%.3f KiB', $bytes / $KIBI) ;
14109 last SWITCH ;
14110 }
14111 if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI ) ) {
14112 $readable_value = mysprintf( '%.3f MiB', $bytes / ($KIBI * $KIBI) ) ;
14113 last SWITCH ;
14114 }
14115 if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI * $KIBI) ) {
14116 $readable_value = mysprintf( '%.3f GiB', $bytes / ($KIBI * $KIBI * $KIBI) ) ;
14117 last SWITCH ;
14118 }
14119 if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI * $KIBI * $KIBI) ) {
14120 $readable_value = mysprintf( '%.3f TiB', $bytes / ($KIBI * $KIBI * $KIBI * $KIBI) ) ;
14121 last SWITCH ;
14122 } else {
14123 $readable_value = mysprintf( '%.3f PiB', $bytes / ($KIBI * $KIBI * $KIBI * $KIBI * $KIBI) ) ;
14124 }
14125 # if you have exabytes (EiB) of email to transfer, you have too much email!
14126 }
14127 #myprint( "$bytes = $readable_value\n" ) ;
14128 return( $readable_value ) ;
14129}
14130
14131
14132sub tests_useheader_suggestion
14133{
14134 note( 'Entering tests_useheader_suggestion()' ) ;
14135
14136 is( undef, useheader_suggestion( ), 'useheader_suggestion: no args => undef' ) ;
14137 my $mysync = {} ;
14138
14139 $mysync->{ h1_nb_msg_noheader } = 0 ;
14140 is( q{}, useheader_suggestion( $mysync ), 'useheader_suggestion: h1_nb_msg_noheader count null => no suggestion' ) ;
14141 $mysync->{ h1_nb_msg_noheader } = 2 ;
14142 is( q{in order to sync those 2 unidentified messages, add option --addheader}, useheader_suggestion( $mysync ),
14143 'useheader_suggestion: h1_nb_msg_noheader count 2 => suggestion of --addheader' ) ;
14144
14145 note( 'Leaving tests_useheader_suggestion()' ) ;
14146 return ;
14147}
14148
14149sub useheader_suggestion
14150{
14151 my $mysync = shift ;
14152 if ( ! defined $mysync->{ h1_nb_msg_noheader } )
14153 {
14154 return ;
14155 }
14156 elsif ( 1 <= $mysync->{ h1_nb_msg_noheader } )
14157 {
14158 return qq{in order to sync those $mysync->{ h1_nb_msg_noheader } unidentified messages, add option --addheader} ;
14159 }
14160 else
14161 {
14162 return q{} ;
14163 }
14164 return ;
14165}
14166
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014167sub do_and_print_stats
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014168{
14169 my $mysync = shift ;
14170
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014171 if ( ! $mysync->{can_do_stats} ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014172 return ;
14173 }
14174
14175 my $timeend = time ;
14176 my $timediff = $timeend - $mysync->{timestart} ;
14177
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014178 my $timeend_str = localtimez( $timeend ) ;
14179
14180 my $cpu_time = cpu_time( $mysync ) ;
14181 my $cpu_percent = cpu_percent( $mysync, $cpu_time, $timediff ) ;
14182 my $cpu_percent_global = cpu_percent_global( $mysync, $cpu_percent ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014183
14184 my $memory_consumption_at_end = memory_consumption( ) || 0 ;
14185 my $memory_consumption_at_start = $mysync->{ memory_consumption_at_start } || 0 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014186 my $memory_ratio = ( $mysync->{ biggest_message_transferred } ) ?
14187 mysprintf( '%.1f', $memory_consumption_at_end / $mysync->{ biggest_message_transferred } ) : 'NA' ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014188
14189 # my $useheader_suggestion = useheader_suggestion( $mysync ) ;
14190 myprint( "++++ Statistics\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014191 myprint( "Transfer started on : $mysync->{ timestart_str }\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014192 myprint( "Transfer ended on : $timeend_str\n" ) ;
14193 myprintf( "Transfer time : %.1f sec\n", $timediff ) ;
14194 myprint( "Folders synced : $h1_folders_wanted_ct/$h1_folders_wanted_nb synced\n" ) ;
14195 myprint( "Messages transferred : $mysync->{ nb_msg_transferred } " ) ;
14196 myprint( "(could be $nb_msg_skipped_dry_mode without dry mode)" ) if ( $mysync->{dry} ) ;
14197 myprint( "\n" ) ;
14198 myprint( "Messages skipped : $mysync->{ nb_msg_skipped }\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014199 myprint( "Messages found duplicate on host1 : $mysync->{ acc1 }->{ nb_msg_duplicate }\n" ) ;
14200 myprint( "Messages found duplicate on host2 : $mysync->{ acc2 }->{ nb_msg_duplicate }\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014201 myprint( "Messages found crossduplicate on host2 : $mysync->{ h2_nb_msg_crossdup }\n" ) ;
14202 myprint( "Messages void (noheader) on host1 : $mysync->{ h1_nb_msg_noheader } ", useheader_suggestion( $mysync ), "\n" ) ;
14203 myprint( "Messages void (noheader) on host2 : $h2_nb_msg_noheader\n" ) ;
14204 nb_messages_in_1_not_in_2( $mysync ) ;
14205 nb_messages_in_2_not_in_1( $mysync ) ;
14206 myprintf( "Messages found in host1 not in host2 : %s messages\n", $mysync->{ nb_messages_in_1_not_in_2 } ) ;
14207 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 +020014208 myprint( "Messages deleted on host1 : $mysync->{ acc1 }->{ nb_msg_deleted }\n" ) ;
14209 myprint( "Messages deleted on host2 : $mysync->{ acc2 }->{ nb_msg_deleted }\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014210 myprintf( "Total bytes transferred : %s (%s)\n",
14211 $mysync->{total_bytes_transferred},
14212 bytes_display_string( $mysync->{total_bytes_transferred} ) ) ;
14213 myprintf( "Total bytes skipped : %s (%s)\n",
14214 $mysync->{ total_bytes_skipped },
14215 bytes_display_string( $mysync->{ total_bytes_skipped } ) ) ;
14216 $timediff ||= 1 ; # No division per 0
14217 myprintf("Message rate : %.1f messages/s\n", $mysync->{nb_msg_transferred} / $timediff ) ;
14218 myprintf("Average bandwidth rate : %.1f KiB/s\n", $mysync->{total_bytes_transferred} / $KIBI / $timediff ) ;
14219 myprint( "Reconnections to host1 : $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT}\n" ) ;
14220 myprint( "Reconnections to host2 : $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT}\n" ) ;
14221 myprintf("Memory consumption at the end : %.1f MiB (started with %.1f MiB)\n",
14222 $memory_consumption_at_end / $KIBI / $KIBI,
14223 $memory_consumption_at_start / $KIBI / $KIBI ) ;
14224 myprint( "Load end is : " . ( join( q{ }, loadavg( ) ) || 'unknown' ), " on $mysync->{cpu_number} cores\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014225 myprint( "CPU time and %cpu : $cpu_time sec $cpu_percent %cpu $cpu_percent_global %allcpus\n" ) ;
14226 myprintf("Biggest message transferred : %s bytes (%s)\n",
14227 $mysync->{ biggest_message_transferred },
14228 bytes_display_string( $mysync->{ biggest_message_transferred } ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014229 myprint( "Memory/biggest message ratio : $memory_ratio\n" ) ;
14230 if ( $mysync->{ foldersizesatend } and $mysync->{ foldersizes } ) {
14231
14232
14233 my $nb_msg_start_diff = diff_or_NA( $mysync->{ h2_nb_msg_start }, $mysync->{ h1_nb_msg_start } ) ;
14234 my $bytes_start_diff = diff_or_NA( $mysync->{ h2_bytes_start }, $mysync->{ h1_bytes_start } ) ;
14235
14236 myprintf("Start difference host2 - host1 : %s messages, %s bytes (%s)\n", $nb_msg_start_diff,
14237 $bytes_start_diff,
14238 bytes_display_string( $bytes_start_diff ) ) ;
14239
14240 my $nb_msg_end_diff = diff_or_NA( $h2_nb_msg_end, $h1_nb_msg_end ) ;
14241 my $bytes_end_diff = diff_or_NA( $h2_bytes_end, $h1_bytes_end ) ;
14242
14243 myprintf("Final difference host2 - host1 : %s messages, %s bytes (%s)\n", $nb_msg_end_diff,
14244 $bytes_end_diff,
14245 bytes_display_string( $bytes_end_diff ) ) ;
14246 }
14247
14248 comment_on_final_diff_in_1_not_in_2( $mysync ) ;
14249 comment_on_final_diff_in_2_not_in_1( $mysync ) ;
14250 myprint( "Detected $mysync->{nb_errors} errors\n\n" ) ;
14251
14252 myprint( $warn_release, "\n" ) ;
14253 myprint( homepage( ), "\n" ) ;
14254 return ;
14255}
14256
14257sub diff_or_NA
14258{
14259 my( $n1, $n2 ) = @ARG ;
14260
14261 if ( not defined $n1 or not defined $n2 ) {
14262 return 'NA' ;
14263 }
14264
14265 if ( not match_number( $n1 )
14266 or not match_number( $n2 ) ) {
14267 return 'NA' ;
14268 }
14269
14270 return( $n1 - $n2 ) ;
14271}
14272
14273sub match_number
14274{
14275 my $n = shift @ARG ;
14276
14277 if ( not defined $n ) {
14278 return 0 ;
14279 }
14280 if ( $n =~ /[0-9]+\.?[0-9]?/x ) {
14281 return 1 ;
14282 }
14283 else {
14284 return 0 ;
14285 }
14286}
14287
14288
14289sub tests_match_number
14290{
14291 note( 'Entering tests_match_number()' ) ;
14292
14293
14294 is( 0, match_number( ), 'match_number: no parameters => 0' ) ;
14295 is( 0, match_number( undef ), 'match_number: undef => 0' ) ;
14296 is( 0, match_number( 'blabla' ), 'match_number: blabla => 0' ) ;
14297 is( 1, match_number( 0 ), 'match_number: 0 => 1' ) ;
14298 is( 1, match_number( 1 ), 'match_number: 1 => 1' ) ;
14299 is( 1, match_number( 1.0 ), 'match_number: 1.0 => 1' ) ;
14300 is( 1, match_number( 0.0 ), 'match_number: 0.0 => 1' ) ;
14301
14302 note( 'Leaving tests_match_number()' ) ;
14303 return ;
14304}
14305
14306
14307
14308sub tests_diff_or_NA
14309{
14310 note( 'Entering tests_diff_or_NA()' ) ;
14311
14312
14313 is( 'NA', diff_or_NA( ), 'diff_or_NA: no parameters => NA' ) ;
14314 is( 'NA', diff_or_NA( undef ), 'diff_or_NA: undef => NA' ) ;
14315 is( 'NA', diff_or_NA( undef, undef ), 'diff_or_NA: undef undef => NA' ) ;
14316 is( 'NA', diff_or_NA( undef, 1 ), 'diff_or_NA: undef 1 => NA' ) ;
14317 is( 'NA', diff_or_NA( 1, undef ), 'diff_or_NA: 1 undef => NA' ) ;
14318 is( 'NA', diff_or_NA( 'blabla', 1 ), 'diff_or_NA: blabla 1 => NA' ) ;
14319 is( 'NA', diff_or_NA( 1, 'blabla' ), 'diff_or_NA: 1 blabla => NA' ) ;
14320 is( 0, diff_or_NA( 1, 1 ), 'diff_or_NA: 1 1 => 0' ) ;
14321 is( 1, diff_or_NA( 1, 0 ), 'diff_or_NA: 1 0 => 1' ) ;
14322 is( -1, diff_or_NA( 0, 1 ), 'diff_or_NA: 0 1 => -1' ) ;
14323 is( 0, diff_or_NA( 1.0, 1 ), 'diff_or_NA: 1.0 1 => 0' ) ;
14324 is( 1, diff_or_NA( 1.0, 0 ), 'diff_or_NA: 1.0 0 => 1' ) ;
14325 is( -1, diff_or_NA( 0, 1.0 ), 'diff_or_NA: 0 1.0 => -1' ) ;
14326
14327 note( 'Leaving tests_diff_or_NA()' ) ;
14328 return ;
14329}
14330
14331sub homepage
14332{
14333 return( 'Homepage: https://imapsync.lamiral.info/' ) ;
14334}
14335
14336
14337sub load_modules
14338{
14339 if ( $sync->{ssl1}
14340 or $sync->{ssl2}
14341 or $sync->{tls1}
14342 or $sync->{tls2}) {
14343 if ( $sync->{inet4} ) {
14344 IO::Socket::SSL->import( 'inet4' ) ;
14345 }
14346 if ( $sync->{inet6} ) {
14347 IO::Socket::SSL->import( 'inet6' ) ;
14348 }
14349 }
14350 return ;
14351}
14352
14353
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014354# Globals: $skipsize $wholeheaderifneeded
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014355sub parse_header_msg
14356{
14357 my ( $mysync, $imap, $m_uid, $s_heads, $s_fir, $side, $s_hash ) = @_ ;
14358
14359 my $head = $s_heads->{$m_uid} ;
14360 my $headnum = scalar keys %{ $head } ;
14361 $mysync->{ debug } and myprint( "$side: uid $m_uid number of headers, pass one: ", $headnum, "\n" ) ;
14362
14363 if ( ( ! $headnum ) and ( $wholeheaderifneeded ) ){
14364 $mysync->{ debug } and myprint( "$side: uid $m_uid no header by parse_headers so taking whole header with BODY.PEEK[HEADER]\n" ) ;
14365 $imap->fetch($m_uid, 'BODY.PEEK[HEADER]' ) ;
14366 my $whole_header = $imap->_transaction_literals ;
14367
14368 #myprint( $whole_header ) ;
14369 $head = decompose_header( $whole_header ) ;
14370
14371 $headnum = scalar keys %{ $head } ;
14372 $mysync->{ debug } and myprint( "$side: uid $m_uid number of headers, pass two: ", $headnum, "\n" ) ;
14373 }
14374
14375 #myprint( Data::Dumper->Dump( [ $head, \%useheader ] ) ) ;
14376
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014377 my $headstr = header_construct( $mysync, $head, $side, $m_uid ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014378
14379 if ( ( ! $headstr ) and ( $mysync->{addheader} ) and ( $side eq 'Host1' ) ) {
14380 my $header = add_header( $m_uid ) ;
14381 $mysync->{ debug } and myprint( "$side: uid $m_uid no header found so adding our own [$header]\n" ) ;
14382 $headstr .= uc $header ;
14383 $s_fir->{$m_uid}->{NO_HEADER} = 1;
14384 }
14385
14386 return if ( ! $headstr ) ;
14387
14388 my $size = $s_fir->{$m_uid}->{'RFC822.SIZE'} ;
14389 my $flags = $s_fir->{$m_uid}->{'FLAGS'} ;
14390 my $idate = $s_fir->{$m_uid}->{'INTERNALDATE'} ;
14391 $size = length $headstr unless ( $size ) ;
14392 my $m_md5 = md5_base64( $headstr ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014393
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014394 my $key ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014395 if ( $skipsize ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014396 $key = "$m_md5";
14397 }
14398 else {
14399 $key = "$m_md5:$size";
14400 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014401
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014402 if ( exists $s_hash->{"$key"} )
14403 {
14404 # 0 return code is used to identify duplicate message hash
14405 my $dup_ref = $s_hash->{"$key"}->{'U'} ;
14406 my $num = scalar( @{ $dup_ref } ) ;
14407 push( @{ $dup_ref }, $m_uid ) ;
14408 my $keydup = "$key#$num" ;
14409 $mysync->{ debug } and myprint( "$side: uid $m_uid sig $keydup size $size idate $idate dup @{ $dup_ref }\n" ) ;
14410 if ( $mysync->{ syncduplicates } )
14411 {
14412 $s_hash->{"$keydup"}{'5'} = $m_md5 ;
14413 $s_hash->{"$keydup"}{'s'} = $size ;
14414 $s_hash->{"$keydup"}{'D'} = $idate ;
14415 $s_hash->{"$keydup"}{'F'} = $flags ;
14416 $s_hash->{"$keydup"}{'m'} = $m_uid ;
14417 }
14418 return 0 ;
14419 }
14420 else
14421 {
14422 $s_hash->{"$key"}{'5'} = $m_md5 ;
14423 $s_hash->{"$key"}{'s'} = $size ;
14424 $s_hash->{"$key"}{'D'} = $idate ;
14425 $s_hash->{"$key"}{'F'} = $flags ;
14426 $s_hash->{"$key"}{'m'} = $m_uid ;
14427 $s_hash->{"$key"}{'U'} = [ $m_uid ] ; # ? or [ ] ?
14428 $mysync->{ debug } and myprint( "$side: uid $m_uid sig $key size $size idate $idate\n" ) ;
14429 return( 1 ) ;
14430 }
14431
14432 # we should not be here
14433 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014434}
14435
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014436sub tests_header_construct
14437{
14438 note( 'Entering tests_header_construct()' ) ;
14439
14440 is( undef, header_construct( ), 'header_construct: no args => undef' ) ;
14441 my $mysync = {} ;
14442 my $head = {
14443 'key1' => [ 'val1_key1' ]
14444 } ;
14445 is( undef, header_construct( $mysync, $head, 'Host1', '1' ), 'header_construct: key1 val1_key1 no useheader => undef' ) ;
14446
14447 $mysync->{useheader}->{ 'KEY1' } = 1 ;
14448 is( 'KEY1: VAL1_KEY1', header_construct( $mysync, $head, 'Host1', '1' ), 'header_construct: key1 val1_key1 => KEY1: VAL1_KEY1' ) ;
14449
14450
14451
14452 $head = {
14453 'key1' => [ 'val1_key1', 'val3_key1', 'val2_key1' ]
14454 } ;
14455 is( 'KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1', header_construct( $mysync, $head, 'Host1', '1' ),
14456 'header_construct: key1 val1_key1 val3_key1 val2_key1 => KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1' ) ;
14457
14458 $head = {
14459 'key1' => [ 'val1_key1', 'val3_key1', ' val2_key1' ]
14460 } ;
14461 is( 'KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1', header_construct( $mysync, $head, 'Host1', '1' ),
14462 'header_construct: key1 val1_key1 val3_key1 val2_key1 => KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1' ) ;
14463
14464 $mysync->{useheader}->{ 'ALL' } = 1 ;
14465
14466 is( 'KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1', header_construct( $mysync, $head, 'Host1', '1' ),
14467 'header_construct: key1 val1_key1 val3_key1 val2_key1 useheader ALL => KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1' ) ;
14468
14469 $mysync->{skipheader} = 'key1' ;
14470 is( undef, header_construct( $mysync, $head, 'Host1', '1' ),
14471 'header_construct: key1 val1_key1 val3_key1 val2_key1 useheader ALL => undef' ) ;
14472
14473 $head = {
14474 'key1' => [ 'val1_key1', 'val3_key1', ' val2_key1' ],
14475 'key2' => [ 'val1_key2', 'val3_key2', ' val2_key2' ]
14476 } ;
14477 is( 'KEY2: VAL1_KEY2KEY2: VAL2_KEY2KEY2: VAL3_KEY2', header_construct( $mysync, $head, 'Host1', '1' ),
14478 'header_construct: ... useheader ALL skipheader key1 => KEY2: VAL1_KEY2KEY2: VAL2_KEY2KEY2: VAL3_KEY2' ) ;
14479
14480
14481 note( 'Leaving tests_header_construct()' ) ;
14482 return ;
14483}
14484
14485
14486# No global in header_construct
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014487sub header_construct
14488{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014489 my( $mysync, $head, $side, $m_uid ) = @_ ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014490
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014491 my @headstr ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014492 foreach my $h ( sort keys %{ $head } ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014493 next if ( not ( exists $mysync->{useheader}->{ uc $h } )
14494 and ( not exists $mysync->{useheader}->{ 'ALL' } )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014495 ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014496 foreach my $val ( @{$head->{$h}} ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014497
14498 my $H = header_line_normalize( $h, $val ) ;
14499
14500 # show stuff in debug mode
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014501 $mysync->{ debug } and myprint( "$side uid $m_uid header [$H]", "\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014502
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014503 if ( $mysync->{skipheader} and $H =~ m/$mysync->{skipheader}/xi) {
14504 $mysync->{ debug } and myprint( "$side uid $m_uid skipping header [$H]\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014505 next ;
14506 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014507 push @headstr, $H ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014508 }
14509 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014510 my $headstr = join( '', sort @headstr ) || undef ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014511 return( $headstr ) ;
14512}
14513
14514
14515sub header_line_normalize
14516{
14517 my( $header_key, $header_val ) = @_ ;
14518
14519 # no 8-bit data in headers !
14520 $header_val =~ s/[\x80-\xff]/X/xog;
14521
14522 # change tabulations to space (Gmail bug on with "Received:" on multilines)
14523 $header_val =~ s/\t/\ /xgo ;
14524
14525 # remove the first blanks ( dbmail bug? )
14526 $header_val =~ s/^\s*//xo;
14527
14528 # remove the last blanks ( Gmail bug )
14529 $header_val =~ s/\s*$//xo;
14530
14531 # remove successive blanks ( Mailenable does it )
14532 $header_val =~ s/\s+/ /xgo;
14533
14534 # remove Message-Id value domain part ( Mailenable changes it )
14535 if ( ( $messageidnodomain ) and ( 'MESSAGE-ID' eq uc $header_key ) ) { $header_val =~ s/^([^@]+).*$/$1/xo ; }
14536
14537 # and uppercase header line
14538 # (dbmail and dovecot)
14539
14540 my $header_line = uc "$header_key: $header_val" ;
14541
14542 return( $header_line ) ;
14543}
14544
14545sub tests_header_line_normalize
14546{
14547 note( 'Entering tests_header_line_normalize()' ) ;
14548
14549
14550 ok( ': ' eq header_line_normalize( q{}, q{} ), 'header_line_normalize: empty args' ) ;
14551 ok( 'HHH: VVV' eq header_line_normalize( 'hhh', 'vvv' ), 'header_line_normalize: hhh vvv ' ) ;
14552 ok( 'HHH: VVV' eq header_line_normalize( 'hhh', ' vvv' ), 'header_line_normalize: remove first blancs' ) ;
14553 ok( 'HHH: AA BB CCC D' eq header_line_normalize( 'hhh', 'aa bb ccc d' ), 'header_line_normalize: remove succesive blanks' ) ;
14554 ok( 'HHH: AA BB CCC' eq header_line_normalize( 'hhh', 'aa bb ccc ' ), 'header_line_normalize: remove last blanks' ) ;
14555 ok( 'HHH: VVV XX YY' eq header_line_normalize( 'hhh', "vvv\t\txx\tyy" ), 'header_line_normalize: tabs' ) ;
14556 ok( 'HHH: XABX' eq header_line_normalize( 'hhh', "\x80AB\xff" ), 'header_line_normalize: 8bit' ) ;
14557
14558 note( 'Leaving tests_header_line_normalize()' ) ;
14559 return ;
14560}
14561
14562
14563sub tests_firstline
14564{
14565 note( 'Entering tests_firstline()' ) ;
14566
14567 is( q{}, firstline( 'W/tmp/tests/noexist.txt' ), 'firstline: getting empty string from inexisting W/tmp/tests/noexist.txt' ) ;
14568
14569 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'firstline: mkpath W/tmp/tests/' ) ;
14570
14571 is( "blabla\n" , string_to_file( "blabla\n", 'W/tmp/tests/firstline.txt' ), 'firstline: put blabla in W/tmp/tests/firstline.txt' ) ;
14572 is( 'blabla' , firstline( 'W/tmp/tests/firstline.txt' ), 'firstline: get blabla from W/tmp/tests/firstline.txt' ) ;
14573
14574 is( q{} , string_to_file( q{}, 'W/tmp/tests/firstline2.txt' ), 'firstline: put empty string in W/tmp/tests/firstline2.txt' ) ;
14575 is( q{} , firstline( 'W/tmp/tests/firstline2.txt' ), 'firstline: get empty string from W/tmp/tests/firstline2.txt' ) ;
14576
14577 is( "\n" , string_to_file( "\n", 'W/tmp/tests/firstline3.txt' ), 'firstline: put CR in W/tmp/tests/firstline3.txt' ) ;
14578 is( q{} , firstline( 'W/tmp/tests/firstline3.txt' ), 'firstline: get empty string from W/tmp/tests/firstline3.txt' ) ;
14579
14580 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' ) ;
14581 is( 'blabla' , firstline( 'W/tmp/tests/firstline4.txt' ), 'firstline: get blabla from W/tmp/tests/firstline4.txt' ) ;
14582
14583 note( 'Leaving tests_firstline()' ) ;
14584 return ;
14585}
14586
14587sub firstline
14588{
14589 # extract the first line of a file (without \n)
14590 # return empty string if error or empty string
14591
14592 my $file = shift ;
14593 my $line ;
14594
14595 $line = nthline( $file, 1 ) ;
14596 return $line ;
14597}
14598
14599
14600
14601sub tests_secondline
14602{
14603 note( 'Entering tests_secondline()' ) ;
14604
14605 is( q{}, secondline( 'W/tmp/tests/noexist.txt' ), 'secondline: getting empty string from inexisting W/tmp/tests/noexist.txt' ) ;
14606 is( q{}, secondline( 'W/tmp/tests/noexist.txt', 2 ), 'secondline: 2nd getting empty string from inexisting W/tmp/tests/noexist.txt' ) ;
14607
14608 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'secondline: mkpath W/tmp/tests/' ) ;
14609
14610 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' ) ;
14611 is( 'L2' , secondline( 'W/tmp/tests/secondline.txt' ), 'secondline: get L2 from W/tmp/tests/secondline.txt' ) ;
14612
14613
14614 note( 'Leaving tests_secondline()' ) ;
14615 return ;
14616}
14617
14618
14619sub secondline
14620{
14621 # extract the second line of a file (without \n)
14622 # return empty string if error or empty string
14623
14624 my $file = shift ;
14625 my $line ;
14626
14627 $line = nthline( $file, 2 ) ;
14628 return $line ;
14629}
14630
14631
14632
14633
14634sub tests_nthline
14635{
14636 note( 'Entering tests_nthline()' ) ;
14637
14638 is( q{}, nthline( 'W/tmp/tests/noexist.txt' ), 'nthline: getting empty string from inexisting W/tmp/tests/noexist.txt' ) ;
14639 is( q{}, nthline( 'W/tmp/tests/noexist.txt', 2 ), 'nthline: 2nd getting empty string from inexisting W/tmp/tests/noexist.txt' ) ;
14640
14641 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'nthline: mkpath W/tmp/tests/' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014642 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' ) ;
14643 is( 'L3' , nthline( 'W/tmp/tests/nthline.txt', 3 ), 'nthline: get L3 from W/tmp/tests/nthline.txt' ) ;
14644
14645
14646 note( 'Leaving tests_nthline()' ) ;
14647 return ;
14648}
14649
14650
14651sub nthline
14652{
14653 # extract the nth line of a file (without \n)
14654 # return empty string if error or empty string
14655
14656 my $file = shift ;
14657 my $num = shift ;
14658
14659 if ( ! all_defined( $file, $num ) ) { return q{} ; }
14660
14661 my $line ;
14662
14663 $line = ( file_to_array( $file ) )[$num - 1] ;
14664 if ( ! defined $line )
14665 {
14666 return q{} ;
14667 }
14668 else
14669 {
14670 chomp $line ;
14671 return $line ;
14672 }
14673}
14674
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014675sub tests_file_to_array
14676{
14677 note( 'Entering tests_file_to_array()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014678
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014679 is( undef, file_to_array( ), 'file_to_array: no args => undef' ) ;
14680 is( undef, file_to_array( '/noexist' ), 'file_to_array: /noexist => undef' ) ;
14681 is( undef, file_to_array( '/' ), 'file_to_array: reading a directory => undef' ) ;
14682
14683 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'file_to_array: mkpath W/tmp/tests/' ) ;
14684 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' ) ;
14685 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' ) ;
14686
14687 note( 'Leaving tests_file_to_array()' ) ;
14688 return ;
14689}
14690
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014691sub file_to_array
14692{
14693
14694 my( $file ) = shift ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014695 if ( ! $file ) { return ; }
14696 if ( ! -e $file ) { return ; }
14697 if ( ! -f $file ) { return ; }
14698 if ( ! -r $file ) { return ; }
14699
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014700 my @string ;
14701
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014702 if ( open my $FILE, '<', $file )
14703 {
14704 @string = <$FILE> ;
14705 close $FILE ;
14706 return( @string ) ;
14707 }
14708 else
14709 {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014710 myprint( "Error reading file $file : $OS_ERROR\n" ) ;
14711 return ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014712 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014713}
14714
14715
14716sub tests_file_to_string
14717{
14718 note( 'Entering tests_file_to_string()' ) ;
14719
14720 is( undef, file_to_string( ), 'file_to_string: no args => undef' ) ;
14721 is( undef, file_to_string( '/noexist' ), 'file_to_string: /noexist => undef' ) ;
14722 is( undef, file_to_string( '/' ), 'file_to_string: reading a directory => undef' ) ;
14723 ok( file_to_string( $PROGRAM_NAME ), 'file_to_string: reading myself' ) ;
14724
14725 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'file_to_string: mkpath W/tmp/tests/' ) ;
14726
14727 is( 'lilili', string_to_file( 'lilili', 'W/tmp/tests/canbewritten' ), 'file_to_string: string_to_file filling W/tmp/tests/canbewritten with lilili' ) ;
14728 is( 'lilili', file_to_string( 'W/tmp/tests/canbewritten' ), 'file_to_string: reading W/tmp/tests/canbewritten is lilili' ) ;
14729
14730 is( q{}, string_to_file( q{}, 'W/tmp/tests/empty' ), 'file_to_string: string_to_file filling W/tmp/tests/empty with empty string' ) ;
14731 is( q{}, file_to_string( 'W/tmp/tests/empty' ), 'file_to_string: reading W/tmp/tests/empty is empty' ) ;
14732
14733 note( 'Leaving tests_file_to_string()' ) ;
14734 return ;
14735}
14736
14737sub file_to_string
14738{
14739 my $file = shift ;
14740 if ( ! $file ) { return ; }
14741 if ( ! -e $file ) { return ; }
14742 if ( ! -f $file ) { return ; }
14743 if ( ! -r $file ) { return ; }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014744
14745 return( join q{}, file_to_array( $file ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014746}
14747
14748
14749sub tests_string_to_file
14750{
14751 note( 'Entering tests_string_to_file()' ) ;
14752
14753 is( undef, string_to_file( ), 'string_to_file: no args => undef' ) ;
14754 is( undef, string_to_file( 'lalala' ), 'string_to_file: one arg => undef' ) ;
14755 is( undef, string_to_file( 'lalala', '.' ), 'string_to_file: writing a directory => undef' ) ;
14756 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'string_to_file: mkpath W/tmp/tests/' ) ;
14757 is( 'lalala', string_to_file( 'lalala', 'W/tmp/tests/canbewritten' ), 'string_to_file: W/tmp/tests/canbewritten with lalala' ) ;
14758 is( q{}, string_to_file( q{}, 'W/tmp/tests/empty' ), 'string_to_file: W/tmp/tests/empty with empty string' ) ;
14759
14760 SKIP: {
14761 Readonly my $NB_UNX_tests_string_to_file => 1 ;
14762 skip( 'Not on Unix non-root', $NB_UNX_tests_string_to_file ) if ('MSWin32' eq $OSNAME or '0' eq $EFFECTIVE_USER_ID ) ;
14763 is( undef, string_to_file( 'lalala', '/cantouch' ), 'string_to_file: /cantouch denied => undef' ) ;
14764 }
14765
14766 note( 'Leaving tests_string_to_file()' ) ;
14767 return ;
14768}
14769
14770sub string_to_file
14771{
14772 my( $string, $file ) = @_ ;
14773 if( ! defined $string ) { return ; }
14774 if( ! defined $file ) { return ; }
14775
14776 if ( ! -e $file && ! -w dirname( $file ) ) {
14777 myprint( "string_to_file: directory of $file is not writable\n" ) ;
14778 return ;
14779 }
14780
14781 if ( ! sysopen( FILE, $file, O_WRONLY|O_TRUNC|O_CREAT, 0600) ) {
14782 myprint( "string_to_file: failure writing to $file with error: $OS_ERROR\n" ) ;
14783 return ;
14784 }
14785 print FILE $string ;
14786 close FILE ;
14787 return $string ;
14788}
14789
147900 and <<'MULTILINE_COMMENT' ;
14791This is a multiline comment.
14792Based on David Carter discussion, to do:
14793* Call parameters stay the same.
14794* Now always "return( $string, $error )". Descriptions below.
14795OK * Still capture STDOUT via "1> $output_tmpfile" to finish in $string and "return( $string, $error )"
14796OK * Now also capture STDERR via "2> $error_tmpfile" to finish in $error and "return( $string, $error )"
14797OK * in case of CHILD_ERROR, return( undef, $error )
14798 and print $error, with folder/UID/maybeSubject context,
14799 on console and at the end with the final error listing. Count this as a sync error.
14800* in case of good command, take final $string as is, unless void. In case $error with value then print it.
14801* in case of good command and final $string empty, consider it like CHILD_ERROR =>
14802 return( undef, $error ) and print $error, with folder/UID/maybeSubject context,
14803 on console and at the end with the final error listing. Count this as a sync error.
14804MULTILINE_COMMENT
14805# End of multiline comment.
14806
14807sub pipemess
14808{
14809 my ( $string, @commands ) = @_ ;
14810 my $error = q{} ;
14811 foreach my $command ( @commands ) {
14812 my $input_tmpfile = "$sync->{ tmpdir }/imapsync_tmp_file.$PROCESS_ID.inp.txt" ;
14813 my $output_tmpfile = "$sync->{ tmpdir }/imapsync_tmp_file.$PROCESS_ID.out.txt" ;
14814 my $error_tmpfile = "$sync->{ tmpdir }/imapsync_tmp_file.$PROCESS_ID.err.txt" ;
14815 string_to_file( $string, $input_tmpfile ) ;
14816 ` $command < $input_tmpfile 1> $output_tmpfile 2> $error_tmpfile ` ;
14817 my $is_command_ko = $CHILD_ERROR ;
14818 my $error_cmd = file_to_string( $error_tmpfile ) ;
14819 chomp( $error_cmd ) ;
14820 $string = file_to_string( $output_tmpfile ) ;
14821 my $string_len = length( $string ) ;
14822 unlink $input_tmpfile, $output_tmpfile, $error_tmpfile ;
14823
14824 if ( $is_command_ko or ( ! $string_len ) ) {
14825 my $cmd_exit_value = $CHILD_ERROR >> 8 ;
14826 my $cmd_end_signal = $CHILD_ERROR & 127 ;
14827 my $signal_log = ( $cmd_end_signal ) ? " signal $cmd_end_signal and" : q{} ;
14828 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} ;
14829 myprint( $error_log ) ;
14830 if ( wantarray ) {
14831 return @{ [ undef, $error_log ] }
14832 }else{
14833 return ;
14834 }
14835 }
14836 if ( $error_cmd ) {
14837 $error .= qq{STDERR of --pipemess "$command": $error_cmd\n} ;
14838 myprint( qq{STDERR of --pipemess "$command": $error_cmd\n} ) ;
14839 }
14840 }
14841 #myprint( "[$string]\n" ) ;
14842 if ( wantarray ) {
14843 return ( $string, $error ) ;
14844 }else{
14845 return $string ;
14846 }
14847}
14848
14849
14850
14851sub tests_pipemess
14852{
14853 note( 'Entering tests_pipemess()' ) ;
14854
14855
14856 SKIP: {
14857 Readonly my $NB_WIN_tests_pipemess => 3 ;
14858 skip( 'Not on MSWin32', $NB_WIN_tests_pipemess ) if ('MSWin32' ne $OSNAME) ;
14859 # Windows
14860 # "type" command does not accept redirection of STDIN with <
14861 # "sort" does
14862 ok( "nochange\n" eq pipemess( 'nochange', 'sort' ), 'pipemess: nearly no change by sort' ) ;
14863 ok( "nochange2\n" eq pipemess( 'nochange2', qw( sort sort ) ), 'pipemess: nearly no change by sort,sort' ) ;
14864 # command not found
14865 #diag( 'Warning and failure about cacaprout are on purpose' ) ;
14866 ok( ! defined( pipemess( q{}, 'cacaprout' ) ), 'pipemess: command not found' ) ;
14867
14868 } ;
14869
14870 my ( $stringT, $errorT ) ;
14871
14872 SKIP: {
14873 Readonly my $NB_UNX_tests_pipemess => 25 ;
14874 skip( 'Not on Unix', $NB_UNX_tests_pipemess ) if ('MSWin32' eq $OSNAME) ;
14875 # Unix
14876 ok( 'nochange' eq pipemess( 'nochange', 'cat' ), 'pipemess: no change by cat' ) ;
14877
14878 ok( 'nochange2' eq pipemess( 'nochange2', 'cat', 'cat' ), 'pipemess: no change by cat,cat' ) ;
14879
14880 ok( " 1\tnumberize\n" eq pipemess( "numberize\n", 'cat -n' ), 'pipemess: numberize by cat -n' ) ;
14881 ok( " 1\tnumberize\n 2\tnumberize\n" eq pipemess( "numberize\nnumberize\n", 'cat -n' ), 'pipemess: numberize by cat -n' ) ;
14882
14883 ok( "A\nB\nC\n" eq pipemess( "A\nC\nB\n", 'sort' ), 'pipemess: sort' ) ;
14884
14885 # command not found
14886 #diag( 'Warning and failure about cacaprout are on purpose' ) ;
14887 is( undef, pipemess( q{}, 'cacaprout' ), 'pipemess: command not found' ) ;
14888
14889 # success with true but no output at all
14890 is( undef, pipemess( q{blabla}, 'true' ), 'pipemess: true but no output' ) ;
14891
14892 # failure with false and no output at all
14893 is( undef, pipemess( q{blabla}, 'false' ), 'pipemess: false and no output' ) ;
14894
14895 # Failure since pipemess is not a real pipe, so first cat wait for standard input
14896 is( q{blabla}, pipemess( q{blabla}, '( cat|cat ) ' ), 'pipemess: ok by ( cat|cat )' ) ;
14897
14898
14899 ( $stringT, $errorT ) = pipemess( 'nochange', 'cat' ) ;
14900 is( $stringT, 'nochange', 'pipemess: list context, no change by cat, string' ) ;
14901 is( $errorT, q{}, 'pipemess: list context, no change by cat, no error' ) ;
14902
14903 ( $stringT, $errorT ) = pipemess( 'dontcare', 'true' ) ;
14904 is( $stringT, undef, 'pipemess: list context, true but no output, string' ) ;
14905 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' ) ;
14906
14907 ( $stringT, $errorT ) = pipemess( 'dontcare', 'false' ) ;
14908 is( $stringT, undef, 'pipemess: list context, false and no output, string' ) ;
14909 like( $errorT, qr{\QFailure: --pipemess command "false" ended with "0" characters exit value "1" and STDERR ""\E}xm,
14910 'pipemess: list context, false and no output, error' ) ;
14911
14912 ( $stringT, $errorT ) = pipemess( 'dontcare', '/bin/echo -n blablabla' ) ;
14913 is( $stringT, q{blablabla}, 'pipemess: list context, "echo -n blablabla", string' ) ;
14914 is( $errorT, q{}, 'pipemess: list context, "echo blablabla", error' ) ;
14915
14916
14917 ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo -n blablabla 3>&1 1>&2 2>&3 )' ) ;
14918 is( $stringT, undef, 'pipemess: list context, "no output STDERR blablabla", string' ) ;
14919 like( $errorT, qr{blablabla"}xm, 'pipemess: list context, "no output STDERR blablabla", error' ) ;
14920
14921
14922 ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo -n blablabla 3>&1 1>&2 2>&3 )', 'false' ) ;
14923 is( $stringT, undef, 'pipemess: list context, "no output STDERR blablabla then false", string' ) ;
14924 like( $errorT, qr{blablabla"}xm, 'pipemess: list context, "no output STDERR blablabla then false", error' ) ;
14925
14926 ( $stringT, $errorT ) = pipemess( 'dontcare', 'false', '( echo -n blablabla 3>&1 1>&2 2>&3 )' ) ;
14927 is( $stringT, undef, 'pipemess: list context, "false then STDERR blablabla", string' ) ;
14928 like( $errorT, qr{\QFailure: --pipemess command "false" ended with "0" characters exit value "1" and STDERR ""\E}xm,
14929 'pipemess: list context, "false then STDERR blablabla", error' ) ;
14930
14931 ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo rrrrr ; echo -n error_blablabla 3>&1 1>&2 2>&3 )' ) ;
14932 like( $stringT, qr{rrrrr}xm, 'pipemess: list context, "STDOUT rrrrr STDERR error_blablabla", string' ) ;
14933 like( $errorT, qr{STDERR.*error_blablabla}xm, 'pipemess: list context, "STDOUT rrrrr STDERR error_blablabla", error' ) ;
14934
14935 }
14936
14937 ( $stringT, $errorT ) = pipemess( 'dontcare', 'cacaprout' ) ;
14938 is( $stringT, undef, 'pipemess: list context, cacaprout not found, string' ) ;
14939 like( $errorT, qr{\QFailure: --pipemess command "cacaprout" ended with "0" characters exit value\E}xm,
14940 'pipemess: list context, cacaprout not found, error' ) ;
14941
14942 note( 'Leaving tests_pipemess()' ) ;
14943 return ;
14944}
14945
14946
14947
14948sub tests_is_a_release_number
14949{
14950 note( 'Entering tests_is_a_release_number()' ) ;
14951
14952 is( undef, is_a_release_number( ), 'is_a_release_number: no args => undef' ) ;
14953 ok( is_a_release_number( $RELEASE_NUMBER_EXAMPLE_1 ), 'is_a_release_number 1.351' ) ;
14954 ok( is_a_release_number( $RELEASE_NUMBER_EXAMPLE_2 ), 'is_a_release_number 42.4242' ) ;
14955 ok( is_a_release_number( imapsync_version( $sync ) ), 'is_a_release_number imapsync_version( )' ) ;
14956 ok( ! is_a_release_number( 'blabla' ), '! is_a_release_number blabla' ) ;
14957
14958 note( 'Leaving tests_is_a_release_number()' ) ;
14959 return ;
14960}
14961
14962sub is_a_release_number
14963{
14964 my $number = shift ;
14965 if ( ! defined $number ) { return ; }
14966 return( $number =~ m{^\d+\.\d+$}xo ) ;
14967}
14968
14969
14970
14971sub imapsync_version_public
14972{
14973
14974 my $local_version = imapsync_version( $sync ) ;
14975 my $imapsync_basename = imapsync_basename( ) ;
14976 my $context = imapsync_context( ) ;
14977 my $agent_info = "$OSNAME system, perl "
14978 . mysprintf( '%vd', $PERL_VERSION)
14979 . ", Mail::IMAPClient $Mail::IMAPClient::VERSION"
14980 . " $imapsync_basename"
14981 . " $context" ;
14982 my $sock = IO::Socket::INET->new(
14983 PeerAddr => 'imapsync.lamiral.info',
14984 PeerPort => 80,
14985 Proto => 'tcp',
14986 ) ;
14987 return( 'unknown' ) if not $sock ;
14988 print $sock
14989 "GET /prj/imapsync/VERSION HTTP/1.0\r\n",
14990 "User-Agent: imapsync/$local_version ($agent_info)\r\n",
14991 "Host: ks.lamiral.info\r\n\r\n" ;
14992 my @line = <$sock> ;
14993 close $sock ;
14994 my $last_release = $line[$LAST] ;
14995 chomp $last_release ;
14996 return( $last_release ) ;
14997}
14998
14999sub not_long_imapsync_version_public
15000{
15001 #myprint( "Entering not_long_imapsync_version_public\n" ) ;
15002
15003 my $fake = shift ;
15004 if ( $fake ) { return $fake }
15005
15006 my $val ;
15007
15008 # Doesn't work with gethostbyname (see perlipc)
15009 #local $SIG{ALRM} = sub { die "alarm\n" } ;
15010
15011 if ('MSWin32' eq $OSNAME) {
15012 local $SIG{ALRM} = sub { die "alarm\n" } ;
15013 }else{
15014
15015 POSIX::sigaction(SIGALRM,
15016 POSIX::SigAction->new(sub { croak 'alarm' } ) )
15017 or myprint( "Error setting SIGALRM handler: $OS_ERROR\n" ) ;
15018 }
15019
15020 my $ret = eval {
15021 alarm 3 ;
15022 {
15023 $val = imapsync_version_public( ) ;
15024 #sleep 4 ;
15025 #myprint( "End of imapsync_version_public\n" ) ;
15026 }
15027 alarm 0 ;
15028 1 ;
15029 } ;
15030 #myprint( "eval [$ret]\n" ) ;
15031 if ( ( not $ret ) or $EVAL_ERROR ) {
15032 #myprint( "$EVAL_ERROR" ) ;
15033 if ($EVAL_ERROR =~ /alarm/) {
15034 # timed out
15035 return('timeout') ;
15036 }else{
15037 alarm 0 ;
15038 return( 'unknown' ) ; # propagate unexpected errors
15039 }
15040 }else {
15041 # Good!
15042 return( $val ) ;
15043 }
15044}
15045
15046sub tests_not_long_imapsync_version_public
15047{
15048 note( 'Entering tests_not_long_imapsync_version_public()' ) ;
15049
15050
15051 is( 1, is_a_release_number( not_long_imapsync_version_public( ) ),
15052 'not_long_imapsync_version_public: public release is a number' ) ;
15053
15054 note( 'Leaving tests_not_long_imapsync_version_public()' ) ;
15055 return ;
15056}
15057
15058sub check_last_release
15059{
15060 my $fake = shift ;
15061 my $public_release = not_long_imapsync_version_public( $fake ) ;
15062 $sync->{ debug } and myprint( "check_last_release: [$public_release]\n" ) ;
15063 my $inline_help_when_on = '( Use --noreleasecheck to avoid this release check. )' ;
15064
15065 if ( $public_release eq 'unknown' ) {
15066 return( 'Imapsync public release is unknown.' . $inline_help_when_on ) ;
15067 }
15068
15069 if ( $public_release eq 'timeout' ) {
15070 return( 'Imapsync public release is unknown (timeout).' . $inline_help_when_on ) ;
15071 }
15072
15073 if ( ! is_a_release_number( $public_release ) ) {
15074 return( "Imapsync public release is unknown ($public_release)." . $inline_help_when_on ) ;
15075 }
15076
15077 my $imapsync_here = imapsync_version( $sync ) ;
15078
15079 if ( $public_release > $imapsync_here ) {
15080 return( 'This imapsync is not up to date. ' . "( local $imapsync_here < official $public_release )" . $inline_help_when_on ) ;
15081 }else{
15082 return( 'This imapsync is up to date. ' . "( local $imapsync_here >= official $public_release )" . $inline_help_when_on ) ;
15083 }
15084
15085 return( 'really unknown' ) ; # Should never arrive here
15086}
15087
15088sub tests_check_last_release
15089{
15090 note( 'Entering tests_check_last_release()' ) ;
15091
15092 diag( check_last_release( 1.1 ) ) ;
15093 # \Q \E here to avoid putting \ before each space
15094 like( check_last_release( 1.1 ), qr/\Qis up to date\E/mxs, 'check_last_release: up to date' ) ;
15095 like( check_last_release( 1.1 ), qr/1\.1/mxs, 'check_last_release: up to date, include number' ) ;
15096 diag( check_last_release( 999.999 ) ) ;
15097 like( check_last_release( 999.999 ), qr/\Qnot up to date\E/mxs, 'check_last_release: not up to date' ) ;
15098 like( check_last_release( 999.999 ), qr/999\.999/mxs, 'check_last_release: not up to date, include number' ) ;
15099 like( check_last_release( 'unknown' ), qr/\QImapsync public release is unknown\E/mxs, 'check_last_release: unknown' ) ;
15100 like( check_last_release( 'timeout' ), qr/\QImapsync public release is unknown (timeout)\E/mxs, 'check_last_release: timeout' ) ;
15101 like( check_last_release( 'lalala' ), qr/\QImapsync public release is unknown (lalala)\E/mxs, 'check_last_release: lalala' ) ;
15102 diag( check_last_release( ) ) ;
15103
15104 note( 'Leaving tests_check_last_release()' ) ;
15105 return ;
15106}
15107
15108sub tests_imapsync_context
15109{
15110 note( 'Entering tests_imapsync_context()' ) ;
15111
15112 like( imapsync_context( ), qr/^CGI|^Docker|^DockerCGI|^Standard/, 'imapsync_context: CGI or Docker or DockerCGI or Standard' ) ;
15113 note( 'Leaving tests_imapsync_context()' ) ;
15114 return ;
15115}
15116
15117sub imapsync_context
15118{
15119 my $mysync = shift ;
15120
15121 my $context = q{} ;
15122
15123 if ( under_docker_context( $mysync ) && under_cgi_context( $mysync ) )
15124 {
15125 $context = 'DockerCGI' ;
15126 }
15127 elsif ( under_docker_context( $mysync ) )
15128 {
15129 $context = 'Docker' ;
15130 }
15131 elsif ( under_cgi_context( $mysync ) )
15132 {
15133 $context = 'CGI' ;
15134 }
15135 else
15136 {
15137 $context = 'Standard' ;
15138 }
15139
15140 return $context ;
15141
15142}
15143
15144sub imapsync_version
15145{
15146 my $mysync = shift ;
15147 my $rcs = $mysync->{rcs} ;
15148 my $version ;
15149
15150 $version = version_from_rcs( $rcs ) ;
15151 return( $version ) ;
15152}
15153
15154
15155sub tests_version_from_rcs
15156{
15157 note( 'Entering tests_version_from_rcs()' ) ;
15158
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015159 is( undef, version_from_rcs( ), 'version_from_rcs: no args => undef' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015160 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' ) ;
15161 is( 'UNKNOWN', version_from_rcs( 1.831 ), 'version_from_rcs: 1.831 => UNKNOWN' ) ;
15162
15163 note( 'Leaving tests_version_from_rcs()' ) ;
15164 return ;
15165}
15166
15167
15168sub version_from_rcs
15169{
15170
15171 my $rcs = shift ;
15172 if ( ! $rcs ) { return ; }
15173
15174 my $version = 'UNKNOWN' ;
15175
15176 if ( $rcs =~ m{,v\s+(\d+\.\d+)}mxso ) {
15177 $version = $1
15178 }
15179
15180 return( $version ) ;
15181}
15182
15183
15184sub tests_imapsync_basename
15185{
15186 note( 'Entering tests_imapsync_basename()' ) ;
15187
15188 ok( imapsync_basename() =~ m/imapsync/, 'imapsync_basename: match imapsync');
15189 ok( 'blabla' ne imapsync_basename(), 'imapsync_basename: do not equal blabla');
15190
15191 note( 'Leaving tests_imapsync_basename()' ) ;
15192 return ;
15193}
15194
15195sub imapsync_basename
15196{
15197
15198 return basename( $PROGRAM_NAME ) ;
15199
15200}
15201
15202
15203sub localhost_info
15204{
15205 my $mysync = shift ;
15206 my( $infos ) = join( q{},
15207 "Here is imapsync ", imapsync_version( $mysync ),
15208 " on host " . hostname(),
15209 ", a $OSNAME system with ",
15210 ram_memory_info( ),
15211 "\n",
15212 'with Perl ',
15213 mysprintf( '%vd ', $PERL_VERSION),
15214 "and Mail::IMAPClient $Mail::IMAPClient::VERSION",
15215 ) ;
15216 return( $infos ) ;
15217}
15218
15219sub tests_cpu_number
15220{
15221 note( 'Entering tests_cpu_number()' ) ;
15222
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015223 is( 1, is_integer( cpu_number( ) ), "cpu_number: is_integer" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015224 ok( 1 <= cpu_number( ), "cpu_number: 1 or more" ) ;
15225 is( 1, cpu_number( 1 ), "cpu_number: 1 => 1" ) ;
15226 is( 1, cpu_number( $MINUS_ONE ), "cpu_number: -1 => 1" ) ;
15227 is( 1, cpu_number( 'lalala' ), "cpu_number: lalala => 1" ) ;
15228 is( $NUMBER_42, cpu_number( $NUMBER_42 ), "cpu_number: $NUMBER_42 => $NUMBER_42" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015229
15230 note( "cpu_number = " . cpu_number( ) . "\n" ) ;
15231 note( "hostname = " . hostname( ) . "\n" ) ;
15232 SKIP: {
15233 if ( ! ( 'i005' eq hostname() ) )
15234 {
15235 skip( 'cpu_number on host != i005 (FreeBSD)', 1 ) ;
15236 }
15237 is( 4, cpu_number( ), "cpu_number: on i005 (FreeBSD) => 4" ) ;
15238 } ;
15239
15240 SKIP: {
15241 if ( ! ( 'petite' eq hostname() ) )
15242 {
15243 skip( 'cpu_number on host != petite (Linux)', 1 ) ;
15244 }
15245 is( 2, cpu_number( ), "cpu_number: on petite (Linux) => 2" ) ;
15246 } ;
15247
15248 SKIP: {
15249 if ( ! ( skip_macosx( ) ) )
15250 {
15251 skip( 'cpu_number on host != polarhome macosx (Darwin MacOS X 10.7.5 Lion)', 1 ) ;
15252 }
15253 is( 2, cpu_number( ), "cpu_number: on polarhome macosx (Darwin MacOS X 10.7.5 Lion) => 2" ) ;
15254 } ;
15255
15256 SKIP: {
15257 if ( ! ( 'pcHPDV7-HP' eq hostname() ) )
15258 {
15259 skip( 'cpu_number on host != pcHPDV7-HP (Windows 7, 64bits)', 1 ) ;
15260 }
15261 is( 2, cpu_number( ), "cpu_number: on pcHPDV7-HP (Windows 7, 64bits) => 2" ) ;
15262 } ;
15263
15264 SKIP: {
15265 if ( ! ( 'CUILLERE' eq hostname() ) )
15266 {
15267 skip( 'cpu_number on host != CUILLERE (Windows XP, 32bits)', 1 ) ;
15268 }
15269 is( 1, cpu_number( ), "cpu_number: on CUILLERE (Windows XP, 32bits) => 1" ) ;
15270 } ;
15271
15272
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015273 note( 'Leaving tests_cpu_number()' ) ;
15274 return ;
15275}
15276
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015277
15278sub cpu_number {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015279
15280 my $cpu_number_forced = shift ;
15281 # Well, here 1 is better than 0 or undef
15282 my $cpu_number = 1 ; # Default value, erased if better found
15283
15284 my @cpuinfo ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015285 if ( $ENV{"NUMBER_OF_PROCESSORS"} )
15286 {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015287 # might be under a Windows system
15288 $cpu_number = $ENV{"NUMBER_OF_PROCESSORS"} ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015289 #myprint( "Number of processors found by env var NUMBER_OF_PROCESSORS: $cpu_number\n" ) ;
15290 }
15291
15292 if ( 'darwin' eq $OSNAME )
15293 {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015294 $cpu_number = backtick( "sysctl -n hw.ncpu" ) ;
15295 chomp( $cpu_number ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015296 #myprint( "Number of processors found by cmd 'sysctl -n hw.ncpu': $cpu_number\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015297 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015298
15299 if ( 'freebsd' eq $OSNAME )
15300 {
15301 $cpu_number = backtick( "sysctl -n kern.smp.cpus" ) ;
15302 chomp( $cpu_number ) ;
15303 #myprint( "Number of processors found by cmd 'sysctl -n kern.smp.cpus': $cpu_number\n" ) ;
15304 }
15305
15306 if ( 'linux' eq $OSNAME && -e '/proc/cpuinfo' )
15307 {
15308 @cpuinfo = file_to_array( '/proc/cpuinfo' ) ;
15309 $cpu_number = grep { /^processor/mxs } @cpuinfo ;
15310 #myprint( "Number of processors found via /proc/cpuinfo: $cpu_number\n" ) ;
15311 }
15312
15313 if ( defined $cpu_number_forced )
15314 {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015315 $cpu_number = $cpu_number_forced ;
15316 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015317
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015318 return( integer_or_1( $cpu_number ) ) ;
15319}
15320
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015321sub tests_integer_or_1
15322{
15323 note( 'Entering tests_integer_or_1()' ) ;
15324
15325 is( 1, integer_or_1( ), 'integer_or_1: no args => 1' ) ;
15326 is( 1, integer_or_1( undef ), 'integer_or_1: undef => 1' ) ;
15327 is( $NUMBER_10, integer_or_1( $NUMBER_10 ), 'integer_or_1: 10 => 10' ) ;
15328 is( 1, integer_or_1( q{} ), 'integer_or_1: empty string => 1' ) ;
15329 is( 1, integer_or_1( 'lalala' ), 'integer_or_1: lalala => 1' ) ;
15330
15331 note( 'Leaving tests_integer_or_1()' ) ;
15332 return ;
15333}
15334
15335sub integer_or_1
15336{
15337 my $number = shift ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015338 if ( is_integer( $number ) ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015339 return $number ;
15340 }
15341 # else
15342 return 1 ;
15343}
15344
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015345sub tests_is_integer
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015346{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015347 note( 'Entering tests_is_integer()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015348
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015349 is( undef, is_integer( ), 'is_integer: no args => undef ' ) ;
15350 ok( is_integer( 1 ), 'is_integer: 1 => yes ') ;
15351 ok( is_integer( $NUMBER_42 ), 'is_integer: 42 => yes ') ;
15352 ok( is_integer( "$NUMBER_42" ), 'is_integer: "$NUMBER_42" => yes ') ;
15353 ok( is_integer( '42' ), 'is_integer: "42" => yes ') ;
15354 ok( is_integer( $NUMBER_104_857_600 ), 'is_integer: 104_857_600 => yes') ;
15355 ok( is_integer( "$NUMBER_104_857_600" ), 'is_integer: "$NUMBER_104_857_600" => yes') ;
15356 ok( is_integer( '104857600' ), 'is_integer: 104857600 => yes') ;
15357 ok( ! is_integer( 'blabla' ), 'is_integer: blabla => no' ) ;
15358 ok( ! is_integer( q{} ), 'is_integer: empty string => no' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015359
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015360 note( 'Leaving tests_is_integer()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015361 return ;
15362}
15363
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015364sub is_integer
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015365{
15366 my $number = shift ;
15367 if ( ! defined $number ) { return ; }
15368 return( $number =~ m{^\d+$}xo ) ;
15369}
15370
15371
15372
15373
15374sub tests_loadavg
15375{
15376 note( 'Entering tests_loadavg()' ) ;
15377
15378
15379 SKIP: {
15380 skip( 'Tests for darwin', 2 ) if ('darwin' ne $OSNAME) ;
15381 is( undef, loadavg( '/noexist' ), 'loadavg: /noexist => undef' ) ;
15382 is_deeply( [ '0.11', '0.22', '0.33' ],
15383 [ loadavg( 'W/t/loadavg.out' ) ],
15384 'loadavg W/t/loadavg.out => 0.11 0.22 0.33' ) ;
15385 } ;
15386
15387 SKIP: {
15388 skip( 'Tests for linux', 3 ) if ('linux' ne $OSNAME) ;
15389 is( undef, loadavg( '/noexist' ), 'loadavg: /noexist => undef' ) ;
15390 ok( loadavg( ), 'loadavg: no args' ) ;
15391
15392 is_deeply( [ '0.39', '0.30', '0.37', '1/602' ],
15393 [ loadavg( '0.39 0.30 0.37 1/602 6073' ) ],
15394 'loadavg 0.39 0.30 0.37 1/602 6073 => [0.39, 0.30, 0.37, 1/602]' ) ;
15395 } ;
15396
15397 SKIP: {
15398 skip( 'Tests for Windows', 1 ) if ('MSWin32' ne $OSNAME) ;
15399 is_deeply( [ 0 ],
15400 [ loadavg( ) ],
15401 'loadavg on MSWin32 => 0' ) ;
15402
15403 } ;
15404
15405 note( 'Leaving tests_loadavg()' ) ;
15406 return ;
15407}
15408
15409
15410sub loadavg
15411{
15412 if ( 'linux' eq $OSNAME ) {
15413 return ( loadavg_linux( @ARG ) ) ;
15414 }
15415 if ( 'freebsd' eq $OSNAME ) {
15416 return ( loadavg_freebsd( @ARG ) ) ;
15417 }
15418 if ( 'darwin' eq $OSNAME ) {
15419 return ( loadavg_darwin( @ARG ) ) ;
15420 }
15421 if ( 'MSWin32' eq $OSNAME ) {
15422 return ( loadavg_windows( @ARG ) ) ;
15423 }
15424 return( 'unknown' ) ;
15425
15426}
15427
15428sub loadavg_linux
15429{
15430 my $line = shift ;
15431
15432 if ( ! $line ) {
15433 $line = firstline( '/proc/loadavg' ) or return ;
15434 }
15435
15436 my ( $avg_1_min, $avg_5_min, $avg_15_min, $current_runs ) = split /\s/mxs, $line ;
15437 if ( all_defined( $avg_1_min, $avg_5_min, $avg_15_min ) ) {
15438 $sync->{ debug } and myprint( "System load: $avg_1_min $avg_5_min $avg_15_min $current_runs\n" ) ;
15439 return ( $avg_1_min, $avg_5_min, $avg_15_min, $current_runs ) ;
15440 }
15441 return ;
15442}
15443
15444sub loadavg_freebsd
15445{
15446 my $file = shift ;
15447 # Example of output of command "sysctl vm.loadavg":
15448 # vm.loadavg: { 0.15 0.08 0.08 }
15449 my $loadavg ;
15450
15451 if ( ! defined $file ) {
15452 eval {
15453 $loadavg = `/sbin/sysctl vm.loadavg` ;
15454 #myprint( "LOADAVG FREEBSD: $loadavg\n" ) ;
15455 } ;
15456 if ( $EVAL_ERROR ) { myprint( "[$EVAL_ERROR]\n" ) ; return ; }
15457 }else{
15458 $loadavg = firstline( $file ) or return ;
15459 }
15460
15461 my ( $avg_1_min, $avg_5_min, $avg_15_min )
15462 = $loadavg =~ /vm\.loadavg\s*[:=]\s*\{?\s*(\d+\.?\d*)\s+(\d+\.?\d*)\s+(\d+\.?\d*)/mxs ;
15463 $sync->{ debug } and myprint( "System load: $avg_1_min $avg_5_min $avg_15_min\n" ) ;
15464 return ( $avg_1_min, $avg_5_min, $avg_15_min ) ;
15465}
15466
15467sub loadavg_darwin
15468{
15469 my $file = shift ;
15470 # Example of output of command "sysctl vm.loadavg":
15471 # vm.loadavg: { 0.15 0.08 0.08 }
15472 my $loadavg ;
15473
15474 if ( ! defined $file ) {
15475 eval {
15476 $loadavg = `/usr/sbin/sysctl vm.loadavg` ;
15477 #myprint( "LOADAVG DARWIN: $loadavg\n" ) ;
15478 } ;
15479 if ( $EVAL_ERROR ) { myprint( "[$EVAL_ERROR]\n" ) ; return ; }
15480 }else{
15481 $loadavg = firstline( $file ) or return ;
15482 }
15483
15484 my ( $avg_1_min, $avg_5_min, $avg_15_min )
15485 = $loadavg =~ /vm\.loadavg\s*[:=]\s*\{?\s*(\d+\.?\d*)\s+(\d+\.?\d*)\s+(\d+\.?\d*)/mxs ;
15486 $sync->{ debug } and myprint( "System load: $avg_1_min $avg_5_min $avg_15_min\n" ) ;
15487 return ( $avg_1_min, $avg_5_min, $avg_15_min ) ;
15488}
15489
15490sub loadavg_windows
15491{
15492 my $file = shift ;
15493 # Example of output of command "wmic cpu get loadpercentage":
15494 # LoadPercentage
15495 # 12
15496 my $loadavg ;
15497
15498 if ( ! defined $file ) {
15499 eval {
15500 #$loadavg = `CMD wmic cpu get loadpercentage` ;
15501 $loadavg = "LoadPercentage\n0\n" ;
15502 #myprint( "LOADAVG WIN: $loadavg\n" ) ;
15503 } ;
15504 if ( $EVAL_ERROR ) { myprint( "[$EVAL_ERROR]\n" ) ; return ; }
15505 }else{
15506 $loadavg = file_to_string( $file ) or return ;
15507 #myprint( "$loadavg" ) ;
15508 }
15509 $loadavg =~ /LoadPercentage\n(\d+)/xms ;
15510 my $num = $1 ;
15511 $num /= 100 ;
15512
15513 $sync->{ debug } and myprint( "System load: $num\n" ) ;
15514 return ( $num ) ;
15515}
15516
15517
15518
15519
15520
15521
15522sub tests_load_and_delay
15523{
15524 note( 'Entering tests_load_and_delay()' ) ;
15525
15526 is( undef, load_and_delay( ), 'load_and_delay: no args => undef ' ) ;
15527 is( undef, load_and_delay( 1 ), 'load_and_delay: not 4 args => undef ' ) ;
15528 is( undef, load_and_delay( 0, 1, 1, 1 ), 'load_and_delay: division per 0 => undef ' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015529
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015530# ( $cpu_num, $avg_1_min, $avg_5_min, $avg_15_min )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015531
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015532 is( 0, load_and_delay( 1, 1, 1, 1 ), 'load_and_delay: one core, loads are all 1 => ok ' ) ;
15533 is( 0, load_and_delay( 1, 1, 1, 1, 'lalala' ), 'load_and_delay: five arguments is ok' ) ;
15534 is( 0, load_and_delay( 2, 2, 2, 2 ), 'load_and_delay: two core, loads are all 2 => ok ' ) ;
15535 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 +010015536
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015537
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015538 is( 0, load_and_delay( 1, 0, 0, 0 ), 'load_and_delay: one core, load1m=0 load5m=0 load15m=0 => 0 ' ) ;
15539 is( 0, load_and_delay( 1, 0, 0, 2 ), 'load_and_delay: one core, load1m=0 load5m=0 load15m=2 => 0 ' ) ;
15540 is( 0, load_and_delay( 1, 0, 2, 0 ), 'load_and_delay: one core, load1m=0 load5m=2 load15m=0 => 0 ' ) ;
15541 is( 0, load_and_delay( 1, 0, 2, 2 ), 'load_and_delay: one core, load1m=0 load5m=2 load15m=2 => 0 ' ) ;
15542 is( 0, load_and_delay( 1, 0, 3, 3 ), 'load_and_delay: one core, load1m=0 load5m=3 load15m=3 => 0 ' ) ;
15543 is( 0, load_and_delay( 1, 0, 4, 4 ), 'load_and_delay: one core, load1m=0 load5m=3 load15m=3 => 0 ' ) ;
15544 is( 0, load_and_delay( 1, 2, 0, 0 ), 'load_and_delay: one core, load1m=2 load5m=0 load15m=0 => 0 ' ) ;
15545 is( 0, load_and_delay( 1, 2, 0, 2 ), 'load_and_delay: one core, load1m=2 load5m=0 load15m=2 => 0 ' ) ;
15546 is( 0, load_and_delay( 1, 2, 2, 0 ), 'load_and_delay: one core, load1m=2 load5m=2 load15m=0 => 0 ' ) ;
15547 is( 0, load_and_delay( 1, 2, 2, 2 ), 'load_and_delay: one core, load1m=2 load5m=2 load15m=2 => 0 ' ) ;
15548 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 +010015549
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015550 is( 0, load_and_delay( 1, 3, 0, 0 ), 'load_and_delay: one core, load1m=3 load5m=0 load15m=0 => 0 ' ) ;
15551 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 ' ) ;
15552 is( 0, load_and_delay( 1, 3, 3, 2.9 ), 'load_and_delay: one core, load1m=3 load5m=3 load15m=2.9 => 0 ' ) ;
15553 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 +010015554
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015555 is( 1, load_and_delay( 1, 6, 0, 0 ), 'load_and_delay: one core, load1m=3 load5m=0 load15m=0 => 1 ' ) ;
15556 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 ' ) ;
15557 is( 5, load_and_delay( 1, 6, 6, 5.9 ), 'load_and_delay: one core, load1m=3 load5m=3 load15m=2.9 => 5 ' ) ;
15558 is( 15, load_and_delay( 1, 6, 6, 6 ), 'load_and_delay: one core, load1m=3 load5m=3 load15m=3 => 15 ' ) ;
15559
15560
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015561
15562 note( 'Leaving tests_load_and_delay()' ) ;
15563 return ;
15564}
15565
15566sub load_and_delay
15567{
15568 # Basically return 0 if load is not heavy, ie <= 1 per processor
15569
15570 # Not enough arguments
15571 if ( 4 > scalar @ARG ) { return ; }
15572
15573 my ( $cpu_num, $avg_1_min, $avg_5_min, $avg_15_min ) = @ARG ;
15574
15575 if ( 0 == $cpu_num ) { return ; }
15576
15577 # Let divide by number of cores
15578 ( $avg_1_min, $avg_5_min, $avg_15_min ) = map { $_ / $cpu_num } ( $avg_1_min, $avg_5_min, $avg_15_min ) ;
15579 # One of avg ok => ok, for now it is a OR
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015580 if ( $avg_1_min < 6 ) { return 0 ; }
15581 if ( $avg_5_min < 6 ) { return 1 ; } # Retry in 1 minute
15582 if ( $avg_15_min < 6 ) { return 5 ; } # Retry in 5 minutes
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015583 return 15 ; # Retry in 15 minutes
15584}
15585
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015586
15587sub tests_cpu_time
15588{
15589 note( 'Entering tests_cpu_time()' ) ;
15590
15591 ok( is_number( cpu_time( ) ), 'cpu_time: no args => a number' ) ;
15592
15593 my $mysync = { } ;
15594 $mysync->{ debug } = 1 ;
15595 ok( is_number( cpu_time( $mysync ) ), 'cpu_time: {} => a number' ) ;
15596
15597 note( 'Leaving tests_cpu_time()' ) ;
15598 return ;
15599}
15600
15601sub cpu_time
15602{
15603 my $mysync = shift ;
15604
15605 my @cpu_times = times ;
15606 if ( ! @cpu_times ) { return ; }
15607
15608 my $cpu_time = 0 ;
15609 # last element is the sum of all elements
15610 $cpu_time = ( map { $cpu_time += $_ } @cpu_times )[ -1 ] ;
15611 $mysync->{ debug } and myprint( join(' + ', @cpu_times), " = $cpu_time\n" ) ;
15612
15613 return $cpu_time ;
15614}
15615
15616
15617sub tests_cpu_percent
15618{
15619 note( 'Entering tests_cpu_percent()' ) ;
15620
15621 is( '0.0', cpu_percent( ), 'cpu_percent: no args => 0.0' ) ;
15622 my $mysync = { } ;
15623 $mysync->{ debug } = 1 ;
15624 is( '0.0', cpu_percent( $mysync ), 'cpu_percent: {} => 0.0' ) ;
15625 is( '0.0', cpu_percent( $mysync, 0 ), 'cpu_percent: {} 0 => 0.0' ) ;
15626 is( '300.0', cpu_percent( $mysync, 3 ), 'cpu_percent: {} 3 => 300.0' ) ;
15627 is( '30.0', cpu_percent( $mysync, 3, 10 ), 'cpu_percent: {} 3 10 => 30.0' ) ;
15628 is( '0.0', cpu_percent( $mysync, 0, 10 ), 'cpu_percent: {} 0 10 => 0.0' ) ;
15629
15630 note( 'Leaving tests_cpu_percent()' ) ;
15631 return ;
15632}
15633
15634sub cpu_percent
15635{
15636 my $mysync = shift ;
15637 my $cpu_time = shift || 0 ;
15638 my $timediff = shift || 1 ; # no division by 0
15639
15640 if ( $cpu_time > $timediff )
15641 {
15642 myprint( "Strange: cpu_time $cpu_time > timediff $timediff\n" ) ;
15643 }
15644 my $cpu_percent = 0 ;
15645 $cpu_percent = mysprintf( '%.1f', 100 * $cpu_time / $timediff ) ;
15646 $mysync->{ debug } and myprint( "cpu_percent: $cpu_percent \n" ) ;
15647
15648 return $cpu_percent ;
15649
15650}
15651
15652sub tests_cpu_percent_global
15653{
15654 note( 'Entering tests_cpu_percent_global()' ) ;
15655
15656 is( '0.0', cpu_percent_global( ), 'cpu_percent_global: no args => 0' ) ;
15657 my $mysync = { } ;
15658 $mysync->{ debug } = 1 ;
15659 is( '0.0', cpu_percent_global( $mysync ), 'cpu_percent_global: {} => 0' ) ;
15660 is( '0.0', cpu_percent_global( $mysync, 0 ), 'cpu_percent_global: {} 0 => 0' ) ;
15661
15662 SKIP: {
15663 if ( ! ( 'i005' eq hostname() ) )
15664 {
15665 skip( 'cpu_percent_global on host != i005', 1 ) ;
15666 }
15667 is( '25.0', cpu_percent_global( $mysync, 100 ), 'cpu_percent_global: {} 100 => 25 on host i005' ) ;
15668 } ;
15669
15670 SKIP: {
15671 if ( ! ( 'petite' eq hostname() ) )
15672 {
15673 skip( 'cpu_percent_global on host != petite', 1 ) ;
15674 }
15675 is( '50.0', cpu_percent_global( $mysync, 100 ), 'cpu_percent_global: {} 100 => 50 on host petite' ) ;
15676 } ;
15677
15678 note( 'Leaving tests_cpu_percent_global()' ) ;
15679 return ;
15680}
15681
15682sub cpu_percent_global
15683{
15684 my $mysync = shift ;
15685 my $cpu_percent = shift || 0 ;
15686
15687 my $cpu_number = cpu_number( ) ;
15688
15689 my $cpu_percent_global ;
15690 $cpu_percent_global = mysprintf( '%.1f', $cpu_percent / $cpu_number ) ;
15691 $mysync->{ debug } and myprint( "cpu_percent_global: $cpu_percent_global \n" ) ;
15692
15693 return( $cpu_percent_global ) ;
15694}
15695
15696
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015697sub ram_memory_info
15698{
15699 # In GigaBytes so division by 1024 * 1024 * 1024
15700 #
15701 return(
15702 sprintf( "%.1f/%.1f free GiB of RAM",
15703 Sys::MemInfo::get("freemem") / ( $KIBI ** 3 ),
15704 Sys::MemInfo::get("totalmem") / ( $KIBI ** 3 ),
15705 )
15706 ) ;
15707}
15708
15709
15710
15711sub tests_memory_stress
15712{
15713 note( 'Entering tests_memory_stress()' ) ;
15714
15715 is( undef, memory_stress( ), 'memory_stress: => undef' ) ;
15716
15717 note( 'Leaving tests_memory_stress()' ) ;
15718 return ;
15719}
15720
15721sub memory_stress
15722{
15723
15724 my $total_ram_in_MB = Sys::MemInfo::get("totalmem") / ( $KIBI * $KIBI ) ;
15725 my $i = 1 ;
15726
15727 myprintf("Stress memory consumption before: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI ) ;
15728 while ( $i < $total_ram_in_MB / 1.7 ) { $a .= "A" x 1000_000; $i++ } ;
15729 myprintf("Stress memory consumption after: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI ) ;
15730 return ;
15731
15732}
15733
15734sub tests_memory_consumption
15735{
15736 note( 'Entering tests_memory_consumption()' ) ;
15737
15738 like( memory_consumption( ), qr{\d+}xms,'memory_consumption no args') ;
15739 like( memory_consumption( 1 ), qr{\d+}xms,'memory_consumption 1') ;
15740 like( memory_consumption( $PROCESS_ID ), qr{\d+}xms,"memory_consumption_of_pids $PROCESS_ID") ;
15741
15742 like( memory_consumption_ratio(), qr{\d+}xms, 'memory_consumption_ratio' ) ;
15743 like( memory_consumption_ratio(1), qr{\d+}xms, 'memory_consumption_ratio 1' ) ;
15744 like( memory_consumption_ratio(10), qr{\d+}xms, 'memory_consumption_ratio 10' ) ;
15745
15746 like( memory_consumption(), qr{\d+}xms, "memory_consumption\n" ) ;
15747
15748 note( 'Leaving tests_memory_consumption()' ) ;
15749 return ;
15750}
15751
15752sub memory_consumption
15753{
15754 # memory consumed by imapsync until now in bytes
15755 return( ( memory_consumption_of_pids( ) )[0] );
15756}
15757
15758sub debugmemory
15759{
15760 my $mysync = shift ;
15761 if ( ! $mysync->{debugmemory} ) { return q{} ; }
15762
15763 my $precision = shift ;
15764 return( mysprintf( "Memory consumption$precision: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI ) ) ;
15765}
15766
15767sub memory_consumption_of_pids
15768{
15769
15770 my @pid = @_;
15771 @pid = ( @pid ) ? @pid : ( $PROCESS_ID ) ;
15772
15773 $sync->{ debug } and myprint( "memory_consumption_of_pids PIDs: @pid\n" ) ;
15774 my @val ;
15775 if ( ( 'MSWin32' eq $OSNAME ) or ( 'cygwin' eq $OSNAME ) ) {
15776 @val = memory_consumption_of_pids_win32( @pid ) ;
15777 }else{
15778 # Unix
15779 my @ps = qx{ ps -o vsz -p @pid } ;
15780 #myprint( "ps: @ps" ) ;
15781
15782 # Use IPC::Open3 from perlcrit -3
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015783 # But it stalls on Darwin, I don't understand why!
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015784 #my @ps = backtick( "ps -o vsz -p @pid" ) ;
15785 #myprint( "ps: @ps" ) ;
15786
15787 shift @ps; # First line is column name "VSZ"
15788 chomp @ps;
15789 # convert to octets
15790
15791 @val = map { $_ * $KIBI } @ps ;
15792 }
15793 $sync->{ debug } and myprint( "@val\n" ) ;
15794 return( @val ) ;
15795}
15796
15797sub memory_consumption_of_pids_win32
15798{
15799 # Windows
15800 my @PID = @_;
15801 my %PID;
15802 # hash of pids as key values
15803 map { $PID{$_}++ } @PID;
15804
15805 # Does not work but should work reading the tasklist documentation
15806 #@ps = qx{ tasklist /FI "PID eq @PID" };
15807
15808 my @ps = qx{ tasklist /NH /FO CSV } ;
15809 #my @ps = backtick( 'tasklist /NH /FO CSV' ) ;
15810 #myprint( "-" x $STD_CHAR_PER_LINE, "\n", @ps, "-" x $STD_CHAR_PER_LINE, "\n" ) ;
15811 my @val;
15812 foreach my $line (@ps) {
15813 my($name, $pid, $mem) = (split ',', $line )[0,1,4];
15814 next if (! $pid);
15815 #myprint( "[$name][$pid][$mem]" ) ;
15816 if ($PID{remove_qq($pid)}) {
15817 #myprint( "MATCH !\n" ) ;
15818 chomp $mem ;
15819 $mem = remove_qq($mem);
15820 $mem = remove_Ko($mem);
15821 $mem = remove_not_num($mem);
15822 #myprint( "[$mem]\n" ) ;
15823 push @val, $mem * $KIBI;
15824 }
15825 }
15826 return(@val);
15827}
15828
15829
15830sub tests_backtick
15831{
15832 note( 'Entering tests_backtick()' ) ;
15833
15834 is( undef, backtick( ), 'backtick: no args' ) ;
15835 is( undef, backtick( q{} ), 'backtick: empty command' ) ;
15836
15837 SKIP: {
15838 skip( 'test for MSWin32', 5 ) if ('MSWin32' ne $OSNAME) ;
15839 my @output ;
15840 @output = backtick( 'echo Hello World!' ) ;
15841 # Add \r on Windows.
15842 ok( "Hello World!\r\n" eq $output[0], 'backtick: echo Hello World!' ) ;
15843 $sync->{ debug } and myprint( "[@output]" ) ;
15844 @output = backtick( 'echo Hello & echo World!' ) ;
15845 ok( "Hello \r\n" eq $output[0], 'backtick: echo Hello & echo World! line 1' ) ;
15846 ok( "World!\r\n" eq $output[1], 'backtick: echo Hello & echo World! line 2' ) ;
15847 $sync->{ debug } and myprint( "[@output][$output[0]][$output[1]]" ) ;
15848 # Scalar context
15849 ok( "Hello World!\r\n" eq backtick( 'echo Hello World!' ),
15850 'backtick: echo Hello World! scalar' ) ;
15851 ok( "Hello \r\nWorld!\r\n" eq backtick( 'echo Hello & echo World!' ),
15852 'backtick: echo Hello & echo World! scalar 2 lines' ) ;
15853 } ;
15854 SKIP: {
15855 skip( 'test for Unix', 7 ) if ('MSWin32' eq $OSNAME) ;
15856 is( undef, backtick( 'aaaarrrg' ), 'backtick: aaaarrrg command not found' ) ;
15857 # Array context
15858 my @output ;
15859 @output = backtick( 'echo Hello World!' ) ;
15860 ok( "Hello World!\n" eq $output[0], 'backtick: echo Hello World!' ) ;
15861 $sync->{ debug } and myprint( "[@output]" ) ;
15862 @output = backtick( "echo Hello\necho World!" ) ;
15863 ok( "Hello\n" eq $output[0], 'backtick: echo Hello; echo World! line 1' ) ;
15864 ok( "World!\n" eq $output[1], 'backtick: echo Hello; echo World! line 2' ) ;
15865 $sync->{ debug } and myprint( "[@output]" ) ;
15866 # Scalar context
15867 ok( "Hello World!\n" eq backtick( 'echo Hello World!' ),
15868 'backtick: echo Hello World! scalar' ) ;
15869 ok( "Hello\nWorld!\n" eq backtick( "echo Hello\necho World!" ),
15870 'backtick: echo Hello; echo World! scalar 2 lines' ) ;
15871 # Return error positive value, that's ok
15872 is( undef, backtick( 'false' ), 'backtick: false returns no output' ) ;
15873 my $mem = backtick( "ps -o vsz -p $PROCESS_ID" ) ;
15874 $sync->{ debug } and myprint( "MEM=$mem\n" ) ;
15875
15876 }
15877
15878 note( 'Leaving tests_backtick()' ) ;
15879 return ;
15880}
15881
15882
15883sub backtick
15884{
15885 my $command = shift ;
15886
15887 if ( ! $command ) { return ; }
15888
15889 my ( $writer, $reader, $err ) ;
15890 my @output ;
15891 my $pid ;
15892 my $eval = eval {
15893 $pid = IPC::Open3::open3( $writer, $reader, $err, $command ) ;
15894 } ;
15895 if ( $EVAL_ERROR ) {
15896 myprint( $EVAL_ERROR ) ;
15897 return ;
15898 }
15899 if ( ! $eval ) { return ; }
15900 if ( ! $pid ) { return ; }
15901 waitpid( $pid, 0 ) ;
15902 @output = <$reader>; # Output here
15903 #
15904 #my @errors = <$err>; #Errors here, instead of the console
15905 if ( not @output ) { return ; }
15906 #myprint( @output ) ;
15907
15908 if ( $output[0] =~ /\Qopen3: exec of $command failed\E/mxs ) { return ; }
15909 if ( wantarray ) {
15910 return( @output ) ;
15911 } else {
15912 return( join( q{}, @output) ) ;
15913 }
15914}
15915
15916
15917
15918sub tests_check_binary_embed_all_dyn_libs
15919{
15920 note( 'Entering tests_check_binary_embed_all_dyn_libs()' ) ;
15921
15922 is( 1, check_binary_embed_all_dyn_libs( ), 'check_binary_embed_all_dyn_libs: no args => 1' ) ;
15923
15924 note( 'Leaving tests_check_binary_embed_all_dyn_libs()' ) ;
15925
15926 return ;
15927}
15928
15929
15930sub check_binary_embed_all_dyn_libs
15931{
15932 my @search_dyn_lib_locale = search_dyn_lib_locale( ) ;
15933
15934 if ( @search_dyn_lib_locale )
15935 {
15936 myprint( "Found myself $PROGRAM_NAME pid $PROCESS_ID using locale dynamic libraries that seems out of myself:\n" ) ;
15937 myprint( @search_dyn_lib_locale ) ;
15938 if ( $PROGRAM_NAME =~ m{imapsync_bin_Darwin} )
15939 {
15940 return 0 ;
15941 }
15942 elsif ( $PROGRAM_NAME =~ m{imapsync.*\.exe} )
15943 {
15944 return 0 ;
15945 }
15946 else
15947 {
15948 # is always ok for non binary
15949 return 1 ;
15950 }
15951 }
15952 else
15953 {
15954 # Found only embedded dynamic lib
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015955 myprint( "Found only embedded dynamic lib. Good!\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015956 return 1 ;
15957 }
15958}
15959
15960sub search_dyn_lib_locale
15961{
15962 if ( 'darwin' eq $OSNAME )
15963 {
15964 return search_dyn_lib_locale_darwin( ) ;
15965 }
15966 if ( 'linux' eq $OSNAME )
15967 {
15968 return search_dyn_lib_locale_linux( ) ;
15969 }
15970 if ( 'MSWin32' eq $OSNAME )
15971 {
15972 return search_dyn_lib_locale_MSWin32( ) ;
15973 }
15974
15975}
15976
15977sub search_dyn_lib_locale_darwin
15978{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015979 my $command = qq{ lsof -p $PROCESS_ID | grep ' REG ' | grep .dylib | grep -v '/par-' } ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015980 myprint( "Search non embeded dynamic libs with the command: $command\n" ) ;
15981 return backtick( $command ) ;
15982}
15983
15984sub search_dyn_lib_locale_linux
15985{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015986 my $command = qq{ lsof -p $PROCESS_ID | grep ' REG ' | grep -v '/tmp/par-' | grep '\.so' } ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015987 myprint( "Search non embeded dynamic libs with the command: $command\n" ) ;
15988 return backtick( $command ) ;
15989}
15990
15991sub search_dyn_lib_locale_MSWin32
15992{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015993 my $command = qq{ Listdlls.exe $PROCESS_ID|findstr Strawberry } ;
15994 # $command = qq{ Listdlls.exe $PROCESS_ID|findstr Strawberry } ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015995 myprint( "Search non embeded dynamic libs with the command: $command\n" ) ;
15996 return qx( $command ) ;
15997}
15998
15999
16000
16001sub remove_not_num
16002{
16003
16004 my $string = shift ;
16005 $string =~ tr/0-9//cd ;
16006 #myprint( "tr [$string]\n" ) ;
16007 return( $string ) ;
16008}
16009
16010sub tests_remove_not_num
16011{
16012 note( 'Entering tests_remove_not_num()' ) ;
16013
16014 ok( '123' eq remove_not_num( 123 ), 'remove_not_num( 123 )' ) ;
16015 ok( '123' eq remove_not_num( '123' ), q{remove_not_num( '123' )} ) ;
16016 ok( '123' eq remove_not_num( '12 3' ), q{remove_not_num( '12 3' )} ) ;
16017 ok( '123' eq remove_not_num( 'a 12 3 Ko' ), q{remove_not_num( 'a 12 3 Ko' )} ) ;
16018
16019 note( 'Leaving tests_remove_not_num()' ) ;
16020 return ;
16021}
16022
16023sub remove_Ko
16024{
16025 my $string = shift;
16026 if ($string =~ /^(.*)\sKo$/xo) {
16027 return($1);
16028 }else{
16029 return($string);
16030 }
16031}
16032
16033sub remove_qq
16034{
16035 my $string = shift;
16036 if ($string =~ /^"(.*)"$/xo) {
16037 return($1);
16038 }else{
16039 return($string);
16040 }
16041}
16042
16043sub memory_consumption_ratio
16044{
16045
16046 my ($base) = @_;
16047 $base ||= 1;
16048 my $consu = memory_consumption();
16049 return($consu / $base);
16050}
16051
16052
16053sub date_from_rcs
16054{
16055 my $d = shift ;
16056
16057 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 ) ;
16058 if ($d =~ m{(\d{4})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) {
16059 # Handles the following format
16060 # 2015/07/10 11:05:59 -- Generated by RCS Date tag.
16061 #myprint( "$d\n" ) ;
16062 #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
16063 my ($year, $month, $day, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6) ;
16064 $month = $num2mon{$month} ;
16065 $d = "$day-$month-$year $hour:$min:$sec +0000" ;
16066 #myprint( "$d\n" ) ;
16067 }
16068 return( $d ) ;
16069}
16070
16071sub tests_date_from_rcs
16072{
16073 note( 'Entering tests_date_from_rcs()' ) ;
16074
16075 ok('19-Sep-2015 16:11:07 +0000'
16076 eq date_from_rcs('Date: 2015/09/19 16:11:07 '), 'date_from_rcs from RCS date' ) ;
16077
16078 note( 'Leaving tests_date_from_rcs()' ) ;
16079 return ;
16080}
16081
16082sub good_date
16083{
16084 # two incoming formats:
16085 # header Tue, 24 Aug 2010 16:00:00 +0200
16086 # internal 24-Aug-2010 16:00:00 +0200
16087
16088 # outgoing format: internal date format
16089 # 24-Aug-2010 16:00:00 +0200
16090
16091 my $d = shift ;
16092 return(q{}) if not defined $d;
16093
16094 SWITCH: {
16095 if ( $d =~ m{(\d?)(\d-...-\d{4})(\s\d{2}:\d{2}:\d{2})(\s(?:\+|-)\d{4})?}xo ) {
16096 #myprint( "internal: [$1][$2][$3][$4]\n" ) ;
16097 my ($day_1, $date_rest, $hour, $zone) = ($1,$2,$3,$4) ;
16098 $day_1 = '0' if ($day_1 eq q{}) ;
16099 $zone = ' +0000' if not defined $zone ;
16100 $d = $day_1 . $date_rest . $hour . $zone ;
16101 last SWITCH ;
16102 }
16103
16104 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 ) {
16105 # Handles any combination of following formats
16106 # Tue, 24 Aug 2010 16:00:00 +0200 -- Standard
16107 # 24 Aug 2010 16:00:00 +0200 -- Missing Day of Week
16108 # Tue, 24 Aug 97 16:00:00 +0200 -- Two digit year
16109 # Tue, 24 Aug 1997 16.00.00 +0200 -- Periods instead of colons
16110 # Tue, 24 Aug 1997 16:00:00 +0200 -- Extra whitespace between year and hour
16111 # Tue, 24 Aug 1997 6:5:2 +0200 -- Single digit hour, min, or second
16112 # Tue, 24, Aug 1997 16:00:00 +0200 -- Extra comma
16113
16114 #myprint( "header: [$1][$2][$3][$4][$5][$6][$7][$8]\n" ) ;
16115 my ($day, $month, $year, $hour, $min, $sec, $zone) = ($1,$2,$3,$4,$5,$6,$7,$8);
16116 $year = '19' . $year if length($year) == 2 && $year =~ m/^[789]/xo;
16117 $year = '20' . $year if length($year) == 2;
16118
16119 $month = substr $month, 0, 3 if length($month) > 4;
16120 $day = mysprintf( '%02d', $day);
16121 $hour = mysprintf( '%02d', $hour);
16122 $min = mysprintf( '%02d', $min);
16123 $sec = '00' if not defined $sec ;
16124 $sec = mysprintf( '%02d', $sec ) ;
16125 $zone = '+0000' if not defined $zone ;
16126 $d = "$day-$month-$year $hour:$min:$sec $zone" ;
16127 last SWITCH ;
16128 }
16129
16130 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 ) {
16131 # Handles any combination of following formats
16132 # Sun Aug 20 11:55:09 2006
16133 # Wed Jan 24 11:58:38 MST 2007
16134 # Wed Jan 2 08:40:57 2008
16135
16136 #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
16137 my ($month, $day, $hour, $min, $sec, $year) = ($1,$2,$3,$4,$5,$6);
16138 $day = mysprintf( '%02d', $day ) ;
16139 $hour = mysprintf( '%02d', $hour ) ;
16140 $min = mysprintf( '%02d', $min ) ;
16141 $sec = mysprintf( '%02d', $sec ) ;
16142 $d = "$day-$month-$year $hour:$min:$sec +0000" ;
16143 last SWITCH ;
16144 }
16145 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 ) ;
16146
16147 if ($d =~ m{(\d{4})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) {
16148 # Handles the following format
16149 # 2015/07/10 11:05:59 -- Generated by RCS Date tag.
16150 #myprint( "$d\n" ) ;
16151 #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
16152 my ($year, $month, $day, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6) ;
16153 $month = $num2mon{$month} ;
16154 $d = "$day-$month-$year $hour:$min:$sec +0000" ;
16155 #myprint( "$d\n" ) ;
16156 last SWITCH ;
16157 }
16158
16159 if ($d =~ m{(\d{2})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) {
16160 # Handles the following format
16161 # 02/06/09 22:18:08 -- Generated by AVTECH TemPageR devices
16162
16163 #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
16164 my ($month, $day, $year, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6);
16165 $year = '20' . $year;
16166 $month = $num2mon{$month};
16167 $d = "$day-$month-$year $hour:$min:$sec +0000";
16168 last SWITCH ;
16169 }
16170
16171 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 ) {
16172 # Handles the following format
16173 # Saturday, December 14, 2002 05:00 PM - KBtoys.com order confirmations
16174
16175 my ($month, $day, $year, $hour, $min, $apm) = ($1,$2,$3,$4,$5,$6);
16176
16177 $hour += 12 if $apm eq 'PM' ;
16178 $day = mysprintf( '%02d', $day ) ;
16179 $d = "$day-$month-$year $hour:$min:00 +0000" ;
16180 last SWITCH ;
16181 }
16182
16183 if ($d =~ m{(\w{3})\s(\d{1,2})\s(\d{4})\s(\d{2}):(\d{2}):(\d{2})\s((?:\+|-)\d{4})}xo ) {
16184 # Handles the following format
16185 # Saturday, December 14, 2002 05:00 PM - jr.com order confirmations
16186
16187 my ($month, $day, $year, $hour, $min, $sec, $zone) = ($1,$2,$3,$4,$5,$6,$7);
16188
16189 $day = mysprintf( '%02d', $day ) ;
16190 $d = "$day-$month-$year $hour:$min:$sec $zone";
16191 last SWITCH ;
16192 }
16193
16194 if ($d =~ m{(\d{1,2})-(\w{3})-(\d{4})}xo ) {
16195 # Handles the following format
16196 # 21-Jun-2001 - register.com domain transfer email circa 2001
16197
16198 my ($day, $month, $year) = ($1,$2,$3);
16199 $day = mysprintf( '%02d', $day);
16200 $d = "$day-$month-$year 11:11:11 +0000";
16201 last SWITCH ;
16202 }
16203
16204 # unknown or unmatch => return same string
16205 return($d);
16206 }
16207
16208 $d = qq("$d") ;
16209 return( $d ) ;
16210}
16211
16212
16213sub tests_good_date
16214{
16215 note( 'Entering tests_good_date()' ) ;
16216
16217 ok(q{} eq good_date(), 'good_date no arg');
16218 ok('"24-Aug-2010 16:00:00 +0200"' eq good_date('24-Aug-2010 16:00:00 +0200'), 'good_date internal 2digit zone');
16219 ok('"24-Aug-2010 16:00:00 +0000"' eq good_date('24-Aug-2010 16:00:00'), 'good_date internal 2digit no zone');
16220 ok('"01-Sep-2010 16:00:00 +0200"' eq good_date( '1-Sep-2010 16:00:00 +0200'), 'good_date internal SP 1digit');
16221 ok('"24-Aug-2010 16:00:00 +0200"' eq good_date('Tue, 24 Aug 2010 16:00:00 +0200'), 'good_date header 2digit zone');
16222 ok('"01-Sep-2010 16:00:00 +0000"' eq good_date('Wed, 1 Sep 2010 16:00:00'), 'good_date header SP 1digit zone');
16223 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');
16224 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');
16225 ok('"06-Feb-2009 22:18:08 +0000"' eq good_date('02/06/09 22:18:08'), 'good_date header TemPageR');
16226 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');
16227 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');
16228 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');
16229 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');
16230 ok('"24-Aug-2067 16:00:00 +0200"' eq good_date('Tue, 24 Aug 67 16:00:00 +0200'), 'good_date header 2digit year');
16231 ok('"24-Aug-1977 16:00:00 +0200"' eq good_date('Tue, 24 Aug 77 16:00:00 +0200'), 'good_date header 2digit year');
16232 ok('"24-Aug-1987 16:00:00 +0200"' eq good_date('Tue, 24 Aug 87 16:00:00 +0200'), 'good_date header 2digit year');
16233 ok('"24-Aug-1997 16:00:00 +0200"' eq good_date('Tue, 24 Aug 97 16:00:00 +0200'), 'good_date header 2digit year');
16234 ok('"24-Aug-2004 16:00:00 +0200"' eq good_date('Tue, 24 Aug 04 16:00:00 +0200'), 'good_date header 2digit year');
16235 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');
16236 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');
16237 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');
16238 ok('"24-Aug-1997 05:06:02 +0200"' eq good_date('Tue, 24, Aug 1997 05:06:02 +0200'), 'good_date header extra commas');
16239 ok('"01-Oct-2003 12:45:24 +0000"' eq good_date('Wednesday, 01 October 2003 12:45:24 CDT'), 'good_date header no abbrev');
16240 ok('"11-Jan-2005 17:58:27 -0500"' eq good_date('Tue, 11 Jan 2005 17:58:27 -0500'), 'good_date extra white space');
16241 ok('"18-Dec-2002 15:07:00 +0000"' eq good_date('Wednesday, December 18, 2002 03:07 PM'), 'good_date kbtoys.com orders');
16242 ok('"16-Dec-2004 02:01:49 -0500"' eq good_date('Dec 16 2004 02:01:49 -0500'), 'good_date jr.com orders');
16243 ok('"21-Jun-2001 11:11:11 +0000"' eq good_date('21-Jun-2001'), 'good_date register.com domain transfer');
16244 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)');
16245 ok('"19-Sep-2015 16:11:07 +0000"' eq good_date('Date: 2015/09/19 16:11:07 '), 'good_date from RCS date' ) ;
16246
16247 note( 'Leaving tests_good_date()' ) ;
16248 return ;
16249}
16250
16251
16252sub tests_list_keys_in_2_not_in_1
16253{
16254 note( 'Entering tests_list_keys_in_2_not_in_1()' ) ;
16255
16256
16257 my @list;
16258 ok( ! list_keys_in_2_not_in_1( {}, {}), 'list_keys_in_2_not_in_1: {} {}');
16259 ok( 0 == compare_lists( [], [ list_keys_in_2_not_in_1( {}, {} ) ] ), 'list_keys_in_2_not_in_1: {} {}');
16260 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}');
16261 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}');
16262 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}');
16263 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}');
16264 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}');
16265
16266 note( 'Leaving tests_list_keys_in_2_not_in_1()' ) ;
16267 return ;
16268}
16269
16270sub list_keys_in_2_not_in_1
16271{
16272 my $hash_1_ref = shift;
16273 my $hash_2_ref = shift;
16274 my @list;
16275
16276 foreach my $key ( sort keys %{ $hash_2_ref } ) {
16277 #$sync->{ debug } and print "$key\n" ;
16278 if ( exists $hash_1_ref->{$key} )
16279 {
16280 next ;
16281 }
16282 #$sync->{ debug } and print "list_keys_in_2_not_in_1: $key\n" ;
16283 push @list, $key ;
16284 }
16285 #$sync->{ debug } and print "@list\n" ;
16286 return( @list ) ;
16287}
16288
16289
16290sub list_folders_in_2_not_in_1
16291{
16292
16293 my ( @h2_folders_not_in_h1, %h2_folders_not_in_h1 ) ;
16294 @h2_folders_not_in_h1 = list_keys_in_2_not_in_1( \%h1_folders_all, \%h2_folders_all ) ;
16295 map { $h2_folders_not_in_h1{$_} = 1} @h2_folders_not_in_h1 ;
16296 @h2_folders_not_in_h1 = list_keys_in_2_not_in_1( \%h2_folders_from_1_all, \%h2_folders_not_in_h1 ) ;
16297 #$sync->{ debug } and print "h2_folders_not_in_h1: @h2_folders_not_in_h1\n" ;
16298 return( reverse @h2_folders_not_in_h1 ) ;
16299}
16300
16301sub tests_nb_messages_in_2_not_in_1
16302{
16303 note( 'Entering tests_stats_across_folders()' ) ;
16304 is( undef, nb_messages_in_2_not_in_1( ), 'nb_messages_in_2_not_in_1: no args => undef' ) ;
16305
16306 my $mysync->{ h1_folders_of_md5 }->{ 'some_id_01' }->{ 'some_folder_01' } = 1 ;
16307 is( 0, nb_messages_in_2_not_in_1( $mysync ), 'nb_messages_in_2_not_in_1: no messages in 2 => 0' ) ;
16308
16309 $mysync->{ h1_folders_of_md5 }->{ 'some_id_in_1_and_2' }->{ 'some_folder_01' } = 2 ;
16310 $mysync->{ h2_folders_of_md5 }->{ 'some_id_in_1_and_2' }->{ 'some_folder_02' } = 4 ;
16311
16312 is( 0, nb_messages_in_2_not_in_1( $mysync ), 'nb_messages_in_2_not_in_1: a common message => 0' ) ;
16313
16314 $mysync->{ h2_folders_of_md5 }->{ 'some_id_in_2_not_in_1' }->{ 'some_folder_02' } = 1 ;
16315 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' ) ;
16316
16317 $mysync->{ h2_folders_of_md5 }->{ 'some_other_id_in_2_not_in_1' }->{ 'some_folder_02' } = 3 ;
16318 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' ) ;
16319
16320 note( 'Leaving tests_stats_across_folders()' ) ;
16321 return ;
16322}
16323
16324sub nb_messages_in_2_not_in_1
16325{
16326 my $mysync = shift ;
16327 if ( not defined $mysync ) { return ; }
16328
16329 $mysync->{ nb_messages_in_2_not_in_1 } = scalar(
16330 list_keys_in_2_not_in_1(
16331 $mysync->{ h1_folders_of_md5 },
16332 $mysync->{ h2_folders_of_md5 } ) ) ;
16333
16334 return $mysync->{ nb_messages_in_2_not_in_1 } ;
16335}
16336
16337
16338sub nb_messages_in_1_not_in_2
16339{
16340 my $mysync = shift ;
16341 if ( not defined $mysync ) { return ; }
16342
16343 $mysync->{ nb_messages_in_1_not_in_2 } = scalar(
16344 list_keys_in_2_not_in_1(
16345 $mysync->{ h2_folders_of_md5 },
16346 $mysync->{ h1_folders_of_md5 } ) ) ;
16347
16348 return $mysync->{ nb_messages_in_1_not_in_2 } ;
16349}
16350
16351
16352
16353sub comment_on_final_diff_in_1_not_in_2
16354{
16355 my $mysync = shift ;
16356
16357 if ( not defined $mysync
16358 or $mysync->{ justfolders }
16359 or $mysync->{ useuid }
16360 )
16361 {
16362 return ;
16363 }
16364
16365 my $nb_identified_h1_messages = scalar( keys %{ $mysync->{ h1_folders_of_md5 } } ) ;
16366 my $nb_identified_h2_messages = scalar( keys %{ $mysync->{ h2_folders_of_md5 } } ) ;
16367 $mysync->{ debug } and myprint( "nb_keys h1_folders_of_md5 $nb_identified_h1_messages\n" ) ;
16368 $mysync->{ debug } and myprint( "nb_keys h2_folders_of_md5 $nb_identified_h2_messages\n" ) ;
16369
16370 if ( 0 == $nb_identified_h1_messages ) { return ; }
16371
16372 # Calculate if not yet done
16373 if ( not defined $mysync->{ nb_messages_in_1_not_in_2 } )
16374 {
16375 nb_messages_in_1_not_in_2( $mysync ) ;
16376 }
16377
16378
16379 if ( 0 == $mysync->{ nb_messages_in_1_not_in_2 } )
16380 {
16381 myprint( "The sync looks good, all ",
16382 $nb_identified_h1_messages,
16383 " identified messages in host1 are on host2.\n" ) ;
16384 }
16385 else
16386 {
16387 myprint( "The sync is not finished, there are ",
16388 $mysync->{ nb_messages_in_1_not_in_2 },
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016389 " among ",
16390 $nb_identified_h1_messages,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016391 " identified messages in host1 that are not on host2.\n" ) ;
16392 }
16393
16394
16395 if ( 1 <= $mysync->{ h1_nb_msg_noheader } )
16396 {
16397 myprint( "There are ",
16398 $mysync->{ h1_nb_msg_noheader },
16399 " unidentified messages (usually Sent or Draft messages).",
16400 " To sync them add option --addheader\n" ) ;
16401 }
16402 else
16403 {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016404 myprint( "There is no unidentified message on host1.\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016405 }
16406
16407 return ;
16408}
16409
16410sub comment_on_final_diff_in_2_not_in_1
16411{
16412 my $mysync = shift ;
16413
16414 if ( not defined $mysync
16415 or $mysync->{ justfolders }
16416 or $mysync->{ useuid }
16417 )
16418 {
16419 return ;
16420 }
16421
16422 my $nb_identified_h2_messages = scalar( keys %{ $mysync->{ h2_folders_of_md5 } } ) ;
16423 # Calculate if not done yet
16424 if ( not defined $mysync->{ nb_messages_in_2_not_in_1 } )
16425 {
16426 nb_messages_in_2_not_in_1( $mysync ) ;
16427 }
16428
16429 if ( 0 == $mysync->{ nb_messages_in_2_not_in_1 } )
16430 {
16431 myprint( "The sync is strict, all ",
16432 $nb_identified_h2_messages,
16433 " identified messages in host2 are on host1.\n" ) ;
16434 }
16435 else
16436 {
16437 myprint( "The sync is not strict, there are ",
16438 $mysync->{ nb_messages_in_2_not_in_1 },
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016439 " among ",
16440 $nb_identified_h2_messages,
16441 " identified messages in host2 that are not on host1.",
16442 " Use --delete2 and sync again to delete them and have a strict sync.\n"
16443 ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016444 }
16445 return ;
16446}
16447
16448
16449sub tests_match
16450{
16451 note( 'Entering tests_match()' ) ;
16452
16453 # undef serie
16454 is( undef, match( ), 'match: no args => undef' ) ;
16455 is( undef, match( 'lalala' ), 'match: one args => undef' ) ;
16456
16457 # This one gives 0 under a binary made by pp
16458 # but 1 under "normal" Perl interpreter. So a PAR bug?
16459 #is( 1, match( q{}, q{} ), 'match: q{} =~ q{} => 1' ) ;
16460
16461 is( 'lalala', match( 'lalala', 'lalala' ), 'match: lalala =~ lalala => lalala' ) ;
16462 is( 'lalala', match( 'lalala', '^lalala' ), 'match: lalala =~ ^lalala => lalala' ) ;
16463 is( 'lalala', match( 'lalala', 'lalala$' ), 'match: lalala =~ lalala$ => lalala' ) ;
16464 is( 'lalala', match( 'lalala', '^lalala$' ), 'match: lalala =~ ^lalala$ => lalala' ) ;
16465 is( '_lalala_', match( '_lalala_', 'lalala' ), 'match: _lalala_ =~ lalala => _lalala_' ) ;
16466 is( 'lalala', match( 'lalala', '.*' ), 'match: lalala =~ .* => lalala' ) ;
16467 is( 'lalala', match( 'lalala', '.' ), 'match: lalala =~ . => lalala' ) ;
16468 is( '/lalala/', match( '/lalala/', '/lalala/' ), 'match: /lalala/ =~ /lalala/ => /lalala/' ) ;
16469
16470 is( 0, match( 'foo', 's/foo/bar/g' ), 'match: foo =~ s/foo/bar/g => 0' ) ;
16471 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' ) ;
16472
16473
16474 is( 0, match( 'lalala', 'ooo' ), 'match: lalala =~ ooo => 0' ) ;
16475 is( 0, match( 'lalala', 'lal_ala' ), 'match: lalala =~ lal_ala => 0' ) ;
16476 is( 0, match( 'lalala', '\.' ), 'match: lalala =~ \. => 0' ) ;
16477 is( 0, match( 'lalalaX', '^lalala$' ), 'match: lalalaX =~ ^lalala$ => 0' ) ;
16478 is( 0, match( 'lalala', '/lalala/' ), 'match: lalala =~ /lalala/ => 0' ) ;
16479
16480 is( 'LALALA', match( 'LALALA', '(?i:lalala)' ), 'match: LALALA =~ (?i:lalala) => 1' ) ;
16481
16482 is( undef, match( 'LALALA', '(?{`ls /`})' ), 'match: LALALA =~ (?{`ls /`}) => undef' ) ;
16483 is( undef, match( 'LALALA', '(?{print "CACA"})' ), 'match: LALALA =~ (?{print "CACA"}) => undef' ) ;
16484 is( undef, match( 'CACA', '(??{print "CACA"})' ), 'match: CACA =~ (??{print "CACA"}) => undef' ) ;
16485
16486 note( 'Leaving tests_match()' ) ;
16487
16488 return ;
16489}
16490
16491sub match
16492{
16493 my( $var, $regex ) = @ARG ;
16494
16495 # undef cases
16496 if ( ( ! defined $var ) or ( ! defined $regex ) ) { return ; }
16497
16498 # normal cases
16499 if ( eval { $var =~ qr{$regex} } ) {
16500 return $var ;
16501 }elsif ( $EVAL_ERROR ) {
16502 myprint( "Fatal regex $regex\n" ) ;
16503 return ;
16504 } else {
16505 return 0 ;
16506 }
16507 return ;
16508}
16509
16510
16511sub tests_notmatch
16512{
16513 note( 'Entering tests_notmatch()' ) ;
16514
16515 # undef serie
16516 is( undef, notmatch( ), 'notmatch: no args => undef' ) ;
16517 is( undef, notmatch( 'lalala' ), 'notmatch: one args => undef' ) ;
16518
16519 is( 1, notmatch( 'lalala', '/lalala/' ), 'notmatch: lalala !~ /lalala/ => 1' ) ;
16520 is( 0, notmatch( '/lalala/', '/lalala/' ), 'notmatch: /lalala/ !~ /lalala/ => 0' ) ;
16521 is( 1, notmatch( 'lalala', '/ooo/' ), 'notmatch: lalala !~ /ooo/ => 1' ) ;
16522
16523 # This one gives 1 under a binary made by pp
16524 # but 0 under "normal" Perl interpreter. So a PAR bug, same in tests_match .
16525 #is( 0, notmatch( q{}, q{} ), 'notmatch: q{} !~ q{} => 0' ) ;
16526
16527 is( 0, notmatch( 'lalala', 'lalala' ), 'notmatch: lalala !~ lalala => 0' ) ;
16528 is( 0, notmatch( 'lalala', '^lalala' ), 'notmatch: lalala !~ ^lalala => 0' ) ;
16529 is( 0, notmatch( 'lalala', 'lalala$' ), 'notmatch: lalala !~ lalala$ => 0' ) ;
16530 is( 0, notmatch( 'lalala', '^lalala$' ), 'notmatch: lalala !~ ^lalala$ => 0' ) ;
16531 is( 0, notmatch( '_lalala_', 'lalala' ), 'notmatch: _lalala_ !~ lalala => 0' ) ;
16532 is( 0, notmatch( 'lalala', '.*' ), 'notmatch: lalala !~ .* => 0' ) ;
16533 is( 0, notmatch( 'lalala', '.' ), 'notmatch: lalala !~ . => 0' ) ;
16534
16535
16536 is( 1, notmatch( 'lalala', 'ooo' ), 'notmatch: does not match regex => 1' ) ;
16537 is( 1, notmatch( 'lalala', 'lal_ala' ), 'notmatch: does not match regex => 1' ) ;
16538 is( 1, notmatch( 'lalala', '\.' ), 'notmatch: matches regex => 0' ) ;
16539 is( 1, notmatch( 'lalalaX', '^lalala$' ), 'notmatch: does not match regex => 1' ) ;
16540
16541 note( 'Leaving tests_notmatch()' ) ;
16542
16543 return ;
16544}
16545
16546sub notmatch
16547{
16548 my( $var, $regex ) = @ARG ;
16549
16550 # undef cases
16551 if ( ( ! defined $var ) or ( ! defined $regex ) ) { return ; }
16552
16553 # normal cases
16554 if ( eval { $var !~ $regex } ) {
16555 return 1 ;
16556 }elsif ( $EVAL_ERROR ) {
16557 myprint( "Fatal regex $regex\n" ) ;
16558 return ;
16559 }else{
16560 return 0 ;
16561 }
16562 return ;
16563}
16564
16565
16566sub delete_folders_in_2_not_in_1
16567{
16568
16569 foreach my $folder ( @h2_folders_not_in_1 ) {
16570 if ( defined $delete2foldersonly and eval "\$folder !~ $delete2foldersonly" ) {
16571 myprint( "Not deleting $folder because of --delete2foldersonly $delete2foldersonly\n" ) ;
16572 next ;
16573 }
16574 if ( defined $delete2foldersbutnot and eval "\$folder =~ $delete2foldersbutnot" ) {
16575 myprint( "Not deleting $folder because of --delete2foldersbutnot $delete2foldersbutnot\n" ) ;
16576 next ;
16577 }
16578 my $res = $sync->{dry} ; # always success in dry mode!
16579 $sync->{imap2}->unsubscribe( $folder ) if ( ! $sync->{dry} ) ;
16580 $res = $sync->{imap2}->delete( $folder ) if ( ! $sync->{dry} ) ;
16581 if ( $res ) {
16582 myprint( "Deleted $folder", "$sync->{dry_message}", "\n" ) ;
16583 }else{
16584 myprint( "Deleting $folder failed", "\n" ) ;
16585 }
16586 }
16587 return ;
16588}
16589
16590sub delete_folder
16591{
16592 my ( $mysync, $imap, $folder, $Side ) = @_ ;
16593 if ( ! $mysync ) { return ; }
16594 if ( ! $imap ) { return ; }
16595 if ( ! $folder ) { return ; }
16596 $Side ||= 'HostX' ;
16597
16598 my $res = $mysync->{dry} ; # always success in dry mode!
16599 if ( ! $mysync->{dry} ) {
16600 $imap->unsubscribe( $folder ) ;
16601 $res = $imap->delete( $folder ) ;
16602 }
16603 if ( $res ) {
16604 myprint( "$Side deleted $folder", $mysync->{dry_message}, "\n" ) ;
16605 return 1 ;
16606 }else{
16607 myprint( "$Side deleting $folder failed", "\n" ) ;
16608 return ;
16609 }
16610}
16611
16612sub delete1emptyfolders
16613{
16614 my $mysync = shift ;
16615 if ( ! $mysync ) { return ; } # abort if no parameter
16616 if ( ! $mysync->{delete1emptyfolders} ) { return ; } # abort if --delete1emptyfolders off
16617 my $imap = $mysync->{imap1} ;
16618 if ( ! $imap ) { return ; } # abort if no imap
16619 if ( $imap->IsUnconnected( ) ) { return ; } # abort if disconnected
16620
16621 my %folders_kept ;
16622 myprint( qq{Host1 deleting empty folders\n} ) ;
16623 foreach my $folder ( reverse sort @{ $mysync->{h1_folders_wanted} } ) {
16624 my $parenthood = $imap->is_parent( $folder ) ;
16625 if ( defined $parenthood and $parenthood ) {
16626 myprint( "Host1: folder $folder has subfolders\n" ) ;
16627 $folders_kept{ $folder }++ ;
16628 next ;
16629 }
16630 my $nb_messages_select = examine_folder_and_count( $mysync, $imap, $folder, 'Host1' ) ;
16631 if ( ! defined $nb_messages_select ) { next ; } # Select failed => Neither continue nor keep this folder }
16632 my $nb_messages_search = scalar( @{ $imap->messages( ) } ) ;
16633 if ( 0 != $nb_messages_select and 0 != $nb_messages_search ) {
16634 myprint( "Host1: folder $folder has messages: $nb_messages_search (search) $nb_messages_select (select)\n" ) ;
16635 $folders_kept{ $folder }++ ;
16636 next ;
16637 }
16638 if ( 0 != $nb_messages_select + $nb_messages_search ) {
16639 myprint( "Host1: folder $folder odd messages count: $nb_messages_search (search) $nb_messages_select (select)\n" ) ;
16640 $folders_kept{ $folder }++ ;
16641 next ;
16642 }
16643 # Here we must have 0 messages by messages() aka "SEARCH ALL" and also "EXAMINE"
16644 if ( uc $folder eq 'INBOX' ) {
16645 myprint( "Host1: Not deleting $folder\n" ) ;
16646 $folders_kept{ $folder }++ ;
16647 next ;
16648 }
16649 myprint( "Host1: deleting empty folder $folder\n" ) ;
16650 # can not delete a SELECTed or EXAMINEd folder so closing it
16651 # could changed be SELECT INBOX
16652 $imap->close( ) ; # close after examine does not expunge; anyway expunging an empty folder...
16653 if ( delete_folder( $mysync, $imap, $folder, 'Host1' ) ) {
16654 next ; # Deleted, good!
16655 }else{
16656 $folders_kept{ $folder }++ ;
16657 next ; # Not deleted, bad!
16658 }
16659 }
16660 remove_deleted_folders_from_wanted_list( $mysync, %folders_kept ) ;
16661 myprint( qq{Host1 ended deleting empty folders\n} ) ;
16662 return ;
16663}
16664
16665sub remove_deleted_folders_from_wanted_list
16666{
16667 my ( $mysync, %folders_kept ) = @ARG ;
16668
16669 my @h1_folders_wanted_init = @{ $mysync->{h1_folders_wanted} } ;
16670 my @h1_folders_wanted_last ;
16671 foreach my $folder ( @h1_folders_wanted_init ) {
16672 if ( $folders_kept{ $folder } ) {
16673 push @h1_folders_wanted_last, $folder ;
16674 }
16675 }
16676 @{ $mysync->{h1_folders_wanted} } = @h1_folders_wanted_last ;
16677 return ;
16678}
16679
16680
16681sub examine_folder_and_count
16682{
16683 my ( $mysync, $imap, $folder, $Side ) = @_ ;
16684 $Side ||= 'HostX' ;
16685
16686 if ( ! examine_folder( $mysync, $imap, $folder, $Side ) ) {
16687 return ;
16688 }
16689 my $nb_messages_select = count_from_select( $imap->History ) ;
16690 return $nb_messages_select ;
16691}
16692
16693
16694sub tests_delete1emptyfolders
16695{
16696 note( 'Entering tests_delete1emptyfolders()' ) ;
16697
16698
16699 is( undef, delete1emptyfolders( ), q{delete1emptyfolders: undef} ) ;
16700 my $syncT ;
16701 is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: undef 2} ) ;
16702 my $imapT ;
16703 $syncT->{imap1} = $imapT ;
16704 is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: undef imap} ) ;
16705
16706 require_ok( "Test::MockObject" ) ;
16707 $imapT = Test::MockObject->new( ) ;
16708 $syncT->{imap1} = $imapT ;
16709
16710 $imapT->set_true( 'IsUnconnected' ) ;
16711 is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: Unconnected imap} ) ;
16712
16713 # Now connected tests
16714 $imapT->set_false( 'IsUnconnected' ) ;
16715 $imapT->mock( 'LastError', sub { q{LastError mocked} } ) ;
16716
16717 $syncT->{delete1emptyfolders} = 0 ;
16718 tests_delete1emptyfolders_unit(
16719 $syncT,
16720 [ qw{ INBOX DELME1 DELME2 } ],
16721 [ qw{ INBOX DELME1 DELME2 } ],
16722 q{tests_delete1emptyfolders: --delete1emptyfolders OFF}
16723 ) ;
16724
16725 # All are parents => no deletion at all
16726 $imapT->set_true( 'is_parent' ) ;
16727 $syncT->{delete1emptyfolders} = 1 ;
16728 tests_delete1emptyfolders_unit(
16729 $syncT,
16730 [ qw{ INBOX DELME1 DELME2 } ],
16731 [ qw{ INBOX DELME1 DELME2 } ],
16732 q{tests_delete1emptyfolders: --delete1emptyfolders ON}
16733 ) ;
16734
16735 # No parents but examine false for all => skip all
16736 $imapT->set_false( 'is_parent', 'examine' ) ;
16737
16738 tests_delete1emptyfolders_unit(
16739 $syncT,
16740 [ qw{ INBOX DELME1 DELME2 } ],
16741 [ ],
16742 q{tests_delete1emptyfolders: EXAMINE fails}
16743 ) ;
16744
16745 # examine ok for all but History bad => skip all
16746 $imapT->set_true( 'examine' ) ;
16747 $imapT->mock( 'History', sub { ( q{History badly mocked} ) } ) ;
16748 tests_delete1emptyfolders_unit(
16749 $syncT,
16750 [ qw{ INBOX DELME1 DELME2 } ],
16751 [ ],
16752 q{tests_delete1emptyfolders: examine ok but History badly mocked so count messages fails}
16753 ) ;
16754
16755 # History good but some messages EXISTS == messages() => no deletion
16756 $imapT->mock( 'History', sub { ( q{* 2 EXISTS} ) } ) ;
16757 $imapT->mock( 'messages', sub { [ qw{ UID_1 UID_2 } ] } ) ;
16758 tests_delete1emptyfolders_unit(
16759 $syncT,
16760 [ qw{ INBOX DELME1 DELME2 } ],
16761 [ qw{ INBOX DELME1 DELME2 } ],
16762 q{tests_delete1emptyfolders: History EXAMINE ok, several messages}
16763 ) ;
16764
16765 # 0 EXISTS but != messages() => no deletion
16766 $imapT->mock( 'History', sub { ( q{* 0 EXISTS} ) } ) ;
16767 $imapT->mock( 'messages', sub { [ qw{ UID_1 UID_2 } ] } ) ;
16768 tests_delete1emptyfolders_unit(
16769 $syncT,
16770 [ qw{ INBOX DELME1 DELME2 } ],
16771 [ qw{ INBOX DELME1 DELME2 } ],
16772 q{tests_delete1emptyfolders: 0 EXISTS but 2 by messages()}
16773 ) ;
16774
16775 # 1 EXISTS but != 0 == messages() => no deletion
16776 $imapT->mock( 'History', sub { ( q{* 1 EXISTS} ) } ) ;
16777 $imapT->mock( 'messages', sub { [ ] } ) ;
16778 tests_delete1emptyfolders_unit(
16779 $syncT,
16780 [ qw{ INBOX DELME1 DELME2 } ],
16781 [ qw{ INBOX DELME1 DELME2 } ],
16782 q{tests_delete1emptyfolders: 1 EXISTS but 0 by messages()}
16783 ) ;
16784
16785 # 0 EXISTS and 0 == messages() => deletion except INBOX
16786 $imapT->mock( 'History', sub { ( q{* 0 EXISTS} ) } ) ;
16787 $imapT->mock( 'messages', sub { [ ] } ) ;
16788 $imapT->set_true( qw{ delete close unsubscribe } ) ;
16789 $syncT->{dry_message} = q{ (not really since in a mocked test)} ;
16790 tests_delete1emptyfolders_unit(
16791 $syncT,
16792 [ qw{ INBOX DELME1 DELME2 } ],
16793 [ qw{ INBOX } ],
16794 q{tests_delete1emptyfolders: 0 EXISTS 0 by messages() delete folders, keep INBOX}
16795 ) ;
16796
16797 note( 'Leaving tests_delete1emptyfolders()' ) ;
16798 return ;
16799}
16800
16801sub tests_delete1emptyfolders_unit
16802{
16803 note( 'Entering tests_delete1emptyfolders_unit()' ) ;
16804
16805 my $syncT = shift ;
16806 my $folders1wanted_init_ref = shift ;
16807 my $folders1wanted_after_ref = shift ;
16808 my $comment = shift || q{delete1emptyfolders:} ;
16809
16810 my @folders1wanted_init = @{ $folders1wanted_init_ref } ;
16811 my @folders1wanted_after = @{ $folders1wanted_after_ref } ;
16812
16813 @{ $syncT->{h1_folders_wanted} } = @folders1wanted_init ;
16814
16815 is_deeply( $syncT->{h1_folders_wanted}, \@folders1wanted_init, qq{$comment, init check} ) ;
16816 delete1emptyfolders( $syncT ) ;
16817 is_deeply( $syncT->{h1_folders_wanted}, \@folders1wanted_after, qq{$comment, after check} ) ;
16818
16819 note( 'Leaving tests_delete1emptyfolders_unit()' ) ;
16820 return ;
16821}
16822
16823sub extract_header
16824{
16825 my $string = shift ;
16826
16827 my ( $header ) = split /\n\n/x, $string ;
16828 if ( ! $header ) { return( q{} ) ; }
16829 #myprint( "[$header]\n" ) ;
16830 return( $header ) ;
16831}
16832
16833sub tests_extract_header
16834{
16835 note( 'Entering tests_extract_header()' ) ;
16836
16837my $h = <<'EOM';
16838Message-Id: <20100428101817.A66CB162474E@plume.est.belle>
16839Date: Wed, 28 Apr 2010 12:18:17 +0200 (CEST)
16840From: gilles@louloutte.dyndns.org (Gilles LAMIRAL)
16841EOM
16842chomp $h ;
16843ok( $h eq extract_header(
16844<<'EOM'
16845Message-Id: <20100428101817.A66CB162474E@plume.est.belle>
16846Date: Wed, 28 Apr 2010 12:18:17 +0200 (CEST)
16847From: gilles@louloutte.dyndns.org (Gilles LAMIRAL)
16848
16849body
16850lalala
16851EOM
16852), 'extract_header: 1') ;
16853
16854
16855
16856 note( 'Leaving tests_extract_header()' ) ;
16857 return ;
16858}
16859
16860sub decompose_header{
16861 my $string = shift ;
16862
16863 # a hash, for a keyword header KEY value are list of strings [VAL1, VAL1_other, etc]
16864 # Think of multiple "Received:" header lines.
16865 my $header = { } ;
16866
16867 my ($key, $val ) ;
16868 my @line = split /\n|\r\n/x, $string ;
16869 foreach my $line ( @line ) {
16870 #myprint( "DDD $line\n" ) ;
16871 # End of header
16872 last if ( $line =~ m{^$}xo ) ;
16873 # Key: value
16874 if ( $line =~ m/(^[^:]+):\s(.*)/xo ) {
16875 $key = $1 ;
16876 $val = $2 ;
16877 $debugdev and myprint( "DDD KV [$key] [$val]\n" ) ;
16878 push @{ $header->{ $key } }, $val ;
16879 # blanc and value => value from previous line continues
16880 }elsif( $line =~ m/^(\s+)(.*)/xo ) {
16881 $val = $2 ;
16882 $debugdev and myprint( "DDD V [$val]\n" ) ;
16883 @{ $header->{ $key } }[ $LAST ] .= " $val" if $key ;
16884 # dirty line?
16885 }else{
16886 next ;
16887 }
16888 }
16889
16890 #myprint( Data::Dumper->Dump( [ $header ] ) ) ;
16891
16892 return( $header ) ;
16893}
16894
16895
16896sub tests_decompose_header{
16897 note( 'Entering tests_decompose_header()' ) ;
16898
16899
16900 my $header_dec ;
16901
16902 $header_dec = decompose_header(
16903<<'EOH'
16904KEY_1: VAL_1
16905KEY_2: VAL_2
16906 VAL_2_+
16907 VAL_2_++
16908KEY_3: VAL_3
16909KEY_1: VAL_1_other
16910KEY_4: VAL_4
16911 VAL_4_+
16912KEY_5 BLANC: VAL_5
16913
16914KEY_6_BAD_BODY: VAL_6
16915EOH
16916 ) ;
16917
16918 ok( 'VAL_3'
16919 eq $header_dec->{ 'KEY_3' }[0], 'decompose_header: VAL_3' ) ;
16920
16921 ok( 'VAL_1'
16922 eq $header_dec->{ 'KEY_1' }[0], 'decompose_header: VAL_1' ) ;
16923
16924 ok( 'VAL_1_other'
16925 eq $header_dec->{ 'KEY_1' }[1], 'decompose_header: VAL_1_other' ) ;
16926
16927 ok( 'VAL_2 VAL_2_+ VAL_2_++'
16928 eq $header_dec->{ 'KEY_2' }[0], 'decompose_header: VAL_2 VAL_2_+ VAL_2_++' ) ;
16929
16930 ok( 'VAL_4 VAL_4_+'
16931 eq $header_dec->{ 'KEY_4' }[0], 'decompose_header: VAL_4 VAL_4_+' ) ;
16932
16933 ok( ' VAL_5'
16934 eq $header_dec->{ 'KEY_5 BLANC' }[0], 'decompose_header: KEY_5 BLANC' ) ;
16935
16936 ok( not( defined $header_dec->{ 'KEY_6_BAD_BODY' }[0] ), 'decompose_header: KEY_6_BAD_BODY' ) ;
16937
16938
16939 $header_dec = decompose_header(
16940<<'EOH'
16941Message-Id: <20100428101817.A66CB162474E@plume.est.belle>
16942Date: Wed, 28 Apr 2010 12:18:17 +0200 (CEST)
16943From: gilles@louloutte.dyndns.org (Gilles LAMIRAL)
16944EOH
16945 ) ;
16946
16947 ok( '<20100428101817.A66CB162474E@plume.est.belle>'
16948 eq $header_dec->{ 'Message-Id' }[0], 'decompose_header: 1' ) ;
16949
16950 $header_dec = decompose_header(
16951<<'EOH'
16952Return-Path: <gilles@louloutte.dyndns.org>
16953Received: by plume.est.belle (Postfix, from userid 1000)
16954 id 120A71624742; Wed, 28 Apr 2010 01:46:40 +0200 (CEST)
16955Subject: test:eekahceishukohpe
16956EOH
16957) ;
16958 ok(
16959'by plume.est.belle (Postfix, from userid 1000) id 120A71624742; Wed, 28 Apr 2010 01:46:40 +0200 (CEST)'
16960 eq $header_dec->{ 'Received' }[0], 'decompose_header: 2' ) ;
16961
16962 $header_dec = decompose_header(
16963<<'EOH'
16964Received: from plume (localhost [127.0.0.1])
16965 by plume.est.belle (Postfix) with ESMTP id C6EB73F6C9
16966 for <gilles@localhost>; Mon, 26 Nov 2007 10:39:06 +0100 (CET)
16967Received: from plume [192.168.68.7]
16968 by plume with POP3 (fetchmail-6.3.6)
16969 for <gilles@localhost> (single-drop); Mon, 26 Nov 2007 10:39:06 +0100 (CET)
16970EOH
16971 ) ;
16972 ok(
16973 '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)'
16974 eq $header_dec->{ 'Received' }[0], 'decompose_header: 3' ) ;
16975 ok(
16976 '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)'
16977 eq $header_dec->{ 'Received' }[1], 'decompose_header: 3' ) ;
16978
16979# Bad header beginning with a blank character
16980 $header_dec = decompose_header(
16981<<'EOH'
16982 KEY_1: VAL_1
16983KEY_2: VAL_2
16984 VAL_2_+
16985 VAL_2_++
16986KEY_3: VAL_3
16987KEY_1: VAL_1_other
16988EOH
16989 ) ;
16990
16991 ok( 'VAL_3'
16992 eq $header_dec->{ 'KEY_3' }[0], 'decompose_header: Bad header VAL_3' ) ;
16993
16994 ok( 'VAL_1_other'
16995 eq $header_dec->{ 'KEY_1' }[0], 'decompose_header: Bad header VAL_1_other' ) ;
16996
16997 ok( 'VAL_2 VAL_2_+ VAL_2_++'
16998 eq $header_dec->{ 'KEY_2' }[0], 'decompose_header: Bad header VAL_2 VAL_2_+ VAL_2_++' ) ;
16999
17000 note( 'Leaving tests_decompose_header()' ) ;
17001 return ;
17002}
17003
17004sub tests_epoch
17005{
17006 note( 'Entering tests_epoch()' ) ;
17007
17008 ok( '1282658400' eq epoch( '24-Aug-2010 16:00:00 +0200' ), 'epoch 24-Aug-2010 16:00:00 +0200 -> 1282658400' ) ;
17009 ok( '1282658400' eq epoch( '24-Aug-2010 14:00:00 +0000' ), 'epoch 24-Aug-2010 14:00:00 +0000 -> 1282658400' ) ;
17010 ok( '1282658400' eq epoch( '24-Aug-2010 12:00:00 -0200' ), 'epoch 24-Aug-2010 12:00:00 -0200 -> 1282658400' ) ;
17011 ok( '1282658400' eq epoch( '24-Aug-2010 16:01:00 +0201' ), 'epoch 24-Aug-2010 16:01:00 +0201 -> 1282658400' ) ;
17012 ok( '1282658400' eq epoch( '24-Aug-2010 14:01:00 +0001' ), 'epoch 24-Aug-2010 14:01:00 +0001 -> 1282658400' ) ;
17013
17014 ok( '1280671200' eq epoch( '1-Aug-2010 16:00:00 +0200' ), 'epoch 1-Aug-2010 16:00:00 +0200 -> 1280671200' ) ;
17015 ok( '1280671200' eq epoch( '1-Aug-2010 14:00:00 +0000' ), 'epoch 1-Aug-2010 14:00:00 +0000 -> 1280671200' ) ;
17016 ok( '1280671200' eq epoch( '1-Aug-2010 12:00:00 -0200' ), 'epoch 1-Aug-2010 12:00:00 -0200 -> 1280671200' ) ;
17017 ok( '1280671200' eq epoch( '1-Aug-2010 16:01:00 +0201' ), 'epoch 1-Aug-2010 16:01:00 +0201 -> 1280671200' ) ;
17018 ok( '1280671200' eq epoch( '1-Aug-2010 14:01:00 +0001' ), 'epoch 1-Aug-2010 14:01:00 +0001 -> 1280671200' ) ;
17019
17020 is( '1280671200', epoch( '1-Aug-2010 14:01:00 +0001' ), 'epoch 1-Aug-2010 14:01:00 +0001 -> 1280671200' ) ;
17021 is( '946684800', epoch( '00-Jan-0000 00:00:00 +0000' ), 'epoch 1-Aug-2010 14:01:00 +0001 -> 1280671200' ) ;
17022
17023 note( 'Leaving tests_epoch()' ) ;
17024 return ;
17025}
17026
17027sub epoch
17028{
17029 # incoming format:
17030 # internal date 24-Aug-2010 16:00:00 +0200
17031
17032 # outgoing format: epoch
17033
17034
17035 my $d = shift ;
17036 return(q{}) if not defined $d;
17037
17038 my ( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m ) ;
17039 my $time ;
17040
17041 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 ) {
17042 #myprint( "internal: [$1][$2][$3][$4][$5][$6][$7][$8][$9]\n" ) ;
17043 ( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m )
17044 = ( $1, $2, $3, $4, $5, $6, $7, $8, $9 ) ;
17045 #myprint( "( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m )\n" ) ;
17046
17047 $sign = +1 if ( '+' eq $sign ) ;
17048 $sign = $MINUS_ONE if ( '-' eq $sign ) ;
17049
17050 if ( 0 == $mday ) {
17051 myprint( "buggy day in $d. Fixed to 01\n" ) ;
17052 $mday = '01' ;
17053 }
17054 $time = timegm( $sec, $min, $hour, $mday, $month_abrev{$month}, $year )
17055 - $sign * ( 3600 * $zone_h + 60 * $zone_m ) ;
17056
17057 #myprint( "$time ", scalar localtime($time), "\n");
17058 }
17059 return( $time ) ;
17060}
17061
17062sub tests_add_header
17063{
17064 note( 'Entering tests_add_header()' ) ;
17065
17066 ok( 'Message-Id: <mistake@imapsync>' eq add_header(), 'add_header no arg' ) ;
17067 ok( 'Message-Id: <123456789@imapsync>' eq add_header( '123456789' ), 'add_header 123456789' ) ;
17068
17069 note( 'Leaving tests_add_header()' ) ;
17070 return ;
17071}
17072
17073sub add_header
17074{
17075 my $header_uid = shift || 'mistake' ;
17076 my $header_Message_Id = 'Message-Id: <' . $header_uid . '@imapsync>' ;
17077 return( $header_Message_Id ) ;
17078}
17079
17080
17081
17082
17083sub tests_max_line_length
17084{
17085 note( 'Entering tests_max_line_length()' ) ;
17086
17087 ok( 0 == max_line_length( q{} ), 'max_line_length: 0 == null string' ) ;
17088 ok( 1 == max_line_length( "\n" ), 'max_line_length: 1 == \n' ) ;
17089 ok( 1 == max_line_length( "\n\n" ), 'max_line_length: 1 == \n\n' ) ;
17090 ok( 1 == max_line_length( "\n" x 500 ), 'max_line_length: 1 == 500 \n' ) ;
17091 ok( 1 == max_line_length( 'a' ), 'max_line_length: 1 == a' ) ;
17092 ok( 2 == max_line_length( "a\na" ), 'max_line_length: 2 == a\na' ) ;
17093 ok( 2 == max_line_length( "a\na\n" ), 'max_line_length: 2 == a\na\n' ) ;
17094 ok( 3 == max_line_length( "a\nab\n" ), 'max_line_length: 3 == a\nab\n' ) ;
17095 ok( 3 == max_line_length( "a\nab\n" x 1_000 ), 'max_line_length: 3 == 1_000 a\nab\n' ) ;
17096 ok( 3 == max_line_length( "a\nab\nabc" ), 'max_line_length: 3 == a\nab\nabc' ) ;
17097
17098 ok( 4 == max_line_length( "a\nab\nabc\n" ), 'max_line_length: 4 == a\nab\nabc\n' ) ;
17099 ok( 5 == max_line_length( "a\nabcd\nabc\n" ), 'max_line_length: 5 == a\nabcd\nabc\n' ) ;
17100 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' ) ;
17101
17102 note( 'Leaving tests_max_line_length()' ) ;
17103 return ;
17104}
17105
17106sub max_line_length
17107{
17108 my $string = shift ;
17109 my $max = 0 ;
17110
17111 while ( $string =~ m/([^\n]*\n?)/msxg ) {
17112 $max = max( $max, length $1 ) ;
17113 }
17114 return( $max ) ;
17115}
17116
17117
17118sub tests_setlogfile
17119{
17120 note( 'Entering tests_setlogfile()' ) ;
17121
17122 my $mysync = {} ;
17123 $mysync->{logdir} = 'vallogdir' ;
17124 $mysync->{logfile} = 'vallogfile.txt' ;
17125 is( 'vallogdir/vallogfile.txt', setlogfile( $mysync ),
17126 'setlogfile: logdir vallogdir, logfile vallogfile.txt, vallogdir/vallogfile.txt' ) ;
17127
17128 SKIP: {
17129 skip( 'Too hard to have a well known timezone on Windows', 9 ) if ( 'MSWin32' eq $OSNAME ) ;
17130
17131 local $ENV{TZ} = 'GMT' ;
17132
17133 $mysync = {
17134 timestart => 2,
17135 } ;
17136
17137 is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000__.txt", setlogfile( $mysync ),
17138 "setlogfile: default is like $DEFAULT_LOGDIR/1970_01_01_00_00_02_000__.txt" ) ;
17139
17140 $mysync = {
17141 timestart => 2,
17142 user1 => 'user1',
17143 user2 => 'user2',
17144 abort => 1,
17145 } ;
17146
17147 is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_abort.txt", setlogfile( $mysync ),
17148 "setlogfile: default is like $DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_abort.txt" ) ;
17149
17150 $mysync = {
17151 timestart => 2,
17152 user1 => 'user1',
17153 user2 => 'user2',
17154 remote => 'zzz',
17155 } ;
17156
17157 is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_remote.txt", setlogfile( $mysync ),
17158 "setlogfile: default is like $DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_remote.txt" ) ;
17159
17160 $mysync = {
17161 timestart => 2,
17162 user1 => 'user1',
17163 user2 => 'user2',
17164 remote => 'zzz',
17165 abort => 1,
17166 } ;
17167
17168 is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_remote_abort.txt", setlogfile( $mysync ),
17169 "setlogfile: default is like $DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_remote_abort.txt" ) ;
17170
17171
17172 $mysync = {
17173 timestart => 2,
17174 user1 => 'user1',
17175 user2 => 'user2',
17176 } ;
17177
17178 is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2.txt", setlogfile( $mysync ),
17179 "setlogfile: default is like $DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2.txt" ) ;
17180
17181 $mysync->{logdir} = undef ;
17182 $mysync->{logfile} = undef ;
17183 is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2.txt", setlogfile( $mysync ),
17184 "setlogfile: logdir undef, $DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2.txt" ) ;
17185
17186 $mysync->{logdir} = q{} ;
17187 $mysync->{logfile} = undef ;
17188 is( '1970_01_01_00_00_02_000_user1_user2.txt', setlogfile( $mysync ),
17189 'setlogfile: logdir empty, 1970_01_01_00_00_02_000_user1_user2.txt' ) ;
17190
17191 $mysync->{logdir} = 'vallogdir' ;
17192 $mysync->{logfile} = undef ;
17193 is( 'vallogdir/1970_01_01_00_00_02_000_user1_user2.txt', setlogfile( $mysync ),
17194 'setlogfile: logdir vallogdir, vallogdir/1970_01_01_00_00_02_000_user1_user2.txt' ) ;
17195
17196 $mysync = {
17197 user1 => 'us/er1a*|?:"<>b',
17198 user2 => 'u/ser2a*|?:"<>b',
17199 } ;
17200
17201 is( "$DEFAULT_LOGDIR/1970_01_01_00_00_00_000_us_er1a_______b_u_ser2a_______b.txt", setlogfile( $mysync ),
17202 "setlogfile: logdir undef, $DEFAULT_LOGDIR/1970_01_01_00_00_00_000_us_er1a_______b_u_ser2a_______b.txt" ) ;
17203
17204
17205
17206 } ;
17207
17208 note( 'Leaving tests_setlogfile()' ) ;
17209 return ;
17210}
17211
17212sub setlogfile
17213{
17214 my( $mysync ) = shift ;
17215
17216 # When aborting another process the log file name finishes with "_abort.txt"
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017217 my $abort_suffix = ( $mysync->{ abort } ) ? '_abort' : q{} ;
17218
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017219 # When acting as a proxy the log file name finishes with "_remote.txt"
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017220 # proxy mode is not done in imapsync, it is done by proximapsync
17221 my $remote_suffix = ( $mysync->{ remote } ) ? '_remote' : q{} ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017222
17223 my $suffix = (
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017224 filter_forbidden_characters( slash_to_underscore( $mysync->{ user1 } ) ) || q{} )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017225 . '_'
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017226 . ( filter_forbidden_characters( slash_to_underscore( $mysync->{ user2 } ) ) || q{} )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017227 . $remote_suffix . $abort_suffix ;
17228
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017229 $mysync->{ logdir } = defined $mysync->{ logdir } ? $mysync->{ logdir } : $DEFAULT_LOGDIR ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017230
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017231 $mysync->{ logfile } = defined $mysync->{ logfile }
17232 ? "$mysync->{ logdir }/$mysync->{ logfile }"
17233 : logfile( $mysync->{ timestart }, $suffix, $mysync->{ logdir } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017234
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017235 return( $mysync->{ logfile } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017236}
17237
17238sub tests_logfile
17239{
17240 note( 'Entering tests_logfile()' ) ;
17241
17242 SKIP: {
17243 # Too hard to have a well known timezone on Windows
17244 skip( 'Too hard to have a well known timezone on Windows', 10 ) if ( 'MSWin32' eq $OSNAME ) ;
17245
17246 local $ENV{TZ} = 'GMT' ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017247 {
17248 POSIX::tzset unless ('MSWin32' eq $OSNAME) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017249 is( '1970_01_01_00_00_00_000.txt', logfile( ), 'logfile: no args => 1970_01_01_00_00_00.txt' ) ;
17250 is( '1970_01_01_00_00_00_000.txt', logfile( 0 ), 'logfile: 0 => 1970_01_01_00_00_00.txt' ) ;
17251 is( '1970_01_01_00_01_01_000.txt', logfile( 61 ), 'logfile: 0 => 1970_01_01_00_01_01.txt' ) ;
17252 is( '1970_01_01_00_01_01_234.txt', logfile( 61.234 ), 'logfile: 0 => 1970_01_01_00_01_01.txt' ) ;
17253 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' ) ;
17254 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' ) ;
17255 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' ) ;
17256 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' ) ;
17257
17258 is( '2010_08_24_14_01_01_234_poup.txt', logfile( 1_282_658_461.2347, 'poup' ),
17259 'logfile: 1_282_658_461.2347 poup => 2010_08_24_14_01_01_234_poup.txt' ) ;
17260
17261 is( 'dirdir/2010_08_24_14_01_01_234_poup.txt', logfile( 1_282_658_461.2347, 'poup', 'dirdir' ),
17262 'logfile: 1_282_658_461.2347 poup dirdir => dirdir/2010_08_24_14_01_01_234_poup.txt' ) ;
17263
17264
17265
17266 }
17267 POSIX::tzset unless ('MSWin32' eq $OSNAME) ;
17268 } ;
17269
17270 note( 'Leaving tests_logfile()' ) ;
17271 return ;
17272}
17273
17274
17275sub logfile
17276{
17277 my ( $time, $suffix, $dir ) = @_ ;
17278
17279 $time ||= 0 ;
17280 $suffix ||= q{} ;
17281 $suffix =~ tr/ //ds ;
17282 my $sep_suffix = ( $suffix ) ? '_' : q{} ;
17283 $dir ||= q{} ;
17284 my $sep_dir = ( $dir ) ? '/' : q{} ;
17285
17286 my $date_str = POSIX::strftime( '%Y_%m_%d_%H_%M_%S', localtime $time ) ;
17287 # Because of ab tests or web accesses, more than one sync withing one second is possible
17288 # so we add also milliseconds
17289 $date_str .= sprintf "_%03d", ($time - int( $time ) ) * 1000 ; # without rounding
17290 my $logfile = "${dir}${sep_dir}${date_str}${sep_suffix}${suffix}.txt" ;
17291 return( $logfile ) ;
17292}
17293
17294
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017295sub tests_localtimez
17296{
17297 note( 'Entering tests_localtimez()' ) ;
17298
17299 SKIP: {
17300 # Too hard to have a well known timezone on Windows
17301 skip( 'Too hard to have a well known timezone on Windows', 1 ) if ( 'MSWin32' eq $OSNAME ) ;
17302 local $ENV{TZ} = 'GMT' ;
17303 like( localtimez( 0 ), qr'1970-01-01 00:00:00 \+0000 (GMT|UTC)', 'localtimez: 0 => match 1970-01-01 00:00:00 +0000 GMT' ) ;
17304 }
17305
17306 is( localtimez( ), localtimez( time ), 'localtimez: undef => equals currrent' ) ;
17307 note( 'Leaving tests_localtimez()' ) ;
17308 return ;
17309}
17310
17311
17312
17313sub localtimez
17314{
17315 my $time = shift ;
17316
17317 $time = defined( $time ) ? $time : time ;
17318
17319 my $datetimestr = POSIX::strftime( '%A %e %B %Y-%m-%d %H:%M:%S %z %Z', localtime( $time ) ) ;
17320
17321 #myprint( "$datetimestr\n" ) ;
17322 return $datetimestr ;
17323}
17324
17325
17326
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017327
17328sub tests_slash_to_underscore
17329{
17330 note( 'Entering tests_slash_to_underscore()' ) ;
17331
17332 is( undef, slash_to_underscore( ), 'slash_to_underscore: no parameters => undef' ) ;
17333 is( '_', slash_to_underscore( '/' ), 'slash_to_underscore: / => _' ) ;
17334 is( '_abc_def_', slash_to_underscore( '/abc/def/' ), 'slash_to_underscore: /abc/def/ => _abc_def_' ) ;
17335 note( 'Leaving tests_slash_to_underscore()' ) ;
17336 return ;
17337}
17338
17339sub slash_to_underscore
17340{
17341 my $string = shift ;
17342
17343 if ( ! defined $string ) { return ; }
17344
17345 $string =~ tr{/}{_} ;
17346
17347 return( $string ) ;
17348}
17349
17350
17351
17352
17353sub tests_million_folders_baby_2
17354{
17355 note( 'Entering tests_million_folders_baby_2()' ) ;
17356
17357 my %long ;
17358 @long{ 1 .. 900_000 } = (1) x 900_000 ;
17359 #myprint( %long, "\n" ) ;
17360 my $pasglop = 0 ;
17361 foreach my $elem ( 1 .. 900_000 ) {
17362 #$debug and myprint( "$elem " ) ;
17363 if ( not exists $long{ $elem } ) {
17364 $pasglop++ ;
17365 }
17366 }
17367 ok( 0 == $pasglop, 'tests_million_folders_baby_2: search among 900_000' ) ;
17368 # myprint( "$pasglop\n" ) ;
17369
17370 note( 'Leaving tests_million_folders_baby_2()' ) ;
17371 return ;
17372}
17373
17374
17375
17376sub tests_always_fail
17377{
17378 note( 'Entering tests_always_fail()' ) ;
17379
17380 is( 0, 1, 'always_fail: 0 is 1' ) ;
17381
17382 note( 'Leaving tests_always_fail()' ) ;
17383 return ;
17384}
17385
17386
17387sub tests_logfileprepa
17388{
17389 note( 'Entering tests_logfileprepa()' ) ;
17390
17391 is( undef, logfileprepa( ), 'logfileprepa: no args => undef' ) ;
17392 my $logfile = 'W/tmp/tests/tests_logfileprepa.txt' ;
17393 is( 1, logfileprepa( $logfile ), 'logfileprepa: W/tmp/tests/tests_logfileprepa.txt => 1' ) ;
17394
17395 note( 'Leaving tests_logfileprepa()' ) ;
17396 return ;
17397}
17398
17399sub logfileprepa
17400{
17401 my $logfile = shift ;
17402
17403 if ( ! defined( $logfile ) )
17404 {
17405 return ;
17406 }else
17407 {
17408 #myprint( "[$logfile]\n" ) ;
17409 my $dirname = dirname( $logfile ) ;
17410 do_valid_directory( $dirname ) || return( 0 ) ;
17411 return( 1 ) ;
17412 }
17413}
17414
17415
17416sub tests_teelaunch
17417{
17418 note( 'Entering tests_teelaunch()' ) ;
17419
17420 is( undef, teelaunch( ), 'teelaunch: no args => undef' ) ;
17421 my $mysync = {} ;
17422 is( undef, teelaunch( $mysync ), 'teelaunch: arg empty {} => undef' ) ;
17423 $mysync->{logfile} = q{} ;
17424 is( undef, teelaunch( $mysync ), 'teelaunch: logfile empty string => undef' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017425
17426 # First time, learning IO::Tee intrasics
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017427 $mysync->{logfile} = 'W/tmp/tests/tests_teelaunch.txt' ;
17428 isa_ok( my $tee = teelaunch( $mysync ), 'IO::Tee' , 'teelaunch: logfile W/tmp/tests/tests_teelaunch.txt' ) ;
17429 is( 1, print( $tee "Hi!\n" ), 'teelaunch: write Hi!') ;
17430 is( "Hi!\n", file_to_string( 'W/tmp/tests/tests_teelaunch.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch.txt is Hi!\n' ) ;
17431 is( 1, print( $tee "Hoo\n" ), 'teelaunch: write Hoo') ;
17432 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' ) ;
17433
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017434 # closing so tee won't be happy
17435 close $mysync->{logfile_handle} ;
17436 is( undef, print( $tee "Argh1\n" ), 'teelaunch: write Argh1') ;
17437 is( undef, print( $tee "Argh2\n" ), 'teelaunch: write Argh2') ;
17438 # write not done
17439 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' ) ;
17440 print join( ' ', $tee->handles ), "\n";
17441 is( 2, scalar $tee->handles, 'teelaunch: 2 handles') ;
17442 shift @{*{$tee}};
17443 print join(' ', $tee->handles), "\n" ;
17444 is( 1, scalar $tee->handles, 'teelaunch: 1 handle') ;
17445 is( 1, print( $tee "Argh3\n" ), 'teelaunch: write Argh3 yeah') ;
17446
17447 shift @{*{$tee}};
17448 # will not print anything now
17449 is( 0, scalar $tee->handles, 'teelaunch: 0 handle') ;
17450 is( 1, print( $tee "Argh 4\n" ), 'teelaunch: write Argh4 no') ;
17451
17452 # Second time, lesson learnt IO::Tee
17453 $mysync->{logfile} = 'W/tmp/tests/tests_teelaunch2.txt' ;
17454 isa_ok( $tee = teelaunch( $mysync ), 'IO::Tee' , 'teelaunch: logfile W/tmp/tests/tests_teelaunch2.txt' ) ;
17455 is( 1, print( $tee "Hi!\n" ), 'teelaunch: write Hi!') ;
17456 is( "Hi!\n", file_to_string( 'W/tmp/tests/tests_teelaunch2.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch2.txt is Hi!\n' ) ;
17457 is( 1, print( $tee "Hoo\n" ), 'teelaunch: write Hoo') ;
17458 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' ) ;
17459
17460 is( 1, teefinish( $mysync ), 'teefinish: return 1') ;
17461 is( 1, print( $tee "Argh1\n" ), 'teelaunch: write Argh1') ;
17462 is( 1, print( $tee "Argh2\n" ), 'teelaunch: write Argh2') ;
17463 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' ) ;
17464 is( 1, teefinish( $mysync ), 'teefinish: still return 1') ;
17465
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017466 note( 'Leaving tests_teelaunch()' ) ;
17467 return ;
17468}
17469
17470sub teelaunch
17471{
17472 my $mysync = shift ;
17473
17474 if ( ! defined( $mysync ) )
17475 {
17476 return ;
17477 }
17478
17479 my $logfile = $mysync->{logfile} ;
17480
17481 if ( ! $logfile )
17482 {
17483 return ;
17484 }
17485
17486 logfileprepa( $logfile ) || croak "Error no valid directory to write log file $logfile : $OS_ERROR" ;
17487
17488 # This is a log file opened during the whole sync
17489 ## no critic (InputOutput::RequireBriefOpen)
17490 open my $logfile_handle, '>', $logfile
17491 or croak( "Can not open $logfile for write: $OS_ERROR" ) ;
17492 binmode $logfile_handle, ":encoding(UTF-8)" ;
17493 my $tee = IO::Tee->new( $logfile_handle, \*STDOUT ) ;
17494 $tee->autoflush( 1 ) ;
17495 $mysync->{logfile_handle} = $logfile_handle ;
17496 $mysync->{tee} = $tee ;
17497 return $tee ;
17498}
17499
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017500sub teefinish
17501{
17502 my $mysync = shift ;
17503
17504 if ( ! defined( $mysync ) ) { return ; }
17505
17506 my $tee = $mysync->{tee} ;
17507
17508 if ( ! defined( $tee ) ) { return ; }
17509
17510 if ( 2 == scalar $tee->handles )
17511 {
17512 shift @{*{$tee}};
17513 }
17514 else
17515 {
17516 # nothing
17517 }
17518 return scalar $tee->handles ;
17519}
17520
17521
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017522sub getpwuid_any_os
17523{
17524 my $uid = shift ;
17525
17526 return( scalar getlogin ) if ( 'MSWin32' eq $OSNAME ) ; # Windows system
17527 return( scalar getpwuid $uid ) ; # Unix system
17528
17529
17530}
17531
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017532
17533
17534sub abortifneeded
17535{
17536 my $mysync = shift ;
17537 if ( -e $mysync->{ abortfile } )
17538 {
17539 myprint( "Asked to terminate by file $mysync->{ abortfile }\n" ) ;
17540 do_and_print_stats( $mysync ) ;
17541 myprint( "You should resynchronize those accounts by running a sync again,\n",
17542 "since some messages and entire folders might still be missing on host2.\n"
17543 ) ;
17544 exit_clean( $mysync, $EXIT_BY_FILE ) ;
17545 return ;
17546 }
17547 else
17548 {
17549 return ;
17550 }
17551}
17552
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017553sub simulong
17554{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017555 my $mysync = shift ;
17556
17557 my $max_seconds = $mysync->{ simulong } ;
17558
17559 if ( ! $max_seconds ) { return ; }
17560
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017561 my $division = 5 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017562 my $last_count = int( $division * $max_seconds ) ;
17563 $mysync->{ debug } and myprint "last_count $last_count = int( division $division * max_seconds $max_seconds)\n" ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017564 foreach my $i ( 1 .. ( $last_count ) ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017565 myprint( "Are you still here ETA: " . ( $last_count - $i ) . "/$last_count msgs left\n" ) ;
17566 #this one is for testing huge page behavior
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017567 #myprint( "Are you still here ETA: " . ($last_count - $i) . "/$last_count msgs left\n" . ( "Ah" x 40 . "\n") x 4000 ) ;
17568 sleep( 1 / $division ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017569 abortifneeded( $mysync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017570 }
17571
17572 return ;
17573}
17574
17575
17576
17577sub printenv
17578{
17579 myprint( "Environment variables listing:\n",
17580 ( map { "$_ => $ENV{$_}\n" } sort keys %ENV),
17581 "Environment variables listing end\n" ) ;
17582 return ;
17583}
17584
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017585
17586sub unittestssuite
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017587{
17588 my $mysync = shift ;
17589 if ( ! ( $mysync->{ tests } or $mysync->{ testsdebug } or $mysync->{ testsunit } ) ) {
17590 return ;
17591 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017592
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017593 my $test_builder = Test::More->builder ;
17594 tests( $mysync ) ;
17595 testsdebug( $mysync ) ;
17596 testunitsession( $mysync ) ;
17597
17598 my @summary = $test_builder->summary() ;
17599 my @details = $test_builder->details() ;
17600 my $nb_tests_run = scalar( @summary ) ;
17601 my $nb_tests_expected = $test_builder->expected_tests() ;
17602 my $nb_tests_failed = count_0s( @summary ) ;
17603 my $tests_failed = report_failures( @details ) ;
17604 if ( $nb_tests_failed or ( $nb_tests_run != $nb_tests_expected ) ) {
17605 #$test_builder->reset( ) ;
17606 myprint( "Summary of tests: failed $nb_tests_failed tests, run $nb_tests_run tests, expected to run $nb_tests_expected tests.\n",
17607 "List of failed tests:\n", $tests_failed ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017608 return $EXIT_TESTS_FAILED ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017609 }
17610
17611 cleanup_mess_from_tests( ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017612
17613 return 0 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017614}
17615
17616sub cleanup_mess_from_tests
17617{
17618 undef @pipemess ;
17619 return ;
17620}
17621
17622sub after_get_options
17623{
17624 my $mysync = shift ;
17625 my $numopt = shift ;
17626
17627
17628 # exit with --help option or no option at all
17629 $mysync->{ debug } and myprint( "numopt:$numopt\n" ) ;
17630
17631 if ( $help or not $numopt ) {
17632 myprint( usage( $mysync ) ) ;
17633 exit ;
17634 }
17635
17636 return ;
17637}
17638
17639sub tests_remove_edging_blanks
17640{
17641 note( 'Entering tests_remove_edging_blanks()' ) ;
17642
17643 is( undef, remove_edging_blanks( ), 'remove_edging_blanks: no args => undef' ) ;
17644 is( 'abcd', remove_edging_blanks( 'abcd' ), 'remove_edging_blanks: abcd => abcd' ) ;
17645 is( 'ab cd', remove_edging_blanks( ' ab cd ' ), 'remove_edging_blanks: " ab cd " => "ab cd"' ) ;
17646
17647 note( 'Leaving tests_remove_edging_blanks()' ) ;
17648 return ;
17649}
17650
17651
17652
17653sub remove_edging_blanks
17654{
17655 my $string = shift ;
17656 if ( ! defined $string )
17657 {
17658 return ;
17659 }
17660 $string =~ s,^ +| +$,,g ;
17661 return $string ;
17662}
17663
17664
17665sub tests_sanitize
17666{
17667 note( 'Entering tests_remove_edging_blanks()' ) ;
17668
17669 is( undef, sanitize( ), 'sanitize: no args => undef' ) ;
17670 my $mysync = {} ;
17671
17672 $mysync->{ host1 } = ' example.com ' ;
17673 $mysync->{ user1 } = ' to to ' ;
17674 $mysync->{ password1 } = ' sex is good! ' ;
17675 is( undef, sanitize( $mysync ), 'sanitize: => undef' ) ;
17676 is( 'example.com', $mysync->{ host1 }, 'sanitize: host1 " example.com " => "example.com"' ) ;
17677 is( 'to to', $mysync->{ user1 }, 'sanitize: user1 " to to " => "to to"' ) ;
17678 is( 'sex is good!', $mysync->{ password1 }, 'sanitize: password1 " sex is good! " => "sex is good!"' ) ;
17679 note( 'Leaving tests_remove_edging_blanks()' ) ;
17680 return ;
17681}
17682
17683
17684sub sanitize
17685{
17686 my $mysync = shift ;
17687 if ( ! defined $mysync )
17688 {
17689 return ;
17690 }
17691
17692 foreach my $parameter ( qw( host1 host2 user1 user2 password1 password2 ) )
17693 {
17694 $mysync->{ $parameter } = remove_edging_blanks( $mysync->{ $parameter } ) ;
17695 }
17696 return ;
17697}
17698
17699sub easyany
17700{
17701 my $mysync = shift ;
17702
17703 # Gmail
17704 if ( $mysync->{gmail1} and $mysync->{gmail2} ) {
17705 $mysync->{ debug } and myprint( "gmail1 gmail2\n") ;
17706 gmail12( $mysync ) ;
17707 return ;
17708 }
17709 if ( $mysync->{gmail1} ) {
17710 $mysync->{ debug } and myprint( "gmail1\n" ) ;
17711 gmail1( $mysync ) ;
17712 }
17713 if ( $mysync->{gmail2} ) {
17714 $mysync->{ debug } and myprint( "gmail2\n" ) ;
17715 gmail2( $mysync ) ;
17716 }
17717 # Office 365
17718 if ( $mysync->{office1} ) {
17719 office1( $mysync ) ;
17720 }
17721
17722 if ( $mysync->{office2} ) {
17723 office2( $mysync ) ;
17724 }
17725
17726 # Exchange
17727 if ( $mysync->{exchange1} ) {
17728 exchange1( $mysync ) ;
17729 }
17730
17731 if ( $mysync->{exchange2} ) {
17732 exchange2( $mysync ) ;
17733 }
17734
17735
17736 # Domino
17737 if ( $mysync->{domino1} ) {
17738 domino1( $mysync ) ;
17739 }
17740
17741 if ( $mysync->{domino2} ) {
17742 domino2( $mysync ) ;
17743 }
17744
17745 return ;
17746}
17747
17748# From and for https://imapsync.lamiral.info/FAQ.d/FAQ.Gmail.txt
17749sub gmail12
17750{
17751 my $mysync = shift ;
17752 # Gmail at host1 and host2
17753 $mysync->{host1} ||= 'imap.gmail.com' ;
17754 $mysync->{ssl1} = ( defined $mysync->{ssl1} ) ? $mysync->{ssl1} : 1 ;
17755 $mysync->{host2} ||= 'imap.gmail.com' ;
17756 $mysync->{ssl2} = ( defined $mysync->{ssl2} ) ? $mysync->{ssl2} : 1 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017757 $mysync->{maxbytespersecond} ||= 20_000 ; # should be less than 10_000 when computed from Gmail documentation
17758 $mysync->{maxbytesafter} ||= 1_000_000_000 ; # In fact it is documented as half: 500_000_000
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017759 $mysync->{automap} = ( defined $mysync->{automap} ) ? $mysync->{automap} : 1 ;
17760 $mysync->{maxsleep} = ( defined $mysync->{maxsleep} ) ? $mysync->{maxsleep} : $MAX_SLEEP ; ;
17761 $skipcrossduplicates = ( defined $skipcrossduplicates ) ? $skipcrossduplicates : 0 ;
17762 $mysync->{ synclabels } = ( defined $mysync->{ synclabels } ) ? $mysync->{ synclabels } : 1 ;
17763 $mysync->{ resynclabels } = ( defined $mysync->{ resynclabels } ) ? $mysync->{ resynclabels } : 1 ;
17764 push @exclude, '\[Gmail\]$' ;
17765 push @folderlast, '[Gmail]/All Mail' ;
17766 return ;
17767}
17768
17769
17770sub gmail1
17771{
17772 my $mysync = shift ;
17773 # Gmail at host2
17774 $mysync->{host1} ||= 'imap.gmail.com' ;
17775 $mysync->{ssl1} = ( defined $mysync->{ssl1} ) ? $mysync->{ssl1} : 1 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017776 $mysync->{maxbytespersecond} ||= 40_000 ; # should be 30_000 computed from by Gmail documentation
17777 $mysync->{maxbytesafter} ||= 3_000_000_000 ; #
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017778 $mysync->{automap} = ( defined $mysync->{automap} ) ? $mysync->{automap} : 1 ;
17779 $mysync->{maxsleep} = ( defined $mysync->{maxsleep} ) ? $mysync->{maxsleep} : $MAX_SLEEP ; ;
17780 $skipcrossduplicates = ( defined $skipcrossduplicates ) ? $skipcrossduplicates : 1 ;
17781
17782 push @useheader, 'X-Gmail-Received', 'Message-Id' ;
17783 push @{ $mysync->{ regextrans2 } }, 's,\[Gmail\].,,' ;
17784 push @folderlast, '[Gmail]/All Mail' ;
17785 return ;
17786}
17787
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017788sub gmail2
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017789{
17790 my $mysync = shift ;
17791 # Gmail at host2
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017792 $mysync->{ host2 } ||= 'imap.gmail.com' ;
17793 $mysync->{ ssl2 } = ( defined $mysync->{ ssl2 } ) ? $mysync->{ ssl2 } : 1 ;
17794 $mysync->{ maxbytespersecond } ||= 20_000 ; # should be less than 10_000 computed from by Gmail documentation
17795 $mysync->{ maxbytesafter } ||= 1_000_000_000 ; # In fact it is documented as half: 500_000_000
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017796
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017797 $mysync->{ automap } = ( defined $mysync->{ automap } ) ? $mysync->{ automap } : 1 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017798 #$skipcrossduplicates = ( defined $skipcrossduplicates ) ? $skipcrossduplicates : 1 ;
17799 $mysync->{ expunge1 } = ( defined $mysync->{ expunge1 } ) ? $mysync->{ expunge1 } : 1 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017800 $mysync->{ addheader } = ( defined $mysync->{ addheader } ) ? $mysync->{ addheader } : 1 ;
17801 $mysync->{ maxsleep } = ( defined $mysync->{ maxsleep } ) ? $mysync->{ maxsleep } : $MAX_SLEEP ; ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017802
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017803 #$mysync->{ maxsize } = ( defined $mysync->{ maxsize } ) ? $mysync->{ maxsize } : $GMAIL_MAXSIZE ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017804
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017805 if ( ! $mysync->{ noexclude } ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017806 push @exclude, '\[Gmail\]$' ;
17807 }
17808 push @useheader, 'Message-Id' ;
17809 push @{ $mysync->{ regextrans2 } }, 's,\[Gmail\].,,' ;
17810
17811 # push @{ $mysync->{ regextrans2 } }, 's/[ ]+/_/g' ; # is now replaced
17812 # by the two more specific following regexes,
17813 # they remove just the beginning and trailing blanks, not all.
17814 push @{ $mysync->{ regextrans2 } }, 's,^ +| +$,,g' ;
17815 push @{ $mysync->{ regextrans2 } }, 's,/ +| +/,/,g' ;
17816 #
17817 push @{ $mysync->{ regextrans2 } }, q{s/['\\^"]/_/g} ; # Verified this
17818 push @folderlast, '[Gmail]/All Mail' ;
17819 return ;
17820}
17821
17822
17823# From https://imapsync.lamiral.info/FAQ.d/FAQ.Exchange.txt
17824sub office1
17825{
17826 # Office 365 at host1
17827 my $mysync = shift ;
17828
17829 output( $mysync, q{Option --office1 is like: --host1 outlook.office365.com --ssl1 --exclude "^Files$"} . "\n" ) ;
17830 output( $mysync, "Option --office1 (cont) : unless overrided with --host1 otherhost --nossl1 --noexclude\n" ) ;
17831 $mysync->{host1} ||= 'outlook.office365.com' ;
17832 $mysync->{ssl1} = ( defined $mysync->{ssl1} ) ? $mysync->{ssl1} : 1 ;
17833 if ( ! $mysync->{noexclude} ) {
17834 push @exclude, '^Files$' ;
17835 }
17836 return ;
17837}
17838
17839
17840sub office2
17841{
17842 # Office 365 at host2
17843 my $mysync = shift ;
17844 output( $mysync, qq{Option --office2 is like: --host2 outlook.office365.com --ssl2 --maxsize 45_000_000 --maxmessagespersecond 4\n} ) ;
17845 output( $mysync, qq{Option --office2 (cont) : --disarmreadreceipts --regexmess "wrap 10500" --f1f2 "Files=Files_renamed_by_imapsync"\n} ) ;
17846 output( $mysync, qq{Option --office2 (cont) : unless overrided with --host2 otherhost --nossl2 ... --nodisarmreadreceipts --noregexmess\n} ) ;
17847 output( $mysync, qq{Option --office2 (cont) : and --nof1f2 to avoid Files folder renamed to Files_renamed_by_imapsync\n} ) ;
17848 $mysync->{host2} ||= 'outlook.office365.com' ;
17849 $mysync->{ssl2} = ( defined $mysync->{ssl2} ) ? $mysync->{ssl2} : 1 ;
17850 $mysync->{ maxsize } ||= 45_000_000 ;
17851 $mysync->{maxmessagespersecond} ||= 4 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017852 #push @{ $mysync->{ regexflag } }, 's/\\\\Flagged//g' ; # No problem without! tested 2018_09_10
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017853 $disarmreadreceipts = ( defined $disarmreadreceipts ) ? $disarmreadreceipts : 1 ;
17854 # I dislike double negation but here is one
17855 if ( ! $mysync->{noregexmess} )
17856 {
17857 push @regexmess, 's,(.{10239}),$1\r\n,g' ;
17858 }
17859 # and another...
17860 if ( ! $mysync->{nof1f2} )
17861 {
17862 push @{ $mysync->{f1f2} }, 'Files=Files_renamed_by_imapsync' ;
17863 }
17864 return ;
17865}
17866
17867sub exchange1
17868{
17869 # Exchange 2010/2013 at host1
17870 my $mysync = shift ;
17871 output( $mysync, "Option --exchange1 does nothing (except printing this line...)\n" ) ;
17872 # Well nothing to do so far
17873 return ;
17874}
17875
17876sub exchange2
17877{
17878 # Exchange 2010/2013 at host2
17879 my $mysync = shift ;
17880 output( $mysync, "Option --exchange2 is like: --maxsize 10_000_000 --maxmessagespersecond 4 --disarmreadreceipts\n" ) ;
17881 output( $mysync, "Option --exchange2 (cont) : --regexflag del Flagged --regexmess wrap 10500\n" ) ;
17882 output( $mysync, "Option --exchange2 (cont) : unless overrided with --maxsize xxx --nodisarmreadreceipts --noregexflag --noregexmess\n" ) ;
17883 $mysync->{ maxsize } ||= 10_000_000 ;
17884 $mysync->{maxmessagespersecond} ||= 4 ;
17885 $disarmreadreceipts = ( defined $disarmreadreceipts ) ? $disarmreadreceipts : 1 ;
17886 # I dislike double negation but here are two
17887 if ( ! $mysync->{noregexflag} ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017888 push @{ $mysync->{ regexflag } }, 's/\\\\Flagged//g' ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017889 }
17890 if ( ! $mysync->{noregexmess} ) {
17891 push @regexmess, 's,(.{10239}),$1\r\n,g' ;
17892 }
17893 return ;
17894}
17895
17896sub domino1
17897{
17898 # Domino at host1
17899 my $mysync = shift ;
17900
17901 $mysync->{ sep1 } = q{\\} ;
17902 $prefix1 = q{} ;
17903 $messageidnodomain = ( defined $messageidnodomain ) ? $messageidnodomain : 1 ;
17904 return ;
17905}
17906
17907sub domino2
17908{
17909 # Domino at host1
17910 my $mysync = shift ;
17911
17912 $mysync->{ sep2 } = q{\\} ;
17913 $prefix2 = q{} ;
17914 $messageidnodomain = ( defined $messageidnodomain ) ? $messageidnodomain : 1 ;
17915 push @{ $mysync->{ regextrans2 } }, 's,^Inbox\\\\(.*),$1,i' ;
17916 return ;
17917}
17918
17919
17920sub tests_resolv
17921{
17922 note( 'Entering tests_resolv()' ) ;
17923
17924 # is( , resolv( ), 'resolv: => ' ) ;
17925 is( undef, resolv( ), 'resolv: no args => undef' ) ;
17926 is( undef, resolv( q{} ), 'resolv: empty string => undef' ) ;
17927 is( undef, resolv( 'hostnotexist' ), 'resolv: hostnotexist => undef' ) ;
17928 is( '127.0.0.1', resolv( '127.0.0.1' ), 'resolv: 127.0.0.1 => 127.0.0.1' ) ;
17929 is( '127.0.0.1', resolv( 'localhost' ), 'resolv: localhost => 127.0.0.1' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017930 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 +010017931
17932 # ip6-localhost ( in /etc/hosts )
17933 is( '::1', resolv( 'ip6-localhost' ), 'resolv: ip6-localhost => ::1' ) ;
17934 is( '::1', resolv( '::1' ), 'resolv: ::1 => ::1' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017935 # ks2ipv6 now has CNAME ks6ipv6
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017936 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 +020017937 is( '2001:41d0:8:9951::1', resolv( 'ks6ipv6.lamiral.info' ), 'resolv: ks6ipv6.lamiral.info => 2001:41d0:8:9951::1' ) ;
17938 # ks6
17939 is( '2001:41d0:8:9951::1', resolv( '2001:41d0:8:9951::1' ), 'resolv: 2001:41d0:8:9951::1 => 2001:41d0:8:9951::1' ) ;
17940 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 +010017941 # ks3
17942 is( '2001:41d0:8:bebd::1', resolv( '2001:41d0:8:bebd::1' ), 'resolv: 2001:41d0:8:bebd::1 => 2001:41d0:8:bebd::1' ) ;
17943 is( '2001:41d0:8:bebd::1', resolv( 'ks3ipv6.lamiral.info' ), 'resolv: ks3ipv6.lamiral.info => 2001:41d0:8:bebd::1' ) ;
17944
17945
17946 note( 'Leaving tests_resolv()' ) ;
17947 return ;
17948}
17949
17950
17951
17952sub resolv
17953{
17954 my $host = shift @ARG ;
17955
17956 if ( ! $host ) { return ; }
17957 my $addr ;
17958 if ( defined &Socket::getaddrinfo ) {
17959 $addr = resolv_with_getaddrinfo( $host ) ;
17960 return( $addr ) ;
17961 }
17962
17963
17964
17965 my $iaddr = inet_aton( $host ) ;
17966 if ( ! $iaddr ) { return ; }
17967 $addr = inet_ntoa( $iaddr ) ;
17968
17969 return $addr ;
17970}
17971
17972sub resolv_with_getaddrinfo
17973{
17974 my $host = shift @ARG ;
17975
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017976 $sync->{ debug } and myprint( "Entering resolv_with_getaddrinfo( $host )\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017977 if ( ! $host ) { return ; }
17978
17979 my ( $err_getaddrinfo, @res ) = Socket::getaddrinfo( $host, "", { socktype => Socket::SOCK_RAW } ) ;
17980 if ( $err_getaddrinfo ) {
17981 myprint( "Cannot getaddrinfo of $host: $err_getaddrinfo\n" ) ;
17982 return ;
17983 }
17984
17985 my @addr ;
17986 while( my $ai = shift @res ) {
17987 my ( $err_getnameinfo, $ipaddr ) = Socket::getnameinfo( $ai->{addr}, Socket::NI_NUMERICHOST(), Socket::NIx_NOSERV() ) ;
17988 if ( $err_getnameinfo ) {
17989 myprint( "Cannot getnameinfo of $host: $err_getnameinfo\n" ) ;
17990 return ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017991 }else{
17992 $sync->{ debug } and myprint( "$host => $ipaddr\n" ) ;
17993 push @addr, $ipaddr ;
17994 my $reverse ;
17995 ( $err_getnameinfo, $reverse ) = Socket::getnameinfo( $ai->{addr}, 0, Socket::NIx_NOSERV() ) ;
17996 $sync->{ debug } and myprint( "$host => $ipaddr => $reverse\n" ) ;
17997 }
17998 $sync->{ debug } and myprint( "$host => $ipaddr\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017999
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018000 }
18001 $sync->{ debug } and myprint( "Leaving resolv_with_getaddrinfo( $host => $addr[0])\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018002 return $addr[0] ;
18003}
18004
18005sub tests_resolvrev
18006{
18007 note( 'Entering tests_resolvrev()' ) ;
18008
18009 # is( , resolvrev( ), 'resolvrev: => ' ) ;
18010 is( undef, resolvrev( ), 'resolvrev: no args => undef' ) ;
18011 is( undef, resolvrev( q{} ), 'resolvrev: empty string => undef' ) ;
18012 is( undef, resolvrev( 'hostnotexist' ), 'resolvrev: hostnotexist => undef' ) ;
18013 is( 'localhost', resolvrev( '127.0.0.1' ), 'resolvrev: 127.0.0.1 => localhost' ) ;
18014 is( 'localhost', resolvrev( 'localhost' ), 'resolvrev: localhost => localhost' ) ;
18015 is( 'ks.lamiral.info', resolvrev( 'imapsync.lamiral.info' ), 'resolvrev: imapsync.lamiral.info => ks.lamiral.info' ) ;
18016
18017 # ip6-localhost ( in /etc/hosts )
18018 is( 'ip6-localhost', resolvrev( 'ip6-localhost' ), 'resolvrev: ip6-localhost => ip6-localhost' ) ;
18019 is( 'ip6-localhost', resolvrev( '::1' ), 'resolvrev: ::1 => ip6-localhost' ) ;
18020 # ks2
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018021 is( 'ks6ipv6.lamiral.info', resolvrev( '2001:41d0:8:d8b6::1' ), 'resolvrev: 2001:41d0:8:d8b6::1 => ks6ipv6.lamiral.info' ) ;
18022 is( 'ks6ipv6.lamiral.info', resolvrev( 'ks6ipv6.lamiral.info' ), 'resolvrev: ks6ipv6.lamiral.info => ks6ipv6.lamiral.info' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018023 # ks3
18024 is( 'ks3ipv6.lamiral.info', resolvrev( '2001:41d0:8:bebd::1' ), 'resolvrev: 2001:41d0:8:bebd::1 => ks3ipv6.lamiral.info' ) ;
18025 is( 'ks3ipv6.lamiral.info', resolvrev( 'ks3ipv6.lamiral.info' ), 'resolvrev: ks3ipv6.lamiral.info => ks3ipv6.lamiral.info' ) ;
18026
18027
18028 note( 'Leaving tests_resolvrev()' ) ;
18029 return ;
18030}
18031
18032sub resolvrev
18033{
18034 my $host = shift @ARG ;
18035
18036 if ( ! $host ) { return ; }
18037
18038 if ( defined &Socket::getaddrinfo ) {
18039 my $name = resolvrev_with_getaddrinfo( $host ) ;
18040 return( $name ) ;
18041 }
18042
18043 return ;
18044}
18045
18046sub resolvrev_with_getaddrinfo
18047{
18048 my $host = shift @ARG ;
18049
18050 if ( ! $host ) { return ; }
18051
18052 my ( $err, @res ) = Socket::getaddrinfo( $host, "", { socktype => Socket::SOCK_RAW } ) ;
18053 if ( $err ) {
18054 myprint( "Cannot getaddrinfo of $host: $err\n" ) ;
18055 return ;
18056 }
18057
18058 my @name ;
18059 while( my $ai = shift @res ) {
18060 my ( $err, $reverse ) = Socket::getnameinfo( $ai->{addr}, 0, Socket::NIx_NOSERV() ) ;
18061 if ( $err ) {
18062 myprint( "Cannot getnameinfo of $host: $err\n" ) ;
18063 return ;
18064 }
18065 $sync->{ debug } and myprint( "$host => $reverse\n" ) ;
18066 push @name, $reverse ;
18067 }
18068
18069 return $name[0] ;
18070}
18071
18072
18073
18074sub tests_imapsping
18075{
18076 note( 'Entering tests_imapsping()' ) ;
18077
18078 is( undef, imapsping( ), 'imapsping: no args => undef' ) ;
18079 is( undef, imapsping( 'hostnotexist' ), 'imapsping: hostnotexist => undef' ) ;
18080 is( 1, imapsping( 'imapsync.lamiral.info' ), 'imapsping: imapsync.lamiral.info => 1' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018081 is( 1, imapsping( 'ks6ipv6.lamiral.info' ), 'imapsping: ks6ipv6.lamiral.info => 1' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018082 note( 'Leaving tests_imapsping()' ) ;
18083 return ;
18084}
18085
18086sub imapsping
18087{
18088 my $host = shift ;
18089 return tcpping( $host, $IMAP_SSL_PORT ) ;
18090}
18091
18092sub tests_tcpping
18093{
18094 note( 'Entering tests_tcpping()' ) ;
18095
18096 is( undef, tcpping( ), 'tcpping: no args => undef' ) ;
18097 is( undef, tcpping( 'hostnotexist' ), 'tcpping: one arg => undef' ) ;
18098 is( undef, tcpping( undef, 888 ), 'tcpping: arg undef, port => undef' ) ;
18099 is( undef, tcpping( 'hostnotexist', 993 ), 'tcpping: hostnotexist 993 => undef' ) ;
18100 is( undef, tcpping( 'hostnotexist', 888 ), 'tcpping: hostnotexist 888 => undef' ) ;
18101 is( 1, tcpping( 'imapsync.lamiral.info', 993 ), 'tcpping: imapsync.lamiral.info 993 => 1' ) ;
18102 is( 0, tcpping( 'imapsync.lamiral.info', 888 ), 'tcpping: imapsync.lamiral.info 888 => 0' ) ;
18103 is( 1, tcpping( '5.135.158.182', 993 ), 'tcpping: 5.135.158.182 993 => 1' ) ;
18104 is( 0, tcpping( '5.135.158.182', 888 ), 'tcpping: 5.135.158.182 888 => 0' ) ;
18105
18106 # Net::Ping supports ipv6 only after release 1.50
18107 # http://cpansearch.perl.org/src/RURBAN/Net-Ping-2.59/Changes
18108 # Anyway I plan to avoid Net-Ping for that too long standing feature
18109 # Net-Ping is integrated in Perl itself, who knows ipv6 for a long time
18110 is( 1, tcpping( '2001:41d0:8:d8b6::1', 993 ), 'tcpping: 2001:41d0:8:d8b6::1 993 => 1' ) ;
18111 is( 0, tcpping( '2001:41d0:8:d8b6::1', 888 ), 'tcpping: 2001:41d0:8:d8b6::1 888 => 0' ) ;
18112
18113 note( 'Leaving tests_tcpping()' ) ;
18114 return ;
18115}
18116
18117sub tcpping
18118{
18119 if ( 2 != scalar( @ARG ) ) {
18120 return ;
18121 }
18122 my ( $host, $port ) = @ARG ;
18123 if ( ! $host ) { return ; }
18124 if ( ! $port ) { return ; }
18125
18126 my $mytimeout = $TCP_PING_TIMEOUT ;
18127 require Net::Ping ;
18128 #my $p = Net::Ping->new( 'tcp' ) ;
18129 my $p = Net::Ping->new( ) ;
18130 $p->{port_num} = $port ;
18131 $p->service_check( 1 ) ;
18132 $p->hires( 1 ) ;
18133 my ($ping_ok, $rtt, $ip ) = $p->ping( $host, $mytimeout ) ;
18134 if ( ! defined $ping_ok ) { return ; }
18135 my $rtt_approx = sprintf( "%.3f", $rtt ) ;
18136 $sync->{ debug } and myprint( "Host $host timeout $mytimeout port $port ok $ping_ok ip $ip acked in $rtt_approx s\n" ) ;
18137 $p->close( ) ;
18138 if( $ping_ok ) {
18139 return 1 ;
18140 }else{
18141 return 0 ;
18142 }
18143}
18144
18145sub tests_sslcheck
18146{
18147 note( 'Entering tests_sslcheck()' ) ;
18148
18149 my $mysync ;
18150
18151 is( undef, sslcheck( $mysync ), 'sslcheck: no sslcheck => undef' ) ;
18152
18153 $mysync = {
18154 sslcheck => 1,
18155 } ;
18156
18157 is( 0, sslcheck( $mysync ), 'sslcheck: no host => 0' ) ;
18158
18159 $mysync = {
18160 sslcheck => 1,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018161 host1 => 'test1.lamiral.info',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018162 tls1 => 1,
18163 } ;
18164
18165 is( 0, sslcheck( $mysync ), 'sslcheck: tls1 => 0' ) ;
18166
18167 $mysync = {
18168 sslcheck => 1,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018169 host1 => 'test1.lamiral.info',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018170 } ;
18171
18172
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018173 is( 1, sslcheck( $mysync ), 'sslcheck: test1.lamiral.info => 1' ) ;
18174 is( 1, $mysync->{ssl1}, 'sslcheck: test1.lamiral.info => ssl1 1' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018175
18176 $mysync->{sslcheck} = 0 ;
18177 is( undef, sslcheck( $mysync ), 'sslcheck: sslcheck off => undef' ) ;
18178
18179 $mysync = {
18180 sslcheck => 1,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018181 host1 => 'test1.lamiral.info',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018182 host2 => 'test2.lamiral.info',
18183 } ;
18184
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018185 is( 2, sslcheck( $mysync ), 'sslcheck: test1.lamiral.info + test2.lamiral.info => 2' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018186
18187 $mysync = {
18188 sslcheck => 1,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018189 host1 => 'test1.lamiral.info',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018190 host2 => 'test2.lamiral.info',
18191 tls1 => 1,
18192 } ;
18193
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018194 is( 1, sslcheck( $mysync ), 'sslcheck: test1.lamiral.info + test2.lamiral.info + tls1 => 1' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018195
18196 note( 'Leaving tests_sslcheck()' ) ;
18197 return ;
18198}
18199
18200sub sslcheck
18201{
18202 my $mysync = shift ;
18203
18204 if ( ! $mysync->{sslcheck} ) {
18205 return ;
18206 }
18207 my $nb_on = 0 ;
18208 $mysync->{ debug } and myprint( "sslcheck\n" ) ;
18209 if (
18210 ( ! defined $mysync->{port1} )
18211 and
18212 ( ! defined $mysync->{tls1} )
18213 and
18214 ( ! defined $mysync->{ssl1} )
18215 and
18216 ( defined $mysync->{host1} )
18217 ) {
18218 myprint( "Host1: probing ssl on port $IMAP_SSL_PORT ( use --nosslcheck to avoid this ssl probe ) \n" ) ;
18219 if ( probe_imapssl( $mysync->{host1} ) ) {
18220 $mysync->{ssl1} = 1 ;
18221 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" ) ;
18222 $nb_on++ ;
18223 }else{
18224 myprint( "Host1: sslcheck did not detected open ssl port $IMAP_SSL_PORT. Will use standard $IMAP_PORT port.\n" ) ;
18225 }
18226 }
18227
18228 if (
18229 ( ! defined $mysync->{port2} )
18230 and
18231 ( ! defined $mysync->{tls2} )
18232 and
18233 ( ! defined $mysync->{ssl2} )
18234 and
18235 ( defined $mysync->{host2} )
18236 ) {
18237 myprint( "Host2: probing ssl on port $IMAP_SSL_PORT ( use --nosslcheck to avoid this ssl probe ) \n" ) ;
18238 if ( probe_imapssl( $mysync->{host2} ) ) {
18239 $mysync->{ssl2} = 1 ;
18240 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" ) ;
18241 $nb_on++ ;
18242 }else{
18243 myprint( "Host2: sslcheck did not detected open ssl port $IMAP_SSL_PORT. Will use standard $IMAP_PORT port.\n" ) ;
18244 }
18245 }
18246 return $nb_on ;
18247}
18248
18249
18250sub testslive_init
18251{
18252 my $mysync = shift ;
18253 $mysync->{host1} ||= 'test1.lamiral.info' ;
18254 $mysync->{user1} ||= 'test1' ;
18255 $mysync->{password1} ||= 'secret1' ;
18256 $mysync->{host2} ||= 'test2.lamiral.info' ;
18257 $mysync->{user2} ||= 'test2' ;
18258 $mysync->{password2} ||= 'secret2' ;
18259 return ;
18260}
18261
18262sub testslive6_init
18263{
18264 my $mysync = shift ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018265 $mysync->{host1} ||= 'ks6ipv6.lamiral.info' ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018266 $mysync->{user1} ||= 'test1' ;
18267 $mysync->{password1} ||= 'secret1' ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018268 $mysync->{host2} ||= 'ks6ipv6.lamiral.info' ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018269 $mysync->{user2} ||= 'test2' ;
18270 $mysync->{password2} ||= 'secret2' ;
18271 return ;
18272}
18273
18274
18275sub tests_backslash_caret
18276{
18277 note( 'Entering tests_backslash_caret()' ) ;
18278
18279 is( "lalala", backslash_caret( "lalala" ), 'backslash_caret: lalala => lalala' ) ;
18280 is( "lalala\n", backslash_caret( "lalala\n" ), 'backslash_caret: lalala => lalala 2nd' ) ;
18281 is( '^', backslash_caret( '\\' ), 'backslash_caret: \\ => ^' ) ;
18282 is( "^\n", backslash_caret( "\\\n" ), 'backslash_caret: \\ => ^' ) ;
18283 is( "\\lalala", backslash_caret( "\\lalala" ), 'backslash_caret: \\lalala => \\lalala' ) ;
18284 is( "\\lal\\ala", backslash_caret( "\\lal\\ala" ), 'backslash_caret: \\lal\\ala => \\lal\\ala' ) ;
18285 is( "\\lalala\n", backslash_caret( "\\lalala\n" ), 'backslash_caret: \\lalala => \\lalala 2nd' ) ;
18286 is( "lalala^\n", backslash_caret( "lalala\\\n" ), 'backslash_caret: lalala\\\n => lalala^\n' ) ;
18287 is( "lalala^\nlalala^\n", backslash_caret( "lalala\\\nlalala\\\n" ), 'backslash_caret: lalala\\\nlalala\\\n => lalala^\nlalala^\n' ) ;
18288 is( "lal\\ala^\nlalala^\n", backslash_caret( "lal\\ala\\\nlalala\\\n" ), 'backslash_caret: lal\\ala\\\nlalala\\\n => lal\\ala^\nlalala^\n' ) ;
18289
18290 note( 'Leaving tests_backslash_caret()' ) ;
18291 return ;
18292}
18293
18294sub backslash_caret
18295{
18296 my $string = shift ;
18297
18298 $string =~ s{\\ $ }{^}gxms ;
18299
18300 return $string ;
18301}
18302
18303sub tests_split_around_equal
18304{
18305 note( 'Entering tests_split_around_equal()' ) ;
18306
18307 is( undef, split_around_equal( ), 'split_around_equal: no args => undef' ) ;
18308 is_deeply( { toto => 'titi' }, { split_around_equal( 'toto=titi' ) }, 'split_around_equal: toto=titi => toto => titi' ) ;
18309 is_deeply( { A => 'B', C => 'D' }, { split_around_equal( 'A=B=C=D' ) }, 'split_around_equal: toto=titi => toto => titi' ) ;
18310 is_deeply( { A => 'B', C => 'D' }, { split_around_equal( 'A=B', 'C=D' ) }, 'split_around_equal: A=B C=D => A => B, C=>D' ) ;
18311
18312 note( 'Leaving tests_split_around_equal()' ) ;
18313 return ;
18314}
18315
18316sub split_around_equal
18317{
18318 if ( ! @ARG ) { return ; } ;
18319 return map { split /=/mxs, $_ } @ARG ;
18320
18321}
18322
18323
18324
18325sub tests_sig_install
18326{
18327 note( 'Entering tests_sig_install()' ) ;
18328
18329 my $mysync ;
18330 is( undef, sig_install( ), 'sig_install: no args => undef' ) ;
18331 is( undef, sig_install( $mysync ), 'sig_install: arg undef => undef' ) ;
18332 $mysync = { } ;
18333 is( undef, sig_install( $mysync ), 'sig_install: empty hash => undef' ) ;
18334
18335 SKIP: {
18336 Readonly my $SKIP_15 => 15 ;
18337 if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests only for Unix', $SKIP_15 ) ; }
18338 # Default to ignore USR1 USR2 in case future install fails
18339 local $SIG{ USR1 } = local $SIG{ USR2 } = sub { } ;
18340 kill( 'USR1', $PROCESS_ID ) ;
18341
18342 $mysync->{ debugsig } = 1 ;
18343 # Assign USR1 to call sub tototo
18344 # Surely a better value than undef should be returned when doing real signal stuff
18345 is( undef, sig_install( $mysync, 'tototo', 'USR1' ), 'sig_install: USR1 tototo' ) ;
18346
18347 is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 1' ) ;
18348 is( 1, $mysync->{ tototo_calls }, 'sig_install: tototo call nb 1' ) ;
18349
18350 #return ;
18351 # Assign USR2 to call sub tototo
18352 is( undef, sig_install( $mysync, 'tototo', 'USR2' ), 'sig_install: USR2 tototo' ) ;
18353
18354 is( 1, kill( 'USR2', $PROCESS_ID ), 'sig_install: kill USR2 myself 1' ) ;
18355 is( 2, $mysync->{ tototo_calls }, 'sig_install: tototo call nb 2' ) ;
18356
18357 is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 2' ) ;
18358 is( 3, $mysync->{ tototo_calls }, 'sig_install: tototo call nb 3' ) ;
18359
18360
18361 local $SIG{ USR1 } = local $SIG{ USR2 } = sub { } ;
18362 is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 3' ) ;
18363 is( 3, $mysync->{ tototo_calls }, 'sig_install: tototo call still nb 3' ) ;
18364
18365 # Assign USR1 + USR2 to call sub tototo
18366 is( undef, sig_install( $mysync, 'tototo', 'USR1', 'USR2' ), 'sig_install: USR1 USR2 tototo' ) ;
18367 is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 4' ) ;
18368 is( 4, $mysync->{ tototo_calls }, 'sig_install: tototo call now nb 4' ) ;
18369
18370 is( 1, kill( 'USR2', $PROCESS_ID ), 'sig_install: kill USR1 myself 2' ) ;
18371 is( 5, $mysync->{ tototo_calls }, 'sig_install: tototo call now nb 5' ) ;
18372 }
18373
18374
18375 note( 'Leaving tests_sig_install()' ) ;
18376 return ;
18377}
18378
18379
18380#
18381sub sig_install
18382{
18383 my $mysync = shift ;
18384 if ( ! $mysync ) { return ; }
18385 my $mysubname = shift ;
18386 if ( ! $mysubname ) { return ; }
18387
18388 if ( ! @ARG ) { return ; }
18389
18390 my @signals = @ARG ;
18391
18392 my $mysub = \&$mysubname ;
18393 #$mysync->{ debugsig } = 1 ;
18394 $mysync->{ debugsig } and myprint( "In sig_install with sub $mysubname and signal @ARG\n" ) ;
18395
18396 my $subsignal = sub {
18397 my $signame = shift ;
18398 $mysync->{ debugsig } and myprint( "In subsignal with $signame and $mysubname\n" ) ;
18399 &$mysub( $mysync, $signame ) ;
18400 } ;
18401
18402 foreach my $signal ( @signals ) {
18403 $mysync->{ debugsig } and myprint( "Installing signal $signal to call sub $mysubname\n") ;
18404 output( $mysync, "kill -$signal $PROCESS_ID # special behavior: call to sub $mysubname\n" ) ;
18405 ## no critic (RequireLocalizedPunctuationVars)
18406 $SIG{ $signal } = $subsignal ;
18407 }
18408 return ;
18409}
18410
18411
18412sub tototo
18413{
18414 my $mysync = shift ;
18415 myprint("In tototo with @ARG\n" ) ;
18416 $mysync->{ tototo_calls } += 1 ;
18417 return ;
18418}
18419
18420sub mygetppid
18421{
18422 if ( 'MSWin32' eq $OSNAME ) {
18423 return( 'unknown under MSWin32 (too complicated)' ) ;
18424 } else {
18425 # Unix
18426 return( getppid( ) ) ;
18427 }
18428}
18429
18430
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018431sub tests_toggle_sleep
18432{
18433 note( 'Entering tests_toggle_sleep()' ) ;
18434
18435 is( undef, toggle_sleep( ), 'toggle_sleep: no args => undef' ) ;
18436 my $mysync ;
18437 is( undef, toggle_sleep( $mysync ), 'toggle_sleep: undef => undef' ) ;
18438 $mysync = { } ;
18439 is( undef, toggle_sleep( $mysync ), 'toggle_sleep: no maxsleep => undef' ) ;
18440
18441 $mysync->{maxsleep} = 3 ;
18442 is( 0, toggle_sleep( $mysync ), 'toggle_sleep: 3 => 0' ) ;
18443
18444 is( $MAX_SLEEP, toggle_sleep( $mysync ), "toggle_sleep: 0 => $MAX_SLEEP" ) ;
18445 is( 0, toggle_sleep( $mysync ), "toggle_sleep: $MAX_SLEEP => 0" ) ;
18446 is( $MAX_SLEEP, toggle_sleep( $mysync ), "toggle_sleep: 0 => $MAX_SLEEP" ) ;
18447 is( 0, toggle_sleep( $mysync ), "toggle_sleep: $MAX_SLEEP => 0" ) ;
18448
18449 SKIP: {
18450 Readonly my $SKIP_9 => 9 ;
18451 if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests only for Unix', $SKIP_9 ) ; }
18452 # Default to ignore USR1 USR2 in case future install fails
18453 local $SIG{ USR1 } = sub { } ;
18454 kill( 'USR1', $PROCESS_ID ) ;
18455
18456 $mysync->{ debugsig } = 1 ;
18457 # Assign USR1 to call sub toggle_sleep
18458 is( undef, sig_install( $mysync, \&toggle_sleep, 'USR1' ), 'toggle_sleep: install USR1 toggle_sleep' ) ;
18459
18460
18461 $mysync->{maxsleep} = 4 ;
18462 is( 1, kill( 'USR1', $PROCESS_ID ), 'toggle_sleep: kill USR1 myself' ) ;
18463 is( 0, $mysync->{ maxsleep }, 'toggle_sleep: toggle_sleep called => sleeps are 0s' ) ;
18464
18465 is( 1, kill( 'USR1', $PROCESS_ID ), 'toggle_sleep: kill USR1 myself again' ) ;
18466 is( $MAX_SLEEP, $mysync->{ maxsleep }, "toggle_sleep: toggle_sleep called => sleeps are ${MAX_SLEEP}s" ) ;
18467
18468 is( 1, kill( 'USR1', $PROCESS_ID ), 'toggle_sleep: kill USR1 myself' ) ;
18469 is( 0, $mysync->{ maxsleep }, 'toggle_sleep: toggle_sleep called => sleeps are 0s' ) ;
18470
18471 is( 1, kill( 'USR1', $PROCESS_ID ), 'toggle_sleep: kill USR1 myself again' ) ;
18472 is( $MAX_SLEEP, $mysync->{ maxsleep }, "toggle_sleep: toggle_sleep called => sleeps are ${MAX_SLEEP}s" ) ;
18473 }
18474
18475 note( 'Leaving tests_toggle_sleep()' ) ;
18476 return ;
18477}
18478
18479
18480sub toggle_sleep
18481{
18482 my $mysync = shift ;
18483
18484 myprint("In toggle_sleep with @ARG\n" ) ;
18485
18486 if ( !defined( $mysync ) ) { return ; }
18487 if ( !defined( $mysync->{maxsleep} ) ) { return ; }
18488
18489 $mysync->{ maxsleep } = max( 0, $MAX_SLEEP - $mysync->{maxsleep} ) ;
18490 myprint("Resetting maxsleep to ", $mysync->{maxsleep}, "s\n" ) ;
18491 return $mysync->{maxsleep} ;
18492}
18493
18494sub mypod2usage
18495{
18496 my $fh_pod2usage = shift ;
18497
18498 pod2usage(
18499 -exitval => 'NOEXIT',
18500 -noperldoc => 1,
18501 -verbose => 99,
18502 -sections => [ qw(NAME VERSION USAGE OPTIONS) ],
18503 -indent => 1,
18504 -loose => 1,
18505 -output => $fh_pod2usage,
18506 ) ;
18507
18508 return ;
18509}
18510
18511sub usage
18512{
18513 my $mysync = shift ;
18514
18515 if ( ! defined $mysync ) { return ; }
18516
18517 my $usage = q{} ;
18518 my $usage_from_pod ;
18519 my $usage_footer = usage_footer( $mysync ) ;
18520
18521 # pod2usage writes on a filehandle only and I want a variable
18522 open my $fh_pod2usage, ">", \$usage_from_pod
18523 or do { warn $OS_ERROR ; return ; } ;
18524 mypod2usage( $fh_pod2usage ) ;
18525 close $fh_pod2usage ;
18526
18527 if ( 'MSWin32' eq $OSNAME ) {
18528 $usage_from_pod = backslash_caret( $usage_from_pod ) ;
18529 }
18530 $usage = join( q{}, $usage_from_pod, $usage_footer ) ;
18531
18532 return( $usage ) ;
18533}
18534
18535sub tests_usage
18536{
18537 note( 'Entering tests_usage()' ) ;
18538
18539 my $usage ;
18540 like( $usage = usage( $sync ), qr/Name:/, 'usage: contains Name:' ) ;
18541 myprint( $usage ) ;
18542 like( $usage, qr/Version:/, 'usage: contains Version:' ) ;
18543 like( $usage, qr/Usage:/, 'usage: contains Usage:' ) ;
18544 like( $usage, qr/imapsync/, 'usage: contains imapsync' ) ;
18545
18546 is( undef, usage( ), 'usage: no args => undef' ) ;
18547
18548 note( 'Leaving tests_usage()' ) ;
18549 return ;
18550}
18551
18552
18553sub usage_footer
18554{
18555 my $mysync = shift ;
18556
18557 my $footer = q{} ;
18558
18559 my $localhost_info = localhost_info( $mysync ) ;
18560 my $rcs = $mysync->{rcs} ;
18561 my $homepage = homepage( ) ;
18562
18563 my $imapsync_release = $STR_use_releasecheck ;
18564
18565 if ( $mysync->{releasecheck} ) {
18566 $imapsync_release = check_last_release( ) ;
18567 }
18568
18569 $footer = qq{$localhost_info
18570$rcs
18571$imapsync_release
18572$homepage
18573} ;
18574 return( $footer ) ;
18575}
18576
18577
18578
18579sub usage_complete
18580{
18581 # Unused, I guess this function could be deleted
18582 my $usage = <<'EOF' ;
18583--skipheader reg : Don't take into account header keyword
18584 matching reg ex: --skipheader 'X.*'
18585
18586--skipsize : Don't take message size into account to compare
18587 messages on both sides. On by default.
18588 Use --no-skipsize for using size comparaison.
18589--allowsizemismatch : allow RFC822.SIZE != fetched msg size
18590 consider also --skipsize to avoid duplicate messages
18591 when running syncs more than one time per mailbox
18592
18593--reconnectretry1 int : reconnect to host1 if connection is lost up to
18594 int times per imap command (default is 3)
18595--reconnectretry2 int : same as --reconnectretry1 but for host2
18596--split1 int : split the requests in several parts on host1.
18597 int is the number of messages handled per request.
18598 default is like --split1 100.
18599--split2 int : same thing on host2.
18600--nofixInboxINBOX : Don't fix Inbox INBOX mapping.
18601EOF
18602 return( $usage ) ;
18603}
18604
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018605
18606
18607
18608sub setvalfromcgikey
18609{
18610 my ( $mysync, $mycgi, $key, $val ) = @ARG ;
18611
18612 my $badthings = 0 ;
18613
18614
18615 my ( $name, $type, $struct ) ;
18616 if ( $key !~ m/^([\w\d\|]+)([=:][isf])?([\+!\@\%])?$/mxs )
18617 {
18618 $badthings++ ;
18619 next ; # Unknown item
18620 }
18621 else
18622 {
18623 $name = [ split '|', $1, 1 ]->[0] ; # option name ab|cd|ef => keep only ab
18624 $type = $2 ; # = or : followed by i or s or f
18625 $struct = $3 ; # + or ! or @ or %
18626 }
18627
18628 if ( ( $struct || q{} ) eq '+' )
18629 {
18630 ${$val} = $mycgi->param( $name ) ; # "Incremental" integer
18631 }
18632 elsif ( $type )
18633 {
18634 my @values = $mycgi->multi_param( $name ) ;
18635
18636 #myprint( "type[$type]values[@values]\$struct[", $struct || q{}, "]val[$val]ref(val)[", ref($val), "]\n" ) ;
18637 if ( ( $struct || q{} ) eq '%' or ref( $val ) eq 'HASH' )
18638 {
18639 setvalfromhash( $val, $type, @values ) ;
18640 }
18641 else
18642 {
18643 setvalfromlist( $mysync, $val, $name, $type, $struct, @values ) ;
18644 }
18645 }
18646 else
18647 {
18648 setvalfromcheckbox( $mysync, $mycgi, $key, $name, $val ) ;
18649 }
18650
18651 return $badthings ;
18652}
18653
18654sub setvalfromlist
18655{
18656 my ( $mysync, $val, $name, $type, $struct, @values ) = @ARG ;
18657 if ( $type =~ m/i$/mxs )
18658 {
18659 @values = map { q{} ne $_ ? int $_ : undef } @values ;
18660 }
18661 elsif ( $type =~ m/f$/mxs )
18662 {
18663 @values = map { 0 + $_ } @values ;
18664 }
18665
18666 if ( ( $struct || q{} ) eq '@' )
18667 {
18668 @{ ${$val} } = @values ;
18669 my @option = map { +( "--$name", "$_" ) } @values ;
18670 push @{ $mysync->{ cmdcgi } }, @option ;
18671 }
18672 elsif ( ref( $val ) eq 'ARRAY' )
18673 {
18674 @{$val} = @values ;
18675 }
18676 elsif ( my $value = $values[0] )
18677 {
18678 ${$val} = $value ;
18679 push @{ $mysync->{ cmdcgi } }, "--$name", $value ;
18680 }
18681 else
18682 {
18683 }
18684
18685 return ;
18686}
18687sub setvalfromhash
18688{
18689 my ( $val, $type, @values ) = @ARG ;
18690
18691 my %values = map { split /=/mxs, $_ } @values ;
18692
18693 if ( $type =~ m/i$/mxs )
18694 {
18695 foreach my $k ( keys %values )
18696 {
18697 $values{$k} = int $values{$k} ;
18698 }
18699 }
18700 elsif ( $type =~ m/f$/mxs )
18701 {
18702 foreach my $k ( keys %values ) {
18703 $values{$k} = 0 + $values{$k};
18704 }
18705 }
18706
18707 if ( 'REF' eq ref $val )
18708 {
18709 %{ ${$val} } = %values ;
18710 }
18711 else
18712 {
18713 %{$val} = %values ;
18714 }
18715
18716 return ;
18717}
18718
18719
18720sub setvalfromcheckbox
18721{
18722 my ( $mysync, $mycgi, $key, $name, $val ) = @ARG ;
18723
18724 # Checkbox
18725 # --noname is set by name=0 or name=
18726 my $value = $mycgi->param( $name ) ;
18727 if ( defined $value )
18728 {
18729 ${$val} = $value ;
18730 if ( $value )
18731 {
18732 push @{ $mysync->{ cmdcgi } }, "--$name" ;
18733 }
18734 else
18735 {
18736 push @{ $mysync->{ cmdcgi } }, "--no$name" ;
18737 }
18738 }
18739 else
18740 {
18741 ${$val} = undef ;
18742 }
18743 return ;
18744}
18745
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018746sub myGetOptions
18747{
18748
18749 # Started as a copy of Luke Ross Getopt::Long::CGI
18750 # https://metacpan.org/release/Getopt-Long-CGI
18751 # So this sub function is under the same license as Getopt-Long-CGI Luke Ross wants it,
18752 # which was Perl 5.6 or later licenses at the date of the copy.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018753 # It also applies for the sub functions called from this one.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018754
18755 my $mysync = shift @ARG ;
18756 my $arguments_ref = shift @ARG ;
18757 my %options = @ARG ;
18758
18759 my $mycgi = $mysync->{cgi} ;
18760
18761 if ( not under_cgi_context() ) {
18762
18763 # Not CGI - pass upstream for normal command line handling
18764 return Getopt::Long::GetOptionsFromArray( $arguments_ref, %options ) ;
18765 }
18766
18767 # We must be in CGI context now
18768 if ( ! defined( $mycgi ) ) { return ; }
18769
18770 my $badthings = 0 ;
18771 foreach my $key ( sort keys %options ) {
18772 my $val = $options{$key} ;
18773
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018774 $badthings += setvalfromcgikey( $mysync, $mycgi, $key, $val ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018775
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018776 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018777
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018778 if ( $badthings ) {
18779 return ; # undef or ()
18780 }
18781 else {
18782 return ( 1 ) ;
18783 }
18784}
18785
18786
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018787
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018788
18789sub tests_get_options_cgi_context
18790{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018791 note( 'Entering tests_get_options_cgi_context()' ) ;
18792
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018793
18794# Temporary, have to think harder about testing CGI context in command line --tests
18795 # API:
18796 # * input arguments: two ways, command line or CGI
18797 # * the program arguments
18798 # * QUERY_STRING env variable
18799 # * return
18800 # * QUERY_STRING length
18801
18802 # CGI context
18803 local $ENV{SERVER_SOFTWARE} = 'Votre serviteur' ;
18804
18805 # Real full test
18806 # = 'host1=test1.lamiral.info&user1=test1&password1=secret1&host2=test2.lamiral.info&user2=test2&password2=secret2&debugenv=on'
18807 my $mysync ;
18808 is( undef, get_options( $mysync ), 'get_options cgi context: no CGI module => undef' ) ;
18809
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018810 # skip all next tests if the CGI module is not available
18811
18812 SKIP: {
18813 if ( ! eval { require CGI ; } ) {
18814 skip( "CGI Perl module is not installed", 19 ) ;
18815 }
18816
18817 CGI->import( qw( -no_debug -utf8 ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018818
18819 is( undef, get_options( $mysync ), 'get_options cgi context: no CGI param => undef' ) ;
18820 # Testing boolean
18821 $mysync->{cgi} = CGI->new( 'version=on&debugenv=on' ) ;
18822 local $ENV{'QUERY_STRING'} = 'version=on&debugenv=on' ;
18823 is( 22, get_options( $mysync ), 'get_options cgi context: QUERY_STRING => 22' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018824 is( 'on', $mysync->{ version }, 'get_options cgi context: --version => on' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018825 # debugenv is not allowed in cgi context
18826 is( undef, $mysync->{debugenv}, 'get_options cgi context: $mysync->{debugenv} => undef' ) ;
18827
18828 # QUERY_STRING in this test is only for return value of get_options
18829 # Have to think harder, GET/POST context, is this return value a good thing?
18830 local $ENV{'QUERY_STRING'} = 'host1=test1.lamiral.info&user1=test1' ;
18831 $mysync->{cgi} = CGI->new( 'host1=test1.lamiral.info&user1=test1' ) ;
18832 is( 36, get_options( $mysync, ), 'get_options cgi context: QUERY_STRING => 36' ) ;
18833 is( 'test1', $mysync->{user1}, 'get_options cgi context: $mysync->{user1} => test1' ) ;
18834 #local $ENV{'QUERY_STRING'} = undef ;
18835
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018836 # Testing s@ as ref
18837 $mysync->{cgi} = CGI->new( 'folder=fd1' ) ;
18838 get_options( $mysync ) ;
18839 is_deeply( [ 'fd1' ], $mysync->{ folder }, 'get_options cgi context: $mysync->{ folder } => fd1' ) ;
18840 $mysync->{cgi} = CGI->new( 'folder=fd1&folder=fd2' ) ;
18841 get_options( $mysync ) ;
18842 is_deeply( [ 'fd1', 'fd2' ], $mysync->{ folder }, 'get_options cgi context: $mysync->{ folder } => fd1, fd2' ) ;
18843
18844 # Testing %
18845 $mysync->{cgi} = CGI->new( 'f1f2h=s1=d1&f1f2h=s2=d2&f1f2h=s3=d3' ) ;
18846 get_options( $mysync ) ;
18847
18848 is_deeply( { 's1' => 'd1', 's2' => 'd2', 's3' => 'd3' },
18849 $mysync->{f1f2h}, 'get_options cgi context: f1f2h => s1=d1 s2=d2 s3=d3' ) ;
18850
18851 # Testing boolean ! with --noxxx, doesnot work
18852 $mysync->{cgi} = CGI->new( 'nodry=on' ) ;
18853 get_options( $mysync ) ;
18854 is( undef, $mysync->{dry}, 'get_options cgi context: --nodry => $mysync->{dry} => undef' ) ;
18855
18856 $mysync->{cgi} = CGI->new( 'host1=example.com' ) ;
18857 get_options( $mysync ) ;
18858 is( 'example.com', $mysync->{host1}, 'get_options cgi context: --host1=example.com => $mysync->{host1} => example.com' ) ;
18859
18860 #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
18861 $mysync->{cgi} = CGI->new( 'simulong=' ) ;
18862 get_options( $mysync ) ;
18863 is( undef, $mysync->{simulong}, 'get_options cgi context: --simulong= => $mysync->{simulong} => undef' ) ;
18864
18865 $mysync->{cgi} = CGI->new( 'simulong' ) ;
18866 get_options( $mysync ) ;
18867 is( undef, $mysync->{simulong}, 'get_options cgi context: --simulong => $mysync->{simulong} => undef' ) ;
18868
18869 $mysync->{cgi} = CGI->new( 'simulong=4' ) ;
18870 get_options( $mysync ) ;
18871 is( 4, $mysync->{simulong}, 'get_options cgi context: --simulong=4 => $mysync->{simulong} => 4' ) ;
18872 is( undef, $mysync->{ folder }, 'get_options cgi context: --simulong=4 => $mysync->{ folder } => undef' ) ;
18873 #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
18874
18875 $mysync ={} ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018876 $mysync->{cgi} = CGI->new( 'testslive=on' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018877 get_options( $mysync ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018878 is( 'on', $mysync->{ testslive }, 'get_options cgi context: --testslive=on => testslive => on' ) ;
18879 #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
18880
18881 $mysync ={} ;
18882 $mysync->{cgi} = CGI->new( 'log=0' ) ;
18883 get_options( $mysync ) ;
18884 is( 0, $mysync->{ log }, 'get_options cgi context: --log=0 => log => 0' ) ;
18885 #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
18886
18887
18888 # What is this fucked up indentation?
18889 }
18890
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018891
18892 note( 'Leaving tests_get_options_cgi_context()' ) ;
18893 return ;
18894}
18895
18896
18897
18898sub get_options_cgi
18899{
18900 # In CGI context arguments are not in @ARGV but in QUERY_STRING variable (with GET).
18901 my $mysync = shift @ARG ;
18902 $mysync->{cgi} || return ;
18903 my @arguments = @ARG ;
18904 # final 0 is used to print usage when no option is given
18905 my $numopt = length $ENV{'QUERY_STRING'} || 1 ;
18906 $mysync->{f1f2h} = {} ;
18907 my $opt_ret = myGetOptions(
18908 $mysync,
18909 \@arguments,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018910 'abort' => \$mysync->{ abort },
18911 'abortbyfile' => \$mysync->{ abortbyfile },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018912 'host1=s' => \$mysync->{ host1 },
18913 'host2=s' => \$mysync->{ host2 },
18914 'user1=s' => \$mysync->{ user1 },
18915 'user2=s' => \$mysync->{ user2 },
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018916 'password1=s' => \$mysync->{ password1 },
18917 'password2=s' => \$mysync->{ password2 },
18918 'dry!' => \$mysync->{ dry },
18919 'dry1!' => \$mysync->{ dry1 },
18920 'version' => \$mysync->{ version },
18921 'ssl1!' => \$mysync->{ ssl1 },
18922 'ssl2!' => \$mysync->{ ssl2 },
18923 'tls1!' => \$mysync->{ tls1 },
18924 'tls2!' => \$mysync->{ tls2 },
18925 'justbanner!' => \$mysync->{ justbanner },
18926 'justlogin!' => \$mysync->{ justlogin },
18927 'justconnect!' => \$mysync->{ justconnect },
18928 'addheader!' => \$mysync->{ addheader },
18929 'automap!' => \$mysync->{ automap },
18930 'justautomap!' => \$mysync->{ justautomap },
18931 'gmail1' => \$mysync->{ gmail1 },
18932 'gmail2' => \$mysync->{ gmail2 },
18933 'office1' => \$mysync->{ office1 },
18934 'office2' => \$mysync->{ office2 },
18935 'exchange1' => \$mysync->{ exchange1 },
18936 'exchange2' => \$mysync->{ exchange2 },
18937 'domino1' => \$mysync->{ domino1 },
18938 'domino2' => \$mysync->{ domino2 },
18939 'f1f2=s@' => \$mysync->{ f1f2 },
18940 'f1f2h=s%' => \$mysync->{ f1f2h },
18941 'folder=s@' => \$mysync->{ folder },
18942 'testslive!' => \$mysync->{ testslive },
18943 'testslive6!' => \$mysync->{ testslive6 },
18944 'releasecheck!' => \$mysync->{ releasecheck },
18945 'simulong=f' => \$mysync->{ simulong },
18946 'debugsleep=f' => \$mysync->{ debugsleep },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018947 'subfolder1=s' => \$mysync->{ subfolder1 },
18948 'subfolder2=s' => \$mysync->{ subfolder2 },
18949 'justfolders!' => \$mysync->{ justfolders },
18950 'justfoldersizes!' => \$mysync->{ justfoldersizes },
18951 'delete1!' => \$mysync->{ delete1 },
18952 'delete2!' => \$mysync->{ delete2 },
18953 'delete2duplicates!' => \$mysync->{ delete2duplicates },
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018954 'tail!' => \$mysync->{ tail },
18955 'tmphash=s' => \$mysync->{ tmphash },
18956 'exitwhenover=i' => \$mysync->{ exitwhenover },
18957 'syncduplicates!' => \$mysync->{ syncduplicates },
18958 'log!' => \$mysync->{ log },
18959 'loglogfile!' => \$mysync->{ loglogfile },
18960
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018961
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018962# f1f2h=s% could be removed but
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018963# tests_get_options_cgi() should be split before
18964# with a sub tests_myGetOptions()
18965 ) ;
18966
18967 $mysync->{ debug } and output( $mysync, "get options: [$opt_ret][$numopt]\n" ) ;
18968
18969 if ( ! $opt_ret ) {
18970 return ;
18971 }
18972 return $numopt ;
18973}
18974
18975sub get_options_cmd
18976{
18977 my $mysync = shift @ARG ;
18978 my @arguments = @ARG ;
18979 my $mycgi = $mysync->{cgi} ;
18980 # final 0 is used to print usage when no option is given on command line
18981 my $numopt = scalar @arguments || 0 ;
18982 my $argv = join "\x00", @arguments ;
18983
18984 if ( $argv =~ m/-delete\x002/x ) {
18985 output( $mysync, "May be you mean --delete2 instead of --delete 2\n" ) ;
18986 return ;
18987 }
18988 $mysync->{f1f2h} = {} ;
18989 my $opt_ret = myGetOptions(
18990 $mysync,
18991 \@arguments,
18992 'debug!' => \$mysync->{ debug },
18993 'debuglist!' => \$debuglist,
18994 'debugcontent!' => \$debugcontent,
18995 'debugsleep=f' => \$mysync->{debugsleep},
18996 'debugflags!' => \$debugflags,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018997 'debugimap!' => \$mysync->{ debugimap },
18998 'debugimap1!' => \$mysync->{ acc1 }->{ debugimap },
18999 'debugimap2!' => \$mysync->{ acc2 }->{ debugimap },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019000 'debugdev!' => \$debugdev,
19001 'debugmemory!' => \$mysync->{debugmemory},
19002 'debugfolders!' => \$mysync->{debugfolders},
19003 'debugssl=i' => \$mysync->{debugssl},
19004 'debugcgi!' => \$debugcgi,
19005 'debugenv!' => \$mysync->{debugenv},
19006 'debugsig!' => \$mysync->{debugsig},
19007 'debuglabels!' => \$mysync->{debuglabels},
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019008
19009 'simulong=f' => \$mysync->{simulong},
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019010 'abort' => \$mysync->{abort},
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019011 'abortbyfile' => \$mysync->{abortbyfile},
19012
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019013 'host1=s' => \$mysync->{ host1 },
19014 'host2=s' => \$mysync->{ host2 },
19015 'port1=i' => \$mysync->{port1},
19016 'port2=i' => \$mysync->{port2},
19017 'inet4|ipv4' => \$mysync->{inet4},
19018 'inet6|ipv6' => \$mysync->{inet6},
19019 'user1=s' => \$mysync->{ user1 },
19020 'user2=s' => \$mysync->{ user2 },
19021 'gmail1' => \$mysync->{gmail1},
19022 'gmail2' => \$mysync->{gmail2},
19023 'office1' => \$mysync->{office1},
19024 'office2' => \$mysync->{office2},
19025 'exchange1' => \$mysync->{exchange1},
19026 'exchange2' => \$mysync->{exchange2},
19027 'domino1' => \$mysync->{domino1},
19028 'domino2' => \$mysync->{domino2},
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019029 'domain1=s' => \$mysync->{ acc1 }->{ domain },
19030 'domain2=s' => \$mysync->{ acc2 }->{ domain },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019031 'password1=s' => \$mysync->{password1},
19032 'password2=s' => \$mysync->{password2},
19033 'passfile1=s' => \$mysync->{ passfile1 },
19034 'passfile2=s' => \$mysync->{ passfile2 },
19035 'authmd5!' => \$authmd5,
19036 'authmd51!' => \$authmd51,
19037 'authmd52!' => \$authmd52,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019038
19039 'trylogin!' => \$mysync->{ trylogin },
19040
19041 'oauthdirect1=s' => \$mysync->{ acc1 }->{ oauthdirect },
19042 'oauthdirect2=s' => \$mysync->{ acc2 }->{ oauthdirect },
19043
19044 'oauthaccesstoken1=s' => \$mysync->{ acc1 }->{ oauthaccesstoken },
19045 'oauthaccesstoken2=s' => \$mysync->{ acc2 }->{ oauthaccesstoken },
19046
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019047 'sep1=s' => \$mysync->{ sep1 },
19048 'sep2=s' => \$mysync->{ sep2 },
19049 'sanitize!' => \$mysync->{ sanitize },
19050 'folder=s@' => \$mysync->{ folder },
19051 'folderrec=s' => \@folderrec,
19052 'include=s' => \@include,
19053 'exclude=s' => \@exclude,
19054 'noexclude' => \$mysync->{noexclude},
19055 'folderfirst=s' => \@folderfirst,
19056 'folderlast=s' => \@folderlast,
19057 'prefix1=s' => \$prefix1,
19058 'prefix2=s' => \$prefix2,
19059 'subfolder1=s' => \$mysync->{ subfolder1 },
19060 'subfolder2=s' => \$mysync->{ subfolder2 },
19061 'fixslash2!' => \$mysync->{ fixslash2 },
19062 'fixInboxINBOX!' => \$fixInboxINBOX,
19063 'regextrans2=s@' => \$mysync->{ regextrans2 },
19064 'mixfolders!' => \$mixfolders,
19065 'skipemptyfolders!' => \$mysync->{ skipemptyfolders },
19066 'regexmess=s' => \@regexmess,
19067 'noregexmess' => \$mysync->{noregexmess},
19068 'skipmess=s' => \@skipmess,
19069 'pipemess=s' => \@pipemess,
19070 'pipemesscheck!' => \$pipemesscheck,
19071 'disarmreadreceipts!' => \$disarmreadreceipts,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019072 'regexflag=s@' => \$mysync->{ regexflag },
19073 'noregexflag' => \$mysync->{ noregexflag },
19074 'filterflags!' => \$mysync->{ filterflags },
19075 'filterbuggyflags!' => \$mysync->{ filterbuggyflags },
19076 'flagscase!' => \$mysync->{ flagscase },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019077 'syncflagsaftercopy!' => \$syncflagsaftercopy,
19078 'resyncflags!' => \$mysync->{ resyncflags },
19079 'synclabels!' => \$mysync->{ synclabels },
19080 'resynclabels!' => \$mysync->{ resynclabels },
19081 'delete|delete1!' => \$mysync->{ delete1 },
19082 'delete2!' => \$mysync->{ delete2 },
19083 'delete2duplicates!' => \$mysync->{ delete2duplicates },
19084 'delete2folders!' => \$delete2folders,
19085 'delete2foldersonly=s' => \$delete2foldersonly,
19086 'delete2foldersbutnot=s' => \$delete2foldersbutnot,
19087 'syncinternaldates!' => \$syncinternaldates,
19088 'idatefromheader!' => \$idatefromheader,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019089 'syncacls!' => \$mysync->{ syncacls },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019090 'maxsize=i' => \$mysync->{ maxsize },
19091 'appendlimit=i' => \$mysync->{ appendlimit },
19092 'truncmess=i' => \$mysync->{ truncmess },
19093 'minsize=i' => \$minsize,
19094 'maxage=f' => \$maxage,
19095 'minage=f' => \$minage,
19096 'search=s' => \$search,
19097 'search1=s' => \$mysync->{ search1 },
19098 'search2=s' => \$mysync->{ search2 },
19099 'foldersizes!' => \$mysync->{ foldersizes },
19100 'foldersizesatend!' => \$mysync->{ foldersizesatend },
19101 'dry!' => \$mysync->{dry},
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019102 'dry1!' => \$mysync->{dry1},
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019103 'expunge1|expunge!' => \$mysync->{ expunge1 },
19104 'expunge2!' => \$mysync->{ expunge2 },
19105 'uidexpunge2!' => \$mysync->{ uidexpunge2 },
19106 'subscribed' => \$subscribed,
19107 'subscribe!' => \$subscribe,
19108 'subscribeall|subscribe_all!' => \$subscribeall,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019109 'justbanner!' => \$mysync->{ justbanner },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019110 'justfolders!'=> \$mysync->{ justfolders },
19111 'justfoldersizes!' => \$mysync->{ justfoldersizes },
19112 'fast!' => \$fast,
19113 'version' => \$mysync->{version},
19114 'help' => \$help,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019115 'timeout=f' => \$mysync->{timeout},
19116 'timeout1=f' => \$mysync->{ acc1 }->{timeout},
19117 'timeout2=f' => \$mysync->{ acc2 }->{timeout},
19118 'skipheader=s' => \$mysync->{ skipheader },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019119 'useheader=s' => \@useheader,
19120 'wholeheaderifneeded!' => \$wholeheaderifneeded,
19121 'messageidnodomain!' => \$messageidnodomain,
19122 'skipsize!' => \$skipsize,
19123 'allowsizemismatch!' => \$allowsizemismatch,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019124 'fastio1!' => \$mysync->{ acc1 }->{ fastio },
19125 'fastio2!' => \$mysync->{ acc2 }->{ fastio },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019126 'sslcheck!' => \$mysync->{sslcheck},
19127 'ssl1!' => \$mysync->{ssl1},
19128 'ssl2!' => \$mysync->{ssl2},
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019129 'ssl1_ssl_version=s' => \$mysync->{ acc1 }->{sslargs}->{SSL_version},
19130 'ssl2_ssl_version=s' => \$mysync->{ acc2 }->{sslargs}->{SSL_version},
19131 'sslargs1=s%' => \$mysync->{ acc1 }->{sslargs},
19132 'sslargs2=s%' => \$mysync->{ acc2 }->{sslargs},
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019133 'tls1!' => \$mysync->{tls1},
19134 'tls2!' => \$mysync->{tls2},
19135 'uid1!' => \$uid1,
19136 'uid2!' => \$uid2,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019137 'authmech1=s' => \$mysync->{ acc1 }->{ authmech },
19138 'authmech2=s' => \$mysync->{ acc2 }->{ authmech },
19139 'authuser1=s' => \$mysync->{ acc1 }->{ authuser },
19140 'authuser2=s' => \$mysync->{ acc2 }->{ authuser },
19141 'proxyauth1' => \$mysync->{ acc1 }->{ proxyauth },
19142 'proxyauth2' => \$mysync->{ acc2 }->{ proxyauth },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019143 'split1=i' => \$split1,
19144 'split2=i' => \$split2,
19145 'buffersize=i' => \$buffersize,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019146 'reconnectretry1=i' => \$mysync->{ acc1 }->{ reconnectretry },
19147 'reconnectretry2=i' => \$mysync->{ acc2 }->{ reconnectretry },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019148 'tests!' => \$mysync->{ tests },
19149 'testsdebug|tests_debug!' => \$mysync->{ testsdebug },
19150 'testsunit=s@' => \$mysync->{testsunit},
19151 'testslive!' => \$mysync->{testslive},
19152 'testslive6!' => \$mysync->{testslive6},
19153 'justlogin!' => \$mysync->{justlogin},
19154 'justconnect!' => \$mysync->{justconnect},
19155 'tmpdir=s' => \$mysync->{ tmpdir },
19156 'pidfile=s' => \$mysync->{pidfile},
19157 'pidfilelocking!' => \$mysync->{pidfilelocking},
19158 'sigexit=s@' => \$mysync->{ sigexit },
19159 'sigreconnect=s@' => \$mysync->{ sigreconnect },
19160 'sigignore=s@' => \$mysync->{ sigignore },
19161 'releasecheck!' => \$mysync->{releasecheck},
19162 'modulesversion|modules_version!' => \$modulesversion,
19163 'usecache!' => \$usecache,
19164 'cacheaftercopy!' => \$cacheaftercopy,
19165 'debugcache!' => \$debugcache,
19166 'useuid!' => \$useuid,
19167 'addheader!' => \$mysync->{addheader},
19168 'exitwhenover=i' => \$mysync->{ exitwhenover },
19169 'checkselectable!' => \$mysync->{ checkselectable },
19170 'checkfoldersexist!' => \$mysync->{ checkfoldersexist },
19171 'checkmessageexists!' => \$checkmessageexists,
19172 'expungeaftereach!' => \$mysync->{ expungeaftereach },
19173 'abletosearch!' => \$mysync->{abletosearch},
19174 'abletosearch1!' => \$mysync->{abletosearch1},
19175 'abletosearch2!' => \$mysync->{abletosearch2},
19176 'showpasswords!' => \$mysync->{showpasswords},
19177 'maxlinelength=i' => \$maxlinelength,
19178 'maxlinelengthcmd=s' => \$maxlinelengthcmd,
19179 'minmaxlinelength=i' => \$minmaxlinelength,
19180 'debugmaxlinelength!' => \$debugmaxlinelength,
19181 'fixcolonbug!' => \$fixcolonbug,
19182 'create_folder_old!' => \$create_folder_old,
19183 'maxmessagespersecond=f' => \$mysync->{maxmessagespersecond},
19184 'maxbytespersecond=i' => \$mysync->{maxbytespersecond},
19185 'maxbytesafter=i' => \$mysync->{maxbytesafter},
19186 'maxsleep=f' => \$mysync->{maxsleep},
19187 'skipcrossduplicates!' => \$skipcrossduplicates,
19188 'debugcrossduplicates!' => \$debugcrossduplicates,
19189 'log!' => \$mysync->{log},
19190 'tail!' => \$mysync->{tail},
19191 'logfile=s' => \$mysync->{logfile},
19192 'logdir=s' => \$mysync->{logdir},
19193 'errorsmax=i' => \$mysync->{errorsmax},
19194 'errorsdump!' => \$mysync->{errorsdump},
19195 'fetch_hash_set=s' => \$fetch_hash_set,
19196 'automap!' => \$mysync->{automap},
19197 'justautomap!' => \$mysync->{justautomap},
19198 'id!' => \$mysync->{id},
19199 'f1f2=s@' => \$mysync->{f1f2},
19200 'nof1f2' => \$mysync->{nof1f2},
19201 'f1f2h=s%' => \$mysync->{f1f2h},
19202 'justfolderlists!' => \$mysync->{justfolderlists},
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019203 'delete1emptyfolders' => \$mysync->{delete1emptyfolders},
19204 'checknoabletosearch!' => \$mysync->{checknoabletosearch},
19205 'syncduplicates!' => \$mysync->{ syncduplicates },
19206 'dockercontext!' => \$mysync->{ dockercontext },
19207
19208
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019209 ) ;
19210 #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
19211 $mysync->{ debug } and output( $mysync, "get options: [$opt_ret][$numopt]\n" ) ;
19212 my $numopt_after = scalar @arguments ;
19213 #myprint( "get options: [$opt_ret][$numopt][$numopt_after]\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019214
19215 # The $arguments[0] test is just because parallel adds "" when it is
19216 # used with {=7=} in sync_parallel_unix.sh
19217 if ( $numopt_after and $arguments[0] ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019218 myprint(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019219 "Found ", scalar( @arguments ), " extra arguments : [@arguments]\n",
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019220 "It usually means a quoting issue in the command line ",
19221 "or some misspelling options.\n",
19222 ) ;
19223 return ;
19224 }
19225 if ( ! $opt_ret ) {
19226 return ;
19227 }
19228 return $numopt ;
19229}
19230
19231
19232
19233sub tests_get_options
19234{
19235 note( 'Entering tests_get_options()' ) ;
19236
19237 # CAVEAT: still setting global variables, be careful
19238 # with tests, the context increases! $debug stays on for example.
19239 # API:
19240 # * input arguments: two ways, command line or CGI
19241 # * the program arguments
19242 # * QUERY_STRING env variable
19243 # * return
19244 # * undef if bad things happened like
19245 # * options not known
19246 # * --delete 2 input
19247 # * number of arguments or QUERY_STRING length
19248 my $mysync = { } ;
19249 is( undef, get_options( $mysync, qw( --noexist ) ), 'get_options: --noexist => undef' ) ;
19250 is( undef, $mysync->{ noexist }, 'get_options: --noexist => undef' ) ;
19251 $mysync = { } ;
19252 is( undef, get_options( $mysync, qw( --lalala --noexist --version ) ), 'get_options: --lalala --noexist --version => undef' ) ;
19253 is( 1, $mysync->{ version }, 'get_options: --version => 1' ) ;
19254 is( undef, $mysync->{ noexist }, 'get_options: --noexist => undef' ) ;
19255 $mysync = { } ;
19256 is( 1, get_options( $mysync, qw( --delete2 ) ), 'get_options: --delete2 => 1' ) ;
19257 is( 1, $mysync->{ delete2 }, 'get_options: --delete2 => var delete2 = 1' ) ;
19258 $mysync = { } ;
19259 is( undef, get_options( $mysync, qw( --delete 2 ) ), 'get_options: --delete 2 => var undef' ) ;
19260 is( undef, $mysync->{ delete1 }, 'get_options: --delete 2 => var still undef ; good!' ) ;
19261 $mysync = { } ;
19262 is( undef, get_options( $mysync, "--delete 2" ), 'get_options: --delete 2 => undef' ) ;
19263
19264 is( 1, get_options( $mysync, "--version" ), 'get_options: --version => 1' ) ;
19265 is( 1, get_options( $mysync, "--help" ), 'get_options: --help => 1' ) ;
19266
19267 is( undef, get_options( $mysync, qw( --noexist --version ) ), 'get_options: --debug --noexist --version => undef' ) ;
19268 is( 1, get_options( $mysync, qw( --version ) ), 'get_options: --version => 1' ) ;
19269 is( undef, get_options( $mysync, qw( extra ) ), 'get_options: extra => undef' ) ;
19270 is( undef, get_options( $mysync, qw( extra1 --version extra2 ) ), 'get_options: extra1 --version extra2 => undef' ) ;
19271
19272 $mysync = { } ;
19273 is( 2, get_options( $mysync, qw( --host1 HOST_01) ), 'get_options: --host1 HOST_01 => 1' ) ;
19274 is( 'HOST_01', $mysync->{ host1 }, 'get_options: --host1 HOST_01 => HOST_01' ) ;
19275 #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
19276
19277 note( 'Leaving tests_get_options()' ) ;
19278 return ;
19279}
19280
19281
19282
19283sub get_options
19284{
19285 my $mysync = shift @ARG ;
19286 my @arguments = @ARG ;
19287 #myprint( "1 mysync: ", Data::Dumper->Dump( [ $mysync ] ) ) ;
19288 my $ret ;
19289 if ( under_cgi_context( ) ) {
19290 # CGI context
19291 $ret = get_options_cgi( $mysync, @arguments ) ;
19292 }else{
19293 # Command line context ;
19294 $ret = get_options_cmd( $mysync, @arguments ) ;
19295 } ;
19296 #myprint( "2 mysync: ", Data::Dumper->Dump( [ $mysync ] ) ) ;
19297 foreach my $key ( sort keys %{ $mysync } ) {
19298 if ( ! defined $mysync->{$key} ) {
19299 delete $mysync->{$key} ;
19300 next ;
19301 }
19302 if ( 'ARRAY' eq ref( $mysync->{$key} )
19303 and 0 == scalar( @{ $mysync->{$key} } ) ) {
19304 delete $mysync->{$key} ;
19305 }
19306 }
19307 return $ret ;
19308}
19309
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019310
19311sub condition_to_leave_after_tests
19312{
19313 my $mysync = shift ;
19314 if ( $mysync->{ testslive } or $mysync->{ testslive6 } )
19315 {
19316 return 0 ;
19317 }
19318
19319 if ( $mysync->{ tests }
19320 or $mysync->{ testsdebug }
19321 or $mysync->{ testsunit }
19322 )
19323 {
19324 return 1 ;
19325 }
19326}
19327
19328
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019329sub testunitsession
19330{
19331 my $mysync = shift ;
19332
19333 if ( ! $mysync ) { return ; }
19334 if ( ! $mysync->{ testsunit } ) { return ; }
19335
19336 my @functions = @{ $mysync->{ testsunit } } ;
19337
19338 if ( ! @functions ) { return ; }
19339
19340 SKIP: {
19341 if ( ! @functions ) { skip 'No test in normal run' ; }
19342 testsunit( @functions ) ;
19343 done_testing( ) ;
19344 }
19345 return ;
19346}
19347
19348sub tests_count_0s
19349{
19350 note( 'Entering tests_count_zeros()' ) ;
19351 is( 0, count_0s( ), 'count_0s: no parameters => 0' ) ;
19352 is( 1, count_0s( 0 ), 'count_0s: 0 => 1' ) ;
19353 is( 0, count_0s( 1 ), 'count_0s: 1 => 0' ) ;
19354 is( 1, count_0s( 1, 0, 1 ), 'count_0s: 1, 0, 1 => 1' ) ;
19355 is( 2, count_0s( 1, 0, 1, 0 ), 'count_0s: 1, 0, 1, 0 => 2' ) ;
19356 note( 'Leaving tests_count_zeros()' ) ;
19357 return ;
19358}
19359sub count_0s
19360{
19361 my @array = @ARG ;
19362
19363 if ( ! @array ) { return 0 ; }
19364 my $nb_zeros = 0 ;
19365 map { $_ == 0 and $nb_zeros += 1 } @array ;
19366 return $nb_zeros ;
19367}
19368
19369sub tests_report_failures
19370{
19371 note( 'Entering tests_report_failures()' ) ;
19372
19373 is( undef, report_failures( ), 'report_failures: no parameters => undef' ) ;
19374 is( "nb 1 - first\n", report_failures( ({'ok' => 0, name => 'first'}) ), 'report_failures: "first" failed => nb 1 - first' ) ;
19375 is( q{}, report_failures( ( {'ok' => 1, name => 'first'} ) ), 'report_failures: "first" success =>' ) ;
19376 is( "nb 2 - second\n", report_failures( ( {'ok' => 1, name => 'second'}, {'ok' => 0, name => 'second'} ) ), 'report_failures: "second" failed => nb 2 - second' ) ;
19377 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' ) ;
19378 note( 'Leaving tests_report_failures()' ) ;
19379 return ;
19380}
19381
19382sub report_failures
19383{
19384 my @details = @ARG ;
19385
19386 if ( ! @details ) { return ; }
19387
19388 my $counter = 1 ;
19389 my $report = q{} ;
19390 foreach my $details ( @details ) {
19391 if ( ! $details->{ 'ok' } ) {
19392 my $name = $details->{ 'name' } || 'NONAME' ;
19393 $report .= "nb $counter - $name\n" ;
19394 }
19395 $counter += 1 ;
19396 }
19397 return $report ;
19398
19399}
19400
19401sub tests_true
19402{
19403 note( 'Entering tests_true()' ) ;
19404
19405 is( 1, 1, 'true: 1 is 1' ) ;
19406 note( 'Leaving tests_true()' ) ;
19407 return ;
19408}
19409
19410sub tests_testsunit
19411{
19412 note( 'Entering tests_testunit()' ) ;
19413
19414 is( undef, testsunit( ), 'testsunit: no parameters => undef' ) ;
19415 is( undef, testsunit( undef ), 'testsunit: an undef parameter => undef' ) ;
19416 is( undef, testsunit( q{} ), 'testsunit: an empty parameter => undef' ) ;
19417 is( undef, testsunit( 'idonotexist' ), 'testsunit: a do not exist function as parameter => undef' ) ;
19418 is( undef, testsunit( 'tests_true' ), 'testsunit: tests_true => undef' ) ;
19419 note( 'Leaving tests_testunit()' ) ;
19420 return ;
19421}
19422
19423sub testsunit
19424{
19425 my @functions = @ARG ;
19426
19427 if ( ! @functions ) { #
19428 myprint( "testsunit warning: no argument given\n" ) ;
19429 return ;
19430 }
19431
19432 foreach my $function ( @functions ) {
19433 if ( ! $function ) {
19434 myprint( "testsunit warning: argument is empty\n" ) ;
19435 next ;
19436 }
19437 if ( ! exists &$function ) {
19438 myprint( "testsunit warning: function $function does not exist\n" ) ;
19439 next ;
19440 }
19441 if ( ! defined &$function ) {
19442 myprint( "testsunit warning: function $function is not defined\n" ) ;
19443 next ;
19444 }
19445 my $function_ref = \&{ $function } ;
19446 &$function_ref() ;
19447 }
19448 return ;
19449}
19450
19451sub testsdebug
19452{
19453 # Now a little obsolete since there is
19454 # imapsync ... --testsunit "anyfunction"
19455 my $mysync = shift ;
19456 if ( ! $mysync->{ testsdebug } ) { return ; }
19457 SKIP: {
19458 if ( ! $mysync->{ testsdebug } ) {
19459 skip 'No test in normal run' ;
19460 }
19461
19462 note( 'Entering testsdebug()' ) ;
19463 #ok( ( ( not -d 'W/tmp/tests' ) or rmtree( 'W/tmp/tests/' ) ), 'testsdebug: rmtree W/tmp/tests' ) ;
19464 #tests_check_binary_embed_all_dyn_libs( ) ;
19465 #tests_killpid_by_parent( ) ;
19466 #tests_killpid_by_brother( ) ;
19467 #tests_kill_zero( ) ;
19468 #tests_connect_socket( ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019469 #tests_probe_imapssl( ) ;
19470 tests_cpu_number( ) ;
19471 tests_mailimapclient_connect( ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019472 #tests_always_fail( ) ;
19473
19474 note( 'Leaving testsdebug()' ) ;
19475 done_testing( ) ;
19476 }
19477 return ;
19478}
19479
19480
19481sub tests
19482{
19483 my $mysync = shift ;
19484 if ( ! $mysync->{ tests } ) { return ; }
19485
19486 SKIP: {
19487 skip 'No test in normal run' if ( ! $mysync->{ tests } ) ;
19488 note( 'Entering tests()' ) ;
19489 tests_folder_routines( ) ;
19490 tests_compare_lists( ) ;
19491 tests_regexmess( ) ;
19492 tests_skipmess( ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019493 tests_regexflags( );
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019494 tests_ucsecond( ) ;
19495 tests_permanentflags();
19496 tests_flags_filter( ) ;
19497 tests_separator_invert( ) ;
19498 tests_imap2_folder_name( ) ;
19499 tests_command_line_nopassword( ) ;
19500 tests_good_date( ) ;
19501 tests_max( ) ;
19502 tests_remove_not_num();
19503 tests_memory_consumption( ) ;
19504 tests_is_a_release_number();
19505 tests_imapsync_basename();
19506 tests_list_keys_in_2_not_in_1();
19507 tests_convert_sep_to_slash( ) ;
19508 tests_match_a_cache_file( ) ;
19509 tests_cache_map( ) ;
19510 tests_get_cache( ) ;
19511 tests_clean_cache( ) ;
19512 tests_clean_cache_2( ) ;
19513 tests_touch( ) ;
19514 tests_flagscase( ) ;
19515 tests_mkpath( ) ;
19516 tests_extract_header( ) ;
19517 tests_decompose_header( ) ;
19518 tests_epoch( ) ;
19519 tests_add_header( ) ;
19520 tests_cache_dir_fix( ) ;
19521 tests_cache_dir_fix_win( ) ;
19522 tests_filter_forbidden_characters( ) ;
19523 tests_cache_folder( ) ;
19524 tests_time_remaining( ) ;
19525 tests_decompose_regex( ) ;
19526 tests_backtick( ) ;
19527 tests_bytes_display_string( ) ;
19528 tests_header_line_normalize( ) ;
19529 tests_fix_Inbox_INBOX_mapping( ) ;
19530 tests_max_line_length( ) ;
19531 tests_subject( ) ;
19532 tests_msgs_from_maxmin( ) ;
19533 tests_tmpdir_has_colon_bug( ) ;
19534 tests_sleep_max_messages( ) ;
19535 tests_sleep_max_bytes( ) ;
19536 tests_logfile( ) ;
19537 tests_setlogfile( ) ;
19538 tests_jux_utf8_old( ) ;
19539 tests_jux_utf8( ) ;
19540 tests_pipemess( ) ;
19541 tests_jux_utf8_list( ) ;
19542 tests_guess_prefix( ) ;
19543 tests_guess_separator( ) ;
19544 tests_format_for_imap_arg( ) ;
19545 tests_imapsync_id( ) ;
19546 tests_date_from_rcs( ) ;
19547 tests_quota_extract_storage_limit_in_bytes( ) ;
19548 tests_quota_extract_storage_current_in_bytes( ) ;
19549 tests_guess_special( ) ;
19550 tests_do_valid_directory( ) ;
19551 tests_delete1emptyfolders( ) ;
19552 tests_message_for_host2( ) ;
19553 tests_length_ref( ) ;
19554 tests_firstline( ) ;
19555 tests_diff_or_NA( ) ;
19556 tests_match_number( ) ;
19557 tests_all_defined( ) ;
19558 tests_special_from_folders_hash( ) ;
19559 tests_notmatch( ) ;
19560 tests_match( ) ;
19561 tests_get_options( ) ;
19562 tests_get_options_cgi_context( ) ;
19563 tests_rand32( ) ;
19564 tests_hashsynclocal( ) ;
19565 tests_hashsync( ) ;
19566 tests_output( ) ;
19567 tests_output_reset_with( ) ;
19568 tests_output_start( ) ;
19569 tests_check_last_release( ) ;
19570 tests_loadavg( ) ;
19571 tests_cpu_number( ) ;
19572 tests_load_and_delay( ) ;
19573 #tests_imapsping( ) ;
19574 #tests_tcpping( ) ;
19575 tests_sslcheck( ) ;
19576 tests_not_long_imapsync_version_public( ) ;
19577 tests_reconnect_if_needed( ) ;
19578 tests_reconnect_12_if_needed( ) ;
19579 tests_sleep_if_needed( ) ;
19580 tests_string_to_file( ) ;
19581 tests_file_to_string( ) ;
19582 tests_under_cgi_context( ) ;
19583 tests_umask( ) ;
19584 tests_umask_str( ) ;
19585 tests_set_umask( ) ;
19586 tests_createhashfileifneeded( ) ;
19587 tests_slash_to_underscore( ) ;
19588 tests_testsunit( ) ;
19589 tests_count_0s( ) ;
19590 tests_report_failures( ) ;
19591 tests_min( ) ;
19592 #tests_connect_socket( ) ;
19593 #tests_resolvrev( ) ;
19594 tests_usage( ) ;
19595 tests_version_from_rcs( ) ;
19596 tests_backslash_caret( ) ;
19597 #tests_mailimapclient_connect_bug( ) ; # it fails with Mail-IMAPClient <= 3.39
19598 tests_write_pidfile( ) ;
19599 tests_remove_pidfile_not_running( ) ;
19600 tests_match_a_pid_number( ) ;
19601 tests_prefix_seperator_invertion( ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019602 tests_is_integer( ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019603 tests_integer_or_1( ) ;
19604 tests_is_number( ) ;
19605 tests_sig_install( ) ;
19606 tests_template( ) ;
19607 tests_split_around_equal( ) ;
19608 tests_toggle_sleep( ) ;
19609 tests_labels( ) ;
19610 tests_synclabels( ) ;
19611 tests_uidexpunge_or_expunge( ) ;
19612 tests_appendlimit_from_capability( ) ;
19613 tests_maxsize_setting( ) ;
19614 tests_mock_capability( ) ;
19615 tests_appendlimit( ) ;
19616 tests_capability_of( ) ;
19617 tests_search_in_array( ) ;
19618 tests_operators_and_exclam_precedence( ) ;
19619 tests_teelaunch( ) ;
19620 tests_logfileprepa( ) ;
19621 tests_useheader_suggestion( ) ;
19622 tests_nb_messages_in_2_not_in_1( ) ;
19623 tests_labels_add_subfolder2( ) ;
19624 tests_labels_remove_subfolder1( ) ;
19625 tests_resynclabels( ) ;
19626 tests_labels_remove_special( ) ;
19627 tests_uniq( ) ;
19628 tests_remove_from_requested_folders( ) ;
19629 tests_errors_log( ) ;
19630 tests_add_subfolder1_to_folderrec( ) ;
19631 tests_sanitize_subfolder( ) ;
19632 tests_remove_edging_blanks( ) ;
19633 tests_sanitize( ) ;
19634 tests_remove_last_char_if_is( ) ;
19635 tests_check_binary_embed_all_dyn_libs( ) ;
19636 tests_nthline( ) ;
19637 tests_secondline( ) ;
19638 tests_tail( ) ;
19639 tests_truncmess( ) ;
19640 tests_eta( ) ;
19641 tests_timesince( ) ;
19642 tests_timenext( ) ;
19643 tests_foldersize( ) ;
19644 tests_imapsync_context( ) ;
19645 tests_abort( ) ;
19646 tests_probe_imapssl( ) ;
19647 tests_mailimapclient_connect( ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019648 tests_checknoabletosearch( ) ;
19649 tests_errorsdump( ) ;
19650 tests_errorsanalyse( ) ;
19651 tests_most_common_error( ) ;
19652 tests_errorclassify( ) ;
19653 tests_error_type( ) ;
19654 tests_sanitize_host( ) ;
19655 tests_hmac_sha1_hex( ) ;
19656 tests_total_bytes_max_reached( ) ;
19657 tests_header_construct( ) ;
19658 tests_remove_doublequotes_if_any( ) ;
19659 tests_login_imap( ) ;
19660 tests_login_imap_oauth( ) ;
19661 tests_skipmess_neg( ) ;
19662 tests_localtimez( ) ;
19663 tests_file_to_array( ) ;
19664 tests_cpu_time( ) ;
19665 tests_cpu_percent( ) ;
19666 tests_cpu_percent_global( ) ;
19667 tests_flags_for_host2( ) ;
19668 tests_under_docker_context( ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019669 #tests_resolv( ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019670
19671 # Those three are for later use, when webserver will be inside imapsync
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019672 # or will be deleted them if I abandon the project.
19673 #tests_killpid_by_parent( ) ;
19674 #tests_killpid_by_brother( ) ;
19675 #tests_kill_zero( ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019676
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019677 #tests_always_fail( ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019678 done_testing( 1742 ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019679 note( 'Leaving tests()' ) ;
19680 }
19681 return ;
19682}
19683
19684sub tests_template
19685{
19686 note( 'Entering tests_template()' ) ;
19687
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019688 is( undef, template( ), 'template: no args => undef' ) ;
19689 my $mysync = { } ;
19690 is( undef, template( $mysync ), 'template: undef => undef' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019691 is_deeply( {}, {}, 'template: a hash is a hash' ) ;
19692 is_deeply( [], [], 'template: an array is an array' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019693
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019694 note( 'Leaving tests_template()' ) ;
19695 return ;
19696}
19697
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019698sub template
19699{
19700 my $mysync = shift @ARG ;
19701
19702 return ;
19703}