blob: de63d658ce80f49bce8e7407939b85047329ddf1 [file] [log] [blame]
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001#!/usr/bin/env perl
2
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003# $Id: imapsync,v 2.178 2022/01/12 21:28:37 gilles Exp gilles $
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004# structure
5# pod documentation
6# use pragmas
7# main program
8# global variables initialization
9# get_options( ) ;
10# default values
11# folder loop
12# subroutines
13# sub usage
14
15
16# pod documentation
17
18=pod
19
20=head1 NAME
21
22imapsync - Email IMAP tool for syncing, copying, migrating
23and archiving email mailboxes between two imap servers, one way,
24and without duplicates.
25
26=head1 VERSION
27
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010028This documentation refers to Imapsync $Revision: 2.178 $
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010029
30=head1 USAGE
31
32 To synchronize the source imap account
33 "test1" on server "test1.lamiral.info" with password "secret1"
34 to the destination imap account
35 "test2" on server "test2.lamiral.info" with password "secret2"
36 do:
37
38 imapsync \
39 --host1 test1.lamiral.info --user1 test1 --password1 secret1 \
40 --host2 test2.lamiral.info --user2 test2 --password2 secret2
41
42=head1 DESCRIPTION
43
44We sometimes need to transfer mailboxes from one imap server to
45one another.
46
47Imapsync command is a tool allowing incremental and
48recursive imap transfers from one mailbox to another.
49If you don't understand the previous sentence, it's normal,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020050it's pedantic computer-oriented jargon.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010051
52All folders are transferred, recursively, meaning
53the whole folder hierarchy is taken, all messages in them,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020054and all message flags (\Seen \Answered \Flagged etc.)
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010055are synced too.
56
57Imapsync reduces the amount of data transferred by not transferring
58a given message if it already resides on the destination side.
59Messages that are on the destination side but not on the
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020060source side stay as they are. See the --delete2
61option to have strict sync and delete them.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010062
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020063How imapsync know a message is already on both sides?
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010064Same specific headers and the transfer is done only once.
65By default, the identification headers are
66"Message-Id:" and "Received:" lines
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020067but this choice can be changed with the --useheader option,
68most often a duplicate problem is solved by using
69--useheader "Message-Id"
70
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010071
72All flags are preserved, unread messages will stay unread,
73read ones will stay read, deleted will stay deleted.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020074In the IMAP protocol, a deleted message is not really deleted,
75it is marked \Deleted and can be undelete. Real destruction
76comes with the EXPUNGE or UIDEXPUNGE IMAP commands.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010077
78You can abort the transfer at any time and restart it later,
79imapsync works well with bad connections and interruptions,
80by design. On a terminal hit Ctr-c twice within two seconds
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020081to abort the program. Hit Ctr-c just once makes
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010082imapsync reconnect to both imap servers.
83
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020084How do you know the sync is finished and well done?
85When imapsync ends by itself it mentions it with lines like those:
86
87 Exiting with return value 0 (EX_OK: successful termination) 0/50 nb_errors/max_errors PID 301
88 Removing pidfile /tmp/imapsync.pid
89 Log file is LOG_imapsync/2020_11_17_15_59_22_761_test1_test2.txt ( to change it, use --logfile filepath ; or use --nolog to turn off logging )
90
91If you don't have those lines it means that either the sync process is still
92running (or eventually hanging indefinitely) or that it ended without
93a whisper, a strong kill -9 on Linux for example.
94
95If you have those final lines then it means the sync process is properly
96finished. It may have encountered problems though.
97
98A good synchronization is mentioned by some lines above the last ones, especially
99those three lines:
100
101 The sync looks good, all 1745 identified messages in host1 are on host2.
102 There is no unidentified message on host1.
103 Detected 0 errors
104
105
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100106A classical scenario is synchronizing a mailbox B from another mailbox A
107where you just want to keep a strict copy of A in B. Strict meaning
108all messages in A will be in B but no more.
109
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200110For this, option --delete2 can be used, it deletes messages in the host2
111folder B that are not in the host1 folder A. If you also need to destroy
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100112host2 folders that are not in host1 then use --delete2folders. See also
113--delete2foldersonly and --delete2foldersbutnot to set up exceptions
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200114on folders to destroy. INBOX will never be destroyed, it's a mandatory
115folder in IMAP so imapsync doesn't even try to remove it.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100116
117A different scenario is to delete the messages from the source mailbox
118after a successful transfer, it can be a good feature when migrating
119mailboxes since messages will be only on one side. The source account
120will only have messages that are not on the destination yet, ie,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200121messages that arrived after a sync or that failed to be transferred.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100122
123In that case, use the --delete1 option. Option --delete1 implies also
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200124the option --expunge1 so all messages marked deleted on host1 will be
125deleted. In IMAP protocol deleting a message does not delete it,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100126it marks it with the flag \Deleted, allowing an undelete. Expunging
127a folder removes, definitively, all the messages marked as \Deleted
128in this folder.
129
130You can also decide to remove empty folders once all of their messages
131have been transferred. Add --delete1emptyfolders to obtain this
132behavior.
133
134
135Imapsync is not adequate for maintaining two active imap accounts
136in synchronization when the user plays independently on both sides.
137Use offlineimap (written by John Goerzen) or mbsync (written by
138Michael R. Elkins) for a 2 ways synchronization.
139
140
141=head1 OPTIONS
142
143 usage: imapsync [options]
144
145The standard options are the six values forming the credentials.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200146Three values on each side are needed in order to login into the IMAP
147servers. These six values are a hostname, a username, and a password, two times.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100148
149Conventions used in the following descriptions of the options:
150
151 str means string
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200152 int means integer number
153 flo means float number
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100154 reg means regular expression
155 cmd means command
156
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200157 --dry : Makes imapsync doing nothing for real; it just print what
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100158 would be done without --dry.
159
160=head2 OPTIONS/credentials
161
162
163 --host1 str : Source or "from" imap server.
164 --port1 int : Port to connect on host1.
165 Optional since default ports are the
166 well known ports imap/143 or imaps/993.
167 --user1 str : User to login on host1.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200168 --password1 str : Password of user1.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100169
170 --host2 str : "destination" imap server.
171 --port2 int : Port to connect on host2. Optional
172 --user2 str : User to login on host2.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200173 --password2 str : Password of user2.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100174
175 --showpasswords : Shows passwords on output instead of "MASKED".
176 Useful to restart a complete run by just reading
177 the command line used in the log,
178 or to debug passwords.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200179 It's not a secure practice at all!
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100180
181 --passfile1 str : Password file for the user1. It must contain the
182 password on the first line. This option avoids showing
183 the password on the command line like --password1 does.
184 --passfile2 str : Password file for the user2.
185
186You can also pass the passwords in the environment variables
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200187IMAPSYNC_PASSWORD1 and IMAPSYNC_PASSWORD2. If you don't pass
188the user1 password via --password1 nor --passfile1 nor $IMAPSYNC_PASSWORD1
189then imapsync will prompt to enter the password on the terminal.
190Same thing for user2 password.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100191
192=head2 OPTIONS/encryption
193
194 --nossl1 : Do not use a SSL connection on host1.
195 --ssl1 : Use a SSL connection on host1. On by default if possible.
196
197 --nossl2 : Do not use a SSL connection on host2.
198 --ssl2 : Use a SSL connection on host2. On by default if possible.
199
200 --notls1 : Do not use a TLS connection on host1.
201 --tls1 : Use a TLS connection on host1. On by default if possible.
202
203 --notls2 : Do not use a TLS connection on host2.
204 --tls2 : Use a TLS connection on host2. On by default if possible.
205
206 --debugssl int : SSL debug mode from 0 to 4.
207
208 --sslargs1 str : Pass any ssl parameter for host1 ssl or tls connection. Example:
209 --sslargs1 SSL_verify_mode=1 --sslargs1 SSL_version=SSLv3
210 See all possibilities in the new() method of IO::Socket::SSL
211 http://search.cpan.org/perldoc?IO::Socket::SSL#Description_Of_Methods
212 --sslargs2 str : Pass any ssl parameter for host2 ssl or tls connection.
213 See --sslargs1
214
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100215
216=head2 OPTIONS/authentication
217
218 --authmech1 str : Auth mechanism to use with host1:
219 PLAIN, LOGIN, CRAM-MD5 etc. Use UPPERCASE.
220 --authmech2 str : Auth mechanism to use with host2. See --authmech1
221
222 --authuser1 str : User to auth with on host1 (admin user).
223 Avoid using --authmech1 SOMETHING with --authuser1.
224 --authuser2 str : User to auth with on host2 (admin user).
225 --proxyauth1 : Use proxyauth on host1. Requires --authuser1.
226 Required by Sun/iPlanet/Netscape IMAP servers to
227 be able to use an administrative user.
228 --proxyauth2 : Use proxyauth on host2. Requires --authuser2.
229
230 --authmd51 : Use MD5 authentication for host1.
231 --authmd52 : Use MD5 authentication for host2.
232 --domain1 str : Domain on host1 (NTLM authentication).
233 --domain2 str : Domain on host2 (NTLM authentication).
234
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200235 --oauthaccesstoken1 str : The access token to authenticate with OAUTH2.
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100236 It will be combined with the --user1 value to form the
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200237 string to pass with XOAUTH2 authentication.
238 The password given by --password1 or --passfile1
239 is ignored.
240 Instead of the access token itself, the value can be a
241 file containing the access token on the first line.
242 If the value is a file, imapsync reads its first line
243 and take this line as the access token. The advantage
244 of the file is that if the access token changes then
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100245 imapsync can read it again when it needs to reconnect
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200246 during a run.
247
248
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100249 --oauthaccesstoken2 str : same thing as --oauthaccesstoken1
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200250
251 --oauthdirect1 str : The direct string to pass with XOAUTH2 authentication.
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100252 The password given by --password1 or --passfile1 and
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200253 the user given by --user1 are ignored.
254
255 --oauthdirect2 str : same thing as oauthdirect1
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100256
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100257
258=head2 OPTIONS/folders
259
260
261 --folder str : Sync this folder.
262 --folder str : and this one, etc.
263 --folderrec str : Sync this folder recursively.
264 --folderrec str : and this one, etc.
265
266 --folderfirst str : Sync this folder first. Ex. --folderfirst "INBOX"
267 --folderfirst str : then this one, etc.
268 --folderlast str : Sync this folder last. --folderlast "[Gmail]/All Mail"
269 --folderlast str : then this one, etc.
270
271 --nomixfolders : Do not merge folders when host1 is case-sensitive
272 while host2 is not (like Exchange). Only the first
273 similar folder is synced (example: with folders
274 "Sent", "SENT" and "sent"
275 on host1 only "Sent" will be synced to host2).
276
277 --skipemptyfolders : Empty host1 folders are not created on host2.
278
279 --include reg : Sync folders matching this regular expression
280 --include reg : or this one, etc.
281 If both --include --exclude options are used, then
282 include is done before.
283 --exclude reg : Skips folders matching this regular expression
284 Several folders to avoid:
285 --exclude 'fold1|fold2|f3' skips fold1, fold2 and f3.
286 --exclude reg : or this one, etc.
287
288 --automap : guesses folders mapping, for folders well known as
289 "Sent", "Junk", "Drafts", "All", "Archive", "Flagged".
290
291 --f1f2 str1=str2 : Force folder str1 to be synced to str2,
292 --f1f2 overrides --automap and --regextrans2.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200293 Use several --f1f2 options to map several folders.
294 Option --f1f2 is a one to one only folder mapping,
295 str1 and str2 have to be full path folder names.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100296
297 --subfolder2 str : Syncs the whole host1 folders hierarchy under the
298 host2 folder named str.
299 It does it internally by adding three
300 --regextrans2 options before all others.
301 Add --debug to see what's really going on.
302
303 --subfolder1 str : Syncs the host1 folders hierarchy which is under folder
304 str to the root hierarchy of host2.
305 It's the couterpart of a sync done by --subfolder2
306 when doing it in the reverse order.
307 Backup/Restore scenario:
308 Use --subfolder2 str for a backup to the folder str
309 on host2. Then use --subfolder1 str for restoring
310 from the folder str, after inverting
311 host1/host2 user1/user2 values.
312
313
314 --subscribed : Transfers subscribed folders.
315 --subscribe : Subscribe to the folders transferred on the
316 host2 that are subscribed on host1. On by default.
317 --subscribeall : Subscribe to the folders transferred on the
318 host2 even if they are not subscribed on host1.
319
320 --prefix1 str : Remove prefix str to all destination folders,
321 usually "INBOX." or "INBOX/" or an empty string "".
322 imapsync guesses the prefix if host1 imap server
323 does not have NAMESPACE capability. So this option
324 should not be used most of the time.
325 --prefix2 str : Add prefix to all host2 folders. See --prefix1
326
327 --sep1 str : Host1 separator. This option should not be used
328 most of the time.
329 Imapsync gets the separator from the server itself,
330 by using NAMESPACE, or it tries to guess it
331 from the folders listing (it counts
332 characters / . \\ \ in folder names and choose the
333 more frequent, or finally / if nothing is found.
334 --sep2 str : Host2 separator. See --sep1
335
336 --regextrans2 reg : Apply the whole regex to each destination folders.
337 --regextrans2 reg : and this one. etc.
338 When you play with the --regextrans2 option, first
339 add also the safe options --dry --justfolders
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200340 Then, when happy, remove --dry for a run, then
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100341 remove --justfolders for the next ones.
342 Have in mind that --regextrans2 is applied after
343 the automatic prefix and separator inversion.
344 For examples see:
345 https://imapsync.lamiral.info/FAQ.d/FAQ.Folders_Mapping.txt
346
347=head2 OPTIONS/folders sizes
348
349 --nofoldersizes : Do not calculate the size of each folder at the
350 beginning of the sync. Default is to calculate them.
351 --nofoldersizesatend: Do not calculate the size of each folder at the
352 end of the sync. Default is to calculate them.
353 --justfoldersizes : Exit after having printed the initial folder sizes.
354
355
356=head2 OPTIONS/tmp
357
358
359 --tmpdir str : Where to store temporary files and subdirectories.
360 Will be created if it doesn't exist.
361 Default is system specific, Unix is /tmp but
362 /tmp is often too small and deleted at reboot.
363 --tmpdir /var/tmp should be better.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200364
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100365 --pidfile str : The file where imapsync pid is written,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200366 it can be dirname/filename complete path.
367 The default name is imapsync.pid in tmpdir.
368
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100369 --pidfilelocking : Abort if pidfile already exists. Useful to avoid
370 concurrent transfers on the same mailbox.
371
372
373=head2 OPTIONS/log
374
375 --nolog : Turn off logging on file
376 --logfile str : Change the default log filename (can be dirname/filename).
377 --logdir str : Change the default log directory. Default is LOG_imapsync/
378
379The default logfile name is for example
380
381 LOG_imapsync/2019_12_22_23_57_59_532_user1_user2.txt
382
383where:
384
385 2019_12_22_23_57_59_532 is nearly the date of the start
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200386 YYYY_MM_DD_HH_MM_SS_mmm
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100387 year_month_day_hour_minute_seconde_millisecond
388
389and user1 user2 are the --user1 --user2 values.
390
391=head2 OPTIONS/messages
392
393 --skipmess reg : Skips messages matching the regex.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200394 Example: 'm/[\x80-\xff]/' # to avoid 8bits messages.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100395 --skipmess is applied before --regexmess
396 --skipmess reg : or this one, etc.
397
398 --skipcrossduplicates : Avoid copying messages that are already copied
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200399 in another folder, good from Gmail to XYZ when
400 XYZ is not also Gmail.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100401 Activated with --gmail1 unless --noskipcrossduplicates
402
403 --debugcrossduplicates : Prints which messages (UIDs) are skipped with
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200404 --skipcrossduplicates and in what other folders
405 they are.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100406
407 --pipemess cmd : Apply this cmd command to each message content
408 before the copy.
409 --pipemess cmd : and this one, etc.
410 With several --pipemess, the output of each cmd
411 command (STDOUT) is given to the input (STDIN)
412 of the next command.
413 For example,
414 --pipemess cmd1 --pipemess cmd2 --pipemess cmd3
415 is like a Unix pipe:
416 "cat message | cmd1 | cmd2 | cmd3"
417
418 --disarmreadreceipts : Disarms read receipts (host2 Exchange issue)
419
420 --regexmess reg : Apply the whole regex to each message before transfer.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200421 Example: 's/\000/ /g' # to replace null characters
422 by spaces.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100423 --regexmess reg : and this one, etc.
424
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100425 --truncmess int : truncates messages when their size exceed the int
426 value, specified in bytes. Good to sync too big
427 messages or to "suppress" attachments.
428 Have in mind that this way, messages become
429 uncoherent somehow.
430
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100431=head2 OPTIONS/labels
432
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200433Gmail present labels as folders in imap. Imapsync can accelerate the sync
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100434by syncing X-GM-LABELS, it will avoid to transfer messages when they are
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200435already on host2 in another folder.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100436
437
438 --synclabels : Syncs also Gmail labels when a message is copied to host2.
439 Activated by default with --gmail1 --gmail2 unless
440 --nosynclabels is added.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200441
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100442 --resynclabels : Resyncs Gmail labels when a message is already on host2.
443 Activated by default with --gmail1 --gmail2 unless
444 --noresynclabels is added.
445
446For Gmail syncs, see also:
447https://imapsync.lamiral.info/FAQ.d/FAQ.Gmail.txt
448
449=head2 OPTIONS/flags
450
451 If you encounter flag problems see also:
452 https://imapsync.lamiral.info/FAQ.d/FAQ.Flags.txt
453
454 --regexflag reg : Apply the whole regex to each flags list.
455 Example: 's/"Junk"//g' # to remove "Junk" flag.
456 --regexflag reg : then this one, etc.
457
458 --resyncflags : Resync flags for already transferred messages.
459 On by default.
460 --noresyncflags : Do not resync flags for already transferred messages.
461 May be useful when a user has already started to play
462 with its host2 account.
463
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200464 --filterbuggyflags : Filter flags known to be buggy and generators of errors
465 "BAD Invalid system flag" or "NO APPEND Invalid flag list".
466
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100467=head2 OPTIONS/deletions
468
469 --delete1 : Deletes messages on host1 server after a successful
470 transfer. Option --delete1 has the following behavior:
471 it marks messages as deleted with the IMAP flag
472 \Deleted, then messages are really deleted with an
473 EXPUNGE IMAP command. If expunging after each message
474 slows down too much the sync then use
475 --noexpungeaftereach to speed up, expunging will then be
476 done only twice per folder, one at the beginning and
477 one at the end of a folder sync.
478
479 --expunge1 : Expunge messages on host1 just before syncing a folder.
480 Expunge is done per folder.
481 Expunge aims is to really delete messages marked deleted.
482 An expunge is also done after each message copied
483 if option --delete1 is set (unless --noexpungeaftereach).
484
485 --noexpunge1 : Do not expunge messages on host1.
486
487 --delete1emptyfolders : Deletes empty folders on host1, INBOX excepted.
488 Useful with --delete1 since what remains on host1
489 is only what failed to be synced.
490
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200491 --delete2 : Delete messages in the host2 account that are not in
492 the host1 account. Useful for backup or pre-sync.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100493 --delete2 implies --uidexpunge2
494
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200495 --delete2duplicates : Deletes messages in host2 that are duplicates in host2.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100496 Works only without --useuid since duplicates are
497 detected with an header part of each message.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200498 NB: --delete2duplicates is far less violent than --delete2
499 since it removes only duplicates.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100500
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200501 --delete2folders : Delete folders in host2 that are not in host1.
502 For safety, first try it like this, it is safe:
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100503 --delete2folders --dry --justfolders --nofoldersizes
504 and see what folders will be deleted.
505
506 --delete2foldersonly reg : Delete only folders matching the regex reg.
507 Example: --delete2foldersonly "/^Junk$|^INBOX.Junk$/"
508 This option activates --delete2folders
509
510 --delete2foldersbutnot reg : Do not delete folders matching the regex rex.
511 Example: --delete2foldersbutnot "/Tasks$|Contacts$|Foo$/"
512 This option activates --delete2folders
513
514 --noexpunge2 : Do not expunge messages on host2.
515 --nouidexpunge2 : Do not uidexpunge messages on the host2 account
516 that are not on the host1 account.
517
518
519=head2 OPTIONS/dates
520
521 If you encounter problems with dates, see also:
522 https://imapsync.lamiral.info/FAQ.d/FAQ.Dates.txt
523
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200524 --syncinternaldates : Sets the internal dates on host2 as the same as host1.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100525 Turned on by default. Internal date is the date
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200526 a message arrived on a host (Unix mtime usually).
527 --idatefromheader : Sets the internal dates on host2 as same as the
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100528 ones in "Date:" headers.
529
530
531
532=head2 OPTIONS/message selection
533
534 --maxsize int : Skip messages larger (or equal) than int bytes
535 --minsize int : Skip messages smaller (or equal) than int bytes
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200536
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100537 --maxage int : Skip messages older than int days.
538 final stats (skipped) don't count older messages
539 see also --minage
540 --minage int : Skip messages newer than int days.
541 final stats (skipped) don't count newer messages
542 You can do (+ zone are the messages selected):
543 past|----maxage+++++++++++++++>now
544 past|+++++++++++++++minage---->now
545 past|----maxage+++++minage---->now (intersection)
546 past|++++minage-----maxage++++>now (union)
547
548 --search str : Selects only messages returned by this IMAP SEARCH
549 command. Applied on both sides.
550 For a complete set of what can be search see
551 https://imapsync.lamiral.info/FAQ.d/FAQ.Messages_Selection.txt
552
553 --search1 str : Same as --search but for selecting host1 messages only.
554 --search2 str : Same as --search but for selecting host2 messages only.
555 So --search CRIT equals --search1 CRIT --search2 CRIT
556
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200557 --noabletosearch : Makes --minage and --maxage options use the internal
558 dates given by a FETCH imap command instead of the
559 "Date:" header. Internal date is the arrival date
560 in the mailbox.
561 --noabletosearch equals --noabletosearch1 --noabletosearch2
562
563 --noabletosearch1 : Like --noabletosearch but for host1 only.
564 --noabletosearch2 : Like --noabletosearch but for host2 only.
565
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100566 --maxlinelength int : skip messages with a line length longer than int bytes.
567 RFC 2822 says it must be no more than 1000 bytes but
568 real life servers and email clients do more.
569
570
571 --useheader str : Use this header to compare messages on both sides.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200572 Example: "Message-Id" or "Received" or "Date".
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100573 --useheader str and this one, etc.
574
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200575 --syncduplicates : Sync also duplicates. Off by default.
576
577 --usecache : Use cache to speed up next syncs. Off by default.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100578 --nousecache : Do not use cache. Caveat: --useuid --nousecache creates
579 duplicates on multiple runs.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200580
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100581 --useuid : Use UIDs instead of headers as a criterion to recognize
582 messages. Option --usecache is then implied unless
583 --nousecache is used.
584
585
586=head2 OPTIONS/miscellaneous
587
588 --syncacls : Synchronizes acls (Access Control Lists).
589 Acls in IMAP are not standardized, be careful
590 since one acl code on one side may signify something
591 else on the other one.
592 --nosyncacls : Does not synchronize acls. This is the default.
593
594 --addheader : When a message has no headers to be identified,
595 --addheader adds a "Message-Id" header,
596 like "Message-Id: 12345@imapsync", where 12345
597 is the imap UID of the message on the host1 folder.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200598 Useful to sync folders "Sent" or "Draft".
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100599
600
601=head2 OPTIONS/debugging
602
603 --debug : Debug mode.
604 --debugfolders : Debug mode for the folders part only.
605 --debugcontent : Debug content of the messages transferred. Huge output.
606 --debugflags : Debug mode for flags.
607 --debugimap1 : IMAP debug mode for host1. Very verbose.
608 --debugimap2 : IMAP debug mode for host2. Very verbose.
609 --debugimap : IMAP debug mode for host1 and host2. Twice very verbose.
610 --debugmemory : Debug mode showing memory consumption after each copy.
611
612 --errorsmax int : Exit when int number of errors is reached. Default is 50.
613
614 --tests : Run local non-regression tests. Exit code 0 means all ok.
615 --testslive : Run a live test with test1.lamiral.info imap server.
616 Useful to check the basics. Needs internet connection.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200617 --testslive6 : Run a live test with ks6ipv6.lamiral.info imap server.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100618 Useful to check the ipv6 connectivity. Needs internet.
619
620
621=head2 OPTIONS/specific
622
623 --gmail1 : sets --host1 to Gmail and other options. See FAQ.Gmail.txt
624 --gmail2 : sets --host2 to Gmail and other options. See FAQ.Gmail.txt
625
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200626 --office1 : sets --host1 to Office365 and other options. See FAQ.Office365.txt
627 --office2 : sets --host2 to Office365 and other options. See FAQ.Office365.txt
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100628
629 --exchange1 : sets options for Exchange. See FAQ.Exchange.txt
630 --exchange2 : sets options for Exchange. See FAQ.Exchange.txt
631
632 --domino1 : sets options for Domino. See FAQ.Domino.txt
633 --domino2 : sets options for Domino. See FAQ.Domino.txt
634
635
636=head2 OPTIONS/behavior
637
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100638 --timeout1 flo : Connection timeout in seconds for host1.
639 Default is 120 and 0 means no timeout at all.
640 --timeout2 flo : Connection timeout in seconds for host2.
641 Default is 120 and 0 means no timeout at all.
642
643 Caveat, under CGI context, you may encounter a timeout
644 from the webserver, killing imapsync and the imap connexions.
645 See the document INSTALL.OnlineUI.txt and search
646 for "Timeout" for how to deal with this issue.
647
648 --keepalive1 : https://metacpan.org/pod/Mail::IMAPClient#Keepalive
649 Some firewalls and network gears like to timeout connections
650 prematurely if the connection sits idle.
651 This option enables SO_KEEPALIVE on the host1 socket.
652 --keepalive1 is on by default since imapsync release 2.169
653 Use --nokeepalive1 to disable it.
654
655 --keepalive2 : Same as --keepalive2 but for host2.
656 Use --nokeepalive2 to disable it.
657
658 --maxmessagespersecond flo : limits the average number of messages
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200659 transferred per second.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100660
661 --maxbytespersecond int : limits the average transfer rate per second.
662 --maxbytesafter int : starts --maxbytespersecond limitation only after
663 --maxbytesafter amount of data transferred.
664
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200665 --maxsleep flo : do not sleep more than int seconds.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100666 On by default, 2 seconds max, like --maxsleep 2
667
668 --abort : terminates a previous call still running.
669 It uses the pidfile to know what process to abort.
670
671 --exitwhenover int : Stop syncing and exits when int total bytes
672 transferred is reached.
673
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200674 --version : Print only the software version.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100675 --noreleasecheck : Do not check for any new imapsync release.
676 --releasecheck : Check for new imapsync release.
677 it's an http request to
678 http://imapsync.lamiral.info/prj/imapsync/VERSION
679
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200680 --noid : Do not send/receive IMAP "ID" command to imap servers.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100681
682 --justconnect : Just connect to both servers and print useful
683 information. Need only --host1 and --host2 options.
684 Obsolete since "imapsync --host1 imaphost" alone
685 implies --justconnect
686
687 --justlogin : Just login to both host1 and host2 with users
688 credentials, then exit.
689
690 --justfolders : Do only things about folders (ignore messages).
691
692 --help : print this help.
693
694 Example: to synchronize imap account "test1" on "test1.lamiral.info"
695 to imap account "test2" on "test2.lamiral.info"
696 with test1 password "secret1"
697 and test2 password "secret2"
698
699 imapsync \
700 --host1 test1.lamiral.info --user1 test1 --password1 secret1 \
701 --host2 test2.lamiral.info --user2 test2 --password2 secret2
702
703
704=cut
705# comment
706
707=pod
708
709
710
711=head1 SECURITY
712
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200713You can use --passfile1 instead of --password1 to mention the
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100714password since it is safer. With --password1 option, on Linux,
715any user on your host can see the password by using the 'ps auxwwww'
716command. Using a variable (like IMAPSYNC_PASSWORD1) is also
717dangerous because of the 'ps auxwwwwe' command. So, saving
718the password in a well protected file (600 or rw-------) is
719the best solution.
720
721Imapsync activates ssl or tls encryption by default, if possible.
722
723What detailed behavior is under this "if possible"?
724
725Imapsync activates ssl if the well known port imaps port (993) is open
726on the imap servers. If the imaps port is closed then it open a
727normal (clear) connection on port 143 but it looks for TLS support
728in the CAPABILITY list of the servers. If TLS is supported
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200729then imapsync goes to encryption with STARTTLS.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100730
731If the automatic ssl and the tls detections fail then imapsync will
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200732not protect against sniffing activities on the network, especially
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100733for passwords.
734
735If you want to force ssl or tls just use --ssl1 --ssl2 or --tls1 --tls2
736
737See also the document FAQ.Security.txt in the FAQ.d/ directory
738or at https://imapsync.lamiral.info/FAQ.d/FAQ.Security.txt
739
740=head1 EXIT STATUS
741
742Imapsync will exit with a 0 status (return code) if everything went good.
743Otherwise, it exits with a non-zero status. That's classical Unix behavior.
744Here is the list of the exit code values (an integer between 0 and 255).
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200745In Bourne Shells, this exit code value can be retrieved within the variable
746value "$?" if you read it just after the imapsync call.
747
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100748The names reflect their meaning:
749
750=for comment
751egrep '^Readonly my.*\$EX' imapsync | egrep -o 'EX.*' | sed 's_^_ _'
752
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100753 EX_OK => 0 ; #/* successful termination */
754 EX_USAGE => 64 ; #/* command line usage error */
755 EX_NOINPUT => 66 ; #/* cannot open input */
756 EX_UNAVAILABLE => 69 ; #/* service unavailable */
757 EX_SOFTWARE => 70 ; #/* internal software error */
758 EXIT_CATCH_ALL => 1 ; # Any other error
759 EXIT_BY_SIGNAL => 6 ; # Should be 128+n where n is the sig_num
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200760 EXIT_BY_FILE => 7 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100761 EXIT_PID_FILE_ERROR => 8 ;
762 EXIT_CONNECTION_FAILURE => 10 ;
763 EXIT_TLS_FAILURE => 12 ;
764 EXIT_AUTHENTICATION_FAILURE => 16 ;
765 EXIT_SUBFOLDER1_NO_EXISTS => 21 ;
766 EXIT_WITH_ERRORS => 111 ;
767 EXIT_WITH_ERRORS_MAX => 112 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200768 EXIT_OVERQUOTA => 113 ;
769 EXIT_ERR_APPEND => 114 ;
770 EXIT_ERR_FETCH => 115 ;
771 EXIT_ERR_CREATE => 116 ;
772 EXIT_ERR_SELECT => 117 ;
773 EXIT_TRANSFER_EXCEEDED => 118 ;
774 EXIT_ERR_APPEND_VIRUS => 119 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100775 EXIT_TESTS_FAILED => 254 ; # Like Test::More API
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200776 EXIT_CONNECTION_FAILURE_HOST1 => 101 ;
777 EXIT_CONNECTION_FAILURE_HOST2 => 102 ;
778 EXIT_AUTHENTICATION_FAILURE_USER1 => 161 ;
779 EXIT_AUTHENTICATION_FAILURE_USER2 => 162 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100780
781
782=head1 LICENSE AND COPYRIGHT
783
784Imapsync is free, open, public but not always gratis software
785cover by the NOLIMIT Public License, now called NLPL.
786See the LICENSE file included in the distribution or just read this
787simple sentence as it IS the licence text:
788
789 "No limits to do anything with this work and this license."
790
791In case it is not long enough, I repeat:
792
793 "No limits to do anything with this work and this license."
794
795Look at https://imapsync.lamiral.info/LICENSE
796
797=head1 AUTHOR
798
799Gilles LAMIRAL <gilles@lamiral.info>
800
801Good feedback is always welcome.
802Bad feedback is very often welcome.
803
804Gilles LAMIRAL earns his living by writing, installing,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200805configuring and sometimes teaching free, open and often gratis
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100806software. Imapsync used to be "always gratis" but now it is
807only "often gratis" because imapsync is sold by its author,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200808your servitor, a good way to maintain and support free open public
809software tools over decades.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100810
811=head1 BUGS AND LIMITATIONS
812
813See https://imapsync.lamiral.info/FAQ.d/FAQ.Reporting_Bugs.txt
814
815=head1 IMAP SERVERS supported
816
817See https://imapsync.lamiral.info/S/imapservers.shtml
818
819=head1 HUGE MIGRATION
820
821If you have many mailboxes to migrate think about a little
822shell program. Write a file called file.txt (for example)
823containing users and passwords.
824The separator used in this example is ';'
825
826The file.txt file contains:
827
828user001_1;password001_1;user001_2;password001_2
829user002_1;password002_1;user002_2;password002_2
830user003_1;password003_1;user003_2;password003_2
831user004_1;password004_1;user004_2;password004_2
832user005_1;password005_1;user005_2;password005_2
833...
834
835On Unix the shell program can be:
836
837 { while IFS=';' read u1 p1 u2 p2; do
838 imapsync --host1 imap.side1.org --user1 "$u1" --password1 "$p1" \
839 --host2 imap.side2.org --user2 "$u2" --password2 "$p2" ...
840 done ; } < file.txt
841
842On Windows the batch program can be:
843
844 FOR /F "tokens=1,2,3,4 delims=; eol=#" %%G IN (file.txt) DO imapsync ^
845 --host1 imap.side1.org --user1 %%G --password1 %%H ^
846 --host2 imap.side2.org --user2 %%I --password2 %%J ...
847
848The ... have to be replaced by nothing or any imapsync option.
849Welcome in shell or batch programming !
850
851You will find already written scripts at
852https://imapsync.lamiral.info/examples/
853
854=head1 INSTALL
855
856 Imapsync works under any Unix with Perl.
857
858 Imapsync works under most Windows (2000, XP, Vista, Seven, Eight, Ten
859 and all Server releases 2000, 2003, 2008 and R2, 2012 and R2, 2016)
860 as a standalone binary software called imapsync.exe,
861 usually launched from a batch file in order to avoid always typing
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200862 the options. There is also a 32bit binary called imapsync_32bit.exe
863
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100864 Imapsync works under OS X as a standalone binary
865 software called imapsync_bin_Darwin
866
867 Purchase latest imapsync at
868 https://imapsync.lamiral.info/
869
870 You'll receive a link to a compressed tarball called imapsync-x.xx.tgz
871 where x.xx is the version number. Untar the tarball where
872 you want (on Unix):
873
874 tar xzvf imapsync-x.xx.tgz
875
876 Go into the directory imapsync-x.xx and read the INSTALL file.
877 As mentioned at https://imapsync.lamiral.info/#install
878 the INSTALL file can also be found at
879 https://imapsync.lamiral.info/INSTALL.d/INSTALL.ANY.txt
880 It is now split in several files for each system
881 https://imapsync.lamiral.info/INSTALL.d/
882
883=head1 CONFIGURATION
884
885There is no specific configuration file for imapsync,
886everything is specified by the command line parameters
887and the default behavior.
888
889
890=head1 HACKING
891
892Feel free to hack imapsync as the NOLIMIT license permits it.
893
894
895=head1 SIMILAR SOFTWARE
896
897 See also https://imapsync.lamiral.info/S/external.shtml
898 for a better up to date list.
899
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200900List verified on Friday July 1, 2021.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100901
902 imapsync: https://github.com/imapsync/imapsync (this is an imapsync copy, sometimes delayed, with --noreleasecheck by default since release 1.592, 2014/05/22)
903 imap_tools: https://web.archive.org/web/20161228145952/http://www.athensfbc.com/imap_tools/. The imap_tools code is now at https://github.com/andrewnimmo/rick-sanders-imap-tools
904 imaputils: https://github.com/mtsatsenko/imaputils (very old imap_tools fork)
905 Doveadm-Sync: https://wiki2.dovecot.org/Tools/Doveadm/Sync ( Dovecot sync tool )
906 davmail: http://davmail.sourceforge.net/
907 offlineimap: http://offlineimap.org/
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200908 fdm: https://github.com/nicm/fdm
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100909 mbsync: http://isync.sourceforge.net/
910 mailsync: http://mailsync.sourceforge.net/
911 mailutil: https://www.washington.edu/imap/ part of the UW IMAP toolkit. (well, seems abandoned now)
912 imaprepl: https://bl0rg.net/software/ http://freecode.com/projects/imap-repl/
913 imapcopy (Pascal): http://www.ardiehl.de/imapcopy/
914 imapcopy (Java): https://code.google.com/archive/p/imapcopy/
915 imapsize: http://www.broobles.com/imapsize/
916 migrationtool: http://sourceforge.net/projects/migrationtool/
917 imapmigrate: http://sourceforge.net/projects/cyrus-utils/
918 larch: https://github.com/rgrove/larch (derived from wonko_imapsync, good at Gmail)
919 wonko_imapsync: http://wonko.com/article/554 (superseded by larch)
920 pop2imap: http://www.linux-france.org/prj/pop2imap/ (I wrote that too)
921 exchange-away: http://exchange-away.sourceforge.net/
922 SyncBackPro: http://www.2brightsparks.com/syncback/sbpro.html
923 ImapSyncClient: https://github.com/ridaamirini/ImapSyncClient
924 MailStore: https://www.mailstore.com/en/products/mailstore-home/
925 mnIMAPSync: https://github.com/manusa/mnIMAPSync
926 imap-upload: http://imap-upload.sourceforge.net/ (A tool for uploading a local mbox file to IMAP4 server)
927 imapbackup: https://github.com/rcarmo/imapbackup (A Python script for incremental backups of IMAP mailboxes)
928 BitRecover email-backup 99 USD, 299 USD https://www.bitrecover.com/email-backup/.
929 ImportExportTools: https://addons.thunderbird.net/en-us/thunderbird/addon/importexporttools/ ImportExportTools for Mozilla Thunderbird by Paolo Kaosmos. ImportExportTools does not do IMAP.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200930 rximapmail: https://sourceforge.net/projects/rximapmail/
931 CodeTwo: https://www.codetwo.com/ but CodeTwo does imap source to Office365 only.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100932
933=head1 HISTORY
934
935I initially wrote imapsync in July 2001 because an enterprise,
936called BaSystemes, paid me to install a new imap server
937without losing huge old mailboxes located in a far
938away remote imap server, accessible by an
939often broken low-bandwidth ISDN link.
940
941I had to verify every mailbox was well transferred, all folders, all messages,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200942without wasting bandwidth or creating duplicates upon resyncs. The imapsync
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100943design was made with the beautiful rsync command in mind.
944
945Imapsync started its life as a patch of the copy_folder.pl
946script. The script copy_folder.pl comes from the Mail-IMAPClient-2.1.3 perl
947module tarball source (more precisely in the examples/ directory of the
948Mail-IMAPClient tarball).
949
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200950So many changes happened since then that I wonder
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100951if it remains any lines of the original
952copy_folder.pl in imapsync source code.
953
954
955=cut
956
957
958# use pragmas
959#
960
961use strict ;
962use warnings ;
963use Carp ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200964use Cwd ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100965use Compress::Zlib ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100966use Data::Dumper ;
967use Digest::HMAC_SHA1 qw( hmac_sha1 hmac_sha1_hex ) ;
968use Digest::MD5 qw( md5 md5_hex md5_base64 ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200969use Encode ;
970use Encode::IMAPUTF7 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100971use English qw( -no_match_vars ) ;
972use Errno qw(EAGAIN EPIPE ECONNRESET) ;
973use Fcntl ;
974use File::Basename ;
975use File::Copy::Recursive ;
976use File::Glob qw( :glob ) ;
977use File::Path qw( mkpath rmtree ) ;
978use File::Spec ;
979use File::stat ;
980use Getopt::Long ( ) ;
981use IO::File ;
982use IO::Socket qw( :crlf SOL_SOCKET SO_KEEPALIVE ) ;
983use IO::Socket::INET6 ;
984use IO::Socket::SSL ;
985use IO::Tee ;
986use IPC::Open3 'open3' ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200987#use locale ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100988use Mail::IMAPClient 3.30 ;
989use MIME::Base64 ;
990use Pod::Usage qw(pod2usage) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200991use POSIX qw( uname SIGALRM :sys_wait_h ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100992use Sys::Hostname ;
993use Term::ReadKey ;
994use Test::More ;
995use Time::HiRes qw( time sleep ) ;
996use Time::Local ;
997use Unicode::String ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100998use Readonly ;
999use Sys::MemInfo ;
1000use Regexp::Common ;
1001use Text::ParseWords ; # for quotewords()
1002use File::Tail ;
1003
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001004
1005
1006local $OUTPUT_AUTOFLUSH = 1 ;
1007
1008# constants
1009
1010# Let us do like sysexits.h
1011# /usr/include/sysexits.h
1012# and https://www.tldp.org/LDP/abs/html/exitcodes.html
1013
1014# Should avoid 2 126 127 128..128+64=192 255
1015# Should use 0 1 3..125 193..254
1016
1017Readonly my $EX_OK => 0 ; #/* successful termination */
1018Readonly my $EX_USAGE => 64 ; #/* command line usage error */
1019#Readonly my $EX_DATAERR => 65 ; #/* data format error */
1020Readonly my $EX_NOINPUT => 66 ; #/* cannot open input */
1021#Readonly my $EX_NOUSER => 67 ; #/* addressee unknown */
1022#Readonly my $EX_NOHOST => 68 ; #/* host name unknown */
1023Readonly my $EX_UNAVAILABLE => 69 ; #/* service unavailable */
1024Readonly my $EX_SOFTWARE => 70 ; #/* internal software error */
1025#Readonly my $EX_OSERR => 71 ; #/* system error (e.g., can't fork) */
1026#Readonly my $EX_OSFILE => 72 ; #/* critical OS file missing */
1027#Readonly my $EX_CANTCREAT => 73 ; #/* can't create (user) output file */
1028#Readonly my $EX_IOERR => 74 ; #/* input/output error */
1029#Readonly my $EX_TEMPFAIL => 75 ; #/* temp failure; user is invited to retry */
1030#Readonly my $EX_PROTOCOL => 76 ; #/* remote error in protocol */
1031#Readonly my $EX_NOPERM => 77 ; #/* permission denied */
1032#Readonly my $EX_CONFIG => 78 ; #/* configuration error */
1033
1034# Mine
1035Readonly my $EXIT_CATCH_ALL => 1 ; # Any other error
1036Readonly my $EXIT_BY_SIGNAL => 6 ; # Should be 128+n where n is the sig_num
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001037Readonly my $EXIT_BY_FILE => 7 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001038Readonly my $EXIT_PID_FILE_ERROR => 8 ;
1039Readonly my $EXIT_CONNECTION_FAILURE => 10 ;
1040Readonly my $EXIT_TLS_FAILURE => 12 ;
1041Readonly my $EXIT_AUTHENTICATION_FAILURE => 16 ;
1042Readonly my $EXIT_SUBFOLDER1_NO_EXISTS => 21 ;
1043Readonly my $EXIT_WITH_ERRORS => 111 ;
1044Readonly my $EXIT_WITH_ERRORS_MAX => 112 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001045Readonly my $EXIT_OVERQUOTA => 113 ;
1046Readonly my $EXIT_ERR_APPEND => 114 ;
1047Readonly my $EXIT_ERR_FETCH => 115 ;
1048Readonly my $EXIT_ERR_CREATE => 116 ;
1049Readonly my $EXIT_ERR_SELECT => 117 ;
1050Readonly my $EXIT_TRANSFER_EXCEEDED => 118 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001051
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001052Readonly my $EXIT_ERR_APPEND_VIRUS => 119 ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001053Readonly my $EXIT_ERR_FLAGS => 120 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001054
1055Readonly my $EXIT_TESTS_FAILED => 254 ; # Like Test::More API
1056
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001057Readonly my $EXIT_CONNECTION_FAILURE_HOST1 => 101 ;
1058Readonly my $EXIT_CONNECTION_FAILURE_HOST2 => 102 ;
1059Readonly my $EXIT_AUTHENTICATION_FAILURE_USER1 => 161 ;
1060Readonly my $EXIT_AUTHENTICATION_FAILURE_USER2 => 162 ;
1061
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001062
1063Readonly my %EXIT_TXT => (
1064 $EX_OK => 'EX_OK: successful termination',
1065 $EX_USAGE => 'EX_USAGE: command line usage error',
1066 $EX_NOINPUT => 'EX_NOINPUT: cannot open input',
1067 $EX_UNAVAILABLE => 'EX_UNAVAILABLE: service unavailable',
1068 $EX_SOFTWARE => 'EX_SOFTWARE: internal software error',
1069
1070 $EXIT_CATCH_ALL => 'EXIT_CATCH_ALL',
1071 $EXIT_BY_SIGNAL => 'EXIT_BY_SIGNAL',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001072 $EXIT_BY_FILE => 'EXIT_BY_FILE',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001073 $EXIT_PID_FILE_ERROR => 'EXIT_PID_FILE_ERROR' ,
1074 $EXIT_CONNECTION_FAILURE => 'EXIT_CONNECTION_FAILURE',
1075 $EXIT_TLS_FAILURE => 'EXIT_TLS_FAILURE',
1076 $EXIT_AUTHENTICATION_FAILURE => 'EXIT_AUTHENTICATION_FAILURE',
1077 $EXIT_SUBFOLDER1_NO_EXISTS => 'EXIT_SUBFOLDER1_NO_EXISTS',
1078 $EXIT_WITH_ERRORS => 'EXIT_WITH_ERRORS',
1079 $EXIT_WITH_ERRORS_MAX => 'EXIT_WITH_ERRORS_MAX',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001080 $EXIT_OVERQUOTA => 'EXIT_OVERQUOTA',
1081 $EXIT_ERR_APPEND => 'EXIT_ERR_APPEND',
1082 $EXIT_ERR_APPEND_VIRUS => 'EXIT_ERR_APPEND_VIRUS',
1083 $EXIT_ERR_FETCH => 'EXIT_ERR_FETCH',
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001084 $EXIT_ERR_FLAGS => 'EXIT_ERR_FLAGS',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001085 $EXIT_ERR_CREATE => 'EXIT_ERR_CREATE',
1086 $EXIT_ERR_SELECT => 'EXIT_ERR_SELECT',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001087 $EXIT_TESTS_FAILED => 'EXIT_TESTS_FAILED',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001088 $EXIT_TRANSFER_EXCEEDED => 'EXIT_TRANSFER_EXCEEDED',
1089 $EXIT_CONNECTION_FAILURE_HOST1 => 'EXIT_CONNECTION_FAILURE_HOST1',
1090 $EXIT_CONNECTION_FAILURE_HOST2 => 'EXIT_CONNECTION_FAILURE_HOST2',
1091 $EXIT_AUTHENTICATION_FAILURE_USER1 => 'EXIT_AUTHENTICATION_FAILURE_USER1',
1092 $EXIT_AUTHENTICATION_FAILURE_USER2 => 'EXIT_AUTHENTICATION_FAILURE_USER2',
1093) ;
1094
1095
1096Readonly my %EXIT_VALUE_OF_ERR_TYPE => (
1097 ERR_APPEND_SIZE => $EXIT_ERR_APPEND,
1098 ERR_OVERQUOTA => $EXIT_OVERQUOTA,
1099 ERR_APPEND => $EXIT_ERR_APPEND,
1100 ERR_APPEND_VIRUS => $EXIT_ERR_APPEND_VIRUS,
1101 ERR_CREATE => $EXIT_ERR_CREATE,
1102 ERR_SELECT => $EXIT_ERR_SELECT,
1103 ERR_Host1_FETCH => $EXIT_ERR_FETCH,
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001104 ERR_FLAGS => $EXIT_ERR_FLAGS,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001105 ERR_UNCLASSIFIED => $EXIT_WITH_ERRORS,
1106 ERR_NOTHING_REPORTED => $EXIT_WITH_ERRORS,
1107 ERR_TRANSFER_EXCEEDED => $EXIT_TRANSFER_EXCEEDED,
1108 ERR_CONNECTION_FAILURE_HOST1 => $EXIT_CONNECTION_FAILURE_HOST1,
1109 ERR_CONNECTION_FAILURE_HOST2 => $EXIT_CONNECTION_FAILURE_HOST2,
1110 ERR_AUTHENTICATION_FAILURE_USER1 => $EXIT_AUTHENTICATION_FAILURE_USER1,
1111 ERR_AUTHENTICATION_FAILURE_USER2 => $EXIT_AUTHENTICATION_FAILURE_USER2,
1112 ERR_EXIT_TLS_FAILURE => $EXIT_TLS_FAILURE,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001113) ;
1114
1115
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001116
1117Readonly my %COMMENT_OF_ERR_TYPE => (
1118 ERR_APPEND_SIZE => \&comment_err_append_size,
1119 ERR_OVERQUOTA => \&comment_err_overquota,
1120 ERR_APPEND => \&comment_err_blank,
1121 ERR_APPEND_VIRUS => \&comment_err_blank,
1122 ERR_CREATE => \&comment_err_blank,
1123 ERR_SELECT => \&comment_err_blank,
1124 ERR_Host1_FETCH => \&comment_err_blank,
1125 ERR_FLAGS => \&comment_err_flags,
1126 ERR_UNCLASSIFIED => \&comment_err_blank,
1127 ERR_NOTHING_REPORTED => \&comment_err_blank,
1128 ERR_TRANSFER_EXCEEDED => \&comment_err_transfer_exceeded,
1129 ERR_CONNECTION_FAILURE_HOST1 => \&comment_err_connection_failure_host1,
1130 ERR_CONNECTION_FAILURE_HOST2 => \&comment_err_connection_failure_host2,
1131 ERR_AUTHENTICATION_FAILURE_USER1 => \&comment_err_authentication_failure_host1,
1132 ERR_AUTHENTICATION_FAILURE_USER2 => \&comment_err_authentication_failure_host2,
1133 ERR_EXIT_TLS_FAILURE => \&comment_err_blank,
1134) ;
1135
1136
1137sub comment_err_blank
1138{
1139 return '' ;
1140}
1141
1142
1143sub comment_err_append_size
1144{
1145 my $mysync = shift @ARG ;
1146
1147 my $comment = "The destination server refuses too big messages. Use --truncmess option. Read https://imapsync.lamiral.info/FAQ.d/FAQ.Messages_Too_Big.txt" ;
1148 return $comment ;
1149}
1150
1151
1152sub comment_err_authentication_failure_host1
1153{
1154 my $mysync = shift @ARG ;
1155
1156 my $comment = "Check the credentials for $mysync->{ user1 }." ;
1157 return $comment ;
1158}
1159
1160sub comment_err_authentication_failure_host2
1161{
1162 my $mysync = shift @ARG ;
1163
1164 my $comment = "Check the credentials for $mysync->{ user2 }." ;
1165 return $comment ;
1166}
1167
1168
1169sub comment_err_connection_failure_host1
1170{
1171 my $mysync = shift @ARG ;
1172
1173 my $comment = "Check that host1 $mysync->{ host1 } on port $mysync->{ port1 } is the right IMAP server to be contacted for your mailbox." ;
1174 return $comment ;
1175}
1176
1177sub comment_err_connection_failure_host2
1178{
1179 my $mysync = shift @ARG ;
1180
1181 my $comment = "Check that host1 $mysync->{ host2 } on port $mysync->{ port2 } is the right IMAP server to be contacted for your mailbox." ;
1182 return $comment ;
1183}
1184
1185sub comment_err_overquota
1186{
1187 my $mysync = shift @ARG ;
1188
1189 my $comment = 'The destination mailbox is 100% full, get free space on it and then resume the sync.' ;
1190 return $comment ;
1191}
1192
1193
1194sub comment_err_transfer_exceeded
1195{
1196 my $mysync = shift @ARG ;
1197
1198 my $size_limit_human = bytes_display_string_dec( $mysync->{ exitwhenover } ) ;
1199 my $comment = "The maximum transfer size for a single sync is reached ( over $size_limit_human ). Relaunch the sync to sync more." ;
1200 return $comment ;
1201}
1202
1203sub comment_err_flags
1204{
1205 my $mysync = shift @ARG ;
1206
1207 my $comment = 'Many STORE errors with FLAGS. Retry with the option --noresyncflags' ;
1208 return $comment ;
1209}
1210
1211
1212
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001213Readonly my $DEFAULT_LOGDIR => 'LOG_imapsync' ;
1214
1215Readonly my $ERRORS_MAX => 50 ; # exit after 50 errors.
1216Readonly my $ERRORS_MAX_CGI => 20 ; # exit after 20 errors in CGI context.
1217
1218
1219
1220Readonly my $INTERVAL_TO_EXIT => 2 ; # interval max to exit instead of reconnect
1221
1222Readonly my $SPLIT => 100 ; # By default, 100 at a time, not more.
1223Readonly my $SPLIT_FACTOR => 10 ; # init_imap() calls Maxcommandlength( $SPLIT_FACTOR * $split )
1224 # which means default Maxcommandlength is 10*100 = 1000 characters ;
1225
1226Readonly my $IMAP_PORT => 143 ; # Well know port for IMAP
1227Readonly my $IMAP_SSL_PORT => 993 ; # Well know port for IMAP over SSL
1228
1229Readonly my $LAST => -1 ;
1230Readonly my $MINUS_ONE => -1 ;
1231Readonly my $MINUS_TWO => -2 ;
1232
1233Readonly my $RELEASE_NUMBER_EXAMPLE_1 => '1.351' ;
1234Readonly my $RELEASE_NUMBER_EXAMPLE_2 => 42.4242 ;
1235
1236Readonly my $TCP_PING_TIMEOUT => 5 ;
1237Readonly my $DEFAULT_TIMEOUT => 120 ;
1238Readonly my $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND => 3 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001239
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001240Readonly my $DEFAULT_BUFFER_SIZE => 4096 ;
1241
1242Readonly my $MAX_SLEEP => 2 ; # 2 seconds max for limiting too long sleeps from --maxbytespersecond and --maxmessagespersecond
1243
1244Readonly my $DEFAULT_EXPIRATION_TIME_OAUTH2_PK12 => 3600 ;
1245
1246Readonly my $PERMISSION_FILTER => 7777 ;
1247
1248Readonly my $KIBI => 1024 ;
1249
1250Readonly my $NUMBER_10 => 10 ;
1251Readonly my $NUMBER_42 => 42 ;
1252Readonly my $NUMBER_100 => 100 ;
1253Readonly my $NUMBER_200 => 200 ;
1254Readonly my $NUMBER_300 => 300 ;
1255Readonly my $NUMBER_123456 => 123_456 ;
1256Readonly my $NUMBER_654321 => 654_321 ;
1257
1258Readonly my $NUMBER_20_000 => 20_000 ;
1259
1260Readonly my $QUOTA_PERCENT_LIMIT => 90 ;
1261
1262Readonly my $NUMBER_104_857_600 => 104_857_600 ;
1263
1264Readonly my $SIZE_MAX_STR => 64 ;
1265
1266Readonly my $NB_SECONDS_IN_A_DAY => 86_400 ;
1267
1268Readonly my $STD_CHAR_PER_LINE => 80 ;
1269
1270Readonly my $TRUE => 1 ;
1271Readonly my $FALSE => 0 ;
1272
1273Readonly my $LAST_RESSORT_SEPARATOR => q{/} ;
1274
1275Readonly my $CGI_TMPDIR_TOP => '/var/tmp/imapsync_cgi' ;
1276Readonly my $CGI_HASHFILE => '/var/tmp/imapsync_hash' ;
1277Readonly my $UMASK_PARANO => '0077' ;
1278
1279Readonly my $STR_use_releasecheck => q{Check if a new imapsync release is available by adding --releasecheck} ;
1280
1281Readonly my $GMAIL_MAXSIZE => 35_651_584 ;
1282
1283Readonly my $FORCE => 1 ;
1284
1285# if ( 'MSWin32' eq $OSNAME )
1286# if ( 'darwin' eq $OSNAME )
1287# if ( 'linux' eq $OSNAME )
1288
1289
1290
1291# global variables
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001292# Currently working to finish with only $sync, $acc1, $acc2
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001293# Not finished yet...
1294
1295my(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001296 $sync, $acc1, $acc2,
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001297 $debugflags,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001298 $debuglist, $debugdev, $debugmaxlinelength, $debugcgi,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001299 @include, @exclude, @folderrec,
1300 @folderfirst, @folderlast,
1301 @h1_folders_all, %h1_folders_all,
1302 @h2_folders_all, %h2_folders_all,
1303 @h2_folders_from_1_wanted, %h2_folders_from_1_all,
1304 %requested_folder,
1305 $h1_folders_wanted_nb, $h1_folders_wanted_ct,
1306 @h2_folders_not_in_1,
1307 %h1_subscribed_folder, %h2_subscribed_folder,
1308 %h2_folders_from_1_wanted,
1309 %h2_folders_from_1_several,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001310 $prefix1, $prefix2,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001311 @regexmess, @skipmess, @pipemess, $pipemesscheck,
1312 $syncflagsaftercopy,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001313 $syncinternaldates,
1314 $idatefromheader,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001315 $minsize, $maxage, $minage,
1316 $search,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001317 @useheader, %useheader,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001318 $skipsize, $allowsizemismatch, $buffersize,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001319 $authmd5, $authmd51, $authmd52,
1320 $subscribed, $subscribe, $subscribeall,
1321 $help,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001322 $nb_msg_skipped_dry_mode,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001323 $h2_nb_msg_noheader,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001324 $h1_bytes_processed,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001325 $h1_nb_msg_end, $h1_bytes_end,
1326 $h2_nb_msg_end, $h2_bytes_end,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001327 $timestart_int,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001328 $uid1, $uid2,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001329 $split1, $split2,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001330 $modulesversion,
1331 $delete2folders, $delete2foldersonly, $delete2foldersbutnot,
1332 $usecache, $debugcache, $cacheaftercopy,
1333 $wholeheaderifneeded, %h1_msgs_copy_by_uid, $useuid, $h2_uidguess,
1334 $checkmessageexists,
1335 $messageidnodomain,
1336 $fixInboxINBOX,
1337 $maxlinelength, $maxlinelengthcmd,
1338 $minmaxlinelength,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001339 $fixcolonbug,
1340 $create_folder_old,
1341 $skipcrossduplicates, $debugcrossduplicates,
1342 $disarmreadreceipts,
1343 $mixfolders,
1344 $fetch_hash_set,
1345 $cgidir,
1346 %month_abrev,
1347 $SSL_VERIFY_POLICY,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001348) ;
1349
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001350single_sync( $sync, $acc1, $acc2 );
1351
1352
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001353
1354sub single_sync
1355{
1356
1357# main program
1358# global variables initialization
1359
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001360# I'm currently removing all global variables except $sync $acc1 $acc2
1361# passing each of them under
1362# $sync->{variable_name}
1363# or $acc1->{variable_name}
1364# or $acc1->{variable_name}
1365
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001366#
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001367$acc1 = {} ;
1368$acc2 = {} ;
1369$sync->{ acc1 } = $acc1 ;
1370$sync->{ acc2 } = $acc2 ;
1371
1372$acc1->{ Side } = 'Host1' ;
1373$acc2->{ Side } = 'Host2' ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001374$acc1->{ N } = '1' ;
1375$acc2->{ N } = '2' ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001376
1377$sync->{timestart} = time ; # Is a float because of use Time::HiRres
1378
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001379$sync->{rcs} = q{$Id: imapsync,v 2.178 2022/01/12 21:28:37 gilles Exp gilles $} ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001380
1381$sync->{ memory_consumption_at_start } = memory_consumption( ) || 0 ;
1382
1383
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001384
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001385my @loadavg = loadavg( ) ;
1386
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001387$sync->{ cpu_number } = cpu_number( ) ;
1388$sync->{ loaddelay } = load_and_delay( $sync->{ cpu_number }, @loadavg ) ;
1389$sync->{ loaddelay } = 0 ;
1390
1391$sync->{ loadavg } = join( q{ }, $loadavg[ 0 ] )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001392 . " on $sync->{cpu_number} cores and "
1393 . ram_memory_info( ) ;
1394
1395
1396
1397$sync->{ total_bytes_transferred } = 0 ;
1398$sync->{ total_bytes_skipped } = 0 ;
1399$sync->{ nb_msg_transferred } = 0 ;
1400$sync->{ nb_msg_skipped } = $nb_msg_skipped_dry_mode = 0 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001401
1402$sync->{ acc1 }->{ nb_msg_deleted } = 0 ;
1403$sync->{ acc2 }->{ nb_msg_deleted } = 0 ;
1404
1405$sync->{ acc1 }->{ nb_msg_duplicate } = 0 ;
1406$sync->{ acc2 }->{ nb_msg_duplicate } = 0 ;
1407
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001408$sync->{ h1_nb_msg_noheader } = 0 ;
1409$h2_nb_msg_noheader = 0 ;
1410
1411
1412$sync->{ h1_nb_msg_start } = 0 ;
1413$sync->{ h1_bytes_start } = 0 ;
1414$sync->{ h2_nb_msg_start } = 0 ;
1415$sync->{ h2_bytes_start } = 0 ;
1416$sync->{ h1_nb_msg_processed } = $h1_bytes_processed = 0 ;
1417
1418$sync->{ h2_nb_msg_crossdup } = 0 ;
1419
1420#$h1_nb_msg_end = $h1_bytes_end = 0 ;
1421#$h2_nb_msg_end = $h2_bytes_end = 0 ;
1422
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001423$sync->{ nb_errors } = 0;
1424$sync->{ biggest_message_transferred } = 0;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001425
1426%month_abrev = (
1427 Jan => '00',
1428 Feb => '01',
1429 Mar => '02',
1430 Apr => '03',
1431 May => '04',
1432 Jun => '05',
1433 Jul => '06',
1434 Aug => '07',
1435 Sep => '08',
1436 Oct => '09',
1437 Nov => '10',
1438 Dec => '11',
1439);
1440
1441
1442
1443# Just create a CGI object if under cgi context only.
1444# Needed for the get_options() call
1445cgibegin( $sync ) ;
1446
1447# In cgi context, printing must start by the header so we delay other prints by using output() storage
1448my $options_good = get_options( $sync, @ARGV ) ;
1449# Is it the first myprint?
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001450cgibuildheader( $sync ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001451docker_context( $sync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001452
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001453print_output_if_needed( $sync ) ;
1454
1455
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001456output_reset_with( $sync ) ;
1457
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001458# don't go on if options are not all known.
1459if ( ! defined $options_good ) { exit $EX_USAGE ; }
1460
1461# If you want releasecheck not to be done by default (like the github maintainer),
1462# then just uncomment the first "$sync->{releasecheck} =" line, the line ending with "0 ;",
1463# the second line (ending with "1 ;") can then stay active or be commented,
1464# the result will be the same: no releasecheck by default (because 0 is then the defined value).
1465
1466#$sync->{releasecheck} = defined $sync->{releasecheck} ? $sync->{releasecheck} : 0 ;
1467$sync->{releasecheck} = defined $sync->{releasecheck} ? $sync->{releasecheck} : 1 ;
1468
1469# just the version
1470if ( $sync->{ version } ) {
1471 myprint( imapsync_version( $sync ), "\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001472 return 0 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001473}
1474
1475#$sync->{debugenv} = 1 ;
1476$sync->{debugenv} and printenv( $sync ) ; # if option --debugenv
1477load_modules( ) ;
1478
1479# after_get_options call usage and exit if --help or options were not well got
1480after_get_options( $sync, $options_good ) ;
1481
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001482#local $ENV{TZ} = 'GMT' if ( under_cgi_context( $sync ) and 'MSWin32' ne $OSNAME ) ;
1483#output( $sync, localtime(time) . " " . gmtime(time) . "\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001484
1485# Under CGI environment, fix caveat emptor potential issues
1486cgisetcontext( $sync ) ;
1487
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001488get_options_extra( $sync ) ;
1489
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001490# --gmail --gmail --exchange --office etc.
1491easyany( $sync ) ;
1492
1493$sync->{ sanitize } = defined $sync->{ sanitize } ? $sync->{ sanitize } : 1 ;
1494sanitize( $sync ) ;
1495
1496$sync->{ tmpdir } ||= File::Spec->tmpdir( ) ;
1497
1498# Unit tests
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001499my $unittestssuite = unittestssuite( $sync ) ;
1500
1501
1502if ( condition_to_leave_after_tests( $sync ) )
1503{
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001504 return $unittestssuite ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001505}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001506
1507# init live varaiables
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001508
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001509if ( $sync->{ testslive } )
1510{
1511 testslive_init( $sync ) ;
1512}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001513
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001514if ( $sync->{ testslive6 } )
1515{
1516 testslive6_init( $sync ) ;
1517}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001518
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001519define_pidfile( $sync ) ;
1520if ( $sync->{ abortbyfile } ) { $sync->{ abort } = 1 ; }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001521
1522install_signals( $sync ) ;
1523
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001524$sync->{ log } = defined $sync->{ log } ? $sync->{ log } : 1 ;
1525$sync->{ errorsdump } = defined $sync->{ errorsdump } ? $sync->{ errorsdump } : 1 ;
1526$sync->{ errorsmax } = defined $sync->{ errorsmax } ? $sync->{ errorsmax } : $ERRORS_MAX ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001527
1528# log and output
1529binmode STDOUT, ":encoding(UTF-8)" ;
1530
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001531
1532if ( $sync->{ log } ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001533 setlogfile( $sync ) ;
1534 teelaunch( $sync ) ;
1535 # now $sync->{tee} is a filehandle to STDOUT and the logfile
1536}
1537
1538#binmode STDERR, ":encoding(UTF-8)" ;
1539# STDERR goes to the same place: LOG and STDOUT (if logging is on)
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001540# Useful only for --debugssl
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001541$sync->{tee} and local *STDERR = *${$sync->{tee}}{IO} ;
1542
1543
1544
1545$timestart_int = int( $sync->{timestart} ) ;
1546$sync->{timebefore} = $sync->{timestart} ;
1547
1548
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001549$sync->{ timestart_str } = localtimez( $sync->{timestart} ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001550
1551# The prints in the log starts here
1552
1553myprint( localhost_info( $sync ), "\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001554myprint( "Transfer started at $sync->{ timestart_str }\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001555myprint( "PID is $PROCESS_ID my PPID is ", mygetppid( ), "\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001556announcelogfile( $sync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001557myprint( "Load is " . ( join( q{ }, loadavg( ) ) || 'unknown' ), " on $sync->{cpu_number} cores\n" ) ;
1558#myprintf( "Memory consumption so far: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI ) ;
1559myprint( 'Current directory is ' . getcwd( ) . "\n" ) ;
1560myprint( 'Real user id is ' . getpwuid_any_os( $REAL_USER_ID ) . " (uid $REAL_USER_ID)\n" ) ;
1561myprint( 'Effective user id is ' . getpwuid_any_os( $EFFECTIVE_USER_ID ). " (euid $EFFECTIVE_USER_ID)\n" ) ;
1562
1563$modulesversion = defined $modulesversion ? $modulesversion : 1 ;
1564
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001565$sync->{ warn_release } = ( $sync->{ releasecheck } ) ? check_last_release( ) : $STR_use_releasecheck ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001566
1567
1568$wholeheaderifneeded = defined $wholeheaderifneeded ? $wholeheaderifneeded : 1;
1569
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001570# Activate --usecache if --useuid is set and there is no --nousecache
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001571$usecache = 1 if ( $useuid and ( ! defined $usecache ) ) ;
1572$cacheaftercopy = 1 if ( $usecache and ( ! defined $cacheaftercopy ) ) ;
1573
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001574
1575
1576
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001577$sync->{ checkfoldersexist } = defined $sync->{ checkfoldersexist } ? $sync->{ checkfoldersexist } : 1 ;
1578$checkmessageexists = defined $checkmessageexists ? $checkmessageexists : 0 ;
1579$sync->{ expungeaftereach } = defined $sync->{ expungeaftereach } ? $sync->{ expungeaftereach } : 1 ;
1580
1581# abletosearch is on by default
1582$sync->{abletosearch} = defined $sync->{abletosearch} ? $sync->{abletosearch} : 1 ;
1583$sync->{abletosearch1} = defined $sync->{abletosearch1} ? $sync->{abletosearch1} : $sync->{abletosearch} ;
1584$sync->{abletosearch2} = defined $sync->{abletosearch2} ? $sync->{abletosearch2} : $sync->{abletosearch} ;
1585$checkmessageexists = 0 if ( not $sync->{abletosearch1} ) ;
1586
1587
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001588$sync->{ trylogin } = defined $sync->{ trylogin } ? $sync->{ trylogin } : 1 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001589$sync->{showpasswords} = defined $sync->{showpasswords} ? $sync->{showpasswords} : 0 ;
1590$sync->{ fixslash2 } = defined $sync->{ fixslash2 } ? $sync->{ fixslash2 } : 1 ;
1591$fixInboxINBOX = defined $fixInboxINBOX ? $fixInboxINBOX : 1 ;
1592$create_folder_old = defined $create_folder_old ? $create_folder_old : 0 ;
1593$mixfolders = defined $mixfolders ? $mixfolders : 1 ;
1594$sync->{automap} = defined $sync->{automap} ? $sync->{automap} : 0 ;
1595
1596$sync->{ delete2duplicates } = 1 if ( $sync->{ delete2 } and ( ! defined $sync->{ delete2duplicates } ) ) ;
1597
1598$sync->{maxmessagespersecond} = defined $sync->{maxmessagespersecond} ? $sync->{maxmessagespersecond} : 0 ;
1599$sync->{maxbytespersecond} = defined $sync->{maxbytespersecond} ? $sync->{maxbytespersecond} : 0 ;
1600
1601$sync->{sslcheck} = defined $sync->{sslcheck} ? $sync->{sslcheck} : 1 ;
1602
1603myprint( banner_imapsync( $sync, @ARGV ) ) ;
1604
1605myprint( "Temp directory is $sync->{ tmpdir } ( to change it use --tmpdir dirpath )\n" ) ;
1606
1607myprint( output( $sync ) ) ;
1608output_reset_with( $sync ) ;
1609
1610do_valid_directory( $sync->{ tmpdir } ) || croak "Error creating tmpdir $sync->{ tmpdir } : $OS_ERROR" ;
1611
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001612remove_pidfile_not_running( $sync->{ pidfile } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001613
1614# if another imapsync is running then tail -f its logfile and exit
1615# useful in cgi context
1616if ( $sync->{ tail } and tail( $sync ) )
1617{
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001618 exit_clean( $sync, $EX_OK, "Tail -f finished. Now finishing myself processus $PROCESS_ID\n" ) ;
1619 exit $EX_OK ;
1620}
1621
1622if ( ! write_pidfile( $sync ) ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001623 myprint( "Exiting with return value $EXIT_PID_FILE_ERROR ($EXIT_TXT{$EXIT_PID_FILE_ERROR}) $sync->{nb_errors}/$sync->{errorsmax} nb_errors/max_errors PID $PROCESS_ID\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001624 exit $EXIT_PID_FILE_ERROR ;
1625}
1626
1627
1628# New place for abort
1629# abort before simulong in order to be able to abort a simulong sync
1630if ( $sync->{ abort } )
1631{
1632 abort( $sync ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001633 # well, the abort job is done, because even when not succeeded
1634 # in aborting another run, this run has to end without doing any
1635 # thing else
1636
1637 exit $EX_OK ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001638}
1639
1640# simulong is just a loop printing some lines for xx seconds with option "--simulong xx".
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001641simulong( $sync ) ;
1642
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001643
1644
1645# New place for cgiload 2019_03_03
1646# because I want to log it
1647# Can break here if load is too heavy
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001648# Have in mind the CGI header has already a 503 Service Unavailable
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001649cgiload( $sync ) ;
1650
1651
1652$fixcolonbug = defined $fixcolonbug ? $fixcolonbug : 1 ;
1653
1654if ( $usecache and $fixcolonbug ) { tmpdir_fix_colon_bug( $sync ) } ;
1655
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001656$modulesversion and myprint( "Modules version list ( use --no-modulesversion to turn off printing this Perl modules list ):\n", modulesversion(), "\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001657
1658
1659check_lib_version( $sync ) or
1660 croak "imapsync needs perl lib Mail::IMAPClient release 3.30 or superior.\n";
1661
1662
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001663
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001664if ( $sync->{ justbanner } )
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001665{
1666 myprint( "Exiting because of --justbanner\n" ) ;
1667 exit_clean( $sync, $EX_OK ) ;
1668}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001669
1670# turn on RFC standard flags correction like \SEEN -> \Seen
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001671$sync->{ flagscase } = defined $sync->{ flagscase } ? $sync->{ flagscase } : 1 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001672
1673# Use PERMANENTFLAGS if available
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001674$sync->{ filterflags } = defined $sync->{ filterflags } ? $sync->{ filterflags } : 1 ;
1675
1676filterbuggyflags( $sync ) ;
1677
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001678
1679# sync flags just after an APPEND, some servers ignore the flags given in the APPEND
1680# like MailEnable IMAP server.
1681# Off by default since it takes time.
1682$syncflagsaftercopy = defined $syncflagsaftercopy ? $syncflagsaftercopy : 0 ;
1683
1684# update flags on host2 for already transferred messages
1685$sync->{resyncflags} = defined $sync->{resyncflags} ? $sync->{resyncflags} : 1 ;
1686if ( $sync->{resyncflags} ) {
1687 myprint( "Info: will resync flags for already transferred messages. Use --noresyncflags to not resync flags.\n" ) ;
1688}else{
1689 myprint( "Info: will not resync flags for already transferred messages. Use --resyncflags to resync flags.\n" ) ;
1690}
1691
1692
1693sslcheck( $sync ) ;
1694#print Data::Dumper->Dump( [ \$sync ] ) ;
1695
1696$split1 ||= $SPLIT ;
1697$split2 ||= $SPLIT ;
1698
1699#$sync->{host1} || missing_option( $sync, '--host1' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001700$sync->{host1} = sanitize_host( $sync->{host1} ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001701$sync->{port1} ||= ( $sync->{ssl1} ) ? $IMAP_SSL_PORT : $IMAP_PORT ;
1702
1703#$sync->{host2} || missing_option( $sync, '--host2' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001704$sync->{host2} = sanitize_host( $sync->{host2} ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001705$sync->{port2} ||= ( $sync->{ssl2} ) ? $IMAP_SSL_PORT : $IMAP_PORT ;
1706
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001707
1708$acc1->{ debugimap } = $acc2->{ debugimap } = 1 if ( $sync->{ debugimap } ) ;
1709# Set on debug mode if one of the imap dialogs are in debug.
1710# imap dialog without the debug mode is scary and useless.
1711$sync->{ debug } = 1 if ( $acc1->{ debugimap } or $acc2->{ debugimap } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001712
1713# By default, don't take size to compare
1714$skipsize = (defined $skipsize) ? $skipsize : 1;
1715
1716$uid1 = defined $uid1 ? $uid1 : 1;
1717$uid2 = defined $uid2 ? $uid2 : 1;
1718
1719$subscribe = defined $subscribe ? $subscribe : 1;
1720
1721# Allow size mismatch by default
1722$allowsizemismatch = defined $allowsizemismatch ? $allowsizemismatch : 1;
1723
1724
1725if ( defined $delete2foldersbutnot or defined $delete2foldersonly ) {
1726 $delete2folders = 1 ;
1727}
1728
1729
1730my %SSL_VERIFY_STR ;
1731
1732Readonly $SSL_VERIFY_POLICY => IO::Socket::SSL::SSL_VERIFY_NONE( ) ;
1733Readonly %SSL_VERIFY_STR => (
1734 IO::Socket::SSL::SSL_VERIFY_NONE( ) => 'SSL_VERIFY_NONE, ie, do not check the certificate server.' ,
1735 IO::Socket::SSL::SSL_VERIFY_PEER( ) => 'SSL_VERIFY_PEER, ie, check the certificate server' ,
1736) ;
1737
1738$IO::Socket::SSL::DEBUG = defined( $sync->{debugssl} ) ? $sync->{debugssl} : 1 ;
1739
1740
1741if ( $sync->{ssl1} or $sync->{ssl2} or $sync->{tls1} or $sync->{tls2}) {
1742 myprint( "SSL debug mode level is --debugssl $IO::Socket::SSL::DEBUG (can be set from 0 meaning no debug to 4 meaning max debug)\n" ) ;
1743}
1744
1745if ( $sync->{ssl1} ) {
1746 myprint( qq{Host1: SSL default mode is like --sslargs1 "SSL_verify_mode=$SSL_VERIFY_POLICY", meaning for host1 $SSL_VERIFY_STR{$SSL_VERIFY_POLICY}\n} ) ;
1747 myprint( 'Host1: Use --sslargs1 SSL_verify_mode=' . IO::Socket::SSL::SSL_VERIFY_PEER( ) . " to have $SSL_VERIFY_STR{IO::Socket::SSL::SSL_VERIFY_PEER( )} of host1\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001748 # $sync->{ acc1 }->{sslargs}->{SSL_verify_mode}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001749}
1750
1751if ( $sync->{ssl2} ) {
1752 myprint( qq{Host2: SSL default mode is like --sslargs2 "SSL_verify_mode=$SSL_VERIFY_POLICY", meaning for host2 $SSL_VERIFY_STR{$SSL_VERIFY_POLICY}\n} ) ;
1753 myprint( 'Host2: Use --sslargs2 SSL_verify_mode=' . IO::Socket::SSL::SSL_VERIFY_PEER( ) . " to have $SSL_VERIFY_STR{IO::Socket::SSL::SSL_VERIFY_PEER( )} of host2\n" ) ;
1754}
1755
1756# ID on by default since 1.832
1757$sync->{id} = defined $sync->{id} ? $sync->{id} : 1 ;
1758
1759if ( $sync->{justconnect}
1760 or not $sync->{user1}
1761 or not $sync->{user2}
1762 or not $sync->{host1}
1763 or not $sync->{host2}
1764 )
1765{
1766 my $justconnect = justconnect( $sync ) ;
1767
1768 myprint( debugmemory( $sync, " after justconnect() call" ) ) ;
1769 exit_clean( $sync, $EX_OK,
1770 "Exiting after a justconnect on host(s): $justconnect\n"
1771 ) ;
1772}
1773
1774
1775#$sync->{user1} || missing_option( $sync, '--user1' ) ;
1776#$sync->{user2} || missing_option( $sync, '--user2' ) ;
1777
1778$syncinternaldates = defined $syncinternaldates ? $syncinternaldates : 1;
1779
1780# Turn on expunge if there is not explicit option --noexpunge1 and option
1781# --delete1 is given.
1782# Done because --delete1 --noexpunge1 is very dangerous on the second run:
1783# the Deleted flag is then synced to all previously transferred messages.
1784# So --delete1 implies --expunge1 is a better usability default behavior.
1785if ( $sync->{ delete1 } ) {
1786 if ( ! defined $sync->{ expunge1 } ) {
1787 myprint( "Info: turning on --expunge1 because --delete1 --noexpunge1 is very dangerous on the second run.\n" ) ;
1788 $sync->{ expunge1 } = 1 ;
1789 }
1790 myprint( "Info: if expunging after each message slows down too much the sync then use --noexpungeaftereach to speed up\n" ) ;
1791}
1792
1793if ( $sync->{ uidexpunge2 } and not Mail::IMAPClient->can( 'uidexpunge' ) ) {
1794 myprint( "Failure: uidexpunge not supported (IMAPClient release < 3.17), use nothing or --expunge2 instead\n" ) ;
1795 $sync->{nb_errors}++ ;
1796 exit_clean( $sync, $EX_SOFTWARE ) ;
1797}
1798
1799if ( ( $sync->{ delete2 } or $sync->{ delete2duplicates } ) and not defined $sync->{ uidexpunge2 } ) {
1800 if ( Mail::IMAPClient->can( 'uidexpunge' ) ) {
1801 myprint( "Info: will act as --uidexpunge2\n" ) ;
1802 $sync->{ uidexpunge2 } = 1 ;
1803 }elsif ( not defined $sync->{ expunge2 } ) {
1804 myprint( "Info: will act as --expunge2 (no uidexpunge support)\n" ) ;
1805 $sync->{ expunge2 } = 1 ;
1806 }
1807}
1808
1809if ( $sync->{ delete1 } and $sync->{ delete2 } ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001810 myprint( "Warning: using --delete1 and --delete2 together is almost always a bad idea. "
1811 . "You should probably launch two runs, the first with --delete2 for a strict sync, "
1812 . "then the second with --delete1 to remove messages from the source account. "
1813 . "Exiting imapsync.\n" ) ;
1814 $sync->{ nb_errors }++ ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001815 exit_clean( $sync, $EX_USAGE ) ;
1816}
1817
1818if ( $idatefromheader ) {
1819 myprint( 'Turned ON idatefromheader, ',
1820 "will set the internal dates on host2 from the 'Date:' header line.\n" ) ;
1821 $syncinternaldates = 0 ;
1822}
1823
1824if ( $syncinternaldates ) {
1825 myprint( 'Info: turned ON syncinternaldates, ',
1826 "will set the internal dates (arrival dates) on host2 same as host1.\n" ) ;
1827}else{
1828 myprint( "Info: turned OFF syncinternaldates\n" ) ;
1829}
1830
1831if ( defined $authmd5 and $authmd5 ) {
1832 $authmd51 = 1 ;
1833 $authmd52 = 1 ;
1834}
1835
1836if ( defined $authmd51 and $authmd51 ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001837 $acc1->{ authmech } ||= 'CRAM-MD5' ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001838}
1839else{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001840 $acc1->{ authmech } ||= $acc1->{ authuser } ? 'PLAIN' : 'LOGIN' ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001841}
1842
1843if ( defined $authmd52 and $authmd52 ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001844 $acc2->{ authmech } ||= 'CRAM-MD5';
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001845}
1846else{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001847 $acc2->{ authmech } ||= $acc2->{ authuser } ? 'PLAIN' : 'LOGIN';
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001848}
1849
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001850$acc1->{ authmech } = uc $acc1->{ authmech } ;
1851$acc2->{ authmech } = uc $acc2->{ authmech } ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001852
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001853if ( defined $acc1->{ proxyauth } && !$acc1->{ authuser } )
1854{
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001855 missing_option( $sync, 'With --proxyauth1, --authuser1' ) ;
1856}
1857
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001858if ( defined $acc2->{ proxyauth } && !$acc2->{ authuser } )
1859{
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001860 missing_option( $sync, 'With --proxyauth2, --authuser2' ) ;
1861}
1862
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001863myprint( "Host1: will try to use $acc1->{ authmech } authentication on host1\n") ;
1864myprint( "Host2: will try to use $acc2->{ authmech } authentication on host2\n") ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001865
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001866$sync->{ timeout } = defined $sync->{ timeout } ?$sync->{ timeout } : $DEFAULT_TIMEOUT ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001867
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001868$sync->{ acc1 }->{timeout} = defined $sync->{ acc1 }->{timeout} ? $sync->{ acc1 }->{timeout} : $sync->{ timeout } ;
1869myprint( "Host1: imap connection timeout is $sync->{ acc1 }->{timeout} seconds\n") ;
1870$sync->{ acc2 }->{timeout} = defined $sync->{ acc2 }->{timeout} ? $sync->{ acc2 }->{timeout} : $sync->{ timeout } ;
1871myprint( "Host2: imap connection timeout is $sync->{ acc2 }->{timeout} seconds\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001872
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001873
1874keepalive1( $sync ) ;
1875keepalive2( $sync ) ;
1876
1877
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001878if ( under_cgi_context( $sync ) )
1879{
1880 myprint( "Under CGI context, a timeout can occur from the webserver, see https://imapsync.lamiral.info/INSTALL.d/INSTALL.OnlineUI.txt\n" ) ;
1881}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001882
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001883$sync->{ syncacls } = defined $sync->{ syncacls } ? $sync->{ syncacls } : 0 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001884
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001885# No folders sizes at the beginning if --justfolders, unless really wanted.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001886if (
1887 $sync->{ justfolders }
1888 and not defined $sync->{ foldersizes }
1889 and not $sync->{ justfoldersizes } )
1890{
1891 $sync->{ foldersizes } = 0 ;
1892 $sync->{ foldersizesatend } = 1 ;
1893}
1894
1895$sync->{ foldersizes } = ( defined $sync->{ foldersizes } ) ? $sync->{ foldersizes } : 1 ;
1896$sync->{ foldersizesatend } = ( defined $sync->{ foldersizesatend } ) ? $sync->{ foldersizesatend } : $sync->{ foldersizes } ;
1897
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001898#$sync->{ checknoabletosearch } = ( defined $sync->{ checknoabletosearch } ) ? $sync->{ checknoabletosearch } : 1 ;
1899set_checknoabletosearch( $sync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001900
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001901
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001902$acc1->{ fastio } = defined $acc1->{ fastio } ? $acc1->{ fastio } : 0 ;
1903$acc2->{ fastio } = defined $acc2->{ fastio } ? $acc2->{ fastio } : 0 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001904
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001905
1906$acc1->{ reconnectretry } = defined $acc1->{ reconnectretry } ? $acc1->{ reconnectretry } : $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ;
1907$acc2->{ reconnectretry } = defined $acc2->{ reconnectretry } ? $acc2->{ reconnectretry } : $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ;
1908
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001909# IMAP compression on by default
1910#$acc1->{ compress } = defined $acc1->{ compress } ? $acc1->{ compress } : 0 ;
1911#$acc2->{ compress } = defined $acc2->{ compress } ? $acc2->{ compress } : 0 ;
1912
1913
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001914
1915if ( ! @useheader ) { @useheader = qw( Message-Id Received ) ; }
1916
1917# Make a hash %useheader of each --useheader 'key' in uppercase
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001918for ( @useheader ) { $sync->{useheader}->{ uc $_ } = undef } ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001919
1920#myprint( Data::Dumper->Dump( [ \%useheader ] ) ) ;
1921#exit ;
1922
1923myprint( "Host1: IMAP server [$sync->{host1}] port [$sync->{port1}] user [$sync->{user1}]\n" ) ;
1924myprint( "Host2: IMAP server [$sync->{host2}] port [$sync->{port2}] user [$sync->{user2}]\n" ) ;
1925
1926get_password1( $sync ) ;
1927get_password2( $sync ) ;
1928
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001929# --dry1 make imapsync not fetching messages from host1, it is on when --dry is on.
1930# Use --dry --nodry1 to make imapsync fetching messages from host1,
1931# It is useful when debugging transformation options like --pipemess or --regexmess
1932$sync->{dry1} = defined $sync->{dry1} ? $sync->{dry1} : $sync->{dry} ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001933
1934$sync->{dry_message} = q{} ;
1935if( $sync->{dry} ) {
1936 $sync->{dry_message} = "\t(not really since --dry mode)" ;
1937}
1938
1939$sync->{ search1 } ||= $search if ( $search ) ;
1940$sync->{ search2 } ||= $search if ( $search ) ;
1941
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001942if ( $disarmreadreceipts )
1943{
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001944 push @regexmess, q{s{\A((?:[^\n]+\r\n)+|)(^Disposition-Notification-To:[^\n]*\n)(\r?\n|.*\n\r?\n)}{$1X-$2$3}ims} ;
1945}
1946
1947$pipemesscheck = ( defined $pipemesscheck ) ? $pipemesscheck : 1 ;
1948
1949if ( @pipemess and $pipemesscheck ) {
1950 myprint( 'Checking each --pipemess command, '
1951 . join( q{, }, @pipemess )
1952 . ", with an space string. ( Can avoid this check with --nopipemesscheck )\n" ) ;
1953 my $string = pipemess( q{ }, @pipemess ) ;
1954 # string undef means something was bad.
1955 if ( not ( defined $string ) ) {
1956 $sync->{nb_errors}++ ;
1957 exit_clean( $sync, $EX_USAGE,
1958 "Error: one of --pipemess command is bad, check it\n"
1959 ) ;
1960 }
1961 myprint( "Ok with each --pipemess @pipemess\n" ) ;
1962}
1963
1964if ( $maxlinelengthcmd ) {
1965 myprint( "Checking --maxlinelengthcmd command,
1966 $maxlinelengthcmd, with an space string.\n"
1967 ) ;
1968 my $string = pipemess( q{ }, $maxlinelengthcmd ) ;
1969 # string undef means something was bad.
1970 if ( not ( defined $string ) ) {
1971 $sync->{nb_errors}++ ;
1972 exit_clean( $sync, $EX_USAGE,
1973 "Error: --maxlinelengthcmd command is bad, check it\n"
1974 ) ;
1975 }
1976 myprint( "Ok with --maxlinelengthcmd $maxlinelengthcmd\n" ) ;
1977}
1978
1979if ( @regexmess ) {
1980 my $string = regexmess( q{ } ) ;
1981 myprint( "Checking each --regexmess command with an space string.\n" ) ;
1982 # string undef means one of the eval regex was bad.
1983 if ( not ( defined $string ) ) {
1984 #errors_incr( $sync, 'Warning: one of --regexmess option may be bad, check them' ) ;
1985 exit_clean( $sync, $EX_USAGE,
1986 "Error: one of --regexmess option is bad, check it\n"
1987 ) ;
1988 }
1989 myprint( "Ok with each --regexmess\n" ) ;
1990}
1991
1992if ( @skipmess ) {
1993 myprint( "Checking each --skipmess command with an space string.\n" ) ;
1994 my $match = skipmess( q{ } ) ;
1995 # match undef means one of the eval regex was bad.
1996 if ( not ( defined $match ) ) {
1997 $sync->{nb_errors}++ ;
1998 exit_clean( $sync, $EX_USAGE,
1999 "Error: one of --skipmess option is bad, check it\n"
2000 ) ;
2001 }
2002 myprint( "Ok with each --skipmess\n" ) ;
2003}
2004
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002005if ( $sync->{ regexflag } ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002006 myprint( "Checking each --regexflag command with an space string.\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002007 my $string = regexflags( $sync, q{ } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002008 # string undef means one of the eval regex was bad.
2009 if ( not ( defined $string ) ) {
2010 $sync->{nb_errors}++ ;
2011 exit_clean( $sync, $EX_USAGE,
2012 "Error: one of --regexflag option is bad, check it\n"
2013 ) ;
2014 }
2015 myprint( "Ok with each --regexflag\n" ) ;
2016}
2017
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002018$sync->{imap1} = login_imap( $sync->{host1}, $sync->{port1}, $sync->{user1}, $sync->{password1},
2019 $sync->{ssl1}, $sync->{tls1},
2020 $uid1, $split1, $sync->{ acc1 }, $sync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002021
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002022$sync->{imap2} = login_imap( $sync->{host2}, $sync->{port2}, $sync->{user2}, $sync->{password2},
2023 $sync->{ssl2}, $sync->{tls2},
2024 $uid2, $split2, $sync->{ acc2 }, $sync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002025
2026
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002027$sync->{ debug } and $sync->{imap1} and myprint( 'Host1 Buffer I/O: ', $sync->{imap1}->Buffer(), "\n" ) ;
2028$sync->{ debug } and $sync->{imap2} and myprint( 'Host2 Buffer I/O: ', $sync->{imap2}->Buffer(), "\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002029
2030
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002031if ( ! $sync->{imap1} || ! $sync->{imap2} )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002032{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002033 exit_most_errors( $sync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002034}
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002035
2036
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002037myprint( "Host1: state Authenticated\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002038myprint( "Host2: state Authenticated\n" ) ;
2039
2040myprint( 'Host1 capability once authenticated: ', join(q{ }, @{ $sync->{imap1}->capability() || [] }), "\n" ) ;
2041
2042#myprint( Data::Dumper->Dump( [ $sync->{imap1} ] ) ) ;
2043#myprint( "imap4rev1: " . $sync->{imap1}->imap4rev1() . "\n" ) ;
2044
2045myprint( 'Host2 capability once authenticated: ', join(q{ }, @{ $sync->{imap2}->capability() || [] }), "\n" ) ;
2046
2047imap_id_stuff( $sync ) ;
2048
2049#quota( $sync, $sync->{imap1}, 'h1' ) ; # quota on host1 is useless and pollute host2 output.
2050quota( $sync, $sync->{imap2}, 'h2' ) ;
2051
2052maxsize_setting( $sync ) ;
2053
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01002054acc_compress_imap( $acc1 ) ;
2055acc_compress_imap( $acc2 ) ;
2056
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002057if ( $sync->{ justlogin } ) {
2058 $sync->{imap1}->logout( ) ;
2059 $sync->{imap2}->logout( ) ;
2060 exit_clean( $sync, $EX_OK, "Exiting because of --justlogin\n" ) ;
2061}
2062
2063
2064#
2065# Folder stuff
2066#
2067
2068$h1_folders_wanted_nb = 0 ; # counter of folders to be done.
2069$h1_folders_wanted_ct = 0 ; # counter of folders done.
2070
2071# All folders on host1 and host2
2072
2073@h1_folders_all = sort $sync->{imap1}->folders( ) ;
2074@h2_folders_all = sort $sync->{imap2}->folders( ) ;
2075
2076myprint( 'Host1: found ', scalar @h1_folders_all , " folders.\n" ) ;
2077myprint( 'Host2: found ', scalar @h2_folders_all , " folders.\n" ) ;
2078
2079foreach my $f ( @h1_folders_all )
2080{
2081 $h1_folders_all{ $f } = 1
2082}
2083
2084foreach my $f ( @h2_folders_all )
2085{
2086 $h2_folders_all{ $f } = 1 ;
2087 $sync->{h2_folders_all_UPPER}{ uc $f } = 1 ;
2088}
2089
2090$sync->{h1_folders_all} = \%h1_folders_all ;
2091$sync->{h2_folders_all} = \%h2_folders_all ;
2092
2093
2094private_folders_separators_and_prefixes( ) ;
2095
2096
2097# Make a hash of subscribed folders in both servers.
2098
2099for ( $sync->{imap1}->subscribed( ) ) { $h1_subscribed_folder{ $_ } = 1 } ;
2100for ( $sync->{imap2}->subscribed( ) ) { $h2_subscribed_folder{ $_ } = 1 } ;
2101
2102
2103if ( defined $sync->{ subfolder1 } ) {
2104 subfolder1( $sync ) ;
2105}
2106
2107
2108
2109
2110if ( defined $sync->{ subfolder2 } ) {
2111 subfolder2( $sync ) ;
2112}
2113
2114if ( $fixInboxINBOX and ( my $reg = fix_Inbox_INBOX_mapping( \%h1_folders_all, \%h2_folders_all ) ) ) {
2115 push @{ $sync->{ regextrans2 } }, $reg ;
2116}
2117
2118
2119
2120if ( ( $sync->{ folder } and scalar @{ $sync->{ folder } } )
2121 or $subscribed
2122 or scalar @folderrec )
2123{
2124 # folders given by option --folder
2125 if ( $sync->{ folder } and scalar @{ $sync->{ folder } } ) {
2126 add_to_requested_folders( @{ $sync->{ folder } } ) ;
2127 }
2128
2129 # option --subscribed
2130 if ( $subscribed ) {
2131 add_to_requested_folders( keys %h1_subscribed_folder ) ;
2132 }
2133
2134 # option --folderrec
2135 if ( scalar @folderrec ) {
2136 foreach my $folderrec ( @folderrec ) {
2137 add_to_requested_folders( $sync->{imap1}->folders( $folderrec ) ) ;
2138 }
2139 }
2140}
2141else
2142{
2143 # no include, no folder/subscribed/folderrec options => all folders
2144 if ( not scalar @include ) {
2145 myprint( "Including all folders found by default. Use --subscribed or --folder or --folderrec or --include to select specific folders. Use --exclude to unselect specific folders.\n" ) ;
2146 add_to_requested_folders( @h1_folders_all ) ;
2147 }
2148}
2149
2150
2151# consider (optional) includes and excludes
2152if ( scalar @include ) {
2153 foreach my $include ( @include ) {
2154 # No, do not add /x after the regex, never.
2155 # Users would kill you!
2156 my @included_folders = grep { /$include/ } @h1_folders_all ;
2157 add_to_requested_folders( @included_folders ) ;
2158 myprint( "Including folders matching pattern $include\n" . jux_utf8_list( @included_folders ) . "\n" ) ;
2159 }
2160}
2161
2162if ( scalar @exclude ) {
2163 foreach my $exclude ( @exclude ) {
2164 my @requested_folder = sort keys %requested_folder ;
2165 # No, do not add /x after the regex, never.
2166 # Users would kill you!
2167 my @excluded_folders = grep { /$exclude/ } @requested_folder ;
2168 remove_from_requested_folders( @excluded_folders ) ;
2169 myprint( "Excluding folders matching pattern $exclude\n" . jux_utf8_list( @excluded_folders ) . "\n" ) ;
2170 }
2171}
2172
2173
2174# sort before is not very powerful
2175# it adds --folderfirst and --folderlast even if they don't exist on host1
2176#@h1_folders_wanted = sort_requested_folders( ) ;
2177$sync->{h1_folders_wanted} = [ sort_requested_folders( ) ] ;
2178
2179# Remove no selectable folders
2180
2181
2182if ( $sync->{ checkfoldersexist } ) {
2183 my @h1_folders_wanted_exist ;
2184 myprint( "Host1: Checking wanted folders exist. Use --nocheckfoldersexist to avoid this check (shared of public namespace targeted).\n" ) ;
2185 foreach my $folder ( @{ $sync->{h1_folders_wanted} } ) {
2186 ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "Checking $folder exists on host1\n" ) ;
2187 if ( ! exists $h1_folders_all{ $folder } ) {
2188 myprint( "Host1: warning! ignoring folder $folder because it is not in host1 whole folders list.\n" ) ;
2189 next ;
2190 }else{
2191 push @h1_folders_wanted_exist, $folder ;
2192 }
2193 }
2194 @{ $sync->{h1_folders_wanted} } = @h1_folders_wanted_exist ;
2195}else{
2196 myprint( "Host1: Not checking that wanted folders exist. Remove --nocheckfoldersexist to get this check.\n" ) ;
2197}
2198
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002199setcheckselectable( $sync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002200
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002201checkselectable( $sync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002202
2203
2204
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002205# Bugfix OpenFind folders named like "kk \*123" are in fact "kk *123" (no \)
2206#foreach my $folder ( @{ $sync->{ h1_folders_wanted } } )
2207#{
2208# $folder =~ s{ \\\*}{ *}g ;
2209#}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002210
2211
2212# this hack is because LWP post does not pass well a hash in the $form parameter
2213# but it does pass well an array
2214%{ $sync->{f1f2h} } = split_around_equal( @{ $sync->{f1f2} } ) ;
2215
2216automap( $sync ) ;
2217
2218
2219foreach my $h1_fold ( @{ $sync->{h1_folders_wanted} } ) {
2220 my $h2_fold ;
2221 $h2_fold = imap2_folder_name( $sync, $h1_fold ) ;
2222 $h2_folders_from_1_wanted{ $h2_fold }++ ;
2223 if ( 1 < $h2_folders_from_1_wanted{ $h2_fold } ) {
2224 $h2_folders_from_1_several{ $h2_fold }++ ;
2225 }
2226}
2227
2228@h2_folders_from_1_wanted = sort keys %h2_folders_from_1_wanted;
2229
2230
2231foreach my $h1_fold ( @h1_folders_all ) {
2232 my $h2_fold ;
2233 $h2_fold = imap2_folder_name( $sync, $h1_fold ) ;
2234 $h2_folders_from_1_all{ $h2_fold }++ ;
2235 # Follows a fix to avoid deleting folder $sync->{ subfolder2 }
2236 # because it usually does not exist on host1.
2237 if ( $sync->{ subfolder2 } )
2238 {
2239 $h2_folders_from_1_all{ $sync->{ h2_prefix } . $sync->{ subfolder2 } }++ ;
2240 $h2_folders_from_1_all{ $sync->{ subfolder2 } }++ ;
2241 }
2242}
2243
2244
2245
2246myprint( << 'END_LISTING' ) ;
2247
2248++++ Listing folders
2249All foldernames are presented between brackets like [X] where X is the foldername.
2250When a foldername contains non-ASCII characters it is presented in the form
2251[X] = [Y] where
2252X is the imap foldername you have to use in command line options and
2253Y is the utf8 output just printed for convenience, to recognize it.
2254
2255END_LISTING
2256
2257myprint(
2258 "Host1: folders list (first the raw imap format then the [X] = [Y]):\n",
2259 $sync->{imap1}->list( ),
2260 "\n",
2261 jux_utf8_list( @h1_folders_all ),
2262 "\n",
2263 "Host2: folders list (first the raw imap format then the [X] = [Y]):\n",
2264 $sync->{imap2}->list( ),
2265 "\n",
2266 jux_utf8_list( @h2_folders_all ),
2267 "\n",
2268 q{}
2269) ;
2270
2271if ( $subscribed ) {
2272 myprint(
2273 'Host1 subscribed folders list: ',
2274 jux_utf8_list( sort keys %h1_subscribed_folder ), "\n",
2275 ) ;
2276}
2277
2278
2279
2280@h2_folders_not_in_1 = list_folders_in_2_not_in_1( ) ;
2281
2282if ( @h2_folders_not_in_1 ) {
2283 myprint( "Folders in host2 not in host1:\n",
2284 jux_utf8_list( @h2_folders_not_in_1 ), "\n" ) ;
2285}
2286
2287
2288if ( keys %{ $sync->{f1f2auto} } ) {
2289 myprint( "Folders mapping from --automap feature (use --f1f2 to override any mapping):\n" ) ;
2290 foreach my $h1_fold ( keys %{ $sync->{f1f2auto} } ) {
2291 my $h2_fold = $sync->{f1f2auto}{$h1_fold} ;
2292 myprintf( "%-40s -> %-40s\n",
2293 jux_utf8( $h1_fold ), jux_utf8( $h2_fold ) ) ;
2294 }
2295 myprint( "\n" ) ;
2296}
2297
2298if ( keys %{ $sync->{f1f2h} } ) {
2299 myprint( "Folders mapping from --f1f2 options, it overrides --automap:\n" ) ;
2300 foreach my $h1_fold ( keys %{ $sync->{f1f2h} } ) {
2301 my $h2_fold = $sync->{f1f2h}{$h1_fold} ;
2302 my $warn = q{} ;
2303 if ( not exists $h1_folders_all{ $h1_fold } ) {
2304 $warn = "BUT $h1_fold does NOT exist on host1!" ;
2305 }
2306 myprintf( "%-40s -> %-40s %s\n",
2307 jux_utf8( $h1_fold ), jux_utf8( $h2_fold ), $warn ) ;
2308 }
2309 myprint( "\n" ) ;
2310}
2311
2312exit_clean( $sync, $EX_OK, "Exiting because of --justfolderlists\n" ) if ( $sync->{ justfolderlists } ) ;
2313exit_clean( $sync, $EX_OK, "Exiting because of --justautomap\n" ) if ( $sync->{ justautomap } ) ;
2314
2315debugsleep( $sync ) ;
2316
2317if ( $sync->{ skipemptyfolders } )
2318{
2319 myprint( "Host1: will not syncing empty folders on host1. Use --noskipemptyfolders to create them anyway on host2\n") ;
2320}
2321
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002322if ( $sync->{ checknoabletosearch } )
2323{
2324 myprint( "Checking SEARCH ALL works on both accounts. To avoid that check, use --nochecknoabletosearch\n" ) ;
2325 my $check1 = checknoabletosearch( $sync, $sync->{ imap1 }, 'INBOX', 'Host1' ) ;
2326 my $check2 = checknoabletosearch( $sync, $sync->{ imap2 }, 'INBOX', 'Host2' ) ;
2327 if ( $check1 or $check2 )
2328 {
2329 myprint( "At least one account can not SEARCH ALL. So acting like --noabletosearch\n" ) ;
2330 $sync->{abletosearch} = 0 ;
2331 $sync->{abletosearch1} = 0 ;
2332 $sync->{abletosearch2} = 0 ;
2333 }
2334 else
2335 {
2336 myprint( "Good! SEARCH ALL works on both accounts.\n" ) ;
2337 }
2338}
2339
2340
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002341
2342if ( $sync->{ foldersizes } ) {
2343
2344 foldersizes_at_the_beggining( $sync ) ;
2345 #foldersizes_at_the_beggining_old( $sync ) ;
2346}
2347
2348
2349
2350if ( $sync->{ justfoldersizes } )
2351{
2352 exit_clean( $sync, $EX_OK, "Exiting because of --justfoldersizes\n" ) ;
2353}
2354
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002355$sync->{can_do_stats} = 1 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002356
2357if ( $sync->{ delete1emptyfolders } ) {
2358 delete1emptyfolders( $sync ) ;
2359}
2360
2361delete_folders_in_2_not_in_1( ) if $delete2folders ;
2362
2363# folder loop
2364$h1_folders_wanted_nb = scalar @{ $sync->{h1_folders_wanted} } ;
2365
2366myprint( "++++ Looping on each one of $h1_folders_wanted_nb folders to sync\n" ) ;
2367
2368$sync->{begin_transfer_time} = time ;
2369
2370my %uid_candidate_for_deletion ;
2371my %uid_candidate_no_deletion ;
2372
2373$sync->{ h2_folders_of_md5 } = { } ;
2374
2375
2376FOLDER: foreach my $h1_fold ( @{ $sync->{h1_folders_wanted} } )
2377{
2378 $sync->{ h1_current_folder } = $h1_fold ;
2379 eta_print( $sync ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002380 abortifneeded( $sync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002381 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2382
2383 my $h2_fold = imap2_folder_name( $sync, $h1_fold ) ;
2384 $sync->{ h2_current_folder } = $h2_fold ;
2385
2386 $h1_folders_wanted_ct++ ;
2387 myprintf( "Folder %7s %-35s -> %-35s\n", "$h1_folders_wanted_ct/$h1_folders_wanted_nb",
2388 jux_utf8( $h1_fold ), jux_utf8( $h2_fold ) ) ;
2389 myprint( debugmemory( $sync, " at folder loop" ) ) ;
2390
2391 # host1 can not be fetched read only, select is needed because of expunge.
2392 select_folder( $sync, $sync->{imap1}, $h1_fold, 'Host1' ) or next FOLDER ;
2393
2394 debugsleep( $sync ) ;
2395
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002396 my $h1_msgs_all_hash_ref ;
2397 my @h1_msgs ;
2398 my $h1_msgs_nb ;
2399 my $h1_msgs_nb_from_select ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002400
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002401 $h1_msgs_nb_from_select = count_from_select( $sync->{imap1}->History ) ;
2402 myprint( "Host1: folder [$h1_fold] has $h1_msgs_nb_from_select messages in total (mentioned by SELECT)\n" ) ;
2403
2404 if ( $sync->{ skipemptyfolders } and 0 == $h1_msgs_nb_from_select ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002405 myprint( "Host1: skipping empty host1 folder [$h1_fold]\n" ) ;
2406 next FOLDER ;
2407 }
2408
2409 # Code added from https://github.com/imapsync/imapsync/issues/95
2410 # Thanks jh1995
2411 # Goal: do not create folder if --search or --max/minage return 0 message.
2412 # even if there are messages by SELECT (no not real empty, empty for the user point of vue).
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002413 if ( $sync->{ skipemptyfolders } or $sync->{ dry } )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002414 {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002415 $h1_msgs_all_hash_ref = { } ;
2416 @h1_msgs = select_msgs( $sync->{imap1}, $h1_msgs_all_hash_ref, $sync->{ search1 }, $sync->{abletosearch1}, $h1_fold ) ;
2417
2418 $h1_msgs_nb = scalar( @h1_msgs ) ;
2419 if ( 0 == $h1_msgs_nb and $sync->{ skipemptyfolders } ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002420 myprint( "Host1: skipping empty host1 folder [$h1_fold] (0 message found by SEARCH)\n" ) ;
2421 next FOLDER ;
2422 }
2423 }
2424
2425 if ( ! exists $h2_folders_all{ $h2_fold } ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002426 # In --dry mode I could count the messages to be transfered instead of 0
2427 # Messages transferred : 0 (could be 0 without dry mode)
2428 if ( ! create_folder( $sync, $sync->{imap2}, $h2_fold, $h1_fold ) )
2429 {
2430 if ( $sync->{ dry } )
2431 {
2432 $nb_msg_skipped_dry_mode += $h1_msgs_nb ;
2433 }
2434 next FOLDER ;
2435 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002436 }
2437
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002438 acls_sync( $sync, $h1_fold, $h2_fold ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002439
2440 # Sometimes the folder on host2 is listed (it exists) but is
2441 # not selectable but becomes selectable by a create (Gmail)
2442 select_folder( $sync, $sync->{imap2}, $h2_fold, 'Host2' )
2443 or ( create_folder( $sync, $sync->{imap2}, $h2_fold, $h1_fold )
2444 and select_folder( $sync, $sync->{imap2}, $h2_fold, 'Host2' ) )
2445 or next FOLDER ;
2446 my @select_results = $sync->{imap2}->Results( ) ;
2447
2448 my $h2_fold_nb_messages = count_from_select( @select_results ) ;
2449 myprint( "Host2: folder [$h2_fold] has $h2_fold_nb_messages messages in total (mentioned by SELECT)\n" ) ;
2450
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01002451 my $permanentflags2 = permanentflags( $sync, @select_results ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002452 myprint( "Host2: folder [$h2_fold] permanentflags: $permanentflags2\n" ) ;
2453
2454 if ( $sync->{ expunge1 } )
2455 {
2456 myprint( "Host1: Expunging $h1_fold $sync->{dry_message}\n" ) ;
2457 if ( ! $sync->{dry} )
2458 {
2459 $sync->{imap1}->expunge( ) ;
2460 }
2461 }
2462
2463 if ( ( ( $subscribe and exists $h1_subscribed_folder{ $h1_fold } ) or $subscribeall )
2464 and not exists $h2_subscribed_folder{ $h2_fold } )
2465 {
2466 myprint( "Host2: Subscribing to folder $h2_fold\n" ) ;
2467 if ( ! $sync->{dry} ) { $sync->{imap2}->subscribe( $h2_fold ) } ;
2468 }
2469
2470 next FOLDER if ( $sync->{ justfolders } ) ;
2471
2472 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2473
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002474
2475 if ( ! defined $h1_msgs_nb )
2476 {
2477 $h1_msgs_all_hash_ref = { } ;
2478 @h1_msgs = select_msgs( $sync->{imap1}, $h1_msgs_all_hash_ref, $sync->{ search1 }, $sync->{abletosearch1}, $h1_fold );
2479 $h1_msgs_nb = scalar @h1_msgs ;
2480 }else{
2481 # select_msgs already done.
2482 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002483
2484 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2485
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002486 myprint( "Host1: folder [$h1_fold] considering $h1_msgs_nb messages\n" ) ;
2487 ( $sync->{ debug } or $debuglist ) and myprint( "Host1: folder [$h1_fold] considering $h1_msgs_nb messages, LIST gives: @h1_msgs\n" ) ;
2488 $sync->{ debug } and myprint( "Host1: selecting messages of folder [$h1_fold] took ", timenext( $sync ), " s\n" ) ;
2489
2490 my $h2_msgs_all_hash_ref = { } ;
2491 my @h2_msgs = select_msgs( $sync->{imap2}, $h2_msgs_all_hash_ref, $sync->{ search2 }, $sync->{abletosearch2}, $h2_fold ) ;
2492
2493 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2494
2495 my $h2_msgs_nb = scalar @h2_msgs ;
2496
2497 myprint( "Host2: folder [$h2_fold] considering $h2_msgs_nb messages\n" ) ;
2498 ( $sync->{ debug } or $debuglist ) and myprint( "Host2: folder [$h2_fold] considering $h2_msgs_nb messages, LIST gives: @h2_msgs\n" ) ;
2499 $sync->{ debug } and myprint( "Host2: selecting messages of folder [$h2_fold] took ", timenext( $sync ), " s\n" ) ;
2500
2501 my $cache_base = "$sync->{ tmpdir }/imapsync_cache/" ;
2502 my $cache_dir = cache_folder( $cache_base,
2503 "$sync->{host1}/$sync->{user1}/$sync->{host2}/$sync->{user2}", $h1_fold, $h2_fold ) ;
2504 my ( $cache_1_2_ref, $cache_2_1_ref ) = ( {}, {} ) ;
2505
2506 my $h1_uidvalidity = $sync->{imap1}->uidvalidity( ) || q{} ;
2507 my $h2_uidvalidity = $sync->{imap2}->uidvalidity( ) || q{} ;
2508
2509 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2510
2511 if ( $usecache ) {
2512 myprint( "Local cache directory: $cache_dir ( " . length( $cache_dir ) . " characters long )\n" ) ;
2513 mkpath( "$cache_dir" ) ;
2514 ( $cache_1_2_ref, $cache_2_1_ref )
2515 = get_cache( $cache_dir, \@h1_msgs, \@h2_msgs, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) ;
2516 myprint( 'CACHE h1 h2: ', scalar keys %{ $cache_1_2_ref } , " files\n" ) ;
2517 $sync->{ debug } and myprint( '[',
2518 map ( { "$_->$cache_1_2_ref->{$_} " } keys %{ $cache_1_2_ref } ), " ]\n" ) ;
2519 }
2520
2521 my %h1_hash = ( ) ;
2522 my %h2_hash = ( ) ;
2523
2524 my ( %h1_msgs, %h2_msgs ) ;
2525 @h1_msgs{ @h1_msgs } = ( ) ;
2526 @h2_msgs{ @h2_msgs } = ( ) ;
2527
2528 my @h1_msgs_in_cache = sort { $a <=> $b } keys %{ $cache_1_2_ref } ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002529 my @h2_msgs_in_cache = sort { $a <=> $b } keys %{ $cache_2_1_ref } ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002530
2531 my ( %h1_msgs_not_in_cache, %h2_msgs_not_in_cache ) ;
2532 %h1_msgs_not_in_cache = %h1_msgs ;
2533 %h2_msgs_not_in_cache = %h2_msgs ;
2534 delete @h1_msgs_not_in_cache{ @h1_msgs_in_cache } ;
2535 delete @h2_msgs_not_in_cache{ @h2_msgs_in_cache } ;
2536
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002537 my @h1_msgs_not_in_cache = sort { $a <=> $b } keys %h1_msgs_not_in_cache ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002538 #myprint( "h1_msgs_not_in_cache: [@h1_msgs_not_in_cache]\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002539 my @h2_msgs_not_in_cache = sort { $a <=> $b } keys %h2_msgs_not_in_cache ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002540
2541 my @h2_msgs_delete2_not_in_cache = () ;
2542 %h1_msgs_copy_by_uid = ( ) ;
2543
2544 if ( $useuid ) {
2545 # use uid so we have to avoid getting header
2546 @h1_msgs_copy_by_uid{ @h1_msgs_not_in_cache } = ( ) ;
2547 @h2_msgs_delete2_not_in_cache = @h2_msgs_not_in_cache if $usecache ;
2548 @h1_msgs_not_in_cache = ( ) ;
2549 @h2_msgs_not_in_cache = ( ) ;
2550
2551 #myprint( "delete2: @h2_msgs_delete2_not_in_cache\n" ) ;
2552 }
2553
2554 $sync->{ debug } and myprint( "Host1: parsing headers of folder [$h1_fold]\n" ) ;
2555
2556 my ($h1_heads_ref, $h1_fir_ref) = ({}, {});
2557 $h1_heads_ref = $sync->{imap1}->parse_headers([@h1_msgs_not_in_cache], @useheader) if (@h1_msgs_not_in_cache);
2558 $sync->{ debug } and myprint( "Host1: parsing headers of folder [$h1_fold] took ", timenext( $sync ), " s\n" ) ;
2559
2560 @{ $h1_fir_ref }{@h1_msgs} = ( undef ) ;
2561
2562 $sync->{ debug } and myprint( "Host1: getting flags idate and sizes of folder [$h1_fold]\n" ) ;
2563
2564 my @h1_common_fetch_param = ( 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE' ) ;
2565 if ( $sync->{ synclabels } or $sync->{ resynclabels } ) { push @h1_common_fetch_param, 'X-GM-LABELS' ; }
2566
2567 if ( $sync->{abletosearch1} )
2568 {
2569 $h1_fir_ref = $sync->{imap1}->fetch_hash( \@h1_msgs, @h1_common_fetch_param, $h1_fir_ref )
2570 if ( @h1_msgs ) ;
2571 }
2572 else
2573 {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002574 my $fetch_hash_uids = $fetch_hash_set || "1:*" ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002575 $h1_fir_ref = $sync->{imap1}->fetch_hash( $fetch_hash_uids, @h1_common_fetch_param, $h1_fir_ref )
2576 if ( @h1_msgs ) ;
2577 }
2578
2579 $sync->{ debug } and myprint( "Host1: getting flags idate and sizes of folder [$h1_fold] took ", timenext( $sync ), " s\n" ) ;
2580 if ( ! $h1_fir_ref )
2581 {
2582 my $error = join( q{}, "Host1: folder $h1_fold : Could not fetch_hash ",
2583 scalar @h1_msgs, ' msgs: ', $sync->{imap1}->LastError || q{}, "\n" ) ;
2584 errors_incr( $sync, $error ) ;
2585 next FOLDER ;
2586 }
2587
2588 my @h1_msgs_duplicate;
2589 foreach my $m ( @h1_msgs_not_in_cache )
2590 {
2591 my $rc = parse_header_msg( $sync, $sync->{imap1}, $m, $h1_heads_ref, $h1_fir_ref, 'Host1', \%h1_hash ) ;
2592 if ( ! defined $rc )
2593 {
2594 my $h1_size = $h1_fir_ref->{$m}->{'RFC822.SIZE'} || 0;
2595 myprint( "Host1: $h1_fold/$m size $h1_size ignored (no wanted headers so we ignore this message. To solve this: use --addheader)\n" ) ;
2596 $sync->{ total_bytes_skipped } += $h1_size ;
2597 $sync->{ nb_msg_skipped } += 1 ;
2598 $sync->{ h1_nb_msg_noheader } +=1 ;
2599 $sync->{ h1_nb_msg_processed } +=1 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002600 } elsif( 0 == $rc )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002601 {
2602 # duplicate
2603 push @h1_msgs_duplicate, $m;
2604 # duplicate, same id same size?
2605 my $h1_size = $h1_fir_ref->{$m}->{'RFC822.SIZE'} || 0;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002606
2607 $sync->{ acc1 }->{ nb_msg_duplicate } += 1;
2608 if ( ! $sync->{ syncduplicates } ) {
2609 $sync->{ nb_msg_skipped } += 1 ;
2610 $sync->{ h1_nb_msg_processed } +=1 ;
2611 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002612 }
2613 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002614
2615
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002616 my $h1_msgs_duplicate_nb = scalar @h1_msgs_duplicate ;
2617
2618 myprint( "Host1: folder [$h1_fold] selected $h1_msgs_nb messages, duplicates $h1_msgs_duplicate_nb\n" ) ;
2619
2620 $sync->{ debug } and myprint( 'Host1: whole time parsing headers took ', timenext( $sync ), " s\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002621
2622
2623
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002624 # Getting headers and metada can be so long that host2 might be disconnected here
2625 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2626
2627
2628 $sync->{ debug } and myprint( "Host2: parsing headers of folder [$h2_fold]\n" ) ;
2629
2630 my ($h2_heads_ref, $h2_fir_ref) = ( {}, {} );
2631 $h2_heads_ref = $sync->{imap2}->parse_headers([@h2_msgs_not_in_cache], @useheader) if (@h2_msgs_not_in_cache);
2632 $sync->{ debug } and myprint( "Host2: parsing headers of folder [$h2_fold] took ", timenext( $sync ), " s\n" ) ;
2633
2634 $sync->{ debug } and myprint( "Host2: getting flags idate and sizes of folder [$h2_fold]\n" ) ;
2635 @{ $h2_fir_ref }{@h2_msgs} = ( ); # fetch_hash can select by uid with last arg as ref
2636
2637
2638 my @h2_common_fetch_param = ( 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE' ) ;
2639 if ( $sync->{ synclabels } or $sync->{ resynclabels } ) { push @h2_common_fetch_param, 'X-GM-LABELS' ; }
2640
2641 if ( $sync->{abletosearch2} and scalar( @h2_msgs ) ) {
2642 $h2_fir_ref = $sync->{imap2}->fetch_hash( \@h2_msgs, @h2_common_fetch_param, $h2_fir_ref) ;
2643 }else{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002644 my $fetch_hash_uids = $fetch_hash_set || "1:*" ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002645 $h2_fir_ref = $sync->{imap2}->fetch_hash( $fetch_hash_uids, @h2_common_fetch_param, $h2_fir_ref )
2646 if ( @h2_msgs ) ;
2647 }
2648
2649 $sync->{ debug } and myprint( "Host2: getting flags idate and sizes of folder [$h2_fold] took ", timenext( $sync ), " s\n" ) ;
2650
2651 my @h2_msgs_duplicate;
2652 foreach my $m (@h2_msgs_not_in_cache) {
2653 my $rc = parse_header_msg( $sync, $sync->{imap2}, $m, $h2_heads_ref, $h2_fir_ref, 'Host2', \%h2_hash ) ;
2654 my $h2_size = $h2_fir_ref->{$m}->{'RFC822.SIZE'} || 0 ;
2655 if (! defined $rc ) {
2656 myprint( "Host2: $h2_fold/$m size $h2_size ignored (no wanted headers so we ignore this message)\n" ) ;
2657 $h2_nb_msg_noheader += 1 ;
2658 } elsif( 0 == $rc ) {
2659 # duplicate
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002660 $sync->{ acc2 }->{ nb_msg_duplicate } += 1 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002661 push @h2_msgs_duplicate, $m ;
2662 }
2663 }
2664
2665 # %h2_folders_of_md5
2666 foreach my $md5 ( keys %h2_hash ) {
2667 $sync->{ h2_folders_of_md5 }->{ $md5 }->{ $h2_fold } ++ ;
2668 }
2669 # %h1_folders_of_md5
2670 foreach my $md5 ( keys %h1_hash ) {
2671 $sync->{ h1_folders_of_md5 }->{ $md5 }->{ $h2_fold } ++ ;
2672 }
2673
2674
2675 my $h2_msgs_duplicate_nb = scalar @h2_msgs_duplicate ;
2676
2677 myprint( "Host2: folder [$h2_fold] selected $h2_msgs_nb messages, duplicates $h2_msgs_duplicate_nb\n" ) ;
2678
2679 $sync->{ debug } and myprint( 'Host2 whole time parsing headers took ', timenext( $sync ), " s\n" ) ;
2680
2681 $sync->{ debug } and myprint( "++++ Verifying [$h1_fold] -> [$h2_fold]\n" ) ;
2682 # messages in host1 that are not in host2
2683
2684 my @h1_hash_keys_sorted_by_uid
2685 = sort {$h1_hash{$a}{'m'} <=> $h1_hash{$b}{'m'}} keys %h1_hash;
2686
2687 #myprint( map { $h1_hash{$_}{'m'} . q{ }} @h1_hash_keys_sorted_by_uid ) ;
2688
2689 my @h2_hash_keys_sorted_by_uid
2690 = sort {$h2_hash{$a}{'m'} <=> $h2_hash{$b}{'m'}} keys %h2_hash;
2691
2692 # Deletions on account2.
2693
2694 if( $sync->{ delete2duplicates } and not exists $h2_folders_from_1_several{ $h2_fold } ) {
2695 my @h2_expunge ;
2696
2697 foreach my $h2_msg ( @h2_msgs_duplicate ) {
2698 myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted [duplicate] on host2 $sync->{dry_message}\n" ) ;
2699 push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 } ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002700 if ( ! $sync->{ dry } ) {
2701 $sync->{ imap2 }->delete_message( $h2_msg ) ;
2702 $sync->{ acc2 }->{ nb_msg_deleted } += 1 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002703 }
2704 }
2705 my $cnt = scalar @h2_expunge ;
2706 if( @h2_expunge and not $sync->{ expunge2 } ) {
2707 myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n" ) ;
2708 $sync->{imap2}->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ;
2709 }
2710 if ( $sync->{ expunge2 } ){
2711 myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n" ) ;
2712 $sync->{imap2}->expunge( ) if ! $sync->{dry} ;
2713 }
2714 }
2715
2716 if( $sync->{ delete2 } and not exists $h2_folders_from_1_several{ $h2_fold } ) {
2717 # No host1 folders f1a f1b ... going all to same f2 (via --regextrans2)
2718 my @h2_expunge;
2719 foreach my $m_id (@h2_hash_keys_sorted_by_uid) {
2720 #myprint( "$m_id " ) ;
2721 if ( ! exists $h1_hash{$m_id} ) {
2722 my $h2_msg = $h2_hash{$m_id}{'m'};
2723 my $h2_flags = $h2_hash{$m_id}{'F'} || q{};
2724 my $isdel = $h2_flags =~ /\B\\Deleted\b/x ? 1 : 0;
2725 myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted on host2 [$m_id] $sync->{dry_message}\n" )
2726 if ! $isdel;
2727 push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 };
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002728 if ( ! ( $sync->{ dry } or $isdel ) ) {
2729 $sync->{ imap2 }->delete_message( $h2_msg );
2730 $sync->{ acc2 }->{ nb_msg_deleted } += 1;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002731 }
2732 }
2733 }
2734 foreach my $h2_msg ( @h2_msgs_delete2_not_in_cache ) {
2735 myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted [not in cache] on host2 $sync->{dry_message}\n" ) ;
2736 push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 };
2737 if ( ! $sync->{dry} ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002738 $sync->{ imap2 }->delete_message( $h2_msg );
2739 $sync->{ acc2 }->{ nb_msg_deleted } += 1;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002740 }
2741 }
2742 my $cnt = scalar @h2_expunge ;
2743
2744 if( @h2_expunge and not $sync->{ expunge2 } ) {
2745 myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n" ) ;
2746 $sync->{imap2}->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ;
2747 }
2748 if ( $sync->{ expunge2 } ) {
2749 myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n" ) ;
2750 $sync->{imap2}->expunge( ) if ! $sync->{dry} ;
2751 }
2752 }
2753
2754 if( $sync->{ delete2 } and exists $h2_folders_from_1_several{ $h2_fold } ) {
2755 myprint( "Host2: folder $h2_fold $h2_folders_from_1_several{ $h2_fold } folders left to sync there\n" ) ;
2756 my @h2_expunge;
2757 foreach my $m_id ( @h2_hash_keys_sorted_by_uid ) {
2758 my $h2_msg = $h2_hash{ $m_id }{ 'm' } ;
2759 if ( ! exists $h1_hash{ $m_id } ) {
2760 my $h2_flags = $h2_hash{ $m_id }{ 'F' } || q{} ;
2761 my $isdel = $h2_flags =~ /\B\\Deleted\b/x ? 1 : 0 ;
2762 if ( ! $isdel ) {
2763 $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion [$m_id]\n" ) ;
2764 $uid_candidate_for_deletion{ $h2_fold }{ $h2_msg }++ ;
2765 }
2766 }else{
2767 $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [$m_id]\n" ) ;
2768 $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
2769 }
2770 }
2771 foreach my $h2_msg ( @h2_msgs_delete2_not_in_cache ) {
2772 myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion [not in cache]\n" ) ;
2773 $uid_candidate_for_deletion{ $h2_fold }{ $h2_msg }++ ;
2774 }
2775
2776 foreach my $h2_msg ( @h2_msgs_in_cache ) {
2777 myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [in cache]\n" ) ;
2778 $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
2779 }
2780
2781
2782 if ( 0 == $h2_folders_from_1_several{ $h2_fold } ) {
2783 # last host1 folder going to $h2_fold
2784 myprint( "Last host1 folder going to $h2_fold\n" ) ;
2785 foreach my $h2_msg ( keys %{ $uid_candidate_for_deletion{ $h2_fold } } ) {
2786 $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion\n" ) ;
2787 if ( exists $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg } ) {
2788 $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg canceled deletion\n" ) ;
2789 }else{
2790 myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted $sync->{dry_message}\n" ) ;
2791 push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 } ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002792 if ( ! $sync->{ dry} ) {
2793 $sync->{ imap2 }->delete_message( $h2_msg ) ;
2794 $sync->{ acc2 }->{ nb_msg_deleted } += 1 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002795 }
2796 }
2797 }
2798 }
2799
2800 my $cnt = scalar @h2_expunge ;
2801 if( @h2_expunge and not $sync->{ expunge2 } ) {
2802 myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n" ) ;
2803 $sync->{imap2}->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ;
2804 }
2805 if ( $sync->{ expunge2 } ) {
2806 myprint( "Host2: Expunging host2 folder $h2_fold $sync->{dry_message}\n" ) ;
2807 $sync->{imap2}->expunge( ) if ! $sync->{dry} ;
2808 }
2809
2810 $h2_folders_from_1_several{ $h2_fold }-- ;
2811 }
2812
2813 my $h2_uidnext = $sync->{imap2}->uidnext( $h2_fold ) ;
2814 $sync->{ debug } and myprint( "Host2: uidnext is $h2_uidnext\n" ) ;
2815 $h2_uidguess = $h2_uidnext ;
2816
2817 # Getting host2 headers, metada and delete2 stuff can be so long that host1 might be disconnected here
2818 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2819
2820 my @h1_msgs_to_delete ;
2821 MESS: foreach my $m_id (@h1_hash_keys_sorted_by_uid) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002822 abortifneeded( $sync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002823 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2824
2825 #myprint( "h1_nb_msg_processed: $sync->{ h1_nb_msg_processed }\n" ) ;
2826 my $h1_size = $h1_hash{$m_id}{'s'};
2827 my $h1_msg = $h1_hash{$m_id}{'m'};
2828 my $h1_idate = $h1_hash{$m_id}{'D'};
2829
2830 #my $labels = labels( $sync->{imap1}, $h1_msg ) ;
2831 #print "LABELS: $labels\n" ;
2832
2833 if ( ( not exists $h2_hash{ $m_id } )
2834 and ( not ( exists $sync->{ h2_folders_of_md5 }->{ $m_id } )
2835 or not $skipcrossduplicates ) )
2836 {
2837 # copy
2838 my $h2_msg = copy_message( $sync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) ;
2839 if ( $h2_msg and $sync->{ delete1 } and not $sync->{ expungeaftereach } ) {
2840 # not expunged
2841 push @h1_msgs_to_delete, $h1_msg ;
2842 }
2843
2844 # A bug here with imapsync 1.920, fixed in 1.921
2845 # Added $h2_msg in the condition. Errors of APPEND were not counted as missing messages on host2!
2846 if ( $h2_msg and not $sync->{ dry } )
2847 {
2848 $sync->{ h2_folders_of_md5 }->{ $m_id }->{ $h2_fold } ++ ;
2849 }
2850
2851 #
2852 if( $sync->{ delete2 } and ( exists $h2_folders_from_1_several{ $h2_fold } ) and $h2_msg ) {
2853 myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [fresh copy] on host2\n" ) ;
2854 $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
2855 }
2856
2857 if ( total_bytes_max_reached( $sync ) ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002858 # Still a bug when using --delete1 --noexpungeaftereach
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002859 # same thing below on all total_bytes_max_reached!
2860 last FOLDER ;
2861 }
2862 next MESS;
2863 }
2864 else
2865 {
2866 # already on host2
2867 if ( exists $h2_hash{ $m_id } )
2868 {
2869 my $h2_msg = $h2_hash{$m_id}{'m'} ;
2870 $sync->{ debug } and myprint( "Host1: found that msg $h1_fold/$h1_msg equals Host2 $h2_fold/$h2_msg\n" ) ;
2871 if ( $usecache )
2872 {
2873 $debugcache and myprint( "touch $cache_dir/${h1_msg}_$h2_msg\n" ) ;
2874 touch( "$cache_dir/${h1_msg}_$h2_msg" )
2875 or croak( "Couldn't touch $cache_dir/${h1_msg}_$h2_msg" ) ;
2876 }
2877 }
2878 elsif( exists $sync->{ h2_folders_of_md5 }->{ $m_id } )
2879 {
2880 my @folders_dup = keys %{ $sync->{ h2_folders_of_md5 }->{ $m_id } } ;
2881 ( $sync->{ debug } or $debugcrossduplicates ) and myprint( "Host1: found that msg $h1_fold/$h1_msg is also in Host2 folders @folders_dup\n" ) ;
2882 $sync->{ h2_nb_msg_crossdup } +=1 ;
2883 }
2884 $sync->{ total_bytes_skipped } += $h1_size ;
2885 $sync->{ nb_msg_skipped } += 1 ;
2886 $sync->{ h1_nb_msg_processed } +=1 ;
2887 }
2888
2889 if ( exists $h2_hash{ $m_id } ) {
2890 #$debug and myprint( "MESSAGE $m_id\n" ) ;
2891 my $h2_msg = $h2_hash{$m_id}{'m'};
2892 if ( $sync->{resyncflags} ) {
2893 sync_flags_fir( $sync, $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) ;
2894 }
2895 # Good
2896 my $h2_size = $h2_hash{$m_id}{'s'};
2897 $sync->{ debug } and myprint(
2898 "Host1: size msg $h1_fold/$h1_msg = $h1_size <> $h2_size = Host2 $h2_fold/$h2_msg\n" ) ;
2899
2900 if ( $sync->{ resynclabels } )
2901 {
2902 resynclabels( $sync, $h1_msg, $h2_msg, $h1_fir_ref, $h2_fir_ref, $h1_fold )
2903 }
2904 }
2905
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002906 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002907
2908 if ( $sync->{ delete1 } ) {
2909 push @h1_msgs_to_delete, $h1_msg ;
2910 }
2911 }
2912 # END MESS: loop
2913
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002914 # @h1_msgs_in_cache are already synced too.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002915 delete_message_on_host1( $sync, $h1_fold, $sync->{ expunge1 }, @h1_msgs_to_delete, @h1_msgs_in_cache ) ;
2916
2917 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2918
2919 # MESS_IN_CACHE:
2920 if ( ! $sync->{ delete1 } )
2921 {
2922 foreach my $h1_msg ( @h1_msgs_in_cache )
2923 {
2924 my $h2_msg = $cache_1_2_ref->{ $h1_msg } ;
2925 $debugcache and myprint( "cache messages update flags $h1_msg->$h2_msg\n" ) ;
2926 if ( $sync->{resyncflags} )
2927 {
2928 sync_flags_fir( $sync, $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) ;
2929 }
2930 my $h1_size = $h1_fir_ref->{ $h1_msg }->{ 'RFC822.SIZE' } || 0 ;
2931 $sync->{ total_bytes_skipped } += $h1_size;
2932 $sync->{ nb_msg_skipped } += 1;
2933 $sync->{ h1_nb_msg_processed } +=1 ;
2934 }
2935 }
2936
2937 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2938
2939 @h1_msgs_to_delete = ( ) ;
2940 #myprint( "Messages by uid: ", map { "$_ " } keys %h1_msgs_copy_by_uid, "\n" ) ;
2941 # MESS_BY_UID:
2942 foreach my $h1_msg ( sort { $a <=> $b } keys %h1_msgs_copy_by_uid )
2943 {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002944 abortifneeded( $sync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002945 $sync->{ debug } and myprint( "Copy by uid $h1_fold/$h1_msg\n" ) ;
2946 if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
2947
2948 my $h2_msg = copy_message( $sync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) ;
2949 if( $sync->{ delete2 } and exists $h2_folders_from_1_several{ $h2_fold } and $h2_msg ) {
2950 myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [fresh copy] on host2\n" ) ;
2951 $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
2952 }
2953 last FOLDER if total_bytes_max_reached( $sync ) ;
2954 }
2955
2956 if ( $sync->{ expunge1 } ){
2957 myprint( "Host1: Expunging folder $h1_fold $sync->{dry_message}\n" ) ;
2958 if ( ! $sync->{dry} ) { $sync->{imap1}->expunge( ) } ;
2959 }
2960 if ( $sync->{ expunge2 } ){
2961 myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n" ) ;
2962 if ( ! $sync->{dry} ) { $sync->{imap2}->expunge( ) } ;
2963 }
2964 $sync->{ debug } and myprint( 'Time: ', timenext( $sync ), " s\n" ) ;
2965}
2966
2967eta_print( $sync ) ;
2968
2969myprint( "++++ End looping on each folder\n" ) ;
2970
2971if ( $sync->{ delete1 } and $sync->{ delete1emptyfolders } ) {
2972 delete1emptyfolders( $sync ) ;
2973}
2974
2975( $sync->{ debug } or $sync->{debugfolders} ) and myprint( 'Time: ', timenext( $sync ), " s\n" ) ;
2976
2977
2978if ( $sync->{ foldersizesatend } ) {
2979 myprint( << 'END_SIZE' ) ;
2980
2981Folders sizes after the synchronization.
2982You can remove this foldersizes listing by using "--nofoldersizesatend"
2983END_SIZE
2984
2985 foldersizesatend( $sync ) ;
2986}
2987
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002988#$sync->{imap1}->State( 0 ); # Unconnected
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01002989if ( ! lost_connection( $sync, $sync->{imap1}, "for host1 [$sync->{host1}]" ) ) { $sync->{imap1}->logout( ) ; }
2990if ( ! lost_connection( $sync, $sync->{imap2}, "for host2 [$sync->{host2}]" ) ) { $sync->{imap2}->logout( ) ; }
2991
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002992do_and_print_stats( $sync ) ;
2993
2994
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01002995if ( $sync->{ nb_errors } )
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02002996{
2997 myprint( errors_listing( $sync ) ) ;
2998}
2999
3000
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003001if ( $sync->{ testslive } or $sync->{ testslive6 } )
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003002{
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003003 tests_live_result( $sync->{ nb_errors } ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003004}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003005
3006
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003007if ( $sync->{ nb_errors } )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003008{
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003009 my $exit_value = exit_value( $sync, $sync->{ most_common_error } ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003010 exit_clean( $sync, $exit_value ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003011}
3012else
3013{
3014 exit_clean( $sync, $EX_OK ) ;
3015}
3016
3017return ;
3018}
3019
3020# END of sub single_sync
3021
3022
3023# subroutines
3024sub myprint
3025{
3026 #print @ARG ;
3027 print { $sync->{ tee } || \*STDOUT } @ARG ;
3028 return ;
3029}
3030
3031sub myprintf
3032{
3033 printf { $sync->{ tee } || \*STDOUT } @ARG ;
3034 return ;
3035}
3036
3037sub mysprintf
3038{
3039 my( $format, @list ) = @ARG ;
3040 return sprintf $format, @list ;
3041}
3042
3043sub output_start
3044{
3045 my $mysync = shift @ARG ;
3046
3047 if ( not $mysync ) { return ; }
3048
3049 my @output = @ARG ;
3050 $mysync->{ output } = join( q{}, @output ) . ( $mysync->{ output } || q{} ) ;
3051 return $mysync->{ output } ;
3052}
3053
3054
3055sub tests_output_start
3056{
3057 note( 'Entering tests_output_start()' ) ;
3058
3059 my $mysync = { } ;
3060
3061 is( undef, output_start( ), 'output_start: no args => undef' ) ;
3062 is( q{}, output_start( $mysync ), 'output_start: one arg => ""' ) ;
3063 is( 'rrrr', output_start( $mysync, 'rrrr' ), 'output_start: rrrr => rrrr' ) ;
3064 is( 'aaaarrrr', output_start( $mysync, 'aaaa' ), 'output_start: aaaa => aaaarrrr' ) ;
3065 is( "\naaaarrrr", output_start( $mysync, "\n" ), 'output_start: \n => \naaaarrrr' ) ;
3066 is( "ABC\naaaarrrr", output_start( $mysync, 'A', 'B', 'C' ), 'output_start: A B C => ABC\naaaarrrr' ) ;
3067
3068 note( 'Leaving tests_output_start()' ) ;
3069 return ;
3070}
3071
3072sub tests_output
3073{
3074 note( 'Entering tests_output()' ) ;
3075
3076 my $mysync = { } ;
3077
3078 is( undef, output( ), 'output: no args => undef' ) ;
3079 is( q{}, output( $mysync ), 'output: one arg => ""' ) ;
3080 is( 'rrrr', output( $mysync, 'rrrr' ), 'output: rrrr => rrrr' ) ;
3081 is( 'rrrraaaa', output( $mysync, 'aaaa' ), 'output: aaaa => rrrraaaa' ) ;
3082 is( "rrrraaaa\n", output( $mysync, "\n" ), 'output: \n => rrrraaaa\n' ) ;
3083 is( "rrrraaaa\nABC", output( $mysync, 'A', 'B', 'C' ), 'output: A B C => rrrraaaaABC\n' ) ;
3084
3085 note( 'Leaving tests_output()' ) ;
3086 return ;
3087}
3088
3089sub output
3090{
3091 my $mysync = shift @ARG ;
3092
3093 if ( not $mysync ) { return ; }
3094
3095 my @output = @ARG ;
3096 $mysync->{ output } .= join( q{}, @output ) ;
3097 return $mysync->{ output } ;
3098}
3099
3100
3101
3102sub tests_output_reset_with
3103{
3104 note( 'Entering tests_output_reset_with()' ) ;
3105
3106 my $mysync = { } ;
3107
3108 is( undef, output_reset_with( ), 'output_reset_with: no args => undef' ) ;
3109 is( q{}, output_reset_with( $mysync ), 'output_reset_with: one arg => ""' ) ;
3110 is( 'rrrr', output_reset_with( $mysync, 'rrrr' ), 'output_reset_with: rrrr => rrrr' ) ;
3111 is( 'aaaa', output_reset_with( $mysync, 'aaaa' ), 'output_reset_with: aaaa => aaaa' ) ;
3112 is( "\n", output_reset_with( $mysync, "\n" ), 'output_reset_with: \n => \n' ) ;
3113
3114 note( 'Leaving tests_output_reset_with()' ) ;
3115 return ;
3116}
3117
3118sub output_reset_with
3119{
3120 my $mysync = shift @ARG ;
3121
3122 if ( not $mysync ) { return ; }
3123
3124 my @output = @ARG ;
3125 $mysync->{ output } = join( q{}, @output ) ;
3126 return $mysync->{ output } ;
3127}
3128
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003129
3130sub tests_print_output_if_needed
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003131{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003132 note( 'Entering tests_print_output_if_needed()' ) ;
3133
3134 is( undef, print_output_if_needed( ), 'print_output_if_needed: no args => undef' ) ;
3135 my $mysync = { } ;
3136 is( q{}, print_output_if_needed( $mysync ), 'print_output_if_needed: undef => undef' ) ;
3137
3138 output( $mysync, "Hello\n" ) ;
3139 is( "Hello\n", print_output_if_needed( $mysync ), 'print_output_if_needed: Hello => Hello' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003140
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003141 $mysync->{ dockercontext } = 1 ;
3142 is( "Hello\n", print_output_if_needed( $mysync ), 'print_output_if_needed: dockercontext + Hello => Hello' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003143
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003144 $mysync->{ version } = 1 ;
3145 is( q{}, print_output_if_needed( $mysync ), 'print_output_if_needed: dockercontext + Hello + --version => ""' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003146
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003147 $mysync->{ dockercontext } = 0 ;
3148 is( "Hello\n", print_output_if_needed( $mysync ), 'print_output_if_needed: Hello + --version => Hello' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003149
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003150 note( 'Leaving tests_print_output_if_needed()' ) ;
3151 return ;
3152}
3153
3154
3155sub print_output_if_needed
3156{
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003157
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003158 my $mysync = shift @ARG ;
3159 if ( ! defined $mysync ) { return ; }
3160 my $output = output( $mysync ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003161
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003162 if ( $mysync->{ version } && under_docker_context( $mysync ) )
3163 {
3164 return q{} ;
3165 }
3166 else
3167 {
3168 myprint( $output ) ;
3169 return $output ;
3170 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003171
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003172}
3173
3174
3175
3176sub define_pidfile
3177{
3178 my $mysync = shift @ARG ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003179
3180 $mysync->{ pidfilelocking } = defined $mysync->{ pidfilelocking } ? $mysync->{ pidfilelocking } : 0 ;
3181
3182 my $host1 = $mysync->{ host1 } || q{} ;
3183 my $user1 = $mysync->{ user1 } || q{} ;
3184 my $host2 = $mysync->{ host2 } || q{} ;
3185 my $user2 = $mysync->{ user2 } || q{} ;
3186
3187 my $account1_filtered = filter_forbidden_characters( slash_to_underscore( $host1 . '_' . $user1 ) ) || q{} ;
3188 my $account2_filtered = filter_forbidden_characters( slash_to_underscore( $host2 . '_' . $user2 ) ) || q{} ;
3189
3190 my $pidfile_basename ;
3191
3192 if ( $ENV{ 'NET_SERVER_SOFTWARE' } and ( $ENV{ 'NET_SERVER_SOFTWARE' } =~ /Net::Server::HTTP/ ) )
3193 {
3194 # under local webserver
3195 $pidfile_basename = 'imapsync' . '_' . $account1_filtered . '_' . $account2_filtered . '.pid' ;
3196 }
3197 else
3198 {
3199 $pidfile_basename = 'imapsync.pid' ;
3200 }
3201
3202 $mysync->{ pidfile } = defined $mysync->{ pidfile } ? $mysync-> { pidfile } : $mysync->{ tmpdir } . "/$pidfile_basename" ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003203 $mysync->{ abortfile } = abortfile( $mysync, $PROCESS_ID ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003204 return ;
3205}
3206
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003207sub abortfile
3208{
3209 my $mysync = shift @ARG ;
3210 my $pid = shift @ARG ;
3211
3212 my $abortfile ;
3213 if ( $mysync->{ abort } )
3214 {
3215 $abortfile = $mysync->{ pidfile } . "abort$pid" ;
3216 }
3217 else
3218 {
3219 $abortfile = $mysync->{ pidfile } . "abort$PROCESS_ID" ;
3220 }
3221 return $abortfile ;
3222}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003223
3224sub tests_kill_zero
3225{
3226 note( 'Entering tests_kill_zero()' ) ;
3227
3228
3229
3230 SKIP: {
3231 if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests tests_kill_zero avoided on Windows', 8 ) ; }
3232
3233
3234 is( 1, kill( 'ZERO', $PROCESS_ID ), "kill ZERO : myself $PROCESS_ID => 1" ) ;
3235 is( 2, kill( 'ZERO', $PROCESS_ID, $PROCESS_ID ), "kill ZERO : myself $PROCESS_ID $PROCESS_ID => 2" ) ;
3236
3237 if ( (-e '/.dockerenv' ) or ( 0 == $EFFECTIVE_USER_ID) )
3238 {
3239 is( 1, kill( 'ZERO', 1 ), "kill ZERO : pid 1 => 1 (docker context or root)" ) ;
3240 is( 2, kill( 'ZERO', $PROCESS_ID, 1 ), "kill ZERO : myself + pid 1, $PROCESS_ID 1 => 2 (docker context or root)" ) ;
3241 }
3242 else
3243 {
3244 is( 0, kill( 'ZERO', 1 ), "kill ZERO : pid 1 => 0 (non root)" ) ;
3245 is( 1, kill( 'ZERO', $PROCESS_ID, 1 ), "kill ZERO : myself + pid 1, $PROCESS_ID 1 => 1 (one is non root)" ) ;
3246
3247 }
3248
3249
3250 my $pid_1 = fork( ) ;
3251 if ( $pid_1 )
3252 {
3253 # parent
3254 }
3255 else
3256 {
3257 # child
3258 sleep 3 ;
3259 exit ;
3260 }
3261
3262 my $pid_2 ;
3263 $pid_2 = fork( ) ;
3264 if ( $pid_2 )
3265 {
3266 # I am the parent
3267 ok( defined( $pid_2 ), "kill_zero: initial fork ok. I am the parent $PROCESS_ID" ) ;
3268 ok( $pid_2 , "kill_zero: initial fork ok, child pid is $pid_2" ) ;
3269 is( 3, kill( 'ZERO', $PROCESS_ID, $pid_2, $pid_1 ), "kill ZERO : myself $PROCESS_ID and child $pid_2 and brother $pid_1 => 3" ) ;
3270
3271 is( $pid_2, waitpid( $pid_2, 0 ), "kill_zero: child $pid_2 no more there => waitpid return $pid_2" ) ;
3272 }
3273 else
3274 {
3275 # I am the child
3276 note( 'This one fails under Windows, kill ZERO returns 0 instead of 2' ) ;
3277 is( 2, kill( 'ZERO', $PROCESS_ID, $pid_1 ), "kill ZERO : myself child $PROCESS_ID brother $pid_1 => 2" ) ;
3278 myprint( "I am the child pid $PROCESS_ID, Exiting\n" ) ;
3279 exit ;
3280 }
3281 wait( ) ;
3282
3283 # End of SKIP block
3284 }
3285
3286 note( 'Leaving tests_kill_zero()' ) ;
3287 return ;
3288}
3289
3290
3291
3292
3293sub tests_killpid_by_parent
3294{
3295 note( 'Entering tests_killpid_by_parent()' ) ;
3296
3297 SKIP: {
3298 if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests tests_killpid_by_parent avoided on Windows', 7 ) ; }
3299
3300 is( undef, killpid( ), 'killpid: no args => undef' ) ;
3301 note( "killpid: trying to kill myself pid $PROCESS_ID, hope I will not succeed" ) ;
3302 is( undef, killpid( $PROCESS_ID ), 'killpid: myself => undef' ) ;
3303
3304 local $SIG{'QUIT'} = sub { myprint "GOT SIG QUIT! I am PID $PROCESS_ID. Exiting\n" ; exit ; } ;
3305
3306 my $pid ;
3307 $pid = fork( ) ;
3308 if ( $pid )
3309 {
3310 # I am the parent
3311 ok( defined( $pid ), "killpid: initial fork ok. I am the parent $PROCESS_ID" ) ;
3312 ok( $pid , "killpid: initial fork ok, child pid is $pid" ) ;
3313
3314 is( 2, kill( 'ZERO', $PROCESS_ID, $pid ), "kill ZERO : myself $PROCESS_ID and child $pid => 2" ) ;
3315 is( 1, killpid( $pid ), "killpid: child $pid killed => 1" ) ;
3316 is( -1, waitpid( $pid, 0 ), "killpid: child $pid no more there => waitpid return -1" ) ;
3317 }
3318 else
3319 {
3320 # I am the child
3321 myprint( "I am the child pid $PROCESS_ID, sleeping 1 + 3 seconds then kill myself\n" ) ;
3322 sleep 1 ;
3323 myprint( "I am the child pid $PROCESS_ID, slept 1 second, should be killed by my parent now, PPID " . mygetppid( ) . "\n" ) ;
3324 sleep 3 ;
3325 # this test should not be run. If it happens => failure.
3326 ok( 0 == 1, "killpid: child pid $PROCESS_ID not dead => failure" ) ;
3327 myprint( "I am the child pid $PROCESS_ID, killing myself failure... Exiting\n" ) ;
3328 exit ;
3329 }
3330
3331 # End of SKIP block
3332 }
3333 note( 'Leaving tests_killpid_by_parent()' ) ;
3334 return ;
3335}
3336
3337sub tests_killpid_by_brother
3338{
3339 note( 'Entering tests_killpid_by_brother()' ) ;
3340
3341
3342 SKIP: {
3343 if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests tests_killpid_by_brother avoided on Windows', 2 ) ; }
3344
3345 local $SIG{'QUIT'} = sub { myprint "GOT SIG QUIT! I am PID $PROCESS_ID. Exiting\n" ; exit ; } ;
3346
3347 my $pid_parent = $PROCESS_ID ;
3348 myprint( "I am the parent pid $pid_parent\n" ) ;
3349 my $pid_1 = fork( ) ;
3350 if ( $pid_1 )
3351 {
3352 # parent
3353 }
3354 else
3355 {
3356 # child
3357 #while ( 1 ) { } ;
3358 sleep 2 ;
3359 sleep 2 ;
3360 # this test should not be run. If it happens => failure.
3361 # Well under Windows this always fails, shit!
3362 ok( 0 == 1 or ( 'MSWin32' eq $OSNAME ) , "killpid: child pid $PROCESS_ID killing by brother but not dead => failure" ) ;
3363 myprint( "I am the child pid $PROCESS_ID, killing by brother failed... Exiting\n" ) ;
3364 exit ;
3365 }
3366
3367 my $pid_2 ;
3368 $pid_2 = fork( ) ;
3369 if ( $pid_2 )
3370 {
3371 # parent
3372 }
3373 else
3374 {
3375 # I am the child
3376 myprint( "I am the child pid $PROCESS_ID, my brother has pid $pid_1\n" ) ;
3377 is( 1, killpid( $pid_1 ), "killpid: brother $pid_1 killed => 1" ) ;
3378 sleep 2 ;
3379 exit ;
3380 }
3381
3382 #sleep 1 ;
3383 is( $pid_1, waitpid( $pid_1, 0), "I am the parent $PROCESS_ID waitpid _1( $pid_1 )" ) ;
3384 is( $pid_2, waitpid( $pid_2, 0 ), "I am the parent $PROCESS_ID waitpid _2( $pid_2 )" ) ;
3385
3386
3387 # End of SKIP block
3388 }
3389
3390 note( 'Leaving tests_killpid_by_brother()' ) ;
3391 return ;
3392}
3393
3394
3395sub killpid
3396{
3397 my $pidtokill = shift ;
3398
3399 if ( ! $pidtokill ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003400 myprint( "No process to kill.\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003401 return ;
3402 }
3403
3404 if ( $PROCESS_ID == $pidtokill ) {
3405 myprint( "I will not kill myself pid $PROCESS_ID via killpid. Sractch it!\n" ) ;
3406 return ;
3407 }
3408
3409
3410 # First ask for suicide
3411 if ( kill( 'ZERO', $pidtokill ) or ( 'MSWin32' eq $OSNAME ) ) {
3412 myprint( "Sending signal QUIT to PID $pidtokill \n" ) ;
3413 kill 'QUIT', $pidtokill ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003414 sleep 3 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003415 waitpid( $pidtokill, WNOHANG) ;
3416 }else{
3417 myprint( "Can not send signal kill ZERO to PID $pidtokill.\n" ) ;
3418 return ;
3419 }
3420
3421 #while ( waitpid( $pidtokill, WNOHANG) > 0 ) { } ;
3422
3423 # Then murder
3424 if ( kill( 'ZERO', $pidtokill ) or ( 'MSWin32' eq $OSNAME ) ) {
3425 myprint( "Sending signal KILL to PID $pidtokill \n" ) ;
3426 kill 'KILL', $pidtokill ;
3427 sleep 1 ;
3428 waitpid( $pidtokill, WNOHANG) ;
3429 }else{
3430 myprint( "Process PID $pidtokill ended.\n" ) ;
3431 return 1;
3432 }
3433 # Well ...
3434 if ( kill( 'ZERO', $pidtokill ) or ( 'xMSWin32' eq $OSNAME ) ) {
3435 myprint( "Process PID $pidtokill seems still there. Can not do much.\n" ) ;
3436 return ;
3437 }else{
3438 myprint( "Process PID $pidtokill ended.\n" ) ;
3439 return 1;
3440 }
3441
3442 return ;
3443}
3444
3445sub tests_abort
3446{
3447 note( 'Entering tests_abort()' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003448 # Well, the abort behavior is tested by test.sh
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003449 is( undef, abort( ), 'abort: no args => undef' ) ;
3450 note( 'Leaving tests_abort()' ) ;
3451 return ;
3452}
3453
3454
3455
3456
3457sub abort
3458{
3459 my $mysync = shift @ARG ;
3460
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003461 myprint( "In abort\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003462 if ( not $mysync ) { return ; }
3463
3464 if ( ! -r $mysync->{pidfile} ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003465 myprint( "In abort: Can not read pidfile $mysync->{pidfile}\n" ) ;
3466 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003467 }
3468 my $pidtokill = firstline( $mysync->{pidfile} ) ;
3469 if ( ! $pidtokill ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003470 myprint( "In abort: No process to abort in $mysync->{pidfile}\n" ) ;
3471 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003472 }
3473
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003474 if ( ! match_a_pid_number( $pidtokill ) )
3475 {
3476 myprint( "In abort: pid $pidtokill in $mysync->{pidfile} is not a pid number\n" ) ;
3477 return ;
3478 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003479
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003480
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003481 if ( $mysync->{abortbyfile} )
3482 {
3483 abortbyfile( $mysync, $pidtokill ) ;
3484 }
3485 else
3486 {
3487 killpid( $pidtokill ) ;
3488 }
3489 return ;
3490}
3491
3492sub abortbyfile
3493{
3494 my $mysync = shift @ARG ;
3495 my $pidtokill = shift @ARG ;
3496
3497 my $abortfile = abortfile( $mysync, $pidtokill ) ;
3498 myprint( "touching $abortfile\n" ) ;
3499 touch( $abortfile ) ;
3500 return ;
3501}
3502
3503
3504sub tests_under_docker_context
3505{
3506 note( 'Entering tests_under_docker_context()' ) ;
3507
3508 is( undef, under_docker_context( ), 'under_docker_context: no args => undef' ) ;
3509
3510 my $mysync = { } ;
3511 $mysync->{ dockercontext } = 1 ;
3512 is( 1, under_docker_context( $mysync ), 'under_docker_context: --dockercontext => 1' ) ;
3513 $mysync->{ dockercontext } = 0 ;
3514 is( 0, under_docker_context( $mysync ), 'under_docker_context: --nodockercontext => 0' ) ;
3515
3516 $mysync = { } ;
3517 # Is not it a stupid test?
3518 if ( under_docker_context( $mysync ) )
3519 {
3520 is( 1, under_docker_context( $mysync ), 'under_docker_context: docker context => 1' ) ;
3521 }
3522 else
3523 {
3524 is( 0, under_docker_context( $mysync ), 'under_docker_context: not docker context => 0' ) ;
3525 }
3526
3527 note( 'Leaving tests_under_docker_context()' ) ;
3528 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003529}
3530
3531
3532sub under_docker_context
3533{
3534 my $mysync = shift ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003535
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003536 if ( ! defined $mysync ) { return ; }
3537
3538 if ( defined $mysync->{ dockercontext } )
3539 {
3540 return( $mysync->{ dockercontext } ) ;
3541 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003542
3543 if ( -e '/.dockerenv' )
3544 {
3545 return 1 ;
3546 }
3547 else
3548 {
3549 return 0 ;
3550 }
3551
3552 return ;
3553}
3554
3555
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003556sub docker_context
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003557{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003558 my $mysync = shift ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003559
3560 if ( ! under_docker_context( $mysync ) )
3561 {
3562 return ;
3563 }
3564
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003565 output( $mysync, "Docker context detected with the file /.dockerenv\n" ) ;
3566 # No pidfile by default
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003567
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003568 $mysync->{ pidfile } = defined( $mysync->{ pidfile } ) ? $mysync->{ pidfile } : q{} ;
3569 # No log by default
3570 if ( defined( $mysync->{ log } ) )
3571 {
3572 output( $mysync, "Logging in Docker context. Be sure you added access to it with a mount or similar. See https://docs.docker.com/storage/volumes/\n" ) ;
3573 }
3574 else
3575 {
3576 output( $mysync, "No log by default in Docker context. Use --log to trigger logging to the logfile.\n" ) ;
3577 $mysync->{ log } = 0 ;
3578 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003579
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003580 # In case something is written relatively to .
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003581 my $tmp_dir = "/var/tmp/uid_$EFFECTIVE_USER_ID" ;
3582 mkpath( $tmp_dir ) ; # silly? No. it is for imapsync --version being ok.
3583 do_valid_directory( $tmp_dir ) ;
3584 output( $mysync, "Changing current directory to $tmp_dir\n" ) ;
3585 chdir $tmp_dir ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003586
3587 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003588}
3589
3590sub cgibegin
3591{
3592 my $mysync = shift ;
3593 if ( ! under_cgi_context( $mysync ) ) { return ; }
3594 require CGI ;
3595 CGI->import( qw( -no_debug -utf8 ) ) ;
3596 require CGI::Carp ;
3597 CGI::Carp->import( qw( fatalsToBrowser ) ) ;
3598 $mysync->{cgi} = CGI->new( ) ;
3599 return ;
3600}
3601
3602sub tests_under_cgi_context
3603{
3604 note( 'Entering tests_under_cgi_context()' ) ;
3605
3606 # $ENV{SERVER_SOFTWARE} = 'under imapsync' ;
3607 do {
3608 # Not in cgi context
3609 delete local $ENV{SERVER_SOFTWARE} ;
3610 is( undef, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE unset => not in cgi context' ) ;
3611 } ;
3612 do {
3613 # In cgi context
3614 local $ENV{SERVER_SOFTWARE} = 'under imapsync' ;
3615 is( 1, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE set => in cgi context' ) ;
3616 } ;
3617 do {
3618 # Not in cgi context
3619 delete local $ENV{SERVER_SOFTWARE} ;
3620 is( undef, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE unset => not in cgi context' ) ;
3621 } ;
3622 do {
3623 # In cgi context
3624 local $ENV{SERVER_SOFTWARE} = 'under imapsync' ;
3625 is( 1, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE set => in cgi context' ) ;
3626 } ;
3627 note( 'Leaving tests_under_cgi_context()' ) ;
3628 return ;
3629}
3630
3631
3632sub under_cgi_context
3633{
3634 my $mysync = shift ;
3635 # Under cgi context
3636 if ( $ENV{SERVER_SOFTWARE} ) {
3637 return 1 ;
3638 }
3639 # Not in cgi context
3640 return ;
3641}
3642
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003643sub cgibuildheader
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003644{
3645 my $mysync = shift ;
3646 if ( ! under_cgi_context( $mysync ) ) { return ; }
3647
3648 my $imapsync_runs = $mysync->{cgi}->cookie( 'imapsync_runs' ) || 0 ;
3649 my $cookie = $mysync->{cgi}->cookie(
3650 -name => 'imapsync_runs',
3651 -value => 1 + $imapsync_runs,
3652 -expires => '+20y',
3653 -path => '/cgi-bin/imapsync',
3654 ) ;
3655 my $httpheader ;
3656 if ( $mysync->{ abort } ) {
3657 $httpheader = $mysync->{cgi}->header(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003658 -type => 'text/plain; charset=UTF-8',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003659 -status => '200 OK to abort syncing IMAP boxes' . ". Here is " . hostname(),
3660 ) ;
3661 }elsif( $mysync->{ loaddelay } ) {
3662# https://tools.ietf.org/html/rfc2616#section-10.5.4
3663# 503 Service Unavailable
3664# The server is currently unable to handle the request due to a temporary overloading or maintenance of the server.
3665 $httpheader = $mysync->{cgi}->header(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003666 -type => 'text/plain; charset=UTF-8',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003667 -status => '503 Service Unavailable' . ". Be back in $mysync->{ loaddelay } min. Load on " . hostname() . " is $mysync->{ loadavg }",
3668 ) ;
3669 }else{
3670 $httpheader = $mysync->{cgi}->header(
3671 -type => 'text/plain; charset=UTF-8',
3672 -status => '200 OK to sync IMAP boxes' . ". Load on " . hostname() . " is $mysync->{ loadavg }",
3673 -cookie => $cookie,
3674 ) ;
3675 }
3676 output_start( $mysync, $httpheader ) ;
3677
3678 return ;
3679}
3680
3681sub cgiload
3682{
3683 # Exit on heavy load in CGI context
3684 my $mysync = shift ;
3685 if ( ! under_cgi_context( $mysync ) ) { return ; }
3686 if ( $mysync->{ abort } ) { return ; } # keep going to abort since some ressources will be free soon
3687 if ( $mysync->{ loaddelay } )
3688 {
3689 $mysync->{nb_errors}++ ;
3690 exit_clean( $mysync, $EX_UNAVAILABLE,
3691 "Server is on heavy load. Be back in $mysync->{ loaddelay } min. Load is $mysync->{ loadavg }\n"
3692 ) ;
3693 }
3694 return ;
3695}
3696
3697sub tests_set_umask
3698{
3699 note( 'Entering tests_set_umask()' ) ;
3700
3701 my $save_umask = umask ;
3702
3703 my $mysync = {} ;
3704 if ( 'MSWin32' eq $OSNAME ) {
3705 is( undef, set_umask( $mysync ), "set_umask: set failure to $UMASK_PARANO on MSWin32" ) ;
3706 }else{
3707 is( 1, set_umask( $mysync ), "set_umask: set to $UMASK_PARANO" ) ;
3708 }
3709
3710 umask $save_umask ;
3711 note( 'Leaving tests_set_umask()' ) ;
3712 return ;
3713}
3714
3715sub set_umask
3716{
3717 my $mysync = shift ;
3718 my $previous_umask = umask_str( ) ;
3719 my $new_umask = umask_str( $UMASK_PARANO ) ;
3720 output( $mysync, "Umask set with $new_umask (was $previous_umask)\n" ) ;
3721 if ( $new_umask eq $UMASK_PARANO ) {
3722 return 1 ;
3723 }
3724 return ;
3725}
3726
3727sub tests_umask_str
3728{
3729 note( 'Entering tests_umask_str()' ) ;
3730
3731 my $save_umask = umask ;
3732
3733 is( umask_str( ), umask_str( ), 'umask_str: no parameters => idopotent' ) ;
3734 is( my $save_umask_str = umask_str( ), umask_str( ), 'umask_str: no parameters => idopotent + save' ) ;
3735 is( '0000', umask_str( q{ } ), 'umask_str: q{ } => 0000' ) ;
3736 is( '0000', umask_str( q{} ), 'umask_str: q{} => 0000' ) ;
3737 is( '0000', umask_str( '0000' ), 'umask_str: 0000 => 0000' ) ;
3738 is( '0000', umask_str( '0' ), 'umask_str: 0 => 0000' ) ;
3739 is( '0200', umask_str( '0200' ), 'umask_str: 0200 => 0200' ) ;
3740 is( '0400', umask_str( '0400' ), 'umask_str: 0400 => 0400' ) ;
3741 is( '0600', umask_str( '0600' ), 'umask_str: 0600 => 0600' ) ;
3742
3743 SKIP: {
3744 if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests success only for Unix', 6 ) ; }
3745 is( '0100', umask_str( '0100' ), 'umask_str: 0100 => 0100' ) ;
3746 is( '0001', umask_str( '0001' ), 'umask_str: 0001 => 0001' ) ;
3747 is( '0777', umask_str( '0777' ), 'umask_str: 0777 => 0777' ) ;
3748 is( '0777', umask_str( '00777' ), 'umask_str: 00777 => 0777' ) ;
3749 is( '0777', umask_str( ' 777 ' ), 'umask_str: 777 => 0777' ) ;
3750 is( "$UMASK_PARANO", umask_str( $UMASK_PARANO ), "umask_str: UMASK_PARANO $UMASK_PARANO => $UMASK_PARANO" ) ;
3751 }
3752
3753 is( $save_umask_str, umask_str( $save_umask_str ), 'umask_str: restore with str' ) ;
3754 is( $save_umask, umask, 'umask_str: umask is restored, controlled by direct umask' ) ;
3755 is( $save_umask, umask $save_umask, 'umask_str: umask is restored by direct umask' ) ;
3756 is( $save_umask, umask, 'umask_str: umask initial controlled by direct umask' ) ;
3757
3758 note( 'Leaving tests_umask_str()' ) ;
3759 return ;
3760}
3761
3762sub umask_str
3763{
3764 my $value = shift ;
3765
3766 if ( defined $value ) {
3767 umask oct( $value ) ;
3768 }
3769 my $current = umask ;
3770
3771 return( sprintf( '%#04o', $current ) ) ;
3772}
3773
3774sub tests_umask
3775{
3776 note( 'Entering tests_umask()' ) ;
3777
3778 my $save_umask ;
3779 is( umask, umask, 'umask: umask is umask' ) ;
3780 is( $save_umask = umask, umask, "umask: umask is umask again + save it: $save_umask" ) ;
3781 is( $save_umask, umask oct(0000), 'umask: umask 0000' ) ;
3782 is( oct(0000), umask, 'umask: umask is now 0000' ) ;
3783 is( oct(0000), umask oct(777), 'umask: umask 0777 call, previous 0000' ) ;
3784
3785 SKIP: {
3786 if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests success only for Unix', 2 ) ; }
3787 is( oct(777), umask, 'umask: umask is now 0777' ) ;
3788 is( oct(777), umask $save_umask, "umask: umask $save_umask restore inital value, previous 0777" ) ;
3789 }
3790
3791 ok( defined umask $save_umask, "umask: umask $save_umask restore inital value, previous defined" ) ;
3792 is( $save_umask, umask, 'umask: umask is umask restored' ) ;
3793 note( 'Leaving tests_umask()' ) ;
3794
3795 return ;
3796}
3797
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003798sub buggyflagsregex
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003799{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003800 # From /X analyse
3801 # cut -d: -f1 Error_112_all_syncs.txt | xargs egrep -oih 'Invalid system flag [^( ]+' | sort | uniq -c | sort -g
3802 my @buggyflagsregex = ( 's/\\\\RECEIPTCHECKED|\\\\Indexed|\\\\X-EON-HAS-ATTACHMENT|\\\\UNSEEN|\\\\ATTACHED|\\\\X-HAS-ATTACH|\\\\FORWARDED|\\\\FORWARD|\\\\X-FORWARDED|\\\\\$FORWARDED|\\\\PRIORITY|\\\\READRCPT//g' ) ;
3803 return( @buggyflagsregex ) ;
3804}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003805
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003806sub cgisetcontext
3807{
3808 my $mysync = shift ;
3809 if ( ! under_cgi_context( $mysync ) ) { return ; }
3810
3811 output( $mysync, "Under cgi context\n" ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003812
3813
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003814 set_umask( $mysync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003815
3816 # Remove all content in unsafe evaled options
3817 @{ $mysync->{ regextrans2 } } = ( ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003818
3819 @{ $mysync->{ regexflag } } = buggyflagsregex( ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003820
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003821 @regexmess = ( ) ;
3822 @skipmess = ( ) ;
3823 @pipemess = ( ) ;
3824 $delete2foldersonly = undef ;
3825 $delete2foldersbutnot = undef ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003826 $maxlinelengthcmd = undef ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003827
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003828 # Set safe default values (I hope...)
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003829
3830
3831 #$mysync->{pidfile} = 'imapsync.pid' ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003832 $mysync->{ pidfilelocking } = 1 ;
3833 $mysync->{ errorsmax } = $ERRORS_MAX_CGI ;
3834 $modulesversion = 0 ;
3835 $mysync->{ releasecheck } = defined $mysync->{ releasecheck } ? $mysync->{ releasecheck } : 1 ;
3836 $usecache = 0 ;
3837 $mysync->{ showpasswords } = 0 ;
3838 $mysync->{ acc1 }->{ debugimap } = 0 ;
3839 $mysync->{ acc2 }->{ debugimap } = 0 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003840
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003841 $mysync->{ acc1 }->{ reconnectretry } = $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ;
3842 $mysync->{ acc2 }->{ reconnectretry } = $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ;
3843
3844 $pipemesscheck = 0 ;
3845
3846 $mysync->{ hashfile } = $CGI_HASHFILE ;
3847 my $hashsynclocal = hashsynclocal( $mysync ) || die "Can not get hashsynclocal. Exiting\n" ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003848
3849 if ( $ENV{ 'NET_SERVER_SOFTWARE' } and ( $ENV{ 'NET_SERVER_SOFTWARE' } =~ /Net::Server::HTTP/ ) )
3850 {
3851 # under local webserver
3852 $cgidir = q{.} ;
3853 }
3854 else
3855 {
3856 $cgidir = $CGI_TMPDIR_TOP . '/' . $hashsynclocal ;
3857 }
3858 -d $cgidir or mkpath $cgidir or die "Can not create $cgidir: $OS_ERROR\n" ;
3859 $mysync->{ tmpdir } = $cgidir ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003860 $mysync->{ logdir } = '' ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003861
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003862 chdir $cgidir or die "Can not cd to $cgidir: $OS_ERROR\n" ;
3863 cgioutputenvcontext( $mysync ) ;
3864 $mysync->{ debug } and output( $mysync, 'Current directory is ' . getcwd( ) . "\n" ) ;
3865 $mysync->{ debug } and output( $mysync, 'Real user id is ' . getpwuid_any_os( $REAL_USER_ID ) . " (uid $REAL_USER_ID)\n" ) ;
3866 $mysync->{ debug } and output( $mysync, 'Effective user id is ' . getpwuid_any_os( $EFFECTIVE_USER_ID ). " (euid $EFFECTIVE_USER_ID)\n" ) ;
3867
3868 $mysync->{ skipemptyfolders } = defined $mysync->{ skipemptyfolders } ? $mysync->{ skipemptyfolders } : 1 ;
3869
3870 # Out of memory with messages over 1 GB ?
3871 $mysync->{ maxsize } = defined $mysync->{ maxsize } ? $mysync->{ maxsize } : 1_000_000_000 ;
3872
3873 # tail -f behaviour on by default
3874 $mysync->{ tail } = defined $mysync->{ tail } ? $mysync->{ tail } : 1 ;
3875
3876 # not sure it's for good
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003877 @useheader = qw( Message-Id Received ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003878
3879 # addheader on by default
3880 $mysync->{ addheader } = defined $mysync->{ addheader } ? $mysync->{ addheader } : 1 ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003881
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003882 # sync duplicates by default in cgi context
3883 $mysync->{ syncduplicates } = defined $mysync->{ syncduplicates } ? $mysync->{ syncduplicates } : 1 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003884
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003885 # log the logfile name by default in cgi context
3886 $mysync->{ loglogfile } = defined $mysync->{ loglogfile } ? $mysync->{ loglogfile } : 1 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003887 return ;
3888}
3889
3890sub cgioutputenvcontext
3891{
3892 my $mysync = shift @ARG ;
3893
3894 for my $envvar ( qw( REMOTE_ADDR REMOTE_HOST HTTP_REFERER HTTP_USER_AGENT SERVER_SOFTWARE SERVER_PORT HTTP_COOKIE ) ) {
3895
3896 my $envval = $ENV{ $envvar } || q{} ;
3897 if ( $envval ) { output( $mysync, "$envvar is $envval\n" ) } ;
3898 }
3899
3900 return ;
3901}
3902
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003903sub announcelogfile
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003904{
3905 my $mysync = shift ;
3906
3907 if ( $mysync->{ log } )
3908 {
3909 myprint( "Log file is $mysync->{ logfile } ( to change it, use --logfile path ; or use --nolog to turn off logging )\n" ) ;
3910 loglogfile( $mysync ) ;
3911 }
3912 else
3913 {
3914 myprint( "No log file because of option --nolog\n" ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003915 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003916 return ;
3917}
3918
3919
3920sub loglogfile
3921{
3922 my $mysync = shift ;
3923 if ( ! $mysync->{ loglogfile } ) { return ; }
3924 if ( ! $mysync->{ log } ) { return ; }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003925
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003926 my $cwd = getcwd( ) ;
3927 my $absolutelogfilepath ;
3928 # Fixme: add case when the logfile name is already absolute
3929 $absolutelogfilepath = "$cwd/$mysync->{ logfile }" ;
3930 my $loglogfilename = '../list_all_logs_auto.txt' ;
3931 myprint( "Writing log file name $absolutelogfilepath to $loglogfilename\n" ) ;
3932 if ( open( my $fh, '>>', $loglogfilename ) )
3933 {
3934 print $fh "$absolutelogfilepath\n" ;
3935 close $fh ;
3936 }
3937 else
3938 {
3939 myprint( "Could not open loglogfile $loglogfilename $!\n" ) ;
3940 }
3941 return ;
3942}
3943
3944
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003945sub checkselectable
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003946{
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003947 my $mysync = shift ;
3948
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003949 if ( $mysync->{ checkselectable } ) {
3950 my @h1_folders_wanted_selectable ;
3951 myprint( "Host1: Checking wanted folders are selectable. Use --nocheckselectable to avoid this check.\n" ) ;
3952 foreach my $folder ( @{ $mysync->{ h1_folders_wanted } } )
3953 {
3954 ( $mysync->{ debug } or $mysync->{ debugfolders } ) and myprint( "Checking $folder is selectable on host1\n" ) ;
3955 # It does an imap command LIST "" $folder and then search for no \Noselect
3956 if ( ! $mysync->{ imap1 }->selectable( $folder ) )
3957 {
3958 myprint( "Host1: warning! ignoring folder $folder because it is not selectable\n" ) ;
3959 }else
3960 {
3961 push @h1_folders_wanted_selectable, $folder ;
3962 }
3963 }
3964 @{ $mysync->{ h1_folders_wanted } } = @h1_folders_wanted_selectable ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003965 ( $mysync->{ debug } or $mysync->{ debugfolders } )
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003966 and myprint( 'Host1: checking folders took ', timenext( $mysync ), " s\n" ) ;
3967 }
3968 else
3969 {
3970 myprint( "Host1: Not checking that wanted folders are selectable. Use --checkselectable to force this check.\n" ) ;
3971 }
3972 return ;
3973}
3974
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003975sub setcheckselectable
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003976{
3977 my $mysync = shift ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003978
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003979 my $h1_folders_wanted_nb = scalar @{ $mysync->{ h1_folders_wanted } } ;
3980 # 152 because 98% of host1 accounts have less than 152 folders on /X service.
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003981 # command to get this value:
3982 # datamash_file_op_index G_Host1_Nb_folders.txt perc:98 4 %16.1f
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003983 if ( ! defined $mysync->{ checkselectable } )
3984 {
3985 if ( 152 >= $h1_folders_wanted_nb )
3986 {
3987 $mysync->{ checkselectable } = 1 ;
3988 }else{
3989 myprint( "Host1: Not checking that $h1_folders_wanted_nb wanted folders are selectable. Use --checkselectable to force this check.\n" ) ;
3990 $mysync->{ checkselectable } = 0 ;
3991 }
3992 }
3993 return ;
3994}
3995
3996
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01003997
3998sub debugsleep
3999{
4000 my $mysync = shift @ARG ;
4001 if ( defined $mysync->{debugsleep} ) {
4002 myprint( "Info: sleeping $mysync->{debugsleep}s\n" ) ;
4003 sleep $mysync->{debugsleep} ;
4004 }
4005 return ;
4006}
4007
4008sub tests_foldersize
4009{
4010 note( 'Entering tests_foldersize()' ) ;
4011
4012 is( undef, foldersize( ), 'foldersize: no args => undef' ) ;
4013
4014
4015 #is_deeply( {}, {}, 'foldersize: a hash is a hash' ) ;
4016 #is_deeply( [], [], 'foldersize: an array is an array' ) ;
4017 note( 'Leaving tests_foldersize()' ) ;
4018 return ;
4019}
4020
4021
4022
4023# Globals:
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004024# $fetch_hash_set
4025#
4026sub foldersize
4027{
4028 # size of one folder
4029 my ( $mysync, $side, $imap, $search_cmd, $abletosearch, $folder ) = @ARG ;
4030
4031 if ( ! all_defined( $mysync, $side, $imap, $folder ) )
4032 {
4033 return ;
4034 }
4035
4036 # FTGate is RFC buggy with EXAMINE it does not act as SELECT
4037 #if ( ! $imap->examine( $folder ) ) {
4038 if ( ! $imap->select( $folder ) ) {
4039 my $error = join q{},
4040 "$side Folder $folder: Could not select: ",
4041 $imap->LastError, "\n" ;
4042 errors_incr( $mysync, $error ) ;
4043 return ;
4044 }
4045
4046 if ( $imap->IsUnconnected( ) )
4047 {
4048 return ;
4049 }
4050
4051 my $hash_ref = { } ;
4052 my @msgs = select_msgs( $imap, undef, $search_cmd, $abletosearch, $folder ) ;
4053 my $nb_msgs = scalar @msgs ;
4054 my $biggest_in_folder = 0 ;
4055 @{ $hash_ref }{ @msgs } = ( undef ) if @msgs ;
4056
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02004057
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004058 my $stot = 0 ;
4059
4060 if ( $imap->IsUnconnected( ) )
4061 {
4062 return ;
4063 }
4064
4065 if ( $nb_msgs > 0 and @msgs ) {
4066 if ( $abletosearch ) {
4067 if ( ! $imap->fetch_hash( \@msgs, 'RFC822.SIZE', $hash_ref) ) {
4068 my $error = "$side failure with fetch_hash: $EVAL_ERROR\n" ;
4069 errors_incr( $mysync, $error ) ;
4070 return ;
4071 }
4072 }
4073 else
4074 {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02004075 my $fetch_hash_uids = $fetch_hash_set || "1:*" ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004076 if ( ! $imap->fetch_hash( $fetch_hash_uids, 'RFC822.SIZE', $hash_ref ) ) {
4077 my $error = "$side failure with fetch_hash: $EVAL_ERROR\n" ;
4078 errors_incr( $mysync, $error ) ;
4079 return ;
4080 }
4081 }
4082 for ( keys %{ $hash_ref } ) {
4083 my $size = $hash_ref->{ $_ }->{ 'RFC822.SIZE' } ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02004084 if ( defined $size )
4085 {
4086 $stot += $size ;
4087 $biggest_in_folder = max( $biggest_in_folder, $size ) ;
4088 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004089 }
4090 }
4091 return( $stot, $nb_msgs, $biggest_in_folder ) ;
4092
4093}
4094
4095
4096# The old subroutine that performed just one side at a time.
4097# Still here for a while, until confident with sub foldersize_diff_compute()
4098sub foldersizes
4099{
4100 my ( $mysync, $side, $imap, $search_cmd, $abletosearch, @folders ) = @_ ;
4101 my $total_size = 0 ;
4102 my $total_nb = 0 ;
4103 my $biggest_in_all = 0 ;
4104
4105 my $nb_folders = scalar @folders ;
4106 my $ct_folders = 0 ; # folder counter.
4107 myprint( "++++ Calculating sizes of $nb_folders folders on $side\n" ) ;
4108 foreach my $folder ( @folders ) {
4109 my $stot = 0 ;
4110 my $nb_msgs = 0 ;
4111 my $biggest_in_folder = 0 ;
4112
4113 $ct_folders++ ;
4114 myprintf( "$side folder %7s %-35s", "$ct_folders/$nb_folders", jux_utf8( $folder ) ) ;
4115 if ( 'Host2' eq $side and not exists $mysync->{h2_folders_all_UPPER}{ uc $folder } ) {
4116 myprint( " does not exist yet\n") ;
4117 next ;
4118 }
4119 if ( 'Host1' eq $side and not exists $h1_folders_all{ $folder } ) {
4120 myprint( " does not exist\n" ) ;
4121 next ;
4122 }
4123
4124 last if $imap->IsUnconnected( ) ;
4125
4126 ( $stot, $nb_msgs, $biggest_in_folder ) = foldersize( $mysync, $side, $imap, $search_cmd, $abletosearch, $folder ) ;
4127
4128 myprintf( ' Size: %9s', $stot ) ;
4129 myprintf( ' Messages: %5s', $nb_msgs ) ;
4130 myprintf( " Biggest: %9s\n", $biggest_in_folder ) ;
4131 $total_size += $stot ;
4132 $total_nb += $nb_msgs ;
4133 $biggest_in_all = max( $biggest_in_all, $biggest_in_folder ) ;
4134 }
4135 myprintf( "%s Nb folders: %11s folders\n", $side, $nb_folders ) ;
4136 myprintf( "%s Nb messages: %11s messages\n", $side, $total_nb ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01004137 myprintf( "%s Total size: %11s bytes (%s)\n", $side, $total_size, bytes_display_string_bin( $total_size ) ) ;
4138 myprintf( "%s Biggest message: %11s bytes (%s)\n", $side, $biggest_in_all, bytes_display_string_bin( $biggest_in_all ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004139 myprintf( "%s Time spent on sizing: %11.1f seconds\n", $side, timenext( $mysync ) ) ;
4140 return( $total_nb, $total_size ) ;
4141}
4142
4143
4144sub foldersize_diff_present
4145{
4146 my $mysync = shift ;
4147 my $folder1 = shift ;
4148 my $folder2 = shift ;
4149 my $counter_str = shift ;
4150 my $force = shift ;
4151
4152 my $values1_str ;
4153 my $values2_str ;
4154
4155 if ( ! defined $mysync->{ folder1 }->{ $folder1 }->{ size } || $force )
4156 {
4157 foldersize_diff_compute( $mysync, $folder1, $folder2, $force ) ;
4158 }
4159
4160 # again, but this time it means no availaible data.
4161 if ( defined $mysync->{ folder1 }->{ $folder1 }->{ size } )
4162 {
4163 $values1_str = sprintf( "Size: %9s Messages: %5s Biggest: %9s\n",
4164 $mysync->{ folder1 }->{ $folder1 }->{ size },
4165 $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs },
4166 $mysync->{ folder1 }->{ $folder1 }->{ biggest },
4167 ) ;
4168 }
4169 else
4170 {
4171 $values1_str = " does not exist\n" ;
4172 }
4173
4174 if ( defined $mysync->{ folder2 }->{ $folder2 }->{ size } )
4175 {
4176 $values2_str = sprintf( "Size: %9s Messages: %5s Biggest: %9s\n",
4177 $mysync->{ folder2 }->{ $folder2 }->{ size },
4178 $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs },
4179 $mysync->{ folder2 }->{ $folder2 }->{ biggest },
4180 ) ;
4181 }
4182 else
4183 {
4184 $values2_str = " does not exist yet\n" ;
4185 }
4186
4187 myprintf( "Host1 folder %7s %-35s %s",
4188 "$counter_str",
4189 jux_utf8( $folder1 ),
4190 $values1_str
4191 ) ;
4192
4193 myprintf( "Host2 folder %7s %-35s %s",
4194 "$counter_str",
4195 jux_utf8( $folder2 ),
4196 $values2_str
4197 ) ;
4198
4199 myprintf( "Host2-Host1 %7s %-35s %9s %5s %9s\n\n",
4200 "",
4201 "",
4202 $mysync->{ folder1 }->{ $folder1 }->{ size_diff },
4203 $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs_diff },
4204 $mysync->{ folder1 }->{ $folder1 }->{ biggest_diff },
4205
4206 ) ;
4207
4208
4209
4210
4211 return ;
4212}
4213
4214sub foldersize_diff_compute
4215{
4216 my $mysync = shift ;
4217 my $folder1 = shift ;
4218 my $folder2 = shift ;
4219 my $force = shift ;
4220
4221
4222
4223 my ( $size_1, $nb_msgs_1, $biggest_1 ) ;
4224 # memoization
4225 if (
4226 exists $h1_folders_all{ $folder1 }
4227 &&
4228 (
4229 ! defined $mysync->{ folder1 }->{ $folder1 }->{ size }
4230 || $force
4231 )
4232 )
4233 {
4234 #myprint( "foldersize folder1 $h1_folders_all{ $folder1 }\n" ) ;
4235 ( $size_1, $nb_msgs_1, $biggest_1 ) =
4236 foldersize( $mysync,
4237 'Host1',
4238 $mysync->{ imap1 },
4239 $mysync->{ search1 },
4240 $mysync->{ abletosearch1 },
4241 $folder1
4242 ) ;
4243 $mysync->{ folder1 }->{ $folder1 }->{ size } = $size_1 ;
4244 $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs } = $nb_msgs_1 ;
4245 $mysync->{ folder1 }->{ $folder1 }->{ biggest } = $biggest_1 ;
4246 }
4247 else
4248 {
4249 $size_1 = $mysync->{ folder1 }->{ $folder1 }->{ size } ;
4250 $nb_msgs_1 = $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs } ;
4251 $biggest_1 = $mysync->{ folder1 }->{ $folder1 }->{ biggest } ;
4252
4253 }
4254
4255
4256 my ( $size_2, $nb_msgs_2, $biggest_2 ) ;
4257 if (
4258 exists $mysync->{ h2_folders_all_UPPER }{ uc $folder2 }
4259 &&
4260 (
4261 ! defined $mysync->{ folder2 }->{ $folder2 }->{ size }
4262 || $force
4263 )
4264 )
4265 {
4266 #myprint( "foldersize folder2\n" ) ;
4267 ( $size_2, $nb_msgs_2, $biggest_2 ) =
4268 foldersize( $mysync,
4269 'Host2',
4270 $mysync->{ imap2 },
4271 $mysync->{ search2 },
4272 $mysync->{ abletosearch2 },
4273 $folder2
4274 ) ;
4275
4276 $mysync->{ folder2 }->{ $folder2 }->{ size } = $size_2 ;
4277 $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs } = $nb_msgs_2 ;
4278 $mysync->{ folder2 }->{ $folder2 }->{ biggest } = $biggest_2 ;
4279 }
4280 else
4281 {
4282 $size_2 = $mysync->{ folder2 }->{ $folder2 }->{ size } ;
4283 $nb_msgs_2 = $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs } ;
4284 $biggest_2 = $mysync->{ folder2 }->{ $folder2 }->{ biggest } ;
4285
4286 }
4287
4288
4289 my $size_diff = diff( $size_2, $size_1 ) ;
4290 my $nb_msgs_diff = diff( $nb_msgs_2, $nb_msgs_1 ) ;
4291 my $biggest_diff = diff( $biggest_2, $biggest_1 ) ;
4292
4293 $mysync->{ folder1 }->{ $folder1 }->{ size_diff } = $size_diff ;
4294 $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs_diff } = $nb_msgs_diff ;
4295 $mysync->{ folder1 }->{ $folder1 }->{ biggest_diff } = $biggest_diff ;
4296
4297 # It's redundant but easier to access later
4298 $mysync->{ folder2 }->{ $folder2 }->{ size_diff } = $size_diff ;
4299 $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs_diff } = $nb_msgs_diff ;
4300 $mysync->{ folder2 }->{ $folder2 }->{ biggest_diff } = $biggest_diff ;
4301
4302 return ;
4303}
4304
4305sub diff
4306{
4307 my $x = shift ;
4308 my $y = shift ;
4309
4310 $x ||= 0 ;
4311 $y ||= 0 ;
4312
4313 return $x - $y ;
4314}
4315
4316sub add
4317{
4318 my $x = shift ;
4319 my $y = shift ;
4320
4321 $x ||= 0 ;
4322 $y ||= 0 ;
4323
4324 return $x + $y ;
4325}
4326
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02004327sub tests_checknoabletosearch
4328{
4329 note( 'Entering checknoabletosearch()' ) ;
4330
4331 is( undef, checknoabletosearch( ), 'checknoabletosearch: no args => undef' ) ;
4332
4333 note( 'Leaving checknoabletosearch()' ) ;
4334 return ;
4335}
4336
4337
4338
4339
4340sub checknoabletosearch
4341{
4342 # call example: checknoabletosearch( $sync, $sync->{ imap1 }, 'INBOX', 'Host1' ) ;
4343 # output:
4344 # * undef if something is not ok to decide
4345 # * 1 if SEARCH ALL failed
4346
4347 my( $mysync, $imap, $folder, $HostX ) = @ARG ;
4348
4349 if ( ! all_defined( $mysync, $imap, $folder, $HostX ) )
4350 {
4351 return ;
4352 }
4353
4354 myprint( "$HostX: checking if SEARCH ALL works on $folder\n" ) ;
4355 if ( ! select_folder( $mysync, $imap, $folder, $HostX ) )
4356 {
4357 myprint( "$HostX: can not SELECT folder [$folder]\n" ) ;
4358 return ;
4359 }
4360 my $count_from_select = count_from_select( $imap->History ) ;
4361 myprint( "$HostX: folder [$folder] has $count_from_select messages mentioned by SELECT\n" ) ;
4362
4363 my $msgs_all = $imap->messages( ) ;
4364 if ( ! $msgs_all )
4365 {
4366 myprint( "$HostX: can not SEARCH ALL folder [$folder]\n" ) ;
4367 myprint( "$HostX: ", $imap->LastError(), "\n" ) ;
4368 return 1 ;
4369 }
4370
4371 my $count_from_search_all = scalar( @{ $msgs_all } ) ;
4372 myprint( "$HostX: folder [$folder] has $count_from_search_all messages found by SEARCH ALL\n" ) ;
4373
4374 if ( $count_from_select == $count_from_search_all )
4375 {
4376 myprint( "$HostX: folder [$folder] has the same messages count ($count_from_select) by SELECT and SEARCH ALL\n" ) ;
4377 }
4378 else
4379 {
4380 myprint( "$HostX: Warning, folder [$folder] has not the same count by SELECT ($count_from_select) and SEARCH ALL ($count_from_search_all)\n" ) ;
4381 return 1 ;
4382 }
4383
4384 return ;
4385}
4386
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004387
4388sub foldersizes_diff_list
4389{
4390 my $mysync = shift ;
4391 my $force = shift ;
4392
4393 my @folders = @{ $mysync->{h1_folders_wanted} } ;
4394 my $nb_folders = scalar @folders ;
4395 my $ct_folders = 0 ; # folder counter.
4396
4397 foreach my $folder1 ( @folders )
4398 {
4399 $ct_folders++ ;
4400 my $counter_str = "$ct_folders/$nb_folders" ;
4401 my $folder2 = imap2_folder_name( $mysync, $folder1 ) ;
4402 foldersize_diff_present( $mysync, $folder1, $folder2, $counter_str, $force ) ;
4403 }
4404
4405 return ;
4406}
4407
4408sub foldersizes_total
4409{
4410 my $mysync = shift ;
4411
4412 my @folders_1 = @{ $mysync->{h1_folders_wanted} } ;
4413 my @folders_2 = @h2_folders_from_1_wanted ;
4414
4415 my $nb_folders_1 = scalar( @folders_1 ) ;
4416 my $nb_folders_2 = scalar( @folders_2 ) ;
4417
4418 my ( $total_size_1, $total_nb_1, $biggest_in_all_1 ) = ( 0, 0, 0 ) ;
4419 my ( $total_size_2, $total_nb_2, $biggest_in_all_2 ) = ( 0, 0, 0 ) ;
4420
4421 foreach my $folder1 ( @folders_1 )
4422 {
4423 $total_size_1 = add( $total_size_1, $mysync->{ folder1 }->{ $folder1 }->{ size } ) ;
4424 $total_nb_1 = add( $total_nb_1, $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs } ) ;
4425 $biggest_in_all_1 = max( $biggest_in_all_1 , $mysync->{ folder1 }->{ $folder1 }->{ biggest } ) ;
4426 }
4427
4428 foreach my $folder2 ( @folders_2 )
4429 {
4430 $total_size_2 = add( $total_size_2, $mysync->{ folder2 }->{ $folder2 }->{ size } ) ;
4431 $total_nb_2 = add( $total_nb_2, $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs } ) ;
4432 $biggest_in_all_2 = max( $biggest_in_all_2 , $mysync->{ folder2 }->{ $folder2 }->{ biggest } ) ;
4433
4434 }
4435
4436 myprintf( "Host1 Nb folders: %11s folders\n", $nb_folders_1 ) ;
4437 myprintf( "Host2 Nb folders: %11s folders\n", $nb_folders_2 ) ;
4438 myprint( "\n" ) ;
4439 myprintf( "Host1 Nb messages: %11s messages\n", $total_nb_1 ) ;
4440 myprintf( "Host2 Nb messages: %11s messages\n", $total_nb_2 ) ;
4441 myprint( "\n" ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01004442 myprintf( "Host1 Total size: %11s bytes (%s)\n", $total_size_1, bytes_display_string_bin( $total_size_1 ) ) ;
4443 myprintf( "Host2 Total size: %11s bytes (%s)\n", $total_size_2, bytes_display_string_bin( $total_size_2 ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004444 myprint( "\n" ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01004445 myprintf( "Host1 Biggest message: %11s bytes (%s)\n", $biggest_in_all_1, bytes_display_string_bin( $biggest_in_all_1 ) ) ;
4446 myprintf( "Host2 Biggest message: %11s bytes (%s)\n", $biggest_in_all_2, bytes_display_string_bin( $biggest_in_all_2 ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004447 myprint( "\n" ) ;
4448 myprintf( "Time spent on sizing: %11.1f seconds\n", timenext( $mysync ) ) ;
4449
4450 my @total_1_2 = ( $total_nb_1, $total_size_1, $total_nb_2, $total_size_2 ) ;
4451 return @total_1_2 ;
4452}
4453
4454sub foldersizesatend_old
4455{
4456 my $mysync = shift ;
4457 timenext( $mysync ) ;
4458 return if ( $mysync->{imap1}->IsUnconnected( ) ) ;
4459 return if ( $mysync->{imap2}->IsUnconnected( ) ) ;
4460 # Get all folders on host2 again since new were created
4461 @h2_folders_all = sort $mysync->{imap2}->folders();
4462 for ( @h2_folders_all ) {
4463 $h2_folders_all{ $_ } = 1 ;
4464 $mysync->{h2_folders_all_UPPER}{ uc $_ } = 1 ;
4465 } ;
4466 ( $h1_nb_msg_end, $h1_bytes_end ) = foldersizes( $mysync, 'Host1', $mysync->{imap1}, $mysync->{ search1 }, $mysync->{abletosearch1}, @{ $mysync->{h1_folders_wanted} } ) ;
4467 ( $h2_nb_msg_end, $h2_bytes_end ) = foldersizes( $mysync, 'Host2', $mysync->{imap2}, $mysync->{ search2 }, $mysync->{abletosearch2}, @h2_folders_from_1_wanted ) ;
4468 if ( not all_defined( $h1_nb_msg_end, $h1_bytes_end, $h2_nb_msg_end, $h2_bytes_end ) ) {
4469 my $error = "Failure getting foldersizes, final differences will not be calculated\n" ;
4470 errors_incr( $mysync, $error ) ;
4471 }
4472 return ;
4473}
4474
4475sub foldersizesatend
4476{
4477 my $mysync = shift ;
4478 timenext( $mysync ) ;
4479 return if ( $mysync->{imap1}->IsUnconnected( ) ) ;
4480 return if ( $mysync->{imap2}->IsUnconnected( ) ) ;
4481 # Get all folders on host2 again since new were created
4482 @h2_folders_all = sort $mysync->{imap2}->folders();
4483 for ( @h2_folders_all ) {
4484 $h2_folders_all{ $_ } = 1 ;
4485 $mysync->{h2_folders_all_UPPER}{ uc $_ } = 1 ;
4486 } ;
4487
4488
4489 foldersizes_diff_list( $mysync, $FORCE ) ;
4490
4491 ( $h1_nb_msg_end, $h1_bytes_end, $h2_nb_msg_end, $h2_bytes_end )
4492 = foldersizes_total( $mysync ) ;
4493
4494
4495 if ( not all_defined( $h1_nb_msg_end, $h1_bytes_end, $h2_nb_msg_end, $h2_bytes_end ) ) {
4496 my $error = "Failure getting foldersizes, final differences will not be calculated\n" ;
4497 errors_incr( $mysync, $error ) ;
4498 }
4499 return ;
4500}
4501
4502
4503sub foldersizes_at_the_beggining
4504{
4505 my $mysync = shift ;
4506
4507 myprint( << 'END_SIZE' ) ;
4508
4509Folders sizes before the synchronization.
4510You can remove foldersizes listings by using "--nofoldersizes" and "--nofoldersizesatend"
4511but then you will also lose the ETA (Estimation Time of Arrival) given after each message copy.
4512END_SIZE
4513
4514 foldersizes_diff_list( $mysync ) ;
4515
4516 ( $mysync->{ h1_nb_msg_start }, $mysync->{ h1_bytes_start },
4517 $mysync->{ h2_nb_msg_start }, $mysync->{ h2_bytes_start } )
4518 = foldersizes_total( $mysync ) ;
4519
4520
4521 if ( not all_defined(
4522 $mysync->{ h1_nb_msg_start },
4523 $mysync->{ h1_bytes_start },
4524 $mysync->{ h2_nb_msg_start },
4525 $mysync->{ h2_bytes_start } ) )
4526 {
4527 my $error = "Failure getting foldersizes, ETA and final diff will not be displayed\n" ;
4528 errors_incr( $mysync, $error ) ;
4529 $mysync->{ foldersizes } = 0 ;
4530 $mysync->{ foldersizesatend } = 0 ;
4531 return ;
4532 }
4533
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02004534 my $h2_bytes_limit = $mysync->{ acc2 }->{quota_limit_bytes} || 0 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004535 if ( $h2_bytes_limit and ( $h2_bytes_limit < $mysync->{ h1_bytes_start } ) )
4536 {
4537 my $quota_percent = mysprintf( '%.0f', $NUMBER_100 * $mysync->{ h1_bytes_start } / $h2_bytes_limit ) ;
4538 my $error = "Host2: Quota limit will be exceeded! Over $quota_percent % ( $mysync->{ h1_bytes_start } bytes / $h2_bytes_limit bytes )\n" ;
4539 errors_incr( $mysync, $error ) ;
4540 }
4541 return ;
4542}
4543
4544
4545# Globals:
4546# @h2_folders_from_1_wanted
4547
4548sub foldersizes_at_the_beggining_old
4549{
4550 my $mysync = shift ;
4551
4552 myprint( << 'END_SIZE' ) ;
4553
4554Folders sizes before the synchronization.
4555You can remove foldersizes listings by using "--nofoldersizes" and "--nofoldersizesatend"
4556but then you will also lose the ETA (Estimation Time of Arrival) given after each message copy.
4557END_SIZE
4558
4559 ( $mysync->{ h1_nb_msg_start }, $mysync->{ h1_bytes_start } ) =
4560 foldersizes( $mysync, 'Host1', $mysync->{imap1}, $mysync->{ search1 },
4561 $mysync->{abletosearch1}, @{ $mysync->{h1_folders_wanted} } ) ;
4562 ( $mysync->{ h2_nb_msg_start }, $mysync->{ h2_bytes_start } ) =
4563 foldersizes( $mysync, 'Host2', $mysync->{imap2}, $mysync->{ search2 },
4564 $mysync->{abletosearch2}, @h2_folders_from_1_wanted ) ;
4565
4566 if ( not all_defined( $mysync->{ h1_nb_msg_start },
4567 $mysync->{ h1_bytes_start }, $mysync->{ h2_nb_msg_start }, $mysync->{ h2_bytes_start } ) )
4568 {
4569 my $error = "Failure getting foldersizes, ETA and final diff will not be displayed\n" ;
4570 errors_incr( $mysync, $error ) ;
4571 $mysync->{ foldersizes } = 0 ;
4572 $mysync->{ foldersizesatend } = 0 ;
4573 return ;
4574 }
4575
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02004576 my $h2_bytes_limit = $mysync->{ acc2 }->{quota_limit_bytes} || 0 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004577 if ( $h2_bytes_limit and ( $h2_bytes_limit < $mysync->{ h1_bytes_start } ) )
4578 {
4579 my $quota_percent = mysprintf( '%.0f', $NUMBER_100 * $mysync->{ h1_bytes_start } / $h2_bytes_limit ) ;
4580 my $error = "Host2: Quota limit will be exceeded! Over $quota_percent % ( $mysync->{ h1_bytes_start } bytes / $h2_bytes_limit bytes )\n" ;
4581 errors_incr( $mysync, $error ) ;
4582 }
4583 return ;
4584}
4585
4586
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02004587sub tests_total_bytes_max_reached
4588{
4589 note( 'Entering tests_total_bytes_max_reached()' ) ;
4590
4591 is( undef, total_bytes_max_reached( ), 'total_bytes_max_reached: no args => undef' ) ;
4592
4593 my $mysync = {} ;
4594 is( undef, total_bytes_max_reached( $mysync ), 'total_bytes_max_reached: no exitwhenover => undef' ) ;
4595
4596 $mysync->{ exitwhenover } = 300 ;
4597 is( undef, total_bytes_max_reached( $mysync ), 'total_bytes_max_reached: exitwhenover 300 but no total_bytes_transferred => undef' ) ;
4598
4599 $mysync->{ total_bytes_transferred } = 200 ;
4600 is( undef, total_bytes_max_reached( $mysync ), 'total_bytes_max_reached: exitwhenover 300 but total_bytes_transferred 200 => undef' ) ;
4601
4602 $mysync->{ total_bytes_transferred } = 400 ;
4603 is( 1, total_bytes_max_reached( $mysync ), 'total_bytes_max_reached: exitwhenover 300 but total_bytes_transferred 400 => 1' ) ;
4604
4605
4606
4607 note( 'Leaving tests_total_bytes_max_reached()' ) ;
4608 return ;
4609}
4610
4611
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004612sub total_bytes_max_reached
4613{
4614 my $mysync = shift ;
4615
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02004616 if ( ! defined $mysync ) { return ; }
4617
4618 if ( ! $mysync->{ exitwhenover } )
4619 {
4620 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004621 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02004622
4623 if ( ! $mysync->{ total_bytes_transferred } )
4624 {
4625 return ;
4626 }
4627
4628 if ( $mysync->{ total_bytes_transferred } >= $mysync->{ exitwhenover } )
4629 {
4630 my $error = "Maximum bytes transferred reached, $mysync->{total_bytes_transferred} >= $mysync->{ exitwhenover }, ending sync\n" ;
4631 errors_incr( $mysync, $error ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004632 return( 1 ) ;
4633 }
4634 return ;
4635}
4636
4637
4638sub tests_mock_capability
4639{
4640 note( 'Entering tests_mock_capability()' ) ;
4641
4642 my $myimap ;
4643 ok( $myimap = mock_capability( ),
4644 'mock_capability: (1) no args => a Test::MockObject'
4645 ) ;
4646 ok( $myimap->isa( 'Test::MockObject' ),
4647 'mock_capability: (2) no args => a Test::MockObject'
4648 ) ;
4649
4650 is( undef, $myimap->capability( ),
4651 'mock_capability: (3) no args => capability undef'
4652 ) ;
4653
4654 ok( mock_capability( $myimap ),
4655 'mock_capability: (1) one arg => MockObject'
4656 ) ;
4657
4658 is( undef, $myimap->capability( ),
4659 'mock_capability: (2) one arg OO style => capability undef'
4660 ) ;
4661
4662 ok( mock_capability( $myimap, $NUMBER_123456 ),
4663 'mock_capability: (1) two args 123456 => capability 123456'
4664 ) ;
4665
4666 is( $NUMBER_123456, $myimap->capability( ),
4667 'mock_capability: (2) two args 123456 => capability 123456'
4668 ) ;
4669
4670 ok( mock_capability( $myimap, 'ABCD' ),
4671 'mock_capability: (1) two args ABCD => capability ABCD'
4672 ) ;
4673 is( 'ABCD', $myimap->capability( ),
4674 'mock_capability: (2) two args ABCD => capability ABCD'
4675 ) ;
4676
4677 ok( mock_capability( $myimap, [ 'ABCD' ] ),
4678 'mock_capability: (1) two args [ ABCD ] => capability [ ABCD ]'
4679 ) ;
4680 is_deeply( [ 'ABCD' ], $myimap->capability( ),
4681 'mock_capability: (2) two args [ ABCD ] => capability [ ABCD ]'
4682 ) ;
4683
4684 ok( mock_capability( $myimap, [ 'ABC', 'DEF' ] ),
4685 'mock_capability: (1) two args [ ABC, DEF ] => capability [ ABC, DEF ]'
4686 ) ;
4687 is_deeply( [ 'ABC', 'DEF' ], $myimap->capability( ),
4688 'mock_capability: (2) two args [ ABC, DEF ] => capability capability [ ABC, DEF ]'
4689 ) ;
4690
4691 ok( mock_capability( $myimap, 'ABC', 'DEF' ),
4692 'mock_capability: (1) two args ABC, DEF => capability [ ABC, DEF ]'
4693 ) ;
4694 is_deeply( [ 'ABC', 'DEF' ], [ $myimap->capability( ) ],
4695 'mock_capability: (2) two args ABC, DEF => capability capability [ ABC, DEF ]'
4696 ) ;
4697
4698 ok( mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ),
4699 'mock_capability: (1) two args IMAP4rev1, APPENDLIMIT=123456 => capability [ IMAP4rev1, APPENDLIMIT=123456 ]'
4700 ) ;
4701 is_deeply( [ 'IMAP4rev1', 'APPENDLIMIT=123456' ], [ $myimap->capability( ) ],
4702 'mock_capability: (2) two args IMAP4rev1, APPENDLIMIT=123456 => capability capability [ IMAP4rev1, APPENDLIMIT=123456 ]'
4703 ) ;
4704
4705 note( 'Leaving tests_mock_capability()' ) ;
4706 return ;
4707}
4708
4709sub sig_install_toggle_sleep
4710{
4711 my $mysync = shift ;
4712 if ( 'MSWin32' ne $OSNAME ) {
4713 #myprint( "sig_install( $mysync, \&toggle_sleep, 'USR1' )\n" ) ;
4714 sig_install( $mysync, 'toggle_sleep', 'USR1' ) ;
4715 }
4716 #myprint( "Leaving sig_install_toggle_sleep\n" ) ;
4717 return ;
4718}
4719
4720
4721sub mock_capability
4722{
4723 my $myimap = shift ;
4724 my @has_capability_value = @ARG ;
4725 my ( $has_capability_value ) = @has_capability_value ;
4726
4727 if ( ! $myimap )
4728 {
4729 require_ok( "Test::MockObject" ) ;
4730 $myimap = Test::MockObject->new( ) ;
4731 }
4732
4733 $myimap->mock(
4734 'capability',
4735 sub { return wantarray ?
4736 @has_capability_value
4737 : $has_capability_value ;
4738 }
4739 ) ;
4740
4741 return $myimap ;
4742}
4743
4744
4745sub tests_capability_of
4746{
4747 note( 'Entering tests_capability_of()' ) ;
4748
4749 is( undef, capability_of( ),
4750 'capability_of: no args => undef' ) ;
4751
4752 my $myimap ;
4753 is( undef, capability_of( $myimap ),
4754 'capability_of: undef => undef' ) ;
4755
4756
4757 $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ;
4758
4759 is( undef, capability_of( $myimap, 'CACA' ),
4760 'capability_of: two args unknown capability => undef' ) ;
4761
4762
4763 is( $NUMBER_123456, capability_of( $myimap, 'APPENDLIMIT' ),
4764 'capability_of: two args APPENDLIMIT 123456 => 123456 yeah!' ) ;
4765
4766 note( 'Leaving tests_capability_of()' ) ;
4767 return ;
4768}
4769
4770
4771sub capability_of
4772{
4773 my $imap = shift || return ;
4774 my $capability_keyword = shift || return ;
4775
4776 my @capability = $imap->capability ;
4777
4778 if ( ! @capability ) { return ; }
4779 my $capability_value = search_in_array( $capability_keyword, @capability ) ;
4780
4781 return $capability_value ;
4782}
4783
4784
4785sub tests_search_in_array
4786{
4787 note( 'Entering tests_search_in_array()' ) ;
4788
4789 is( undef, search_in_array( 'KA' ),
4790 'search_in_array: no array => undef ' ) ;
4791
4792 is( 'VA', search_in_array( 'KA', ( 'KA=VA' ) ),
4793 'search_in_array: KA KA=VA => VA ' ) ;
4794
4795 is( 'VA', search_in_array( 'KA', ( 'KA=VA', 'KB=VB' ) ),
4796 'search_in_array: KA KA=VA KB=VB => VA ' ) ;
4797
4798 is( 'VB', search_in_array( 'KB', ( 'KA=VA', 'KB=VB' ) ),
4799 'search_in_array: KA=VA KB=VB => VB ' ) ;
4800
4801 note( 'Leaving tests_search_in_array()' ) ;
4802 return ;
4803}
4804
4805sub search_in_array
4806{
4807 my ( $key, @array ) = @ARG ;
4808
4809 foreach my $item ( @array )
4810 {
4811
4812 if ( $item =~ /([^=]+)=(.*)/ )
4813 {
4814 if ( $1 eq $key )
4815 {
4816 return $2 ;
4817 }
4818 }
4819 }
4820
4821 return ;
4822}
4823
4824
4825
4826
4827sub tests_appendlimit_from_capability
4828{
4829 note( 'Entering tests_appendlimit_from_capability()' ) ;
4830
4831 is( undef, appendlimit_from_capability( ),
4832 'appendlimit_from_capability: no args => undef'
4833 ) ;
4834
4835 my $myimap ;
4836 is( undef, appendlimit_from_capability( $myimap ),
4837 'appendlimit_from_capability: undef arg => undef'
4838 ) ;
4839
4840
4841 $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ;
4842
4843 # Normal behavior
4844 is( $NUMBER_123456, appendlimit_from_capability( $myimap ),
4845 'appendlimit_from_capability: APPENDLIMIT=123456 => 123456'
4846 ) ;
4847
4848 # Not a number
4849 $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=ABC' ) ;
4850
4851 is( undef, appendlimit_from_capability( $myimap ),
4852 'appendlimit_from_capability: not a number => undef'
4853 ) ;
4854
4855 note( 'Leaving tests_appendlimit_from_capability()' ) ;
4856 return ;
4857}
4858
4859
4860sub appendlimit_from_capability
4861{
4862 my $myimap = shift ;
4863 if ( ! $myimap )
4864 {
4865 myprint( "Warn: no imap with call to appendlimit_from_capability\n" ) ;
4866 return ;
4867 }
4868
4869 #myprint( Data::Dumper->Dump( [ \$myimap ] ) ) ;
4870 my $appendlimit = capability_of( $myimap, 'APPENDLIMIT' ) ;
4871 #myprint( "has_capability APPENDLIMIT $appendlimit\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02004872 if ( is_integer( $appendlimit ) )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004873 {
4874 return $appendlimit ;
4875 }
4876 return ;
4877}
4878
4879
4880sub tests_appendlimit
4881{
4882 note( 'Entering tests_appendlimit()' ) ;
4883
4884 is( undef, appendlimit( ),
4885 'appendlimit: no args => undef'
4886 ) ;
4887
4888 my $mysync = { } ;
4889
4890 is( undef, appendlimit( $mysync ),
4891 'appendlimit: no imap2 => undef'
4892 ) ;
4893
4894 my $myimap ;
4895 $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ;
4896
4897 $mysync->{ imap2 } = $myimap ;
4898
4899 is( 123456, appendlimit( $mysync ),
4900 'appendlimit: imap2 with APPENDLIMIT=123456 => 123456'
4901 ) ;
4902
4903 note( 'Leaving tests_appendlimit()' ) ;
4904 return ;
4905}
4906
4907sub appendlimit
4908{
4909 my $mysync = shift || return ;
4910 my $myimap = $mysync->{ imap2 } ;
4911
4912 my $appendlimit = appendlimit_from_capability( $myimap ) ;
4913 if ( defined $appendlimit )
4914 {
4915 myprint( "Host2: found APPENDLIMIT=$appendlimit in CAPABILITY (use --appendlimit xxxx to override this automatic setting)\n" ) ;
4916 return $appendlimit ;
4917 }
4918 return ;
4919
4920}
4921
4922
4923sub tests_maxsize_setting
4924{
4925 note( 'Entering tests_maxsize_setting()' ) ;
4926
4927 is( undef, maxsize_setting( ),
4928 'maxsize_setting: no args => undef'
4929 ) ;
4930
4931 my $mysync ;
4932
4933 is( undef, maxsize_setting( $mysync ),
4934 'maxsize_setting: undef arg => undef'
4935 ) ;
4936
4937 $mysync = { } ;
4938 $mysync->{ maxsize } = $NUMBER_123456 ;
4939
4940 # --maxsize alone
4941 is( $NUMBER_123456, maxsize_setting( $mysync ),
4942 'maxsize_setting: --maxsize 123456 alone => 123456'
4943 ) ;
4944
4945
4946 $mysync = { } ;
4947 my $myimap ;
4948
4949 $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=654321' ) ;
4950 $mysync->{ imap2 } = $myimap ;
4951
4952 # APPENDLIMIT alone
4953 is( $NUMBER_654321, maxsize_setting( $mysync ),
4954 'maxsize_setting: APPENDLIMIT 654321 alone => 654321'
4955 ) ;
4956
4957 is( $NUMBER_654321, $mysync->{ maxsize },
4958 'maxsize_setting: APPENDLIMIT 654321 alone => maxsize 654321'
4959 ) ;
4960
4961 # APPENDLIMIT with --appendlimit => --appendlimit wins
4962 $mysync->{ appendlimit } = $NUMBER_123456 ;
4963
4964 is( $NUMBER_123456, maxsize_setting( $mysync ),
4965 'maxsize_setting: APPENDLIMIT 654321 + --appendlimit 123456 => 123456'
4966 ) ;
4967
4968 is( $NUMBER_123456, $mysync->{ maxsize },
4969 'maxsize_setting: APPENDLIMIT 654321 + --appendlimit 123456 => maxsize 123456'
4970 ) ;
4971
4972 # Fresh
4973 $mysync = { } ;
4974 $mysync->{ imap2 } = $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=654321' ) ;
4975
4976 # Case: "APPENDLIMIT >= --maxsize" => maxsize.
4977 $mysync->{ maxsize } = $NUMBER_123456 ;
4978
4979 is( $NUMBER_123456, maxsize_setting( $mysync ),
4980 'maxsize_setting: APPENDLIMIT 654321 --maxsize 123456 => 123456'
4981 ) ;
4982
4983 # Case: "APPENDLIMIT < --maxsize" => APPENDLIMIT.
4984
4985
4986 # Fresh
4987 $mysync = { } ;
4988 $mysync->{ imap2 } = $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ;
4989 $mysync->{ maxsize } = $NUMBER_654321 ;
4990
4991 is( $NUMBER_123456, maxsize_setting( $mysync ),
4992 'maxsize_setting: APPENDLIMIT 123456 --maxsize 654321 => 123456 '
4993 ) ;
4994
4995 # Now --truncmess stuff
4996
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004997 note( 'Leaving tests_maxsize_setting()' ) ;
4998
4999 return ;
5000}
5001
5002# Three variables to take account of
5003# appendlimit (given by --appendlimit or CAPABILITY...)
5004# maxsize
5005# truncmess
5006
5007sub maxsize_setting
5008{
5009 my $mysync = shift || return ;
5010
5011 if ( defined $mysync->{ appendlimit } )
5012 {
5013 myprint( "Host2: Getting appendlimit from --appendlimit $mysync->{ appendlimit }\n" ) ;
5014 }
5015 else
5016 {
5017 $mysync->{ appendlimit } = appendlimit( $mysync ) ;
5018 }
5019
5020
5021 if ( all_defined( $mysync->{ appendlimit }, $mysync->{ maxsize } ) )
5022 {
5023 my $min_maxsize_appendlimit = min( $mysync->{ maxsize }, $mysync->{ appendlimit } ) ;
5024 myprint( "Host2: Setting maxsize to $min_maxsize_appendlimit (min of --maxsize $mysync->{ maxsize } and appendlimit $mysync->{ appendlimit }\n" ) ;
5025 $mysync->{ maxsize } = $min_maxsize_appendlimit ;
5026 return $mysync->{ maxsize } ;
5027 }
5028 elsif ( defined $mysync->{ appendlimit } )
5029 {
5030 myprint( "Host2: Setting maxsize to appendlimit $mysync->{ appendlimit }\n" ) ;
5031 $mysync->{ maxsize } = $mysync->{ appendlimit } ;
5032 return $mysync->{ maxsize } ;
5033 }elsif ( defined $mysync->{ maxsize } )
5034 {
5035 return $mysync->{ maxsize } ;
5036 }else
5037 {
5038 return ;
5039 }
5040}
5041
5042
5043
5044
5045sub all_defined
5046{
5047 if ( not @ARG ) {
5048 return 0 ;
5049 }
5050 foreach my $elem ( @ARG ) {
5051 if ( not defined $elem ) {
5052 return 0 ;
5053 }
5054 }
5055 return 1 ;
5056}
5057
5058sub tests_all_defined
5059{
5060 note( 'Entering tests_all_defined()' ) ;
5061
5062 is( 0, all_defined( ), 'all_defined: no param => 0' ) ;
5063 is( 0, all_defined( () ), 'all_defined: void list => 0' ) ;
5064 is( 0, all_defined( undef ), 'all_defined: undef => 0' ) ;
5065 is( 0, all_defined( undef, undef ), 'all_defined: undef => 0' ) ;
5066 is( 0, all_defined( 1, undef ), 'all_defined: 1 undef => 0' ) ;
5067 is( 0, all_defined( undef, 1 ), 'all_defined: undef 1 => 0' ) ;
5068 is( 1, all_defined( 1, 1 ), 'all_defined: 1 1 => 1' ) ;
5069 is( 1, all_defined( (1, 1) ), 'all_defined: (1 1) => 1' ) ;
5070
5071 note( 'Leaving tests_all_defined()' ) ;
5072 return ;
5073}
5074
5075
5076sub tests_hashsynclocal
5077{
5078 note( 'Entering tests_hashsynclocal()' ) ;
5079
5080 my $mysync = {
5081 host1 => q{},
5082 user1 => q{},
5083 password1 => q{},
5084 host2 => q{},
5085 user2 => q{},
5086 password2 => q{},
5087 } ;
5088
5089 is( undef, hashsynclocal( $mysync ), 'hashsynclocal: no hashfile name' ) ;
5090
5091 $mysync->{ hashfile } = q{} ;
5092 is( undef, hashsynclocal( $mysync ), 'hashsynclocal: empty hashfile name' ) ;
5093
5094 $mysync->{ hashfile } = './noexist/rrr' ;
5095 is( undef, hashsynclocal( $mysync ), 'hashsynclocal: no exists hashfile dir' ) ;
5096
5097 SKIP: {
5098 if ( 'MSWin32' eq $OSNAME or '0' eq $EFFECTIVE_USER_ID ) { skip( 'Tests only for non-root Unix', 1 ) ; }
5099 $mysync->{ hashfile } = '/rrr' ;
5100 is( undef, hashsynclocal( $mysync ), 'hashsynclocal: permission denied' ) ;
5101 }
5102 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'hashsynclocal: mkpath W/tmp/tests/' ) ;
5103 $mysync->{ hashfile } = 'W/tmp/tests/imapsync_hash' ;
5104
5105 ok( ! -e 'W/tmp/tests/imapsync_hash' || unlink 'W/tmp/tests/imapsync_hash', 'hashsynclocal: unlink W/tmp/tests/imapsync_hash' ) ;
5106 ok( ! -e 'W/tmp/tests/imapsync_hash', 'hashsynclocal: verify there is no W/tmp/tests/imapsync_hash' ) ;
5107 is( 'ecdeb4ede672794d173da4e08c52b8ee19b7d252', hashsynclocal( $mysync, 'mukksyhpmbixkxkpjlqivmlqsulpictj' ), 'hashsynclocal: creating/reading W/tmp/tests/imapsync_hash' ) ;
5108 # A second time now
5109 is( 'ecdeb4ede672794d173da4e08c52b8ee19b7d252', hashsynclocal( $mysync ), 'hashsynclocal: reading W/tmp/tests/imapsync_hash second time => same' ) ;
5110
5111 note( 'Leaving tests_hashsynclocal()' ) ;
5112 return ;
5113}
5114
5115sub hashsynclocal
5116{
5117 my $mysync = shift ;
5118 my $hashkey = shift ; # Optional, only there for tests
5119 my $hashfile = $mysync->{ hashfile } ;
5120 $hashfile = createhashfileifneeded( $hashfile, $hashkey ) ;
5121 if ( ! $hashfile ) {
5122 return ;
5123 }
5124 $hashkey = firstline( $hashfile ) ;
5125 if ( ! $hashkey ) {
5126 myprint( "No hashkey!\n" ) ;
5127 return ;
5128 }
5129 my $hashsynclocal = hashsync( $mysync, $hashkey ) ;
5130 return( $hashsynclocal ) ;
5131
5132}
5133
5134sub tests_hashsync
5135{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02005136 note( 'Entering tests_hashsync()' ) ;
5137
5138 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hashsync( ), 'hashsync: no args' ) ;
5139
5140 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hashsync( {}, q{} ), 'hashsync: empty args' ) ;
5141 my $mysync ;
5142 $mysync->{ host1 } = 'zzz' ;
5143 is( 'e86a28a3611c1e7bbaf8057cd00ae122781a11fe', hashsync( $mysync, q{} ), 'hashsync: host1 zzz => ' ) ;
5144 is( '6a7b451ac99eab1531ad8e6cd544b32420c552ac', hashsync( $mysync, q{A} ), 'hashsync: host1 zzz => ' ) ;
5145 $mysync->{ host2 } = 'zzz' ;
5146 is( '15959573e4a86763253a7aedb1a2b0c60d133dc2', hashsync( $mysync, q{} ), 'hashsync: + host2 zzz => ' ) ;
5147 is( 'b8d4ab541b209c75928528020ca28ee43488bd8f', hashsync( $mysync, 'A' ), 'hashsync: + hashkey A => ' ) ;
5148
5149 $mysync = undef ;
5150 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hashsync( $mysync, q{} ), 'hashsync: undef $mysync' ) ;
5151 $mysync->{ password1 } = 'abcd' ;
5152 is( 'afa29ab8534495251ac8346a985717c54bc49c26', hashsync( $mysync, q{} ), 'hashsync: password1: abcd' ) ;
5153
5154 # A user reported a massive failure on /X (Thomas V. 21/04/2020 Ã 21:41 Subject: Error)
5155 # "Wide character in subroutine entry at /usr/local/lib/perl5/site_perl/Digest/HMAC.pm"
5156 # I can reproduce it now
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005157
5158
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02005159 # The eval is there to avoid a complete crash
5160 # this one is fatal so it is commented
5161 # is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', 1 / 0 , 'hashsync: 1 / 0 fatal' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005162
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02005163 my $eval ;
5164 # this one is not fatal
5165 is( undef, $eval = eval { 1 / 0 } , 'hashsync: 1/0 not fatal' ) ;
5166 # this one neither
5167 $mysync->{ password1 } = 'Ö' ;
5168 is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', $eval = eval { hashsync( $mysync, q{} ) } , 'hashsync: password1: Ö with eval' ) ;
5169
5170 $mysync->{ password1 } = 'Ö' ;
5171 is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hashsync( $mysync, q{} ), 'hashsync: password1: Ö without eval' ) ;
5172
5173 $mysync->{ password1 } = qq{\x{00D6}} ;
5174 is( 'bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a', $eval = eval { hashsync( $mysync, q{} ) }, 'hashsync: password1: \x{00D6}' ) ; #
5175
5176 print qq{1 00D6:Ö\n} ;
5177 print encode_utf8( qq{2 00D6:Ö\n} ) ;
5178 print qq{3 00D6:\x{00D6}\n} ;
5179 print encode_utf8( qq{4 00D6:\x{00D6}\n} ) ;
5180
5181
5182 print qq{5 6536:收\n} ;
5183 print encode_utf8( qq{6 6536:收\n} ) ;
5184 # the next one prints "Wide character in print at ./imapsync line xxxx"
5185 print qq{7 6536:\x{6536}\n} ;
5186 print encode_utf8( qq{8 6536:\x{6536}\n} ) ;
5187
5188 $mysync->{ password1 } = qq{收} ;
5189 is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hashsync( $mysync, q{} ), 'hashsync: password1: 收' ) ;
5190
5191 $mysync->{ password1 } = qq{\x{6536}} ;
5192 is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', $eval = eval{ hashsync( $mysync, q{} ) }, 'hashsync: password1: \x{6536} with eval' ) ;
5193
5194 # No side effect.
5195 $mysync->{ password1 } = 'abcd' ;
5196 is( 'afa29ab8534495251ac8346a985717c54bc49c26', hashsync( $mysync, q{} ), 'hashsync: password1: abcd again' ) ;
5197
5198 note( 'Leaving tests_hashsync()' ) ;
5199 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005200}
5201
5202sub hashsync
5203{
5204 my $mysync = shift ;
5205 my $hashkey = shift ;
5206
5207 my $mystring = join( q{},
5208 $mysync->{ host1 } || q{},
5209 $mysync->{ user1 } || q{},
5210 $mysync->{ password1 } || q{},
5211 $mysync->{ host2 } || q{},
5212 $mysync->{ user2 } || q{},
5213 $mysync->{ password2 } || q{},
5214 ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02005215 #my $hashsync = hmac_sha1_hex( $mystring, $hashkey ) ;
5216 my $hashsync = hmac_sha1_hex_robust( $mystring, $hashkey ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005217 #myprint( "$hashsync\n" ) ;
5218 return( $hashsync ) ;
5219}
5220
5221
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02005222sub tests_hmac_sha1_hex
5223{
5224 note( 'Entering tests_hmac_sha1_hex()' ) ;
5225
5226 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex( ), 'hmac_sha1_hex: no args => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
5227 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex( '' ), 'hmac_sha1_hex: empty string => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
5228 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex( '', '' ), 'hmac_sha1_hex: empty strings => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
5229 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex( '', '', 'caca' ), 'hmac_sha1_hex: empty strings + caca => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
5230
5231 # Good
5232 is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hmac_sha1_hex( 'Ö' ), 'hmac_sha1_hex: Ö => f1a3f3dac3f137fd658027c11678b895f773ce55' ) ;
5233 is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hmac_sha1_hex( encode_utf8(qq{\x{00D6}}) ), 'hmac_sha1_hex: encode_utf8 \x{00D6} => f1a3f3dac3f137fd658027c11678b895f773ce55' ) ;
5234 # Bad
5235 is( 'fe8dc3b9ba3e8850bb4a7b070b2279e911003af2', hmac_sha1_hex( encode_utf8( 'Ö' ) ), 'hmac_sha1_hex: encode_utf8 Ö => fe8dc3b9ba3e8850bb4a7b070b2279e911003af2' ) ;
5236 is( 'bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a', hmac_sha1_hex( qq{\x{00D6}} ), 'hmac_sha1_hex: qq{\x{00D6}} => bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a' ) ;
5237
5238 # Good
5239 is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex( 'A' ), 'hmac_sha1_hex: A => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
5240 is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex( encode_utf8(qq{\x{0041}}) ), 'hmac_sha1_hex: encode_utf8 \x{0041} => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
5241 is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex( encode_utf8( 'A' ) ), 'hmac_sha1_hex: encode_utf8 A => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
5242 is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex( qq{\x{0041}} ), 'hmac_sha1_hex: \x{0041} => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
5243
5244 # Good
5245 is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex( 'A', 'B' ), 'hmac_sha1_hex: A B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
5246 is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex( encode_utf8(qq{\x{0041}}), 'B' ), 'hmac_sha1_hex: encode_utf8 \x{0041} B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
5247 is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex( encode_utf8( 'A' ), 'B' ), 'hmac_sha1_hex: encode_utf8 A B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
5248 is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex( qq{\x{0041}}, 'B' ), 'hmac_sha1_hex: \x{0041} B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
5249
5250 # http://unicode.scarfboy.com/?s=U%2B6536
5251 # Good
5252 is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex( '收' ), 'hmac_sha1_hex: 收 => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ;
5253 is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex( encode_utf8(qq{\x{6536}}) ), 'hmac_sha1_hex: encode_utf8 \x{6536} => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ;
5254 # Bad
5255 is( 'e82217119628ad03e659cc89671d05ea4cee7238', hmac_sha1_hex( encode_utf8( '收' ) ), 'hmac_sha1_hex: encode_utf8 收 => e82217119628ad03e659cc89671d05ea4cee7238' ) ;
5256 # Very very bad, perl dies...
5257 #is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex( qq{\x{6536}} ), 'hmac_sha1_hex: \x{6536} => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ;
5258 # Ok but well, bad indeed
5259 is( undef, my $eval = eval{ hmac_sha1_hex( qq{\x{6536}} ) }, 'hmac_sha1_hex: \x{6536} => undef' ) ;
5260
5261
5262 note( 'Leaving tests_hmac_sha1_hex()' ) ;
5263 return ;
5264}
5265
5266sub tests_hmac_sha1_hex_robust
5267{
5268 note( 'Entering tests_hmac_sha1_hex_robust()' ) ;
5269
5270 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex_robust( ), 'hmac_sha1_hex_robust: no args => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
5271 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex_robust( '' ), 'hmac_sha1_hex_robust: empty string => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
5272 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex_robust( '', '' ), 'hmac_sha1_hex_robust: empty strings => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
5273 is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex_robust( '', '', 'caca' ), 'hmac_sha1_hex_robust: empty strings + caca => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
5274
5275 # Good
5276 is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hmac_sha1_hex_robust( 'Ö' ), 'hmac_sha1_hex_robust: Ö => f1a3f3dac3f137fd658027c11678b895f773ce55' ) ;
5277 is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hmac_sha1_hex_robust( encode_utf8(qq{\x{00D6}}) ), 'hmac_sha1_hex_robust: encode_utf8 \x{00D6} => f1a3f3dac3f137fd658027c11678b895f773ce55' ) ;
5278 # Bad
5279 is( 'fe8dc3b9ba3e8850bb4a7b070b2279e911003af2', hmac_sha1_hex_robust( encode_utf8( 'Ö' ) ), 'hmac_sha1_hex_robust: encode_utf8 Ö => fe8dc3b9ba3e8850bb4a7b070b2279e911003af2' ) ;
5280 is( 'bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a', hmac_sha1_hex_robust( qq{\x{00D6}} ), 'hmac_sha1_hex_robust: qq{\x{00D6}} => bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a' ) ;
5281
5282 # Good
5283 is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex_robust( 'A' ), 'hmac_sha1_hex_robust: A => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
5284 is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex_robust( encode_utf8(qq{\x{0041}}) ), 'hmac_sha1_hex_robust: encode_utf8 \x{0041} => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
5285 is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex_robust( encode_utf8( 'A' ) ), 'hmac_sha1_hex_robust: encode_utf8 A => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
5286 is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex_robust( qq{\x{0041}} ), 'hmac_sha1_hex_robust: \x{0041} => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
5287
5288 # Good
5289 is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex_robust( 'A', 'B' ), 'hmac_sha1_hex_robust: A B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
5290 is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex_robust( encode_utf8(qq{\x{0041}}), 'B' ), 'hmac_sha1_hex_robust: encode_utf8 \x{0041} B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
5291 is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex_robust( encode_utf8( 'A' ), 'B' ), 'hmac_sha1_hex_robust: encode_utf8 A B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
5292 is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex_robust( qq{\x{0041}}, 'B' ), 'hmac_sha1_hex_robust: \x{0041} B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
5293
5294 # http://unicode.scarfboy.com/?s=U%2B6536
5295 # Good
5296 is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex_robust( '收' ), 'hmac_sha1_hex_robust: 收 => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ;
5297 is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex_robust( encode_utf8(qq{\x{6536}}) ), 'hmac_sha1_hex_robust: encode_utf8 \x{6536} => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ;
5298 # Bad
5299 is( 'e82217119628ad03e659cc89671d05ea4cee7238', hmac_sha1_hex_robust( encode_utf8( '收' ) ), 'hmac_sha1_hex_robust: encode_utf8 收 => e82217119628ad03e659cc89671d05ea4cee7238' ) ;
5300 # Good
5301 is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex_robust( qq{\x{6536}} ), 'hmac_sha1_hex_robust: \x{6536} => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ;
5302 # Good again
5303 is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', my $eval = eval{ hmac_sha1_hex_robust( qq{\x{6536}} ) }, 'hmac_sha1_hex_robust: \x{6536} => undef' ) ;
5304
5305 note( 'Leaving tests_hmac_sha1_hex_robust()' ) ;
5306 return ;
5307}
5308
5309
5310sub hmac_sha1_hex_robust
5311{
5312 my $string = shift ;
5313 my $val ;
5314 if ( defined( $val = eval{ hmac_sha1_hex( $string, @ARG ) } ) )
5315 {
5316 return $val ;
5317 }
5318 elsif( defined( $val = eval{ hmac_sha1_hex( encode_utf8( $string ), @ARG ) } ) )
5319 {
5320 return $val ;
5321 }
5322 else
5323 {
5324 return ;
5325 }
5326}
5327
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005328sub tests_createhashfileifneeded
5329{
5330 note( 'Entering tests_createhashfileifneeded()' ) ;
5331
5332 is( undef, createhashfileifneeded( ), 'createhashfileifneeded: no parameters => undef' ) ;
5333
5334 note( 'Leaving tests_createhashfileifneeded()' ) ;
5335 return ;
5336}
5337
5338sub createhashfileifneeded
5339{
5340 my $hashfile = shift ;
5341 my $hashkey = shift || rand32( ) ;
5342
5343 # no name
5344 if ( ! $hashfile ) {
5345 return ;
5346 }
5347 # already there
5348 if ( -e -r $hashfile ) {
5349 return $hashfile ;
5350 }
5351 # not creatable
5352 if ( ! -w dirname( $hashfile ) ) {
5353 return ;
5354 }
5355 # creatable
5356 open my $FILE_HANDLE, '>', $hashfile
5357 or do {
5358 myprint( "Could not open $hashfile for writing. Check permissions or disk space." ) ;
5359 return ;
5360 } ;
5361 myprint( "Writing random hashkey in $hashfile, once for all times\n" ) ;
5362 print $FILE_HANDLE $hashkey ;
5363 close $FILE_HANDLE ;
5364 # Should be there now
5365 if ( -e -r $hashfile ) {
5366 return $hashfile ;
5367 }
5368 # unknown failure
5369 return ;
5370}
5371
5372sub tests_rand32
5373{
5374 note( 'Entering tests_rand32()' ) ;
5375
5376 my $string = rand32( ) ;
5377 myprint( "$string\n" ) ;
5378 is( 32, length( $string ), 'rand32: 32 characters long' ) ;
5379 is( 32, length( rand32( ) ), 'rand32: 32 characters long, another one' ) ;
5380
5381 note( 'Leaving tests_rand32()' ) ;
5382 return ;
5383}
5384
5385sub rand32
5386{
5387 my @chars = ( "a".."z" ) ;
5388 my $string;
5389 $string .= $chars[rand @chars] for 1..32 ;
5390 return $string ;
5391}
5392
5393sub imap_id_stuff
5394{
5395 my $mysync = shift ;
5396
5397 if ( not $mysync->{id} ) { return ; } ;
5398
5399 $mysync->{h1_imap_id} = imap_id( $mysync, $mysync->{imap1}, 'Host1' ) ;
5400 #myprint( 'Host1: ' . $mysync->{h1_imap_id} ) ;
5401 $mysync->{h2_imap_id} = imap_id( $mysync, $mysync->{imap2}, 'Host2' ) ;
5402 #myprint( 'Host2: ' . $mysync->{h2_imap_id} ) ;
5403
5404 return ;
5405}
5406
5407sub imap_id
5408{
5409 my ( $mysync, $imap, $Side ) = @_ ;
5410
5411 if ( not $mysync->{id} ) { return q{} ; } ;
5412
5413 $Side ||= q{} ;
5414 my $imap_id_response = q{} ;
5415
5416 if ( not $imap->has_capability( 'ID' ) ) {
5417 $imap_id_response = 'No ID capability' ;
5418 myprint( "$Side: No ID capability\n" ) ;
5419 }else{
5420 my $id_inp = imapsync_id( $mysync, { side => lc $Side } ) ;
5421 myprint( "\n$Side: found ID capability. Sending/receiving ID, presented in raw IMAP for now.\n"
5422 . "In order to avoid sending/receiving ID, use option --noid\n" ) ;
5423 my $debug_before = $imap->Debug( ) ;
5424 $imap->Debug( 1 ) ;
5425 my $id_out = $imap->tag_and_run( 'ID ' . $id_inp ) ;
5426 #my $id_out = $imap->tag_and_run( 'ID NIL' ) ;
5427 myprint( "\n" ) ;
5428 $imap->Debug( $debug_before ) ;
5429 #$imap_id_response = Data::Dumper->Dump( [ $id_out ], [ 'IMAP_ID' ] ) ;
5430 }
5431 return( $imap_id_response ) ;
5432}
5433
5434sub imapsync_id
5435{
5436 my $mysync = shift ;
5437 my $overhashref = shift ;
5438 # See http://tools.ietf.org/html/rfc2971.html
5439
5440 my $imapsync_id = { } ;
5441
5442 my $imapsync_id_lamiral = {
5443 name => 'imapsync',
5444 version => imapsync_version( $mysync ),
5445 os => $OSNAME,
5446 vendor => 'Gilles LAMIRAL',
5447 'support-url' => 'https://imapsync.lamiral.info/',
5448 # Example of date-time: 19-Sep-2015 08:56:07
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01005449 date => date_from_rcs( q{$Date: 2022/01/12 21:28:37 $ } ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005450 } ;
5451
5452 my $imapsync_id_github = {
5453 name => 'imapsync',
5454 version => imapsync_version( $mysync ),
5455 os => $OSNAME,
5456 vendor => 'github',
5457 'support-url' => 'https://github.com/imapsync/imapsync',
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01005458 date => date_from_rcs( q{$Date: 2022/01/12 21:28:37 $ } ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005459 } ;
5460
5461 $imapsync_id = $imapsync_id_lamiral ;
5462 #$imapsync_id = $imapsync_id_github ;
5463 my %mix = ( %{ $imapsync_id }, %{ $overhashref } ) ;
5464 my $imapsync_id_str = format_for_imap_arg( \%mix ) ;
5465 #myprint( "$imapsync_id_str\n" ) ;
5466 return( $imapsync_id_str ) ;
5467}
5468
5469sub tests_imapsync_id
5470{
5471 note( 'Entering tests_imapsync_id()' ) ;
5472
5473 my $mysync ;
5474 ok( '("name" "imapsync" "version" "111" "os" "beurk" "vendor" "Gilles LAMIRAL" "support-url" "https://imapsync.lamiral.info/" "date" "22-12-1968" "side" "host1")'
5475 eq imapsync_id( $mysync,
5476 {
5477 version => 111,
5478 os => 'beurk',
5479 date => '22-12-1968',
5480 side => 'host1'
5481 }
5482 ),
5483 'tests_imapsync_id override'
5484 ) ;
5485
5486 note( 'Leaving tests_imapsync_id()' ) ;
5487 return ;
5488}
5489
5490sub format_for_imap_arg
5491{
5492 my $ref = shift ;
5493
5494 my $string = q{} ;
5495 my %terms = %{ $ref } ;
5496 my @terms = ( ) ;
5497 if ( not ( %terms ) ) { return( 'NIL' ) } ;
5498 # sort like in RFC then add extra key/values
5499 foreach my $key ( qw( name version os os-version vendor support-url address date command arguments environment) ) {
5500 if ( $terms{ $key } ) {
5501 push @terms, $key, $terms{ $key } ;
5502 delete $terms{ $key } ;
5503 }
5504 }
5505 push @terms, %terms ;
5506 $string = '(' . ( join q{ }, map { '"' . $_ . '"' } @terms ) . ')' ;
5507 return( $string ) ;
5508}
5509
5510
5511
5512sub tests_format_for_imap_arg
5513{
5514 note( 'Entering tests_format_for_imap_arg()' ) ;
5515
5516 ok( 'NIL' eq format_for_imap_arg( { } ), 'format_for_imap_arg empty hash ref' ) ;
5517 ok( '("name" "toto")' eq format_for_imap_arg( { name => 'toto' } ), 'format_for_imap_arg { name => toto }' ) ;
5518 ok( '("name" "toto" "key" "val")' eq format_for_imap_arg( { name => 'toto', key => 'val' } ), 'format_for_imap_arg 2 x key val' ) ;
5519
5520 note( 'Leaving tests_format_for_imap_arg()' ) ;
5521 return ;
5522}
5523
5524sub quota
5525{
5526 my ( $mysync, $imap, $side ) = @_ ;
5527
5528 my %side = (
5529 h1 => 'Host1',
5530 h2 => 'Host2',
5531 ) ;
5532 my $Side = $side{ $side } ;
5533 my $debug_before = $imap->Debug( ) ;
5534 $imap->Debug( 1 ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01005535 if ( not $imap->has_capability( 'QUOTA' ) )
5536 {
5537 myprint( "$Side: No QUOTA capability found, skipping it.\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005538 $imap->Debug( $debug_before ) ;
5539 return ;
5540 } ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01005541 myprint( "\n$Side: QUOTA capability found, presented in raw IMAP on next lines\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005542 my $getquotaroot = $imap->getquotaroot( 'INBOX' ) ;
5543 # Gmail INBOX quotaroot is "" but with it Mail::IMAPClient does a literal GETQUOTA {2} \n ""
5544 #$imap->quota( 'ROOT' ) ;
5545 #$imap->quota( '""' ) ;
5546 myprint( "\n" ) ;
5547 $imap->Debug( $debug_before ) ;
5548 my $quota_limit_bytes = quota_extract_storage_limit_in_bytes( $mysync, $getquotaroot ) ;
5549 my $quota_current_bytes = quota_extract_storage_current_in_bytes( $mysync, $getquotaroot ) ;
5550 $mysync->{$side}->{quota_limit_bytes} = $quota_limit_bytes ;
5551 $mysync->{$side}->{quota_current_bytes} = $quota_current_bytes ;
5552 my $quota_percent ;
5553 if ( $quota_limit_bytes > 0 ) {
5554 $quota_percent = mysprintf( '%.2f', $NUMBER_100 * $quota_current_bytes / $quota_limit_bytes ) ;
5555 }else{
5556 $quota_percent = 0 ;
5557 }
5558 myprint( "$Side: Quota current storage is $quota_current_bytes bytes. Limit is $quota_limit_bytes bytes. So $quota_percent % full\n" ) ;
5559 if ( $QUOTA_PERCENT_LIMIT < $quota_percent ) {
5560 my $error = "$Side: $quota_percent % full: it is time to find a bigger place! ( $quota_current_bytes bytes / $quota_limit_bytes bytes )\n" ;
5561 errors_incr( $mysync, $error ) ;
5562 }
5563 return ;
5564}
5565
5566sub tests_quota_extract_storage_limit_in_bytes
5567{
5568 note( 'Entering tests_quota_extract_storage_limit_in_bytes()' ) ;
5569
5570 my $mysync = {} ;
5571 my $imap_output = [
5572 '* QUOTAROOT "INBOX" "Storage quota" "Messages quota"',
5573 '* QUOTA "Storage quota" (STORAGE 1 104857600)',
5574 '* QUOTA "Messages quota" (MESSAGE 2 100000)',
5575 '5 OK Getquotaroot completed.'
5576 ] ;
5577 ok( $NUMBER_104_857_600 * $KIBI == quota_extract_storage_limit_in_bytes( $mysync, $imap_output ), 'quota_extract_storage_limit_in_bytes ') ;
5578
5579 note( 'Leaving tests_quota_extract_storage_limit_in_bytes()' ) ;
5580 return ;
5581}
5582
5583sub quota_extract_storage_limit_in_bytes
5584{
5585 my $mysync = shift ;
5586 my $imap_output = shift ;
5587
5588 my $limit_kb ;
5589 $limit_kb = ( map { /.*\(\s*STORAGE\s+\d+\s+(\d+)\s*\)/x ? $1 : () } @{ $imap_output } )[0] ;
5590 $limit_kb ||= 0 ;
5591 $mysync->{ debug } and myprint( "storage_limit_kb = $limit_kb\n" ) ;
5592 return( $KIBI * $limit_kb ) ;
5593}
5594
5595
5596sub tests_quota_extract_storage_current_in_bytes
5597{
5598 note( 'Entering tests_quota_extract_storage_current_in_bytes()' ) ;
5599
5600 my $mysync = {} ;
5601 my $imap_output = [
5602 '* QUOTAROOT "INBOX" "Storage quota" "Messages quota"',
5603 '* QUOTA "Storage quota" (STORAGE 1 104857600)',
5604 '* QUOTA "Messages quota" (MESSAGE 2 100000)',
5605 '5 OK Getquotaroot completed.'
5606 ] ;
5607 ok( 1*$KIBI == quota_extract_storage_current_in_bytes( $mysync, $imap_output ), 'quota_extract_storage_current_in_bytes: 1 => 1024 ') ;
5608
5609 note( 'Leaving tests_quota_extract_storage_current_in_bytes()' ) ;
5610 return ;
5611}
5612
5613sub quota_extract_storage_current_in_bytes
5614{
5615 my $mysync = shift ;
5616 my $imap_output = shift ;
5617
5618 my $current_kb ;
5619 $current_kb = ( map { /.*\(\s*STORAGE\s+(\d+)\s+\d+\s*\)/x ? $1 : () } @{ $imap_output } )[0] ;
5620 $current_kb ||= 0 ;
5621 $mysync->{ debug } and myprint( "storage_current_kb = $current_kb\n" ) ;
5622 return( $KIBI * $current_kb ) ;
5623
5624}
5625
5626
5627sub automap
5628{
5629 my ( $mysync ) = @_ ;
5630
5631 if ( $mysync->{automap} ) {
5632 myprint( "Turned on automapping folders ( use --noautomap to turn off automapping )\n" ) ;
5633 }else{
5634 myprint( "Turned off automapping folders ( use --automap to turn on automapping )\n" ) ;
5635 return ;
5636 }
5637
5638 $mysync->{h1_special} = special_from_folders_hash( $mysync, $mysync->{imap1}, 'Host1' ) ;
5639 $mysync->{h2_special} = special_from_folders_hash( $mysync, $mysync->{imap2}, 'Host2' ) ;
5640
5641 build_possible_special( $mysync ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01005642 build_guess_special( $mysync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005643 build_automap( $mysync ) ;
5644
5645 return ;
5646}
5647
5648
5649
5650
5651sub build_guess_special
5652{
5653 my ( $mysync ) = shift ;
5654
5655 foreach my $h1_fold ( sort keys %{ $mysync->{h1_folders_all} } ) {
5656 my $special = guess_special( $h1_fold, $mysync->{possible_special}, $mysync->{h1_prefix} ) ;
5657 if ( $special ) {
5658 $mysync->{h1_special_guessed}{$h1_fold} = $special ;
5659 my $already_guessed = $mysync->{h1_special_guessed}{$special} ;
5660 if ( $already_guessed ) {
5661 myprint( "Host1: $h1_fold not $special because set to $already_guessed\n" ) ;
5662 }else{
5663 $mysync->{h1_special_guessed}{$special} = $h1_fold ;
5664 }
5665 }
5666 }
5667 foreach my $h2_fold ( sort keys %{ $mysync->{h2_folders_all} } ) {
5668 my $special = guess_special( $h2_fold, $mysync->{possible_special}, $mysync->{h2_prefix} ) ;
5669 if ( $special ) {
5670 $mysync->{h2_special_guessed}{$h2_fold} = $special ;
5671 my $already_guessed = $mysync->{h2_special_guessed}{$special} ;
5672 if ( $already_guessed ) {
5673 myprint( "Host2: $h2_fold not $special because set to $already_guessed\n" ) ;
5674 }else{
5675 $mysync->{h2_special_guessed}{$special} = $h2_fold ;
5676 }
5677 }
5678 }
5679 return ;
5680}
5681
5682sub guess_special
5683{
5684 my( $folder, $possible_special_ref, $prefix ) = @_ ;
5685
5686 my $folder_no_prefix = $folder ;
5687 $folder_no_prefix =~ s/\Q${prefix}\E//xms ;
5688 #$debug and myprint( "folder_no_prefix: $folder_no_prefix\n" ) ;
5689
5690 my $guess_special = $possible_special_ref->{ $folder }
5691 || $possible_special_ref->{ $folder_no_prefix }
5692 || q{} ;
5693
5694 return( $guess_special ) ;
5695}
5696
5697sub tests_guess_special
5698{
5699 note( 'Entering tests_guess_special()' ) ;
5700
5701 my $possible_special_ref = build_possible_special( my $mysync ) ;
5702 ok( '\Sent' eq guess_special( 'Sent', $possible_special_ref, q{} ) ,'guess_special: Sent => \Sent' ) ;
5703 ok( q{} eq guess_special( 'Blabla', $possible_special_ref, q{} ) ,'guess_special: Blabla => q{}' ) ;
5704 ok( '\Sent' eq guess_special( 'INBOX.Sent', $possible_special_ref, 'INBOX.' ) ,'guess_special: INBOX.Sent => \Sent' ) ;
5705 ok( '\Sent' eq guess_special( 'IN BOX.Sent', $possible_special_ref, 'IN BOX.' ) ,'guess_special: IN BOX.Sent => \Sent' ) ;
5706
5707 note( 'Leaving tests_guess_special()' ) ;
5708 return ;
5709}
5710
5711sub build_automap
5712{
5713 my $mysync = shift ;
5714 $mysync->{ debug } and myprint( "Entering build_automap\n" ) ;
5715 foreach my $h1_fold ( @{ $mysync->{h1_folders_wanted} } ) {
5716 my $h2_fold ;
5717 my $h1_special = $mysync->{h1_special}{$h1_fold} ;
5718 my $h1_special_guessed = $mysync->{h1_special_guessed}{$h1_fold} ;
5719
5720 # Case 1: special on both sides.
5721 if ( $h1_special
5722 and exists $mysync->{h2_special}{$h1_special} ) {
5723 $h2_fold = $mysync->{h2_special}{$h1_special} ;
5724 $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ;
5725 next ;
5726 }
5727 # Case 2: special on host1, not on host2
5728 if ( $h1_special
5729 and ( not exists $mysync->{h2_special}{$h1_special} )
5730 and ( exists $mysync->{h2_special_guessed}{$h1_special} )
5731 ) {
5732 # special_guessed on host2
5733 $h2_fold = $mysync->{h2_special_guessed}{$h1_special} ;
5734 $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ;
5735 next ;
5736 }
5737 # Case 3: no special on host1, special on host2
5738 if ( ( not $h1_special )
5739 and ( $h1_special_guessed )
5740 and ( exists $mysync->{h2_special}{$h1_special_guessed} )
5741 ) {
5742 $h2_fold = $mysync->{h2_special}{$h1_special_guessed} ;
5743 $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ;
5744 next ;
5745 }
5746 # Case 4: no special on both sides.
5747 if ( ( not $h1_special )
5748 and ( $h1_special_guessed )
5749 and ( not exists $mysync->{h2_special}{$h1_special_guessed} )
5750 and ( exists $mysync->{h2_special_guessed}{$h1_special_guessed} )
5751 ) {
5752 $h2_fold = $mysync->{h2_special_guessed}{$h1_special_guessed} ;
5753 $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ;
5754 next ;
5755 }
5756 }
5757 return( $mysync->{f1f2auto} ) ;
5758}
5759
5760# I will not add what there is at:
5761# http://stackoverflow.com/questions/2185391/localized-gmail-imap-folders/2185548#2185548
5762# because it works well without
5763sub build_possible_special
5764{
5765 my $mysync = shift ;
5766 my $possible_special = { } ;
5767 # All|Archive|Drafts|Flagged|Junk|Sent|Trash
5768
5769 $possible_special->{'\All'} = [ 'All', 'All Messages', '&BBIEQQQ1-' ] ;
5770 $possible_special->{'\Archive'} = [ 'Archive', 'Archives', '&BBAEQARFBDgEMg-' ] ;
5771 $possible_special->{'\Drafts'} = [ 'Drafts', 'DRAFTS', '&BCcENQRABD0EPgQyBDgEOgQ4-', 'Szkice', 'Wersje robocze' ] ;
5772 $possible_special->{'\Flagged'} = [ 'Flagged', 'Starred', '&BB8EPgQ8BDUERwQ1BD0EPQRLBDU-' ] ;
5773 $possible_special->{'\Junk'} = [ 'Junk', 'junk', 'Spam', 'SPAM', '&BCEEPwQwBDw-',
5774 'Potwierdzony spam', 'Wiadomo&AVs-ci-&AVs-mieci',
5775 'Junk E-Mail', 'Junk Email'] ;
5776 $possible_special->{'\Sent'} = [ 'Sent', 'Sent Messages', 'Sent Items',
5777 'Gesendete Elemente', 'Gesendete Objekte',
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01005778 '&AMk-l&AOk-ments envoy&AOk-s', 'E&AwE-le&AwE-ments envoye&AwE-s', 'Envoy&AOk-', 'Objets envoy&AOk-s',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005779 'Elementos enviados',
5780 '&kAFP4W4IMH8wojCkMMYw4A-',
5781 '&BB4EQgQ,BEAEMAQyBDsENQQ9BD0ESwQ1-',
5782 'Elementy wys&AUI-ane'] ;
5783 $possible_special->{'\Trash'} = [ 'Trash', 'TRASH',
5784 '&BCMENAQwBDsENQQ9BD0ESwQ1-', '&BBoEPgRABDcEOAQ9BDA-',
5785 'Kosz',
5786 'Deleted Items', 'Deleted Messages' ] ;
5787
5788
5789 foreach my $special ( qw( \All \Archive \Drafts \Flagged \Junk \Sent \Trash ) ){
5790 foreach my $possible_folder ( @{ $possible_special->{$special} } ) {
5791 $possible_special->{ $possible_folder } = $special ;
5792 } ;
5793 }
5794 $mysync->{possible_special} = $possible_special ;
5795 $mysync->{ debug } and myprint( Data::Dumper->Dump( [ $possible_special ], [ 'possible_special' ] ) ) ;
5796 return( $possible_special ) ;
5797}
5798
5799sub tests_special_from_folders_hash
5800{
5801 note( 'Entering tests_special_from_folders_hash()' ) ;
5802
5803 my $mysync = {} ;
5804 require_ok( "Test::MockObject" ) ;
5805 my $imapT = Test::MockObject->new( ) ;
5806
5807 is( undef, special_from_folders_hash( ), 'special_from_folders_hash: no args' ) ;
5808 is( undef, special_from_folders_hash( $mysync ), 'special_from_folders_hash: undef args' ) ;
5809 is_deeply( {}, special_from_folders_hash( $mysync, $imapT ), 'special_from_folders_hash: $imap void' ) ;
5810
5811 $imapT->mock( 'folders_hash', sub { return( [ { name => 'Sent', attrs => [ '\Sent' ] } ] ) } ) ;
5812
5813 is_deeply( { Sent => '\Sent', '\Sent' => 'Sent' },
5814 special_from_folders_hash( $mysync, $imapT ), 'special_from_folders_hash: $imap \Sent' ) ;
5815
5816 note( 'Leaving tests_special_from_folders_hash()' ) ;
5817 return( ) ;
5818}
5819
5820sub special_from_folders_hash
5821{
5822 my ( $mysync, $imap, $side ) = @_ ;
5823 my %special = ( ) ;
5824
5825 if ( ! defined $imap ) { return ; }
5826 $side = defined $side ? $side : 'Host?' ;
5827
5828 if ( ! $imap->can( 'folders_hash' ) ) {
5829 my $error = "$side: To have automagic rfc6154 folder mapping, upgrade Mail::IMAPClient >= 3.34\n" ;
5830 errors_incr( $mysync, $error ) ;
5831 return( \%special ) ; # empty hash ref
5832 }
5833 my $folders_hash = $imap->folders_hash( ) ;
5834 foreach my $fhash (@{ $folders_hash } ) {
5835 my @special = grep { /\\(?:All|Archive|Drafts|Flagged|Junk|Sent|Trash)/x } @{ $fhash->{attrs} } ;
5836 if ( @special ) {
5837 my $special = $special[0] ; # keep first one. Could be not very good.
5838 if ( exists $special{ $special } ) {
5839 myprintf( "%s: special %-20s = %s already assigned to %s\n",
5840 $side, $fhash->{name}, join( q{ }, @special ), $special{ $special } ) ;
5841 }else{
5842 myprintf( "%s: special %-20s = %s\n",
5843 $side, $fhash->{name}, join( q{ }, @special ) ) ;
5844 $special{ $special } = $fhash->{name} ;
5845 $special{ $fhash->{name} } = $special ; # double entry value => key
5846 }
5847 }
5848 }
5849 myprint( "\n" ) if ( %special ) ;
5850 return( \%special ) ;
5851}
5852
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01005853
5854sub tests_errors_log
5855{
5856 note( 'Entering tests_errors_log()' ) ;
5857 is( undef, errors_log( ), 'errors_log: no args => undef' ) ;
5858 my $mysync = {} ;
5859 is( undef, errors_log( $mysync ), 'errors_log: empty => undef' ) ;
5860 is_deeply( [ 'aieaie' ], [ errors_log( $mysync, 'aieaie' ) ], 'errors_log: aieaie => aieaie' ) ;
5861 # cumulative
5862 is_deeply( [ 'aieaie' ], [ errors_log( $mysync ) ], 'errors_log: nothing more => aieaie' ) ;
5863 is_deeply( [ 'aieaie', 'ouille' ], [ errors_log( $mysync, 'ouille' ) ], 'errors_log: ouille => aieaie ouille' ) ;
5864 is_deeply( [ 'aieaie', 'ouille' ], [ errors_log( $mysync ) ], 'errors_log: nothing more => aieaie ouille' ) ;
5865 note( 'Leaving tests_errors_log()' ) ;
5866 return ;
5867}
5868
5869sub errors_log
5870{
5871 my ( $mysync, @error ) = @ARG ;
5872
5873 if ( ! $mysync->{errors_log} ) {
5874 $mysync->{errors_log} = [] ;
5875 }
5876
5877 if ( @error ) {
5878 push @{ $mysync->{errors_log} }, join( q{}, @error ) ;
5879 }
5880 if ( @{ $mysync->{errors_log} } ) {
5881 return @{ $mysync->{errors_log} } ;
5882 }
5883 else {
5884 return ;
5885 }
5886}
5887
5888
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01005889
5890sub tests_comment_of_error_type
5891{
5892 note( 'Entering tests_comment_of_error_type()' ) ;
5893
5894 is( undef, comment_of_error_type( ), 'comment_of_error_type: no args => undef' ) ;
5895
5896 my $mysync = { } ;
5897 is( undef, comment_of_error_type( $mysync ), 'comment_of_error_type: undef => undef' ) ;
5898
5899 is( "", comment_of_error_type( $mysync, '' ), 'comment_of_error_type: "" => ""' ) ;
5900 is( "", comment_of_error_type( $mysync, 'blabla' ), 'comment_of_error_type: blabla => ""' ) ;
5901
5902 is( "", comment_of_error_type( $mysync, 'ERR_UNCLASSIFIED' ), 'comment_of_error_type: ERR_UNCLASSIFIED => ""' ) ;
5903
5904 like( comment_of_error_type( $mysync, 'ERR_OVERQUOTA' ), qr{100% full}, 'comment_of_error_type: ERR_OVERQUOTA => matches 100% full' ) ;
5905
5906
5907
5908 note( 'Leaving tests_comment_of_error_type()' ) ;
5909 return ;
5910}
5911
5912sub comment_of_error_type
5913{
5914 my $mysync = shift @ARG ;
5915 my $error_type = shift @ARG ;
5916
5917 if ( ! defined $mysync ) { return ; }
5918 if ( ! defined $error_type ) { return ; }
5919
5920 my $comment ;
5921
5922 if ( exists( $COMMENT_OF_ERR_TYPE{ $error_type } ) )
5923 {
5924 $comment = $COMMENT_OF_ERR_TYPE{ $error_type }->( $mysync ) ;
5925 }
5926 else
5927 {
5928 $comment = "" ;
5929 }
5930 return $comment ;
5931}
5932
5933
5934
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02005935sub tests_error_type
5936{
5937 note( 'Entering tests_error_type()' ) ;
5938
5939 is( 'ERR_NOTHING_REPORTED', error_type( ), 'error_type: no args => ERR_NOTHING_REPORTED' ) ;
5940 is( 'ERR_NOTHING_REPORTED', error_type( '' ), 'error_type: empty string => ERR_NOTHING_REPORTED' ) ;
5941
5942 is( 'ERR_UNCLASSIFIED', error_type( 'ERR_UNCLASSIFIED' ), 'error_type: ERR_UNCLASSIFIED => ERR_UNCLASSIFIED' ) ;
5943 is( 'ERR_UNCLASSIFIED', error_type( 'aie' ), 'error_type: aie => ERR_UNCLASSIFIED' ) ;
5944 is( 'ERR_UNCLASSIFIED', error_type( 'ouille' ), 'error_type: ouille => ERR_UNCLASSIFIED' ) ;
5945
5946 is( 'ERR_Host1_FETCH', error_type( 'Message xxx could not be fetched: blabla' ),
5947 'error_type: could not be fetched => ERR_Host1_FETCH'
5948 ) ;
5949
5950 is( 'ERR_APPEND_SIZE',
5951 error_type( 'could not append message xxx: BAD maximum message size exceeded' ),
5952 'error_type: could not append message xxx: BAD maximum message size exceeded => ERR_APPEND_SIZE'
5953 ) ;
5954
5955 is( 'ERR_OVERQUOTA',
5956 error_type( 'Quota limit will be exceeded' ),
5957 'error_type: Quota limit will be exceeded => ERR_OVERQUOTA'
5958 ) ;
5959
5960 is( 'ERR_APPEND', error_type( 'could not append' ), 'error_type: could not append => ERR_APPEND' ) ;
5961
5962 is( 'ERR_CREATE',
5963 error_type( 'Could not create folder' ),
5964 'error_type: Could not create folder => ERR_CREATE'
5965 ) ;
5966
5967 is( 'ERR_SELECT',
5968 error_type( 'Could not select: blabla' ),
5969 'error_type: Could not select: blabla => ERR_SELECT'
5970 ) ;
5971
5972
5973 #
5974 #Maximum bytes transferred reached, 423 >= 100, ending sync
5975 is( 'ERR_TRANSFER_EXCEEDED',
5976 error_type( 'Maximum bytes transferred reached, blabla' ),
5977 'error_type: Maximum bytes transferred reached, blabla => ERR_TRANSFER_EXCEEDED'
5978 ) ;
5979
5980 #
5981 is( 'ERR_CONNECTION_FAILURE_HOST1',
5982 error_type( 'Host1 failure: can not open imap connection on host1 [badhostkaka] with user [tata]: Unable to connect to badhostkaka: Invalid argument' ),
5983 'error_type: can not open imap connection on host1 => ERR_CONNECTION_FAILURE_HOST1'
5984 ) ;
5985
5986 is( 'ERR_CONNECTION_FAILURE_HOST2',
5987 error_type( 'Host2 failure: can not open imap connection on host2 [badhostkiki] with user [titi]: Unable to connect to badhostkiki: Invalid argument' ),
5988 'error_type: can not open imap connection on host2 => ERR_CONNECTION_FAILURE_HOST2'
5989 ) ;
5990
5991 is( 'ERR_APPEND_VIRUS',
5992 error_type( 'could not append ( Subject:[For Your Consideration], Date:["29-Nov-2016 03:21:10 -0800"], Size:[5505], Flags:[\Seen] ) to folder INBOX: 275 NO Message refused because it contains a virus' ),
5993 'error_type: could not append ... virus => ERR_APPEND_VIRUS'
5994 ) ;
5995
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01005996
5997 is( 'ERR_FLAGS',
5998 error_type( 'Host2: flags msg INBOX/957910 could not add flags [PasGlop \PasGlopRe]: 33 NO Error in IMAP command received by server.' ),
5999 'error_type: could not add flags => ERR_FLAGS'
6000 ) ;
6001
6002
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006003 note( 'Leaving tests_error_type()' ) ;
6004 return ;
6005}
6006
6007
6008
6009# Could be implemented with https://metacpan.org/pod/Tie::RegexpHash
6010# with just a hash of error regexes as keys and types as values.
6011
6012sub error_type
6013{
6014 my $error = shift ;
6015
6016 if ( ! defined $error ) { return 'ERR_NOTHING_REPORTED' ; }
6017 if ( ! $error ) { return 'ERR_NOTHING_REPORTED' ; }
6018
6019 #
6020 if ( $error =~ m{Host1 failure: Error login on} ) { return 'ERR_AUTHENTICATION_FAILURE_USER1' } ;
6021 if ( $error =~ m{Host2 failure: Error login on} ) { return 'ERR_AUTHENTICATION_FAILURE_USER2' } ;
6022
6023 if ( $error =~ m{Host. failure: Can not go to tls encryption on host.} ) { return 'ERR_EXIT_TLS_FAILURE' } ;
6024 #
6025
6026 if ( $error =~ m{could not be fetched:} ) { return 'ERR_Host1_FETCH' } ;
6027
6028 # could not append .*BAD maximum message size exceeded
6029 # could not append.*Maximum size of appendable message has been exceeded
6030 if ( $error =~ m{could not append .*BAD maximum message size exceeded} )
6031 { return 'ERR_APPEND_SIZE' ; } ;
6032
6033 if ( $error =~ m{could not append.*Maximum size of appendable message has been exceeded} )
6034 { return 'ERR_APPEND_SIZE' ; } ;
6035
6036 # Could not create folder *[OVERQUOTA] Not enough disk quota
6037 # could not append .*[OVERQUOTA] Not enough disk quota
6038 # could not append .*[OVERQUOTA] Mailbox is full / Blocks limit exceeded / Inode limit exceeded
6039 if ( $error =~ m{OVERQUOTA} ) { return 'ERR_OVERQUOTA' ; } ;
6040 if ( $error =~ m{Quota limit will be exceeded} ) { return 'ERR_OVERQUOTA' ; } ;
6041 if ( $error =~ m{full: it is time to find a bigger place} ) { return 'ERR_OVERQUOTA' ; } ;
6042
6043 # could not append ... to folder INBOX: 276 NO Message refused because it contains a virus
6044 if ( $error =~ m{could not append.*virus} )
6045 { return 'ERR_APPEND_VIRUS' ; } ;
6046
6047 # could not append .*Write failed 'Broken pipe'
6048 # could not append .*timeout waiting .* for data from server
6049 # could not append .*BAD Invalid Arguments: Unable to parse message
6050 # could not append .*BAD Command Argument Error. 11
6051 # could not append .*NO header limit reached
6052 if ( $error =~ m{could not append} ) { return 'ERR_APPEND' ; } ;
6053
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01006054 # could not add flags
6055 if ( $error =~ m{could not add flags} ) { return 'ERR_FLAGS' ; } ;
6056
6057
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006058 # Could not create folder .*Invalid mailbox name
6059 if ( $error =~ m{Could not create folder} ) { return 'ERR_CREATE' ; } ;
6060
6061
6062 # Could not select:.*NO [NOPERM] Permission denied
6063 # Could not select:.*NO Mailbox doesn't exist
6064 # Could not select:.*NO [SERVERBUG] Internal error occurred.
6065 # Could not select:.*[CANNOT] Mailbox isn't a valid mbox file
6066 if ( $error =~ m{Could not select:} ) { return 'ERR_SELECT' ; } ;
6067
6068 #Maximum bytes transferred reached, 423 >= 100, ending sync
6069 if ( $error =~ m{Maximum bytes transferred reached} ) { return 'ERR_TRANSFER_EXCEEDED' ; } ;
6070
6071 if ( $error =~ m{can not open imap connection on host1} ) { return 'ERR_CONNECTION_FAILURE_HOST1' ; } ;
6072 if ( $error =~ m{can not open imap connection on host2} ) { return 'ERR_CONNECTION_FAILURE_HOST2' ; } ;
6073
6074 # Default is ERR_UNCLASSIFIED
6075 return 'ERR_UNCLASSIFIED' ;
6076
6077}
6078
6079sub tests_errorclassify
6080{
6081 note( 'Entering tests_errorclassify()' ) ;
6082
6083 is( undef, errorclassify( ), 'errorclassify: no args => undef' ) ;
6084
6085 is_deeply( { 'ERR_UNCLASSIFIED' => 1 }, errorclassify( 'aie' ), 'errorclassify: aie => { ERR_UNCLASSIFIED => 1 }' ) ;
6086 is_deeply( { 'ERR_UNCLASSIFIED' => 2 }, errorclassify( 'aie', 'ouille' ), 'errorclassify: aie ouille => { ERR_UNCLASSIFIED => 2 }' ) ;
6087 is_deeply( { 'ERR_UNCLASSIFIED' => 2, 'ERR_NOTHING_REPORTED' => 1 }, errorclassify( 'aie', 'ouille', '' ), 'errorclassify: aie ouille "" => { ERR_UNCLASSIFIED => 2 }' ) ;
6088 is_deeply( { 'ERR_UNCLASSIFIED' => 3 }, errorclassify( 'aie', 'ouille', 'aie' ), 'errorclassify: aie ouille aie => { ERR_UNCLASSIFIED => 3 }' ) ;
6089 is_deeply( { 'ERR_UNCLASSIFIED' => 1, 'ERR_OVERQUOTA' => 2 }, errorclassify( 'aie', 'OVERQUOTA pipi', 'OVERQUOTA caca' ), 'errorclassify: aie OVERQUOTA OVERQUOTA' ) ;
6090 is_deeply( { 'ERR_NOTHING_REPORTED' => 1 }, errorclassify( '' ), 'errorclassify: "" => { ERR_NOTHING_REPORTED => 1 }' ) ;
6091 is_deeply( { 'ERR_NOTHING_REPORTED' => 2 }, errorclassify( '', '' ), 'errorclassify: "", "" => { ERR_NOTHING_REPORTED => 1 }' ) ;
6092
6093 note( 'Leaving tests_errorclassify()' ) ;
6094 return ;
6095}
6096
6097
6098
6099sub errorclassify
6100{
6101 my @errors = @ARG ;
6102
6103 if ( ! @errors ) { return ; } ;
6104
6105 my $error_type_count = { } ;
6106 foreach my $error ( @errors )
6107 {
6108 my $error_type = error_type( $error ) ;
6109 $error_type_count->{ $error_type }++ ;
6110 }
6111
6112 return $error_type_count ;
6113}
6114
6115sub tests_most_common_error
6116{
6117 note( 'Entering tests_most_common_error()' ) ;
6118
6119 is( 'ERR_NOTHING_REPORTED', most_common_error( ), 'most_common_error: no args => ERR_NOTHING_REPORTED' ) ;
6120 is( 'ERR_NOTHING_REPORTED', most_common_error( {} ), 'most_common_error: empty hash ref => ERR_NOTHING_REPORTED' ) ;
6121 is( 'ERR_NOTHING_REPORTED', most_common_error( 'blabla' ), 'most_common_error: not a hash ref => ERR_NOTHING_REPORTED' ) ;
6122
6123 is( 'ERR_FOO', most_common_error( { ERR_FOO => 1 } ), 'most_common_error: { ERR_FOO => 1 } => ERR_FOO' ) ;
6124 is( 'ERR_BAR', most_common_error( { ERR_FOO => 1, ERR_BAR => 2 } ), 'most_common_error: { ERR_FOO => 1, ERR_BAR => 2 } => ERR_BAR' ) ;
6125 is( 'ERR_FOO', most_common_error( { ERR_FOO => 2, ERR_BAR => 1 } ), 'most_common_error: { ERR_FOO => 2, ERR_BAR => 1 } => ERR_FOO' ) ;
6126 # exaequo => first lexical wins. ERR_BAR <= ERR_FOO
6127 is( 'ERR_BAR', most_common_error( { ERR_FOO => 2, ERR_BAR => 2 } ), 'most_common_error: { ERR_FOO => 2, ERR_BAR => 2 } => ERR_BAR' ) ;
6128
6129 is( 'A', most_common_error( { A => 5, B => 5, C => 5 } ), 'most_common_error: { A => 5, B => 5, C => 5 } => A' ) ;
6130 is( 'B', most_common_error( { A => 5, B => 6, C => 6 } ), 'most_common_error: { A => 5, B => 6, C => 6 } => B' ) ;
6131 is( 'C', most_common_error( { A => 5, B => 5, C => 7 } ), 'most_common_error: { A => 5, B => 5, C => 7 } => C' ) ;
6132 is( 'C', most_common_error( { A => 5, B => 6, C => 7 } ), 'most_common_error: { A => 5, B => 5, C => 7 } => C' ) ;
6133
6134 note( 'Leaving tests_most_common_error()' ) ;
6135 return ;
6136}
6137
6138
6139
6140sub most_common_error
6141{
6142 my $errors_counted_ref = shift ;
6143
6144 if ( ! defined $errors_counted_ref ) { return 'ERR_NOTHING_REPORTED' ; }
6145
6146 if ( 'HASH' ne ref $errors_counted_ref ) { return 'ERR_NOTHING_REPORTED' ; }
6147
6148 # empty hash
6149 if ( !%{ $errors_counted_ref } ) { return 'ERR_NOTHING_REPORTED' ; }
6150
6151 # non empty hash
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01006152 # in case of equality the winner error is the first in alphabetic order
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006153 my $most_common_error = ( sort
6154 {
6155 $errors_counted_ref->{$b} <=> $errors_counted_ref->{$a}
6156 || $a cmp $b
6157 } keys %{$errors_counted_ref} )[0] ;
6158
6159 return $most_common_error ;
6160
6161}
6162
6163
6164
6165sub tests_errorsanalyse
6166{
6167 note( 'Entering tests_errorsanalyse()' ) ;
6168
6169 is( 'ERR_NOTHING_REPORTED', errorsanalyse( ), 'errorsanalyse: no args => ERR_NOTHING_REPORTED' ) ;
6170 is( 'ERR_NOTHING_REPORTED', errorsanalyse( ( ) ), 'errorsanalyse: empty list => ERR_NOTHING_REPORTED' ) ;
6171 is( 'ERR_UNCLASSIFIED', errorsanalyse( 'aie' ), 'errorsanalyse: aie => ERR_UNCLASSIFIED' ) ;
6172
6173 # in case of equality, empty wins
6174 is( 'ERR_NOTHING_REPORTED', errorsanalyse( 'aie', '' ), 'errorsanalyse: aie => ERR_UNCLASSIFIED' ) ;
6175 is( 'ERR_NOTHING_REPORTED', errorsanalyse( '', 'aie' ), 'errorsanalyse: aie => ERR_UNCLASSIFIED' ) ;
6176
6177
6178 is( 'ERR_UNCLASSIFIED', errorsanalyse( 'aie', 'ouille' ), 'errorsanalyse: aie, ouille => ERR_UNCLASSIFIED' ) ;
6179 is( 'ERR_UNCLASSIFIED', errorsanalyse( 'aie', 'ouille', '' ), 'errorsanalyse: aie, ouille, "" => ERR_UNCLASSIFIED' ) ;
6180 is( 'ERR_UNCLASSIFIED', errorsanalyse( '', 'aie', 'ouille' ), 'errorsanalyse: aie, ouille, "" => ERR_UNCLASSIFIED' ) ;
6181
6182 is( 'ERR_NOTHING_REPORTED', errorsanalyse( '' ), 'errorsanalyse: "" => ERR_NOTHING_REPORTED' ) ;
6183 is( 'ERR_NOTHING_REPORTED', errorsanalyse( ( '' ) ), 'errorsanalyse: ( "" ) => ERR_NOTHING_REPORTED' ) ;
6184 is( 'ERR_NOTHING_REPORTED', errorsanalyse( ( '', '' ) ), 'errorsanalyse: ( "", "" ) => ERR_NOTHING_REPORTED' ) ;
6185
6186 note( 'Leaving tests_errorsanalyse()' ) ;
6187 return ;
6188}
6189
6190
6191
6192sub errorsanalyse
6193{
6194 my @errors = @ARG ;
6195 my $errors_types_counted = errorclassify( @errors ) ;
6196
6197 my $most_common_error = most_common_error( $errors_types_counted ) ;
6198
6199 return $most_common_error ;
6200}
6201
6202
6203
6204sub tests_errorsdump
6205{
6206 note( 'Entering tests_errorsdump()' ) ;
6207
6208 is( undef, errorsdump( ), 'errorsdump: no args => undef' ) ;
6209 is( undef, errorsdump( ( ) ), 'errorsdump: empty list => undef' ) ;
6210 is( "Err 1/1: ", errorsdump( '' ), 'errorsdump: one empty string => "Err 1/1: "' ) ;
6211 is( "Err 1/1: aieaieaie", errorsdump( 'aieaieaie' ), 'errorsdump: aieaieaie => "Err 1/1: aieaieaie"' ) ;
6212 is( "Err 1/2: Aie Err 2/2: Ouille", errorsdump( 'Aie ', 'Ouille' ), 'errorsdump: Aie Ouille => "Err 1/2: Aie Err 2/2: Ouille"' ) ;
6213 note( 'Leaving tests_errorsdump()' ) ;
6214 return ;
6215}
6216
6217
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006218sub errorsdump
6219{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006220 if ( ! @ARG ) { return ; }
6221
6222 my @errors_log = @ARG ;
6223 my $nb_errors = @errors_log ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006224 my $error_num = 0 ;
6225 my $errors_list = q{} ;
6226 if ( @errors_log ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006227 foreach my $error ( @errors_log )
6228 {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006229 $error_num++ ;
6230 $errors_list .= "Err $error_num/$nb_errors: $error" ;
6231 }
6232 }
6233 return( $errors_list ) ;
6234}
6235
6236
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006237
6238sub errors_listing
6239{
6240 my $mysync = shift ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01006241 $mysync->{ most_common_error } = errorsanalyse( errors_log( $mysync ) ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006242
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01006243 my $errors_listing = '' ;
6244
6245 if ( $mysync->{ errorsdump } )
6246 {
6247 $errors_listing = join( '',
6248 "++++ Listing $mysync->{nb_errors} errors encountered during the sync ( avoid this listing with --noerrorsdump ).\n",
6249 errorsdump( errors_log( $mysync ) ),
6250 ) ;
6251 }
6252
6253 $errors_listing .= join( '',
6254 "The most frequent error is $mysync->{ most_common_error }. ",
6255 comment_of_error_type( $mysync, $mysync->{ most_common_error } ),
6256 "\n",
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006257 ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01006258
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006259 return $errors_listing ;
6260}
6261
6262
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01006263sub errors_incr
6264{
6265 my ( $mysync, @error ) = @ARG ;
6266 $mysync->{ nb_errors }++ ;
6267
6268 if ( @error ) {
6269 errors_log( $mysync, @error ) ;
6270 myprint( @error ) ;
6271 }
6272
6273 $mysync->{ errorsmax } ||= $ERRORS_MAX ;
6274
6275
6276 if ( $mysync->{ nb_errors } >= $mysync->{ errorsmax } )
6277 {
6278 myprint( errorsmax_msg( $mysync ) ) ;
6279 myprint( errors_listing( $mysync ) ) ;
6280
6281 if ( $mysync->{ errorsdump } )
6282 {
6283 # again since errorsdump( ) can be very verbose and masquerade previous warning
6284 myprint( errorsmax_msg( $mysync ) ) ;
6285 }
6286 my $exit_value = exit_value( $mysync, $mysync->{ most_common_error } ) ;
6287 exit_clean( $mysync, $exit_value ) ;
6288 }
6289 return ;
6290}
6291
6292
6293
6294sub errorsmax_msg
6295{
6296 my $mysync = shift @ARG ;
6297 my $msg = "Maximum number of errors $mysync->{errorsmax} reached "
6298 . "( you can change $mysync->{errorsmax} to any value, for example 100 with --errorsmax 100 ). "
6299 . "Exiting.\n" ;
6300 return $msg ;
6301}
6302
6303
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006304
6305
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006306sub tests_live_result
6307{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006308 note( 'Entering tests_live_result()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006309
6310 my $nb_errors = shift ;
6311 if ( $nb_errors ) {
6312 myprint( "Live tests failed with $nb_errors errors\n" ) ;
6313 } else {
6314 myprint( "Live tests ended successfully\n" ) ;
6315 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006316 note( 'Leaving tests_live_result()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006317 return ;
6318}
6319
6320
6321sub size_filtered_flag
6322{
6323 my $mysync = shift ;
6324 my $h1_size = shift ;
6325
6326 if ( defined $mysync->{ maxsize } and $h1_size >= $mysync->{ maxsize } ) {
6327 return( 1 ) ;
6328 }
6329 if ( defined $minsize and $h1_size <= $minsize ) {
6330 return( 1 ) ;
6331 }
6332 return( 0 ) ;
6333}
6334
6335sub sync_flags_fir
6336{
6337 my ( $mysync, $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) = @_ ;
6338
6339 if ( not defined $h1_msg ) { return } ;
6340 if ( not defined $h2_msg ) { return } ;
6341
6342 my $h1_size = $h1_fir_ref->{$h1_msg}->{'RFC822.SIZE'} ;
6343 return if size_filtered_flag( $mysync, $h1_size ) ;
6344
6345 # used cached flag values for efficiency
6346 my $h1_flags = $h1_fir_ref->{ $h1_msg }->{ 'FLAGS' } || q{} ;
6347 my $h2_flags = $h2_fir_ref->{ $h2_msg }->{ 'FLAGS' } || q{} ;
6348
6349 sync_flags( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) ;
6350
6351 return ;
6352}
6353
6354sub sync_flags_after_copy
6355{
6356 # Activated with option --syncflagsaftercopy
6357 my( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $permanentflags2 ) = @_ ;
6358
6359 if ( my @h2_flags = $mysync->{imap2}->flags( $h2_msg ) ) {
6360 my $h2_flags = "@h2_flags" ;
6361 ( $mysync->{ debug } or $debugflags ) and myprint( "Host2: msg $h2_fold/$h2_msg flags before sync flags after copy ( $h2_flags )\n" ) ;
6362 sync_flags( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) ;
6363 }else{
6364 myprint( "Host2: msg $h2_fold/$h2_msg could not get its flags for sync flags after copy\n" ) ;
6365 }
6366 return ;
6367}
6368
6369# Globals
6370# $debug
6371# $debugflags
6372# $permanentflags2
6373
6374
6375sub sync_flags
6376{
6377 my( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) = @_ ;
6378
6379 ( $mysync->{ debug } or $debugflags ) and
6380 myprint( "Host1: flags init msg $h1_fold/$h1_msg flags( $h1_flags ) Host2 msg $h2_fold/$h2_msg flags( $h2_flags )\n" ) ;
6381
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006382 $h1_flags = flags_for_host2( $mysync, $h1_flags, $permanentflags2 ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006383
6384 $h2_flags = flagscase( $h2_flags ) ;
6385
6386 ( $mysync->{ debug } or $debugflags ) and
6387 myprint( "Host1: flags filt msg $h1_fold/$h1_msg flags( $h1_flags ) Host2 msg $h2_fold/$h2_msg flags( $h2_flags )\n" ) ;
6388
6389
6390 # compare flags - set flags if there a difference
6391 my @h1_flags = sort split(q{ }, $h1_flags );
6392 my @h2_flags = sort split(q{ }, $h2_flags );
6393 my $diff = compare_lists( \@h1_flags, \@h2_flags );
6394
6395 $diff and ( $mysync->{ debug } or $debugflags )
6396 and myprint( "Host2: flags msg $h2_fold/$h2_msg replacing h2 flags( $h2_flags ) with h1 flags( $h1_flags )\n" ) ;
6397
6398 # This sets flags exactly. So flags can be removed with this.
6399 # When you remove a \Seen flag on host1 you want it
6400 # to be removed on host2. Just add flags is not what
6401 # we need most of the time, so no + like in "+FLAGS.SILENT".
6402
6403 if ( not $mysync->{dry} and $diff and not $mysync->{imap2}->store( $h2_msg, "FLAGS.SILENT (@h1_flags)" ) ) {
6404 my $error_msg = join q{}, "Host2: flags msg $h2_fold/$h2_msg could not add flags [@h1_flags]: ",
6405 $mysync->{imap2}->LastError || q{}, "\n" ;
6406 errors_incr( $mysync, $error_msg ) ;
6407 }
6408
6409 return ;
6410}
6411
6412
6413
6414sub _filter
6415{
6416 my $mysync = shift ;
6417 my $str = shift or return q{} ;
6418 my $sz = $SIZE_MAX_STR ;
6419 my $len = length $str ;
6420 if ( not $mysync->{ debug } and $len > $sz*2 ) {
6421 my $beg = substr $str, 0, $sz ;
6422 my $end = substr $str, -$sz, $sz ;
6423 $str = $beg . '...' . $end ;
6424 }
6425 $str =~ s/\012?\015$//x ;
6426 return "(len=$len) " . $str ;
6427}
6428
6429
6430
6431sub lost_connection
6432{
6433 my( $mysync, $imap, $error_message ) = @_;
6434 if ( $imap->IsUnconnected( ) ) {
6435 $mysync->{nb_errors}++ ;
6436 my $lcomm = $imap->LastIMAPCommand || q{} ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006437
6438 my $einfo = imap_last_error( $imap ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006439
6440 # if string is long try reduce to a more reasonable size
6441 $lcomm = _filter( $mysync, $lcomm ) ;
6442 $einfo = _filter( $mysync, $einfo ) ;
6443 myprint( "Failure: last command: $lcomm\n") if ( $mysync->{ debug } && $lcomm) ;
6444 myprint( "Failure: lost connection $error_message: ", $einfo, "\n") ;
6445 return( 1 ) ;
6446 }
6447 else{
6448 return( 0 ) ;
6449 }
6450}
6451
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006452sub imap_last_error
6453{
6454 my $imap = shift ;
6455 my $einfo = $imap->LastError || @{$imap->History}[$LAST] || q{} ;
6456 chomp( $einfo ) ;
6457 return( $einfo ) ;
6458}
6459
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006460sub tests_max
6461{
6462 note( 'Entering tests_max()' ) ;
6463
6464 is( 0, max( 0 ), 'max 0 => 0' ) ;
6465 is( 1, max( 1 ), 'max 1 => 1' ) ;
6466 is( $MINUS_ONE, max( $MINUS_ONE ), 'max -1 => -1') ;
6467 is( undef, max( ), 'max no arg => undef' ) ;
6468 is( undef, max( undef ), 'undef => undef' ) ;
6469 is( undef, max( undef, undef ), 'undef, undef => undef' ) ;
6470
6471 is( $NUMBER_100, max( 1, $NUMBER_100 ), 'max 1 100 => 100' ) ;
6472 is( $NUMBER_100, max( $NUMBER_100, 1 ), 'max 100 1 => 100' ) ;
6473 is( $NUMBER_100, max( $NUMBER_100, $NUMBER_42, 1 ), 'max 100 42 1 => 100' ) ;
6474 is( $NUMBER_100, max( $NUMBER_100, '42', 1 ), 'max 100 42 1 => 100' ) ;
6475 is( $NUMBER_100, max( '100', '42', 1 ), 'max 100 42 1 => 100' ) ;
6476 is( $NUMBER_100, max( $NUMBER_100, 'haha', 1 ), 'max 100 haha 1 => 100') ;
6477 is( $NUMBER_100, max( 'bb', $NUMBER_100, 'haha' ), 'max bb 100 haha => 100') ;
6478 is( $MINUS_ONE, max( q{}, $MINUS_ONE, 'haha' ), 'max "" -1 haha => -1') ;
6479 is( $MINUS_ONE, max( q{}, $MINUS_ONE, $MINUS_TWO ), 'max "" -1 -2 => -1') ;
6480 is( $MINUS_ONE, max( 'haha', $MINUS_ONE, $MINUS_TWO ), 'max haha -1 -2 => -1') ;
6481 is( 1, max( $MINUS_ONE, 1 ), 'max -1 1 => 1') ;
6482 is( 1, max( undef, 1 ), 'max undef 1 => 1' ) ;
6483 is( 0, max( undef, 0 ), 'max undef 0 => 0' ) ;
6484 is( 'haha', max( 'haha' ), 'max haha => haha') ;
6485 is( 'bb', max( 'aa', 'bb' ), 'max aa bb => bb') ;
6486 is( 'bb', max( 'bb', 'aa' ), 'max bb aa => bb') ;
6487 is( 'bb', max( 'bb', 'aa', 'bb' ), 'max bb aa bb => bb') ;
6488 note( 'Leaving tests_max()' ) ;
6489 return ;
6490}
6491
6492sub max
6493{
6494 my @list = @_ ;
6495 return( undef ) if ( 0 == scalar @list ) ;
6496
6497 my( @numbers, @notnumbers ) ;
6498 foreach my $item ( @list )
6499 {
6500 if ( is_number( $item ) )
6501 {
6502 push @numbers, $item ;
6503 }
6504 elsif ( defined $item )
6505 {
6506 push @notnumbers, $item ;
6507 }
6508 }
6509
6510 my @sorted ;
6511
6512 if ( @numbers )
6513 {
6514 @sorted = sort { $a <=> $b } @numbers ;
6515 }
6516 elsif ( @notnumbers )
6517 {
6518 @sorted = sort { $a cmp $b } @notnumbers ;
6519 }
6520 else
6521 {
6522 return ;
6523 }
6524
6525 return( pop @sorted ) ;
6526}
6527
6528sub tests_is_number
6529{
6530 note( 'Entering tests_is_number()' ) ;
6531
6532 is( undef, is_number( ), 'is_number: no args => undef ' ) ;
6533 is( undef, is_number( undef ), 'is_number: undef => undef ' ) ;
6534 ok( is_number( 1 ), 'is_number: 1 => 1' ) ;
6535 ok( is_number( 1.1 ), 'is_number: 1.1 => 1' ) ;
6536 ok( is_number( 0 ), 'is_number: 0 => 1' ) ;
6537 ok( is_number( -1 ), 'is_number: -1 => 1' ) ;
6538 ok( ! is_number( 1.1.1 ), 'is_number: 1.1.1 => no' ) ;
6539 ok( ! is_number( q{} ), 'is_number: q{} => no' ) ;
6540 ok( ! is_number( 'haha' ), 'is_number: haha => no' ) ;
6541 ok( ! is_number( '0haha' ), 'is_number: 0haha => no' ) ;
6542 ok( ! is_number( '2haha' ), 'is_number: 2haha => no' ) ;
6543 ok( ! is_number( 'haha2' ), 'is_number: haha2 => no' ) ;
6544
6545 note( 'Leaving tests_is_number()' ) ;
6546 return ;
6547}
6548
6549
6550
6551sub is_number
6552{
6553 my $item = shift ;
6554
6555 if ( ! defined $item ) { return ; }
6556
6557 if ( $item =~ /\A$RE{num}{real}\Z/ ) {
6558 return 1 ;
6559 }
6560 return ;
6561}
6562
6563sub tests_min
6564{
6565 note( 'Entering tests_min()' ) ;
6566
6567 is( 0, min( 0 ), 'min 0 => 0' ) ;
6568 is( 1, min( 1 ), 'min 1 => 1' ) ;
6569 is( $MINUS_ONE, min( $MINUS_ONE ), 'min -1 => -1' ) ;
6570 is( undef, min( ), 'min no arg => undef' ) ;
6571 is( 1, min( 1, $NUMBER_100 ), 'min 1 100 => 1' ) ;
6572 is( 1, min( $NUMBER_100, 1 ), 'min 100 1 => 1' ) ;
6573 is( 1, min( $NUMBER_100, $NUMBER_42, 1 ), 'min 100 42 1 => 1' ) ;
6574 is( 1, min( $NUMBER_100, '42', 1 ), 'min 100 42 1 => 1' ) ;
6575 is( 1, min( '100', '42', 1 ), 'min 100 42 1 => 1' ) ;
6576 is( 1, min( $NUMBER_100, 'haha', 1 ), 'min 100 haha 1 => 1') ;
6577 is( $MINUS_ONE, min( $MINUS_ONE, 1 ), 'min -1 1 => -1') ;
6578
6579 is( 1, min( undef, 1 ), 'min undef 1 => 1' ) ;
6580 is( 0, min( undef, 0 ), 'min undef 0 => 0' ) ;
6581 is( 1, min( undef, 1 ), 'min undef 1 => 1' ) ;
6582 is( 0, min( undef, 2, 0, 1 ), 'min undef, 2, 0, 1 => 0' ) ;
6583
6584 is( 'haha', min( 'haha' ), 'min haha => haha') ;
6585 is( 'aa', min( 'aa', 'bb' ), 'min aa bb => aa') ;
6586 is( 'aa', min( 'bb', 'aa' ), 'min bb aa bb => aa') ;
6587 is( 'aa', min( 'bb', 'aa', 'bb' ), 'min bb aa bb => aa') ;
6588
6589 note( 'Leaving tests_min()' ) ;
6590 return ;
6591}
6592
6593
6594sub min
6595{
6596 my @list = @_ ;
6597 return( undef ) if ( 0 == scalar @list ) ;
6598
6599 my( @numbers, @notnumbers ) ;
6600 foreach my $item ( @list ) {
6601 if ( is_number( $item ) ) {
6602 push @numbers, $item ;
6603 }else{
6604 push @notnumbers, $item ;
6605 }
6606 }
6607
6608 my @sorted ;
6609 if ( @numbers ) {
6610 @sorted = sort { $a <=> $b } @numbers ;
6611 }elsif( @notnumbers ) {
6612 @sorted = sort { $a cmp $b } @notnumbers ;
6613 }else{
6614 return ;
6615 }
6616
6617 return( shift @sorted ) ;
6618}
6619
6620
6621sub check_lib_version
6622{
6623 my $mysync = shift ;
6624 $mysync->{ debug } and myprint( "IMAPClient $Mail::IMAPClient::VERSION\n" ) ;
6625 if ( '2.2.9' eq $Mail::IMAPClient::VERSION ) {
6626 myprint( "imapsync no longer supports Mail::IMAPClient 2.2.9, upgrade it\n" ) ;
6627 return 0 ;
6628 }
6629 else{
6630 # 3.x.x is no longer buggy with imapsync.
6631 # 3.30 or currently superior is imposed in the Perl "use Mail::IMAPClient line".
6632 return 1 ;
6633 }
6634 return ;
6635}
6636
6637sub module_version_str
6638{
6639 my( $module_name, $module_version ) = @_ ;
6640 my $str = mysprintf( "%-20s %s\n", $module_name, $module_version ) ;
6641 return( $str ) ;
6642}
6643
6644sub modulesversion
6645{
6646
6647 my @list_version;
6648
6649 my %modulesversion = (
6650 'Authen::NTLM' => sub { $Authen::NTLM::VERSION },
6651 'CGI' => sub { $CGI::VERSION },
6652 'Compress::Zlib' => sub { $Compress::Zlib::VERSION },
6653 'Crypt::OpenSSL::RSA' => sub { $Crypt::OpenSSL::RSA::VERSION },
6654 'Data::Uniqid' => sub { $Data::Uniqid::VERSION },
6655 'Digest::HMAC_MD5' => sub { $Digest::HMAC_MD5::VERSION },
6656 'Digest::HMAC_SHA1' => sub { $Digest::HMAC_SHA1::VERSION },
6657 'Digest::MD5' => sub { $Digest::MD5::VERSION },
6658 'Encode' => sub { $Encode::VERSION },
6659 'Encode::IMAPUTF7' => sub { $Encode::IMAPUTF7::VERSION },
6660 'File::Copy::Recursive' => sub { $File::Copy::Recursive::VERSION },
6661 'File::Spec' => sub { $File::Spec::VERSION },
6662 'Getopt::Long' => sub { $Getopt::Long::VERSION },
6663 'HTML::Entities' => sub { $HTML::Entities::VERSION },
6664 'IO::Socket' => sub { $IO::Socket::VERSION },
6665 'IO::Socket::INET' => sub { $IO::Socket::INET::VERSION },
6666 'IO::Socket::INET6' => sub { $IO::Socket::INET6::VERSION },
6667 'IO::Socket::IP' => sub { $IO::Socket::IP::VERSION },
6668 'IO::Socket::SSL' => sub { $IO::Socket::SSL::VERSION },
6669 'IO::Tee' => sub { $IO::Tee::VERSION },
6670 'JSON' => sub { $JSON::VERSION },
6671 'JSON::WebToken' => sub { $JSON::WebToken::VERSION },
6672 'LWP' => sub { $LWP::VERSION },
6673 'Mail::IMAPClient' => sub { $Mail::IMAPClient::VERSION },
6674 'MIME::Base64' => sub { $MIME::Base64::VERSION },
6675 'Net::Ping' => sub { $Net::Ping::VERSION },
6676 'Net::SSLeay' => sub { $Net::SSLeay::VERSION },
6677 'Term::ReadKey' => sub { $Term::ReadKey::VERSION },
6678 'Test::MockObject' => sub { $Test::MockObject::VERSION },
6679 'Time::HiRes' => sub { $Time::HiRes::VERSION },
6680 'Unicode::String' => sub { $Unicode::String::VERSION },
6681 'URI::Escape' => sub { $URI::Escape::VERSION },
6682 #'Lalala' => sub { $Lalala::VERSION },
6683 ) ;
6684
6685 foreach my $module_name ( sort keys %modulesversion ) {
6686 # trick from http://www.perlmonks.org/?node_id=152122
6687 my $file_name = $module_name . '.pm' ;
6688 $file_name =~s,::,/,xmgs; # Foo::Bar::Baz => Foo/Bar/Baz.pm
6689 my $v ;
6690 eval {
6691 require $file_name ;
6692 $v = defined $modulesversion{ $module_name } ? $modulesversion{ $module_name }->() : q{?} ;
6693 } or $v = q{Not installed} ;
6694
6695 push @list_version, module_version_str( $module_name, $v ) ;
6696 }
6697 return( @list_version ) ;
6698}
6699
6700
6701sub tests_command_line_nopassword
6702{
6703 note( 'Entering tests_command_line_nopassword()' ) ;
6704
6705 ok( q{} eq command_line_nopassword(), 'command_line_nopassword void' );
6706 my $mysync = {} ;
6707 ok( '--blabla' eq command_line_nopassword( $mysync, '--blabla' ), 'command_line_nopassword --blabla' );
6708 #myprint( command_line_nopassword((qw{ --password1 secret1 })), "\n" ) ;
6709 ok( '--password1 MASKED' eq command_line_nopassword( $mysync, qw{ --password1 secret1}), 'command_line_nopassword --password1' );
6710 ok( '--blabla --password1 MASKED --blibli'
6711 eq command_line_nopassword( $mysync, qw{ --blabla --password1 secret1 --blibli } ), 'command_line_nopassword --password1 --blibli' );
6712 $mysync->{showpasswords} = 1 ;
6713 ok( q{} eq command_line_nopassword(), 'command_line_nopassword void' );
6714 ok( '--blabla' eq command_line_nopassword( $mysync, '--blabla'), 'command_line_nopassword --blabla' );
6715 #myprint( command_line_nopassword((qw{ --password1 secret1 })), "\n" ) ;
6716 ok( '--password1 secret1' eq command_line_nopassword( $mysync, qw{ --password1 secret1} ), 'command_line_nopassword --password1' );
6717 ok( '--blabla --password1 secret1 --blibli'
6718 eq command_line_nopassword( $mysync, qw{ --blabla --password1 secret1 --blibli } ), 'command_line_nopassword --password1 --blibli' );
6719
6720 note( 'Leaving tests_command_line_nopassword()' ) ;
6721 return ;
6722}
6723
6724# Construct a command line copy with passwords replaced by MASKED.
6725sub command_line_nopassword
6726{
6727 my $mysync = shift @ARG ;
6728 my @argv = @ARG ;
6729 my @argv_nopassword ;
6730
6731 if ( $mysync->{ cmdcgi } ) {
6732 @argv_nopassword = mask_password_value( @{ $mysync->{ cmdcgi } } ) ;
6733 return( "@argv_nopassword" ) ;
6734 }
6735
6736 if ( $mysync->{showpasswords} )
6737 {
6738 return( "@argv" ) ;
6739 }
6740
6741 @argv_nopassword = mask_password_value( @argv ) ;
6742 return("@argv_nopassword") ;
6743}
6744
6745sub mask_password_value
6746{
6747 my @argv = @ARG ;
6748 my @argv_nopassword ;
6749 while ( @argv ) {
6750 my $arg = shift @argv ; # option name or value
6751 if ( $arg =~ m/-password[12]/x ) {
6752 shift @argv ; # password value
6753 push @argv_nopassword, $arg, 'MASKED' ; # option name and fake value
6754 }else{
6755 push @argv_nopassword, $arg ; # same option or value
6756 }
6757 }
6758 return @argv_nopassword ;
6759}
6760
6761
6762sub tests_get_stdin_masked
6763{
6764 note( 'Entering tests_get_stdin_masked()' ) ;
6765
6766 is( q{}, get_stdin_masked( ), 'get_stdin_masked: no args' ) ;
6767 is( q{}, get_stdin_masked( 'Please ENTER: ' ), 'get_stdin_masked: ENTER' ) ;
6768
6769 note( 'Leaving tests_get_stdin_masked()' ) ;
6770 return ;
6771}
6772
6773#######################################################
6774# The issue is that prompt() does not prompt the prompt
6775# when the program is used like
6776# { sleep 2 ; echo blablabla ; } | ./imapsync ...--host1 lo --user1 tata --host2 lo --user2 titi
6777
6778# use IO::Prompter ;
6779sub get_stdin_masked
6780{
6781 my $prompt = shift || 'Say something: ' ;
6782 local @ARGV = () ;
6783 my $input = prompt(
6784 -prompt => $prompt,
6785 -echo => '*',
6786 ) ;
6787 #myprint( "You said: $input\n" ) ;
6788 return $input ;
6789}
6790
6791sub ask_for_password_new
6792{
6793 my $prompt = shift ;
6794 my $password = get_stdin_masked( $prompt ) ;
6795 return $password ;
6796}
6797#########################################################
6798
6799
6800sub ask_for_password
6801{
6802 my $prompt = shift ;
6803 myprint( $prompt ) ;
6804 Term::ReadKey::ReadMode( 2 ) ;
6805 ## no critic (InputOutput::ProhibitExplicitStdin)
6806 my $password = <STDIN> ;
6807 chomp $password ;
6808 myprint( "\nGot it\n" ) ;
6809 Term::ReadKey::ReadMode( 0 ) ;
6810 return $password ;
6811}
6812
6813# Have to refactor get_password1() get_password2()
6814# to have only get_password() and two calls
6815sub get_password1
6816{
6817
6818 my $mysync = shift ;
6819
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006820 $mysync->{ password1 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006821 || $mysync->{ passfile1 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006822 || 'PREAUTH' eq $mysync->{ acc1 }->{ authmech }
6823 || 'EXTERNAL' eq $mysync->{ acc1 }->{ authmech }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006824 || $ENV{IMAPSYNC_PASSWORD1}
6825 || do
6826 {
6827 myprint( << 'FIN_PASSFILE' ) ;
6828
6829If you are afraid of giving password on the command line arguments, you can put the
6830password of user1 in a file named file1 and use "--passfile1 file1" instead of typing it.
6831Then give this file restrictive permissions with the command "chmod 600 file1".
6832An other solution is to set the environment variable IMAPSYNC_PASSWORD1
6833FIN_PASSFILE
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006834 my $user = $mysync->{ acc1 }->{ authuser } || $mysync->{ user1 } ;
6835 my $host = $mysync->{ host1 } ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006836 my $prompt = "What's the password for $user" . ' at ' . "$host? (not visible while you type, then enter RETURN) " ;
6837 $mysync->{password1} = ask_for_password( $prompt ) ;
6838 } ;
6839
6840 if ( defined $mysync->{ passfile1 } ) {
6841 if ( ! -e -r $mysync->{ passfile1 } ) {
6842 myprint( "Failure: file from parameter --passfile1 $mysync->{ passfile1 } does not exist or is not readable\n" ) ;
6843 $mysync->{nb_errors}++ ;
6844 exit_clean( $mysync, $EX_NOINPUT ) ;
6845 }
6846 # passfile1 readable
6847 $mysync->{password1} = firstline ( $mysync->{ passfile1 } ) ;
6848 return ;
6849 }
6850 if ( $ENV{IMAPSYNC_PASSWORD1} ) {
6851 $mysync->{password1} = $ENV{IMAPSYNC_PASSWORD1} ;
6852 return ;
6853 }
6854 return ;
6855}
6856
6857sub get_password2
6858{
6859
6860 my $mysync = shift ;
6861
6862 $mysync->{password2}
6863 || $mysync->{ passfile2 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006864 || 'PREAUTH' eq $mysync->{ acc2 }->{ authmech }
6865 || 'EXTERNAL' eq $mysync->{ acc2 }->{ authmech }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006866 || $ENV{IMAPSYNC_PASSWORD2}
6867 || do
6868 {
6869 myprint( << 'FIN_PASSFILE' ) ;
6870
6871If you are afraid of giving password on the command line arguments, you can put the
6872password of user2 in a file named file2 and use "--passfile2 file2" instead of typing it.
6873Then give this file restrictive permissions with the command "chmod 600 file2".
6874An other solution is to set the environment variable IMAPSYNC_PASSWORD2
6875FIN_PASSFILE
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006876 my $user = $mysync->{ acc2 }->{ authuser } || $mysync->{ user2 } ;
6877 my $host = $mysync->{ host2 } ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006878 my $prompt = "What's the password for $user" . ' at ' . "$host? (not visible while you type, then enter RETURN) " ;
6879 $mysync->{password2} = ask_for_password( $prompt ) ;
6880 } ;
6881
6882
6883 if ( defined $mysync->{ passfile2 } ) {
6884 if ( ! -e -r $mysync->{ passfile2 } ) {
6885 myprint( "Failure: file from parameter --passfile2 $mysync->{ passfile2 } does not exist or is not readable\n" ) ;
6886 $mysync->{nb_errors}++ ;
6887 exit_clean( $mysync, $EX_NOINPUT ) ;
6888 }
6889 # passfile2 readable
6890 $mysync->{password2} = firstline ( $mysync->{ passfile2 } ) ;
6891 return ;
6892 }
6893 if ( $ENV{IMAPSYNC_PASSWORD2} ) {
6894 $mysync->{password2} = $ENV{IMAPSYNC_PASSWORD2} ;
6895 return ;
6896 }
6897 return ;
6898}
6899
6900
6901
6902
6903sub remove_tmp_files
6904{
6905 my $mysync = shift or return ;
6906 $mysync->{pidfile} or return ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006907
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006908 if ( -e $mysync->{pidfile} ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006909 myprint( "Removing pidfile $mysync->{pidfile}\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006910 unlink $mysync->{pidfile} ;
6911 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006912 if ( -e $mysync->{abortfile} ) {
6913 myprint( "Removing pidfile $mysync->{abortfile}\n" ) ;
6914 unlink $mysync->{abortfile} ;
6915 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006916 return ;
6917}
6918
6919sub cleanup_before_exit
6920{
6921 my $mysync = shift ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01006922
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006923 remove_tmp_files( $mysync ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01006924
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006925 if ( $mysync->{imap1} and $mysync->{imap1}->IsConnected() )
6926 {
6927 myprint( "Disconnecting from host1 $mysync->{ host1 } user1 $mysync->{ user1 }\n" ) ;
6928 $mysync->{imap1}->logout( ) ;
6929 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01006930
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006931 if ( $mysync->{imap2} and $mysync->{imap2}->IsConnected() )
6932 {
6933 myprint( "Disconnecting from host2 $mysync->{ host2 } user2 $mysync->{ user2 }\n" ) ;
6934 $mysync->{imap2}->logout( ) ;
6935 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01006936
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006937 if ( $mysync->{log} ) {
6938 myprint( "Log file is $mysync->{logfile} ( to change it, use --logfile filepath ; or use --nolog to turn off logging )\n" ) ;
6939 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006940 else
6941 {
6942 myprint( "No log file because of option --nolog\n" ) ;
6943 }
6944
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006945 if ( $mysync->{log} and $mysync->{logfile_handle} ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006946 #print( "Closing $mysync->{ logfile }\n" ) ;
6947 teefinish( $mysync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006948 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01006949
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006950 return ;
6951}
6952
6953
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01006954sub tests_exit_value
6955{
6956 note( 'Entering tests_exit_value()' ) ;
6957
6958 is( $EXIT_CATCH_ALL, exit_value( ), 'exit_value: no args => EXIT_CATCH_ALL' ) ;
6959
6960 my $mysync = { } ;
6961 is( $EXIT_CATCH_ALL, exit_value( $mysync ), 'exit_value: undef => EXIT_CATCH_ALL' ) ;
6962
6963 is( $EXIT_CATCH_ALL, exit_value( $mysync, 'Blabla_unknown' ), 'exit_value: Blabla => EXIT_CATCH_ALL' ) ;
6964 is( $EXIT_CATCH_ALL, exit_value( $mysync, '' ), 'exit_value: empty => EXIT_CATCH_ALL' ) ;
6965
6966
6967 is( $EXIT_OVERQUOTA, exit_value( $mysync, 'ERR_OVERQUOTA' ), 'exit_value: ERR_OVERQUOTA => EXIT_OVERQUOTA' ) ;
6968 is( $EXIT_TRANSFER_EXCEEDED, exit_value( $mysync, 'ERR_TRANSFER_EXCEEDED' ), 'exit_value: ERR_TRANSFER_EXCEEDED => EXIT_TRANSFER_EXCEEDED' ) ;
6969
6970 note( 'Leaving tests_exit_value()' ) ;
6971 return ;
6972}
6973
6974sub exit_value
6975{
6976 my $mysync = shift @ARG ;
6977 my $most_common_error = shift @ARG ;
6978
6979 if ( ! defined $most_common_error ) { return $EXIT_CATCH_ALL ; }
6980 my $exit_value = $EXIT_VALUE_OF_ERR_TYPE{ $most_common_error } || $EXIT_CATCH_ALL ;
6981
6982 return $exit_value ;
6983}
6984
6985
6986
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006987sub exit_most_errors
6988{
6989 my $mysync = shift @ARG ;
6990
6991 myprint( errors_listing( $mysync ) ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01006992 my $exit_value = exit_value( $mysync, $mysync->{ most_common_error } ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02006993 exit_clean( $mysync, $exit_value ) ;
6994 return ;
6995}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006996
6997sub exit_clean
6998{
6999 my $mysync = shift @ARG ;
7000 my $status = shift @ARG ;
7001 my @messages = @ARG ;
7002 if ( @messages )
7003 {
7004 myprint( @messages ) ;
7005 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007006 myprint( "Exiting with return value $status ($EXIT_TXT{$status}) $mysync->{nb_errors}/$mysync->{errorsmax} nb_errors/max_errors PID $PROCESS_ID\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007007 cleanup_before_exit( $mysync ) ;
7008
7009 exit $status ;
7010}
7011
7012sub missing_option
7013{
7014 my $mysync = shift ;
7015 my $option = shift ;
7016 $mysync->{nb_errors}++ ;
7017 exit_clean( $mysync, $EX_USAGE, "$option option is mandatory, for help run $PROGRAM_NAME --help\n" ) ;
7018 return ;
7019}
7020
7021
7022sub catch_ignore
7023{
7024 my $mysync = shift ;
7025 my $signame = shift ;
7026
7027 my $sigcounter = ++$mysync->{ sigcounter }{ $signame } ;
7028 myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ),
7029 "). Received $sigcounter $signame signals so far. Thanks!\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007030 do_and_print_stats( $mysync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007031 return ;
7032}
7033
7034
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007035
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007036sub catch_exit
7037{
7038 my $mysync = shift ;
7039 my $signame = shift || q{} ;
7040 if ( $signame ) {
7041 myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ),
7042 "). Asked to terminate\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007043 if ( $mysync->{can_do_stats} ) {
7044 do_and_print_stats( $mysync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007045 myprint( "Ended by a signal $signame (my PID is $PROCESS_ID my PPID is ",
7046 getppid( ), "). I am asked to terminate immediately.\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007047 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007048 myprint( "You should resynchronize those accounts by running a sync again,\n",
7049 "since some messages and entire folders might still be missing on host2.\n"
7050 ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007051 ## no critic (RequireLocalizedPunctuationVars)
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007052 # Well, restore default action does not work well
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007053 $SIG{ $signame } = 'DEFAULT'; # restore default action
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007054 #$SIG{ 'TERM' } = 'DEFAULT'; # restore default action
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007055 # kill myself with $signame
7056 # https://www.cons.org/cracauer/sigint.html
7057 myprint( "Killing myself with signal $signame\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007058 #cleanup_before_exit( $mysync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007059 kill( $signame, $PROCESS_ID ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007060 #kill( 'TERM', $PROCESS_ID ) ;
7061 #sleep 1 ;
7062 #while ( 1 ) { } ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007063 $mysync->{nb_errors}++ ;
7064 exit_clean( $mysync, $EXIT_BY_SIGNAL,
7065 "Still there after killing myself with signal $signame...\n"
7066 ) ;
7067 }
7068 else
7069 {
7070 $mysync->{nb_errors}++ ;
7071 exit_clean( $mysync, $EXIT_BY_SIGNAL, "Exiting in catch_exit with no signal...\n" ) ;
7072 }
7073 return ;
7074}
7075
7076
7077sub catch_print
7078{
7079 my $mysync = shift ;
7080 my $signame = shift ;
7081
7082 my $sigcounter = ++$mysync->{ sigcounter }{ $signame } ;
7083 myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ),
7084 "). Received $sigcounter $signame signals so far. Thanks!\n" ) ;
7085 return ;
7086}
7087
7088sub here_twice
7089{
7090 my $mysync = shift ;
7091 my $now = time ;
7092 my $previous = $mysync->{lastcatch} || 0 ;
7093 $mysync->{lastcatch} = $now ;
7094
7095 if ( $INTERVAL_TO_EXIT >= $now - $previous ) {
7096 return $TRUE ;
7097 }else{
7098 return $FALSE ;
7099 }
7100}
7101
7102
7103sub catch_reconnect
7104{
7105 my $mysync = shift ;
7106 my $signame = shift ;
7107 if ( here_twice( $mysync ) ) {
7108 myprint( "Got two signals $signame within $INTERVAL_TO_EXIT seconds. Exiting...\n" ) ;
7109 catch_exit( $mysync, $signame ) ;
7110 }else{
7111 myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ), ")\n",
7112 "Hit 2 ctr-c within 2 seconds to exit the program\n",
7113 "Hit only 1 ctr-c to reconnect to both imap servers\n",
7114 ) ;
7115 myprint( "For now only one signal $signame within $INTERVAL_TO_EXIT seconds.\n" ) ;
7116
7117 if ( ! defined $mysync->{imap1} ) { return ; }
7118 if ( ! defined $mysync->{imap2} ) { return ; }
7119
7120 myprint( "Info: reconnecting to host1 imap server $mysync->{host1}\n" ) ;
7121 $mysync->{imap1}->State( Mail::IMAPClient::Unconnected ) ;
7122 $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT} += 1 ;
7123 if ( $mysync->{imap1}->reconnect( ) )
7124 {
7125 myprint( "Info: reconnected to host1 imap server $mysync->{host1}\n" ) ;
7126 }
7127 else
7128 {
7129 $mysync->{nb_errors}++ ;
7130 exit_clean( $mysync, $EXIT_CONNECTION_FAILURE ) ;
7131 }
7132 myprint( "Info: reconnecting to host2 imap server\n" ) ;
7133 $mysync->{imap2}->State( Mail::IMAPClient::Unconnected ) ;
7134 $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT} += 1 ;
7135 if ( $mysync->{imap2}->reconnect( ) )
7136 {
7137 myprint( "Info: reconnected to host2 imap server $mysync->{host2}\n" ) ;
7138 }
7139 else
7140 {
7141 $mysync->{nb_errors}++ ;
7142 exit_clean( $mysync, $EXIT_CONNECTION_FAILURE ) ;
7143 }
7144 myprint( "Info: reconnected to both imap servers\n" ) ;
7145 }
7146 return ;
7147}
7148
7149sub install_signals
7150{
7151 my $mysync = shift ;
7152
7153 if ( under_docker_context( $mysync ) )
7154 {
7155 # output( $mysync, "Under docker context so leaving signals as they are\n" ) ;
7156 output( $mysync, "Under docker context so installing only signals to exit\n" ) ;
7157 @{ $mysync->{ sigexit } } = ( defined( $mysync->{ sigexit } ) ) ? @{ $mysync->{ sigexit } } : ( 'INT', 'QUIT', 'TERM' ) ;
7158 sig_install( $mysync, 'catch_exit', @{ $mysync->{ sigexit } } ) ;
7159 }
7160 else
7161 {
7162 # Unix signals
7163 @{ $mysync->{ sigexit } } = ( defined( $mysync->{ sigexit } ) ) ? @{ $mysync->{ sigexit } } : ( 'QUIT', 'TERM' ) ;
7164 @{ $mysync->{ sigreconnect } } = ( defined( $mysync->{ sigreconnect } ) ) ? @{ $mysync->{ sigreconnect } } : ( 'INT' ) ;
7165 @{ $mysync->{ sigprint } } = ( defined( $mysync->{ sigprint } ) ) ? @{ $mysync->{ sigprint } } : ( 'HUP' ) ;
7166 @{ $mysync->{ sigignore } } = ( defined( $mysync->{ sigignore } ) ) ? @{ $mysync->{ sigignore } } : ( ) ;
7167
7168 #local %SIG = %SIG ;
7169 sig_install( $mysync, 'catch_exit', @{ $mysync->{ sigexit } } ) ;
7170 sig_install( $mysync, 'catch_reconnect', @{ $mysync->{ sigreconnect } } ) ;
7171 sig_install( $mysync, 'catch_print', @{ $mysync->{ sigprint } } ) ;
7172 # --sigignore can override sigexit, sigreconnect and sigprint (for the same signals only)
7173 sig_install( $mysync, 'catch_ignore', @{ $mysync->{ sigignore } } ) ;
7174
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007175 # remove/add sleeping mechanism when receiving USR1 signal (except on Win32)
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007176 sig_install_toggle_sleep( $mysync ) ;
7177 }
7178
7179 return ;
7180}
7181
7182
7183
7184sub tests_reconnect_12_if_needed
7185{
7186 note( 'Entering tests_reconnect_12_if_needed()' ) ;
7187
7188 my $mysync ;
7189
7190 $mysync->{imap1} = Mail::IMAPClient->new( ) ;
7191 $mysync->{imap2} = Mail::IMAPClient->new( ) ;
7192 $mysync->{imap1}->Server( 'test1.lamiral.info' ) ;
7193 $mysync->{imap2}->Server( 'test2.lamiral.info' ) ;
7194 is( 2, reconnect_12_if_needed( $mysync ), 'reconnect_12_if_needed: test1&test2 .lamiral.info => 1' ) ;
7195 is( 1, $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_12_if_needed: test1.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ;
7196 is( 1, $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_12_if_needed: test2.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ;
7197
7198 note( 'Leaving tests_reconnect_12_if_needed()' ) ;
7199 return ;
7200}
7201
7202sub reconnect_12_if_needed
7203{
7204 my $mysync = shift ;
7205 #return 2 ;
7206 if ( ! reconnect_if_needed( $mysync->{imap1} ) ) {
7207 return ;
7208 }
7209 if ( ! reconnect_if_needed( $mysync->{imap2} ) ) {
7210 return ;
7211 }
7212 # both were good
7213 return 2 ;
7214}
7215
7216
7217sub tests_reconnect_if_needed
7218{
7219 note( 'Entering tests_reconnect_if_needed()' ) ;
7220
7221
7222 my $myimap ;
7223
7224 is( undef, reconnect_if_needed( ), 'reconnect_if_needed: no args => undef' ) ;
7225 is( undef, reconnect_if_needed( $myimap ), 'reconnect_if_needed: undef arg => undef' ) ;
7226
7227 $myimap = Mail::IMAPClient->new( ) ;
7228 $myimap->Debug( 1 ) ;
7229 is( undef, reconnect_if_needed( $myimap ), 'reconnect_if_needed: empty new Mail::IMAPClient => undef' ) ;
7230 $myimap->Server( 'test.lamiral.info' ) ;
7231 is( 1, reconnect_if_needed( $myimap ), 'reconnect_if_needed: test.lamiral.info => 1' ) ;
7232 is( 1, $myimap->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_if_needed: test.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ;
7233
7234 note( 'Leaving tests_reconnect_if_needed()' ) ;
7235 return ;
7236}
7237
7238sub reconnect_if_needed
7239{
7240 # return undef upon failure.
7241 # return 1 upon connection success, with or without reconnection.
7242
7243 my $imap = shift ;
7244
7245 if ( ! defined $imap ) { return ; }
7246 if ( ! $imap->Server( ) ) { return ; }
7247
7248 if ( $imap->IsUnconnected( ) ) {
7249 $imap->{IMAPSYNC_RECONNECT_COUNT} += 1 ;
7250 if ( $imap->reconnect( ) ) {
7251 return 1 ;
7252 }
7253 }else{
7254 return 1 ;
7255 }
7256
7257 # A last forced one
7258 $imap->State( Mail::IMAPClient::Unconnected ) ;
7259 $imap->reconnect( ) ;
7260 $imap->{IMAPSYNC_RECONNECT_COUNT} += 1 ;
7261 if ( $imap->noop ) {
7262 # NOOP is ok
7263 return 1 ;
7264 }
7265
7266 return ;
7267}
7268
7269
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007270sub justconnect
7271{
7272 my $mysync = shift ;
7273 my $justconnect1 = justconnect1( $sync ) ;
7274 my $justconnect2 = justconnect2( $sync ) ;
7275 return "$justconnect1 $justconnect2";
7276}
7277
7278sub justconnect1
7279{
7280 my $mysync = shift ;
7281 if ( $mysync->{host1} )
7282 {
7283 myprint( "Host1: Will just connect to $mysync->{host1} without login\n" ) ;
7284 $mysync->{imap1} = connect_imap(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007285 $mysync->{host1}, $mysync->{port1},
7286 $mysync->{ssl1}, $mysync->{tls1},
7287 $mysync->{ acc1 } ) ;
7288
7289 imap_id( $mysync, $mysync->{imap1}, $mysync->{ acc1 }->{ Side } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007290 $mysync->{imap1}->logout( ) ;
7291 return $mysync->{host1} ;
7292 }
7293
7294 return q{} ;
7295}
7296
7297sub justconnect2
7298{
7299 my $mysync = shift ;
7300 if ( $mysync->{host2} )
7301 {
7302 myprint( "Host2: Will just connect to $mysync->{host2} without login\n" ) ;
7303 $mysync->{imap2} = connect_imap(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007304 $mysync->{host2}, $mysync->{port2},
7305 $mysync->{ssl2}, $mysync->{tls2},
7306 $mysync->{ acc2 } ) ;
7307
7308 imap_id( $mysync, $mysync->{imap2}, $mysync->{ acc2 }->{ Side } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007309 $mysync->{imap2}->logout( ) ;
7310 return $mysync->{host2} ;
7311 }
7312
7313 return q{} ;
7314}
7315
7316sub skip_macosx
7317{
7318 #return ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007319 # hostname is sometimes "macosx.polarhome.com" sometimes "macosx"
7320 return( ( ( 'macosx.polarhome.com' eq hostname( ) ) || ( 'macosx' eq hostname( ) ) )
7321 && ( 'darwin' eq $OSNAME ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007322}
7323
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007324sub skip_macosx_binary
7325{
7326 #return ;
7327 return( skip_macosx( ) && ( $PROGRAM_NAME =~ m{imapsync_bin_Darwin} ) ) ;
7328}
7329
7330
7331
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007332sub tests_mailimapclient_connect
7333{
7334 note( 'Entering tests_mailimapclient_connect()' ) ;
7335
7336 my $imap ;
7337 # ipv4
7338 ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect ipv4: new' ) ;
7339 is( 'Mail::IMAPClient', ref( $imap ), 'mailimapclient_connect ipv4: ref is Mail::IMAPClient' ) ;
7340
7341 # Mail::IMAPClient 3.40 die on this... So we skip it, thanks to "mature" IO::Socket::IP
7342 # Mail::IMAPClient 3.42 is ok so this test is back.
7343 is( undef, $imap->connect( ), 'mailimapclient_connect ipv4: connect with no server => failure' ) ;
7344
7345
7346 is( 'test.lamiral.info', $imap->Server( 'test.lamiral.info' ), 'mailimapclient_connect ipv4: setting Server(test.lamiral.info)' ) ;
7347 is( 1, $imap->Debug( 1 ), 'mailimapclient_connect ipv4: setting Debug( 1 )' ) ;
7348 is( 143, $imap->Port( 143 ), 'mailimapclient_connect ipv4: setting Port( 143 )' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007349 is( 10, $imap->Timeout( 10 ), 'mailimapclient_connect ipv4: setting Timeout( 10 )' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007350 like( ref( $imap->connect( ) ), qr/IO::Socket::INET|IO::Socket::IP/, 'mailimapclient_connect ipv4: connect to test.lamiral.info' ) ;
7351 like( $imap->logout( ), qr/Mail::IMAPClient/, 'mailimapclient_connect ipv4: logout' ) ;
7352 is( undef, undef $imap, 'mailimapclient_connect ipv4: free variable' ) ;
7353
7354 # ipv4 + ssl
7355 ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect ipv4 + ssl: new' ) ;
7356 is( 'test.lamiral.info', $imap->Server( 'test.lamiral.info' ), 'mailimapclient_connect ipv4 + ssl: setting Server(test.lamiral.info)' ) ;
7357 is( 1, $imap->Debug( 1 ), 'mailimapclient_connect ipv4 + ssl: setting Debug( 1 )' ) ;
7358 ok( $imap->Ssl( [ SSL_verify_mode => SSL_VERIFY_NONE, SSL_cipher_list => 'DEFAULT:!DH' ] ), 'mailimapclient_connect ipv4 + ssl: setting Ssl( SSL_VERIFY_NONE )' ) ;
7359 is( 993, $imap->Port( 993 ), 'mailimapclient_connect ipv4 + ssl: setting Port( 993 )' ) ;
7360 like( ref( $imap->connect( ) ), qr/IO::Socket::SSL/, 'mailimapclient_connect ipv4 + ssl: connect to test.lamiral.info' ) ;
7361 like( $imap->logout( ), qr/Mail::IMAPClient/, 'mailimapclient_connect ipv4 + ssl: logout in ssl does not cause failure' ) ;
7362 is( undef, undef $imap, 'mailimapclient_connect ipv4 + ssl: free variable' ) ;
7363
7364 # ipv6 + ssl
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007365
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007366 ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect ipv6 + ssl: new' ) ;
7367 is( 'petiteipv6.lamiral.info', $imap->Server( 'petiteipv6.lamiral.info' ), 'mailimapclient_connect ipv6 + ssl: setting Server petiteipv6.lamiral.info' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007368 is( 10, $imap->Timeout( 10 ), 'mailimapclient_connect ipv6: setting Timeout( 10 )' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007369 ok( $imap->Ssl( [ SSL_verify_mode => SSL_VERIFY_NONE, SSL_cipher_list => 'DEFAULT:!DH' ] ), 'mailimapclient_connect ipv6 + ssl: setting Ssl( SSL_VERIFY_NONE )' ) ;
7370 is( 993, $imap->Port( 993 ), 'mailimapclient_connect ipv6 + ssl: setting Port( 993 )' ) ;
7371 SKIP: {
7372 if (
7373 'CUILLERE' eq hostname()
7374 or
7375 skip_macosx()
7376 or
7377 -e '/.dockerenv'
7378 or
7379 'pcHPDV7-HP' eq hostname()
7380 )
7381 {
7382 skip( 'Tests avoided on CUILLERE/pcHPDV7-HP/macosx.polarhome.com/docker cannot do ipv6', 4 ) ;
7383 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007384
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007385 is( 1, $imap->Debug( 1 ), 'mailimapclient_connect ipv4 + ssl: setting Debug( 1 )' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007386
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007387 # It sounds stupid but it avoids failures on the next test about $imap->connect
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007388 is( '2a01:e34:ecde:70d0:223:54ff:fec2:36d7', resolv( 'petiteipv6.lamiral.info' ), 'resolv: petiteipv6.lamiral.info => 2a01:e34:ecde:70d0:223:54ff:fec2:36d7' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007389
7390 like( ref( $imap->connect( ) ), qr/IO::Socket::SSL/, 'mailimapclient_connect ipv6 + ssl: connect to petiteipv6.lamiral.info' ) ;
7391 # This one is ok on petite, not on ks2, do not know why, so commented.
7392 like( ref( $imap->logout( ) ), qr/Mail::IMAPClient/, 'mailimapclient_connect ipv6 + ssl: logout in ssl is ok on petiteipv6.lamiral.info' ) ;
7393 }
7394
7395 is( undef, undef $imap, 'mailimapclient_connect ipv6 + ssl: free variable' ) ;
7396
7397
7398 note( 'Leaving tests_mailimapclient_connect()' ) ;
7399 return ;
7400}
7401
7402
7403sub tests_mailimapclient_connect_bug
7404{
7405 note( 'Entering tests_mailimapclient_connect_bug()' ) ;
7406
7407 my $imap ;
7408
7409 # ipv6
7410 ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect_bug ipv6: new' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007411 is( 'ks6ipv6.lamiral.info', $imap->Server( 'ks6ipv6.lamiral.info' ), 'mailimapclient_connect_bug ipv6: setting Server(ks6ipv6.lamiral.info)' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007412 is( 143, $imap->Port( 143 ), 'mailimapclient_connect_bug ipv6: setting Port( 993 )' ) ;
7413
7414 SKIP: {
7415 if (
7416 'CUILLERE' eq hostname()
7417 or
7418 skip_macosx()
7419 or
7420 -e '/.dockerenv'
7421 or
7422 'pcHPDV7-HP' eq hostname()
7423 )
7424 {
7425 skip( 'Tests avoided on CUILLERE/pcHPDV7-HP/macosx.polarhome.com/docker cannot do ipv6', 1 ) ;
7426 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007427 like( ref( $imap->connect( ) ), qr/IO::Socket::INET/, 'mailimapclient_connect_bug ipv6: connect to ks6ipv6.lamiral.info' )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007428 or diag( 'mailimapclient_connect_bug ipv6: ', $imap->LastError( ), $!, ) ;
7429 }
7430 #is( $imap->logout( ), undef, 'mailimapclient_connect_bug ipv6: logout in ssl causes failure' ) ;
7431 is( undef, undef $imap, 'mailimapclient_connect_bug ipv6: free variable' ) ;
7432
7433 note( 'Leaving tests_mailimapclient_connect_bug()' ) ;
7434 return ;
7435}
7436
7437
7438
7439sub tests_connect_socket
7440{
7441 note( 'Entering tests_connect_socket()' ) ;
7442
7443 is( undef, connect_socket( ), 'connect_socket: no args' ) ;
7444
7445 my $socket ;
7446 my $imap ;
7447 SKIP: {
7448 if (
7449 'CUILLERE' eq hostname()
7450 or
7451 skip_macosx()
7452 or
7453 -e '/.dockerenv'
7454 or
7455 'pcHPDV7-HP' eq hostname()
7456 )
7457 {
7458 skip( 'Tests avoided on CUILLERE/pcHPDV7-HP/macosx.polarhome.com/docker cannot do ipv6', 2 ) ;
7459 }
7460
7461 $socket = IO::Socket::INET6->new(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007462 PeerAddr => 'ks6ipv6.lamiral.info',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007463 PeerPort => 143,
7464 ) ;
7465
7466
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007467 ok( $imap = connect_socket( $socket ), 'connect_socket: ks6ipv6.lamiral.info port 143 IO::Socket::INET6' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007468 #$imap->Debug( 1 ) ;
7469 # myprint( $imap->capability( ) ) ;
7470 if ( $imap ) {
7471 $imap->logout( ) ;
7472 }
7473
7474 $IO::Socket::SSL::DEBUG = 4 ;
7475 $socket = IO::Socket::SSL->new(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007476 PeerHost => 'ks6ipv6.lamiral.info',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007477 PeerPort => 993,
7478 SSL_verify_mode => SSL_VERIFY_NONE,
7479 SSL_cipher_list => 'DEFAULT:!DH',
7480 ) ;
7481 # myprint( $socket ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007482 ok( $imap = connect_socket( $socket ), 'connect_socket: ks6ipv6.lamiral.info port 993 IO::Socket::SSL' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007483 #$imap->Debug( 1 ) ;
7484 # myprint( $imap->capability( ) ) ;
7485 # $socket->close( ) ;
7486 if ( $imap ) {
7487 $socket->close( ) ;
7488 }
7489 #$socket->close(SSL_no_shutdown => 1) ;
7490 #$imap->logout( ) ;
7491 #myprint( "\n" ) ;
7492 #$imap->logout( ) ;
7493 }
7494 note( 'Leaving tests_connect_socket()' ) ;
7495 return ;
7496}
7497
7498sub connect_socket
7499{
7500 my( $socket ) = @ARG ;
7501
7502 if ( ! defined $socket ) { return ; }
7503
7504 my $host = $socket->peerhost( ) ;
7505 my $port = $socket->peerport( ) ;
7506 #print "socket->peerhost: ", $socket->peerhost( ), "\n" ;
7507 #print "socket->peerport: ", $socket->peerport( ), "\n" ;
7508 my $imap = Mail::IMAPClient->new( ) ;
7509 $imap->Socket( $socket ) ;
7510 my $banner = $imap->Results()->[0] ;
7511 #myprint( "banner: $banner" ) ;
7512 return $imap ;
7513}
7514
7515
7516sub tests_probe_imapssl
7517{
7518 note( 'Entering tests_probe_imapssl()' ) ;
7519
7520 is( undef, probe_imapssl( ), 'probe_imapssl: no args => undef' ) ;
7521 is( undef, probe_imapssl( 'unknown' ), 'probe_imapssl: unknown => undef' ) ;
7522
7523 note( "hostname is: ", hostname() ) ;
7524 SKIP: {
7525 if (
7526 'CUILLERE' eq hostname()
7527 or
7528 skip_macosx()
7529 or
7530 -e '/.dockerenv'
7531 or
7532 'pcHPDV7-HP' eq hostname()
7533 )
7534 {
7535 skip( 'Tests avoided on CUILLERE or pcHPDV7-HP or Mac or docker: cannot do ipv6', 0 ) ;
7536 }
7537 # fed up with this one
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007538 #like( probe_imapssl( 'ks6ipv6.lamiral.info' ), qr/^\* OK/, 'probe_imapssl: ks6ipv6.lamiral.info matches "* OK"' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007539 } ;
7540
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007541
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007542 # It sounds stupid but it avoids failures on the next test about $imap->connect
7543 ok( resolv( 'imap.gmail.com' ), 'resolv: imap.gmail.com => something' ) ;
7544 like( probe_imapssl( 'imap.gmail.com' ), qr/^\* OK/, 'probe_imapssl: imap.gmail.com matches "* OK"' ) ;
7545
7546 like( probe_imapssl( 'test1.lamiral.info' ), qr/^\* OK/, 'probe_imapssl: test1.lamiral.info matches "* OK"' ) ;
7547
7548 note( 'Leaving tests_probe_imapssl()' ) ;
7549 return ;
7550}
7551
7552
7553sub probe_imapssl
7554{
7555 my $host = shift ;
7556
7557 if ( ! $host ) { return ; }
7558 $sync->{ debug } and $IO::Socket::SSL::DEBUG = 4 ;
7559 my $socket = IO::Socket::SSL->new(
7560 PeerHost => $host,
7561 PeerPort => $IMAP_SSL_PORT,
7562 SSL_verifycn_scheme => 'imap',
7563 SSL_verify_mode => $SSL_VERIFY_POLICY,
7564 SSL_cipher_list => 'DEFAULT:!DH',
7565 ) ;
7566 if ( ! $socket ) { return ; }
7567 $sync->{ debug } and print "socket: $socket\n" ;
7568
7569 my $banner ;
7570 $socket->sysread( $banner, 65_536 ) ;
7571 $sync->{ debug } and print "banner: $banner" ;
7572 $socket->close( ) ;
7573 return $banner ;
7574
7575}
7576
7577sub connect_imap
7578{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007579 my( $host, $port, $ssl, $tls, $acc ) = @_ ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007580 my $imap = Mail::IMAPClient->new( ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007581
7582 if ( $ssl ) { set_ssl( $imap, $acc ) }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007583 $imap->Server( $host ) ;
7584 $imap->Port( $port ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007585 $imap->Debug( $acc->{ debugimap } ) ;
7586 $imap->Timeout( $acc->{ timeout } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007587
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007588 #$imap->Keepalive( $acc->{ keepalive } ) ;
7589
7590
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007591 my $side = lc $acc->{ Side } ;
7592
7593 myprint( "$acc->{ Side }: connecting on $side [$host] port [$port]\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007594
7595 if ( ! $imap->connect( ) )
7596 {
7597 $sync->{nb_errors}++ ;
7598 exit_clean( $sync, $EXIT_CONNECTION_FAILURE,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007599 "$acc->{ Side }: Can not open imap connection on [$host]: ",
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007600 $imap->LastError,
7601 " $OS_ERROR\n"
7602 ) ;
7603 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007604 myprint( "$acc->{ Side } IP address: ", $imap->Socket->peerhost(), "\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007605
7606 my $banner = $imap->Results()->[0] ;
7607
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007608 myprint( "$acc->{ Side } banner: $banner" ) ;
7609 myprint( "$acc->{ Side } capability: ", join(q{ }, @{ $imap->capability() || [] }), "\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007610
7611 if ( $tls ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007612 set_tls( $imap, $acc ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007613 if ( ! $imap->starttls( ) )
7614 {
7615 $sync->{nb_errors}++ ;
7616 exit_clean( $sync, $EXIT_TLS_FAILURE,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007617 "$acc->{ Side }: Can not go to tls encryption on $side [$host]:",
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007618 $imap->LastError, "\n"
7619 ) ;
7620 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007621 myprint( "$acc->{ Side }: Socket successfully converted to SSL\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007622 }
7623 return( $imap ) ;
7624}
7625
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007626sub tests_compress_ssl
7627{
7628 note( 'Entering tests_compress_ssl()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007629
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007630 SKIP: {
7631 if ( skip_macosx( ) )
7632 {
7633 skip( 'Tests avoided on host polarhome macosx, no clue "ssl3_get_server_certificate:certificate verify failed"', 12 ) ;
7634 }
7635 else
7636 {
7637 my $myimap ;
7638 my $acc = {} ;
7639 $acc->{ Side } = 'HostK' ;
7640 $acc->{ authmech } = 'LOGIN' ;
7641 $acc->{ debugimap } = 1 ;
7642 $acc->{ compress } = 1 ;
7643 $acc->{ N } = 'K' ;
7644
7645 ok(
7646 $myimap = login_imap( 'test1.lamiral.info', 993, 'test1', 'secret1',
7647 1, undef,
7648 1, 100, $acc, {},
7649 ), 'acc_compress_imap: test1.lamiral.info test1 ssl' ) ;
7650 ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 ssl IsAuthenticated' ) ;
7651
7652
7653 is( $acc->{ imap }, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info ok" ) ;
7654 is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info 2nd nok" ) ;
7655
7656 ok(
7657 $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1',
7658 0, undef,
7659 1, 100, $acc, {},
7660 ), 'acc_compress_imap: test1.lamiral.info test1 tls' ) ;
7661 ok( $myimap && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 tls IsAuthenticated' ) ;
7662
7663 is( $acc->{ imap }, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info tls ok" ) ;
7664 is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info tls 2nd nok" ) ;
7665
7666 # Third, no compression
7667 $acc->{ compress } = 0 ;
7668 ok(
7669 $myimap = login_imap( 'test1.lamiral.info', 993, 'test1', 'secret1',
7670 1, undef,
7671 1, 100, $acc, {},
7672 ), 'acc_compress_imap: test1.lamiral.info test1 ssl' ) ;
7673 ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 ssl IsAuthenticated' ) ;
7674
7675
7676 is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info off ok" ) ;
7677 is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info 2nd off ok" ) ;
7678
7679 }
7680 }
7681 note( 'Leaving tests_compress_ssl()' ) ;
7682 return ;
7683}
7684
7685sub tests_compress
7686{
7687 note( 'Entering tests_compress()' ) ;
7688
7689 my $myimap ;
7690 my $acc = {} ;
7691 $acc->{ Side } = 'HostK' ;
7692 $acc->{ authmech } = 'LOGIN' ;
7693 $acc->{ debugimap } = 1 ;
7694 $acc->{ compress } = 1 ;
7695 $acc->{ N } = 'K' ;
7696
7697 ok(
7698 $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1',
7699 0, 0,
7700 1, 100, $acc, {},
7701 ), 'acc_compress_imap: test1.lamiral.info test1' ) ;
7702 ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 IsAuthenticated' ) ;
7703
7704
7705 is( $acc->{ imap }, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info ok" ) ;
7706 is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info 2nd nok" ) ;
7707
7708 ok(
7709 $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1',
7710 0, 0,
7711 1, 100, $acc, {},
7712 ), 'acc_compress_imap: test1.lamiral.info test1 tls' ) ;
7713 ok( $myimap && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 tls IsAuthenticated' ) ;
7714
7715 is( $acc->{ imap }, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info tls ok" ) ;
7716 is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info tls 2nd nok" ) ;
7717
7718 # Third, no compression
7719 $acc->{ compress } = 0 ;
7720 ok(
7721 $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1',
7722 0, 0,
7723 1, 100, $acc, {},
7724 ), 'acc_compress_imap: test1.lamiral.info test1 ssl' ) ;
7725 ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 ssl IsAuthenticated' ) ;
7726
7727
7728 is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info off ok" ) ;
7729 is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info 2nd off ok" ) ;
7730
7731 note( 'Leaving tests_compress()' ) ;
7732 return ;
7733}
7734
7735
7736sub acc_compress_imap
7737{
7738 my $acc = shift ;
7739
7740 if ( ! defined( $acc ) ) { return ; }
7741
7742 my $ret ;
7743 my $imap = $acc->{ imap } ;
7744 if ( ! defined $imap ) { return ; }
7745
7746 if ( $imap && $acc->{ compress } )
7747 {
7748 myprint( "$acc->{ Side }: Trying to turn imap compression on. Use --nocompress" . $acc->{ N } . " to avoid compression on " . lc( $acc->{ Side } ) . "\n" ) ;
7749 if ( $ret = $imap->compress() )
7750 {
7751 myprint( "$acc->{ Side }: Compression is on now\n" ) ;
7752 }
7753 else
7754 {
7755 myprint( "$acc->{ Side }: Failed to turn compression on\n" ) ;
7756 }
7757 }
7758 else
7759 {
7760 myprint( "$acc->{ Side }: Compression is off. Use --compress" . $acc->{ N } . " to allow compression on " . lc( $acc->{ Side } ) . "\n" ) ;
7761 }
7762 # $ret is $acc->{ imap } on success, undef on failure or when there is nothing to do.
7763 return $ret ;
7764}
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007765
7766sub tests_login_imap
7767{
7768 note( 'Entering tests_login_imap()' ) ;
7769
7770 is( undef, login_imap( ), 'login_imap: no args => undef' ) ;
7771
7772 SKIP: {
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007773 if ( skip_macosx( ) )
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007774 {
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007775 skip( 'Tests avoided only on binary on host polarhome macosx, no clue "ssl3_get_server_certificate:certificate verify failed"', 15 ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007776 }
7777 else{
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007778
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007779 my $myimap ;
7780 my $acc = {} ;
7781 $acc->{ Side } = 'HostK' ;
7782 $acc->{ authmech } = 'LOGIN' ;
7783 #$IO::Socket::SSL::DEBUG = 4 ;
7784 # Each month (trimester?):
7785 # echo | openssl s_client -crlf -connect test1.lamiral.info:993
7786 # ...
7787 # certificate has expired
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007788 # Fix: ssh root@test1.lamiral.info 'apt update && apt upgrade && /etc/init.d/dovecot restart'
7789 #
7790 # or
7791 # echo | openssl s_client -crlf -connect test1.lamiral.info:993
7792 # ...
7793 # Verify return code: 9 (certificate is not yet valid)
7794 # Fix: /etc/init.d/openntpd restart
7795 # 2021_09_04 done
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007796 ok(
7797 $myimap = login_imap( 'test1.lamiral.info', 993, 'test1', 'secret1',
7798 1, undef,
7799 1, 100, $acc, {},
7800 ), 'login_imap: test1.lamiral.info test1 ssl' ) ;
7801 ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: test1.lamiral.info test1 ssl IsAuthenticated' ) ;
7802
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007803 is( $myimap, $acc->{ imap }, "login_imap: acc->{ imap } ok test1 ssl") ;
7804
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007805 ok(
7806 $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1',
7807 0, undef,
7808 1, 100, $acc, {},
7809 ), 'login_imap: test1.lamiral.info test1 tls' ) ;
7810 ok( $myimap && $myimap->IsAuthenticated( ), 'login_imap: test1.lamiral.info test1 tls IsAuthenticated' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007811 is( $myimap, $acc->{ imap }, "login_imap: acc->{ imap } ok test1 tls") ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007812
7813 #$IO::Socket::SSL::DEBUG = 4 ;
7814 $acc->{sslargs} = { SSL_version => 'SSLv2' } ;
7815 # SSLv2 not supported
7816 is(
7817 undef, $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1',
7818 0, undef,
7819 1, 100, $acc, {},
7820 ), 'login_imap: test1.lamiral.info test1 tls SSLv2 not supported' ) ;
7821#SSL_verify_mode => 1
7822#SSL_version => 'TLSv1_1'
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007823 is( undef, $acc->{ imap }, "login_imap: acc->{ imap } test1 tls error => undef") ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007824
7825
7826 # I have left ? exit_clean to be replaced by errors_incr( $mysync, 'error message' )
7827 # 1 in login_imap()
7828
7829
7830 my $mysync = {} ;
7831 $acc = {} ;
7832 $acc->{ Side } = 'Host2' ;
7833 $acc->{ authmech } = 'LOGIN' ;
7834 is(
7835 undef, login_imap( 'noresol.lamiral.info', 143, 'test1', 'secret1',
7836 0, undef,
7837 1, 100, $acc, $mysync,
7838 ), 'login_imap: noresol.lamiral.info undef' ) ;
7839
7840 is( 'ERR_CONNECTION_FAILURE_HOST2', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host2 noresol.lamiral.info => ERR_CONNECTION_FAILURE_HOST2' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007841 is( undef, $acc->{ imap }, "login_imap: acc->{ imap } noresol error => undef") ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007842
7843 # authentication failure for user2
7844 $mysync = {} ;
7845 is(
7846 undef, login_imap( 'test1.lamiral.info', 143, 'test1', 'Ce crétin',
7847 0, undef,
7848 1, 100, $acc, $mysync,
7849 ), 'login_imap: user2 bad passord => undef' ) ;
7850
7851 is( 'ERR_AUTHENTICATION_FAILURE_USER2', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host2 bad password => ERR_AUTHENTICATION_FAILURE_USER2' ) ;
7852
7853 # authentication failure for user1
7854 $mysync = {} ;
7855 $acc = {} ;
7856 $acc->{ Side } = 'Host1' ;
7857 $acc->{ authmech } = 'LOGIN' ;
7858 is(
7859 undef, login_imap( 'test1.lamiral.info', 143, 'test1', 'Ce crétin',
7860 0, undef,
7861 1, 100, $acc, $mysync,
7862 ), 'login_imap: user1 bad passord => undef' ) ;
7863
7864 is( 'ERR_AUTHENTICATION_FAILURE_USER1', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host1 bad password => ERR_AUTHENTICATION_FAILURE_USER1' ) ;
7865
7866 }
7867 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007868
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007869 note( 'Leaving tests_login_imap()' ) ;
7870 return ;
7871}
7872
7873sub oauthgenerateaccess
7874{
7875 if ( "petite" eq hostname() )
7876 {
7877 myprint( "oauthgenerateaccess\n" ) ;
7878 my @output = backtick( 'cd oauth2 && pwd && ./generate_gmail_token imapsync.gl0@gmail.com' ) ;
7879 myprint( @output ) ;
7880 }
7881 return ;
7882}
7883
7884sub tests_login_imap_oauth
7885{
7886 note( 'Entering tests_login_imap_oauth()' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007887
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007888 oauthgenerateaccess() ;
7889
7890 SKIP: {
7891 if ( skip_macosx_binary( ) )
7892 {
7893 skip( 'Tests avoided only on binary on host polarhome macosx, no clue "ssl3_get_server_certificate:certificate verify failed"', 6 ) ;
7894 }
7895 else
7896 {
7897
7898 my $mysync ;
7899 my $acc ;
7900 # oauthdirect authentication failure for user2
7901 $mysync = {} ;
7902 $acc = {} ;
7903 $acc->{ oauthdirect } = 'caca2' ;
7904 $acc->{ debugimap } = 1 ;
7905 $mysync->{ showpasswords } = 1 ;
7906 $acc->{ Side } = 'Host2' ;
7907 $acc->{ authmech } = 'QQQ' ;
7908 is(
7909 undef, login_imap( 'imap.gmail.com', 993, 'test1', 'Ce crétin',
7910 1, undef,
7911 1, 100, $acc, $mysync,
7912 ), 'login_imap: user2 bad oauthdirect => undef' ) ;
7913
7914 is( 'ERR_AUTHENTICATION_FAILURE_USER2', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host2 bad oauthdirect => ERR_AUTHENTICATION_FAILURE_USER2' ) ;
7915
7916 # oauthdirect authentication failure for user1
7917 $mysync = {} ;
7918 $acc = {} ;
7919 $acc->{ Side } = 'Host1' ;
7920 $acc->{ oauthdirect } = 'caca1' ;
7921 $acc->{ debugimap } = 1 ;
7922 $mysync->{ showpasswords } = 1 ;
7923 $acc->{ authmech } = 'QQQ' ;
7924 is(
7925 undef, login_imap( 'imap.gmail.com', 993, 'test1', 'Ce crétin',
7926 1, undef,
7927 1, 100, $acc, $mysync,
7928 ), 'login_imap: user1 bad oauthdirect => undef' ) ;
7929
7930 is( 'ERR_AUTHENTICATION_FAILURE_USER1', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host1 bad oauthdirect => ERR_AUTHENTICATION_FAILURE_USER1' ) ;
7931
7932 # oauthdirect authentication failure for user1
7933 $mysync = {} ;
7934 $acc = {} ;
7935 $acc->{ Side } = 'Host1' ;
7936 $acc->{ oauthdirect } = '' ;
7937 $acc->{ debugimap } = 1 ;
7938 $mysync->{ showpasswords } = 1 ;
7939 $acc->{ authmech } = 'QQQ' ;
7940 is(
7941 undef, login_imap( 'imap.gmail.com', 993, 'test1', 'Ce crétin',
7942 1, undef,
7943 1, 100, $acc, $mysync,
7944 ), 'login_imap: user1 bad oauthdirect => undef' ) ;
7945
7946 is( 'ERR_AUTHENTICATION_FAILURE_USER1', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host1 no oauthdirect value => ERR_AUTHENTICATION_FAILURE_USER1' ) ;
7947
7948 }
7949 }
7950
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007951 # oauthdirect authentication success for user1
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007952 SKIP: {
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007953 if ( ! -r 'oauth2/D_oauth2_oauthdirect_imapsync.gl0@gmail.com.txt' )
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007954 {
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007955 skip( 'oauthdirect: no oauthdirect file', 6 ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007956 }
7957 my $myimap ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007958 my $mysync = {} ;
7959 my $acc = {} ;
7960 $acc->{ Side } = 'Host1' ;
7961 $acc->{ oauthdirect } = 'oauth2/D_oauth2_oauthdirect_imapsync.gl0@gmail.com.txt' ;
7962 $acc->{ debugimap } = 1 ;
7963 $mysync->{ showpasswords } = 1 ;
7964 $acc->{ authmech } = 'QQQ' ;
7965 isa_ok(
7966 $myimap = login_imap( 'imap.gmail.com', 993, 'user_useless', 'password_useless',
7967 1, undef,
7968 1, 100, $acc, $mysync,
7969 ), 'Mail::IMAPClient', 'login_imap: user1 good oauthdirect => Mail::IMAPClient' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007970
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007971 ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthdirect IsAuthenticated' ) ;
7972
7973 ok( defined( $myimap ) && $myimap->logout( ), 'login_imap: gmail oauth2 oauthdirect logout' ) ;
7974 ok( defined( $myimap ) && ! $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthdirect not IsAuthenticated after logout' ) ;
7975 ok( defined( $myimap ) && $myimap->reconnect( ), 'login_imap: gmail oauth2 oauthdirect reconnect ok' ) ;
7976 ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthdirect IsAuthenticated after reconnect' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007977 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007978
7979
7980
7981 # oauthaccesstoken authentication success for user1
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007982 SKIP: {
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007983 if ( ! -r 'oauth2/D_oauth2_access_token_imapsync.gl0@gmail.com.txt' )
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007984 {
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007985 skip( 'oauthaccesstoken: no access_token file', 6 ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02007986 }
7987 my $myimap ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01007988 my $mysync = {} ;
7989 my $acc = {} ;
7990 $acc->{ Side } = 'Host1' ;
7991 $acc->{ oauthaccesstoken } = 'oauth2/D_oauth2_access_token_imapsync.gl0@gmail.com.txt' ;
7992 $acc->{ debugimap } = 1 ;
7993 $mysync->{ showpasswords } = 1 ;
7994 $acc->{ authmech } = 'QQQ' ;
7995 isa_ok(
7996 $myimap = login_imap( 'imap.gmail.com', 993, 'imapsync.gl0@gmail.com', 'password_useless',
7997 1, undef,
7998 1, 100, $acc, $mysync,
7999 ), 'Mail::IMAPClient', 'login_imap: user1 good oauthaccesstoken => Mail::IMAPClient' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008000
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008001 ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthaccesstoken IsAuthenticated' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008002
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008003 ok( defined( $myimap ) && $myimap->logout( ), 'login_imap: gmail oauth2 oauthaccesstoken logout' ) ;
8004 ok( defined( $myimap ) && ! $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthaccesstoken not IsAuthenticated after logout' ) ;
8005 ok( defined( $myimap ) && $myimap->reconnect( ), 'login_imap: gmail oauth2 oauthaccesstoken reconnect ok' ) ;
8006 ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthaccesstoken IsAuthenticated after reconnect' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008007 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008008
8009
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008010 note( 'Leaving tests_login_imap_oauth()' ) ;
8011 return ;
8012}
8013
8014
8015
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008016sub login_imap
8017{
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008018 my @allargs = @_ ;
8019 my(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008020 $host, $port, $user, $password,
8021 $ssl, $tls,
8022 $uid, $split, $acc, $mysync ) = @allargs ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008023
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008024 $acc->{ imap } = undef ;
8025
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008026 if ( ! all_defined( $host, $port, $user, $acc->{ Side } ) )
8027 {
8028 return ;
8029 }
8030
8031 my $side = lc $acc->{ Side } ;
8032 myprint( "$acc->{ Side }: connecting and login on $side [$host] port [$port] with user [$user]\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008033
8034 my $imap = init_imap( @allargs ) ;
8035
8036 if ( ! $imap->connect() )
8037 {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008038 my $error = "$acc->{ Side } failure: can not open imap connection on $side [$host] with user [$user]: "
8039 . $imap->LastError . " $OS_ERROR\n" ;
8040 errors_incr( $mysync, $error ) ;
8041 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008042 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008043 myprint( "$acc->{ Side } IP address: ", $imap->Socket->peerhost(), "\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008044 my $banner = $imap->Results()->[0] ;
8045
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008046 myprint( "$acc->{ Side } banner: $banner" ) ;
8047 myprint( "$acc->{ Side } capability before authentication: ", join(q{ }, @{ $imap->capability() || [] }), "\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008048
8049 if ( (! $ssl) and (! defined $tls ) and $imap->has_capability( 'STARTTLS' ) ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008050 myprint( "$acc->{ Side }: going to ssl because STARTTLS is in CAPABILITY. Use --notls1 or --notls2 to avoid that behavior\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008051 $tls = 1 ;
8052 }
8053
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008054
8055 #myprint( Data::Dumper->Dump( [ @allargs ] ) ) ;
8056 if ( $tls ) {
8057 set_tls( $imap, $acc ) ;
8058
8059 if ( ! $imap->starttls( ) )
8060 {
8061 my $error = "$acc->{ Side } failure: Can not go to tls encryption on $side [$host]: "
8062 . $imap->LastError . "\n" ;
8063
8064 errors_incr( $mysync, $error ) ;
8065 return ;
8066 }
8067 myprint( "$acc->{ Side }: Socket successfully converted to SSL\n" ) ;
8068 }
8069
8070 if ( $acc->{ authmech } eq 'PREAUTH' ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008071 if ( $imap->IsAuthenticated( ) ) {
8072 $imap->Socket ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008073 myprintf("%s: Assuming PREAUTH for %s\n", $acc->{ Side }, $imap->Server ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008074 }else{
8075 $mysync->{nb_errors}++ ;
8076 exit_clean(
8077 $mysync, $EXIT_AUTHENTICATION_FAILURE,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008078 "$acc->{ Side } failure: error login on $side [$host] with user [$user] auth [PREAUTH]\n"
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008079 ) ;
8080 }
8081 }
8082
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008083
8084
8085 if ( authenticate_imap( $imap, @allargs ) )
8086 {
8087 myprint( "$acc->{ Side }: success login on [$host] with user [$user] auth [$acc->{ authmech }] or [LOGIN]\n" ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008088 $acc->{ imap } = $imap ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008089 return( $imap ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008090 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008091 else
8092 {
8093 # The errors are already printed
8094 myprint( "$acc->{ Side }: failed login on [$host] with user [$user] auth [$acc->{ authmech }]\n" ) ;
8095 return ;
8096 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008097}
8098
8099
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008100
8101sub init_imap
8102{
8103 my(
8104 $host, $port, $user, $password,
8105 $ssl, $tls,
8106 $uid, $split, $acc, $mysync ) = @_ ;
8107
8108 my ( $imap ) ;
8109
8110 $imap = Mail::IMAPClient->new() ;
8111
8112 if ( $mysync->{ tee } )
8113 {
8114 # Well, it does not change anything, does it?
8115 # It does when suppressing the hack with *STDERR
8116 $imap->Debug_fh( $mysync->{ tee } ) ;
8117 }
8118
8119 if ( $ssl ) { set_ssl( $imap, $acc ) }
8120 if ( $tls ) { } # can not do set_tls() here because connect() will directly do a STARTTLS
8121 $imap->Clear( 1 ) ;
8122 $imap->Server( $host ) ;
8123 $imap->Port( $port ) ;
8124 $imap->Fast_io( $acc->{ fastio } ) ;
8125 $imap->Buffer( $buffersize || $DEFAULT_BUFFER_SIZE ) ;
8126 $imap->Uid( $uid ) ;
8127
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008128 $imap->Peek( 1 ) ;
8129 $imap->Debug( $acc->{ debugimap } ) ;
8130 if ( $mysync->{ showpasswords } ) {
8131 $imap->Showcredentials( 1 ) ;
8132 }
8133
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008134 if ( defined( $acc->{ timeout } ) )
8135 {
8136 $imap->Timeout( $acc->{ timeout } ) ;
8137 }
8138
8139 if ( defined $acc->{ keepalive } )
8140 {
8141 $imap->Keepalive( $acc->{ keepalive } ) ;
8142 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008143
8144 if ( defined $acc->{ reconnectretry } )
8145 {
8146 $imap->Reconnectretry( $acc->{ reconnectretry } ) ;
8147 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008148
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008149 $imap->{IMAPSYNC_RECONNECT_COUNT} = 0 ;
8150 $imap->Ignoresizeerrors( $allowsizemismatch ) ;
8151 $split and $imap->Maxcommandlength( $SPLIT_FACTOR * $split ) ;
8152
8153
8154 return( $imap ) ;
8155
8156}
8157
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008158sub authenticate_imap
8159{
8160 my( $imap,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008161 $host, $port, $user, $password,
8162 $ssl, $tls,
8163 $uid, $split, $acc, $mysync ) = @_ ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008164
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008165 check_capability( $imap, $acc->{ authmech }, $acc->{ Side } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008166 $imap->User( $user ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008167
8168 if ( defined $acc->{ domain } )
8169 {
8170 $imap->Domain( $acc->{ domain } ) ;
8171 $mysync->{ debug } and myprint( "Domain: $acc->{ domain }\n" ) ;
8172 }
8173
8174 $imap->Authuser( $acc->{ authuser } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008175 $imap->Password( $password ) ;
8176
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008177 if ( 'X-MASTERAUTH' eq $acc->{ authmech } )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008178 {
8179 xmasterauth( $imap ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008180 return 1 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008181 }
8182
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008183
8184 if ( defined $acc->{ oauthdirect } )
8185 {
8186 $acc->{ authmech } = 'XOAUTH2 direct' ;
8187 return( oauthdirect( $mysync, $acc, $imap, $host, $user ) ) ;
8188 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008189
8190
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008191 if ( defined $acc->{ oauthaccesstoken } )
8192 {
8193 $acc->{ authmech } = 'XOAUTH2 accesstoken' ;
8194 return( oauthaccesstoken( $mysync, $acc, $imap, $host, $user ) ) ;
8195 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008196
8197
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008198
8199
8200 if ( $acc->{ proxyauth } ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008201 $imap->Authmechanism(q{}) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008202 $imap->User( $acc->{ authuser } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008203 } else {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008204 $imap->Authmechanism( $acc->{ authmech } ) unless ( $acc->{ authmech } eq 'LOGIN' or $acc->{ authmech } eq 'PREAUTH' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008205 }
8206
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008207 $imap->Authcallback(\&xoauth2) if ( 'XOAUTH2' eq $acc->{ authmech } ) ;
8208 $imap->Authcallback(\&plainauth) if ( ( 'PLAIN' eq $acc->{ authmech } ) or ( 'EXTERNAL' eq $acc->{ authmech } ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008209
8210
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008211 unless ( $acc->{ authmech } eq 'PREAUTH' or $imap->login( ) ) {
8212 my $info = "$acc->{ Side } failure: Error login on [$host] with user [$user] auth" ;
8213 my $einfo = imap_last_error( $imap ) ;
8214 my $error = "$info [$acc->{ authmech }]: $einfo\n" ;
8215
8216
8217 if ( ( $acc->{ authmech } eq 'LOGIN' ) or $imap->IsUnconnected( ) or $acc->{ authuser } ) {
8218 $acc->{ authuser } ||= "" ;
8219 myprint( "$acc->{ Side } info: authmech [$acc->{ authmech }] user [$user] authuser [$acc->{ authuser }] IsUnconnected [", $imap->IsUnconnected( ), "]\n" ) ;
8220 errors_incr( $mysync, $error ) ;
8221 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008222 }else{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008223 errors_incr( $mysync, $error ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008224 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008225
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008226 # It is not secure to try plain text LOGIN when another authmech failed
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008227 # but I do it anyway. This behavior is optional as option --notrylogin will skip it.
8228 if ( $mysync->{ trylogin } )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008229 {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008230 myprint( "$acc->{ Side } info: trying LOGIN Auth mechanism on [$host] with user [$user]. Use option --notrylogin to avoid this second chance to login via LOGIN auth\n" ) ;
8231 $imap->Authmechanism(q{}) ;
8232 if ( ! $imap->login( ) )
8233 {
8234 failure_login( $mysync, $acc, 'LOGIN', $imap, $host, $user ) ;
8235 return ;
8236 }
8237 else
8238 {
8239 myprint( "$acc->{ Side }: success login on [$host] with user [$user] auth [LOGIN] after [$acc->{ authmech }] failure\n" ) ;
8240 }
8241 }
8242 else
8243 {
8244 myprint( "$acc->{ Side } info: not trying LOGIN Auth mechanism on [$host] with user [$user]. Use option --trylogin to have this second chance to login via LOGIN auth\n" ) ;
8245 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008246 }
8247 }
8248
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008249 if ( $acc->{ proxyauth } ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008250 if ( ! $imap->proxyauth( $user ) ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008251 failure_proxyauth( $mysync, $acc, $acc->{ authmech }, $imap, $host, $user ) ;
8252 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008253 }
8254 }
8255
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008256 return 1;
8257}
8258
8259
8260sub failure_login
8261{
8262 my( $mysync, $acc, $authmech, $imap, $host, $user ) = @ARG ;
8263 my $info = "$acc->{ Side } failure: Error login on [$host] with user [$user] auth" ;
8264 my $einfo = imap_last_error( $imap ) ;
8265 my $error = "$info [$authmech]: $einfo\n" ;
8266 errors_incr( $mysync, $error ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008267 return ;
8268}
8269
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008270# failure_login and failure_proxyauth function are similar but
8271# variable $error so no factoring
8272sub failure_proxyauth
8273{
8274 my( $mysync, $acc, $authmech, $imap, $host, $user ) = @ARG ;
8275 my $info = "$acc->{ Side } failure: Error login on [$host] with user [$user] auth" ;
8276 my $einfo = imap_last_error( $imap ) ;
8277 my $error = "$info [$authmech] using proxy-login as [$acc->{ authuser }]: $einfo\n" ;
8278 errors_incr( $mysync, $error ) ;
8279 return ;
8280}
8281
8282
8283
8284
8285sub oauthdirect
8286{
8287 my( $mysync, $acc, $imap, $host, $user ) = @_ ;
8288
8289 my $oauthdirect_str ;
8290 if ( -f -r $acc->{ oauthdirect } )
8291 {
8292 $oauthdirect_str = firstline( $acc->{ oauthdirect } ) ;
8293 }
8294 else
8295 {
8296 $oauthdirect_str = $acc->{ oauthdirect } || 'Please define oauthdirect value' ;
8297 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008298
8299 $imap->Authmechanism( 'XOAUTH2' ) ;
8300 $imap->Authcallback( sub { return $oauthdirect_str } ) ;
8301
8302 #if ( $imap->authenticate('XOAUTH2', sub { return $oauthdirect_str } ) )
8303 if ( $imap->login( ) )
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008304 {
8305 return 1 ;
8306 }
8307 else
8308 {
8309 failure_login( $mysync, $acc, $acc->{ authmech }, $imap, $host, $user ) ;
8310 return ;
8311 }
8312 return ;
8313}
8314
8315
8316
8317
8318sub oauthaccesstoken
8319{
8320 my( $mysync, $acc, $imap, $host, $user ) = @_ ;
8321
8322 my $oauthaccesstoken_str ;
8323 if ( -f -r $acc->{ oauthaccesstoken } )
8324 {
8325 $oauthaccesstoken_str = firstline( $acc->{ oauthaccesstoken } ) ;
8326 }
8327 else
8328 {
8329 $oauthaccesstoken_str = $acc->{ oauthaccesstoken } || 'Please define oauthaccesstoken value' ;
8330 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008331
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008332 my $oauth_string = "user=" . $user . "\x01auth=Bearer ". $oauthaccesstoken_str . "\x01\x01" ;
8333 #myprint "oauth_string: $oauth_string\n" ;
8334
8335 my $oauth_string_base64 = encode_base64( $oauth_string , '' ) ;
8336 #myprint "oauth_string_base64: $oauth_string_base64\n" ;
8337
8338 my $oauthdirect_str = $oauth_string_base64 ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008339
8340 $imap->Authmechanism( 'XOAUTH2' ) ;
8341 $imap->Authcallback( sub { return $oauthdirect_str } ) ;
8342
8343 #if ( $imap->authenticate('XOAUTH2', sub { return $oauthdirect_str } ) )
8344 if ( $imap->login( ) )
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008345 {
8346 return 1 ;
8347 }
8348 else
8349 {
8350 failure_login( $mysync, $acc, $acc->{ authmech }, $imap, $host, $user ) ;
8351 return ;
8352 }
8353 return ;
8354}
8355
8356
8357
8358
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008359sub check_capability
8360{
8361
8362 my( $imap, $authmech, $Side ) = @_ ;
8363
8364
8365 if ( $imap->has_capability( "AUTH=$authmech" )
8366 or $imap->has_capability( $authmech ) )
8367 {
8368 myprintf("%s: %s says it has CAPABILITY for AUTHENTICATE %s\n",
8369 $Side, $imap->Server, $authmech) ;
8370 return ;
8371 }
8372
8373 if ( $authmech eq 'LOGIN' )
8374 {
8375 # Well, the warning is so common and useless that I prefer to remove it
8376 # No more "... says it has NO CAPABILITY for AUTHENTICATE LOGIN"
8377 return ;
8378 }
8379
8380
8381 myprintf( "%s: %s says it has NO CAPABILITY for AUTHENTICATE %s\n",
8382 $Side, $imap->Server, $authmech ) ;
8383
8384 if ( $authmech eq 'PLAIN' )
8385 {
8386 myprint( "$Side: frequently PLAIN is only supported with SSL, try --ssl or --tls options\n" ) ;
8387 }
8388
8389 return ;
8390}
8391
8392sub set_ssl
8393{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008394 my ( $imap, $acc ) = @_ ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008395 # SSL_version can be
8396 # SSLv3 SSLv2 SSLv23 SSLv23:!SSLv2 (last one is the default in IO-Socket-SSL-1.953)
8397 #
8398
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008399 my $sslargs_hash = $acc->{sslargs} ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008400
8401 my $sslargs_default = {
8402 SSL_verify_mode => $SSL_VERIFY_POLICY,
8403 SSL_verifycn_scheme => 'imap',
8404 SSL_cipher_list => 'DEFAULT:!DH',
8405 } ;
8406
8407 # initiate with default values
8408 my %sslargs_mix = %{ $sslargs_default } ;
8409 # now override with passed values
8410 @sslargs_mix{ keys %{ $sslargs_hash } } = values %{ $sslargs_hash } ;
8411 # remove keys with undef values
8412 foreach my $key ( keys %sslargs_mix ) {
8413 delete $sslargs_mix{ $key } if ( not defined $sslargs_mix{ $key } ) ;
8414 }
8415 # back to an ARRAY
8416 my @sslargs_mix = %sslargs_mix ;
8417 #myprint( Data::Dumper->Dump( [ $sslargs_hash, $sslargs_default, \%sslargs_mix, \@sslargs_mix ] ) ) ;
8418 $imap->Ssl( \@sslargs_mix ) ;
8419 return ;
8420}
8421
8422sub set_tls
8423{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008424 my ( $imap, $acc ) = @_ ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008425
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008426 my $sslargs_hash = $acc->{sslargs} ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008427
8428 my $sslargs_default = {
8429 SSL_verify_mode => $SSL_VERIFY_POLICY,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008430 SSL_cipher_list => 'DEFAULT:!DH',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008431 } ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008432 #myprint( Data::Dumper->Dump( [ $acc, $sslargs_hash, $sslargs_default ] ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008433
8434 # initiate with default values
8435 my %sslargs_mix = %{ $sslargs_default } ;
8436 # now override with passed values
8437 @sslargs_mix{ keys %{ $sslargs_hash } } = values %{ $sslargs_hash } ;
8438 # remove keys with undef values
8439 foreach my $key ( keys %sslargs_mix ) {
8440 delete $sslargs_mix{ $key } if ( not defined $sslargs_mix{ $key } ) ;
8441 }
8442 # back to an ARRAY
8443 my @sslargs_mix = %sslargs_mix ;
8444
8445 $imap->Starttls( \@sslargs_mix ) ;
8446 return ;
8447}
8448
8449
8450
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008451sub plainauth
8452{
8453 my $code = shift;
8454 my $imap = shift;
8455
8456 my $string = mysprintf("%s\x00%s\x00%s", $imap->User,
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008457 defined $imap->Authuser ? $imap->Authuser : "", $imap->Password);
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008458 return encode_base64("$string", q{});
8459}
8460
8461# Copy from https://github.com/imapsync/imapsync/pull/25/files
8462# Changes "use" pragmas to "require".
8463# The openssl system call shall be replaced by pure Perl and
8464# https://metacpan.org/pod/Crypt::OpenSSL::PKCS12
8465
8466# Now the Joaquin Lopez code:
8467#
8468# Used this as an example: https://gist.github.com/gsainio/6322375
8469#
8470# And this as a reference: https://developers.google.com/accounts/docs/OAuth2ServiceAccount
8471# (note there is an http/rest tab, where the real info is hidden away... went on a witch hunt
8472# until I noticed that...)
8473#
8474# This is targeted at gmail to maintain compatibility after google's oauth1 service is deactivated
8475# on May 5th, 2015: https://developers.google.com/gmail/oauth_protocol
8476# If there are other oauth2 implementations out there, this would need to be modified to be
8477# compatible
8478#
8479# This is a good guide on setting up the google api/apps side of the equation:
8480# http://www.limilabs.com/blog/oauth2-gmail-imap-service-account
8481#
8482# 2016/05/27: Updated to support oauth/key data in the .json files Google now defaults to
8483# when creating gmail service accounts. They're easier to work with since they neither
8484# requiring decrypting nor specifying the oauth2 client id separately.
8485#
8486# If the password arg ends in .json, it will assume this new json method, otherwise it
8487# will fallback to the "oauth client id;.p12" format it was previously using.
8488sub xoauth2
8489{
8490 require JSON::WebToken ;
8491 require LWP::UserAgent ;
8492 require HTML::Entities ;
8493 require JSON ;
8494 require JSON::WebToken::Crypt::RSA ;
Matthias Andreas Benkardd1f5b682023-11-18 13:18:30 +01008495 require Crypt::OpenSSL::PKCS12;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008496 require Crypt::OpenSSL::RSA ;
8497 require Encode::Byte ;
8498 require IO::Socket::SSL ;
8499
8500 my $code = shift;
8501 my $imap = shift;
8502
8503 my ($iss,$key);
8504
8505 if( $imap->Password =~ /^(.*\.json)$/x )
8506 {
8507 my $json = JSON->new( ) ;
8508 my $filename = $1;
8509 $sync->{ debug } and myprint( "XOAUTH2 json file: $filename\n" ) ;
8510 my $FILE ;
8511 if ( ! open( $FILE, '<', $filename ) )
8512 {
8513 $sync->{nb_errors}++ ;
8514 exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE,
8515 "error [$filename]: $OS_ERROR\n"
8516 ) ;
8517 }
8518 my $jsonfile = $json->decode( join q{}, <$FILE> ) ;
8519 close $FILE ;
8520
8521 $iss = $jsonfile->{client_id};
8522 $key = $jsonfile->{private_key};
8523 $sync->{ debug } and myprint( "Service account: $iss\n");
8524 $sync->{ debug } and myprint( "Private key:\n$key\n");
8525 }
8526 else
8527 {
8528 # Get iss (service account address), keyfile name, and keypassword if necessary
8529 ( $iss, my $keyfile, my $keypass ) = $imap->Password =~ /([\-\d\w\@\.]+);([a-zA-Z0-9 \_\-\.\/]+);?(.*)?/x ;
8530
8531 # Assume key password is google default if not provided
8532 $keypass = 'notasecret' if not $keypass;
8533
8534 $sync->{ debug } and myprint( "Service account: $iss\nKey file: $keyfile\nKey password: $keypass\n");
8535
Matthias Andreas Benkardd1f5b682023-11-18 13:18:30 +01008536 # Get private key from p12 file
8537 my $pkcs12 = Crypt::OpenSSL::PKCS12->new_from_file($keyfile);
8538 $key = $pkcs12->private_key($keypass);
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008539
8540 $sync->{ debug } and myprint( "Private key:\n$key\n");
8541 }
8542
8543 # Create jwt of oauth2 request
8544 my $time = time ;
8545 my $jwt = JSON::WebToken->encode( {
8546 'iss' => $iss, # service account
8547 'scope' => 'https://mail.google.com/',
8548 'aud' => 'https://www.googleapis.com/oauth2/v3/token',
8549 'exp' => $time + $DEFAULT_EXPIRATION_TIME_OAUTH2_PK12,
8550 'iat' => $time,
8551 'prn' => $imap->User # user to auth as
8552 },
8553 $key, 'RS256', {'typ' => 'JWT'} ); # Crypt::OpenSSL::RSA needed here.
8554
8555 # Post oauth2 request
8556 my $ua = LWP::UserAgent->new( ) ;
8557 $ua->env_proxy( ) ;
8558
8559 my $response = $ua->post('https://www.googleapis.com/oauth2/v3/token',
8560 { grant_type => HTML::Entities::encode_entities('urn:ietf:params:oauth:grant-type:jwt-bearer'),
8561 assertion => $jwt } ) ;
8562
8563 unless( $response->is_success( ) ) {
8564 $sync->{nb_errors}++ ;
8565 exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE,
8566 $response->code, "\n", $response->content, "\n"
8567 ) ;
8568 }else{
8569 $sync->{ debug } and myprint( $response->content ) ;
8570 }
8571
8572 # access_token in response is what we need
8573 my $data = JSON::decode_json( $response->content ) ;
8574
8575 # format as oauth2 auth data
8576 my $xoauth2_string = encode_base64( 'user=' . $imap->User . "\1auth=Bearer " . $data->{access_token} . "\1\1", q{} ) ;
8577
8578 $sync->{ debug } and myprint( "XOAUTH2 String: $xoauth2_string\n");
8579 return($xoauth2_string);
8580}
8581
8582
8583
8584
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008585sub xmasterauth
8586{
8587 # This is Kerio auth admin
8588 # This code comes from
8589 # https://github.com/imapsync/imapsync/pull/53/files
8590
8591 my $imap = shift ;
8592
8593 my $user = $imap->User( ) ;
8594 my $password = $imap->Password( ) ;
8595 my $authmech = 'X-MASTERAUTH' ;
8596
8597 my @challenge = $imap->tag_and_run( $authmech, "+" ) ;
8598 if ( not defined $challenge[0] )
8599 {
8600 $sync->{nb_errors}++ ;
8601 exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE,
8602 "Failure authenticate with $authmech: ",
8603 $imap->LastError, "\n"
8604 ) ;
8605 return ; # hahaha!
8606 }
8607 $sync->{ debug } and myprint( "X-MASTERAUTH challenge: [@challenge]\n" ) ;
8608
8609 $challenge[1] =~ s/^\+ |^\s+|\s+$//g ;
8610 if ( ! $imap->_imap_command( { addcrlf => 1, addtag => 0, tag => $imap->Count }, md5_hex( $challenge[1] . $password ) ) )
8611 {
8612 $sync->{nb_errors}++ ;
8613 exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE,
8614 "Failure authenticate with $authmech: ",
8615 $imap->LastError, "\n"
8616 ) ;
8617 }
8618
8619 if ( ! $imap->tag_and_run( 'X-SETUSER ' . $user ) )
8620 {
8621 $sync->{nb_errors}++ ;
8622 exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE,
8623 "Failure authenticate with $authmech: ",
8624 "X-SETUSER ", $imap->LastError, "\n"
8625 ) ;
8626 }
8627
8628 $imap->State( Mail::IMAPClient::Authenticated ) ;
8629 # I comment this state because "Selected" state is usually done by SELECT or EXAMINE imap commands
8630 # $imap->State( Mail::IMAPClient::Selected ) ;
8631
8632 return ;
8633}
8634
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008635sub keepalive1
8636{
8637 my $mysync = shift ;
8638
8639 $mysync->{ acc1 }->{ keepalive } = defined $mysync->{ acc1 }->{ keepalive } ? $mysync->{ acc1 }->{ keepalive } : 1 ;
8640
8641 if ( $mysync->{ acc1 }->{ keepalive } )
8642 {
8643 myprint( "Host1: imap connection keepalive is on on host1. Use --nokeepalive1 to disable it.\n" ) ;
8644 }
8645 else
8646 {
8647 myprint( "Host1: imap connection keepalive is off on host1. Use --keepalive1 to enable it.\n" ) ;
8648 }
8649}
8650
8651sub keepalive2
8652{
8653 my $mysync = shift ;
8654
8655 $mysync->{ acc2 }->{ keepalive } = defined $mysync->{ acc2 }->{ keepalive } ? $mysync->{ acc2 }->{ keepalive } : 1 ;
8656
8657 if ( $mysync->{ acc2 }->{ keepalive } )
8658 {
8659 myprint( "Host2: imap connection keepalive is on on host2. Use --nokeepalive2 to disable it.\n" ) ;
8660 }
8661 else
8662 {
8663 myprint( "Host2: imap connection keepalive is off on host2. Use --keepalive2 to enable it.\n" ) ;
8664 }
8665}
8666
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008667
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008668
8669sub banner_imapsync
8670{
8671 my $mysync = shift @ARG ;
8672 my @argv = @ARG ;
8673
8674 my $banner_imapsync = join q{},
8675 q{$RCSfile: imapsync,v $ },
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008676 q{$Revision: 2.178 $ },
8677 q{$Date: 2022/01/12 21:28:37 $ },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008678 "\n",
8679 "Command line used, run by $EXECUTABLE_NAME:\n",
8680 "$PROGRAM_NAME ", command_line_nopassword( $mysync, @argv ), "\n" ;
8681
8682 return( $banner_imapsync ) ;
8683}
8684
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008685sub tests_do_valid_directory
8686{
8687 note( 'Entering tests_do_valid_directory()' ) ;
8688
8689 is( 1, do_valid_directory( '.'), 'do_valid_directory: . good' ) ;
8690 is( 1, do_valid_directory( './W/tmp/tests/valid/sub'), 'do_valid_directory: ./W/tmp/tests/valid/sub good' ) ;
8691
8692 Readonly my $NB_UNIX_tests_do_valid_directory_non_root => 2 ;
8693 diag( "OSNAME=$OSNAME EFFECTIVE_USER_ID=$EFFECTIVE_USER_ID" ) ;
8694
8695 SKIP: {
8696 skip( 'Tests only for non roor user', $NB_UNIX_tests_do_valid_directory_non_root ) if ( '0' eq $EFFECTIVE_USER_ID ) ;
8697 diag( 'The "Error / is not writable" is on purpose' ) ;
8698 ok( 0 == do_valid_directory( '/'), 'do_valid_directory: / bad' ) ;
8699 diag( 'The "Error permission denied" on /noway is on purpose' ) ;
8700 ok( 0 == do_valid_directory( '/noway'), 'do_valid_directory: /noway bad' ) ;
8701 }
8702
8703
8704 note( 'Leaving tests_do_valid_directory()' ) ;
8705 return ;
8706}
8707
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008708sub do_valid_directory
8709{
8710 my $dir = shift @ARG ;
8711
8712 # all good => return ok.
8713 return( 1 ) if ( -d $dir and -r _ and -w _ ) ;
8714
8715 # exist but bad
8716 if ( -e $dir and not -d _ ) {
8717 myprint( "Error: $dir exists but is not a directory\n" ) ;
8718 return( 0 ) ;
8719 }
8720 if ( -e $dir and not -w _ ) {
8721 my $sb = stat $dir ;
8722 myprintf( "Error: directory %s is not writable for user %s, permissions are %04o and owner is %s ( uid %s )\n",
8723 $dir, getpwuid_any_os( $EFFECTIVE_USER_ID ), ($sb->mode & oct($PERMISSION_FILTER) ), getpwuid_any_os( $sb->uid ), $sb->uid( ) ) ;
8724 return( 0 ) ;
8725 }
8726 # Trying to create it
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008727 myprint( "Creating directory $dir (current directory is " . getcwd( ) . ")\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008728 if ( ! eval { mkpath( $dir ) } ) {
8729 myprint( "$EVAL_ERROR" ) if ( $EVAL_ERROR ) ;
8730 }
8731 return( 1 ) if ( -d $dir and -r _ and -w _ ) ;
8732 return( 0 ) ;
8733}
8734
8735
8736sub tests_match_a_pid_number
8737{
8738 note( 'Entering tests_match_a_pid_number()' ) ;
8739
8740 is( undef, match_a_pid_number( ), 'match_a_pid_number: no args => undef' ) ;
8741 is( undef, match_a_pid_number( q{} ), 'match_a_pid_number: "" => undef' ) ;
8742 is( undef, match_a_pid_number( 'lalala' ), 'match_a_pid_number: lalala => undef' ) ;
8743 is( 1, match_a_pid_number( 1 ), 'match_a_pid_number: 1 => 1' ) ;
8744 is( 1, match_a_pid_number( 123 ), 'match_a_pid_number: 123 => 1' ) ;
8745 is( 1, match_a_pid_number( -123 ), 'match_a_pid_number: -123 => 1' ) ;
8746 is( 1, match_a_pid_number( '123' ), 'match_a_pid_number: "123" => 1' ) ;
8747 is( 1, match_a_pid_number( '-123' ), 'match_a_pid_number: "-123" => 1' ) ;
8748 is( undef, match_a_pid_number( 'a123' ), 'match_a_pid_number: a123 => undef' ) ;
8749 is( undef, match_a_pid_number( '-a123' ), 'match_a_pid_number: -a123 => undef' ) ;
8750 is( 1, match_a_pid_number( 99999 ), 'match_a_pid_number: 99999 => 1' ) ;
8751 is( 1, match_a_pid_number( -99999 ), 'match_a_pid_number: -99999 => 1' ) ;
8752 is( undef, match_a_pid_number( 0 ), 'match_a_pid_number: 0 => undef' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008753 is( 1, match_a_pid_number( 100000 ), 'match_a_pid_number: 100000 => 1' ) ;
8754 is( 1, match_a_pid_number( 123456 ), 'match_a_pid_number: 123456 => 1' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008755 is( undef, match_a_pid_number( '-0' ), 'match_a_pid_number: "-0" => undef' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008756 is( 1, match_a_pid_number( -100000 ), 'match_a_pid_number: -100000 => 1' ) ;
8757 is( 1, match_a_pid_number( -123456 ), 'match_a_pid_number: -123456 => 1' ) ;
8758 is( 1, match_a_pid_number( 2**22 ), 'match_a_pid_number: 2**22 => 1' ) ;
8759 is( undef, match_a_pid_number( 2**22 + 1 ), 'match_a_pid_number: 2**22 + 1 => undef' ) ;
8760 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 +01008761
8762 note( 'Leaving tests_match_a_pid_number()' ) ;
8763 return ;
8764}
8765
8766sub match_a_pid_number
8767{
8768 my $pid = shift @ARG ;
8769 if ( ! defined $pid ) { return ; }
8770 #print "$pid\n" ;
8771 if ( ! match( $pid, '^-?\d+$' ) ) { return ; }
8772 #print "$pid\n" ;
8773 # can be negative on Windows
8774 #if ( 0 > $pid ) { return ; }
8775 #if ( 65535 < $pid ) { return ; }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008776 if ( 2**22 < abs( $pid ) ) { return ; }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008777 if ( 0 == abs( $pid ) ) { return ; }
8778 return 1 ;
8779}
8780
8781sub tests_remove_pidfile_not_running
8782{
8783 note( 'Entering tests_remove_pidfile_not_running()' ) ;
8784
8785 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'remove_pidfile_not_running: mkpath W/tmp/tests/' ) ;
8786 is( undef, remove_pidfile_not_running( ), 'remove_pidfile_not_running: no args => undef' ) ;
8787 is( undef, remove_pidfile_not_running( './W' ), 'remove_pidfile_not_running: a dir => undef' ) ;
8788 is( undef, remove_pidfile_not_running( 'noexists' ), 'remove_pidfile_not_running: noexists => undef' ) ;
8789 is( 1, touch( 'W/tmp/tests/empty.pid' ), 'remove_pidfile_not_running: prepa empty W/tmp/tests/empty.pid' ) ;
8790 is( undef, remove_pidfile_not_running( 'W/tmp/tests/empty.pid' ), 'remove_pidfile_not_running: W/tmp/tests/empty.pid => undef' ) ;
8791 is( 'lalala', string_to_file( 'lalala', 'W/tmp/tests/lalala.pid' ), 'remove_pidfile_not_running: prepa W/tmp/tests/lalala.pid' ) ;
8792 is( undef, remove_pidfile_not_running( 'W/tmp/tests/lalala.pid' ), 'remove_pidfile_not_running: W/tmp/tests/lalala.pid => undef' ) ;
8793 is( '55555', string_to_file( '55555', 'W/tmp/tests/notrunning.pid' ), 'remove_pidfile_not_running: prepa W/tmp/tests/notrunning.pid' ) ;
8794 is( 1, remove_pidfile_not_running( 'W/tmp/tests/notrunning.pid' ), 'remove_pidfile_not_running: W/tmp/tests/notrunning.pid => 1' ) ;
8795 is( $PROCESS_ID, string_to_file( $PROCESS_ID, 'W/tmp/tests/running.pid' ), 'remove_pidfile_not_running: prepa W/tmp/tests/running.pid' ) ;
8796 is( undef, remove_pidfile_not_running( 'W/tmp/tests/running.pid' ), 'remove_pidfile_not_running: W/tmp/tests/running.pid => undef' ) ;
8797
8798 note( 'Leaving tests_remove_pidfile_not_running()' ) ;
8799 return ;
8800}
8801
8802sub remove_pidfile_not_running
8803{
8804 #
8805 my $pid_filename = shift @ARG ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008806
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008807 #myprint( "In remove_pidfile_not_running $pid_filename\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008808 if ( ! $pid_filename ) { myprint( "No variable pid_filename\n" ) ; return } ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008809 if ( ! -e $pid_filename )
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008810 {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008811 myprint( "File $pid_filename does not exist\n" ) ;
8812 return ;
8813 }
8814 #myprint( "Still In remove_pidfile_not_running $pid_filename\n" ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008815
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008816 if ( ! -f $pid_filename ) { myprint( "File $pid_filename is not a file\n" ) ; return } ;
8817
8818 my $pid = firstline( $pid_filename ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008819 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 +01008820 # can't kill myself => do nothing
8821 if ( ! kill 'ZERO', $PROCESS_ID ) { myprint( "Can not kill ZERO myself $PROCESS_ID\n" ) ; return } ;
8822
8823 # can't kill ZERO the pid => it is gone or own by another user => remove pidfile
8824 if ( ! kill 'ZERO', $pid ) {
8825 myprint( "Removing old $pid_filename since its PID $pid is not running anymore (oo-killed?)\n" ) ;
8826 if ( unlink $pid_filename ) {
8827 myprint( "Removed old $pid_filename\n" ) ;
8828 return 1 ;
8829 }else{
8830 myprint( "Could not remove old $pid_filename because $!\n" ) ;
8831 return ;
8832 }
8833 }
8834 myprint( "Another imapsync process $pid is running as says pidfile $pid_filename\n" ) ;
8835 return ;
8836}
8837
8838
8839sub tests_tail
8840{
8841 note( 'Entering tests_tail()' ) ;
8842
8843 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'tail: mkpath W/tmp/tests/' ) ;
8844 ok( ( ! -e 'W/tmp/tests/tail.pid' || unlink 'W/tmp/tests/tail.pid' ), 'tail: unlink W/tmp/tests/tail.pid' ) ;
8845 ok( ( ! -e 'W/tmp/tests/tail.txt' || unlink 'W/tmp/tests/tail.txt' ), 'tail: unlink W/tmp/tests/tail.txt' ) ;
8846
8847 is( undef, tail( ), 'tail: no args => undef' ) ;
8848 my $mysync ;
8849 is( undef, tail( $mysync ), 'tail: no pidfile => undef' ) ;
8850
8851 $mysync->{pidfile} = 'W/tmp/tests/tail.pid' ;
8852 is( undef, tail( $mysync ), 'tail: no pidfilelocking => undef' ) ;
8853
8854 $mysync->{pidfilelocking} = 1 ;
8855 is( undef, tail( $mysync ), 'tail: pidfile no exists => undef' ) ;
8856
8857
8858 my $pidandlog = "33333\nW/tmp/tests/tail.txt\n" ;
8859 is( $pidandlog, string_to_file( $pidandlog, $mysync->{pidfile} ), 'tail: put pid 33333 and tail.txt in pidfile' ) ;
8860 is( undef, tail( $mysync ), 'tail: logfile to tail no exists => undef' ) ;
8861
8862 my $tailcontent = "L1\nL2\nL3\nL4\nL5\n" ;
8863 is( $tailcontent, string_to_file( $tailcontent, 'W/tmp/tests/tail.txt' ),
8864 'tail: put L1\nL2\nL3\nL4\nL5\n in W/tmp/tests/tail.txt' ) ;
8865
8866 is( undef, tail( $mysync ), 'tail: fake pid in pidfile + tail off => 1' ) ;
8867
8868 $mysync->{ tail } = 1 ;
8869 is( 1, tail( $mysync ), 'tail: fake pid in pidfile + tail on=> 1' ) ;
8870
8871 # put my own pid, won't do tail
8872 $pidandlog = "$PROCESS_ID\nW/tmp/tests/tail.txt\n" ;
8873 is( $pidandlog, string_to_file( $pidandlog, $mysync->{pidfile} ), 'tail: put my own PID in pidfile' ) ;
8874 is( undef, tail( $mysync ), 'tail: my own pid in pidfile => undef' ) ;
8875
8876 note( 'Leaving tests_tail()' ) ;
8877 return ;
8878}
8879
8880
8881
8882sub tail
8883{
8884 # return undef on failures
8885 # return 1 on success
8886
8887 my $mysync = shift ;
8888
8889 # no tail when aborting!
8890 if ( $mysync->{ abort } ) { return ; }
8891
8892 my $pidfile = $mysync->{pidfile} ;
8893 my $lock = $mysync->{pidfilelocking} ;
8894 my $tail = $mysync->{tail} ;
8895
8896 if ( ! $pidfile ) { return ; }
8897 if ( ! $lock ) { return ; }
8898 if ( ! $tail ) { return ; }
8899
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02008900 if ( ! -e $pidfile ) { return ; }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008901
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01008902 my $pidtotail = firstline( $pidfile ) ;
8903 if ( ! $pidtotail ) { return ; }
8904
8905
8906
8907 # It should not happen but who knows...
8908 if ( $pidtotail eq $PROCESS_ID ) { return ; }
8909
8910
8911 my $filetotail = secondline( $pidfile ) ;
8912 if ( ! $filetotail ) { return ; }
8913
8914 if ( ! -r $filetotail )
8915 {
8916 #myprint( "Error: can not read $filetotail\n" ) ;
8917 return ;
8918 }
8919
8920 myprint( "Doing a tail -f on $filetotail for processus pid $pidtotail until it is finished.\n" ) ;
8921 my $file = File::Tail->new(
8922 name => $filetotail,
8923 nowait => 1,
8924 interval => 1,
8925 tail => 1,
8926 adjustafter => 2
8927 );
8928
8929 my $moretimes = 200 ;
8930 # print one line at least
8931 my $line = $file->read ;
8932 myprint( $line ) ;
8933 while ( isrunning( $pidtotail, \$moretimes ) and defined( $line = $file->read ) )
8934 {
8935 myprint( $line );
8936 sleep( 0.02 ) ;
8937 }
8938
8939 return 1 ;
8940}
8941
8942sub isrunning
8943{
8944 my $pidtocheck = shift ;
8945 my $moretimes_ref = shift ;
8946
8947 if ( kill 'ZERO', $pidtocheck )
8948 {
8949 #myprint( "$pidtocheck running\n" ) ;
8950 return 1 ;
8951 }
8952 elsif ( $$moretimes_ref >= 0 )
8953 {
8954 # continue to consider it running
8955 $$moretimes_ref-- ;
8956 return 1 ;
8957 }
8958 else
8959 {
8960 myprint( "Tailed processus $pidtocheck ended\n" ) ;
8961 return ;
8962 }
8963}
8964
8965sub tests_write_pidfile
8966{
8967 note( 'Entering tests_write_pidfile()' ) ;
8968
8969 my $mysync ;
8970
8971 is( 1, write_pidfile( ), 'write_pidfile: no args => 1' ) ;
8972
8973 # no pidfile => ok
8974 $mysync->{pidfile} = q{} ;
8975 is( 1, write_pidfile( $mysync ), 'write_pidfile: no pidfile => undef' ) ;
8976
8977 # The pidfile path is bad => failure
8978 $mysync->{pidfile} = '/no/no/no.pid' ;
8979 is( undef, write_pidfile( $mysync ), 'write_pidfile: no permission for /no/no/no.pid, no lock => undef' ) ;
8980
8981 $mysync->{pidfilelocking} = 1 ;
8982 is( undef, write_pidfile( $mysync ), 'write_pidfile: no permission for /no/no/no.pid + lock => undef' ) ;
8983
8984 $mysync->{pidfile} = 'W/tmp/tests/test.pid' ;
8985 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'write_pidfile: mkpath W/tmp/tests/' ) ;
8986 is( 1, touch( $mysync->{pidfile} ), 'write_pidfile: lock prepa' ) ;
8987
8988 $mysync->{pidfilelocking} = 0 ;
8989 is( 1, write_pidfile( $mysync ), 'write_pidfile: W/tmp/tests/test.pid + no lock => 1' ) ;
8990 is( $PROCESS_ID, firstline( 'W/tmp/tests/test.pid' ), "write_pidfile: W/tmp/tests/test.pid contains $PROCESS_ID" ) ;
8991 is( q{}, secondline( 'W/tmp/tests/test.pid' ), "write_pidfile: W/tmp/tests/test.pid contains no second line" ) ;
8992
8993 $mysync->{pidfilelocking} = 1 ;
8994 is( undef, write_pidfile( $mysync ), 'write_pidfile: W/tmp/tests/test.pid + lock => undef' ) ;
8995
8996
8997 $mysync->{pidfilelocking} = 0 ;
8998 $mysync->{ logfile } = 'rrrr.txt' ;
8999 is( 1, write_pidfile( $mysync ), 'write_pidfile: W/tmp/tests/test.pid + no lock + logfile => 1' ) ;
9000 is( $PROCESS_ID, firstline( 'W/tmp/tests/test.pid' ), "write_pidfile: + no lock + logfile W/tmp/tests/test.pid contains $PROCESS_ID" ) ;
9001 is( q{rrrr.txt}, secondline( 'W/tmp/tests/test.pid' ), "write_pidfile: + no lock + logfile W/tmp/tests/test.pid contains rrrr.txt" ) ;
9002
9003
9004 note( 'Leaving tests_write_pidfile()' ) ;
9005 return ;
9006}
9007
9008
9009
9010sub write_pidfile
9011{
9012 # returns undef if something is considered fatal
9013 # returns 1 otherwise
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01009014
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009015 #myprint( "In write_pidfile\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009016 if ( ! @ARG ) { return 1 ; }
9017
9018 my $mysync = shift @ARG ;
9019
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009020 # Do not write the pid file if the current process goal is to abort the process designed by the pid file
9021 if ( $mysync->{ abort } ) { return 1 ; }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009022
9023 #
9024 my $pid_filename = $mysync->{ pidfile } ;
9025 my $lock = $mysync->{ pidfilelocking } ;
9026
9027 if ( ! $pid_filename )
9028 {
9029 myprint( "PID file is unset ( to set it, use --pidfile filepath ; to avoid it use --pidfile \"\" )\n" ) ;
9030 return( 1 ) ;
9031 }
9032
9033 myprint( "PID file is $pid_filename ( to change it, use --pidfile filepath ; to avoid it use --pidfile \"\" )\n" ) ;
9034 if ( -e $pid_filename and $lock ) {
9035 myprint( "$pid_filename already exists, another imapsync may be curently running. Aborting imapsync.\n" ) ;
9036 return ;
9037
9038 }
9039
9040 if ( -e $pid_filename ) {
9041 myprint( "$pid_filename already exists, overwriting it ( use --pidfilelocking to avoid concurrent runs )\n" ) ;
9042 }
9043
9044 my $pid_string = "$PROCESS_ID\n" ;
9045 my $pid_message = "Writing my PID $PROCESS_ID in $pid_filename\n" ;
9046
9047 if ( $mysync->{ logfile } )
9048 {
9049 $pid_string .= "$mysync->{ logfile }\n" ;
9050 $pid_message .= "Writing also my logfile name in $pid_filename : $mysync->{ logfile }\n" ;
9051 }
9052
9053 if ( open my $FILE_HANDLE, '>', $pid_filename ) {
9054 myprint( $pid_message ) ;
9055 print $FILE_HANDLE $pid_string ;
9056 close $FILE_HANDLE ;
9057 return( 1 ) ;
9058 }
9059 else
9060 {
9061 myprint( "Could not open $pid_filename for writing. Check permissions or disk space: $OS_ERROR\n" ) ;
9062 return ;
9063 }
9064}
9065
9066
9067sub fix_Inbox_INBOX_mapping
9068{
9069 my( $h1_all, $h2_all ) = @_ ;
9070
9071 my $regex = q{} ;
9072 SWITCH: {
9073 if ( exists $h1_all->{INBOX} and exists $h2_all->{INBOX} ) { $regex = q{} ; last SWITCH ; } ;
9074 if ( exists $h1_all->{Inbox} and exists $h2_all->{Inbox} ) { $regex = q{} ; last SWITCH ; } ;
9075 if ( exists $h1_all->{INBOX} and exists $h2_all->{Inbox} ) { $regex = q{s/^INBOX$/Inbox/x} ; last SWITCH ; } ;
9076 if ( exists $h1_all->{Inbox} and exists $h2_all->{INBOX} ) { $regex = q{s/^Inbox$/INBOX/x} ; last SWITCH ; } ;
9077 } ;
9078 return( $regex ) ;
9079}
9080
9081sub tests_fix_Inbox_INBOX_mapping
9082{
9083 note( 'Entering tests_fix_Inbox_INBOX_mapping()' ) ;
9084
9085
9086 my( $h1_all, $h2_all ) ;
9087
9088 $h1_all = { 'INBOX' => q{} } ;
9089 $h2_all = { 'INBOX' => q{} } ;
9090 ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX INBOX' ) ;
9091
9092 $h1_all = { 'Inbox' => q{} } ;
9093 $h2_all = { 'Inbox' => q{} } ;
9094 ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: Inbox Inbox' ) ;
9095
9096 $h1_all = { 'INBOX' => q{} } ;
9097 $h2_all = { 'Inbox' => q{} } ;
9098 ok( q{s/^INBOX$/Inbox/x} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX Inbox' ) ;
9099
9100 $h1_all = { 'Inbox' => q{} } ;
9101 $h2_all = { 'INBOX' => q{} } ;
9102 ok( q{s/^Inbox$/INBOX/x} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: Inbox INBOX' ) ;
9103
9104 $h1_all = { 'INBOX' => q{} } ;
9105 $h2_all = { 'rrrrr' => q{} } ;
9106 ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX rrrrrr' ) ;
9107
9108 $h1_all = { 'rrrrr' => q{} } ;
9109 $h2_all = { 'Inbox' => q{} } ;
9110 ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: rrrrr Inbox' ) ;
9111
9112 note( 'Leaving tests_fix_Inbox_INBOX_mapping()' ) ;
9113 return ;
9114}
9115
9116
9117sub jux_utf8_list
9118{
9119 my @s_inp = @_ ;
9120 my $s_out = q{} ;
9121 foreach my $s ( @s_inp ) {
9122 $s_out .= jux_utf8( $s ) . "\n" ;
9123 }
9124 return( $s_out ) ;
9125}
9126
9127sub tests_jux_utf8_list
9128{
9129 note( 'Entering tests_jux_utf8_list()' ) ;
9130
9131 use utf8 ;
9132 is( q{}, jux_utf8_list( ), 'jux_utf8_list: void' ) ;
9133 is( "[]\n", jux_utf8_list( q{} ), 'jux_utf8_list: empty string' ) ;
9134 is( "[INBOX]\n", jux_utf8_list( 'INBOX' ), 'jux_utf8_list: INBOX' ) ;
9135 is( "[&ANY-] = [Ö]\n", jux_utf8_list( '&ANY-' ), 'jux_utf8_list: [&ANY-] = [Ö]' ) ;
9136
9137 note( 'Leaving tests_jux_utf8_list()' ) ;
9138 return( 0 ) ;
9139}
9140
9141# editing utf8 can be tricky without an utf8 editor
9142sub tests_jux_utf8_old
9143{
9144 note( 'Entering tests_jux_utf8_old()' ) ;
9145
9146 no utf8 ;
9147
9148 is( '[]', jux_utf8_old( q{} ), 'jux_utf8_old: void => []' ) ;
9149 is( '[INBOX]', jux_utf8_old( 'INBOX'), 'jux_utf8_old: INBOX => [INBOX]' ) ;
9150 is( '[&ZTZO9nux-] = [收件箱]', jux_utf8_old( '&ZTZO9nux-'), 'jux_utf8_old: => [&ZTZO9nux-] = [收件箱]' ) ;
9151 is( '[&ANY-] = [Ö]', jux_utf8_old( '&ANY-'), 'jux_utf8_old: &ANY- => [&ANY-] = [Ö]' ) ;
9152 # +BD8EQAQ1BDQEOwQ+BDM- SHOULD stay as is!
9153 is( '[+BD8EQAQ1BDQEOwQ+BDM-] = [предлог]', jux_utf8_old( '+BD8EQAQ1BDQEOwQ+BDM-' ), 'jux_utf8_old: => [+BD8EQAQ1BDQEOwQ+BDM-] = [предлог]' ) ;
9154 is( '[&BB8EQAQ+BDUEOgRC-] = [Проект]', jux_utf8_old( '&BB8EQAQ+BDUEOgRC-' ), 'jux_utf8_old: => [&BB8EQAQ+BDUEOgRC-] = [Проект]' ) ;
9155
9156 note( 'Leaving tests_jux_utf8_old()' ) ;
9157 return ;
9158}
9159
9160sub jux_utf8_old
9161{
9162 # juxtapose utf8 at the right if different
9163 my ( $s_utf7 ) = shift ;
9164 my ( $s_utf8 ) = imap_utf7_decode_old( $s_utf7 ) ;
9165
9166 if ( $s_utf7 eq $s_utf8 ) {
9167 #myprint( "[$s_utf7]\n" ) ;
9168 return( "[$s_utf7]" ) ;
9169 }else{
9170 #myprint( "[$s_utf7] = [$s_utf8]\n" ) ;
9171 return( "[$s_utf7] = [$s_utf8]" ) ;
9172 }
9173}
9174
9175# Copied from http://cpansearch.perl.org/src/FABPOT/Unicode-IMAPUtf7-2.01/lib/Unicode/IMAPUtf7.pm
9176# and then fixed with
9177# https://rt.cpan.org/Public/Bug/Display.html?id=11172
9178sub imap_utf7_decode_old
9179{
9180 my ( $s ) = shift ;
9181
9182 # Algorithm
9183 # On remplace , par / dans les BASE 64 (, entre & et -)
9184 # On remplace les &, non suivi d'un - par +
9185 # On remplace les &- par &
9186 $s =~ s/&([^,&\-]*),([^,\-&]*)\-/&$1\/$2\-/xg ;
9187 $s =~ s/&(?!\-)/\+/xg ;
9188 $s =~ s/&\-/&/xg ;
9189 return( Unicode::String::utf7( $s )->utf8 ) ;
9190}
9191
9192
9193
9194
9195
9196sub tests_jux_utf8
9197{
9198 note( 'Entering tests_jux_utf8()' ) ;
9199 #no utf8 ;
9200 use utf8 ;
9201
9202 #binmode STDOUT, ":encoding(UTF-8)" ;
9203 binmode STDERR, ":encoding(UTF-8)" ;
9204
9205 # This test is because the binary can fail on it, a PAR.pm issue.
9206 # The failure was with the underlying Encode::IMAPUTF7 module line 66 release 1.05
9207 # Was solved by including Encode in imapsync and using "pp -x".
9208 ok( find_encoding( "UTF-16BE"), 'jux_utf8: Encode::find_encoding: UTF-16BE' ) ;
9209
9210 #
9211 is( '[]', jux_utf8( q{} ), 'jux_utf8: void => []' ) ;
9212 is( '[INBOX]', jux_utf8( 'INBOX'), 'jux_utf8: INBOX => [INBOX]' ) ;
9213 is( '[&ANY-] = [Ö]', jux_utf8( '&ANY-'), 'jux_utf8: &ANY- => [&ANY-] = [Ö]' ) ;
9214 # +BD8EQAQ1BDQEOwQ+BDM- must stay as is
9215 is( '[+BD8EQAQ1BDQEOwQ+BDM-]', jux_utf8( '+BD8EQAQ1BDQEOwQ+BDM-' ), 'jux_utf8: => [+BD8EQAQ1BDQEOwQ+BDM-] = [+BD8EQAQ1BDQEOwQ+BDM-]' ) ;
9216 is( '[&BB8EQAQ+BDUEOgRC-] = [Проект]', jux_utf8( '&BB8EQAQ+BDUEOgRC-' ), 'jux_utf8: => [&BB8EQAQ+BDUEOgRC-] = [Проект]' ) ;
9217
9218 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]' ) ;
9219 my $str = Encode::IMAPUTF7::encode("IMAP-UTF-7", 'Réponses 1200+1201+1202' ) ;
9220 is( '[R&AOk-ponses 1200+1201+1202] = [Réponses 1200+1201+1202]', jux_utf8( $str ), "jux_utf8: [$str] = [Réponses 1200+1201+1202]" ) ;
9221
9222 is( '[INBOX.&AOkA4ADnAPk-&-*] = [INBOX.éà çù&*]', jux_utf8( 'INBOX.&AOkA4ADnAPk-&-*' ), "jux_utf8: [INBOX.&AOkA4ADnAPk-&-*] = [INBOX.éà çù&*]" ) ;
9223
9224 is( '[&ZTZO9nux-] = [收件箱]', jux_utf8( '&ZTZO9nux-'), 'jux_utf8: => [&ZTZO9nux-] = [收件箱]' ) ;
9225 #
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009226 #
9227 is( '[!Old Emails]', jux_utf8( '!Old Emails'), 'jux_utf8: !Old Emails => [!Old Emails]' ) ;
9228 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 +01009229 note( 'Leaving tests_jux_utf8()' ) ;
9230 return ;
9231}
9232
9233sub jux_utf8
9234{
9235 #use utf8 ;
9236 # juxtapose utf8 at the right if different
9237 my ( $s_utf7 ) = shift ;
9238 my ( $s_utf8 ) = imap_utf7_decode( $s_utf7 ) ;
9239
9240 if ( $s_utf7 eq $s_utf8 ) {
9241 #myprint( "[$s_utf7]\n" ) ;
9242 return( "[$s_utf7]" ) ;
9243 }else{
9244 #myprint( "[$s_utf7] = [$s_utf8]\n" ) ;
9245 return( "[$s_utf7] = [$s_utf8]" ) ;
9246 }
9247}
9248
9249sub imap_utf7_decode
9250{
9251 #use utf8 ;
9252 my ( $s ) = shift ;
9253 return( Encode::IMAPUTF7::decode("IMAP-UTF-7", $s ) ) ;
9254}
9255
9256sub imap_utf7_encode
9257{
9258 #use utf8 ;
9259 my ( $s ) = shift ;
9260 return( Encode::IMAPUTF7::encode("IMAP-UTF-7", $s ) ) ;
9261}
9262
9263
9264
9265sub imap_utf7_encode_old
9266{
9267 my ( $s ) = @_ ;
9268
9269 $s = Unicode::String::utf8( $s )->utf7 ;
9270
9271 $s =~ s/\+([^\/&\-]*)\/([^\/\-&]*)\-/\+$1,$2\-/xg ;
9272 $s =~ s/&/&\-/xg ;
9273 $s =~ s/\+([^+\-]+)?\-/&$1\-/xg ;
9274 return( $s ) ;
9275}
9276
9277
9278
9279
9280sub select_folder
9281{
9282 my ( $mysync, $imap, $folder, $hostside ) = @_ ;
9283 if ( ! $imap->select( $folder ) ) {
9284 my $error = join q{},
9285 "$hostside folder $folder: Could not select: ",
9286 $imap->LastError, "\n" ;
9287 errors_incr( $mysync, $error ) ;
9288 return( 0 ) ;
9289 }else{
9290 # ok select succeeded
9291 return( 1 ) ;
9292 }
9293}
9294
9295sub examine_folder
9296{
9297 my ( $mysync, $imap, $folder, $hostside ) = @_ ;
9298 if ( ! $imap->examine( $folder ) ) {
9299 my $error = join q{},
9300 "$hostside folder $folder: Could not examine: ",
9301 $imap->LastError, "\n" ;
9302 errors_incr( $mysync, $error ) ;
9303 return( 0 ) ;
9304 }else{
9305 # ok select succeeded
9306 return( 1 ) ;
9307 }
9308}
9309
9310
9311sub count_from_select
9312{
9313 my @lines = @ARG ;
9314 my $count ;
9315 foreach my $line ( @lines ) {
9316 #myprint( "line = [$line]\n" ) ;
9317 if ( $line =~ m/^\*\s+(\d+)\s+EXISTS/x ) {
9318 $count = $1 ;
9319 return( $count ) ;
9320 }
9321 }
9322 return( undef ) ;
9323}
9324
9325
9326
9327sub create_folder_old
9328{
9329 my $mysync = shift @ARG ;
9330 my( $imap, $h2_fold, $h1_fold ) = @ARG ;
9331
9332 myprint( "Creating (old way) folder [$h2_fold] on host2\n" ) ;
9333 if ( ( 'INBOX' eq uc $h2_fold )
9334 and ( $imap->exists( $h2_fold ) ) ) {
9335 myprint( "Folder [$h2_fold] already exists\n" ) ;
9336 return( 1 ) ;
9337 }
9338 if ( ! $mysync->{dry} ){
9339 if ( ! $imap->create( $h2_fold ) ) {
9340 my $error = join q{},
9341 "Could not create folder [$h2_fold] from [$h1_fold]: ",
9342 $imap->LastError( ), "\n" ;
9343 errors_incr( $mysync, $error ) ;
9344 # success if folder exists ("already exists" error)
9345 return( 1 ) if $imap->exists( $h2_fold ) ;
9346 # failure since create failed
9347 return( 0 ) ;
9348 }else{
9349 #create succeeded
9350 myprint( "Created ( the old way ) folder [$h2_fold] on host2\n" ) ;
9351 return( 1 ) ;
9352 }
9353 }else{
9354 # dry mode, no folder so many imap will fail, assuming failure
9355 myprint( "Created ( the old way ) folder [$h2_fold] on host2 $mysync->{dry_message}\n" ) ;
9356 return( 0 ) ;
9357 }
9358}
9359
9360
9361sub create_folder
9362{
9363 my $mysync = shift @ARG ;
9364 my( $myimap2 , $h2_fold , $h1_fold ) = @ARG ;
9365 my( @parts , $parent ) ;
9366
9367 if ( $myimap2->IsUnconnected( ) ) {
9368 myprint( "Host2: Unconnected state\n" ) ;
9369 return( 0 ) ;
9370 }
9371
9372 if ( $create_folder_old ) {
9373 return( create_folder_old( $mysync, $myimap2 , $h2_fold , $h1_fold ) ) ;
9374 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009375
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01009376 # $imap->exists() calls $imap->status() that does an IMAP STATUS folder
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009377 myprint( "Creating folder [$h2_fold] on host2\n" ) ;
9378 if ( ( 'INBOX' eq uc $h2_fold )
9379 and ( $myimap2->exists( $h2_fold ) ) ) {
9380 myprint( "Folder [$h2_fold] already exists\n" ) ;
9381 return( 1 ) ;
9382 }
9383
9384 if ( $mixfolders and $myimap2->exists( $h2_fold ) ) {
9385 myprint( "Folder [$h2_fold] already exists (--nomixfolders is not set)\n" ) ;
9386 return( 1 ) ;
9387 }
9388
9389
9390 if ( ( not $mixfolders ) and ( $myimap2->exists( $h2_fold ) ) ) {
9391 myprint( "Folder [$h2_fold] already exists and --nomixfolders is set\n" ) ;
9392 return( 0 ) ;
9393 }
9394
9395 @parts = split /\Q$mysync->{ h2_sep }\E/x, $h2_fold ;
9396 pop @parts ;
9397 $parent = join $mysync->{ h2_sep }, @parts ;
9398 $parent =~ s/^\s+|\s+$//xg ;
9399 if ( ( $parent ne q{} ) and ( ! $myimap2->exists( $parent ) ) ) {
9400 create_folder( $mysync, $myimap2 , $parent , $h1_fold ) ;
9401 }
9402
9403 if ( ! $mysync->{dry} ) {
9404 if ( ! $myimap2->create( $h2_fold ) ) {
9405 my $error = join q{},
9406 "Could not create folder [$h2_fold] from [$h1_fold]: " ,
9407 $myimap2->LastError( ), "\n" ;
9408 errors_incr( $mysync, $error ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01009409 # success if folder exists ("already exists" error) or selectable
9410 if ( $myimap2->exists( $h2_fold ) or select_folder( $mysync, $myimap2, $h2_fold, 'Host2' ) )
9411 {
9412 return( 1 ) ;
9413 }
9414 # failure since create failed + not exist + not selectable
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009415 return( 0 ) ;
9416 }else{
9417 #create succeeded
9418 myprint( "Created folder [$h2_fold] on host2\n" ) ;
9419 return( 1 ) ;
9420 }
9421 }else{
9422 # dry mode, no folder so many imap will fail, assuming failure
9423 myprint( "Created folder [$h2_fold] on host2 $mysync->{dry_message}\n" ) ;
9424 if ( ! $mysync->{ justfolders } ) {
9425 myprint( "Since --dry mode is on and folder [$h2_fold] on host2 does not exist yet, syncing messages will not be simulated.\n"
9426 . "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 +02009427 # The messages that could be transferred are counted and the number is given at the end.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009428 }
9429 return( 0 ) ;
9430 }
9431}
9432
9433
9434
9435sub tests_folder_routines
9436{
9437 note( 'Entering tests_folder_routines()' ) ;
9438
9439 ok( !is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 1' );
9440 ok( add_to_requested_folders('folder_foo'), 'add_to_requested_folders folder_foo' );
9441 ok( is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 2' );
9442 ok( !is_requested_folder('folder_NO_EXIST'), 'is_requested_folder folder_NO_EXIST' );
9443
9444 is_deeply( [ 'folder_foo' ], [ remove_from_requested_folders( 'folder_foo' ) ], 'removed folder_foo => folder_foo' ) ;
9445 ok( !is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 3' );
9446 my @f ;
9447 ok( @f = add_to_requested_folders('folder_bar', 'folder_toto'), "add result: @f" );
9448 ok( is_requested_folder('folder_bar'), 'is_requested_folder 4' );
9449 ok( is_requested_folder('folder_toto'), 'is_requested_folder 5' );
9450 ok( remove_from_requested_folders('folder_toto'), 'remove_from_requested_folders: ' );
9451 ok( !is_requested_folder('folder_toto'), 'is_requested_folder 6' );
9452
9453 is_deeply( [ 'folder_bar' ], [ remove_from_requested_folders('folder_bar') ], 'remove_from_requested_folders: empty' ) ;
9454
9455 ok( 0 == compare_lists( [ sort_requested_folders( ) ], [] ), 'sort_requested_folders: all empty' ) ;
9456 ok( add_to_requested_folders( 'A_99', 'M_55', 'Z_11' ), 'add_to_requested_folders M_55 Z_11' );
9457 ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'A_99', 'M_55', 'Z_11' ] ), 'sort_requested_folders: middle' ) ;
9458
9459
9460 @folderfirst = ( 'Z_11' ) ;
9461
9462 ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'A_99', 'M_55' ] ), 'sort_requested_folders: first+middle' ) ;
9463
9464 is_deeply( [ 'Z_11', 'A_99', 'M_55' ], [ sort_requested_folders( ) ], 'sort_requested_folders: first+middle is_deeply' ) ;
9465
9466 @folderlast = ( 'A_99' ) ;
9467 ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'M_55', 'A_99' ] ), 'sort_requested_folders: first+middle+last 1' ) ;
9468
9469 ok( add_to_requested_folders('M_55', 'M_44',), 'add_to_requested_folders M_55 M_44' ) ;
9470
9471 ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'M_44', 'M_55', 'A_99'] ), 'sort_requested_folders: first+middle+last 2' ) ;
9472
9473
9474 ok( add_to_requested_folders('A_88', 'Z_22',), 'add_to_requested_folders A_88 Z_22' ) ;
9475 @folderfirst = qw( Z_22 Z_11 ) ;
9476 @folderlast = qw( A_99 A_88 ) ;
9477 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' ) ;
9478 undef @folderfirst ;
9479 undef @folderlast ;
9480
9481 note( 'Leaving tests_folder_routines()' ) ;
9482 return ;
9483}
9484
9485
9486sub sort_requested_folders
9487{
9488 my @requested_folders_sorted = () ;
9489
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009490 $sync->{ debug } and myprint "folderfirst: @folderfirst\n" ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009491 my @folderfirst_requested = remove_from_requested_folders( @folderfirst ) ;
9492 #myprint "folderfirst_requested: @folderfirst_requested\n" ;
9493
9494 my @folderlast_requested = remove_from_requested_folders( @folderlast ) ;
9495
9496 my @middle = sort keys %requested_folder ;
9497
9498 @requested_folders_sorted = ( @folderfirst_requested, @middle, @folderlast_requested ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009499 $sync->{ debug } and myprint "requested_folders_sorted: @requested_folders_sorted\n" ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009500 add_to_requested_folders( @requested_folders_sorted ) ;
9501
9502 return( @requested_folders_sorted ) ;
9503}
9504
9505sub is_requested_folder
9506{
9507 my ( $folder ) = @_;
9508
9509 return( defined $requested_folder{ $folder } ) ;
9510}
9511
9512
9513sub add_to_requested_folders
9514{
9515 my @wanted_folders = @_ ;
9516
9517 foreach my $folder ( @wanted_folders ) {
9518 ++$requested_folder{ $folder } ;
9519 }
9520 return( keys %requested_folder ) ;
9521}
9522
9523sub tests_remove_from_requested_folders
9524{
9525 note( 'Entering tests_remove_from_requested_folders()' ) ;
9526
9527 is( undef, undef, 'remove_from_requested_folders: undef is undef' ) ;
9528 is_deeply( [], [ remove_from_requested_folders( ) ], 'remove_from_requested_folders: no args' ) ;
9529 %requested_folder = (
9530 'F1' => 1,
9531 ) ;
9532 is_deeply( [], [ remove_from_requested_folders( ) ], 'remove_from_requested_folders: remove nothing among F1 => nothing' ) ;
9533 is_deeply( [], [ remove_from_requested_folders( 'Fno' ) ], 'remove_from_requested_folders: remove Fno among F1 => nothing' ) ;
9534 is_deeply( [ 'F1' ], [ remove_from_requested_folders( 'F1' ) ], 'remove_from_requested_folders: remove F1 among F1 => F1' ) ;
9535 is_deeply( { }, { %requested_folder }, 'remove_from_requested_folders: remove F1 among F1 => %requested_folder emptied' ) ;
9536
9537 %requested_folder = (
9538 'F1' => 1,
9539 'F2' => 1,
9540 ) ;
9541 is_deeply( [], [ remove_from_requested_folders( ) ], 'remove_from_requested_folders: remove nothing among F1 F2 => nothing' ) ;
9542 is_deeply( [], [ remove_from_requested_folders( 'Fno' ) ], 'remove_from_requested_folders: remove Fno among F1 F2 => nothing' ) ;
9543 is_deeply( [ 'F1' ], [ remove_from_requested_folders( 'F1' ) ], 'remove_from_requested_folders: remove F1 among F1 F2 => F1' ) ;
9544 is_deeply( { 'F2' => 1 }, { %requested_folder }, 'remove_from_requested_folders: remove F1 among F1 F2 => %requested_folder F2' ) ;
9545
9546 is_deeply( [], [ remove_from_requested_folders( 'F1' ) ], 'remove_from_requested_folders: remove F1 among F2 => nothing' ) ;
9547 is_deeply( [ 'F2' ], [ remove_from_requested_folders( 'F1', 'F2' ) ], 'remove_from_requested_folders: remove F1 F2 among F2 => F2' ) ;
9548 is_deeply( {}, { %requested_folder }, 'remove_from_requested_folders: remove F1 among F1 F2 => %requested_folder F2' ) ;
9549
9550 %requested_folder = (
9551 'F1' => 1,
9552 'F2' => 1,
9553 'F3' => 1,
9554 ) ;
9555 is_deeply( [ 'F1', 'F2' ], [ remove_from_requested_folders( 'F1', 'F2' ) ], 'remove_from_requested_folders: remove F1 F2 among F1 F2 F3 => F1 F2' ) ;
9556 is_deeply( { 'F3' => 1 }, { %requested_folder }, 'remove_from_requested_folders: remove F1 F2 among F1 F2 F3 => %requested_folder F3' ) ;
9557
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009558 undef %requested_folder ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009559
9560 note( 'Leaving tests_remove_from_requested_folders()' ) ;
9561 return ;
9562}
9563
9564
9565sub remove_from_requested_folders
9566{
9567 my @unwanted_folders = @_ ;
9568
9569 my @removed_folders = () ;
9570 foreach my $folder ( @unwanted_folders ) {
9571 if ( exists $requested_folder{ $folder } )
9572 {
9573 delete $requested_folder{ $folder } ;
9574 push @removed_folders, $folder ;
9575 }
9576 }
9577 return( @removed_folders ) ;
9578}
9579
9580sub compare_lists
9581{
9582 my ($list_1_ref, $list_2_ref) = @_;
9583
9584 return($MINUS_ONE) if ((not defined $list_1_ref) and defined $list_2_ref);
9585 return(0) if ((not defined $list_1_ref) and not defined $list_2_ref); # end if no list
9586 return(1) if (not defined $list_2_ref); # end if only one list
9587
9588 if (not ref $list_1_ref ) {$list_1_ref = [$list_1_ref]};
9589 if (not ref $list_2_ref ) {$list_2_ref = [$list_2_ref]};
9590
9591
9592 my $last_used_indice = $MINUS_ONE;
9593
9594
9595 ELEMENT:
9596 foreach my $indice ( 0 .. $#{ $list_1_ref } ) {
9597 $last_used_indice = $indice ;
9598
9599 # End of list_2
9600 return 1 if ($indice > $#{ $list_2_ref } ) ;
9601
9602 my $element_list_1 = $list_1_ref->[$indice] ;
9603 my $element_list_2 = $list_2_ref->[$indice] ;
9604 my $balance = $element_list_1 cmp $element_list_2 ;
9605 next ELEMENT if ($balance == 0) ;
9606 return $balance ;
9607 }
9608 # each element equal until last indice of list_1
9609 return $MINUS_ONE if ($last_used_indice < $#{ $list_2_ref } ) ;
9610
9611 # same size, each element equal
9612 return 0 ;
9613}
9614
9615sub tests_compare_lists
9616{
9617 note( 'Entering tests_compare_lists()' ) ;
9618
9619 my $empty_list_ref = [];
9620
9621 ok( 0 == compare_lists() , 'compare_lists, no args');
9622 ok( 0 == compare_lists(undef) , 'compare_lists, undef = nothing');
9623 ok( 0 == compare_lists(undef, undef) , 'compare_lists, undef = undef');
9624 ok($MINUS_ONE == compare_lists(undef , []) , 'compare_lists, undef < []');
9625 ok($MINUS_ONE == compare_lists(undef , [1]) , 'compare_lists, undef < [1]');
9626 ok($MINUS_ONE == compare_lists(undef , [0]) , 'compare_lists, undef < [0]');
9627 ok(+1 == compare_lists([]) , 'compare_lists, [] > nothing');
9628 ok(+1 == compare_lists([], undef) , 'compare_lists, [] > undef');
9629 ok( 0 == compare_lists([] , []) , 'compare_lists, [] = []');
9630
9631 ok($MINUS_ONE == compare_lists([] , [1]) , 'compare_lists, [] < [1]');
9632 ok(+1 == compare_lists([1] , []) , 'compare_lists, [1] > []');
9633
9634
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009635 ok( 0 == compare_lists( [1], 1 ) , 'compare_lists, [1] = 1 ') ;
9636 ok( 0 == compare_lists( 1 , [1] ) , 'compare_lists, 1 = [1]') ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009637 ok( 0 == compare_lists( 1 , 1 ) , 'compare_lists, 1 = 1 ') ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009638 ok( $MINUS_ONE == compare_lists( 0 , 1 ) , 'compare_lists, 0 < 1 ') ;
9639 ok( $MINUS_ONE == compare_lists( $MINUS_ONE , 0 ) , 'compare_lists, -1 < 0 ') ;
9640 ok( $MINUS_ONE == compare_lists( 1 , 2 ) , 'compare_lists, 1 < 2 ') ;
9641 ok( +1 == compare_lists( 2 , 1 ) , 'compare_lists, 2 > 1 ') ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009642
9643
9644 ok( 0 == compare_lists([1,2], [1,2]) , 'compare_lists, [1,2] = [1,2]' ) ;
9645 ok($MINUS_ONE == compare_lists([1], [1,2]) , 'compare_lists, [1] < [1,2]' ) ;
9646 ok(+1 == compare_lists([2], [1,2]) , 'compare_lists, [2] > [1,2]' ) ;
9647 ok($MINUS_ONE == compare_lists([1], [1,1]) , 'compare_lists, [1] < [1,1]' ) ;
9648 ok(+1 == compare_lists([1, 1], [1]) , 'compare_lists, [1, 1] > [1]' ) ;
9649 ok( 0 == compare_lists([1 .. $NUMBER_20_000] , [1 .. $NUMBER_20_000])
9650 , 'compare_lists, [1..20_000] = [1..20_000]' ) ;
9651 ok($MINUS_ONE == compare_lists([1], [2]) , 'compare_lists, [1] < [2]') ;
9652 ok( 0 == compare_lists([2], [2]) , 'compare_lists, [0] = [2]') ;
9653 ok(+1 == compare_lists([2], [1]) , 'compare_lists, [2] > [1]') ;
9654
9655 ok($MINUS_ONE == compare_lists(['a'], ['b']) , 'compare_lists, ["a"] < ["b"]') ;
9656 ok( 0 == compare_lists(['a'], ['a']) , 'compare_lists, ["a"] = ["a"]') ;
9657 ok( 0 == compare_lists(['ab'], ['ab']) , 'compare_lists, ["ab"] = ["ab"]') ;
9658 ok(+1 == compare_lists(['b'], ['a']) , 'compare_lists, ["b"] > ["a"]') ;
9659 ok($MINUS_ONE == compare_lists(['a'], ['aa']) , 'compare_lists, ["a"] < ["aa"]') ;
9660 ok($MINUS_ONE == compare_lists(['a'], ['a', 'a']), 'compare_lists, ["a"] < ["a", "a"]') ;
9661 ok( 0 == compare_lists([split q{ }, 'a b' ], ['a', 'b']), 'compare_lists, split') ;
9662 ok( 0 == compare_lists([sort split q{ }, 'b a' ], ['a', 'b']), 'compare_lists, sort split') ;
9663
9664 note( 'Leaving tests_compare_lists()' ) ;
9665 return ;
9666}
9667
9668
9669sub guess_prefix
9670{
9671 my @foldernames = @_ ;
9672
9673 my $prefix_guessed = q{} ;
9674 foreach my $folder ( @foldernames ) {
9675 next if ( $folder =~ m{^INBOX$}xi ) ; # no guessing from INBOX
9676 if ( $folder !~ m{^INBOX}xi ) {
9677 $prefix_guessed = q{} ; # prefix empty guessed
9678 last ;
9679 }
9680 if ( $folder =~ m{^(INBOX(?:\.|\/))}xi ) {
9681 $prefix_guessed = $1 ; # prefix Inbox/ or INBOX. guessed
9682 }
9683 }
9684 return( $prefix_guessed ) ;
9685}
9686
9687sub tests_guess_prefix
9688{
9689 note( 'Entering tests_guess_prefix()' ) ;
9690
9691 is( guess_prefix( ), q{}, 'guess_prefix: no args => empty string' ) ;
9692 is( q{} , guess_prefix( 'INBOX' ), 'guess_prefix: INBOX alone' ) ;
9693 is( q{} , guess_prefix( 'Inbox' ), 'guess_prefix: Inbox alone' ) ;
9694 is( q{} , guess_prefix( 'INBOX' ), 'guess_prefix: INBOX alone' ) ;
9695 is( 'INBOX/' , guess_prefix( 'INBOX', 'INBOX/Junk' ), 'guess_prefix: INBOX INBOX/Junk' ) ;
9696 is( 'INBOX.' , guess_prefix( 'INBOX', 'INBOX.Junk' ), 'guess_prefix: INBOX INBOX.Junk' ) ;
9697 is( 'Inbox/' , guess_prefix( 'Inbox', 'Inbox/Junk' ), 'guess_prefix: Inbox Inbox/Junk' ) ;
9698 is( 'Inbox.' , guess_prefix( 'Inbox', 'Inbox.Junk' ), 'guess_prefix: Inbox Inbox.Junk' ) ;
9699 is( 'INBOX/' , guess_prefix( 'INBOX', 'INBOX/Junk', 'INBOX/rrr' ), 'guess_prefix: INBOX INBOX/Junk INBOX/rrr' ) ;
9700 is( q{} , guess_prefix( 'INBOX', 'INBOX/Junk', 'INBOX/rrr', 'zzz' ), 'guess_prefix: INBOX INBOX/Junk INBOX/rrr zzz' ) ;
9701 is( q{} , guess_prefix( 'INBOX', 'Junk' ), 'guess_prefix: INBOX Junk' ) ;
9702 is( q{} , guess_prefix( 'INBOX', 'Junk' ), 'guess_prefix: INBOX Junk' ) ;
9703
9704 note( 'Leaving tests_guess_prefix()' ) ;
9705 return ;
9706}
9707
9708sub get_prefix
9709{
9710 my( $imap, $prefix_in, $prefix_opt, $Side, $folders_ref ) = @_ ;
9711 my( $prefix_out, $prefix_guessed ) ;
9712
9713 ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "$Side: Getting prefix\n" ) ;
9714 $prefix_guessed = guess_prefix( @{ $folders_ref } ) ;
9715 myprint( "$Side: guessing prefix from folder listing: [$prefix_guessed]\n" ) ;
9716 ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "$Side: Calling namespace capability\n" ) ;
9717 if ( $imap->has_capability( 'namespace' ) ) {
9718 my $r_namespace = $imap->namespace( ) ;
9719 $prefix_out = $r_namespace->[0][0][0] ;
9720 myprint( "$Side: prefix given by NAMESPACE: [$prefix_out]\n" ) ;
9721 if ( defined $prefix_in ) {
9722 myprint( "$Side: but using [$prefix_in] given by $prefix_opt\n" ) ;
9723 $prefix_out = $prefix_in ;
9724 return( $prefix_out ) ;
9725 }else{
9726 # all good
9727 return( $prefix_out ) ;
9728 }
9729 }
9730 else{
9731 if ( defined $prefix_in ) {
9732 myprint( "$Side: using [$prefix_in] given by $prefix_opt\n" ) ;
9733 $prefix_out = $prefix_in ;
9734 return( $prefix_out ) ;
9735 }else{
9736 myprint(
9737 "$Side: No NAMESPACE capability so using guessed prefix [$prefix_guessed]\n",
9738 help_to_guess_prefix( $imap, $prefix_opt ) ) ;
9739 return( $prefix_guessed ) ;
9740 }
9741 }
9742 return ;
9743}
9744
9745
9746sub guess_separator
9747{
9748 my @foldernames = @_ ;
9749
9750 #return( undef ) unless ( @foldernames ) ;
9751
9752 my $sep_guessed ;
9753 my %counter ;
9754 foreach my $folder ( @foldernames ) {
9755 $counter{'/'}++ while ( $folder =~ m{/}xg ) ; # count /
9756 $counter{'.'}++ while ( $folder =~ m{\.}xg ) ; # count .
9757 $counter{'\\\\'}++ while ( $folder =~ m{(\\){2}}xg ) ; # count \\
9758 $counter{'\\'}++ while ( $folder =~ m{[^\\](\\){1}(?=[^\\])}xg ) ; # count \
9759 }
9760 my @race_sorted = sort { $counter{ $b } <=> $counter{ $a } } keys %counter ;
9761 $sync->{ debug } and myprint( "@foldernames\n@race_sorted\n", %counter, "\n" ) ;
9762 $sep_guessed = shift @race_sorted || $LAST_RESSORT_SEPARATOR ; # / when nothing found.
9763 return( $sep_guessed ) ;
9764}
9765
9766sub tests_guess_separator
9767{
9768 note( 'Entering tests_guess_separator()' ) ;
9769
9770 ok( '/' eq guess_separator( ), 'guess_separator: no args' ) ;
9771 ok( '/' eq guess_separator( 'abcd' ), 'guess_separator: abcd' ) ;
9772 ok( '/' eq guess_separator( 'a/b/c.d' ), 'guess_separator: a/b/c.d' ) ;
9773 ok( '.' eq guess_separator( 'a.b/c.d' ), 'guess_separator: a.b/c.d' ) ;
9774 ok( '\\\\' eq guess_separator( 'a\\\\b\\\\c.c\\\\d/e/f' ), 'guess_separator: a\\\\b\\\\c.c\\\\d/e/f' ) ;
9775 ok( '\\' eq guess_separator( 'a\\b\\c.c\\d/e/f' ), 'guess_separator: a\\b\\c.c\\d/e/f' ) ;
9776 ok( '\\' eq guess_separator( 'a\\b' ), 'guess_separator: a\\b' ) ;
9777 ok( '\\' eq guess_separator( 'a\\b\\c' ), 'guess_separator: a\\b\\c' ) ;
9778
9779 note( 'Leaving tests_guess_separator()' ) ;
9780 return ;
9781}
9782
9783sub get_separator
9784{
9785 my( $imap, $sep_in, $sep_opt, $Side, $folders_ref ) = @_ ;
9786 my( $sep_out, $sep_guessed ) ;
9787
9788 ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "$Side: Getting separator\n" ) ;
9789 $sep_guessed = guess_separator( @{ $folders_ref } ) ;
9790 myprint( "$Side: guessing separator from folder listing: [$sep_guessed]\n" ) ;
9791
9792 ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "$Side: calling namespace capability\n" ) ;
9793 if ( $imap->has_capability( 'namespace' ) )
9794 {
9795 $sep_out = $imap->separator( ) ;
9796 if ( defined $sep_out ) {
9797 myprint( "$Side: separator given by NAMESPACE: [$sep_out]\n" ) ;
9798 if ( defined $sep_in ) {
9799 myprint( "$Side: but using [$sep_in] given by $sep_opt\n" ) ;
9800 $sep_out = $sep_in ;
9801 return( $sep_out ) ;
9802 }else{
9803 return( $sep_out ) ;
9804 }
9805 }else{
9806 if ( defined $sep_in ) {
9807 myprint( "$Side: NAMESPACE request failed but using [$sep_in] given by $sep_opt\n" ) ;
9808 $sep_out = $sep_in ;
9809 return( $sep_out ) ;
9810 }else{
9811 myprint(
9812 "$Side: NAMESPACE request failed so using guessed separator [$sep_guessed]\n",
9813 help_to_guess_sep( $imap, $sep_opt ) ) ;
9814 return( $sep_guessed ) ;
9815 }
9816 }
9817 }
9818 else
9819 {
9820 if ( defined $sep_in ) {
9821 myprint( "$Side: No NAMESPACE capability but using [$sep_in] given by $sep_opt\n" ) ;
9822 $sep_out = $sep_in ;
9823 return( $sep_out ) ;
9824 }else{
9825 myprint(
9826 "$Side: No NAMESPACE capability, so using guessed separator [$sep_guessed]\n",
9827 help_to_guess_sep( $imap, $sep_opt ) ) ;
9828 return( $sep_guessed ) ;
9829 }
9830 }
9831 return ;
9832}
9833
9834sub help_to_guess_sep
9835{
9836 my( $imap, $sep_opt ) = @_ ;
9837
9838 my $help_to_guess_sep = "You can set the separator character with the $sep_opt option,\n"
9839 . "the complete listing of folders may help you to find it\n"
9840 . folders_list_to_help( $imap ) ;
9841
9842 return( $help_to_guess_sep ) ;
9843}
9844
9845sub help_to_guess_prefix
9846{
9847 my( $imap, $prefix_opt ) = @_ ;
9848
9849 my $help_to_guess_prefix = "You can set the prefix namespace with the $prefix_opt option,\n"
9850 . "the folowing listing of folders may help you to find it:\n"
9851 . folders_list_to_help( $imap ) ;
9852
9853 return( $help_to_guess_prefix ) ;
9854}
9855
9856
9857sub folders_list_to_help
9858{
9859 my( $imap ) = shift ;
9860
9861 my @folders = $imap->folders ;
9862 my $listing = join q{}, map { "[$_]\n" } @folders ;
9863 return( $listing ) ;
9864}
9865
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009866# Globals are $sync @h1_folders_all @h2_folders_all $prefix1 $prefix2
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009867sub private_folders_separators_and_prefixes
9868{
9869# what are the private folders separators and prefixes for each server ?
9870
9871 ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "Getting separators\n" ) ;
9872 $sync->{ h1_sep } = get_separator( $sync->{imap1}, $sync->{ sep1 }, '--sep1', 'Host1', \@h1_folders_all ) ;
9873 $sync->{ h2_sep } = get_separator( $sync->{imap2}, $sync->{ sep2 }, '--sep2', 'Host2', \@h2_folders_all ) ;
9874
9875
9876 $sync->{ h1_prefix } = get_prefix( $sync->{imap1}, $prefix1, '--prefix1', 'Host1', \@h1_folders_all ) ;
9877 $sync->{ h2_prefix } = get_prefix( $sync->{imap2}, $prefix2, '--prefix2', 'Host2', \@h2_folders_all ) ;
9878
9879 myprint( "Host1: separator and prefix: [$sync->{ h1_sep }][$sync->{ h1_prefix }]\n" ) ;
9880 myprint( "Host2: separator and prefix: [$sync->{ h2_sep }][$sync->{ h2_prefix }]\n" ) ;
9881 return ;
9882}
9883
9884
9885sub subfolder1
9886{
9887 my $mysync = shift ;
9888 my $subfolder1 = sanitize_subfolder( $mysync->{ subfolder1 } ) ;
9889
9890 if ( $subfolder1 )
9891 {
9892 # turns off automap
9893 myprint( "Turning off automapping folders because of --subfolder1\n" ) ;
9894 $mysync->{ automap } = undef ;
9895 myprint( "Sanitizing subfolder1: [$mysync->{ subfolder1 }] => [$subfolder1]\n" ) ;
9896 $mysync->{ subfolder1 } = $subfolder1 ;
9897 if ( ! add_subfolder1_to_folderrec( $mysync ) )
9898 {
9899 $mysync->{nb_errors}++ ;
9900 exit_clean( $mysync, $EXIT_SUBFOLDER1_NO_EXISTS, "subfolder1 $subfolder1 does not exist\n" ) ;
9901 }
9902 }
9903 else
9904 {
9905 $mysync->{ subfolder1 } = undef ;
9906 }
9907
9908 return ;
9909}
9910
9911sub subfolder2
9912{
9913 my $mysync = shift ;
9914 my $subfolder2 = sanitize_subfolder( $mysync->{ subfolder2 } ) ;
9915 if ( $subfolder2 )
9916 {
9917 # turns off automap
9918 myprint( "Turning off automapping folders because of --subfolder2\n" ) ;
9919 $mysync->{ automap } = undef ;
9920 myprint( "Sanitizing subfolder2: [$mysync->{ subfolder2 }] => [$subfolder2]\n" ) ;
9921 $mysync->{ subfolder2 } = $subfolder2 ;
9922 set_regextrans2_for_subfolder2( $mysync ) ;
9923 }
9924 else
9925 {
9926 $mysync->{ subfolder2 } = undef ;
9927 }
9928
9929 return ;
9930}
9931
9932sub tests_sanitize_subfolder
9933{
9934 note( 'Entering tests_sanitize_subfolder()' ) ;
9935
9936 is( undef, sanitize_subfolder( ), 'sanitize_subfolder: no args => undef' ) ;
9937 is( undef, sanitize_subfolder( q{} ), 'sanitize_subfolder: empty => undef' ) ;
9938 is( undef, sanitize_subfolder( ' ' ), 'sanitize_subfolder: blank => undef' ) ;
9939 is( undef, sanitize_subfolder( ' ' ), 'sanitize_subfolder: blanks => undef' ) ;
9940 is( 'abcd', sanitize_subfolder( 'abcd' ), 'sanitize_subfolder: abcd => abcd' ) ;
9941 is( 'ab cd', sanitize_subfolder( ' ab cd ' ), 'sanitize_subfolder: " ab cd " => "ab cd"' ) ;
9942 is( 'abcd', sanitize_subfolder( q{a&~b#\\c[]=d;} ), 'sanitize_subfolder: "a&~b#\\c[]=d;" => "abcd"' ) ;
9943 is( 'aA.b-_ 8c/dD', sanitize_subfolder( 'aA.b-_ 8c/dD' ), 'sanitize_subfolder: aA.b-_ 8c/dD => aA.b-_ 8c/dD' ) ;
9944 note( 'Leaving tests_sanitize_subfolder()' ) ;
9945 return ;
9946}
9947
9948
9949sub sanitize_subfolder
9950{
9951 my $subfolder = shift ;
9952
9953 if ( ! $subfolder )
9954 {
9955 return ;
9956 }
9957 # Remove edging blanks
9958 $subfolder =~ s,^ +| +$,,g ;
9959 # Keep only abcd...ABCD...0123... and -_./
9960 $subfolder =~ tr,-_a-zA-Z0-9./ ,,cd ;
9961
9962 # A blank subfolder is not a subfolder
9963 if ( ! $subfolder )
9964 {
9965 return ;
9966 }
9967 else
9968 {
9969 return $subfolder ;
9970 }
9971}
9972
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009973sub tests_sanitize_host
9974{
9975 note( 'Entering tests_sanitize_host()' ) ;
9976
9977 is( undef, sanitize_host( ), 'sanitize_host: no args => undef' ) ;
9978 is( '', sanitize_host( '' ), 'sanitize_host: empty => empty' ) ;
9979 is( 'imap.example.org', sanitize_host( 'imap.example.org' ), 'sanitize_host: imap.example.org => imap.example.org' ) ;
9980 is( 'imap.example.org', sanitize_host( ' imap.example.org' ), 'sanitize_host: imap.example.org 1 => imap.example.org' ) ;
9981 is( 'imap.example.org', sanitize_host( 'imap.example.org ' ), 'sanitize_host: imap.example.org 2 => imap.example.org' ) ;
9982 is( 'imap.example.org', sanitize_host( 'imap.exam ple.org' ), 'sanitize_host: imap.example.org 3 => imap.example.org' ) ;
9983 is( 'imap.example.org', sanitize_host( ' imap.exam ple.org ' ), 'sanitize_host: imap.example.org 4 => imap.example.org' ) ;
9984 is( 'imap.example.org', sanitize_host( 'imap.exa/mple.org/' ), 'sanitize_host: imap.example.org/ => imap.example.org' ) ;
9985
9986 note( 'Leaving tests_sanitize_host()' ) ;
9987 return ;
9988}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009989
9990
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02009991sub sanitize_host
9992{
9993 my $host = shift ;
9994 if ( ! defined $host ) { return ; }
9995
9996 $host =~ tr{ /}{}d ;
9997 return $host ;
9998}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01009999
10000
10001sub tests_add_subfolder1_to_folderrec
10002{
10003 note( 'Entering tests_add_subfolder1_to_folderrec()' ) ;
10004
10005 is( undef, add_subfolder1_to_folderrec( ), 'add_subfolder1_to_folderrec: undef => undef' ) ;
10006 is_deeply( [], [ add_subfolder1_to_folderrec( ) ], 'add_subfolder1_to_folderrec: no args => empty array' ) ;
10007 @folderrec = () ;
10008 my $mysync = {} ;
10009 is_deeply( [ ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: empty => empty array' ) ;
10010 is_deeply( [ ], [ @folderrec ], 'add_subfolder1_to_folderrec: empty => empty folderrec' ) ;
10011 $mysync->{ subfolder1 } = 'SUBI' ;
10012 $h1_folders_all{ 'SUBI' } = 1 ;
10013 $mysync->{ h1_prefix } = 'INBOX/' ;
10014 is_deeply( [ 'SUBI' ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: SUBI => SUBI' ) ;
10015 is_deeply( [ 'SUBI' ], [ @folderrec ], 'add_subfolder1_to_folderrec: SUBI => folderrec SUBI ' ) ;
10016
10017 @folderrec = () ;
10018 $mysync->{ subfolder1 } = 'SUBO' ;
10019 is_deeply( [ ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: SUBO no exists => empty array' ) ;
10020 is_deeply( [ ], [ @folderrec ], 'add_subfolder1_to_folderrec: SUBO no exists => empty folderrec' ) ;
10021 $h1_folders_all{ 'INBOX/SUBO' } = 1 ;
10022 is_deeply( [ 'INBOX/SUBO' ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: SUBO + INBOX/SUBO exists => INBOX/SUBO' ) ;
10023 is_deeply( [ 'INBOX/SUBO' ], [ @folderrec ], 'add_subfolder1_to_folderrec: SUBO + INBOX/SUBO exists => INBOX/SUBO folderrec' ) ;
10024
10025 note( 'Leaving tests_add_subfolder1_to_folderrec()' ) ;
10026 return ;
10027}
10028
10029
10030sub add_subfolder1_to_folderrec
10031{
10032 my $mysync = shift ;
10033 if ( ! $mysync || ! $mysync->{ subfolder1 } )
10034 {
10035 return ;
10036 }
10037
10038 my $subfolder1 = $mysync->{ subfolder1 } ;
10039 my $subfolder1_extended = $mysync->{ h1_prefix } . $subfolder1 ;
10040
10041 if ( exists $h1_folders_all{ $subfolder1 } )
10042 {
10043 myprint( qq{Acting like --folderrec "$subfolder1"\n} ) ;
10044 push @folderrec, $subfolder1 ;
10045 }
10046 elsif ( exists $h1_folders_all{ $subfolder1_extended } )
10047 {
10048 myprint( qq{Acting like --folderrec "$subfolder1_extended"\n} ) ;
10049 push @folderrec, $subfolder1_extended ;
10050 }
10051 else
10052 {
10053 myprint( qq{Nor folder "$subfolder1" nor "$subfolder1_extended" exists on host1\n} ) ;
10054 }
10055 return @folderrec ;
10056}
10057
10058sub set_regextrans2_for_subfolder2
10059{
10060 my $mysync = shift ;
10061
10062
10063 unshift @{ $mysync->{ regextrans2 } },
10064 q(s,^$mysync->{ h2_prefix }(.*),$mysync->{ h2_prefix }$mysync->{ subfolder2 }$mysync->{ h2_sep }$1,),
10065 q(s,^INBOX$,$mysync->{ h2_prefix }$mysync->{ subfolder2 }$mysync->{ h2_sep }INBOX,),
10066 q(s,^($mysync->{ h2_prefix }){2},$mysync->{ h2_prefix },);
10067
10068 #myprint( "@{ $mysync->{ regextrans2 } }\n" ) ;
10069 return ;
10070}
10071
10072
10073
10074# Looks like no globals here
10075
10076sub tests_imap2_folder_name
10077{
10078 note( 'Entering tests_imap2_folder_name()' ) ;
10079
10080 my $mysync = {} ;
10081 $mysync->{ h1_prefix } = q{} ;
10082 $mysync->{ h2_prefix } = q{} ;
10083 $mysync->{ h1_sep } = '/';
10084 $mysync->{ h2_sep } = '.';
10085
10086 $mysync->{ debug } and myprint( <<"EOS"
10087prefix1: [$mysync->{ h1_prefix }]
10088prefix2: [$mysync->{ h2_prefix }]
10089sep1: [$sync->{ h1_sep }]
10090sep2: [$sync->{ h2_sep }]
10091EOS
10092) ;
10093
10094 $mysync->{ fixslash2 } = 0 ;
10095 is( q{INBOX}, imap2_folder_name( $mysync, q{} ), 'imap2_folder_name: empty string' ) ;
10096 is( 'blabla', imap2_folder_name( $mysync, 'blabla' ), 'imap2_folder_name: blabla' ) ;
10097 is('spam.spam', imap2_folder_name( $mysync, 'spam/spam' ), 'imap2_folder_name: spam/spam' ) ;
10098
10099 is( 'spam/spam', imap2_folder_name( $mysync, 'spam.spam' ), 'imap2_folder_name: spam.spam') ;
10100 is( 'spam.spam/spam', imap2_folder_name( $mysync, 'spam/spam.spam' ), 'imap2_folder_name: spam/spam.spam' ) ;
10101 is( 's pam.spam/sp am', imap2_folder_name( $mysync, 's pam/spam.sp am' ), 'imap2_folder_name: s pam/spam.sp am' ) ;
10102
10103 $mysync->{f1f2h}{ 'auto' } = 'moto' ;
10104 is( 'moto', imap2_folder_name( $mysync, 'auto' ), 'imap2_folder_name: auto' ) ;
10105 $mysync->{f1f2h}{ 'auto/auto' } = 'moto x 2' ;
10106 is( 'moto x 2', imap2_folder_name( $mysync, 'auto/auto' ), 'imap2_folder_name: auto/auto' ) ;
10107
10108 @{ $mysync->{ regextrans2 } } = ( 's,/,X,g' ) ;
10109 is( q{INBOX}, imap2_folder_name( $mysync, q{} ), 'imap2_folder_name: empty string [s,/,X,g]' ) ;
10110 is( 'blabla', imap2_folder_name( $mysync, 'blabla' ), 'imap2_folder_name: blabla [s,/,X,g]' ) ;
10111 is('spam.spam', imap2_folder_name( $mysync, 'spam/spam'), 'imap2_folder_name: spam/spam [s,/,X,g]');
10112 is('spamXspam', imap2_folder_name( $mysync, 'spam.spam'), 'imap2_folder_name: spam.spam [s,/,X,g]');
10113 is('spam.spamXspam', imap2_folder_name( $mysync, 'spam/spam.spam'), 'imap2_folder_name: spam/spam.spam [s,/,X,g]');
10114
10115 @{ $mysync->{ regextrans2 } } = ( 's, ,_,g' ) ;
10116 is('blabla', imap2_folder_name( $mysync, 'blabla'), 'imap2_folder_name: blabla [s, ,_,g]');
10117 is('bla_bla', imap2_folder_name( $mysync, 'bla bla'), 'imap2_folder_name: blabla [s, ,_,g]');
10118
10119 @{ $mysync->{ regextrans2 } } = ( q{s,(.*),\U$1,} ) ;
10120 is( 'BLABLA', imap2_folder_name( $mysync, 'blabla' ), q{imap2_folder_name: blabla [s,\U(.*)\E,$1,]} ) ;
10121
10122 $mysync->{ fixslash2 } = 1 ;
10123 @{ $mysync->{ regextrans2 } } = ( ) ;
10124 is(q{INBOX}, imap2_folder_name( $mysync, q{}), 'imap2_folder_name: empty string');
10125 is('blabla', imap2_folder_name( $mysync, 'blabla'), 'imap2_folder_name: blabla');
10126 is('spam.spam', imap2_folder_name( $mysync, 'spam/spam'), 'imap2_folder_name: spam/spam -> spam.spam');
10127 is('spam_spam', imap2_folder_name( $mysync, 'spam.spam'), 'imap2_folder_name: spam.spam -> spam_spam');
10128 is('spam.spam_spam', imap2_folder_name( $mysync, 'spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam_spam');
10129 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');
10130
10131 $mysync->{ h1_sep } = '.';
10132 $mysync->{ h2_sep } = '/';
10133 is( q{INBOX}, imap2_folder_name( $mysync, q{}), 'imap2_folder_name: empty string');
10134 is('blabla', imap2_folder_name( $mysync, 'blabla'), 'imap2_folder_name: blabla');
10135 is('spam.spam', imap2_folder_name( $mysync, 'spam/spam'), 'imap2_folder_name: spam/spam -> spam.spam');
10136 is('spam/spam', imap2_folder_name( $mysync, 'spam.spam'), 'imap2_folder_name: spam.spam -> spam/spam');
10137 is('spam.spam/spam', imap2_folder_name( $mysync, 'spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam');
10138
10139
10140
10141 $mysync->{ fixslash2 } = 0 ;
10142 $mysync->{ h1_prefix } = q{ };
10143
10144 is( 'spam.spam/spam', imap2_folder_name( $mysync, 'spam/spam.spam' ), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam' ) ;
10145 is( 'spam.spam/spam', imap2_folder_name( $mysync, ' spam/spam.spam' ), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam' ) ;
10146
10147 $mysync->{ h1_sep } = '.' ;
10148 $mysync->{ h2_sep } = '/' ;
10149 $mysync->{ h1_prefix } = 'INBOX.' ;
10150 $mysync->{ h2_prefix } = q{} ;
10151 @{ $mysync->{ regextrans2 } } = ( q{s,(.*),\U$1,} ) ;
10152 is( 'BLABLA', imap2_folder_name( $mysync, 'blabla' ), 'imap2_folder_name: blabla' ) ;
10153 is( 'TEST/TEST/TEST/TEST', imap2_folder_name( $mysync, 'INBOX.TEST.test.Test.tesT' ), 'imap2_folder_name: INBOX.TEST.test.Test.tesT' ) ;
10154 @{ $mysync->{ regextrans2 } } = ( q{s,(.*),\L$1,} ) ;
10155 is( 'test/test/test/test', imap2_folder_name( $mysync, 'INBOX.TEST.test.Test.tesT' ), 'imap2_folder_name: INBOX.TEST.test.Test.tesT' ) ;
10156
10157 # INBOX
10158 $mysync = {} ;
10159 $mysync->{ h1_prefix } = q{Pf1.} ;
10160 $mysync->{ h2_prefix } = q{Pf2/} ;
10161 $mysync->{ h1_sep } = '.';
10162 $mysync->{ h2_sep } = '/';
10163
10164 #
10165 #$mysync->{ debug } = 1 ;
10166 is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'F1.F2.F3' ), 'imap2_folder_name: F1.F2.F3 -> Pf2/F1/F2/F3' ) ;
10167 is( 'Pf2/F1/INBOX', imap2_folder_name( $mysync, 'F1.INBOX' ), 'imap2_folder_name: F1.INBOX -> Pf2/F1/INBOX' ) ;
10168 is( 'INBOX', imap2_folder_name( $mysync, 'INBOX' ), 'imap2_folder_name: INBOX -> INBOX' ) ;
10169
10170 is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'Pf1.F1.F2.F3' ), 'imap2_folder_name: Pf1.F1.F2.F3 -> Pf2/F1/F2/F3' ) ;
10171 is( 'Pf2/F1/INBOX', imap2_folder_name( $mysync, 'Pf1.F1.INBOX' ), 'imap2_folder_name: Pf1.F1.INBOX -> Pf2/F1/INBOX' ) ;
10172 is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.INBOX' ), 'imap2_folder_name: Pf1.INBOX -> INBOX' ) ; # not Pf2/INBOX: Yes I can!
10173
10174
10175
10176 # subfolder2
10177 $mysync = {} ;
10178 $mysync->{ h1_prefix } = q{} ;
10179 $mysync->{ h2_prefix } = q{} ;
10180 $mysync->{ h1_sep } = '/';
10181 $mysync->{ h2_sep } = '.';
10182
10183
10184 set_regextrans2_for_subfolder2( $mysync ) ;
10185 $mysync->{ subfolder2 } = 'S1.S2' ;
10186 is( 'S1.S2.F1.F2.F3', imap2_folder_name( $mysync, 'F1/F2/F3' ), 'imap2_folder_name: F1/F2/F3 -> S1.S2.F1.F2.F3' ) ;
10187 is( 'S1.S2.INBOX', imap2_folder_name( $mysync, 'INBOX' ), 'imap2_folder_name: F1/F2/F3 -> S1.S2.INBOX' ) ;
10188
10189 $mysync = {} ;
10190 $mysync->{ h1_prefix } = q{Pf1/} ;
10191 $mysync->{ h2_prefix } = q{Pf2.} ;
10192 $mysync->{ h1_sep } = '/';
10193 $mysync->{ h2_sep } = '.';
10194 #$mysync->{ debug } = 1 ;
10195
10196 set_regextrans2_for_subfolder2( $mysync ) ;
10197 $mysync->{ subfolder2 } = 'Pf2.S1.S2' ;
10198 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' ) ;
10199 is( 'Pf2.S1.S2.INBOX', imap2_folder_name( $mysync, 'INBOX' ), 'imap2_folder_name: INBOX -> Pf2.S1.S2.INBOX' ) ;
10200 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' ) ;
10201 is( 'Pf2.S1.S2.INBOX', imap2_folder_name( $mysync, 'Pf1/INBOX' ), 'imap2_folder_name: INBOX -> Pf2.S1.S2.INBOX' ) ;
10202
10203 # subfolder1
10204 # scenario as the reverse of the previous tests, separators point of vue
10205 $mysync = {} ;
10206 $mysync->{ h1_prefix } = q{Pf1.} ;
10207 $mysync->{ h2_prefix } = q{Pf2/} ;
10208 $mysync->{ h1_sep } = '.';
10209 $mysync->{ h2_sep } = '/';
10210 #$mysync->{ debug } = 1 ;
10211
10212 $mysync->{ subfolder1 } = 'S1.S2' ;
10213 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' ) ;
10214 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' ) ;
10215
10216 is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.INBOX' ), 'imap2_folder_name: S1.S2.INBOX -> INBOX' ) ;
10217 is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2' ), 'imap2_folder_name: S1.S2 -> INBOX' ) ;
10218 is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.' ), 'imap2_folder_name: S1.S2. -> INBOX' ) ;
10219
10220 is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.INBOX' ), 'imap2_folder_name: Pf1.S1.S2.INBOX -> INBOX' ) ;
10221 is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2' ), 'imap2_folder_name: Pf1.S1.S2 -> INBOX' ) ;
10222 is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.' ), 'imap2_folder_name: Pf1.S1.S2. -> INBOX' ) ;
10223
10224
10225 $mysync->{ subfolder1 } = 'S1.S2.' ;
10226 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' ) ;
10227 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' ) ;
10228
10229 is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.INBOX' ), 'imap2_folder_name: S1.S2.INBOX -> INBOX' ) ;
10230 is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2' ), 'imap2_folder_name: S1.S2 -> INBOX' ) ;
10231 is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.' ), 'imap2_folder_name: S1.S2. -> INBOX' ) ;
10232
10233 is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.INBOX' ), 'imap2_folder_name: Pf1.S1.S2.INBOX -> INBOX' ) ;
10234 is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2' ), 'imap2_folder_name: Pf1.S1.S2 -> INBOX' ) ;
10235 is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.' ), 'imap2_folder_name: Pf1.S1.S2. -> INBOX' ) ;
10236
10237
10238 # subfolder1
10239 # scenario as Gmail
10240 $mysync = {} ;
10241 $mysync->{ h1_prefix } = q{} ;
10242 $mysync->{ h2_prefix } = q{} ;
10243 $mysync->{ h1_sep } = '/';
10244 $mysync->{ h2_sep } = '/';
10245 #$mysync->{ debug } = 1 ;
10246
10247 $mysync->{ subfolder1 } = 'S1/S2' ;
10248 is( 'F1/F2/F3', imap2_folder_name( $mysync, 'S1/S2/F1/F2/F3' ), 'imap2_folder_name: S1/S2/F1/F2/F3 -> F1/F2/F3' ) ;
10249 is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/INBOX' ), 'imap2_folder_name: S1/S2/INBOX -> INBOX' ) ;
10250 is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2' ), 'imap2_folder_name: S1/S2 -> INBOX' ) ;
10251 is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/' ), 'imap2_folder_name: S1/S2/ -> INBOX' ) ;
10252
10253 $mysync->{ subfolder1 } = 'S1/S2/' ;
10254 is( 'F1/F2/F3', imap2_folder_name( $mysync, 'S1/S2/F1/F2/F3' ), 'imap2_folder_name: S1/S2/F1/F2/F3 -> F1/F2/F3' ) ;
10255 is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/INBOX' ), 'imap2_folder_name: S1/S2/INBOX -> INBOX' ) ;
10256 is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2' ), 'imap2_folder_name: S1/S2 -> INBOX' ) ;
10257 is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/' ), 'imap2_folder_name: S1/S2/ -> INBOX' ) ;
10258
10259
10260 note( 'Leaving tests_imap2_folder_name()' ) ;
10261 return ;
10262}
10263
10264
10265# Global variables to remove:
10266# None?
10267
10268
10269sub imap2_folder_name
10270{
10271 my $mysync = shift ;
10272 my ( $h1_fold ) = shift ;
10273 my ( $h2_fold ) ;
10274 if ( $mysync->{f1f2h}{ $h1_fold } ) {
10275 $h2_fold = $mysync->{f1f2h}{ $h1_fold } ;
10276 ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "f1f2 [$h1_fold] -> [$h2_fold]\n" ) ;
10277 return( $h2_fold ) ;
10278 }
10279 if ( $mysync->{f1f2auto}{ $h1_fold } ) {
10280 $h2_fold = $mysync->{f1f2auto}{ $h1_fold } ;
10281 ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "automap [$h1_fold] -> [$h2_fold]\n" ) ;
10282 return( $h2_fold ) ;
10283 }
10284
10285 if ( $mysync->{ subfolder1 } )
10286 {
10287 my $esc_h1_sep = "\\" . $mysync->{ h1_sep } ;
10288 # case where subfolder1 has the sep1 at the end, then remove it
10289 my $part_to_removed = remove_last_char_if_is( $mysync->{ subfolder1 }, $mysync->{ h1_sep } ) ;
10290 # remove the subfolder1 part and the sep1 if present after
10291 $h1_fold =~ s{$part_to_removed($esc_h1_sep)?}{} ;
10292 #myprint( "h1_fold=$h1_fold\n" ) ;
10293 }
10294
10295 if ( ( q{} eq $h1_fold ) or ( $mysync->{ h1_prefix } eq $h1_fold ) )
10296 {
10297 $h1_fold = 'INBOX' ;
10298 }
10299
10300 $h2_fold = prefix_seperator_invertion( $mysync, $h1_fold ) ;
10301 $h2_fold = regextrans2( $mysync, $h2_fold ) ;
10302 return( $h2_fold ) ;
10303}
10304
10305
10306sub tests_remove_last_char_if_is
10307{
10308 note( 'Entering tests_remove_last_char_if_is()' ) ;
10309
10310 is( undef, remove_last_char_if_is( ), 'remove_last_char_if_is: no args => undef' ) ;
10311 is( q{}, remove_last_char_if_is( q{} ), 'remove_last_char_if_is: empty => empty' ) ;
10312 is( q{}, remove_last_char_if_is( q{}, 'Z' ), 'remove_last_char_if_is: empty Z => empty' ) ;
10313 is( q{}, remove_last_char_if_is( 'Z', 'Z' ), 'remove_last_char_if_is: Z Z => empty' ) ;
10314 is( 'abc', remove_last_char_if_is( 'abcZ', 'Z' ), 'remove_last_char_if_is: abcZ Z => abc' ) ;
10315 is( 'abcY', remove_last_char_if_is( 'abcY', 'Z' ), 'remove_last_char_if_is: abcY Z => abcY' ) ;
10316 note( 'Leaving tests_remove_last_char_if_is()' ) ;
10317 return ;
10318}
10319
10320
10321
10322
10323sub remove_last_char_if_is
10324{
10325 my $string = shift ;
10326 my $char = shift ;
10327
10328 if ( ! defined $string )
10329 {
10330 return ;
10331 }
10332
10333 if ( ! defined $char )
10334 {
10335 return $string ;
10336 }
10337
10338 my $last_char = substr $string, -1 ;
10339 if ( $char eq $last_char )
10340 {
10341 chop $string ;
10342 return $string ;
10343 }
10344 else
10345 {
10346 return $string ;
10347 }
10348}
10349
10350sub tests_prefix_seperator_invertion
10351{
10352 note( 'Entering tests_prefix_seperator_invertion()' ) ;
10353
10354 is( undef, prefix_seperator_invertion( ), 'prefix_seperator_invertion: no args => undef' ) ;
10355 is( q{}, prefix_seperator_invertion( undef, q{} ), 'prefix_seperator_invertion: empty string => empty string' ) ;
10356 is( 'lalala', prefix_seperator_invertion( undef, 'lalala' ), 'prefix_seperator_invertion: lalala => lalala' ) ;
10357 is( 'lal/ala', prefix_seperator_invertion( undef, 'lal/ala' ), 'prefix_seperator_invertion: lal/ala => lal/ala' ) ;
10358 is( 'lal.ala', prefix_seperator_invertion( undef, 'lal.ala' ), 'prefix_seperator_invertion: lal.ala => lal.ala' ) ;
10359 is( '////', prefix_seperator_invertion( undef, '////' ), 'prefix_seperator_invertion: //// => ////' ) ;
10360 is( '.....', prefix_seperator_invertion( undef, '.....' ), 'prefix_seperator_invertion: ..... => .....' ) ;
10361
10362 my $mysync = {
10363 h1_prefix => q{},
10364 h2_prefix => q{},
10365 h1_sep => '/',
10366 h2_sep => '/',
10367 } ;
10368
10369 is( q{}, prefix_seperator_invertion( $mysync, q{} ), 'prefix_seperator_invertion: $mysync empty string => empty string' ) ;
10370 is( 'lalala', prefix_seperator_invertion( $mysync, 'lalala' ), 'prefix_seperator_invertion: $mysync lalala => lalala' ) ;
10371 is( 'lal/ala', prefix_seperator_invertion( $mysync, 'lal/ala' ), 'prefix_seperator_invertion: $mysync lal/ala => lal/ala' ) ;
10372 is( 'lal.ala', prefix_seperator_invertion( $mysync, 'lal.ala' ), 'prefix_seperator_invertion: $mysync lal.ala => lal.ala' ) ;
10373 is( '////', prefix_seperator_invertion( $mysync, '////' ), 'prefix_seperator_invertion: $mysync //// => ////' ) ;
10374 is( '.....', prefix_seperator_invertion( $mysync, '.....' ), 'prefix_seperator_invertion: $mysync ..... => .....' ) ;
10375
10376 $mysync = {
10377 h1_prefix => 'PPP',
10378 h2_prefix => 'QQQ',
10379 h1_sep => 's',
10380 h2_sep => 't',
10381 } ;
10382
10383 is( q{QQQ}, prefix_seperator_invertion( $mysync, q{} ), 'prefix_seperator_invertion: PPPQQQst empty string => QQQ' ) ;
10384 is( 'QQQlalala', prefix_seperator_invertion( $mysync, 'lalala' ), 'prefix_seperator_invertion: PPPQQQst lalala => QQQlalala' ) ;
10385 is( 'QQQlal/ala', prefix_seperator_invertion( $mysync, 'lal/ala' ), 'prefix_seperator_invertion: PPPQQQst lal/ala => QQQlal/ala' ) ;
10386 is( 'QQQlal.ala', prefix_seperator_invertion( $mysync, 'lal.ala' ), 'prefix_seperator_invertion: PPPQQQst lal.ala => QQQlal.ala' ) ;
10387 is( 'QQQ////', prefix_seperator_invertion( $mysync, '////' ), 'prefix_seperator_invertion: PPPQQQst //// => QQQ////' ) ;
10388 is( 'QQQ.....', prefix_seperator_invertion( $mysync, '.....' ), 'prefix_seperator_invertion: PPPQQQst ..... => QQQ.....' ) ;
10389
10390 is( 'QQQPlalala', prefix_seperator_invertion( $mysync, 'PPPPlalala' ), 'prefix_seperator_invertion: PPPQQQst PPPPlalala => QQQPlalala' ) ;
10391 is( 'QQQ', prefix_seperator_invertion( $mysync, 'PPP' ), 'prefix_seperator_invertion: PPPQQQst PPP => QQQ' ) ;
10392 is( 'QQQttt', prefix_seperator_invertion( $mysync, 'sss' ), 'prefix_seperator_invertion: PPPQQQst sss => QQQttt' ) ;
10393 is( 'QQQt', prefix_seperator_invertion( $mysync, 's' ), 'prefix_seperator_invertion: PPPQQQst s => QQQt' ) ;
10394 is( 'QQQtAAAtBBB', prefix_seperator_invertion( $mysync, 'PPPsAAAsBBB' ), 'prefix_seperator_invertion: PPPQQQst PPPsAAAsBBB => QQQtAAAtBBB' ) ;
10395
10396 note( 'Leaving tests_prefix_seperator_invertion()' ) ;
10397 return ;
10398}
10399
10400# Global variables to remove:
10401
10402
10403sub prefix_seperator_invertion
10404{
10405 my $mysync = shift ;
10406 my $h1_fold = shift ;
10407 my $h2_fold ;
10408
10409 if ( not defined $h1_fold ) { return ; }
10410
10411 my $my_h1_prefix = $mysync->{ h1_prefix } || q{} ;
10412 my $my_h2_prefix = $mysync->{ h2_prefix } || q{} ;
10413 my $my_h1_sep = $mysync->{ h1_sep } || '/' ;
10414 my $my_h2_sep = $mysync->{ h2_sep } || '/' ;
10415
10416 # first we remove the prefix
10417 $h1_fold =~ s/^\Q$my_h1_prefix\E//x ;
10418 ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "removed host1 prefix: [$h1_fold]\n" ) ;
10419 $h2_fold = separator_invert( $mysync, $h1_fold, $my_h1_sep, $my_h2_sep ) ;
10420 ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "inverted separators: [$h2_fold]\n" ) ;
10421
10422 # Adding the prefix supplied by namespace or the --prefix2 option
10423 # except for INBOX or Inbox
10424 if ( $h2_fold !~ m/^INBOX$/xi )
10425 {
10426 $h2_fold = $my_h2_prefix . $h2_fold ;
10427 }
10428
10429 ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "added host2 prefix: [$h2_fold]\n" ) ;
10430 return( $h2_fold ) ;
10431}
10432
10433sub tests_separator_invert
10434{
10435 note( 'Entering tests_separator_invert()' ) ;
10436
10437 my $mysync = {} ;
10438 $mysync->{ fixslash2 } = 0 ;
10439 ok( not( defined separator_invert( ) ), 'separator_invert: no args' ) ;
10440 ok( not( defined separator_invert( q{} ) ), 'separator_invert: not enough args' ) ;
10441 ok( not( defined separator_invert( q{}, q{} ) ), 'separator_invert: not enough args' ) ;
10442
10443 ok( q{} eq separator_invert( $mysync, q{}, q{}, q{} ), 'separator_invert: 3 empty strings' ) ;
10444 ok( 'lalala' eq separator_invert( $mysync, 'lalala', q{}, q{} ), 'separator_invert: empty separator' ) ;
10445 ok( 'lalala' eq separator_invert( $mysync, 'lalala', '/', '/' ), 'separator_invert: same separator /' ) ;
10446 ok( 'lal/ala' eq separator_invert( $mysync, 'lal/ala', '/', '/' ), 'separator_invert: same separator / 2' ) ;
10447 ok( 'lal.ala' eq separator_invert( $mysync, 'lal/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
10448 ok( 'lal/ala' eq separator_invert( $mysync, 'lal.ala', '.', '/' ), 'separator_invert: separators ./' ) ;
10449 ok( 'la.l/ala' eq separator_invert( $mysync, 'la/l.ala', '.', '/' ), 'separator_invert: separators ./' ) ;
10450
10451 ok( 'l/al.ala' eq separator_invert( $mysync, 'l.al/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
10452 $mysync->{ fixslash2 } = 1 ;
10453 ok( 'l_al.ala' eq separator_invert( $mysync, 'l.al/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
10454
10455 note( 'Leaving tests_separator_invert()' ) ;
10456 return ;
10457}
10458
10459# Global variables to remove:
10460#
10461sub separator_invert
10462{
10463 my( $mysync, $h1_fold, $h1_separator, $h2_separator ) = @_ ;
10464
10465 return( undef ) if ( not all_defined( $mysync, $h1_fold, $h1_separator, $h2_separator ) ) ;
10466 # The separator we hope we'll never encounter: 00000000 == 0x00
10467 my $o_sep = "\000" ;
10468
10469 my $h2_fold = $h1_fold ;
10470 $h2_fold =~ s,\Q$h2_separator,$o_sep,xg ;
10471 $h2_fold =~ s,\Q$h1_separator,$h2_separator,xg ;
10472 $h2_fold =~ s,\Q$o_sep,$h1_separator,xg ;
10473 $h2_fold =~ s,/,_,xg if( $mysync->{ fixslash2 } and '/' ne $h2_separator and '/' eq $h1_separator ) ;
10474 return( $h2_fold ) ;
10475}
10476
10477
10478sub regextrans2
10479{
10480 my( $mysync, $h2_fold ) = @_ ;
10481 # Transforming the folder name by the --regextrans2 option(s)
10482 foreach my $regextrans2 ( @{ $mysync->{ regextrans2 } } ) {
10483 my $h2_fold_before = $h2_fold ;
10484 my $ret = eval "\$h2_fold =~ $regextrans2 ; 1 " ;
10485 ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "[$h2_fold_before] -> [$h2_fold] using regextrans2 [$regextrans2]\n" ) ;
10486 if ( not ( defined $ret ) or $EVAL_ERROR ) {
10487 $mysync->{nb_errors}++ ;
10488 exit_clean( $mysync, $EX_USAGE,
10489 "error: eval regextrans2 '$regextrans2': $EVAL_ERROR\n"
10490 ) ;
10491 }
10492 }
10493 return( $h2_fold ) ;
10494}
10495
10496
10497sub tests_decompose_regex
10498{
10499 note( 'Entering tests_decompose_regex()' ) ;
10500
10501 ok( 1, 'decompose_regex 1' ) ;
10502 ok( 0 == compare_lists( [ q{}, q{} ], [ decompose_regex( q{} ) ] ), 'decompose_regex empty string' ) ;
10503 ok( 0 == compare_lists( [ '.*', 'lala' ], [ decompose_regex( 's/.*/lala/' ) ] ), 'decompose_regex s/.*/lala/' ) ;
10504
10505 note( 'Leaving tests_decompose_regex()' ) ;
10506 return ;
10507}
10508
10509sub decompose_regex
10510{
10511 my $regex = shift ;
10512 my( $left_part, $right_part ) ;
10513
10514 ( $left_part, $right_part ) = $regex =~ m{^s/((?:[^/]|\\/)+)/((?:[^/]|\\/)+)/}x;
10515 return( q{}, q{} ) if not $left_part ;
10516 return( $left_part, $right_part ) ;
10517}
10518
10519
10520
10521sub tests_timenext
10522{
10523 note( 'Entering tests_timenext()' ) ;
10524
10525 is( undef, timenext( ), 'timenext: no args => undef' ) ;
10526 my $mysync ;
10527 is( undef, timenext( $mysync ), 'timenext: undef => undef' ) ;
10528 $mysync = {} ;
10529 ok( time - timenext( $mysync ) <= 1e-02, 'timenext: defined first time => ~ time' ) ;
10530 ok( timenext( $mysync ) <= 1e-02, 'timenext: second time => less than 1e-02' ) ;
10531 ok( timenext( $mysync ) <= 1e-02, 'timenext: third time => less than 1e-02' ) ;
10532
10533 note( 'Leaving tests_timenext()' ) ;
10534 return ;
10535}
10536
10537
10538sub timenext
10539{
10540 my $mysync = shift ;
10541
10542 if ( ! defined $mysync )
10543 {
10544 return ;
10545 }
10546 my ( $timenow, $timediff ) ;
10547
10548 $mysync->{ timebefore } ||= 0; # epoch...
10549 $timenow = time ;
10550 $timediff = $timenow - $mysync->{ timebefore } ;
10551 $mysync->{ timebefore } = $timenow ;
10552 # myprint( "timenext: $timediff\n" ) ;
10553 return( $timediff ) ;
10554}
10555
10556
10557sub tests_timesince
10558{
10559 note( 'Entering tests_timesince()' ) ;
10560
10561 ok( timesince( time - 1 ) - 1 <= 1e-02, 'timesince: time - 1 => <= 1 + 1e-02' ) ;
10562 ok( timesince( time ) <= 1e-02, 'timesince: time => <= 1e-02' ) ;
10563 ok( timesince( ) - time <= 1e-02, 'timesince: no args => <= time + 1e-02' ) ;
10564 note( 'Leaving tests_timesince()' ) ;
10565 return ;
10566}
10567
10568
10569
10570sub timesince
10571{
10572 my $timeinit = shift || 0 ;
10573 my ( $timenow, $timediff ) ;
10574 $timenow = time ;
10575 $timediff = $timenow - $timeinit ;
10576 # Often used in a division so no 0 but a nano seconde.
10577 return( max( $timediff, min( 1e-09, $timediff ) ) ) ;
10578}
10579
10580
10581
10582
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010583sub tests_regexflags
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010584{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010585 note( 'Entering tests_regexflags()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010586
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010587 my $mysync = {} ;
10588
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010589 ok( q{} eq regexflags( $mysync, q{} ), 'regexflags, null string q{}' ) ;
10590 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 +010010591
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010592 @{ $mysync->{ regexflag } } = ('I am BAD' ) ;
10593 ok( not ( defined regexflags( $mysync, q{} ) ), 'regexflags, bad regex' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010594
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010595 @{ $mysync->{ regexflag } } = ( 's/NonJunk//g' ) ;
10596 ok( q{\Seen $Spam} eq regexflags( $mysync, q{\Seen NonJunk $Spam} ), q{regexflags, remove NonJunk: 's/NonJunk//g'} ) ;
10597 @{ $mysync->{ regexflag } } = ( q{s/\$Spam//g} ) ;
10598 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 +010010599
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010600 @{ $mysync->{ regexflag } } = ( 's/\\\\Seen//g' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010601
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010602 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 +010010603
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010604 @{ $mysync->{ regexflag } } = ( 's/(\s|^)[^\\\\]\w+//g' ) ;
10605 ok( q{\Seen \Middle \End} eq regexflags( $mysync, q{\Seen NonJunk \Middle $Spam \End} ), q{regexflags: only \word among \Seen NonJunk \Middle $Spam \End} ) ;
10606 ok( q{ \Seen \Middle \End1} eq regexflags( $mysync, q{Begin \Seen NonJunk \Middle $Spam \End1 End} ),
10607 q{regexflags: only \word among Begin \Seen NonJunk \Middle $Spam \End1 End} ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010608
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010609 @{ $mysync->{ regexflag } } = ( q{s/.*?(Keep1|Keep2|Keep3)/$1 /g} ) ;
10610 ok( 'Keep1 Keep2 ReB' eq regexflags( $mysync, 'ReA Keep1 REM Keep2 ReB' ), 'Keep only regex' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010611
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010612 ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM REM Keep1 Keep2' ), 'Keep only regex' ) ;
10613 ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 REM REM Keep2' ), 'Keep only regex' ) ;
10614 ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM Keep1 REM REM Keep2' ), 'Keep only regex' ) ;
10615 ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 Keep2' ), 'Keep only regex' ) ;
10616 ok( 'Keep1 ' eq regexflags( $mysync, 'REM Keep1' ), 'Keep only regex' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010617
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010618 @{ $mysync->{ regexflag } } = ( q{s/(Keep1|Keep2|Keep3) (?!(Keep1|Keep2|Keep3)).*/$1 /g} ) ;
10619 ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 Keep2 ReB' ), 'Keep only regex' ) ;
10620 ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 Keep2 REM REM REM' ), 'Keep only regex' ) ;
10621 ok( 'Keep2 ' eq regexflags( $mysync, 'Keep2 REM REM REM' ), 'Keep only regex' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010622
10623
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010624 @{ $mysync->{ regexflag } } = ( q{s/.*?(Keep1|Keep2|Keep3)/$1 /g},
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010625 's/(Keep1|Keep2|Keep3) (?!(Keep1|Keep2|Keep3)).*/$1 /g' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010626 ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM Keep1 REM Keep2 REM' ), 'Keep only regex' ) ;
10627 ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 REM Keep2 REM' ), 'Keep only regex' ) ;
10628 ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM Keep1 Keep2 REM' ), 'Keep only regex' ) ;
10629 ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM Keep1 REM Keep2' ), 'Keep only regex' ) ;
10630 ok( 'Keep1 Keep2 Keep3 ' eq regexflags( $mysync, 'REM Keep1 REM Keep2 REM REM Keep3 REM' ), 'Keep only regex' ) ;
10631 ok( 'Keep1 ' eq regexflags( $mysync, 'REM REM Keep1 REM REM REM ' ), 'Keep only regex' ) ;
10632 ok( 'Keep1 Keep3 ' eq regexflags( $mysync, 'RE1 Keep1 RE2 Keep3 RE3 RE4 RE5 ' ), 'Keep only regex' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010633
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010634 @{ $mysync->{ regexflag } } = ( 's/(.*)/$1 jrdH8u/' ) ;
10635 ok('REM REM REM REM REM jrdH8u' eq regexflags( $mysync, 'REM REM REM REM REM' ), q{Add jrdH8u 's/(.*)/\$1 jrdH8u/'} ) ;
10636 @{ $mysync->{ regexflag } } = ('s/jrdH8u *//' );
10637 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 +010010638
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010639 @{ $mysync->{ regexflag } } = (
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010640 's/.*?(?:(\\\\(?:Answered|Flagged|Deleted|Seen|Draft)\s?)|$)/defined($1)?$1:q()/eg'
10641 );
10642
10643 ok( '\\Deleted \\Answered '
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010644 eq regexflags( $mysync, 'Blabla \$Junk \\Deleted machin \\Answered truc' ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010645 'Keep only regex: Exchange case (Phil)' ) ;
10646
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010647 ok( q{} eq regexflags( $mysync, q{} ), 'Keep only regex: Exchange case, null string (Phil)' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010648
10649 ok( q{}
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010650 eq regexflags( $mysync, 'Blabla $Junk machin truc' ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010651 'Keep only regex: Exchange case, no accepted flags (Phil)' ) ;
10652
10653 ok('\\Deleted \\Answered \\Draft \\Flagged '
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010654 eq regexflags( $mysync, '\\Deleted \\Answered \\Draft \\Flagged ' ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010655 'Keep only regex: Exchange case (Phil)' ) ;
10656
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010657 @{ $mysync->{ regexflag } } = ( 's/\\\\Flagged//g' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010658
10659 is('\Deleted \Answered \Draft ',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010660 regexflags( $mysync, '\\Deleted \\Answered \\Draft \\Flagged ' ),
10661 'regexflags: remove \Flagged 1' ) ;
10662
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010663 is('\\Deleted \\Answered \\Draft',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010664 regexflags( $mysync, '\\Deleted \\Flagged \\Answered \\Draft' ),
10665 'regexflags: remove \Flagged 2' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010666
10667 # I didn't understand why it gives \F
10668 # https://perldoc.perl.org/perlrebackslash.html
10669 # \F Foldcase till \E. Not in [].
10670 # https://perldoc.perl.org/functions/fc.html
10671
10672 # \F Not available in old Perl so I comment the test
10673
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010674 # @{ $mysync->{ regexflag } } = ( 's/\\Flagged/X/g' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010675 #is('\Deleted FX \Answered \FX \Draft \FX',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010676 #regexflags( '\Deleted Flagged \Answered \Flagged \Draft \Flagged' ),
10677 # 'regexflags: remove \Flagged 3 mistery...' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010678
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010679 $mysync->{ regexflag } = [ ] ;
10680 $mysync->{ filterbuggyflags } = 1 ;
10681 filterbuggyflags( $mysync ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010682
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010683 is( '\Deleted \Answered \Draft \Flagged',
10684 regexflags( $mysync, '\\Deleted \\Answered \\RECEIPTCHECKED \\Draft \\Indexed \\Flagged' ),
10685 'regexflags: remove famous /X 1' ) ;
10686
10687 is( '\\Deleted \\Flagged \\Answered \\Draft',
10688 regexflags( $mysync, '\\Deleted \\RECEIPTCHECKED \\Flagged \\Answered \\Indexed \\Draft' ),
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010689 'regexflags: remove famous /X 2' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010690
10691 is( '\ ', '\\ ', 'regexflags: \ is \\ ' ) ;
10692 is( '\\ ', '\\ ', 'regexflags: \\ is \\ ' ) ;
10693 is( '\\ \ ', '\ \\ ', 'regexflags: \\ \ is \ \\ ' ) ;
10694 note( 'Leaving tests_regexflags()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010695 return ;
10696}
10697
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010698sub regexflags
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010699{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010700 my $mysync = shift ;
10701 my $flags = shift ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010702
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010703 foreach my $regexflag ( @{ $mysync->{ regexflag } } )
10704 {
10705 my $flags_orig = $flags ;
10706 $debugflags and myprint( "eval \$flags =~ $regexflag\n" ) ;
10707 my $ret = eval "\$flags =~ $regexflag ; 1 " ;
10708 $debugflags and myprint( "regexflag $regexflag [$flags_orig] -> [$flags]\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010709 if( not ( defined $ret ) or $EVAL_ERROR ) {
10710 myprint( "Error: eval regexflag '$regexflag': $EVAL_ERROR\n" ) ;
10711 return( undef ) ;
10712 }
10713 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010714 return( $flags ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010715}
10716
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010717
10718sub filterbuggyflags
10719{
10720 my $mysync = shift ;
10721 if ( $mysync->{ filterbuggyflags } )
10722 {
10723 unshift @{ $mysync->{ regexflag } }, buggyflagsregex( ) ;
10724 }
10725 return ;
10726}
10727
10728
10729sub tests_remove_doublequotes_if_any
10730{
10731 note( 'Entering tests_remove_doublequotes_if_any()' ) ;
10732 # the number of tests is stupid here
10733 is( undef, remove_doublequotes_if_any( ), 'remove_doublequotes_if_any: no args => undef' ) ;
10734 is( q{}, remove_doublequotes_if_any( q{} ), 'remove_doublequotes_if_any: empty string => empty string' ) ;
10735 is( q{}, remove_doublequotes_if_any( q{""} ), 'remove_doublequotes_if_any: double-quotes => empty string' ) ;
10736 is( q{}, remove_doublequotes_if_any( q{"""} ), 'remove_doublequotes_if_any: double-quotes => empty string' ) ;
10737 is( q{}, remove_doublequotes_if_any( q{"""} ), 'remove_doublequotes_if_any: double-quotes => empty string' ) ;
10738 is( q{toto}, remove_doublequotes_if_any( q{"toto"} ), 'remove_doublequotes_if_any: "toto" => toto' ) ;
10739 is( q{toto}, remove_doublequotes_if_any( q{toto} ), 'remove_doublequotes_if_any: toto => toto' ) ;
10740 is( q{toto}, remove_doublequotes_if_any( q{to"to} ), 'remove_doublequotes_if_any: to"to => toto' ) ;
10741 is( q{toto}, remove_doublequotes_if_any( q{toto"} ), 'remove_doublequotes_if_any: toto" => toto' ) ;
10742 is( q{toto}, remove_doublequotes_if_any( q{"toto} ), 'remove_doublequotes_if_any: "toto => toto' ) ;
10743 is( q{toto}, remove_doublequotes_if_any( q{"to"to} ), 'remove_doublequotes_if_any: "to"to => toto' ) ;
10744 is( q{toto}, remove_doublequotes_if_any( q{to"to"} ), 'remove_doublequotes_if_any: to"to" => toto' ) ;
10745
10746 is( q{toto}, remove_doublequotes_if_any( q{to\"to} ), 'remove_doublequotes_if_any: to\"to => toto' ) ;
10747 is( q{toto}, remove_doublequotes_if_any( q{toto\"} ), 'remove_doublequotes_if_any: toto\" => toto' ) ;
10748 is( q{toto}, remove_doublequotes_if_any( q{\"toto} ), 'remove_doublequotes_if_any: \"toto => toto' ) ;
10749 is( q{toto}, remove_doublequotes_if_any( q{\"to\"to} ), 'remove_doublequotes_if_any: \"to\"to => toto' ) ;
10750 is( q{toto}, remove_doublequotes_if_any( q{to\"to\"} ), 'remove_doublequotes_if_any: to\"to" => toto' ) ;
10751
10752
10753 note( 'Leaving tests_remove_doublequotes_if_any()' ) ;
10754 return ;
10755}
10756
10757
10758
10759sub remove_doublequotes_if_any
10760{
10761 my $string = shift ;
10762
10763 if ( ! defined $string ) { return ; }
10764 $string =~ s/\\\"//g ;
10765 $string =~ tr/"//d ;
10766 return $string ;
10767}
10768
10769
10770# No globals here
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010771sub acls_sync
10772{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010773# https://tools.ietf.org/html/rfc4314
10774# Standard Rights:
10775# https://tools.ietf.org/html/rfc4314#section-2.1
10776
10777 my( $mysync, $h1_fold, $h2_fold ) = @_ ;
10778 if ( $mysync->{ syncacls } ) {
10779 my $h1_hash = $mysync->{imap1}->getacl($h1_fold)
10780 or myprint( "Host1: Could not getacl for $h1_fold: $EVAL_ERROR\n" ) ;
10781 my $h2_hash = $mysync->{imap2}->getacl($h2_fold)
10782 or myprint( "Host2: Could not getacl for $h2_fold: $EVAL_ERROR\n" ) ;
10783
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010784 my %users = map { ($_, 1) } ( keys %{ $h1_hash} , keys %{ $h2_hash } ) ;
10785 foreach my $user (sort keys %users ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010786 my $h1_acl = remove_doublequotes_if_any( $h1_hash->{$user} ) || '' ;
10787 my $h2_acl = remove_doublequotes_if_any( $h2_hash->{$user} ) || '' ;
10788 myprint( "Host1: user $user has acl [$h1_acl] on host1\n" ) ;
10789 myprint( "Host2: user $user has acl [$h2_acl] on host2\n" ) ;
10790 # removes surrounding double-quotes if any
10791 my $user_no_quotes = remove_doublequotes_if_any( $user ) ;
10792
10793 if ( $h1_hash->{$user}
10794 && $h2_hash->{$user}
10795 && $h1_hash->{$user} eq $h2_hash->{$user} )
10796 {
10797 myprint( "Host2: user $user_no_quotes has already the same acl, no need to set it.\n" ) ;
10798 next ;
10799 }
10800 myprint( "Host2: setting acl for folder $h2_fold user $user_no_quotes acl $h1_acl $mysync->{dry_message}\n" ) ;
10801 unless ( $mysync->{dry} ) {
10802 $mysync->{imap2}->setacl( $h2_fold, $user_no_quotes, $h1_acl )
10803 or myprint( "Could not set acl for user $user_no_quotes on host2: $EVAL_ERROR\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010804 }
10805 }
10806 }
10807 return ;
10808}
10809
10810
10811sub tests_permanentflags
10812{
10813 note( 'Entering tests_permanentflags()' ) ;
10814
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010815 my $mysync = { } ;
10816 ok( q{} eq permanentflags( $mysync, ' * OK [PERMANENTFLAGS (\* \Draft \Answered)] Limited' ),
10817 'permanentflags \*' ) ;
10818
10819 ok( '\Draft \Answered' eq permanentflags( $mysync, ' * OK [PERMANENTFLAGS (\Draft \Answered)] Limited' ),
10820 'permanentflags \Draft \Answered' ) ;
10821
10822 ok( '\Draft \Answered'
10823 eq permanentflags( $mysync, 'Blabla',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010824 ' * OK [PERMANENTFLAGS (\Draft \Answered)] Limited',
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010825 'Blabla' ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010826 'permanentflags \Draft \Answered'
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010827 ) ;
10828
10829 ok( q{} eq permanentflags( $mysync, 'Blabla' ), 'permanentflags nothing' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010830
10831 note( 'Leaving tests_permanentflags()' ) ;
10832 return ;
10833}
10834
10835sub permanentflags
10836{
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010837 my $mysync = shift ;
10838
10839 my @lines = @_ ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010840
10841 foreach my $line (@lines) {
10842 if ( $line =~ m{\[PERMANENTFLAGS\s\(([^)]+?)\)\]}x ) {
10843 ( $debugflags or $sync->{ debug } ) and myprint( "permanentflags: $line" ) ;
10844 my $permanentflags = $1 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010845 if ( $permanentflags =~ m{\\\*}x )
10846 {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010847 $permanentflags = q{} ;
10848 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010849 return( $permanentflags ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010850 } ;
10851 }
10852 return( q{} ) ;
10853}
10854
10855sub tests_flags_filter
10856{
10857 note( 'Entering tests_flags_filter()' ) ;
10858
10859 ok( '\Seen' eq flags_filter('\Seen', '\Draft \Seen \Answered'), 'flags_filter ' );
10860 ok( q{} eq flags_filter('\Seen', '\Draft \Answered'), 'flags_filter ' );
10861 ok( '\Seen' eq flags_filter('\Seen', '\Seen'), 'flags_filter ' );
10862 ok( '\Seen' eq flags_filter('\Seen', ' \Seen '), 'flags_filter ' );
10863 ok( '\Seen \Draft'
10864 eq flags_filter('\Seen \Draft', '\Draft \Seen \Answered'), 'flags_filter ' );
10865 ok( '\Seen \Draft'
10866 eq flags_filter('\Seen \Draft', ' \Draft \Seen \Answered '), 'flags_filter ' );
10867
10868 note( 'Leaving tests_flags_filter()' ) ;
10869 return ;
10870}
10871
10872sub flags_filter
10873{
10874 my( $flags, $allowed_flags ) = @_ ;
10875
10876 my @flags = split /\s+/x, $flags ;
10877 my %allowed_flags = map { $_ => 1 } split q{ }, $allowed_flags ;
10878 my @flags_out = map { exists $allowed_flags{$_} ? $_ : () } @flags ;
10879
10880 my $flags_out = join q{ }, @flags_out ;
10881
10882 return( $flags_out ) ;
10883}
10884
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010885sub tests_flagscase
10886{
10887 note( 'Entering tests_flagscase()' ) ;
10888
10889 ok( '\Seen' eq flagscase( '\Seen' ), 'flagscase: \Seen -> \Seen' ) ;
10890 ok( '\Seen' eq flagscase( '\SEEN' ), 'flagscase: \SEEN -> \Seen' ) ;
10891
10892 ok( '\Seen \Draft' eq flagscase( '\SEEN \DRAFT' ), 'flagscase: \SEEN \DRAFT -> \Seen \Draft' ) ;
10893 ok( '\Draft \Seen' eq flagscase( '\DRAFT \SEEN' ), 'flagscase: \DRAFT \SEEN -> \Draft \Seen' ) ;
10894
10895 ok( '\Draft LALA \Seen' eq flagscase( '\DRAFT LALA \SEEN' ), 'flagscase: \DRAFT LALA \SEEN -> \Draft LALA \Seen' ) ;
10896 ok( '\Draft lala \Seen' eq flagscase( '\DRAFT lala \SEEN' ), 'flagscase: \DRAFT lala \SEEN -> \Draft lala \Seen' ) ;
10897
10898 note( 'Leaving tests_flagscase()' ) ;
10899 return ;
10900}
10901
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010902sub flagscase
10903{
10904 my $flags = shift ;
10905
10906 my @flags = split /\s+/x, $flags ;
10907 my %rfc_flags = map { $_ => 1 } split q{ }, '\Answered \Flagged \Deleted \Seen \Draft' ;
10908 my @flags_out = map { exists $rfc_flags{ ucsecond( lc $_ ) } ? ucsecond( lc $_ ) : $_ } @flags ;
10909
10910 my $flags_out = join q{ }, @flags_out ;
10911
10912 return( $flags_out ) ;
10913}
10914
10915
10916
10917sub tests_flags_for_host2
10918{
10919 note( 'Entering tests_flags_for_host2()' ) ;
10920
10921 is( undef, flags_for_host2( ), 'flags_for_host2: no args => undef' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010922
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010923 my $mysync ;
10924 is( undef, flags_for_host2( $mysync ), 'flags_for_host2: undef => undef' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010925
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010926 $mysync = { } ;
10927 is( undef, flags_for_host2( $mysync ), 'flags_for_host2: nothing => undef' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010928
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010929 is( q{}, flags_for_host2( $mysync, '' ), 'flags_for_host2: no flags => empty string' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010930
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010931 is( q{}, flags_for_host2( $mysync, '\Recent' ), 'flags_for_host2: \Recent => empty string' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010932
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010933 is( q{\Seen}, flags_for_host2( $mysync, '\Recent \Seen' ), 'flags_for_host2: \Recent \Seen => \Seen' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010934
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010935 is( q{\Deleted \Seen}, flags_for_host2( $mysync, '\Deleted \Recent \Seen' ), 'flags_for_host2: \Deleted \Recent \Seen => \Deleted \Seen' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010936
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010937 $mysync->{ flagscase } = 0 ;
10938 is( q{\DELETED \Seen}, flags_for_host2( $mysync, '\DELETED \Seen' ), 'flags_for_host2: flagscase = 0 \DELETED \Seen => \DELETED \Seen' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010939
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010940 $mysync->{ flagscase } = 1 ;
10941 is( q{\Deleted \Seen}, flags_for_host2( $mysync, '\DELETED \Seen' ), 'flags_for_host2: flagscase = 1 \DELETED \Seen => \Deleted \Seen' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010942
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010943 $mysync->{ filterflags } = 0 ;
10944 is( q{\Seen \Blabla}, flags_for_host2( $mysync, '\Seen \Blabla', '\Seen \Junk' ), 'flags_for_host2: filterflags = 0 \Seen \Blabla among \Seen \Junk => \Seen \Blabla' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010945
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010946 $mysync->{ filterflags } = 1 ;
10947 is( q{\Seen}, flags_for_host2( $mysync, '\Seen \Blabla', '\Seen \Junk' ), 'flags_for_host2: filterflags = 1 \Seen \Blabla among \Seen \Junk => \Seen' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010948
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010949 $mysync->{ filterflags } = 1 ;
10950 is( q{\Seen \Blabla}, flags_for_host2( $mysync, '\Seen \Blabla', '' ), 'flags_for_host2: filterflags = 1 \Seen \Blabla among "" => \Seen \Blabla' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010951
10952
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010953 note( 'Leaving tests_flags_for_host2()' ) ;
10954 return ;
10955}
10956
10957
10958
10959
10960sub flags_for_host2
10961{
10962 my $mysync = shift ;
10963 my $h1_flags = shift ;
10964 my $permanentflags2 = shift ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010965
10966 if ( ! all_defined( $mysync, $h1_flags ) ) { return ; } ;
10967
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010968 # RFC 2060: This flag can not be altered by any client
10969 $h1_flags =~ s@\\Recent\s?@@xgi ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010970
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010971 my $h1_flags_re ;
10972 if ( $mysync->{ regexflag } and defined( $h1_flags_re = regexflags( $mysync, $h1_flags ) ) ) {
10973 $h1_flags = $h1_flags_re ;
10974 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010975
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010976 if ( $mysync->{ flagscase } )
10977 {
10978 $h1_flags = flagscase( $h1_flags ) ;
10979 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010980
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010981 if ( $permanentflags2 and $mysync->{ filterflags } )
10982 {
10983 $h1_flags = flags_filter( $h1_flags, $permanentflags2 ) ;
10984 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010010985
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020010986 return( $h1_flags ) ;
10987}
10988
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010010989
10990
10991sub ucsecond
10992{
10993 my $string = shift ;
10994 my $output ;
10995
10996 return( $string ) if ( 1 >= length $string ) ;
10997
10998 $output = ( substr( $string, 0, 1) ) . ( uc substr $string, 1, 1 ) . ( substr $string, 2 ) ;
10999 #myprint( "UUU $string -> $output\n" ) ;
11000 return( $output ) ;
11001}
11002
11003
11004sub tests_ucsecond
11005{
11006 note( 'Entering tests_ucsecond()' ) ;
11007
11008 ok( 'aBcde' eq ucsecond( 'abcde' ), 'ucsecond: abcde -> aBcde' ) ;
11009 ok( 'ABCDE' eq ucsecond( 'ABCDE' ), 'ucsecond: ABCDE -> ABCDE' ) ;
11010 ok( 'ABCDE' eq ucsecond( 'AbCDE' ), 'ucsecond: AbCDE -> ABCDE' ) ;
11011 ok( 'ABCde' eq ucsecond( 'AbCde' ), 'ucsecond: AbCde -> ABCde' ) ;
11012 ok( 'A' eq ucsecond( 'A' ), 'ucsecond: A -> A' ) ;
11013 ok( 'AB' eq ucsecond( 'Ab' ), 'ucsecond: Ab -> AB' ) ;
11014 ok( '\B' eq ucsecond( '\b' ), 'ucsecond: \b -> \B' ) ;
11015 ok( '\Bcde' eq ucsecond( '\bcde' ), 'ucsecond: \bcde -> \Bcde' ) ;
11016
11017 note( 'Leaving tests_ucsecond()' ) ;
11018 return ;
11019}
11020
11021
11022sub select_msgs
11023{
11024 my ( $imap, $msgs_all_hash_ref, $search_cmd, $abletosearch, $folder ) = @_ ;
11025 my ( @msgs ) ;
11026
11027 if ( $abletosearch ) {
11028 @msgs = select_msgs_by_search( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) ;
11029 }else{
11030 @msgs = select_msgs_by_fetch( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) ;
11031 }
11032 return( @msgs ) ;
11033
11034}
11035
11036sub select_msgs_by_search
11037{
11038 my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ;
11039 my ( @msgs, @msgs_all ) ;
11040
11041 # Need to have the whole list in msgs_all_hash_ref
11042 # without calling messages() several times.
11043 # Need all messages list to avoid deleting useful cache part
11044 # in case of --search or --minage or --maxage
11045
11046 if ( ( defined $msgs_all_hash_ref and $usecache )
11047 or ( not defined $maxage and not defined $minage and not defined $search_cmd )
11048 ) {
11049
11050 $debugdev and myprint( "Calling messages()\n" ) ;
11051 @msgs_all = $imap->messages( ) ;
11052
11053 return if ( $#msgs_all == 0 && !defined $msgs_all[0] ) ;
11054
11055 if ( defined $msgs_all_hash_ref ) {
11056 @{ $msgs_all_hash_ref }{ @msgs_all } = () ;
11057 }
11058 # return all messages
11059 if ( not defined $maxage and not defined $minage and not defined $search_cmd ) {
11060 return( @msgs_all ) ;
11061 }
11062 }
11063
11064 if ( defined $search_cmd ) {
11065 @msgs = $imap->search( $search_cmd ) ;
11066 return( @msgs ) ;
11067 }
11068
11069 # we are here only if $maxage or $minage is defined
11070 @msgs = select_msgs_by_age( $imap ) ;
11071 return( @msgs );
11072}
11073
11074
11075sub select_msgs_by_fetch
11076{
11077 my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ;
11078 my ( @msgs, @msgs_all, %fetch ) ;
11079
11080 # Need to have the whole list in msgs_all_hash_ref
11081 # without calling messages() several times.
11082 # Need all messages list to avoid deleting useful cache part
11083 # in case of --search or --minage or --maxage
11084
11085
11086 $debugdev and myprint( "Calling fetch_hash()\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011087 my $fetch_hash_uids = $fetch_hash_set || "1:*" ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011088 %fetch = %{$imap->fetch_hash( $fetch_hash_uids, 'INTERNALDATE' ) } ;
11089
11090 @msgs_all = sort { $a <=> $b } keys %fetch ;
11091 $debugdev and myprint( "Done fetch_hash()\n" ) ;
11092
11093 return if ( $#msgs_all == 0 && !defined $msgs_all[0] ) ;
11094
11095 if ( defined $msgs_all_hash_ref ) {
11096 @{ $msgs_all_hash_ref }{ @msgs_all } = () ;
11097 }
11098 # return all messages
11099 if ( not defined $maxage and not defined $minage and not defined $search_cmd ) {
11100 return( @msgs_all ) ;
11101 }
11102
11103 if ( defined $search_cmd ) {
11104 myprint( "Warning: strange to see --search with --noabletosearch, an error can happen\n" ) ;
11105 @msgs = $imap->search( $search_cmd ) ;
11106 return( @msgs ) ;
11107 }
11108
11109 # we are here only if $maxage or $minage is defined
11110 my( @max, @min, $maxage_epoch, $minage_epoch ) ;
11111 if ( defined $maxage ) { $maxage_epoch = $timestart_int - $NB_SECONDS_IN_A_DAY * $maxage ; }
11112 if ( defined $minage ) { $minage_epoch = $timestart_int - $NB_SECONDS_IN_A_DAY * $minage ; }
11113 foreach my $msg ( @msgs_all ) {
11114 my $idate = $fetch{ $msg }->{'INTERNALDATE'} ;
11115 #myprint( "$idate\n" ) ;
11116 if ( defined $maxage and ( epoch( $idate ) >= $maxage_epoch ) ) {
11117 push @max, $msg ;
11118 }
11119 if ( defined $minage and ( epoch( $idate ) <= $minage_epoch ) ) {
11120 push @min, $msg ;
11121 }
11122 }
11123 @msgs = msgs_from_maxmin( \@max, \@min ) ;
11124 return( @msgs ) ;
11125}
11126
11127sub select_msgs_by_age
11128{
11129 my( $imap ) = @_ ;
11130
11131 my( @max, @min, @msgs, @inter, @union ) ;
11132
11133 if ( defined $maxage ) {
11134 @max = $imap->sentsince( $timestart_int - $NB_SECONDS_IN_A_DAY * $maxage ) ;
11135 }
11136 if ( defined $minage ) {
11137 @min = $imap->sentbefore( $timestart_int - $NB_SECONDS_IN_A_DAY * $minage ) ;
11138 }
11139
11140 @msgs = msgs_from_maxmin( \@max, \@min ) ;
11141 return( @msgs ) ;
11142}
11143
11144sub msgs_from_maxmin
11145{
11146 my( $max_ref, $min_ref ) = @_ ;
11147 my( @max, @min, @msgs, @inter, @union ) ;
11148
11149 @max = @{ $max_ref } ;
11150 @min = @{ $min_ref } ;
11151
11152 SWITCH: {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011153 if ( not ( defined $minage or defined $maxage ) )
11154 {
11155 return ;
11156 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011157 unless( defined $minage ) { @msgs = @max ; last SWITCH } ;
11158 unless( defined $maxage ) { @msgs = @min ; last SWITCH } ;
11159 my ( %union, %inter ) ;
11160 foreach my $m ( @min, @max ) { $union{ $m }++ && $inter{ $m }++ }
11161 @inter = sort { $a <=> $b } keys %inter ;
11162 @union = sort { $a <=> $b } keys %union ;
11163 # normal case
11164 if ( $minage <= $maxage ) { @msgs = @inter ; last SWITCH } ;
11165 # just exclude messages between
11166 if ( $minage > $maxage ) { @msgs = @union ; last SWITCH } ;
11167
11168 }
11169 return( @msgs ) ;
11170}
11171
11172sub tests_msgs_from_maxmin
11173{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011174 note( 'Entering tests_msgs_from_maxmin()' ) ;
11175
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011176
11177 my @msgs ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011178
11179 # no maxage nor minage
11180 @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
11181 is_deeply( [ ], \@msgs , 'msgs_from_maxmin: no maxage nor minage => empty result' ) ;
11182
11183 # maxage alone
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011184 $maxage = $NUMBER_200 ;
11185 @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011186 is_deeply( [ '1', '2' ], \@msgs , 'msgs_from_maxmin: maxage++' ) ;
11187
11188 # maxage > minage -> intersection
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011189 $minage = $NUMBER_100 ;
11190 @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011191 is_deeply( [ '2' ], \@msgs , 'msgs_from_maxmin: -maxage++minage-' ) ;
11192
11193 # maxage < minage -> union
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011194 $minage = $NUMBER_300 ;
11195 @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011196 is_deeply( [ '1', '2', '3' ], \@msgs, 'msgs_from_maxmin: ++maxage-minage++' ) ;
11197
11198
11199 # minage alone
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011200 $maxage = undef ;
11201 @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011202 is_deeply( [ '2', '3' ], \@msgs, 'msgs_from_maxmin: ++minage-' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011203
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011204
11205 note( 'Leaving tests_msgs_from_maxmin()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011206 return ;
11207}
11208
11209sub tests_info_date_from_uid
11210{
11211 note( 'Entering tests_info_date_from_uid()' ) ;
11212 note( 'Leaving tests_info_date_from_uid()' ) ;
11213
11214 return ;
11215}
11216
11217sub info_date_from_uid
11218{
11219
11220 #my $first_uid = $msgs_all[ 0 ] ;
11221 #my $first_idate = $fetch{ $first_uid }->{'INTERNALDATE'} ;
11222 #my $first_epoch = epoch( $first_idate ) ;
11223 #my $first_days = ( $timestart_int - $first_epoch ) / $NB_SECONDS_IN_A_DAY ;
11224 #myprint( "\nOldest msg has UID $first_uid INTERNALDATE $first_idate EPOCH $first_epoch DAYS AGO $first_days\n" ) ;
11225}
11226
11227
11228sub lastuid
11229{
11230 my $imap = shift ;
11231 my $folder = shift ;
11232 my $lastuid_guess = shift ;
11233 my $lastuid ;
11234
11235 # rfc3501: The only reliable way to identify recent messages is to
11236 # look at message flags to see which have the \Recent flag
11237 # set, or to do a SEARCH RECENT.
11238 # SEARCH RECENT doesn't work this way on courrier.
11239
11240 my @recent_messages ;
11241 # SEARCH RECENT for each transfer can be expensive with a big folder
11242 # Call commented for now
11243 #@recent_messages = $imap->recent( ) ;
11244 #myprint( "Recent: @recent_messages\n" ) ;
11245
11246 my $max_recent ;
11247 $max_recent = max( @recent_messages ) ;
11248
11249 if ( defined $max_recent and ($lastuid_guess <= $max_recent ) ) {
11250 $lastuid = $max_recent ;
11251 }else{
11252 $lastuid = $lastuid_guess
11253 }
11254 return( $lastuid ) ;
11255}
11256
11257sub size_filtered
11258{
11259 my( $h1_size, $h1_msg, $h1_fold, $h2_fold ) = @_ ;
11260
11261 $h1_size = 0 if ( ! $h1_size ) ; # null if empty or undef
11262 if ( defined $sync->{ maxsize } and $h1_size > $sync->{ maxsize } ) {
11263 myprint( "msg $h1_fold/$h1_msg skipped ($h1_size exceeds maxsize limit $sync->{ maxsize } bytes)\n" ) ;
11264 $sync->{ total_bytes_skipped } += $h1_size;
11265 $sync->{ nb_msg_skipped } += 1;
11266 return( 1 ) ;
11267 }
11268 if ( defined $minsize and $h1_size <= $minsize ) {
11269 myprint( "msg $h1_fold/$h1_msg skipped ($h1_size smaller than minsize $minsize bytes)\n" ) ;
11270 $sync->{ total_bytes_skipped } += $h1_size;
11271 $sync->{ nb_msg_skipped } += 1;
11272 return( 1 ) ;
11273 }
11274 return( 0 ) ;
11275}
11276
11277sub message_exists
11278{
11279 my( $imap, $msg ) = @_ ;
11280 return( 1 ) if not $imap->Uid( ) ;
11281
11282 my $search_uid ;
11283 ( $search_uid ) = $imap->search( "UID $msg" ) ;
11284 #myprint( "$search ? $msg\n" ) ;
11285 return( 1 ) if ( $search_uid eq $msg ) ;
11286 return( 0 ) ;
11287}
11288
11289
11290# Globals
11291# $sync->{ total_bytes_skipped }
11292# $sync->{ nb_msg_skipped }
11293# $mysync->{ h1_nb_msg_processed }
11294sub stats_update_skip_message
11295{
11296 my $mysync = shift ; # to be used
11297 my $h1_size = shift ;
11298
11299 $mysync->{ total_bytes_skipped } += $h1_size ;
11300 $mysync->{ nb_msg_skipped } += 1 ;
11301 $mysync->{ h1_nb_msg_processed } +=1 ;
11302 return ;
11303}
11304
11305sub copy_message
11306{
11307 # copy
11308
11309 my ( $mysync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) = @_ ;
11310 ( $mysync->{ debug } or $mysync->{dry} )
11311 and myprint( "msg $h1_fold/$h1_msg copying to $h2_fold $mysync->{dry_message} " . eta( $mysync ) . "\n" ) ;
11312
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011313 if ( $mysync->{dry1} )
11314 {
11315 $mysync->{ h1_nb_msg_processed } +=1 ;
11316 $nb_msg_skipped_dry_mode += 1 ;
11317 return ;
11318 }
11319
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011320 my $h1_size = $h1_fir_ref->{$h1_msg}->{'RFC822.SIZE'} || 0 ;
11321 my $h1_flags = $h1_fir_ref->{$h1_msg}->{'FLAGS'} || q{} ;
11322 my $h1_idate = $h1_fir_ref->{$h1_msg}->{'INTERNALDATE'} || q{} ;
11323
11324
11325 if ( size_filtered( $h1_size, $h1_msg, $h1_fold, $h2_fold ) ) {
11326 $mysync->{ h1_nb_msg_processed } +=1 ;
11327 return ;
11328 }
11329
11330 debugsleep( $mysync ) ;
11331 myprint( "- msg $h1_fold/$h1_msg S[$h1_size] F[$h1_flags] I[$h1_idate] has RFC822.SIZE null!\n" ) if ( ! $h1_size ) ;
11332
11333 if ( $checkmessageexists and not message_exists( $mysync->{imap1}, $h1_msg ) ) {
11334 stats_update_skip_message( $mysync, $h1_size ) ;
11335 return ;
11336 }
11337 myprint( debugmemory( $mysync, " at C1" ) ) ;
11338
11339 my ( $string, $string_len ) ;
11340 ( $string_len ) = message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, \$string ) ;
11341
11342 myprint( debugmemory( $mysync, " at C2" ) ) ;
11343
11344 # not defined or empty $string
11345 if ( ( not $string ) or ( not $string_len ) ) {
11346 myprint( "- msg $h1_fold/$h1_msg skipped.\n" ) ;
11347 stats_update_skip_message( $mysync, $h1_size ) ;
11348 return ;
11349 }
11350
11351 # Lines too long (or not enough) => do no copy or fix
11352 if ( ( defined $maxlinelength ) or ( defined $minmaxlinelength ) ) {
11353 $string = linelengthstuff( $string, $h1_fold, $h1_msg, $string_len, $h1_size, $h1_flags, $h1_idate ) ;
11354 if ( not defined $string ) {
11355 stats_update_skip_message( $mysync, $h1_size ) ;
11356 return ;
11357 }
11358 }
11359
11360 my $h1_date = date_for_host2( $h1_msg, $h1_idate ) ;
11361
11362 ( $mysync->{ debug } or $debugflags ) and
11363 myprint( "Host1: flags init msg $h1_fold/$h1_msg date [$h1_date] flags [$h1_flags] size [$h1_size]\n" ) ;
11364
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011365 $h1_flags = flags_for_host2( $mysync, $h1_flags, $permanentflags2 ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011366
11367 ( $mysync->{ debug } or $debugflags ) and
11368 myprint( "Host1: flags filt msg $h1_fold/$h1_msg date [$h1_date] flags [$h1_flags] size [$h1_size]\n" ) ;
11369
11370 $h1_date = undef if ( $h1_date eq q{} ) ;
11371
11372 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 ) ;
11373
11374
11375
11376 if ( $new_id and $syncflagsaftercopy ) {
11377 sync_flags_after_copy( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $new_id, $permanentflags2 ) ;
11378 }
11379
11380 myprint( debugmemory( $mysync, " at C3" ) ) ;
11381
11382 return $new_id ;
11383}
11384
11385
11386
11387sub linelengthstuff
11388{
11389 my( $string, $h1_fold, $h1_msg, $string_len, $h1_size, $h1_flags, $h1_idate ) = @_ ;
11390 my $maxlinelength_string = max_line_length( $string ) ;
11391 $debugmaxlinelength and myprint( "msg $h1_fold/$h1_msg maxlinelength: $maxlinelength_string\n" ) ;
11392
11393 if ( ( defined $minmaxlinelength ) and ( $maxlinelength_string <= $minmaxlinelength ) ) {
11394 my $subject = subject( $string ) ;
11395 $debugdev and myprint( "- msg $h1_fold/$h1_msg skipped S[$h1_size] F[$h1_flags] I[$h1_idate] "
11396 . "(Subject:[$subject]) (max line length under minmaxlinelength $minmaxlinelength bytes)\n" ) ;
11397 return ;
11398 }
11399
11400 if ( ( defined $maxlinelength ) and ( $maxlinelength_string > $maxlinelength ) ) {
11401 my $subject = subject( $string ) ;
11402 if ( $maxlinelengthcmd ) {
11403 $string = pipemess( $string, $maxlinelengthcmd ) ;
11404 # string undef means something was bad.
11405 if ( not ( defined $string ) ) {
11406 myprint( "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate] "
11407 . "(Subject:[$subject]) could not be successfully transformed by --maxlinelengthcmd option\n" ) ;
11408 return ;
11409 }else{
11410 return $string ;
11411 }
11412 }
11413 myprint( "- msg $h1_fold/$h1_msg skipped S[$h1_size] F[$h1_flags] I[$h1_idate] "
11414 . "(Subject:[$subject]) (line length exceeds maxlinelength $maxlinelength bytes)\n" ) ;
11415 return ;
11416 }
11417 return $string ;
11418}
11419
11420
11421sub message_for_host2
11422{
11423
11424# global variable list:
11425# @skipmess
11426# @regexmess
11427# @pipemess
11428# $debugcontent
11429# $debug
11430#
11431# API current
11432#
11433# at failure:
11434# * return nothing ( will then be undef or () )
11435# * $string_ref content is undef or empty
11436# at success:
11437# * return string length ($string_ref content length)
11438# * $string_ref content filled with message
11439
11440# API future
11441#
11442#
11443 my ( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ) = @_ ;
11444
11445 # abort when missing a parameter
11446 if ( ( ! $mysync ) or ( ! $h1_msg ) or ( ! $h1_fold ) or ( ! defined $h1_size )
11447 or ( ! defined $h1_flags) or ( ! defined $h1_idate )
11448 or ( ! $h1_fir_ref) or ( ! $string_ref ) )
11449 {
11450 return ;
11451 }
11452
11453 myprint( debugmemory( $mysync, " at M1" ) ) ;
11454
11455
11456 my $string_ok = $mysync->{imap1}->message_to_file( $string_ref, $h1_msg ) ;
11457
11458 myprint( debugmemory( $mysync, " at M2" ) ) ;
11459
11460 my $string_len = length_ref( $string_ref ) ;
11461
11462
11463 unless ( defined $string_ok and $string_len ) {
11464 # undef or 0 length
11465 my $error = join q{},
11466 "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate] could not be fetched: ",
11467 $mysync->{imap1}->LastError || q{}, "\n" ;
11468 errors_incr( $mysync, $error ) ;
11469 $mysync->{ h1_nb_msg_processed } +=1 ;
11470 return ;
11471 }
11472
11473 if ( @skipmess ) {
11474 my $match = skipmess( ${ $string_ref } ) ;
11475 # string undef means the eval regex was bad.
11476 if ( not ( defined $match ) ) {
11477 myprint(
11478 "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
11479 . " could not be skipped by --skipmess option, bad regex\n" ) ;
11480 return ;
11481 }
11482 if ( $match ) {
11483 my $subject = subject( ${ $string_ref } ) ;
11484 myprint( "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
11485 . " (Subject:[$subject]) skipped by --skipmess\n" ) ;
11486 return ;
11487 }
11488 }
11489
11490 if ( @regexmess ) {
11491 ${ $string_ref } = regexmess( ${ $string_ref } ) ;
11492 # string undef means the eval regex was bad.
11493 if ( not ( defined ${ $string_ref } ) ) {
11494 myprint(
11495 "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
11496 . " could not be transformed by --regexmess\n" ) ;
11497 return ;
11498 }
11499 }
11500
11501 if ( @pipemess ) {
11502 ${ $string_ref } = pipemess( ${ $string_ref }, @pipemess ) ;
11503 # string undef means something was bad.
11504 if ( not ( defined ${ $string_ref } ) ) {
11505 myprint(
11506 "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
11507 . " could not be successfully transformed by --pipemess option\n" ) ;
11508 return ;
11509 }
11510 }
11511
11512 if ( $mysync->{addheader} and defined $h1_fir_ref->{$h1_msg}->{'NO_HEADER'} ) {
11513 my $header = add_header( $h1_msg ) ;
11514 $mysync->{ debug } and myprint( "msg $h1_fold/$h1_msg adding custom header [$header]\n" ) ;
11515 ${ $string_ref } = $header . "\r\n" . ${ $string_ref } ;
11516 }
11517
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020011518 if ( ( defined $mysync->{ truncmess } ) and is_integer( $mysync->{ truncmess } ) )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011519 {
11520 ${ $string_ref } = truncmess( ${ $string_ref }, $mysync->{ truncmess } ) ;
11521 }
11522
11523 $string_len = length_ref( $string_ref ) ;
11524
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010011525 $mysync->{ debugcontent } and myprint( debugcontent( $mysync, $string_ref ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011526
11527 myprint( debugmemory( $mysync, " at M3" ) ) ;
11528
11529 return $string_len ;
11530}
11531
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010011532sub tests_debugcontent
11533{
11534 note( 'Entering tests_debugcontent()' ) ;
11535
11536 is( undef, debugcontent( ), 'debugcontent: no args => undef' ) ;
11537 my $mysync = { } ;
11538 is( undef, debugcontent( $mysync ), 'debugcontent: undef => undef' ) ;
11539 is( undef, debugcontent( $mysync, 'mm' ), 'debugcontent: undef, mm => undef' ) ;
11540 #my $string_ref = \'zztop' ;
11541 my $string = '================================================================================
11542F message content begin next line (2 characters long)
11543mm
11544F message content ended on previous line
11545================================================================================
11546' ;
11547 is( $string, debugcontent( $mysync, \'mm' ), 'debugcontent: undef, mm => mm' ) ;
11548
11549 note( 'Leaving tests_debugcontent()' ) ;
11550 return ;
11551}
11552
11553sub debugcontent
11554{
11555 my $mysync = shift @ARG ;
11556 if ( ! defined $mysync ) { return ; }
11557
11558 my $string_ref = shift @ARG ;
11559 if ( ! defined $string_ref ) { return ; }
11560 if ( 'SCALAR' ne ref( $string_ref ) ) { return ; }
11561
11562 my $string_len = length_ref( $string_ref ) ;
11563
11564 my $string = join( '',
11565 q{=} x $STD_CHAR_PER_LINE, "\n",
11566 "F message content begin next line ($string_len characters long)\n",
11567 ${ $string_ref },
11568 "\nF message content ended on previous line\n", q{=} x $STD_CHAR_PER_LINE, "\n",
11569 ) ;
11570
11571 return $string ;
11572}
11573
11574
11575
11576
11577
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010011578sub tests_truncmess
11579{
11580 note( 'Entering tests_truncmess()' ) ;
11581
11582 is( undef, truncmess( ), 'truncmess: no args => undef' ) ;
11583 is( 'abc', truncmess( 'abc' ), 'truncmess: abc => abc' ) ;
11584 is( 'ab', truncmess( 'abc', 2 ), 'truncmess: abc 2 => ab' ) ;
11585 is( 'abc', truncmess( 'abc', 3 ), 'truncmess: abc 3 => abc' ) ;
11586 is( 'abc', truncmess( 'abc', 4 ), 'truncmess: abc 4 => abc' ) ;
11587 is( '12345', truncmess( "123456789\n", 5 ), 'truncmess: "123456789\n", 5 => 12345' ) ;
11588 is( "123456789\n" x 5000, truncmess( "123456789\n" x 100000, 50000 ), 'truncmess: "123456789\n" x 100000, 50000 => "123456789\n" x 5000' ) ;
11589 note( 'Leaving tests_truncmess()' ) ;
11590 return ;
11591}
11592
11593sub truncmess
11594{
11595 my $string = shift ;
11596 my $length = shift ;
11597
11598 if ( not defined $string ) { return ; }
11599 if ( not defined $length ) { return $string ; }
11600
11601 $string = substr $string, 0, $length ;
11602 return $string ;
11603}
11604
11605sub tests_message_for_host2
11606{
11607 note( 'Entering tests_message_for_host2()' ) ;
11608
11609
11610 my ( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ) ;
11611
11612 is( undef, message_for_host2( ), q{message_for_host2: no args} ) ;
11613 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} ) ;
11614
11615 require_ok( "Test::MockObject" ) ;
11616 my $imapT = Test::MockObject->new( ) ;
11617 $mysync->{imap1} = $imapT ;
11618 my $string ;
11619
11620 $h1_msg = 1 ;
11621 $h1_fold = 'FoldFoo';
11622 $h1_size = 9 ;
11623 $h1_flags = q{} ;
11624 $h1_idate = '10-Jul-2015 09:00:00 +0200' ;
11625 $h1_fir_ref = {} ;
11626 $string_ref = \$string ;
11627 $imapT->mock( 'message_to_file',
11628 sub {
11629 my ( $imap, $mystring_ref, $msg ) = @_ ;
11630 ${$mystring_ref} = 'blablabla' ;
11631 return length ${$mystring_ref} ;
11632 }
11633 ) ;
11634 is( 9, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
11635 q{message_for_host2: msg 1 == "blablabla", length} ) ;
11636 is( 'blablabla', $string, q{message_for_host2: msg 1 == "blablabla", value} ) ;
11637
11638 # so far so good
11639 # now the --pipemess stuff
11640
11641 SKIP: {
11642 Readonly my $NB_WIN_tests_message_for_host2 => 0 ;
11643 skip( 'Not on MSWin32', $NB_WIN_tests_message_for_host2 ) if ('MSWin32' ne $OSNAME) ;
11644 # Windows
11645 # "type" command does not accept redirection of STDIN with <
11646 # "sort" does
11647
11648 } ;
11649
11650 SKIP: {
11651 Readonly my $NB_UNX_tests_message_for_host2 => 6 ;
11652 skip( 'Not on Unix', $NB_UNX_tests_message_for_host2 ) if ('MSWin32' eq $OSNAME) ;
11653 # Unix
11654
11655 # no change by cat
11656 @pipemess = ( 'cat' ) ;
11657 is( 9, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
11658 q{message_for_host2: --pipemess 'cat', length} ) ;
11659 is( 'blablabla', $string, q{message_for_host2: --pipemess 'cat', value} ) ;
11660
11661
11662 # failure by false
11663 @pipemess = ( 'false' ) ;
11664 is( undef, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
11665 q{message_for_host2: --pipemess 'false', length} ) ;
11666 is( undef, $string, q{message_for_host2: --pipemess 'false', value} ) ;
11667
11668 # failure by true since no output
11669 @pipemess = ( 'true' ) ;
11670 is( undef, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
11671 q{message_for_host2: --pipemess 'true', length} ) ;
11672 is( undef, $string, q{message_for_host2: --pipemess 'true', value} ) ;
11673 }
11674
11675 note( 'Leaving tests_message_for_host2()' ) ;
11676 return ;
11677}
11678
11679sub tests_labels_remove_subfolder1
11680{
11681 note( 'Entering tests_labels_remove_subfolder1()' ) ;
11682 is( undef, labels_remove_subfolder1( ), 'labels_remove_subfolder1: no parameters => undef' ) ;
11683 is( 'Blabla', labels_remove_subfolder1( 'Blabla' ), 'labels_remove_subfolder1: one parameter Blabla => Blabla' ) ;
11684 is( 'Blan blue', labels_remove_subfolder1( 'Blan blue' ), 'labels_remove_subfolder1: one parameter Blan blue => Blan blue' ) ;
11685 is( '\Bla "Blan blan" Blabla', labels_remove_subfolder1( '\Bla "Blan blan" Blabla' ),
11686 'labels_remove_subfolder1: one parameter \Bla "Blan blan" Blabla => \Bla "Blan blan" Blabla' ) ;
11687
11688 is( 'Bla', labels_remove_subfolder1( 'Subf/Bla', 'Subf' ), 'labels_remove_subfolder1: Subf/Bla Subf => "Bla"' ) ;
11689
11690
11691 is( '"\\\\Bla"', labels_remove_subfolder1( '"\\\\Bla"', 'Subf' ), 'labels_remove_subfolder1: "\\\\Bla" Subf => "\\\\Bla"' ) ;
11692
11693 is( 'Bla Kii', labels_remove_subfolder1( 'Subf/Bla Subf/Kii', 'Subf' ),
11694 'labels_remove_subfolder1: Subf/Bla Subf/Kii, Subf => "Bla" "Kii"' ) ;
11695
11696 is( '"\\\\Bla" Kii', labels_remove_subfolder1( '"\\\\Bla" Subf/Kii', 'Subf' ),
11697 'labels_remove_subfolder1: "\\\\Bla" Subf/Kii Subf => "\\\\Bla" Kii' ) ;
11698
11699 is( '"Blan blan"', labels_remove_subfolder1( '"Subf/Blan blan"', 'Subf' ),
11700 'labels_remove_subfolder1: "Subf/Blan blan" Subf => "Blan blan"' ) ;
11701
11702 is( '"\\\\Loo" "Blan blan" Kii', labels_remove_subfolder1( '"\\\\Loo" "Subf/Blan blan" Subf/Kii', 'Subf' ),
11703 'labels_remove_subfolder1: "\\\\Loo" "Subf/Blan blan" Subf/Kii + Subf => "\\\\Loo" "Blan blan" Kii' ) ;
11704
11705 is( '"\\\\Inbox"', labels_remove_subfolder1( 'Subf/INBOX', 'Subf' ),
11706 'labels_remove_subfolder1: Subf/INBOX + Subf => "\\\\Inbox"' ) ;
11707
11708 is( '"\\\\Loo" "Blan blan" Kii "\\\\Inbox"', labels_remove_subfolder1( '"\\\\Loo" "Subf/Blan blan" Subf/Kii Subf/INBOX', 'Subf' ),
11709 'labels_remove_subfolder1: "\\\\Loo" "Subf/Blan blan" Subf/Kii Subf/INBOX + Subf => "\\\\Loo" "Blan blan" Kii "\\\\Inbox"' ) ;
11710
11711
11712 note( 'Leaving tests_labels_remove_subfolder1()' ) ;
11713 return ;
11714}
11715
11716
11717
11718sub labels_remove_subfolder1
11719{
11720 my $labels = shift ;
11721 my $subfolder1 = shift ;
11722
11723 if ( not defined $labels ) { return ; }
11724 if ( not defined $subfolder1 ) { return $labels ; }
11725
11726 my @labels = quotewords('\s+', 1, $labels ) ;
11727 #myprint( "@labels\n" ) ;
11728 my @labels_subfolder2 ;
11729
11730 foreach my $label ( @labels )
11731 {
11732 if ( $label =~ m{zzzzzzzzzz} )
11733 {
11734 # \Seen \Deleted ... stay the same
11735 push @labels_subfolder2, $label ;
11736 }
11737 else
11738 {
11739 # Remove surrounding quotes if any, to add them again in case of space
11740 $label = join( q{}, quotewords('\s+', 0, $label ) ) ;
11741 $label =~ s{$subfolder1/?}{} ;
11742 if ( 'INBOX' eq $label )
11743 {
11744 push @labels_subfolder2, q{"\\\\Inbox"} ;
11745 }
11746 elsif ( $label =~ m{\\} )
11747 {
11748 push @labels_subfolder2, qq{"\\$label"} ;
11749 }
11750 elsif ( $label =~ m{ } )
11751 {
11752 push @labels_subfolder2, qq{"$label"} ;
11753 }
11754 else
11755 {
11756 push @labels_subfolder2, $label ;
11757 }
11758 }
11759 }
11760
11761 my $labels_subfolder2 = join( ' ', sort uniq( @labels_subfolder2 ) ) ;
11762
11763 return $labels_subfolder2 ;
11764}
11765
11766sub tests_labels_remove_special
11767{
11768 note( 'Entering tests_labels_remove_special()' ) ;
11769
11770 is( undef, labels_remove_special( ), 'labels_remove_special: no parameters => undef' ) ;
11771 is( q{}, labels_remove_special( q{} ), 'labels_remove_special: empty string => empty string' ) ;
11772 is( q{}, labels_remove_special( '"\\\\Inbox"' ), 'labels_remove_special:"\\\\Inbox" => empty string' ) ;
11773 is( q{}, labels_remove_special( '"\\\\Inbox" "\\\\Starred"' ), 'labels_remove_special:"\\\\Inbox" "\\\\Starred" => empty string' ) ;
11774 is( 'Bar Foo', labels_remove_special( 'Foo Bar' ), 'labels_remove_special:Foo Bar => Bar Foo' ) ;
11775 is( 'Bar Foo', labels_remove_special( 'Foo Bar "\\\\Inbox"' ), 'labels_remove_special:Foo Bar "\\\\Inbox" => Bar Foo' ) ;
11776 note( 'Leaving tests_labels_remove_special()' ) ;
11777 return ;
11778}
11779
11780
11781
11782
11783sub labels_remove_special
11784{
11785 my $labels = shift ;
11786
11787 if ( not defined $labels ) { return ; }
11788
11789 my @labels = quotewords('\s+', 1, $labels ) ;
11790 myprint( "labels before remove_non_folded: @labels\n" ) ;
11791 my @labels_remove_special ;
11792
11793 foreach my $label ( @labels )
11794 {
11795 if ( $label =~ m{^\"\\\\} )
11796 {
11797 # not kept
11798 }
11799 else
11800 {
11801 push @labels_remove_special, $label ;
11802 }
11803 }
11804
11805 my $labels_remove_special = join( ' ', sort @labels_remove_special ) ;
11806
11807 return $labels_remove_special ;
11808}
11809
11810
11811sub tests_labels_add_subfolder2
11812{
11813 note( 'Entering tests_labels_add_subfolder2()' ) ;
11814 is( undef, labels_add_subfolder2( ), 'labels_add_subfolder2: no parameters => undef' ) ;
11815 is( 'Blabla', labels_add_subfolder2( 'Blabla' ), 'labels_add_subfolder2: one parameter Blabla => Blabla' ) ;
11816 is( 'Blan blue', labels_add_subfolder2( 'Blan blue' ), 'labels_add_subfolder2: one parameter Blan blue => Blan blue' ) ;
11817 is( '\Bla "Blan blan" Blabla', labels_add_subfolder2( '\Bla "Blan blan" Blabla' ),
11818 'labels_add_subfolder2: one parameter \Bla "Blan blan" Blabla => \Bla "Blan blan" Blabla' ) ;
11819
11820 is( 'Subf/Bla', labels_add_subfolder2( 'Bla', 'Subf' ), 'labels_add_subfolder2: Bla Subf => "Subf/Bla"' ) ;
11821
11822
11823 is( 'Subf/\Bla', labels_add_subfolder2( '\\\\Bla', 'Subf' ), 'labels_add_subfolder2: \Bla Subf => \Bla' ) ;
11824
11825 is( 'Subf/Bla Subf/Kii', labels_add_subfolder2( 'Bla Kii', 'Subf' ),
11826 'labels_add_subfolder2: Bla Kii Subf => "Subf/Bla" "Subf/Kii"' ) ;
11827
11828 is( 'Subf/Kii Subf/\Bla', labels_add_subfolder2( '\\\\Bla Kii', 'Subf' ),
11829 'labels_add_subfolder2: \Bla Kii Subf => \Bla Subf/Kii' ) ;
11830
11831 is( '"Subf/Blan blan"', labels_add_subfolder2( '"Blan blan"', 'Subf' ),
11832 'labels_add_subfolder2: "Blan blan" Subf => "Subf/Blan blan"' ) ;
11833
11834 is( '"Subf/Blan blan" Subf/Kii Subf/\Loo', labels_add_subfolder2( '\\\\Loo "Blan blan" Kii', 'Subf' ),
11835 'labels_add_subfolder2: \Loo "Blan blan" Kii + Subf => "Subf/Blan blan" Subf/Kii Subf/\Loo' ) ;
11836
11837 # "\\Inbox" is special, add to subfolder INBOX also because Gmail will but ...
11838 is( '"Subf/\\\\Inbox" Subf/INBOX', labels_add_subfolder2( '"\\\\Inbox"', 'Subf' ),
11839 'labels_add_subfolder2: "\\\\Inbox" Subf => "Subf/\\\\Inbox" Subf/INBOX' ) ;
11840
11841 # but not with INBOX folder
11842 is( '"Subf/\\\\Inbox"', labels_add_subfolder2( '"\\\\Inbox"', 'Subf', 'INBOX' ),
11843 'labels_add_subfolder2: "\\\\Inbox" Subf INBOX => "Subf/\\\\Inbox"' ) ;
11844
11845 # two times => one time
11846 is( '"Subf/\\\\Inbox" Subf/INBOX', labels_add_subfolder2( '"\\\\Inbox" "\\\\Inbox"', 'Subf' ),
11847 'labels_add_subfolder2: "\\\\Inbox" "\\\\Inbox" Subf => "Subf/\\\\Inbox"' ) ;
11848
11849 is( '"Subf/\\\\Starred"', labels_add_subfolder2( '"\\\\Starred"', 'Subf' ),
11850 'labels_add_subfolder2: "\\\\Starred" Subf => "Subf/\\\\Starred"' ) ;
11851
11852 note( 'Leaving tests_labels_add_subfolder2()' ) ;
11853 return ;
11854}
11855
11856sub labels_add_subfolder2
11857{
11858 my $labels = shift ;
11859 my $subfolder2 = shift ;
11860 my $h1_folder = shift || q{} ;
11861
11862 if ( not defined $labels ) { return ; }
11863 if ( not defined $subfolder2 ) { return $labels ; }
11864
11865 # Isn't it messy?
11866 if ( 'INBOX' eq $h1_folder )
11867 {
11868 $labels .= ' "\\\\Inbox"' ;
11869 }
11870
11871 my @labels = uniq( quotewords('\s+', 1, $labels ) ) ;
11872 myprint( "labels before subfolder2: @labels\n" ) ;
11873 my @labels_subfolder2 ;
11874
11875
11876 foreach my $label ( @labels )
11877 {
11878 # Isn't it more messy?
11879 if ( ( q{"\\\\Inbox"} eq $label ) and ( 'INBOX' ne $h1_folder ) )
11880 {
11881 if ( $subfolder2 =~ m{ } )
11882 {
11883 push @labels_subfolder2, qq{"$subfolder2/INBOX"} ;
11884 }
11885 else
11886 {
11887 push @labels_subfolder2, "$subfolder2/INBOX" ;
11888 }
11889 }
11890 if ( $label =~ m{^\"\\\\} )
11891 {
11892 # \Seen \Deleted ... stay the same
11893 #push @labels_subfolder2, $label ;
11894 # Remove surrounding quotes if any, to add them again
11895 $label = join( q{}, quotewords('\s+', 0, $label ) ) ;
11896 push @labels_subfolder2, qq{"$subfolder2/\\$label"} ;
11897
11898 }
11899 else
11900 {
11901 # Remove surrounding quotes if any, to add them again in case of space
11902 $label = join( q{}, quotewords('\s+', 0, $label ) ) ;
11903 if ( $label =~ m{ } )
11904 {
11905 push @labels_subfolder2, qq{"$subfolder2/$label"} ;
11906 }
11907 else
11908 {
11909 push @labels_subfolder2, "$subfolder2/$label" ;
11910 }
11911 }
11912 }
11913
11914 my $labels_subfolder2 = join( ' ', sort @labels_subfolder2 ) ;
11915
11916 return $labels_subfolder2 ;
11917}
11918
11919sub tests_labels
11920{
11921 note( 'Entering tests_labels()' ) ;
11922
11923 is( undef, labels( ), 'labels: no parameters => undef' ) ;
11924 is( undef, labels( undef ), 'labels: undef => undef' ) ;
11925 require_ok( "Test::MockObject" ) ;
11926 my $myimap = Test::MockObject->new( ) ;
11927
11928 $myimap->mock( 'fetch_hash',
11929 sub {
11930 return(
11931 { '1' => {
11932 'X-GM-LABELS' => '\Seen Blabla'
11933 }
11934 }
11935 ) ;
11936 }
11937 ) ;
11938 $myimap->mock( 'Debug' , sub { } ) ;
11939 $myimap->mock( 'Unescape', sub { return Mail::IMAPClient::Unescape( @_ ) } ) ; # real one
11940
11941 is( undef, labels( $myimap ), 'labels: one parameter => undef' ) ;
11942 is( '\Seen Blabla', labels( $myimap, '1' ), 'labels: $mysync UID_1 => \Seen Blabla' ) ;
11943
11944 note( 'Leaving tests_labels()' ) ;
11945 return ;
11946}
11947
11948sub labels
11949{
11950 my ( $myimap, $uid ) = @ARG ;
11951
11952 if ( not all_defined( $myimap, $uid ) ) {
11953 return ;
11954 }
11955
11956 my $hash = $myimap->fetch_hash( [ $uid ], 'X-GM-LABELS' ) ;
11957
11958 my $labels = $hash->{ $uid }->{ 'X-GM-LABELS' } ;
11959 #$labels = $myimap->Unescape( $labels ) ;
11960 return $labels ;
11961}
11962
11963sub tests_synclabels
11964{
11965 note( 'Entering tests_synclabels()' ) ;
11966
11967 is( undef, synclabels( ), 'synclabels: no parameters => undef' ) ;
11968 is( undef, synclabels( undef ), 'synclabels: undef => undef' ) ;
11969 my $mysync ;
11970 is( undef, synclabels( $mysync ), 'synclabels: var undef => undef' ) ;
11971
11972 require_ok( "Test::MockObject" ) ;
11973 $mysync = {} ;
11974
11975 my $myimap1 = Test::MockObject->new( ) ;
11976 $myimap1->mock( 'fetch_hash',
11977 sub {
11978 return(
11979 { '1' => {
11980 'X-GM-LABELS' => '\Seen Blabla'
11981 }
11982 }
11983 ) ;
11984 }
11985 ) ;
11986 $myimap1->mock( 'Debug', sub { } ) ;
11987 $myimap1->mock( 'Unescape', sub { return Mail::IMAPClient::Unescape( @_ ) } ) ; # real one
11988
11989 my $myimap2 = Test::MockObject->new( ) ;
11990
11991 $myimap2->mock( 'store',
11992 sub {
11993 return 1 ;
11994 }
11995 ) ;
11996
11997
11998 $mysync->{imap1} = $myimap1 ;
11999 $mysync->{imap2} = $myimap2 ;
12000
12001 is( undef, synclabels( $mysync ), 'synclabels: fresh $mysync => undef' ) ;
12002
12003 is( undef, synclabels( $mysync, '1' ), 'synclabels: $mysync UID_1 alone => undef' ) ;
12004 is( 1, synclabels( $mysync, '1', '2' ), 'synclabels: $mysync UID_1 UID_2 => 1' ) ;
12005
12006 note( 'Leaving tests_synclabels()' ) ;
12007 return ;
12008}
12009
12010
12011sub synclabels
12012{
12013 my( $mysync, $uid1, $uid2 ) = @ARG ;
12014
12015 if ( not all_defined( $mysync, $uid1, $uid2 ) ) {
12016 return ;
12017 }
12018 my $myimap1 = $mysync->{ 'imap1' } || return ;
12019 my $myimap2 = $mysync->{ 'imap2' } || return ;
12020
12021 $mysync->{debuglabels} and $myimap1->Debug( 1 ) ;
12022 my $labels1 = labels( $myimap1, $uid1 ) ;
12023 $mysync->{debuglabels} and $myimap1->Debug( 0 ) ;
12024 $mysync->{debuglabels} and myprint( "Host1 labels: $labels1\n" ) ;
12025
12026
12027
12028 if ( $mysync->{ subfolder1 } and $labels1 )
12029 {
12030 $labels1 = labels_remove_subfolder1( $labels1, $mysync->{ subfolder1 } ) ;
12031 $mysync->{debuglabels} and myprint( "Host1 labels with subfolder1: $labels1\n" ) ;
12032 }
12033
12034 if ( $mysync->{ subfolder2 } and $labels1 )
12035 {
12036 $labels1 = labels_add_subfolder2( $labels1, $mysync->{ subfolder2 } ) ;
12037 $mysync->{debuglabels} and myprint( "Host1 labels with subfolder2: $labels1\n" ) ;
12038 }
12039
12040 my $store ;
12041 if ( $labels1 and not $mysync->{ dry } )
12042 {
12043 $mysync->{ debuglabels } and $myimap2->Debug( 1 ) ;
12044 $store = $myimap2->store( $uid2, "X-GM-LABELS ($labels1)" ) ;
12045 $mysync->{ debuglabels } and $myimap2->Debug( 0 ) ;
12046 }
12047 return $store ;
12048}
12049
12050
12051sub tests_resynclabels
12052{
12053 note( 'Entering tests_resynclabels()' ) ;
12054
12055 is( undef, resynclabels( ), 'resynclabels: no parameters => undef' ) ;
12056 is( undef, resynclabels( undef ), 'resynclabels: undef => undef' ) ;
12057 my $mysync ;
12058 is( undef, resynclabels( $mysync ), 'resynclabels: var undef => undef' ) ;
12059
12060 my ( $h1_fir_ref, $h2_fir_ref ) ;
12061
12062 $mysync->{ debuglabels } = 1 ;
12063 $h1_fir_ref->{ 11 }->{ 'X-GM-LABELS' } = '\Seen Baa Kii' ;
12064 $h2_fir_ref->{ 22 }->{ 'X-GM-LABELS' } = '\Seen Baa Kii' ;
12065
12066 # labels are equal
12067 is( 1, resynclabels( $mysync, 11, 22, $h1_fir_ref, $h2_fir_ref ),
12068 'resynclabels: $mysync UID_1 UID_2 labels are equal => 1' ) ;
12069
12070 # labels are different
12071 $h2_fir_ref->{ 22 }->{ 'X-GM-LABELS' } = '\Seen Zuu' ;
12072 require_ok( "Test::MockObject" ) ;
12073 my $myimap2 = Test::MockObject->new( ) ;
12074 $myimap2->mock( 'store',
12075 sub {
12076 return 1 ;
12077 }
12078 ) ;
12079 $myimap2->mock( 'Debug', sub { } ) ;
12080 $mysync->{imap2} = $myimap2 ;
12081
12082 is( 1, resynclabels( $mysync, 11, 22, $h1_fir_ref, $h2_fir_ref ),
12083 'resynclabels: $mysync UID_1 UID_2 labels are not equal => store => 1' ) ;
12084
12085 note( 'Leaving tests_resynclabels()' ) ;
12086 return ;
12087}
12088
12089
12090
12091sub resynclabels
12092{
12093 my( $mysync, $uid1, $uid2, $h1_fir_ref, $h2_fir_ref, $h1_folder ) = @ARG ;
12094
12095 if ( not all_defined( $mysync, $uid1, $uid2, $h1_fir_ref, $h2_fir_ref ) ) {
12096 return ;
12097 }
12098
12099 my $labels1 = $h1_fir_ref->{ $uid1 }->{ 'X-GM-LABELS' } || q{} ;
12100 my $labels2 = $h2_fir_ref->{ $uid2 }->{ 'X-GM-LABELS' } || q{} ;
12101
12102 if ( $mysync->{ subfolder1 } and $labels1 )
12103 {
12104 $labels1 = labels_remove_subfolder1( $labels1, $mysync->{ subfolder1 } ) ;
12105 }
12106
12107 if ( $mysync->{ subfolder2 } and $labels1 )
12108 {
12109 $labels1 = labels_add_subfolder2( $labels1, $mysync->{ subfolder2 }, $h1_folder ) ;
12110 $labels2 = labels_remove_special( $labels2 ) ;
12111 }
12112 $mysync->{ debuglabels } and myprint( "Host1 labels fixed: $labels1\n" ) ;
12113 $mysync->{ debuglabels } and myprint( "Host2 labels : $labels2\n" ) ;
12114
12115 my $store ;
12116 if ( $labels1 eq $labels2 )
12117 {
12118 # no sync needed
12119 $mysync->{ debuglabels } and myprint( "Labels are already equal\n" ) ;
12120 return 1 ;
12121 }
12122 elsif ( not $mysync->{ dry } )
12123 {
12124 # sync needed
12125 $mysync->{debuglabels} and $mysync->{imap2}->Debug( 1 ) ;
12126 $store = $mysync->{imap2}->store( $uid2, "X-GM-LABELS ($labels1)" ) ;
12127 $mysync->{debuglabels} and $mysync->{imap2}->Debug( 0 ) ;
12128 }
12129
12130 return $store ;
12131}
12132
12133sub tests_uniq
12134{
12135 note( 'Entering tests_uniq()' ) ;
12136
12137 is( 0, uniq( ), 'uniq: undef => 0' ) ;
12138 is_deeply( [ 'one' ], [ uniq( 'one' ) ], 'uniq: one => one' ) ;
12139 is_deeply( [ 'one' ], [ uniq( 'one', 'one' ) ], 'uniq: one one => one' ) ;
12140 is_deeply( [ 'one', 'two' ], [ uniq( 'one', 'one', 'two', 'one', 'two' ) ], 'uniq: one one two one two => one two' ) ;
12141 note( 'Leaving tests_uniq()' ) ;
12142 return ;
12143}
12144
12145sub uniq
12146{
12147 my @list = @ARG ;
12148 my %seen = ( ) ;
12149 my @uniq = ( ) ;
12150 foreach my $item ( @list ) {
12151 if ( ! $seen{ $item } ) {
12152 $seen{ $item } = 1 ;
12153 push( @uniq, $item ) ;
12154 }
12155 }
12156 return @uniq ;
12157}
12158
12159
12160sub length_ref
12161{
12162 my $string_ref = shift ;
12163 my $string_len = defined ${ $string_ref } ? length( ${ $string_ref } ) : q{} ; # length or empty string
12164 return $string_len ;
12165}
12166
12167sub tests_length_ref
12168{
12169 note( 'Entering tests_length_ref()' ) ;
12170
12171 my $notdefined ;
12172 is( q{}, length_ref( \$notdefined ), q{length_ref: value not defined} ) ;
12173 my $notref ;
12174 is( q{}, length_ref( $notref ), q{length_ref: param not a ref} ) ;
12175
12176 my $lala = 'lala' ;
12177 is( 4, length_ref( \$lala ), q{length_ref: lala length == 4} ) ;
12178 is( 4, length_ref( \'lili' ), q{length_ref: lili length == 4} ) ;
12179
12180 note( 'Leaving tests_length_ref()' ) ;
12181 return ;
12182}
12183
12184sub date_for_host2
12185{
12186 my( $h1_msg, $h1_idate ) = @_ ;
12187
12188 my $h1_date = q{} ;
12189
12190 if ( $syncinternaldates ) {
12191 $h1_date = $h1_idate ;
12192 $sync->{ debug } and myprint( "internal date from host1: [$h1_date]\n" ) ;
12193 $h1_date = good_date( $h1_date ) ;
12194 $sync->{ debug } and myprint( "internal date from host1: [$h1_date] (fixed)\n" ) ;
12195 }
12196
12197 if ( $idatefromheader ) {
12198 $h1_date = $sync->{imap1}->get_header( $h1_msg, 'Date' ) ;
12199 $sync->{ debug } and myprint( "header date from host1: [$h1_date]\n" ) ;
12200 $h1_date = good_date( $h1_date ) ;
12201 $sync->{ debug } and myprint( "header date from host1: [$h1_date] (fixed)\n" ) ;
12202 }
12203
12204 return( $h1_date ) ;
12205}
12206
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012207
12208sub subject
12209{
12210 my $string = shift ;
12211 my $subject = q{} ;
12212
12213 my $header = extract_header( $string ) ;
12214
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012215 if( $header =~ m/^Subject:[ \t]*([^\n\r]*)\r?$/msx ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012216 #myprint( "MMM[$1]\n" ) ;
12217 $subject = $1 ;
12218 }
12219 return( $subject ) ;
12220}
12221
12222sub tests_subject
12223{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012224 note( 'Entering tests_subject()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012225
12226 ok( q{} eq subject( q{} ), 'subject: null') ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012227 is( '', subject( 'Subject:' ), 'Subject:') ;
12228 is( '', subject( "Subject:\r\n" ), 'Subject:\r\n') ;
12229 ok( 'toto le hero' eq subject( 'Subject: toto le hero' ), 'Subject: toto le hero') ;
12230 ok( 'toto le hero' eq subject( 'Subject:toto le hero' ), 'Subject:toto le hero') ;
12231 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 +010012232
12233 my $MESS ;
12234 $MESS = <<'EOF';
12235From: lalala
12236Subject: toto le hero
12237Date: zzzzzz
12238
12239Boogie boogie
12240EOF
12241 ok( 'toto le hero' eq subject( $MESS ), 'subject: toto le hero 2') ;
12242
12243 $MESS = <<'EOF';
12244Subject: toto le hero
12245From: lalala
12246Date: zzzzzz
12247
12248Boogie boogie
12249EOF
12250 ok( 'toto le hero' eq subject( $MESS ), 'subject: toto le hero 3') ;
12251
12252
12253 $MESS = <<'EOF';
12254From: lalala
12255Subject: cuicui
12256Date: zzzzzz
12257
12258Subject: toto le hero
12259EOF
12260 ok( 'cuicui' eq subject( $MESS ), 'subject: cuicui') ;
12261
12262 $MESS = <<'EOF';
12263From: lalala
12264Date: zzzzzz
12265
12266Subject: toto le hero
12267EOF
12268 ok( q{} eq subject( $MESS ), 'subject: null but body could') ;
12269
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012270
12271 $MESS = <<'EOF';
12272From: lalala
12273Subject:
12274Date: zzzzzz
12275
12276Subject: toto le hero
12277EOF
12278 is( '', subject( $MESS ), 'Subject:') ;
12279
12280
12281
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012282 note( 'Leaving tests_subject()' ) ;
12283 return ;
12284}
12285
12286
12287# GlobVar
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012288# $h2_uidguess
12289# ...
12290#
12291#
12292sub append_message_on_host2
12293{
12294 my( $mysync, $string_ref, $h1_fold, $h1_msg, $string_len, $h2_fold, $h1_size, $h1_flags, $h1_date, $cache_dir ) = @_ ;
12295 myprint( debugmemory( $mysync, " at A1" ) ) ;
12296
12297 my $new_id ;
12298 if ( ! $mysync->{dry} ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012299 $new_id = $mysync->{imap2}->append_string( $h2_fold, ${ $string_ref }, $h1_flags, $h1_date ) ;
12300 myprint( debugmemory( $mysync, " at A2" ) ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012301 if ( ! defined $new_id ){
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012302 my $subject = subject( ${ $string_ref } ) ;
12303 my $error_imap = $mysync->{imap2}->LastError || q{} ;
12304 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" ;
12305 errors_incr( $mysync, $error ) ;
12306 $mysync->{ h1_nb_msg_processed } +=1 ;
12307 return ;
12308 }
12309 else{
12310 # good
12311 # $new_id is an id if the IMAP server has the
12312 # UIDPLUS capability else just a ref
12313 if ( $new_id !~ m{^\d+$}x ) {
12314 $new_id = lastuid( $mysync->{imap2}, $h2_fold, $h2_uidguess ) ;
12315 }
12316 if ( $mysync->{ synclabels } ) { synclabels( $mysync, $h1_msg, $new_id ) }
12317 $h2_uidguess += 1 ;
12318 $mysync->{ total_bytes_transferred } += $string_len ;
12319 $mysync->{ nb_msg_transferred } += 1 ;
12320 $mysync->{ h1_nb_msg_processed } +=1 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012321 $mysync->{ biggest_message_transferred } = max( $string_len, $mysync->{ biggest_message_transferred } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012322
12323 my $time_spent = timesince( $mysync->{begin_transfer_time} ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010012324 my $rate = bytes_display_string_bin( $mysync->{total_bytes_transferred} / $time_spent ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012325 my $eta = eta( $mysync ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010012326 my $amount_transferred = bytes_display_string_bin( $mysync->{total_bytes_transferred} ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012327 myprintf( "msg %s/%-19s copied to %s/%-10s %.2f msgs/s %s/s %s copied %s\n",
12328 $h1_fold, "$h1_msg {$string_len}", $h2_fold, $new_id, $mysync->{nb_msg_transferred}/$time_spent, $rate,
12329 $amount_transferred,
12330 $eta );
12331 sleep_if_needed( $mysync ) ;
12332 if ( $usecache and $cacheaftercopy and $new_id =~ m{^\d+$}x ) {
12333 $debugcache and myprint( "touch $cache_dir/${h1_msg}_$new_id\n" ) ;
12334 touch( "$cache_dir/${h1_msg}_$new_id" )
12335 or croak( "Couldn't touch $cache_dir/${h1_msg}_$new_id" ) ;
12336 }
12337 if ( $mysync->{ delete1 } ) {
12338 delete_message_on_host1( $mysync, $h1_fold, $mysync->{ expungeaftereach }, $h1_msg ) ;
12339 }
12340 #myprint( "PRESS ENTER" ) and my $a = <> ;
12341
12342 return( $new_id ) ;
12343 }
12344 }
12345 else{
12346 $nb_msg_skipped_dry_mode += 1 ;
12347 $mysync->{ h1_nb_msg_processed } += 1 ;
12348 }
12349
12350 return ;
12351}
12352
12353
12354sub tests_sleep_if_needed
12355{
12356 note( 'Entering tests_sleep_if_needed()' ) ;
12357
12358 is( undef, sleep_if_needed( ), 'sleep_if_needed: no args => undef' ) ;
12359 my $mysync ;
12360 is( undef, sleep_if_needed( $mysync ), 'sleep_if_needed: arg undef => undef' ) ;
12361
12362 $mysync->{maxbytespersecond} = 1000 ;
12363 is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: maxbytespersecond only => no sleep => 0' ) ;
12364 $mysync->{begin_transfer_time} = time ; # now
12365 is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: begin_transfer_time now => no sleep => 0' ) ;
12366 $mysync->{begin_transfer_time} = time - 2 ; # 2 s before
12367 is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 0 => no sleep => 0' ) ;
12368
12369 $mysync->{total_bytes_transferred} = 2200 ;
12370 $mysync->{begin_transfer_time} = time - 2 ; # 2 s before
12371 is( '0.20', sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 2200 since 2s => sleep 0.2s' ) ;
12372 is( '0', sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 2200 since 2+2 == 4s => no sleep' ) ;
12373
12374 $mysync->{maxsleep} = 0.1 ;
12375 $mysync->{begin_transfer_time} = time - 2 ; # 2 s before again
12376 is( '0.10', sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 4000 since 2s but maxsleep 0.1s => sleep 0.1s' ) ;
12377
12378 $mysync->{maxbytesafter} = 4000 ;
12379 $mysync->{begin_transfer_time} = time - 2 ; # 2 s before again
12380 is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: maxbytesafter == total_bytes_transferred => no sleep => 0' ) ;
12381
12382 note( 'Leaving tests_sleep_if_needed()' ) ;
12383 return ;
12384}
12385
12386
12387sub sleep_if_needed
12388{
12389 my( $mysync ) = shift ;
12390
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012391 if ( ! $mysync ) {
12392 return ;
12393 }
12394 # No need to go further if there is no limit set
12395 if (
12396 not (
12397 $mysync->{maxmessagespersecond}
12398 or $mysync->{maxbytespersecond}
12399 )
12400 ) {
12401 return ;
12402 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012403
12404 $mysync->{maxsleep} = defined $mysync->{maxsleep} ? $mysync->{maxsleep} : $MAX_SLEEP ;
12405 # Must be positive
12406 $mysync->{maxsleep} = max( 0, $mysync->{maxsleep} ) ;
12407
12408 my $time_spent = timesince( $mysync->{begin_transfer_time} ) ;
12409 my $sleep_max_messages = sleep_max_messages( $mysync->{nb_msg_transferred}, $time_spent, $mysync->{maxmessagespersecond} ) ;
12410
12411 my $maxbytesafter = $mysync->{maxbytesafter} || 0 ;
12412 my $total_bytes_transferred = $mysync->{total_bytes_transferred} || 0 ;
12413 my $total_bytes_to_consider = $total_bytes_transferred - $maxbytesafter ;
12414
12415 #myprint( "maxbytesafter:$maxbytesafter\n" ) ;
12416 #myprint( "total_bytes_to_consider:$total_bytes_to_consider\n" ) ;
12417
12418 my $sleep_max_bytes = sleep_max_bytes( $total_bytes_to_consider, $time_spent, $mysync->{maxbytespersecond} ) ;
12419 my $sleep_max = min( $mysync->{maxsleep}, max( $sleep_max_messages, $sleep_max_bytes ) ) ;
12420 $sleep_max = mysprintf( "%.2f", $sleep_max ) ; # round with 2 decimals.
12421 if ( $sleep_max > 0 ) {
12422 myprint( "sleeping $sleep_max s\n" ) ;
12423 sleep $sleep_max ;
12424 # Slept
12425 return $sleep_max ;
12426 }
12427 # No sleep
12428 return 0 ;
12429}
12430
12431sub sleep_max_messages
12432{
12433 # how long we have to sleep to go under max_messages_per_second
12434 my( $nb_msg_transferred, $time_spent, $maxmessagespersecond ) = @_ ;
12435 if ( ( not defined $maxmessagespersecond ) or $maxmessagespersecond <= 0 ) { return( 0 ) } ;
12436 my $sleep = ( $nb_msg_transferred / $maxmessagespersecond ) - $time_spent ;
12437 # the sleep must be positive
12438 return( max( 0, $sleep ) ) ;
12439}
12440
12441
12442sub tests_sleep_max_messages
12443{
12444 note( 'Entering tests_sleep_max_messages()' ) ;
12445
12446 ok( 0 == sleep_max_messages( 4, 2, undef ), 'sleep_max_messages: maxmessagespersecond = undef') ;
12447 ok( 0 == sleep_max_messages( 4, 2, 0 ), 'sleep_max_messages: maxmessagespersecond = 0') ;
12448 ok( 0 == sleep_max_messages( 4, 2, $MINUS_ONE ), 'sleep_max_messages: maxmessagespersecond = -1') ;
12449 ok( 0 == sleep_max_messages( 4, 2, 2 ), 'sleep_max_messages: maxmessagespersecond = 2 max reached') ;
12450 ok( 2 == sleep_max_messages( 8, 2, 2 ), 'sleep_max_messages: maxmessagespersecond = 2 max over') ;
12451 ok( 0 == sleep_max_messages( 2, 2, 2 ), 'sleep_max_messages: maxmessagespersecond = 2 max not reached') ;
12452
12453 note( 'Leaving tests_sleep_max_messages()' ) ;
12454 return ;
12455}
12456
12457
12458sub sleep_max_bytes
12459{
12460 # how long we have to sleep to go under max_bytes_per_second
12461 my( $total_bytes_to_consider, $time_spent, $maxbytespersecond ) = @_ ;
12462 $total_bytes_to_consider ||= 0 ;
12463 $time_spent ||= 0 ;
12464
12465 if ( ( not defined $maxbytespersecond ) or $maxbytespersecond <= 0 ) { return( 0 ) } ;
12466 #myprint( "total_bytes_to_consider:$total_bytes_to_consider\n" ) ;
12467 my $sleep = ( $total_bytes_to_consider / $maxbytespersecond ) - $time_spent ;
12468 # the sleep must be positive
12469 return( max( 0, $sleep ) ) ;
12470}
12471
12472
12473sub tests_sleep_max_bytes
12474{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012475 note( 'Entering tests_sleep_max_bytes()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012476
12477 ok( 0 == sleep_max_bytes( 4000, 2, undef ), 'sleep_max_bytes: maxbytespersecond == undef => sleep 0' ) ;
12478 ok( 0 == sleep_max_bytes( 4000, 2, 0 ), 'sleep_max_bytes: maxbytespersecond = 0 => sleep 0') ;
12479 ok( 0 == sleep_max_bytes( 4000, 2, $MINUS_ONE ), 'sleep_max_bytes: maxbytespersecond = -1 => sleep 0') ;
12480 ok( 0 == sleep_max_bytes( 4000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max reached sharp => sleep 0') ;
12481 ok( 2 == sleep_max_bytes( 8000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max over => sleep a little') ;
12482 ok( 0 == sleep_max_bytes( -8000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max not reached => sleep 0') ;
12483 ok( 0 == sleep_max_bytes( 2000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max not reached => sleep 0') ;
12484 ok( 0 == sleep_max_bytes( -2000, 2, 1000 ), 'sleep_max_bytes: maxbytespersecond = 1k max not reached => sleep 0') ;
12485
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012486 note( 'Leaving tests_sleep_max_bytes()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012487 return ;
12488}
12489
12490
12491sub delete_message_on_host1
12492{
12493 my( $mysync, $h1_fold, $expunge, @h1_msg ) = @_ ;
12494 if ( ! $mysync->{ delete1 } ) { return ; }
12495 if ( ! @h1_msg ) { return ; }
12496 delete_messages_on_any(
12497 $mysync,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012498 $mysync->{ acc1 },
12499 $mysync->{ imap1 },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012500 "Host1: $h1_fold",
12501 $expunge,
12502 $split1,
12503 @h1_msg ) ;
12504 return ;
12505}
12506
12507sub tests_operators_and_exclam_precedence
12508{
12509 note( 'Entering tests_operators_and_exclam_precedence()' ) ;
12510
12511 is( 1, ! 0, 'tests_operators_and_exclam_precedence: ! 0 => 1' ) ;
12512 is( "", ! 1, 'tests_operators_and_exclam_precedence: ! 1 => ""' ) ;
12513 is( 1, not( 0 ), 'tests_operators_and_exclam_precedence: not( 0 ) => 1' ) ;
12514 is( "", not( 1 ), 'tests_operators_and_exclam_precedence: not( 1 ) => ""' ) ;
12515
12516 # I wrote those tests to avoid perlcrit "Mixed high and low-precedence booleans"
12517 # and change sub delete_messages_on_any() but got 4 more warnings... So now commented.
12518
12519 #is( 0, ( ! 0 and 0 ), 'tests_operators_and_exclam_precedence: ! 0 and 0 ) => 0' ) ;
12520 #is( 1, ( ! 0 and 1 ), 'tests_operators_and_exclam_precedence: ! 0 and 1 ) => 1' ) ;
12521 #is( "", ( ! 1 and 0 ), 'tests_operators_and_exclam_precedence: ! 1 and 0 ) => ""' ) ;
12522 #is( "", ( ! 1 and 1 ), 'tests_operators_and_exclam_precedence: ! 1 and 1 ) => ""' ) ;
12523
12524 is( 0, ( ! 0 && 0 ), 'tests_operators_and_exclam_precedence: ! 0 && 0 ) => 0' ) ;
12525 is( 1, ( ! 0 && 1 ), 'tests_operators_and_exclam_precedence: ! 0 && 1 ) => 1' ) ;
12526 is( "", ( ! 1 && 0 ), 'tests_operators_and_exclam_precedence: ! 1 && 0 ) => ""' ) ;
12527 is( "", ( ! 1 && 1 ), 'tests_operators_and_exclam_precedence: ! 1 && 1 ) => ""' ) ;
12528
12529 is( 2, ( ! 0 && 2 ), 'tests_operators_and_exclam_precedence: ! 0 && 2 ) => 1' ) ;
12530
12531 note( 'Leaving tests_operators_and_exclam_precedence()' ) ;
12532 return ;
12533}
12534
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012535
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012536sub delete_messages_on_any
12537{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012538 # $acc is not used yet,
12539 #
12540 my( $mysync, $acc, $imap, $hostX_folder, $expunge, $split, @messages ) = @_ ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012541 my $expunge_message = q{} ;
12542
12543 my $dry_message = $mysync->{ dry_message } ;
12544 $expunge_message = 'and expunged' if ( $expunge ) ;
12545 # "Host1: msg "
12546
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012547 # $imap->Debug( 1 ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012548
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012549 my @messages_to_mark_deleted = @messages ;
12550 while ( my @messages_part = splice @messages_to_mark_deleted, 0, $split )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012551 {
12552 foreach my $message ( @messages_part )
12553 {
12554 myprint( "$hostX_folder/$message marking deleted $expunge_message $dry_message\n" ) ;
12555 }
12556 if ( ! $mysync->{dry} && @messages_part )
12557 {
12558 my $nb_deleted = $imap->delete_message( $imap->Range( @messages_part ) ) ;
12559 if ( defined $nb_deleted )
12560 {
12561 # $nb_deleted is not accurate
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012562 $acc->{ nb_msg_deleted } += scalar @messages_part ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012563 }
12564 else
12565 {
12566 my $error_imap = $imap->LastError || q{} ;
12567 my $error = join( q{}, "$hostX_folder folder, could not delete ",
12568 scalar @messages_part, ' messages: ', $error_imap, "\n" ) ;
12569 errors_incr( $mysync, $error ) ;
12570 }
12571 }
12572 }
12573
12574 if ( $expunge ) {
12575 uidexpunge_or_expunge( $mysync, $imap, @messages ) ;
12576 }
12577
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012578 #$imap->Debug( 0 ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012579
12580 return ;
12581}
12582
12583
12584sub tests_uidexpunge_or_expunge
12585{
12586 note( 'Entering tests_uidexpunge_or_expunge()' ) ;
12587
12588
12589 is( undef, uidexpunge_or_expunge( ), 'uidexpunge_or_expunge: no args => undef' ) ;
12590 my $mysync ;
12591 is( undef, uidexpunge_or_expunge( $mysync ), 'uidexpunge_or_expunge: undef args => undef' ) ;
12592 $mysync = {} ;
12593 is( undef, uidexpunge_or_expunge( $mysync ), 'uidexpunge_or_expunge: arg empty => undef' ) ;
12594 my $imap ;
12595 is( undef, uidexpunge_or_expunge( $mysync, $imap ), 'uidexpunge_or_expunge: undef Mail-IMAPClient instance => undef' ) ;
12596
12597 require_ok( "Test::MockObject" ) ;
12598 $imap = Test::MockObject->new( ) ;
12599 is( undef, uidexpunge_or_expunge( $mysync, $imap ), 'uidexpunge_or_expunge: no message (1) to uidexpunge => undef' ) ;
12600
12601 my @messages = ( ) ;
12602 is( undef, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: no message (2) to uidexpunge => undef' ) ;
12603
12604 @messages = ( '2', '1' ) ;
12605 $imap->mock( 'uidexpunge', sub { return ; } ) ;
12606 $imap->mock( 'expunge', sub { return ; } ) ;
12607 is( undef, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: uidexpunge failure => expunge failure => undef' ) ;
12608
12609 $imap->mock( 'expunge', sub { return 1 ; } ) ;
12610 is( 1, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: uidexpunge failure => expunge ok => 1' ) ;
12611
12612 $imap->mock( 'uidexpunge', sub { return 1 ; } ) ;
12613 is( 1, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: messages to uidexpunge ok => 1' ) ;
12614
12615 note( 'Leaving tests_uidexpunge_or_expunge()' ) ;
12616 return ;
12617}
12618
12619sub uidexpunge_or_expunge
12620{
12621 my $mysync = shift ;
12622 my $imap = shift ;
12623 my @messages = @ARG ;
12624
12625 if ( ! $imap ) { return ; } ;
12626 if ( ! @messages ) { return ; } ;
12627
12628 # Doing uidexpunge
12629 my @uidexpunge_result = $imap->uidexpunge( @messages ) ;
12630 if ( @uidexpunge_result ) {
12631 return 1 ;
12632 }
12633 # Failure so doing expunge
12634 my $expunge_result = $imap->expunge( ) ;
12635 if ( $expunge_result ) {
12636 return 1 ;
12637 }
12638 # bad trip
12639 return ;
12640}
12641
12642sub eta_print
12643{
12644 my $mysync = shift ;
12645 if ( my $eta = eta( $mysync ) )
12646 {
12647 myprint( "$eta\n" ) ;
12648 }
12649 return ;
12650}
12651
12652sub tests_eta
12653{
12654 note( 'Entering tests_eta()' ) ;
12655
12656 is( q{}, eta( ), 'eta: no args => ""' ) ;
12657 is( q{}, eta( undef ), 'eta: undef => ""' ) ;
12658 my $mysync = {} ;
12659 # No foldersizes
12660 is( q{}, eta( $mysync ), 'eta: No foldersizes => ""' ) ;
12661
12662 $mysync->{ foldersizes } = 1 ;
12663
12664 $mysync->{ begin_transfer_time } = time ; # Now
12665 $mysync->{ h1_nb_msg_processed } = 0 ;
12666
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012667 is( "ETA: " . localtimez( time ) . " 0 s 0/0 msgs left",
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012668 eta( $mysync ),
12669 'eta: no args => ETA: "Now" 0 s 0/0 msgs left' ) ;
12670
12671 $mysync->{ h1_nb_msg_processed } = 1 ;
12672 $mysync->{ h1_nb_msg_start } = 2 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012673 is( "ETA: " . localtimez( time ) . " 0 s 1/2 msgs left",
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012674 eta( $mysync ),
12675 'eta: 1, 1, 2 => ETA: "Now" 0 s 1/2 msgs left' ) ;
12676
12677 note( 'Leaving tests_eta()' ) ;
12678 return ;
12679}
12680
12681
12682sub eta
12683{
12684 my( $mysync ) = shift ;
12685
12686 if ( ! $mysync )
12687 {
12688 return q{} ;
12689 }
12690
12691 return( q{} ) if not $mysync->{ foldersizes } ;
12692
12693 my $h1_nb_msg_start = $mysync->{ h1_nb_msg_start } ;
12694 my $h1_nb_processed = $mysync->{ h1_nb_msg_processed } ;
12695 my $nb_msg_transferred = ( $mysync->{dry} ) ? $mysync->{ h1_nb_msg_processed } : $mysync->{ nb_msg_transferred } ;
12696 my $time_spent = timesince( $mysync->{ begin_transfer_time } ) ;
12697 $h1_nb_processed ||= 0 ;
12698 $h1_nb_msg_start ||= 0 ;
12699 $time_spent ||= 0 ;
12700
12701 my $time_remaining = time_remaining( $time_spent, $h1_nb_processed, $h1_nb_msg_start, $nb_msg_transferred ) ;
12702 $mysync->{ debug } and myprint( "time_spent: $time_spent time_remaining: $time_remaining\n" ) ;
12703 my $nb_msg_remaining = $h1_nb_msg_start - $h1_nb_processed ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012704 my $eta_date = localtimez( time + $time_remaining ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012705 return( mysprintf( 'ETA: %s %1.0f s %s/%s msgs left',
12706 $eta_date, $time_remaining, $nb_msg_remaining, $h1_nb_msg_start ) ) ;
12707}
12708
12709
12710
12711
12712sub time_remaining
12713{
12714
12715 my( $my_time_spent, $h1_nb_processed, $h1_nb_msg_start, $nb_transferred ) = @_ ;
12716
12717 $nb_transferred ||= 1 ; # At least one is done (no division by zero)
12718 $h1_nb_processed ||= 0 ;
12719 $h1_nb_msg_start ||= $h1_nb_processed ;
12720 $my_time_spent ||= 0 ;
12721
12722 my $time_remaining = ( $my_time_spent / $nb_transferred ) * ( $h1_nb_msg_start - $h1_nb_processed ) ;
12723 return( $time_remaining ) ;
12724}
12725
12726
12727sub tests_time_remaining
12728{
12729 note( 'Entering tests_time_remaining()' ) ;
12730
12731 # time_spent, nb_processed, nb_to_do_total, nb_transferred
12732 is( 0, time_remaining( ), 'time_remaining: no args -> 0' ) ;
12733 is( 0, time_remaining( 0, 0, 0, 0 ), 'time_remaining: 0, 0, 0, 0 -> 0' ) ;
12734 is( 1, time_remaining( 1, 1, 2, 1 ), 'time_remaining: 1, 1, 2, 1 -> 1' ) ;
12735 is( 1, time_remaining( 9, 9, 10, 9 ), 'time_remaining: 9, 9, 10, 9 -> 1' ) ;
12736 is( 9, time_remaining( 1, 1, 10, 1 ), 'time_remaining: 1, 1, 10, 1 -> 9' ) ;
12737 is( 5, time_remaining( 5, 5, 10, 5 ), 'time_remaining: 5, 5, 10, 5 -> 5' ) ;
12738 is( 25, time_remaining( 5, 5, 10, 0 ), 'time_remaining: 5, 5, 10, 0 -> ( 5 / 1 ) * ( 10 - 5) = 25' ) ;
12739 is( 25, time_remaining( 5, 5, 10, 1 ), 'time_remaining: 5, 5, 10, 1 -> ( 5 / 1 ) * ( 10 - 5) = 25' ) ;
12740
12741 note( 'Leaving tests_time_remaining()' ) ;
12742 return ;
12743}
12744
12745
12746sub cache_map
12747{
12748 my ( $cache_files_ref, $h1_msgs_ref, $h2_msgs_ref ) = @_;
12749 my ( %map1_2, %map2_1, %done2 ) ;
12750
12751 my $h1_msgs_hash_ref = { } ;
12752 my $h2_msgs_hash_ref = { } ;
12753
12754 @{ $h1_msgs_hash_ref }{ @{ $h1_msgs_ref } } = ( ) ;
12755 @{ $h2_msgs_hash_ref }{ @{ $h2_msgs_ref } } = ( ) ;
12756
12757 foreach my $file ( sort @{ $cache_files_ref } ) {
12758 $debugcache and myprint( "C12: $file\n" ) ;
12759 ( $uid1, $uid2 ) = match_a_cache_file( $file ) ;
12760
12761 if ( exists( $h1_msgs_hash_ref->{ defined $uid1 ? $uid1 : q{} } )
12762 and exists( $h2_msgs_hash_ref->{ defined $uid2 ? $uid2 : q{} } ) ) {
12763 # keep only the greatest uid2
12764 # 130_2301 and
12765 # 130_231 => keep only 130 -> 2301
12766
12767 # keep only the greatest uid1
12768 # 1601_260 and
12769 # 161_260 => keep only 1601 -> 260
12770 my $max_uid2 = max( $uid2, $map1_2{ $uid1 } || $MINUS_ONE ) ;
12771 if ( exists $done2{ $max_uid2 } ) {
12772 if ( $done2{ $max_uid2 } < $uid1 ) {
12773 $map1_2{ $uid1 } = $max_uid2 ;
12774 delete $map1_2{ $done2{ $max_uid2 } } ;
12775 $done2{ $max_uid2 } = $uid1 ;
12776 }
12777 }else{
12778 $map1_2{ $uid1 } = $max_uid2 ;
12779 $done2{ $max_uid2 } = $uid1 ;
12780 }
12781 };
12782
12783 }
12784 %map2_1 = reverse %map1_2 ;
12785 return( \%map1_2, \%map2_1) ;
12786}
12787
12788sub tests_cache_map
12789{
12790 note( 'Entering tests_cache_map()' ) ;
12791
12792 #$debugcache = 1 ;
12793 my @cache_files = qw (
12794 100_200
12795 101_201
12796 120_220
12797 142_242
12798 143_243
12799 177_277
12800 177_278
12801 177_279
12802 155_255
12803 180_280
12804 181_280
12805 182_280
12806 130_231
12807 130_2301
12808 161_260
12809 1601_260
12810 ) ;
12811
12812 my $msgs_1 = [120, 142, 143, 144, 161, 1601, 177, 182, 130 ];
12813 my $msgs_2 = [ 242, 243, 260, 299, 377, 279, 255, 280, 231, 2301 ];
12814
12815 my( $c12, $c21 ) ;
12816 ok( ( $c12, $c21 ) = cache_map( \@cache_files, $msgs_1, $msgs_2 ), 'cache_map: 02' );
12817 my $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
12818 my $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
12819 ok( 0 == compare_lists( [ 130, 142, 143, 177, 182, 1601 ], $a1 ), 'cache_map: 03' );
12820 ok( 0 == compare_lists( [ 242, 243, 260, 279, 280, 2301 ], $a2 ), 'cache_map: 04' );
12821 ok( ! $c12->{161}, 'cache_map: ! 161 -> 260' );
12822 ok( 260 == $c12->{1601}, 'cache_map: 1601 -> 260' );
12823 ok( 2301 == $c12->{130}, 'cache_map: 130 -> 2301' );
12824 #myprint( $c12->{1601}, "\n" ) ;
12825
12826 note( 'Leaving tests_cache_map()' ) ;
12827 return ;
12828
12829}
12830
12831sub cache_dir_fix
12832{
12833 my $cache_dir = shift ;
12834 $cache_dir =~ s/([;<>\*\|`&\$!#\(\)\[\]\{\}:'"\\])/\\$1/xg ;
12835 #myprint( "cache_dir_fix: $cache_dir\n" ) ;
12836 return( $cache_dir ) ;
12837}
12838
12839sub tests_cache_dir_fix
12840{
12841 note( 'Entering tests_cache_dir_fix()' ) ;
12842
12843 ok( 'lalala' eq cache_dir_fix('lalala'), 'cache_dir_fix: lalala -> lalala' );
12844 ok( 'ii\\\\ii' eq cache_dir_fix('ii\ii'), 'cache_dir_fix: ii\ii -> ii\\\\ii' );
12845 ok( 'ii@ii' eq cache_dir_fix('ii@ii'), 'cache_dir_fix: ii@ii -> ii@ii' );
12846 ok( 'ii@ii\\:ii' eq cache_dir_fix('ii@ii:ii'), 'cache_dir_fix: ii@ii:ii -> ii@ii\\:ii' );
12847 ok( 'i\\\\i\\\\ii' eq cache_dir_fix('i\i\ii'), 'cache_dir_fix: i\i\ii -> i\\\\i\\\\ii' );
12848 ok( 'i\\\\ii' eq cache_dir_fix('i\\ii'), 'cache_dir_fix: i\\ii -> i\\\\\\\\ii' );
12849 ok( '\\\\ ' eq cache_dir_fix('\\ '), 'cache_dir_fix: \\ -> \\\\\ ' );
12850 ok( '\\\\ ' eq cache_dir_fix('\ '), 'cache_dir_fix: \ -> \\\\\ ' );
12851 ok( '\[bracket\]' eq cache_dir_fix('[bracket]'), 'cache_dir_fix: [bracket] -> \[bracket\]' );
12852
12853 note( 'Leaving tests_cache_dir_fix()' ) ;
12854 return ;
12855}
12856
12857sub cache_dir_fix_win
12858{
12859 my $cache_dir = shift ;
12860 $cache_dir =~ s/(\[|\])/[$1]/xg ;
12861 #myprint( "cache_dir_fix_win: $cache_dir\n" ) ;
12862 return( $cache_dir ) ;
12863}
12864
12865sub tests_cache_dir_fix_win
12866{
12867 note( 'Entering tests_cache_dir_fix_win()' ) ;
12868
12869 ok( 'lalala' eq cache_dir_fix_win('lalala'), 'cache_dir_fix_win: lalala -> lalala' );
12870 ok( '[[]bracket[]]' eq cache_dir_fix_win('[bracket]'), 'cache_dir_fix_win: [bracket] -> [[]bracket[]]' );
12871
12872 note( 'Leaving tests_cache_dir_fix_win()' ) ;
12873 return ;
12874}
12875
12876
12877
12878
12879sub get_cache
12880{
12881 my ( $cache_dir, $h1_msgs_ref, $h2_msgs_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) = @_;
12882
12883 $debugcache and myprint( "Entering get_cache\n" ) ;
12884
12885 -d $cache_dir or return( undef ); # exit if cache directory doesn't exist
12886 $debugcache and myprint( "cache_dir : $cache_dir\n" ) ;
12887
12888
12889 if ( 'MSWin32' ne $OSNAME ) {
12890 $cache_dir = cache_dir_fix( $cache_dir ) ;
12891 }else{
12892 $cache_dir = cache_dir_fix_win( $cache_dir ) ;
12893 }
12894
12895 $debugcache and myprint( "cache_dir_fix: $cache_dir\n" ) ;
12896
12897 my @cache_files = bsd_glob( "$cache_dir/*" ) ;
12898 #$debugcache and myprint( "cache_files: [@cache_files]\n" ) ;
12899
12900 $debugcache and myprint( 'cache_files: ', scalar @cache_files , " files found\n" ) ;
12901
12902 my( $cache_1_2_ref, $cache_2_1_ref )
12903 = cache_map( \@cache_files, $h1_msgs_ref, $h2_msgs_ref ) ;
12904
12905 clean_cache( \@cache_files, $cache_1_2_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) ;
12906
12907 $debugcache and myprint( "Exiting get_cache\n" ) ;
12908 return( $cache_1_2_ref, $cache_2_1_ref ) ;
12909}
12910
12911
12912sub tests_get_cache
12913{
12914 note( 'Entering tests_get_cache()' ) ;
12915
12916 ok( not( get_cache('/cache_no_exist') ), 'get_cache: /cache_no_exist' );
12917 ok( ( not -d 'W/tmp/cache/F1/F2' or rmtree( 'W/tmp/cache/F1/F2' ) ), 'get_cache: rmtree W/tmp/cache/F1/F2' ) ;
12918 ok( mkpath( 'W/tmp/cache/F1/F2' ), 'get_cache: mkpath W/tmp/cache/F1/F2' ) ;
12919
12920 my @test_files_cache = ( qw(
12921 W/tmp/cache/F1/F2/100_200
12922 W/tmp/cache/F1/F2/101_201
12923 W/tmp/cache/F1/F2/120_220
12924 W/tmp/cache/F1/F2/142_242
12925 W/tmp/cache/F1/F2/143_243
12926 W/tmp/cache/F1/F2/177_277
12927 W/tmp/cache/F1/F2/177_377
12928 W/tmp/cache/F1/F2/177_777
12929 W/tmp/cache/F1/F2/155_255
12930 ) ) ;
12931 ok( touch( @test_files_cache ), 'get_cache: touch W/tmp/cache/F1/F2/...' ) ;
12932
12933
12934 # on cache: 100_200 101_201 142_242 143_243 177_277 177_377 177_777 155_255
12935 # on live:
12936 my $msgs_1 = [120, 142, 143, 144, 177 ];
12937 my $msgs_2 = [ 242, 243, 299, 377, 777, 255 ];
12938
12939 my $msgs_all_1 = { 120 => 0, 142 => 0, 143 => 0, 144 => 0, 177 => 0 } ;
12940 my $msgs_all_2 = { 242 => 0, 243 => 0, 299 => 0, 377 => 0, 777 => 0, 255 => 0 } ;
12941
12942 my( $c12, $c21 ) ;
12943 ok( ( $c12, $c21 ) = get_cache( 'W/tmp/cache/F1/F2', $msgs_1, $msgs_2, $msgs_all_1, $msgs_all_2 ), 'get_cache: 02' );
12944 my $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
12945 my $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
12946 ok( 0 == compare_lists( [ 142, 143, 177 ], $a1 ), 'get_cache: 03' );
12947 ok( 0 == compare_lists( [ 242, 243, 777 ], $a2 ), 'get_cache: 04' );
12948 ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 142_242');
12949 ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 143_243');
12950 ok( ! -f 'W/tmp/cache/F1/F2/100_200', 'get_cache: file removed 100_200');
12951 ok( ! -f 'W/tmp/cache/F1/F2/101_201', 'get_cache: file removed 101_201');
12952
12953 # test clean_cache executed
12954 $maxage = 2 ;
12955 ok( touch(@test_files_cache), 'get_cache: touch W/tmp/cache/F1/F2/...' ) ;
12956 ok( ( $c12, $c21 ) = get_cache('W/tmp/cache/F1/F2', $msgs_1, $msgs_2, $msgs_all_1, $msgs_all_2 ), 'get_cache: 02' );
12957 ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 142_242');
12958 ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 143_243');
12959 ok( ! -f 'W/tmp/cache/F1/F2/100_200', 'get_cache: file NOT removed 100_200');
12960 ok( ! -f 'W/tmp/cache/F1/F2/101_201', 'get_cache: file NOT removed 101_201');
12961
12962
12963 # strange files
12964 #$debugcache = 1 ;
12965 $maxage = undef ;
12966 ok( ( not -d 'W/tmp/cache/rr\uee' or rmtree( 'W/tmp/cache/rr\uee' )), 'get_cache: rmtree W/tmp/cache/rr\uee' ) ;
12967 ok( mkpath( 'W/tmp/cache/rr\uee' ), 'get_cache: mkpath W/tmp/cache/rr\uee' ) ;
12968
12969 @test_files_cache = ( qw(
12970 W/tmp/cache/rr\uee/100_200
12971 W/tmp/cache/rr\uee/101_201
12972 W/tmp/cache/rr\uee/120_220
12973 W/tmp/cache/rr\uee/142_242
12974 W/tmp/cache/rr\uee/143_243
12975 W/tmp/cache/rr\uee/177_277
12976 W/tmp/cache/rr\uee/177_377
12977 W/tmp/cache/rr\uee/177_777
12978 W/tmp/cache/rr\uee/155_255
12979 ) ) ;
12980 ok( touch(@test_files_cache), 'get_cache: touch strange W/tmp/cache/...' ) ;
12981
12982 # on cache: 100_200 101_201 142_242 143_243 177_277 177_377 177_777 155_255
12983 # on live:
12984 $msgs_1 = [120, 142, 143, 144, 177 ] ;
12985 $msgs_2 = [ 242, 243, 299, 377, 777, 255 ] ;
12986
12987 $msgs_all_1 = { 120 => q{}, 142 => q{}, 143 => q{}, 144 => q{}, 177 => q{} } ;
12988 $msgs_all_2 = { 242 => q{}, 243 => q{}, 299 => q{}, 377 => q{}, 777 => q{}, 255 => q{} } ;
12989
12990 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' );
12991 $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
12992 $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
12993 ok( 0 == compare_lists( [ 142, 143, 177 ], $a1 ), 'get_cache: strange path 03' );
12994 ok( 0 == compare_lists( [ 242, 243, 777 ], $a2 ), 'get_cache: strange path 04' );
12995 ok( -f 'W/tmp/cache/rr\uee/142_242', 'get_cache: strange path file kept 142_242');
12996 ok( -f 'W/tmp/cache/rr\uee/142_242', 'get_cache: strange path file kept 143_243');
12997 ok( ! -f 'W/tmp/cache/rr\uee/100_200', 'get_cache: strange path file removed 100_200');
12998 ok( ! -f 'W/tmp/cache/rr\uee/101_201', 'get_cache: strange path file removed 101_201');
12999
13000 note( 'Leaving tests_get_cache()' ) ;
13001 return ;
13002}
13003
13004sub match_a_cache_file
13005{
13006 my $file = shift ;
13007 my ( $cache_uid1, $cache_uid2 ) ;
13008
13009 return( ( undef, undef ) ) if ( ! $file ) ;
13010 if ( $file =~ m{(?:^|/)(\d+)_(\d+)$}x ) {
13011 $cache_uid1 = $1 ;
13012 $cache_uid2 = $2 ;
13013 }
13014 return( $cache_uid1, $cache_uid2 ) ;
13015}
13016
13017sub tests_match_a_cache_file
13018{
13019 note( 'Entering tests_match_a_cache_file()' ) ;
13020
13021 my ( $tuid1, $tuid2 ) ;
13022 ok( ( $tuid1, $tuid2 ) = match_a_cache_file( ), 'match_a_cache_file: no arg' ) ;
13023 ok( ! defined $tuid1 , 'match_a_cache_file: no arg 1' ) ;
13024 ok( ! defined $tuid2 , 'match_a_cache_file: no arg 2' ) ;
13025
13026 ok( ( $tuid1, $tuid2 ) = match_a_cache_file( q{} ), 'match_a_cache_file: empty arg' ) ;
13027 ok( ! defined $tuid1 , 'match_a_cache_file: empty arg 1' ) ;
13028 ok( ! defined $tuid2 , 'match_a_cache_file: empty arg 2' ) ;
13029
13030 ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '000_000' ), 'match_a_cache_file: 000_000' ) ;
13031 ok( '000' eq $tuid1, 'match_a_cache_file: 000_000 1' ) ;
13032 ok( '000' eq $tuid2, 'match_a_cache_file: 000_000 2' ) ;
13033
13034 ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '123_456' ), 'match_a_cache_file: 123_456' ) ;
13035 ok( '123' eq $tuid1, 'match_a_cache_file: 123_456 1' ) ;
13036 ok( '456' eq $tuid2, 'match_a_cache_file: 123_456 2' ) ;
13037
13038 ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '/tmp/truc/123_456' ), 'match_a_cache_file: /tmp/truc/123_456' ) ;
13039 ok( '123' eq $tuid1, 'match_a_cache_file: /tmp/truc/123_456 1' ) ;
13040 ok( '456' eq $tuid2, 'match_a_cache_file: /tmp/truc/123_456 2' ) ;
13041
13042 ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '/lala123_456' ), 'match_a_cache_file: NO /lala123_456' ) ;
13043 ok( ! $tuid1, 'match_a_cache_file: /lala123_456 1' ) ;
13044 ok( ! $tuid2, 'match_a_cache_file: /lala123_456 2' ) ;
13045
13046 ok( ( $tuid1, $tuid2 ) = match_a_cache_file( 'la123_456' ), 'match_a_cache_file: NO la123_456' ) ;
13047 ok( ! $tuid1, 'match_a_cache_file: la123_456 1' ) ;
13048 ok( ! $tuid2, 'match_a_cache_file: la123_456 2' ) ;
13049
13050 note( 'Leaving tests_match_a_cache_file()' ) ;
13051 return ;
13052}
13053
13054sub clean_cache
13055{
13056 my ( $cache_files_ref, $cache_1_2_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) = @_ ;
13057
13058 $debugcache and myprint( "Entering clean_cache\n" ) ;
13059
13060 $debugcache and myprint( map { "$_ -> " . $cache_1_2_ref->{ $_ } . "\n" } keys %{ $cache_1_2_ref } ) ;
13061 foreach my $file ( @{ $cache_files_ref } ) {
13062 $debugcache and myprint( "$file\n" ) ;
13063 my ( $cache_uid1, $cache_uid2 ) = match_a_cache_file( $file ) ;
13064 $debugcache and myprint( "u1: $cache_uid1 u2: $cache_uid2 c12: ", $cache_1_2_ref->{ $cache_uid1 } || q{}, "\n") ;
13065# or ( ! exists( $cache_1_2_ref->{ $cache_uid1 } ) )
13066# or ( ! ( $cache_uid2 == $cache_1_2_ref->{ $cache_uid1 } ) )
13067 if ( ( not defined $cache_uid1 )
13068 or ( not defined $cache_uid2 )
13069 or ( not exists $h1_msgs_all_hash_ref->{ $cache_uid1 } )
13070 or ( not exists $h2_msgs_all_hash_ref->{ $cache_uid2 } )
13071 ) {
13072 $debugcache and myprint( "remove $file\n" ) ;
13073 unlink $file or myprint( "$OS_ERROR" ) ;
13074 }
13075 }
13076
13077 $debugcache and myprint( "Exiting clean_cache\n" ) ;
13078 return( 1 ) ;
13079}
13080
13081sub tests_clean_cache
13082{
13083 note( 'Entering tests_clean_cache()' ) ;
13084
13085 ok( ( not -d 'W/tmp/cache/G1/G2' or rmtree( 'W/tmp/cache/G1/G2' )), 'clean_cache: rmtree W/tmp/cache/G1/G2' ) ;
13086 ok( mkpath( 'W/tmp/cache/G1/G2' ), 'clean_cache: mkpath W/tmp/cache/G1/G2' ) ;
13087
13088 my @test_files_cache = ( qw(
13089 W/tmp/cache/G1/G2/100_200
13090 W/tmp/cache/G1/G2/101_201
13091 W/tmp/cache/G1/G2/120_220
13092 W/tmp/cache/G1/G2/142_242
13093 W/tmp/cache/G1/G2/143_243
13094 W/tmp/cache/G1/G2/177_277
13095 W/tmp/cache/G1/G2/177_377
13096 W/tmp/cache/G1/G2/177_777
13097 W/tmp/cache/G1/G2/155_255
13098 ) ) ;
13099 ok( touch(@test_files_cache), 'clean_cache: touch W/tmp/cache/G1/G2/...' ) ;
13100
13101 ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache: 100_200 before' );
13102 ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache: 142_242 before' );
13103 ok( -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache: 177_277 before' );
13104 ok( -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache: 177_377 before' );
13105 ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache: 177_777 before' );
13106 ok( -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache: 155_255 before' );
13107
13108 my $cache = {
13109 142 => 242,
13110 177 => 777,
13111 } ;
13112
13113 my $all_1 = {
13114 142 => q{},
13115 177 => q{},
13116 } ;
13117
13118 my $all_2 = {
13119 200 => q{},
13120 242 => q{},
13121 777 => q{},
13122 } ;
13123 ok( clean_cache( \@test_files_cache, $cache, $all_1, $all_2 ), 'clean_cache: ' ) ;
13124
13125 ok( ! -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache: 100_200 after' );
13126 ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache: 142_242 after' );
13127 ok( ! -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache: 177_277 after' );
13128 ok( ! -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache: 177_377 after' );
13129 ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache: 177_777 after' );
13130 ok( ! -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache: 155_255 after' );
13131
13132 note( 'Leaving tests_clean_cache()' ) ;
13133 return ;
13134}
13135
13136sub tests_clean_cache_2
13137{
13138 note( 'Entering tests_clean_cache_2()' ) ;
13139
13140 ok( ( not -d 'W/tmp/cache/G1/G2' or rmtree( 'W/tmp/cache/G1/G2' )), 'clean_cache_2: rmtree W/tmp/cache/G1/G2' ) ;
13141 ok( mkpath( 'W/tmp/cache/G1/G2' ), 'clean_cache_2: mkpath W/tmp/cache/G1/G2' ) ;
13142
13143 my @test_files_cache = ( qw(
13144 W/tmp/cache/G1/G2/100_200
13145 W/tmp/cache/G1/G2/101_201
13146 W/tmp/cache/G1/G2/120_220
13147 W/tmp/cache/G1/G2/142_242
13148 W/tmp/cache/G1/G2/143_243
13149 W/tmp/cache/G1/G2/177_277
13150 W/tmp/cache/G1/G2/177_377
13151 W/tmp/cache/G1/G2/177_777
13152 W/tmp/cache/G1/G2/155_255
13153 ) ) ;
13154 ok( touch(@test_files_cache), 'clean_cache_2: touch W/tmp/cache/G1/G2/...' ) ;
13155
13156 ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache_2: 100_200 before' );
13157 ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache_2: 142_242 before' );
13158 ok( -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache_2: 177_277 before' );
13159 ok( -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache_2: 177_377 before' );
13160 ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache_2: 177_777 before' );
13161 ok( -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache_2: 155_255 before' );
13162
13163 my $cache = {
13164 142 => 242,
13165 177 => 777,
13166 } ;
13167
13168 my $all_1 = {
13169 $NUMBER_100 => q{},
13170 142 => q{},
13171 177 => q{},
13172 } ;
13173
13174 my $all_2 = {
13175 200 => q{},
13176 242 => q{},
13177 777 => q{},
13178 } ;
13179
13180
13181
13182 ok( clean_cache( \@test_files_cache, $cache, $all_1, $all_2 ), 'clean_cache_2: ' ) ;
13183
13184 ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache_2: 100_200 after' );
13185 ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache_2: 142_242 after' );
13186 ok( ! -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache_2: 177_277 after' );
13187 ok( ! -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache_2: 177_377 after' );
13188 ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache_2: 177_777 after' );
13189 ok( ! -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache_2: 155_255 after' );
13190
13191 note( 'Leaving tests_clean_cache_2()' ) ;
13192 return ;
13193}
13194
13195
13196
13197sub tests_mkpath
13198{
13199 note( 'Entering tests_mkpath()' ) ;
13200
13201 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' )), 'mkpath: mkpath W/tmp/tests/' ) ;
13202
13203 SKIP: {
13204 skip( 'Tests only for Unix', 10 ) if ( 'MSWin32' eq $OSNAME ) ;
13205 my $long_path_unix = '123456789/' x 30 ;
13206 ok( ( -d "W/tmp/tests/long/$long_path_unix" or mkpath( "W/tmp/tests/long/$long_path_unix" ) ), 'mkpath: mkpath 300 char' ) ;
13207 ok( -d "W/tmp/tests/long/$long_path_unix", 'mkpath: mkpath > 300 char verified' ) ;
13208 ok( ( -d "W/tmp/tests/long/$long_path_unix" and rmtree( 'W/tmp/tests/long/' ) ), 'mkpath: rmtree 300 char' ) ;
13209 ok( ! -d "W/tmp/tests/long/$long_path_unix", 'mkpath: rmtree 300 char verified' ) ;
13210
13211 ok( ( -d 'W/tmp/tests/trailing_dots...' or mkpath( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: mkpath trailing_dots...' ) ;
13212 ok( -d 'W/tmp/tests/trailing_dots...', 'mkpath: mkpath trailing_dots... verified' ) ;
13213 ok( ( -d 'W/tmp/tests/trailing_dots...' and rmtree( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: rmtree trailing_dots...' ) ;
13214 ok( ! -d 'W/tmp/tests/trailing_dots...', 'mkpath: rmtree trailing_dots... verified' ) ;
13215
13216 eval { ok( 1 / 0, 'mkpath: divide by 0' ) ; } or ok( 1, 'mkpath: can not divide by 0' ) ;
13217 ok( 1, 'mkpath: still alive' ) ;
13218 } ;
13219
13220 SKIP: {
13221 skip( 'Tests only for MSWin32', 13 ) if ( 'MSWin32' ne $OSNAME ) ;
13222 my $long_path_2_prefix = ".\\imapsync_tests" || '\\\?\\E:\\TEMP\\imapsync_tests' ;
13223 myprint( "long_path_2_prefix: $long_path_2_prefix\n" ) ;
13224
13225 my $long_path_100 = $long_path_2_prefix . '\\' . '123456789\\' x 10 . 'END' ;
13226 my $long_path_300 = $long_path_2_prefix . '\\' . '123456789\\' x 30 . 'END' ;
13227
13228 #myprint( "$long_path_100\n" ) ;
13229
13230 ok( ( -d $long_path_2_prefix or mkpath( $long_path_2_prefix ) ), 'mkpath: -d mkpath small path' ) ;
13231 ok( ( -d $long_path_2_prefix ), 'mkpath: -d mkpath small path done' ) ;
13232 ok( ( -d $long_path_100 or mkpath( $long_path_100 ) ), 'mkpath: mkpath > 100 char' ) ;
13233 ok( ( -d $long_path_100 ), 'mkpath: -d mkpath > 200 char done' ) ;
13234 ok( ( -d $long_path_2_prefix and rmtree( $long_path_2_prefix ) ), 'mkpath: rmtree > 100 char' ) ;
13235 ok( (! -d $long_path_2_prefix ), 'mkpath: ! -d rmtree done' ) ;
13236
13237 # Without the eval the following mkpath 300 just kill the whole process without a whisper
13238 #myprint( "$long_path_300\n" ) ;
13239 eval { ok( ( -d $long_path_300 or mkpath( $long_path_300 ) ), 'mkpath: create a path with 300 characters' ) ; }
13240 or ok( 1, 'mkpath: can not create a path with 300 characters' ) ;
13241 ok( ( ( ! -d $long_path_300 ) or -d $long_path_300 and rmtree( $long_path_300 ) ), 'mkpath: rmtree the 300 character path' ) ;
13242 ok( 1, 'mkpath: still alive' ) ;
13243
13244 ok( ( -d 'W/tmp/tests/trailing_dots...' or mkpath( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: mkpath trailing_dots...' ) ;
13245 ok( -d 'W/tmp/tests/trailing_dots...', 'mkpath: mkpath trailing_dots... verified' ) ;
13246 ok( ( -d 'W/tmp/tests/trailing_dots...' and rmtree( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: rmtree trailing_dots...' ) ;
13247 ok( ! -d 'W/tmp/tests/trailing_dots...', 'mkpath: rmtree trailing_dots... verified' ) ;
13248
13249
13250 } ;
13251
13252 note( 'Leaving tests_mkpath()' ) ;
13253 # Keep this because of the eval used by the caller (failed badly?)
13254 return 1 ;
13255}
13256
13257sub tests_touch
13258{
13259 note( 'Entering tests_touch()' ) ;
13260
13261 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' )), 'touch: mkpath W/tmp/tests/' ) ;
13262 ok( 1 == touch( 'W/tmp/tests/lala'), 'touch: W/tmp/tests/lala') ;
13263 ok( 1 == touch( 'W/tmp/tests/\y'), 'touch: W/tmp/tests/\y') ;
13264 ok( 0 == touch( '/no/no/no/aaa'), 'touch: not /aaa') ;
13265 ok( 1 == touch( 'W/tmp/tests/lili', 'W/tmp/tests/lolo'), 'touch: 2 files') ;
13266 ok( 0 == touch( 'W/tmp/tests/\y', '/no/no/aaa'), 'touch: 2 files, 1 fails' ) ;
13267
13268 note( 'Leaving tests_touch()' ) ;
13269 return ;
13270}
13271
13272
13273sub touch
13274{
13275 my @files = @_ ;
13276 my $failures = 0 ;
13277
13278 foreach my $file ( @files ) {
13279 my $fh = IO::File->new ;
13280 if ( $fh->open(">> $file" ) ) {
13281 $fh->close ;
13282 }else{
13283 myprint( "Could not open file $file in write/append mode\n" ) ;
13284 $failures++ ;
13285 }
13286 }
13287 return( ! $failures );
13288}
13289
13290
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013291
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013292sub tests_tmpdir_has_colon_bug
13293{
13294 note( 'Entering tests_tmpdir_has_colon_bug()' ) ;
13295
13296 ok( 0 == tmpdir_has_colon_bug( q{} ), 'tmpdir_has_colon_bug: ' ) ;
13297 ok( 0 == tmpdir_has_colon_bug( '/tmp' ), 'tmpdir_has_colon_bug: /tmp' ) ;
13298 ok( 1 == tmpdir_has_colon_bug( 'C:' ), 'tmpdir_has_colon_bug: C:' ) ;
13299 ok( 1 == tmpdir_has_colon_bug( 'C:\temp' ), 'tmpdir_has_colon_bug: C:\temp' ) ;
13300
13301 note( 'Leaving tests_tmpdir_has_colon_bug()' ) ;
13302 return ;
13303}
13304
13305sub tmpdir_has_colon_bug
13306{
13307 my $path = shift ;
13308
13309 my $path_filtered = filter_forbidden_characters( $path ) ;
13310 if ( $path_filtered ne $path ) {
13311 ( -d $path_filtered ) and myprint( "Path $path was previously mistakely changed to $path_filtered\n" ) ;
13312 return( 1 ) ;
13313 }
13314 return( 0 ) ;
13315}
13316
13317sub tmpdir_fix_colon_bug
13318{
13319 my $mysync = shift ;
13320 my $err = 0 ;
13321 if ( not (-d $mysync->{ tmpdir } and -r _ and -w _) ) {
13322 myprint( "tmpdir $mysync->{ tmpdir } is not valid\n" ) ;
13323 return( 0 ) ;
13324 }
13325 my $cachedir_new = "$mysync->{ tmpdir }/imapsync_cache" ;
13326
13327 if ( not tmpdir_has_colon_bug( $cachedir_new ) ) { return( 0 ) } ;
13328
13329 # check if old cache directory already exists
13330 my $cachedir_old = filter_forbidden_characters( $cachedir_new ) ;
13331 if ( not ( -d $cachedir_old ) ) {
13332 myprint( "Old cache directory $cachedir_new no exists, nothing to do\n" ) ;
13333 return( 1 ) ;
13334 }
13335 # check if new cache directory already exists
13336 if ( -d $cachedir_new ) {
13337 myprint( "New fixed cache directory $cachedir_new already exists, not moving the old one $cachedir_old. Fix this manually.\n" ) ;
13338 return( 0 ) ;
13339 }else{
13340 # move the old one to the new place
13341 myprint( "Moving $cachedir_old to $cachedir_new Do not interrupt this task.\n" ) ;
13342 File::Copy::Recursive::rmove( $cachedir_old, $cachedir_new )
13343 or do {
13344 myprint( "Could not move $cachedir_old to $cachedir_new\n" ) ;
13345 $err++ ;
13346 } ;
13347 # check it succeeded
13348 if ( -d $cachedir_new and -r _ and -w _ ) {
13349 myprint( "New fixed cache directory $cachedir_new ok\n" ) ;
13350 }else{
13351 myprint( "New fixed cache directory $cachedir_new does not exist\n" ) ;
13352 $err++ ;
13353 }
13354 if ( -d $cachedir_old ) {
13355 myprint( "Old cache directory $cachedir_old still exists\n" ) ;
13356 $err++ ;
13357 }else{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013358 myprint( "Old cache directory $cachedir_old successfully moved\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013359 }
13360 }
13361 return( not $err ) ;
13362}
13363
13364
13365sub tests_cache_folder
13366{
13367 note( 'Entering tests_cache_folder()' ) ;
13368
13369 ok( '/path/fold1/fold2' eq cache_folder( q{}, '/path', 'fold1', 'fold2'), 'cache_folder: /path, fold1, fold2 -> /path/fold1/fold2' ) ;
13370 ok( '/pa_th/fold1/fold2' eq cache_folder( q{}, '/pa*th', 'fold1', 'fold2'), 'cache_folder: /pa*th, fold1, fold2 -> /path/fold1/fold2' ) ;
13371 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' ) ;
13372
13373 ok( 'D:/path/fold1/fold2' eq cache_folder( 'D:', '/path', 'fold1', 'fold2'), 'cache_folder: /path, fold1, fold2 -> /path/fold1/fold2' ) ;
13374 ok( 'D:/pa_th/fold1/fold2' eq cache_folder( 'D:', '/pa*th', 'fold1', 'fold2'), 'cache_folder: /pa*th, fold1, fold2 -> /path/fold1/fold2' ) ;
13375 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' ) ;
13376 ok( '//' eq cache_folder( q{}, q{}, q{}, q{}), 'cache_folder: -> //' ) ;
13377 ok( '//_______' eq cache_folder( q{}, q{}, q{}, '*|?:"<>'), 'cache_folder: *|?:"<> -> //_______' ) ;
13378
13379 note( 'Leaving tests_cache_folder()' ) ;
13380 return ;
13381}
13382
13383sub cache_folder
13384{
13385 my( $cache_base, $cache_dir, $h1_fold, $h2_fold ) = @_ ;
13386
13387 my $sep_1 = $sync->{ h1_sep } || '/';
13388 my $sep_2 = $sync->{ h2_sep } || '/';
13389
13390 #myprint( "$cache_dir h1_fold $h1_fold sep1 $sep_1 h2_fold $h2_fold sep2 $sep_2\n" ) ;
13391 $h1_fold = convert_sep_to_slash( $h1_fold, $sep_1 ) ;
13392 $h2_fold = convert_sep_to_slash( $h2_fold, $sep_2 ) ;
13393
13394 my $cache_folder = "$cache_base" . filter_forbidden_characters( "$cache_dir/$h1_fold/$h2_fold" ) ;
13395 #myprint( "cache_folder [$cache_folder]\n" ) ;
13396 return( $cache_folder ) ;
13397}
13398
13399sub tests_filter_forbidden_characters
13400{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013401 note( 'Entering tests_filter_forbidden_characters()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013402
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013403 is( undef , filter_forbidden_characters( ), 'filter_forbidden_characters: no args -> undef' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013404
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013405 is( 'a_b' , filter_forbidden_characters( 'a_b' ), 'filter_forbidden_characters: a_b -> a_b' ) ;
13406 is( 'a_b' , filter_forbidden_characters( 'a*b' ), 'filter_forbidden_characters: a*b -> a_b' ) ;
13407 is( 'a_b' , filter_forbidden_characters( 'a|b' ), 'filter_forbidden_characters: a|b -> a_b' ) ;
13408 is( 'a_b' , filter_forbidden_characters( 'a?b' ), 'filter_forbidden_characters: a?b -> a_b' ) ;
13409 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 +010013410
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013411
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013412 is( 'a_b_' , filter_forbidden_characters( 'a b ' ), 'filter_forbidden_characters: "a b " -> "a_b_"' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013413
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013414
13415 is( 'a_b' , filter_forbidden_characters( "a\tb" ), 'filter_forbidden_characters: a\tb -> a_b' ) ;
13416 is( "a_b" , filter_forbidden_characters( "a\rb" ), 'filter_forbidden_characters: a\rb -> a_b' ) ;
13417 is( "a_b" , filter_forbidden_characters( "a\nb" ), 'filter_forbidden_characters: a\nb -> a_b' ) ;
13418 is( "a_b" , filter_forbidden_characters( "a\\b" ), 'filter_forbidden_characters: a\b -> a_b' ) ;
13419
13420 is( 'a-b' , filter_forbidden_characters( 'a-b' ), 'filter_forbidden_characters: a-b -> a-b' ) ;
13421 is( 'a__-__-__-__-__b' , filter_forbidden_characters( 'aé-è-à -ç-Öb' ), 'filter_forbidden_characters: aé-è-à -ç-Öb -> a__-__-__-__-__b' ) ;
13422
13423 is( 'abcdABCDwxyzWXYZ012789' , filter_forbidden_characters( 'abcdABCDwxyzWXYZ012789' ),
13424 'filter_forbidden_characters: abcdABCDwxyzWXYZ012789 -> abcdABCDwxyzWXYZ012789' ) ;
13425
13426
13427 note( 'Leaving tests_filter_forbidden_characters()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013428 return ;
13429}
13430
13431sub filter_forbidden_characters
13432{
13433 my $string = shift ;
13434
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013435 if ( ! defined $string ) { return ; }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013436
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013437 $string =~ s{[\Q*|?:"<>' \E\t\r\n\\]}{_}xg ;
13438 # replace all non-ascii and control characters by _
13439 $string =~ s/[[:^ascii:][:cntrl:]]/_/xg ;
13440
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013441 #myprint( "[$string]\n" ) ;
13442 return( $string ) ;
13443}
13444
13445sub tests_convert_sep_to_slash
13446{
13447 note( 'Entering tests_convert_sep_to_slash()' ) ;
13448
13449
13450 ok(q{} eq convert_sep_to_slash(q{}, '/'), 'convert_sep_to_slash: no folder');
13451 ok('INBOX' eq convert_sep_to_slash('INBOX', '/'), 'convert_sep_to_slash: INBOX');
13452 ok('INBOX/foo' eq convert_sep_to_slash('INBOX/foo', '/'), 'convert_sep_to_slash: INBOX/foo');
13453 ok('INBOX/foo' eq convert_sep_to_slash('INBOX_foo', '_'), 'convert_sep_to_slash: INBOX_foo');
13454 ok('INBOX/foo/zob' eq convert_sep_to_slash('INBOX_foo_zob', '_'), 'convert_sep_to_slash: INBOX_foo_zob');
13455 ok('INBOX/foo' eq convert_sep_to_slash('INBOX.foo', '.'), 'convert_sep_to_slash: INBOX.foo');
13456 ok('INBOX/foo/hi' eq convert_sep_to_slash('INBOX.foo.hi', '.'), 'convert_sep_to_slash: INBOX.foo.hi');
13457
13458 note( 'Leaving tests_convert_sep_to_slash()' ) ;
13459 return ;
13460}
13461
13462sub convert_sep_to_slash
13463{
13464 my ( $folder, $sep ) = @_ ;
13465
13466 $folder =~ s{\Q$sep\E}{/}xg ;
13467 return( $folder ) ;
13468}
13469
13470
13471sub tests_regexmess
13472{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013473 note( 'Entering tests_regexmess()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013474
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013475 ok( 'blabla' eq regexmess( 'blabla' ), 'regexmess: no regexmess, nothing to do' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013476
13477 @regexmess = ( 'lalala' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013478 ok( not( defined regexmess( 'popopo' ) ), 'regexmess: bad regex lalala' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013479
13480 @regexmess = ( 's/p/Z/g' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013481 ok( 'ZoZoZo' eq regexmess( 'popopo' ), 'regexmess: s/p/Z/g' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013482
13483 @regexmess = ( 's{c}{C}gxms' ) ;
13484 ok("H1: abC\nH2: Cde\n\nBody abC"
13485 eq regexmess( "H1: abc\nH2: cde\n\nBody abc"),
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013486 'regexmess: c->C');
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013487
13488 @regexmess = ( 's{\AFrom\ }{From:}gxms' ) ;
13489 ok( q{}
13490 eq regexmess(q{}),
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013491 'regexmess: From mbox 1 add colon blank');
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013492
13493 ok( 'From:<tartanpion@machin.truc>'
13494 eq regexmess('From <tartanpion@machin.truc>'),
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013495 'regexmess: From mbox 2 add colo');
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013496
13497 ok( "\n" . 'From <tartanpion@machin.truc>'
13498 eq regexmess("\n" . 'From <tartanpion@machin.truc>'),
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013499 'regexmess: From mbox 3 add colo') ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013500
13501 ok( "From: zzz\n" . 'From <tartanpion@machin.truc>'
13502 eq regexmess("From zzz\n" . 'From <tartanpion@machin.truc>'),
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013503 'regexmess: From mbox 4 add colo') ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013504
13505 @regexmess = ( 's{\AFrom\ [^\n]*(\n)?}{}gxms' ) ;
13506 ok( q{}
13507 eq regexmess(q{}),
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013508 'regexmess: From mbox 1 remove, blank');
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013509
13510 ok( q{}
13511 eq regexmess('From <tartanpion@machin.truc>'),
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013512 'regexmess: From mbox 2 remove');
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013513
13514 ok( "\n" . 'From <tartanpion@machin.truc>'
13515 eq regexmess("\n" . 'From <tartanpion@machin.truc>'),
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013516 'regexmess: From mbox 3 remove');
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013517
13518 #myprint( "[", regexmess("From zzz\n" . 'From <tartanpion@machin.truc>'), "]" ) ;
13519 ok( q{} . 'From <tartanpion@machin.truc>'
13520 eq regexmess("From zzz\n" . 'From <tartanpion@machin.truc>'),
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013521 'regexmess: From mbox 4 remove');
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013522
13523
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013524 is(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013525<<'EOM'
13526Date: Sat, 10 Jul 2010 05:34:45 -0700
13527From:<tartanpion@machin.truc>
13528
13529Hello,
13530Bye.
13531EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013532 , regexmess(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013533<<'EOM'
13534From zzz
13535Date: Sat, 10 Jul 2010 05:34:45 -0700
13536From:<tartanpion@machin.truc>
13537
13538Hello,
13539Bye.
13540EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013541 ), 'regexmess: From mbox 5 remove');
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013542
13543
13544@regexmess = ( 's{\A((?:[^\n]+\n)+|)^Disposition-Notification-To:[^\n]*\n(\r?\n|.*\n\r?\n)}{$1$2}xms' ) ; # SUPER SUPER BEST!
13545 ok(
13546<<'EOM'
13547Date: Sat, 10 Jul 2010 05:34:45 -0700
13548From:<tartanpion@machin.truc>
13549
13550Hello,
13551Bye.
13552EOM
13553 eq regexmess(
13554<<'EOM'
13555Date: Sat, 10 Jul 2010 05:34:45 -0700
13556Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13557From:<tartanpion@machin.truc>
13558
13559Hello,
13560Bye.
13561EOM
13562 ),
13563 'regexmess: 1 Delete header Disposition-Notification-To:');
13564
13565 ok(
13566<<'EOM'
13567Date: Sat, 10 Jul 2010 05:34:45 -0700
13568From:<tartanpion@machin.truc>
13569
13570Hello,
13571Bye.
13572EOM
13573 eq regexmess(
13574<<'EOM'
13575Date: Sat, 10 Jul 2010 05:34:45 -0700
13576From:<tartanpion@machin.truc>
13577Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13578
13579Hello,
13580Bye.
13581EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013582 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013583 'regexmess: 2 Delete header Disposition-Notification-To:');
13584
13585 ok(
13586<<'EOM'
13587Date: Sat, 10 Jul 2010 05:34:45 -0700
13588From:<tartanpion@machin.truc>
13589
13590Hello,
13591Bye.
13592EOM
13593 eq regexmess(
13594<<'EOM'
13595Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13596Date: Sat, 10 Jul 2010 05:34:45 -0700
13597From:<tartanpion@machin.truc>
13598
13599Hello,
13600Bye.
13601EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013602 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013603 'regexmess: 3 Delete header Disposition-Notification-To:');
13604
13605 ok(
13606<<'EOM'
13607Date: Sat, 10 Jul 2010 05:34:45 -0700
13608From:<tartanpion@machin.truc>
13609
13610Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13611Bye.
13612EOM
13613 eq regexmess(
13614<<'EOM'
13615Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13616Date: Sat, 10 Jul 2010 05:34:45 -0700
13617From:<tartanpion@machin.truc>
13618
13619Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13620Bye.
13621EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013622 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013623 'regexmess: 4 Delete header Disposition-Notification-To:');
13624
13625
13626 ok(
13627<<'EOM'
13628Date: Sat, 10 Jul 2010 05:34:45 -0700
13629From:<tartanpion@machin.truc>
13630
13631Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13632Bye.
13633EOM
13634 eq regexmess(
13635<<'EOM'
13636Date: Sat, 10 Jul 2010 05:34:45 -0700
13637From:<tartanpion@machin.truc>
13638
13639Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13640Bye.
13641EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013642 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013643 'regexmess: 5 Delete header Disposition-Notification-To:');
13644
13645
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013646 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013647<<'EOM'
13648Date: Sat, 10 Jul 2010 05:34:45 -0700
13649From:<tartanpion@machin.truc>
13650
13651Hello,
13652Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13653Bye.
13654EOM
13655 eq regexmess(
13656<<'EOM'
13657Date: Sat, 10 Jul 2010 05:34:45 -0700
13658From:<tartanpion@machin.truc>
13659
13660Hello,
13661Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13662Bye.
13663EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013664 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013665 'regexmess: 6 Delete header Disposition-Notification-To:');
13666
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013667 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013668<<'EOM'
13669Date: Sat, 10 Jul 2010 05:34:45 -0700
13670From:<tartanpion@machin.truc>
13671
13672Hello,
13673Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13674
13675Bye.
13676EOM
13677 eq regexmess(
13678<<'EOM'
13679Date: Sat, 10 Jul 2010 05:34:45 -0700
13680From:<tartanpion@machin.truc>
13681
13682Hello,
13683Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13684
13685Bye.
13686EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013687 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013688 'regexmess: 7 Delete header Disposition-Notification-To:');
13689
13690
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013691 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013692<<'EOM'
13693Date: Sat, 10 Jul 2010 05:34:45 -0700
13694From:<tartanpion@machin.truc>
13695
13696Hello,
13697Bye.
13698EOM
13699 eq regexmess(
13700<<'EOM'
13701Date: Sat, 10 Jul 2010 05:34:45 -0700
13702From:<tartanpion@machin.truc>
13703
13704Hello,
13705Bye.
13706EOM
13707),
13708 'regexmess: 8 Delete header Disposition-Notification-To:');
13709
13710
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013711 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013712<<'EOM'
13713Date: Sat, 10 Jul 2010 05:34:45 -0700
13714From:<tartanpion@machin.truc>
13715
13716Hello,
13717Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13718Bye.
13719EOM
13720 eq regexmess(
13721<<'EOM'
13722Date: Sat, 10 Jul 2010 05:34:45 -0700
13723From:<tartanpion@machin.truc>
13724
13725Hello,
13726Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13727Bye.
13728EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013729 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013730 'regexmess: 9 Delete header Disposition-Notification-To:');
13731
13732
13733
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013734 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013735<<'EOM'
13736Date: Sat, 10 Jul 2010 05:34:45 -0700
13737From:<tartanpion@machin.truc>
13738
13739Hello,
13740Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13741
13742
13743Bye.
13744EOM
13745 eq regexmess(
13746<<'EOM'
13747Date: Sat, 10 Jul 2010 05:34:45 -0700
13748From:<tartanpion@machin.truc>
13749
13750Hello,
13751Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13752
13753
13754Bye.
13755EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013756 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013757 'regexmess: 10 Delete header Disposition-Notification-To:');
13758
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013759 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013760<<'EOM'
13761Date: Sat, 10 Jul 2010 05:34:45 -0700
13762From:<tartanpion@machin.truc>
13763
13764Hello,
13765
13766Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13767
13768Bye.
13769EOM
13770 eq regexmess(
13771<<'EOM'
13772Date: Sat, 10 Jul 2010 05:34:45 -0700
13773From:<tartanpion@machin.truc>
13774
13775Hello,
13776
13777Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13778
13779Bye.
13780EOM
13781),
13782 'regexmess: 11 Delete header Disposition-Notification-To:');
13783
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013784 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013785<<'EOM'
13786Date: Sat, 10 Jul 2010 05:34:45 -0700
13787From:<tartanpion@machin.truc>
13788
13789Hello,
13790
13791Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13792
13793Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13794
13795Bye.
13796EOM
13797 eq regexmess(
13798<<'EOM'
13799Date: Sat, 10 Jul 2010 05:34:45 -0700
13800From:<tartanpion@machin.truc>
13801
13802Hello,
13803
13804Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13805
13806Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13807
13808Bye.
13809EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013810 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013811 'regexmess: 12 Delete header Disposition-Notification-To:');
13812
13813
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013814 @regexmess = ( 's{\A(.*?(?! ^$))^Disposition-Notification-To:(.*?)$}{$1X-Disposition-Notification-To:$2}igxms' ) ; # BAD!
13815 @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 +010013816
13817
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013818 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013819<<'EOM'
13820Date: Sat, 10 Jul 2010 05:34:45 -0700
13821From:<tartanpion@machin.truc>
13822
13823Hello,
13824
13825Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13826
13827Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13828
13829Bye.
13830EOM
13831 eq regexmess(
13832<<'EOM'
13833Date: Sat, 10 Jul 2010 05:34:45 -0700
13834From:<tartanpion@machin.truc>
13835
13836Hello,
13837
13838Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13839
13840Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13841
13842Bye.
13843EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013844 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013845 'regexmess: 13 Delete header Disposition-Notification-To:');
13846
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013847 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013848<<'EOM'
13849Date: Sat, 10 Jul 2010 05:34:45 -0700
13850X-Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13851From:<tartanpion@machin.truc>
13852
13853Hello,
13854
13855Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13856
13857Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13858
13859Bye.
13860EOM
13861 eq regexmess(
13862<<'EOM'
13863Date: Sat, 10 Jul 2010 05:34:45 -0700
13864Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13865From:<tartanpion@machin.truc>
13866
13867Hello,
13868
13869Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13870
13871Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13872
13873Bye.
13874EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013875 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013876 'regexmess: 14 Delete header Disposition-Notification-To:');
13877
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013878 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013879<<'EOM'
13880Date: Sat, 10 Jul 2010 05:34:45 -0700
13881X-Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13882From:<tartanpion@machin.truc>
13883
13884Hello,
13885
13886Bye.
13887EOM
13888 eq regexmess(
13889<<'EOM'
13890Date: Sat, 10 Jul 2010 05:34:45 -0700
13891Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13892From:<tartanpion@machin.truc>
13893
13894Hello,
13895
13896Bye.
13897EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013898 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013899 'regexmess: 15 Delete header Disposition-Notification-To:');
13900
13901
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013902 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013903<<'EOM'
13904Date: Sat, 10 Jul 2010 05:34:45 -0700
13905From:<tartanpion@machin.truc>
13906X-Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13907
13908Hello,
13909
13910Bye.
13911EOM
13912 eq regexmess(
13913<<'EOM'
13914Date: Sat, 10 Jul 2010 05:34:45 -0700
13915From:<tartanpion@machin.truc>
13916Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13917
13918Hello,
13919
13920Bye.
13921EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013922 ),
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013923 'regexmess: 16 Delete header Disposition-Notification-To:');
13924
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013925 ok(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013926<<'EOM'
13927X-Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13928Date: Sat, 10 Jul 2010 05:34:45 -0700
13929From:<tartanpion@machin.truc>
13930
13931Hello,
13932
13933Bye.
13934EOM
13935 eq regexmess(
13936<<'EOM'
13937Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
13938Date: Sat, 10 Jul 2010 05:34:45 -0700
13939From:<tartanpion@machin.truc>
13940
13941Hello,
13942
13943Bye.
13944EOM
13945),
13946 'regexmess: 17 Delete header Disposition-Notification-To:');
13947
13948 @regexmess = ( 's/.{11}\K.*//gs' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013949 is( "0123456789\n", regexmess( "0123456789\n" x 100 ), 'regexmess: truncate whole message after 11 characters' ) ;
13950 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 +010013951
13952 @regexmess = ( 's/.{10000}\K.*//gs' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013953 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 +010013954
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013955 @regexmess = ( 's/^(X-Ham-Report.*?\n)^X-/X-/sm' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013956
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013957 is(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013958<<'EOM'
13959X-Spam-Score: -1
13960X-Spam-Bar: /
13961X-Spam-Flag: NO
13962Date: Sat, 10 Jul 2010 05:34:45 -0700
13963From:<tartanpion@machin.truc>
13964
13965Hello,
13966
13967Bye.
13968EOM
13969,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013970 regexmess(
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013971<<'EOM'
13972X-Spam-Score: -1
13973X-Spam-Bar: /
13974X-Ham-Report: =?utf-8?Q?Spam_detection_software=2C_running?=
13975 =?utf-8?Q?_on_the_system_=22ohp-ag006.int200?=
13976_has_NOT_identified_thi?=
13977 =?utf-8?Q?s_incoming_email_as_spam.__The_o?=
13978_message_has_been_attac?=
13979 =?utf-8?Q?hed_to_this_so_you_can_view_it_o?=
13980___________________________?=
13981 =?utf-8?Q?__author's_domain
13982X-Spam-Flag: NO
13983Date: Sat, 10 Jul 2010 05:34:45 -0700
13984From:<tartanpion@machin.truc>
13985
13986Hello,
13987
13988Bye.
13989EOM
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013990 ),
13991 'regexmess: Delete header X-Ham-Report:');
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013992
13993
13994# regex to play with Date: from the FAQ
13995#@regexmess = 's{\A(.*?(?! ^$))^Date:(.*?)$}{$1Date:$2\nX-Date:$2}gxms'
13996
13997
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020013998# Change 8bit characters in whole email to X characters
13999 @regexmess = ( 's{[\x80-\xff]}{X}gxms' ) ;
14000 is( 'X-8bit: kaka 1 XX kiki', regexmess('X-8bit: kaka 1 ¤ kiki'), 'regexmess: 1 Change 8bit characters in whole email to X characters');
14001
14002# Same change but using tr
14003 @regexmess = ( 'tr [\x80-\xff] [X]' ) ;
14004 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 +010014005
14006
14007
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014008# Add a final \r\n if missing
14009 @regexmess = ( 's{(?<![\n])\z}{\r\n}gxms' ) ;
14010 is( "\r\n", regexmess(""), 'regexmess: 1. Add a final \r\n if missing. Missing' ) ;
14011 is( "abc\r\n", regexmess("abc"), 'regexmess: 2. Add a final \r\n if missing. Missing' ) ;
14012 is( "abc\ndef\r\n", regexmess("abc\ndef"), 'regexmess: 3. Add a final \r\n if missing. Missing' ) ;
14013 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 +010014014
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014015 is( "\r\n", regexmess("\r\n"), 'regexmess: 3. Add a final \r\n if missing. Not missing' ) ;
14016 is( "abc\n", regexmess("abc\n"), 'regexmess: 4. Add a final \r\n if missing. Not missing' ) ;
14017 is( "abc\r\n", regexmess("abc\r\n"), 'regexmess: 4. Add a final \r\n if missing. Not missing' ) ;
14018 is( "abc\ndef\n", regexmess("abc\ndef\n"), 'regexmess: 4. Add a final \r\n if missing. Not missing' ) ;
14019 is( "abc\r\ndef\r\n", regexmess("abc\r\ndef\r\n"), 'regexmess: 4. Add a final \r\n if missing. Not missing' ) ;
14020
14021# Remove the fucking buggy X-Spam-Report: a bad header on several lines that can even begin without a space!
14022
14023 @regexmess = ( 's{X-Spam-Report:.*?\n(^[^\n]+:|^\r?\n)}{$1}xms' ) ;
14024 # Damien regexes:
14025 #@regexmess = ( 's{X-Spam-Report:.*?\n(^[a-zA-Z0-9\-]+:)}{$1}xms' ) ;
14026 #@regexmess = ( 's{X-Spam-Report:.*?\n(^[a-zA-Z0-9\-]+:|^\r?\n)}{$1}xms' ) ;
14027
14028 is(
14029<<'EOM'
14030Date: Sat, 10 Jul 2010 05:34:45 -0700
14031From:<tartanpion@machin.truc>
14032LaSuite: super
14033
14034Hello,
14035Bye.
14036EOM
14037 , regexmess(
14038<<'EOM'
14039Date: Sat, 10 Jul 2010 05:34:45 -0700
14040From:<tartanpion@machin.truc>
14041X-Spam-Report: caca
14042caca
14043 caca
14044caca
14045LaSuite: super
14046
14047Hello,
14048Bye.
14049EOM
14050 ), 'regexmess: 1 remove buggy X-Spam-Report: across several lines, not the final header');
14051
14052
14053 is(
14054<<'EOM'
14055Date: Sat, 10 Jul 2010 05:34:45 -0700
14056From:<tartanpion@machin.truc>
14057LaSuite: super
14058LaSuite2: super 2
14059
14060Hello,
14061Bye.
14062EOM
14063 , regexmess(
14064<<'EOM'
14065Date: Sat, 10 Jul 2010 05:34:45 -0700
14066From:<tartanpion@machin.truc>
14067X-Spam-Report: caca
14068caca
14069 caca
14070caca
14071LaSuite: super
14072LaSuite2: super 2
14073
14074Hello,
14075Bye.
14076EOM
14077 ), 'regexmess: 2 remove buggy X-Spam-Report: across several lines, not the final header');
14078
14079
14080 is(
14081<<'EOM'
14082Date: Sat, 10 Jul 2010 05:34:45 -0700
14083From:<tartanpion@machin.truc>
14084LaSuite: super
14085LaSuite2: super 2
14086
14087Hello,
14088Bye.
14089EOM
14090 , regexmess(
14091<<'EOM'
14092X-Spam-Report: caca
14093caca
14094 caca
14095caca
14096Date: Sat, 10 Jul 2010 05:34:45 -0700
14097From:<tartanpion@machin.truc>
14098LaSuite: super
14099LaSuite2: super 2
14100
14101Hello,
14102Bye.
14103EOM
14104 ), 'regexmess: 3 remove buggy X-Spam-Report: across several lines, first header');
14105
14106
14107
14108
14109 is(
14110<<'EOM'
14111Date: Sat, 10 Jul 2010 05:34:45 -0700
14112From:<tartanpion@machin.truc>
14113
14114Hello,
14115Bye.
14116EOM
14117 , regexmess(
14118<<'EOM'
14119Date: Sat, 10 Jul 2010 05:34:45 -0700
14120From:<tartanpion@machin.truc>
14121X-Spam-Report: caca
14122caca
14123 caca
14124caca
14125
14126Hello,
14127Bye.
14128EOM
14129 ), 'regexmess: 4 remove buggy X-Spam-Report: across several lines, final header');
14130
14131
14132 is(
14133<<'EOM'
14134Date: Sat, 10 Jul 2010 05:34:45 -0700
14135From:<tartanpion@machin.truc>
14136
14137Hello,
14138Bye.
14139EOM
14140 , regexmess(
14141<<'EOM'
14142Date: Sat, 10 Jul 2010 05:34:45 -0700
14143From:<tartanpion@machin.truc>
14144
14145Hello,
14146Bye.
14147EOM
14148 ), 'regexmess: 5 remove buggy X-Spam-Report: not there at all');
14149
14150
14151 is(
14152<<"EOM"
14153Date: Sat, 10 Jul 2010 05:34:45 -0700\r
14154From:<tartanpion>\r
14155LaSuite: super\r
14156LaSuite2: super 2\r
14157\r
14158Hello,\r
14159Bye.\r
14160EOM
14161 , regexmess(
14162<<"EOM"
14163X-Spam-Report: caca\r
14164caca\r
14165 caca\r
14166caca\r
14167Date: Sat, 10 Jul 2010 05:34:45 -0700\r
14168From:<tartanpion>\r
14169LaSuite: super\r
14170LaSuite2: super 2\r
14171\r
14172Hello,\r
14173Bye.\r
14174EOM
14175 ), 'regexmess: 6 remove buggy X-Spam-Report: across several lines, first header, with \r');
14176
14177
14178 is(
14179<<"EOM"
14180Date: Sat, 10 Jul 2010 05:34:45 -0700\r
14181From:<tartanpion>\r
14182LaSuite: super\r
14183LaSuite2: super 2\r
14184\r
14185Hello,\r
14186Bye.\r
14187EOM
14188 , regexmess(
14189<<"EOM"
14190Date: Sat, 10 Jul 2010 05:34:45 -0700\r
14191From:<tartanpion>\r
14192X-Spam-Report: caca\r
14193caca\r
14194 caca\r
14195caca\r
14196LaSuite: super\r
14197LaSuite2: super 2\r
14198\r
14199Hello,\r
14200Bye.\r
14201EOM
14202 ), 'regexmess: 7 remove buggy X-Spam-Report: across several lines, middle header, with \r');
14203
14204
14205 is(
14206<<"EOM"
14207Date: Sat, 10 Jul 2010 05:34:45 -0700\r
14208From:<tartanpion>\r
14209\r
14210Hello,\r
14211Bye.\r
14212EOM
14213 , regexmess(
14214<<"EOM"
14215Date: Sat, 10 Jul 2010 05:34:45 -0700\r
14216From:<tartanpion>\r
14217X-Spam-Report: caca\r
14218caca\r
14219 caca\r
14220caca\r
14221\r
14222Hello,\r
14223Bye.\r
14224EOM
14225 ), 'regexmess: 8 remove buggy X-Spam-Report: across several lines, final header, with \r');
14226
14227
14228 undef @regexmess ;
14229 note( 'Leaving tests_regexmess()' ) ;
14230 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014231}
14232
14233sub regexmess
14234{
14235 my ( $string ) = @_ ;
14236 foreach my $regexmess ( @regexmess ) {
14237 $sync->{ debug } and myprint( "eval \$string =~ $regexmess\n" ) ;
14238 my $ret = eval "\$string =~ $regexmess ; 1" ;
14239 #myprint( "eval [$ret]\n" ) ;
14240 if ( ( not $ret ) or $EVAL_ERROR ) {
14241 myprint( "Error: eval regexmess '$regexmess': $EVAL_ERROR" ) ;
14242 return( undef ) ;
14243 }
14244 }
14245 $sync->{ debug } and myprint( "$string\n" ) ;
14246 return( $string ) ;
14247}
14248
14249
14250sub tests_skipmess
14251{
14252 note( 'Entering tests_skipmess()' ) ;
14253
14254 ok( not( defined skipmess( 'blabla' ) ), 'skipmess, no skipmess, no skip' ) ;
14255
14256 @skipmess = ('[') ;
14257 ok( not( defined skipmess( 'popopo' ) ), 'skipmess, bad regex [' ) ;
14258
14259 @skipmess = ('lalala') ;
14260 ok( not( defined skipmess( 'popopo' ) ), 'skipmess, bad regex lalala' ) ;
14261
14262 @skipmess = ('/popopo/') ;
14263 ok( 1 == skipmess( 'popopo' ), 'skipmess, popopo match regex /popopo/' ) ;
14264
14265 @skipmess = ('/popopo/') ;
14266 ok( 0 == skipmess( 'rrrrrr' ), 'skipmess, rrrrrr does not match regex /popopo/' ) ;
14267
14268 @skipmess = ('m{^$}') ;
14269 ok( 1 == skipmess( q{} ), 'skipmess: empty string yes' ) ;
14270 ok( 0 == skipmess( 'Hi!' ), 'skipmess: empty string no' ) ;
14271
14272 @skipmess = ('m{i}') ;
14273 ok( 1 == skipmess( 'Hi!' ), 'skipmess: i string yes' ) ;
14274 ok( 0 == skipmess( 'Bye!' ), 'skipmess: i string no' ) ;
14275
14276 @skipmess = ('m{[\x80-\xff]}') ;
14277 ok( 0 == skipmess( 'Hi!' ), 'skipmess: i 8bit no' ) ;
14278 ok( 1 == skipmess( "\xff" ), 'skipmess: \xff 8bit yes' ) ;
14279
14280 @skipmess = ('m{A}', 'm{B}') ;
14281 ok( 0 == skipmess( 'Hi!' ), 'skipmess: A or B no' ) ;
14282 ok( 0 == skipmess( 'lala' ), 'skipmess: A or B no' ) ;
14283 ok( 0 == skipmess( "\xff" ), 'skipmess: A or B no' ) ;
14284 ok( 1 == skipmess( 'AB' ), 'skipmess: A or B yes' ) ;
14285 ok( 1 == skipmess( 'BA' ), 'skipmess: A or B yes' ) ;
14286 ok( 1 == skipmess( 'AA' ), 'skipmess: A or B yes' ) ;
14287 ok( 1 == skipmess( 'Ok Bye' ), 'skipmess: A or B yes' ) ;
14288
14289
14290 @skipmess = ( 'm#\A((?:[^\n]+\n)+|)^Content-Type: Message/Partial;[^\n]*\n(?:\n|.*\n\n)#ism' ) ; # SUPER BEST!
14291
14292
14293
14294 ok( 1 == skipmess(
14295<<'EOM'
14296Date: Sat, 10 Jul 2010 05:34:45 -0700
14297Content-Type: Message/Partial; blabla
14298From:<tartanpion@machin.truc>
14299
14300Hello!
14301Bye.
14302EOM
14303),
14304 'skipmess: 1 match Content-Type: Message/Partial' ) ;
14305
14306 ok( 0 == skipmess(
14307<<'EOM'
14308Date: Sat, 10 Jul 2010 05:34:45 -0700
14309From:<tartanpion@machin.truc>
14310
14311Hello!
14312Bye.
14313EOM
14314),
14315 'skipmess: 2 not match Content-Type: Message/Partial' ) ;
14316
14317
14318 ok( 1 == skipmess(
14319<<'EOM'
14320Date: Sat, 10 Jul 2010 05:34:45 -0700
14321From:<tartanpion@machin.truc>
14322Content-Type: Message/Partial; blabla
14323
14324Hello!
14325Bye.
14326EOM
14327),
14328 'skipmess: 3 match Content-Type: Message/Partial' ) ;
14329
14330 ok( 0 == skipmess(
14331<<'EOM'
14332Date: Sat, 10 Jul 2010 05:34:45 -0700
14333From:<tartanpion@machin.truc>
14334
14335Hello!
14336Content-Type: Message/Partial; blabla
14337Bye.
14338EOM
14339),
14340 'skipmess: 4 not match Content-Type: Message/Partial' ) ;
14341
14342
14343 ok( 0 == skipmess(
14344<<'EOM'
14345Date: Sat, 10 Jul 2010 05:34:45 -0700
14346From:<tartanpion@machin.truc>
14347
14348Hello!
14349Content-Type: Message/Partial; blabla
14350
14351Bye.
14352EOM
14353),
14354 'skipmess: 5 not match Content-Type: Message/Partial' ) ;
14355
14356
14357 ok( 1 == skipmess(
14358<<'EOM'
14359Date: Sat, 10 Jul 2010 05:34:45 -0700
14360Content-Type: Message/Partial; blabla
14361From:<tartanpion@machin.truc>
14362
14363Hello!
14364
14365Content-Type: Message/Partial; blabla
14366
14367Bye.
14368EOM
14369),
14370 'skipmess: 6 match Content-Type: Message/Partial' ) ;
14371
14372 ok( 1 == skipmess(
14373<<'EOM'
14374Date: Sat, 10 Jul 2010 05:34:45 -0700
14375Content-Type: Message/Partial;
14376From:<tartanpion@machin.truc>
14377
14378Hello!
14379Bye.
14380EOM
14381),
14382 'skipmess: 7 match Content-Type: Message/Partial' ) ;
14383
14384 ok( 1 == skipmess(
14385<<'EOM'
14386Date: Wed, 2 Jul 2014 02:26:40 +0000
14387MIME-Version: 1.0
14388Content-Type: message/partial;
14389 id="TAN_U_P<1404267997.00007489ed17>";
14390 number=3;
14391 total=3
14392
143936HQ6Hh3CdXj77qEGixerQ6zHx0OnQ/Cf5On4W0Y6vtU2crABZQtD46Hx1EOh8dDz4+OnTr1G
14394
14395
14396Hello!
14397Bye.
14398EOM
14399),
14400 'skipmess: 8 match Content-Type: Message/Partial' ) ;
14401
14402
14403ok( 1 == skipmess(
14404<<'EOM'
14405Return-Path: <gilles@lamiral.info>
14406Received: by lamiral.info (Postfix, from userid 1000)
14407 id 21EB12443BF; Mon, 2 Mar 2015 15:38:35 +0100 (CET)
14408Subject: test: aethaecohngiexao
14409To: <tata@petite.lamiral.info>
14410X-Mailer: mail (GNU Mailutils 2.2)
14411Message-Id: <20150302143835.21EB12443BF@lamiral.info>
14412Content-Type: message/partial;
14413 id="TAN_U_P<1404267997.00007489ed17>";
14414 number=3;
14415 total=3
14416Date: Mon, 2 Mar 2015 15:38:34 +0100 (CET)
14417From: gilles@lamiral.info (Gilles LAMIRAL)
14418
14419test: aethaecohngiexao
14420EOM
14421),
14422 'skipmess: 9 match Content-Type: Message/Partial' ) ;
14423
14424ok( 1 == skipmess(
14425<<'EOM'
14426Date: Mon, 2 Mar 2015 15:38:34 +0100 (CET)
14427From: gilles@lamiral.info (Gilles LAMIRAL)
14428Content-Type: message/partial;
14429 id="TAN_U_P<1404267997.00007489ed17>";
14430 number=3;
14431 total=3
14432
14433test: aethaecohngiexao
14434EOM
14435. "lalala\n" x 3_000_000
14436),
14437 'skipmess: 10 match Content-Type: Message/Partial' ) ;
14438
14439ok( 0 == skipmess(
14440<<'EOM'
14441Date: Mon, 2 Mar 2015 15:38:34 +0100 (CET)
14442From: gilles@lamiral.info (Gilles LAMIRAL)
14443
14444test: aethaecohngiexao
14445EOM
14446. "lalala\n" x 3_000_000
14447),
14448 'skipmess: 11 match Content-Type: Message/Partial' ) ;
14449
14450
14451ok( 0 == skipmess(
14452<<"EOM"
14453From: fff\r
14454To: fff\r
14455Subject: Testing imapsync --skipmess\r
14456Date: Mon, 22 Aug 2011 08:40:20 +0800\r
14457Mime-Version: 1.0\r
14458Content-Type: text/plain; charset=iso-8859-1\r
14459Content-Transfer-Encoding: 7bit\r
14460\r
14461EOM
14462. qq{!#"d%&'()*+,-./0123456789:;<=>?\@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefg\r\n } x 32_730
14463),
14464 'skipmess: 12 not match Content-Type: Message/Partial' ) ;
14465 # Complex regular subexpression recursion limit (32766) exceeded with more lines
14466 # exit;
14467
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014468
14469 undef @skipmess ;
14470 note( 'Leaving tests_skipmess()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014471 return ;
14472}
14473
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014474
14475sub tests_skipmess_neg
14476{
14477 note( 'Entering tests_skipmess_neg()' ) ;
14478
14479
14480 @skipmess = ('m{i}') ;
14481 ok( 1 == skipmess( 'Hi!' ), 'skipmess: i string yes' ) ;
14482 ok( 0 == skipmess( 'Ho!' ), 'skipmess: i string no' ) ;
14483
14484 @skipmess = ('m{\A(?!.*i)}') ;
14485 ok( 0 == skipmess( 'Hi!' ), 'skipmess: not i string no' ) ;
14486 ok( 1 == skipmess( 'Ho!' ), 'skipmess: not i string yes' ) ;
14487
14488
14489 @skipmess = ('m{\A(?!.*^From:[^\n]*tartanpion\@machin\.truc)}xms') ;
14490
14491 ok( 0 == skipmess(
14492<<'EOM'
14493Date: Sat, 10 Jul 2010 05:34:45 -0700
14494From: <tartanpion@machin.truc>
14495
14496Bye.
14497EOM
14498),
14499 'skipmess: 1 not From tartanpion@machin.truc' ) ;
14500
14501ok( 1 == skipmess(
14502<<'EOM'
14503Date: Sat, 10 Jul 2010 05:34:45 -0700
14504From: <kikiki@machin.truc>
14505
14506Bye.
14507EOM
14508),
14509 'skipmess: 2 not From tartanpion@machin.truc' ) ;
14510
14511
14512
14513
14514 ok( 0 == skipmess(
14515<<'EOM'
14516Date: Sat, 10 Jul 2010 05:34:45 -0700
14517From: <tartanpion@machin.truc>
14518
14519 From: <tartanpion@machin.truc>
14520Bye.
14521EOM
14522),
14523 'skipmess: 3 not From tartanpion@machin.truc' ) ;
14524
14525ok( 1 == skipmess(
14526<<'EOM'
14527Date: Sat, 10 Jul 2010 05:34:45 -0700
14528From: <kikiki@machin.truc>
14529
14530 From: <tartanpion@machin.truc>
14531Bye.
14532EOM
14533),
14534 'skipmess: 4 not From tartanpion@machin.truc' ) ;
14535
14536
14537
14538
14539 undef @skipmess ;
14540 note( 'Leaving tests_skipmess_neg()' ) ;
14541 return ;
14542}
14543
14544
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014545sub skipmess
14546{
14547 my ( $string ) = @_ ;
14548 my $match ;
14549 #myprint( "$string\n" ) ;
14550 foreach my $skipmess ( @skipmess ) {
14551 $sync->{ debug } and myprint( "eval \$match = \$string =~ $skipmess\n" ) ;
14552 my $ret = eval "\$match = \$string =~ $skipmess ; 1" ;
14553 #myprint( "eval [$ret]\n" ) ;
14554 $sync->{ debug } and myprint( "match [$match]\n" ) ;
14555 if ( ( not $ret ) or $EVAL_ERROR ) {
14556 myprint( "Error: eval skipmess '$skipmess': $EVAL_ERROR" ) ;
14557 return( undef ) ;
14558 }
14559 return( $match ) if ( $match ) ;
14560 }
14561 return( $match ) ;
14562}
14563
14564
14565
14566
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010014567sub tests_bytes_display_string_bin
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014568{
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010014569 note( 'Entering tests_bytes_display_string_bin()' ) ;
14570
14571 is( 'NA', bytes_display_string_bin( ), 'bytes_display_string_bin: no args => NA' ) ;
14572 is( 'NA', bytes_display_string_bin( undef ), 'bytes_display_string_bin: undef => NA' ) ;
14573 is( 'NA', bytes_display_string_bin( 'blabla' ), 'bytes_display_string_bin: blabla => NA' ) ;
14574
14575 is( '0.000 KiB', bytes_display_string_bin( 0 ), 'bytes_display_string_bin: 0 => 0.000 KiB' ) ;
14576 is( '0.001 KiB', bytes_display_string_bin( 1 ), 'bytes_display_string_bin: 1 => 0.001 KiB' ) ;
14577 is( '0.010 KiB', bytes_display_string_bin( 10 ), 'bytes_display_string_bin: 10 => 0.010 KiB' ) ;
14578 is( '0.976 KiB', bytes_display_string_bin( 999 ), 'bytes_display_string_bin: 999 => 0.976 KiB' ) ;
14579 note( bytes_display_string_bin( 999 ) ) ;
14580
14581 is( '0.999 KiB', bytes_display_string_bin( 1023 ), 'bytes_display_string_bin: 1023 => 0.999 KiB' ) ;
14582 note( bytes_display_string_bin( 1023 ) ) ;
14583 is( '1.000 KiB', bytes_display_string_bin( 1024 ), 'bytes_display_string_bin: 1024 => 1.000 KiB' ) ;
14584 note( bytes_display_string_bin( 1024 ) ) ;
14585 is( '1.001 KiB', bytes_display_string_bin( 1025 ), 'bytes_display_string_bin: 1025 => 1.001 KiB' ) ;
14586
14587 is( '9.999 KiB', bytes_display_string_bin( 10_239 ), 'bytes_display_string_bin: 10_239 => 9.999 KiB' ) ;
14588 note( bytes_display_string_bin( 10_239 ) ) ;
14589
14590 is( '10.000 KiB', bytes_display_string_bin( 10_240 ), 'bytes_display_string_bin: 10_240 => 10.000 KiB' ) ;
14591 note( bytes_display_string_bin( 10_240 ) ) ;
14592
14593 is( '999.999 KiB', bytes_display_string_bin( 1_023_999 ), 'bytes_display_string_bin: 1_023_999 => 999.999 KiB' ) ;
14594 note( bytes_display_string_bin( 1_023_999 ) ) ;
14595
14596 is( '0.977 MiB', bytes_display_string_bin( 1_024_000 ), 'bytes_display_string_bin: 1_024_000 => 0.977 MiB' ) ;
14597 note( bytes_display_string_bin( 1_024_000 ) ) ;
14598
14599 is( '0.999 MiB', bytes_display_string_bin( 1_047_527 ), 'bytes_display_string_bin: 1_047_527 => 0.999 MiB' ) ;
14600 note( bytes_display_string_bin( 1_047_527 ) ) ;
14601
14602 is( '0.999 MiB', bytes_display_string_bin( 1_048_051 ), 'bytes_display_string_bin: 1_048_051 => 0.999 MiB' ) ;
14603 note( bytes_display_string_bin( 1_048_051 ) ) ;
14604
14605 is( '1.000 MiB', bytes_display_string_bin( 1_048_052 ), 'bytes_display_string_bin: 1_048_052 => 1.000 MiB' ) ;
14606 note( bytes_display_string_bin( 1_048_052 ) ) ;
14607
14608 is( '1.000 MiB', bytes_display_string_bin( 1_048_575 ), 'bytes_display_string_bin: 1_048_575 => 1.000 MiB' ) ;
14609 is( '1.000 MiB', bytes_display_string_bin( 1_048_576 ), 'bytes_display_string_bin: 1_048_576 => 1.000 MiB' ) ;
14610
14611 is( '1.000 GiB', bytes_display_string_bin( 1_073_741_823 ), 'bytes_display_string_bin: 1_073_741_823 => 1.000 GiB' ) ;
14612 is( '1.000 GiB', bytes_display_string_bin( 1_073_741_824 ), 'bytes_display_string_bin: 1_073_741_824 => 1.000 GiB' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014613
14614
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010014615 is( '1.000 TiB', bytes_display_string_bin( 1_099_511_627_775 ), 'bytes_display_string_bin: 1_099_511_627_775 => 1.000 TiB' ) ;
14616 is( '1.000 TiB', bytes_display_string_bin( 1_099_511_627_776 ), 'bytes_display_string_bin: 1_099_511_627_776 => 1.000 TiB' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014617
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010014618 is( '1.000 PiB', bytes_display_string_bin( 1_125_899_906_842_623 ), 'bytes_display_string_bin: 1_125_899_906_842_623 => 1.000 PiB' ) ;
14619 is( '1.000 PiB', bytes_display_string_bin( 1_125_899_906_842_624 ), 'bytes_display_string_bin: 1_125_899_906_842_624 => 1.000 PiB' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014620
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010014621 is( '1024.000 PiB', bytes_display_string_bin( 1_152_921_504_606_846_975 ), 'bytes_display_string_bin: 1_152_921_504_606_846_975 => 1024.000 PiB' ) ;
14622 is( '1024.000 PiB', bytes_display_string_bin( 1_152_921_504_606_846_976 ), 'bytes_display_string_bin: 1_152_921_504_606_846_976 => 1024.000 PiB' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014623
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010014624 is( '1048576.000 PiB', bytes_display_string_bin( 1_180_591_620_717_411_303_424 ), 'bytes_display_string_bin: 1_180_591_620_717_411_303_424 => 1048576.000 PiB' ) ;
14625 note( bytes_display_string_bin( 1_180_591_620_717_411_303_424 ) ) ;
14626 note( bytes_display_string_bin( 3_000_000_000 ) ) ;
14627 note( 'Leaving tests_bytes_display_string_bin()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014628
14629 return ;
14630}
14631
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010014632sub bytes_display_string_bin
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014633{
14634 my ( $bytes ) = @_ ;
14635
14636 my $readable_value = q{} ;
14637
14638 if ( ! defined( $bytes ) ) {
14639 return( 'NA' ) ;
14640 }
14641
14642 if ( not match_number( $bytes ) ) {
14643 return( 'NA' ) ;
14644 }
14645
14646
14647
14648 SWITCH: {
14649 if ( abs( $bytes ) < ( 1000 * $KIBI ) ) {
14650 $readable_value = mysprintf( '%.3f KiB', $bytes / $KIBI) ;
14651 last SWITCH ;
14652 }
14653 if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI ) ) {
14654 $readable_value = mysprintf( '%.3f MiB', $bytes / ($KIBI * $KIBI) ) ;
14655 last SWITCH ;
14656 }
14657 if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI * $KIBI) ) {
14658 $readable_value = mysprintf( '%.3f GiB', $bytes / ($KIBI * $KIBI * $KIBI) ) ;
14659 last SWITCH ;
14660 }
14661 if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI * $KIBI * $KIBI) ) {
14662 $readable_value = mysprintf( '%.3f TiB', $bytes / ($KIBI * $KIBI * $KIBI * $KIBI) ) ;
14663 last SWITCH ;
14664 } else {
14665 $readable_value = mysprintf( '%.3f PiB', $bytes / ($KIBI * $KIBI * $KIBI * $KIBI * $KIBI) ) ;
14666 }
14667 # if you have exabytes (EiB) of email to transfer, you have too much email!
14668 }
14669 #myprint( "$bytes = $readable_value\n" ) ;
14670 return( $readable_value ) ;
14671}
14672
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010014673sub tests_bytes_display_string_dec
14674{
14675 note( 'Entering tests_bytes_display_string_dec()' ) ;
14676
14677 is( 'NA', bytes_display_string_dec( ), 'bytes_display_string_dec: no args => NA' ) ;
14678 is( 'NA', bytes_display_string_dec( undef ), 'bytes_display_string_dec: undef => NA' ) ;
14679 is( 'NA', bytes_display_string_dec( 'blabla' ), 'bytes_display_string_dec: blabla => NA' ) ;
14680
14681 is( '0 bytes', bytes_display_string_dec( 0 ), 'bytes_display_string_dec: 0 => 0 bytes' ) ;
14682 is( '1 bytes', bytes_display_string_dec( 1 ), 'bytes_display_string_dec: 1 => 1 bytes' ) ;
14683 is( '10 bytes', bytes_display_string_dec( 10 ), 'bytes_display_string_dec: 10 => 10 bytes' ) ;
14684 is( '999 bytes', bytes_display_string_dec( 999 ), 'bytes_display_string_dec: 999 => 999 bytes' ) ;
14685
14686 is( '1.000 KB', bytes_display_string_dec( 1000 ), 'bytes_display_string_dec: 1000 => 1.000 KB' ) ;
14687 is( '1.001 KB', bytes_display_string_dec( 1001 ), 'bytes_display_string_dec: 1000 => 1.1001 KB' ) ;
14688
14689 is( '999.999 KB', bytes_display_string_dec( 999_999 ), 'bytes_display_string_dec: 999_999 => 999.999 KB' ) ;
14690
14691 is( '1.000 MB', bytes_display_string_dec( 1_000_000 ), 'bytes_display_string_dec: 1_000_000 => 1.000 MB' ) ;
14692 is( '1.000 MB', bytes_display_string_dec( 1_000_500 ), 'bytes_display_string_dec: 1_000_500 => 1.000 MB' ) ;
14693 is( '1.001 MB', bytes_display_string_dec( 1_000_501 ), 'bytes_display_string_dec: 1_000_501 => 1.001 MB' ) ;
14694 is( '999.999 MB', bytes_display_string_dec( 999_999_000 ), 'bytes_display_string_dec: 999_999_000 => 999.999 MB' ) ;
14695 is( '999.999 MB', bytes_display_string_dec( 999_999_499 ), 'bytes_display_string_dec: 999_999_499 => 999.999 MB' ) ;
14696 is( '1.000 GB', bytes_display_string_dec( 999_999_500 ), 'bytes_display_string_dec: 999_999_500 => 1.000 GB' ) ;
14697
14698 is( '1.000 GB', bytes_display_string_dec( 1_000_000_000 ), 'bytes_display_string_dec: 1_000_000_000 => 1.000 GB' ) ;
14699 is( '1.000 GB', bytes_display_string_dec( 1_000_500_000 ), 'bytes_display_string_dec: 1_000_500_000 => 1.000 GB' ) ;
14700 is( '1.001 GB', bytes_display_string_dec( 1_000_500_001 ), 'bytes_display_string_dec: 1_000_501_000 => 1.001 GB' ) ;
14701 is( '999.999 GB', bytes_display_string_dec( 999_999_000_000 ), 'bytes_display_string_dec: 999_999_000_000 => 999.999 GB' ) ;
14702 is( '999.999 GB', bytes_display_string_dec( 999_999_499_999 ), 'bytes_display_string_dec: 999_999_499_999 => 999.999 GB' ) ;
14703 is( '1.000 TB', bytes_display_string_dec( 999_999_500_000 ), 'bytes_display_string_dec: 999_999_500_000 => 1.000 TB' ) ;
14704
14705 is( '1.000 TB', bytes_display_string_dec( 1_000_000_000_000 ), 'bytes_display_string_dec: 1_000_000_000_000 => 1.000 TB' ) ;
14706 is( '1.000 TB', bytes_display_string_dec( 1_000_500_000_000 ), 'bytes_display_string_dec: 1_000_500_000_000 => 1.000 TB' ) ;
14707 is( '1.001 TB', bytes_display_string_dec( 1_000_500_000_001 ), 'bytes_display_string_dec: 1_000_500_000_000 => 1.000 TB' ) ;
14708 is( '999.999 TB', bytes_display_string_dec( 999_999_000_000_000 ), 'bytes_display_string_dec: 999_999_000_000_000 => 999.999 TB' ) ;
14709 is( '999.999 TB', bytes_display_string_dec( 999_999_499_999_999 ), 'bytes_display_string_dec: 999_999_499_999_999 => 999.999 TB' ) ;
14710 is( '1.000 PB', bytes_display_string_dec( 999_999_500_000_000 ), 'bytes_display_string_dec: 999_999_500_000_000 => 1.000 PB' ) ;
14711
14712 is( '3.000 GB', bytes_display_string_dec( 3_000_000_000 ), 'bytes_display_string_dec: 3_000_000_000 => 3.000 GB' ) ;
14713
14714 note( 'Leaving tests_bytes_display_string_dec()' ) ;
14715 return ;
14716}
14717
14718sub bytes_display_string_dec
14719{
14720 my ( $bytes ) = @_ ;
14721
14722 my $readable_value = q{} ;
14723
14724 if ( ! defined( $bytes ) ) {
14725 return( 'NA' ) ;
14726 }
14727
14728 if ( not match_number( $bytes ) ) {
14729 return( 'NA' ) ;
14730 }
14731
14732 SWITCH: {
14733 if ( abs( $bytes ) < ( 1000 ) ) {
14734 $readable_value = mysprintf( '%.0f bytes', $bytes ) ;
14735 last SWITCH ;
14736 }
14737 if ( abs( $bytes ) < ( 1000**2 ) ) {
14738 $readable_value = mysprintf( '%.3f KB', $bytes / 1000 ) ;
14739 last SWITCH ;
14740 }
14741 if ( abs( $bytes ) < ( 999_999_500 ) ) {
14742 $readable_value = mysprintf( '%.3f MB', $bytes / ( 1000**2 ) ) ;
14743 last SWITCH ;
14744 }
14745 if ( abs( $bytes ) < ( 999_999_500_000 ) ) {
14746 $readable_value = mysprintf( '%.3f GB', $bytes / ( 1000**3 ) ) ;
14747 last SWITCH ;
14748 }
14749 if ( abs( $bytes ) < ( 999_999_500_000_000 ) ) {
14750 $readable_value = mysprintf( '%.3f TB', $bytes / ( 1000**4 ) ) ;
14751 last SWITCH ;
14752 } else {
14753 $readable_value = mysprintf( '%.3f PB', $bytes / ( 1000**5 ) ) ;
14754 }
14755 # if you have exabytes (EiB) of email to transfer, you have too much email!
14756 }
14757 #myprint( "$bytes = $readable_value\n" ) ;
14758
14759 return( $readable_value ) ;
14760}
14761
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014762
14763sub tests_useheader_suggestion
14764{
14765 note( 'Entering tests_useheader_suggestion()' ) ;
14766
14767 is( undef, useheader_suggestion( ), 'useheader_suggestion: no args => undef' ) ;
14768 my $mysync = {} ;
14769
14770 $mysync->{ h1_nb_msg_noheader } = 0 ;
14771 is( q{}, useheader_suggestion( $mysync ), 'useheader_suggestion: h1_nb_msg_noheader count null => no suggestion' ) ;
14772 $mysync->{ h1_nb_msg_noheader } = 2 ;
14773 is( q{in order to sync those 2 unidentified messages, add option --addheader}, useheader_suggestion( $mysync ),
14774 'useheader_suggestion: h1_nb_msg_noheader count 2 => suggestion of --addheader' ) ;
14775
14776 note( 'Leaving tests_useheader_suggestion()' ) ;
14777 return ;
14778}
14779
14780sub useheader_suggestion
14781{
14782 my $mysync = shift ;
14783 if ( ! defined $mysync->{ h1_nb_msg_noheader } )
14784 {
14785 return ;
14786 }
14787 elsif ( 1 <= $mysync->{ h1_nb_msg_noheader } )
14788 {
14789 return qq{in order to sync those $mysync->{ h1_nb_msg_noheader } unidentified messages, add option --addheader} ;
14790 }
14791 else
14792 {
14793 return q{} ;
14794 }
14795 return ;
14796}
14797
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014798sub do_and_print_stats
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014799{
14800 my $mysync = shift ;
14801
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014802 if ( ! $mysync->{can_do_stats} ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014803 return ;
14804 }
14805
14806 my $timeend = time ;
14807 my $timediff = $timeend - $mysync->{timestart} ;
14808
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014809 my $timeend_str = localtimez( $timeend ) ;
14810
14811 my $cpu_time = cpu_time( $mysync ) ;
14812 my $cpu_percent = cpu_percent( $mysync, $cpu_time, $timediff ) ;
14813 my $cpu_percent_global = cpu_percent_global( $mysync, $cpu_percent ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014814
14815 my $memory_consumption_at_end = memory_consumption( ) || 0 ;
14816 my $memory_consumption_at_start = $mysync->{ memory_consumption_at_start } || 0 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014817 my $memory_ratio = ( $mysync->{ biggest_message_transferred } ) ?
14818 mysprintf( '%.1f', $memory_consumption_at_end / $mysync->{ biggest_message_transferred } ) : 'NA' ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014819
14820 # my $useheader_suggestion = useheader_suggestion( $mysync ) ;
14821 myprint( "++++ Statistics\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014822 myprint( "Transfer started on : $mysync->{ timestart_str }\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014823 myprint( "Transfer ended on : $timeend_str\n" ) ;
14824 myprintf( "Transfer time : %.1f sec\n", $timediff ) ;
14825 myprint( "Folders synced : $h1_folders_wanted_ct/$h1_folders_wanted_nb synced\n" ) ;
14826 myprint( "Messages transferred : $mysync->{ nb_msg_transferred } " ) ;
14827 myprint( "(could be $nb_msg_skipped_dry_mode without dry mode)" ) if ( $mysync->{dry} ) ;
14828 myprint( "\n" ) ;
14829 myprint( "Messages skipped : $mysync->{ nb_msg_skipped }\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014830 myprint( "Messages found duplicate on host1 : $mysync->{ acc1 }->{ nb_msg_duplicate }\n" ) ;
14831 myprint( "Messages found duplicate on host2 : $mysync->{ acc2 }->{ nb_msg_duplicate }\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014832 myprint( "Messages found crossduplicate on host2 : $mysync->{ h2_nb_msg_crossdup }\n" ) ;
14833 myprint( "Messages void (noheader) on host1 : $mysync->{ h1_nb_msg_noheader } ", useheader_suggestion( $mysync ), "\n" ) ;
14834 myprint( "Messages void (noheader) on host2 : $h2_nb_msg_noheader\n" ) ;
14835 nb_messages_in_1_not_in_2( $mysync ) ;
14836 nb_messages_in_2_not_in_1( $mysync ) ;
14837 myprintf( "Messages found in host1 not in host2 : %s messages\n", $mysync->{ nb_messages_in_1_not_in_2 } ) ;
14838 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 +020014839 myprint( "Messages deleted on host1 : $mysync->{ acc1 }->{ nb_msg_deleted }\n" ) ;
14840 myprint( "Messages deleted on host2 : $mysync->{ acc2 }->{ nb_msg_deleted }\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014841 myprintf( "Total bytes transferred : %s (%s)\n",
14842 $mysync->{total_bytes_transferred},
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010014843 bytes_display_string_bin( $mysync->{total_bytes_transferred} ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014844 myprintf( "Total bytes skipped : %s (%s)\n",
14845 $mysync->{ total_bytes_skipped },
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010014846 bytes_display_string_bin( $mysync->{ total_bytes_skipped } ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014847 $timediff ||= 1 ; # No division per 0
14848 myprintf("Message rate : %.1f messages/s\n", $mysync->{nb_msg_transferred} / $timediff ) ;
14849 myprintf("Average bandwidth rate : %.1f KiB/s\n", $mysync->{total_bytes_transferred} / $KIBI / $timediff ) ;
14850 myprint( "Reconnections to host1 : $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT}\n" ) ;
14851 myprint( "Reconnections to host2 : $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT}\n" ) ;
14852 myprintf("Memory consumption at the end : %.1f MiB (started with %.1f MiB)\n",
14853 $memory_consumption_at_end / $KIBI / $KIBI,
14854 $memory_consumption_at_start / $KIBI / $KIBI ) ;
14855 myprint( "Load end is : " . ( join( q{ }, loadavg( ) ) || 'unknown' ), " on $mysync->{cpu_number} cores\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014856 myprint( "CPU time and %cpu : $cpu_time sec $cpu_percent %cpu $cpu_percent_global %allcpus\n" ) ;
14857 myprintf("Biggest message transferred : %s bytes (%s)\n",
14858 $mysync->{ biggest_message_transferred },
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010014859 bytes_display_string_bin( $mysync->{ biggest_message_transferred } ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014860 myprint( "Memory/biggest message ratio : $memory_ratio\n" ) ;
14861 if ( $mysync->{ foldersizesatend } and $mysync->{ foldersizes } ) {
14862
14863
14864 my $nb_msg_start_diff = diff_or_NA( $mysync->{ h2_nb_msg_start }, $mysync->{ h1_nb_msg_start } ) ;
14865 my $bytes_start_diff = diff_or_NA( $mysync->{ h2_bytes_start }, $mysync->{ h1_bytes_start } ) ;
14866
14867 myprintf("Start difference host2 - host1 : %s messages, %s bytes (%s)\n", $nb_msg_start_diff,
14868 $bytes_start_diff,
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010014869 bytes_display_string_bin( $bytes_start_diff ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014870
14871 my $nb_msg_end_diff = diff_or_NA( $h2_nb_msg_end, $h1_nb_msg_end ) ;
14872 my $bytes_end_diff = diff_or_NA( $h2_bytes_end, $h1_bytes_end ) ;
14873
14874 myprintf("Final difference host2 - host1 : %s messages, %s bytes (%s)\n", $nb_msg_end_diff,
14875 $bytes_end_diff,
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010014876 bytes_display_string_bin( $bytes_end_diff ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014877 }
14878
14879 comment_on_final_diff_in_1_not_in_2( $mysync ) ;
14880 comment_on_final_diff_in_2_not_in_1( $mysync ) ;
14881 myprint( "Detected $mysync->{nb_errors} errors\n\n" ) ;
14882
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010014883 myprint( $mysync->{ warn_release }, "\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014884 myprint( homepage( ), "\n" ) ;
14885 return ;
14886}
14887
14888sub diff_or_NA
14889{
14890 my( $n1, $n2 ) = @ARG ;
14891
14892 if ( not defined $n1 or not defined $n2 ) {
14893 return 'NA' ;
14894 }
14895
14896 if ( not match_number( $n1 )
14897 or not match_number( $n2 ) ) {
14898 return 'NA' ;
14899 }
14900
14901 return( $n1 - $n2 ) ;
14902}
14903
14904sub match_number
14905{
14906 my $n = shift @ARG ;
14907
14908 if ( not defined $n ) {
14909 return 0 ;
14910 }
14911 if ( $n =~ /[0-9]+\.?[0-9]?/x ) {
14912 return 1 ;
14913 }
14914 else {
14915 return 0 ;
14916 }
14917}
14918
14919
14920sub tests_match_number
14921{
14922 note( 'Entering tests_match_number()' ) ;
14923
14924
14925 is( 0, match_number( ), 'match_number: no parameters => 0' ) ;
14926 is( 0, match_number( undef ), 'match_number: undef => 0' ) ;
14927 is( 0, match_number( 'blabla' ), 'match_number: blabla => 0' ) ;
14928 is( 1, match_number( 0 ), 'match_number: 0 => 1' ) ;
14929 is( 1, match_number( 1 ), 'match_number: 1 => 1' ) ;
14930 is( 1, match_number( 1.0 ), 'match_number: 1.0 => 1' ) ;
14931 is( 1, match_number( 0.0 ), 'match_number: 0.0 => 1' ) ;
14932
14933 note( 'Leaving tests_match_number()' ) ;
14934 return ;
14935}
14936
14937
14938
14939sub tests_diff_or_NA
14940{
14941 note( 'Entering tests_diff_or_NA()' ) ;
14942
14943
14944 is( 'NA', diff_or_NA( ), 'diff_or_NA: no parameters => NA' ) ;
14945 is( 'NA', diff_or_NA( undef ), 'diff_or_NA: undef => NA' ) ;
14946 is( 'NA', diff_or_NA( undef, undef ), 'diff_or_NA: undef undef => NA' ) ;
14947 is( 'NA', diff_or_NA( undef, 1 ), 'diff_or_NA: undef 1 => NA' ) ;
14948 is( 'NA', diff_or_NA( 1, undef ), 'diff_or_NA: 1 undef => NA' ) ;
14949 is( 'NA', diff_or_NA( 'blabla', 1 ), 'diff_or_NA: blabla 1 => NA' ) ;
14950 is( 'NA', diff_or_NA( 1, 'blabla' ), 'diff_or_NA: 1 blabla => NA' ) ;
14951 is( 0, diff_or_NA( 1, 1 ), 'diff_or_NA: 1 1 => 0' ) ;
14952 is( 1, diff_or_NA( 1, 0 ), 'diff_or_NA: 1 0 => 1' ) ;
14953 is( -1, diff_or_NA( 0, 1 ), 'diff_or_NA: 0 1 => -1' ) ;
14954 is( 0, diff_or_NA( 1.0, 1 ), 'diff_or_NA: 1.0 1 => 0' ) ;
14955 is( 1, diff_or_NA( 1.0, 0 ), 'diff_or_NA: 1.0 0 => 1' ) ;
14956 is( -1, diff_or_NA( 0, 1.0 ), 'diff_or_NA: 0 1.0 => -1' ) ;
14957
14958 note( 'Leaving tests_diff_or_NA()' ) ;
14959 return ;
14960}
14961
14962sub homepage
14963{
14964 return( 'Homepage: https://imapsync.lamiral.info/' ) ;
14965}
14966
14967
14968sub load_modules
14969{
14970 if ( $sync->{ssl1}
14971 or $sync->{ssl2}
14972 or $sync->{tls1}
14973 or $sync->{tls2}) {
14974 if ( $sync->{inet4} ) {
14975 IO::Socket::SSL->import( 'inet4' ) ;
14976 }
14977 if ( $sync->{inet6} ) {
14978 IO::Socket::SSL->import( 'inet6' ) ;
14979 }
14980 }
14981 return ;
14982}
14983
14984
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020014985# Globals: $skipsize $wholeheaderifneeded
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010014986sub parse_header_msg
14987{
14988 my ( $mysync, $imap, $m_uid, $s_heads, $s_fir, $side, $s_hash ) = @_ ;
14989
14990 my $head = $s_heads->{$m_uid} ;
14991 my $headnum = scalar keys %{ $head } ;
14992 $mysync->{ debug } and myprint( "$side: uid $m_uid number of headers, pass one: ", $headnum, "\n" ) ;
14993
14994 if ( ( ! $headnum ) and ( $wholeheaderifneeded ) ){
14995 $mysync->{ debug } and myprint( "$side: uid $m_uid no header by parse_headers so taking whole header with BODY.PEEK[HEADER]\n" ) ;
14996 $imap->fetch($m_uid, 'BODY.PEEK[HEADER]' ) ;
14997 my $whole_header = $imap->_transaction_literals ;
14998
14999 #myprint( $whole_header ) ;
15000 $head = decompose_header( $whole_header ) ;
15001
15002 $headnum = scalar keys %{ $head } ;
15003 $mysync->{ debug } and myprint( "$side: uid $m_uid number of headers, pass two: ", $headnum, "\n" ) ;
15004 }
15005
15006 #myprint( Data::Dumper->Dump( [ $head, \%useheader ] ) ) ;
15007
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015008 my $headstr = header_construct( $mysync, $head, $side, $m_uid ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015009
15010 if ( ( ! $headstr ) and ( $mysync->{addheader} ) and ( $side eq 'Host1' ) ) {
15011 my $header = add_header( $m_uid ) ;
15012 $mysync->{ debug } and myprint( "$side: uid $m_uid no header found so adding our own [$header]\n" ) ;
15013 $headstr .= uc $header ;
15014 $s_fir->{$m_uid}->{NO_HEADER} = 1;
15015 }
15016
15017 return if ( ! $headstr ) ;
15018
15019 my $size = $s_fir->{$m_uid}->{'RFC822.SIZE'} ;
15020 my $flags = $s_fir->{$m_uid}->{'FLAGS'} ;
15021 my $idate = $s_fir->{$m_uid}->{'INTERNALDATE'} ;
15022 $size = length $headstr unless ( $size ) ;
15023 my $m_md5 = md5_base64( $headstr ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015024
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015025 my $key ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015026 if ( $skipsize ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015027 $key = "$m_md5";
15028 }
15029 else {
15030 $key = "$m_md5:$size";
15031 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015032
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015033 if ( exists $s_hash->{"$key"} )
15034 {
15035 # 0 return code is used to identify duplicate message hash
15036 my $dup_ref = $s_hash->{"$key"}->{'U'} ;
15037 my $num = scalar( @{ $dup_ref } ) ;
15038 push( @{ $dup_ref }, $m_uid ) ;
15039 my $keydup = "$key#$num" ;
15040 $mysync->{ debug } and myprint( "$side: uid $m_uid sig $keydup size $size idate $idate dup @{ $dup_ref }\n" ) ;
15041 if ( $mysync->{ syncduplicates } )
15042 {
15043 $s_hash->{"$keydup"}{'5'} = $m_md5 ;
15044 $s_hash->{"$keydup"}{'s'} = $size ;
15045 $s_hash->{"$keydup"}{'D'} = $idate ;
15046 $s_hash->{"$keydup"}{'F'} = $flags ;
15047 $s_hash->{"$keydup"}{'m'} = $m_uid ;
15048 }
15049 return 0 ;
15050 }
15051 else
15052 {
15053 $s_hash->{"$key"}{'5'} = $m_md5 ;
15054 $s_hash->{"$key"}{'s'} = $size ;
15055 $s_hash->{"$key"}{'D'} = $idate ;
15056 $s_hash->{"$key"}{'F'} = $flags ;
15057 $s_hash->{"$key"}{'m'} = $m_uid ;
15058 $s_hash->{"$key"}{'U'} = [ $m_uid ] ; # ? or [ ] ?
15059 $mysync->{ debug } and myprint( "$side: uid $m_uid sig $key size $size idate $idate\n" ) ;
15060 return( 1 ) ;
15061 }
15062
15063 # we should not be here
15064 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015065}
15066
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015067sub tests_header_construct
15068{
15069 note( 'Entering tests_header_construct()' ) ;
15070
15071 is( undef, header_construct( ), 'header_construct: no args => undef' ) ;
15072 my $mysync = {} ;
15073 my $head = {
15074 'key1' => [ 'val1_key1' ]
15075 } ;
15076 is( undef, header_construct( $mysync, $head, 'Host1', '1' ), 'header_construct: key1 val1_key1 no useheader => undef' ) ;
15077
15078 $mysync->{useheader}->{ 'KEY1' } = 1 ;
15079 is( 'KEY1: VAL1_KEY1', header_construct( $mysync, $head, 'Host1', '1' ), 'header_construct: key1 val1_key1 => KEY1: VAL1_KEY1' ) ;
15080
15081
15082
15083 $head = {
15084 'key1' => [ 'val1_key1', 'val3_key1', 'val2_key1' ]
15085 } ;
15086 is( 'KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1', header_construct( $mysync, $head, 'Host1', '1' ),
15087 'header_construct: key1 val1_key1 val3_key1 val2_key1 => KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1' ) ;
15088
15089 $head = {
15090 'key1' => [ 'val1_key1', 'val3_key1', ' val2_key1' ]
15091 } ;
15092 is( 'KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1', header_construct( $mysync, $head, 'Host1', '1' ),
15093 'header_construct: key1 val1_key1 val3_key1 val2_key1 => KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1' ) ;
15094
15095 $mysync->{useheader}->{ 'ALL' } = 1 ;
15096
15097 is( 'KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1', header_construct( $mysync, $head, 'Host1', '1' ),
15098 'header_construct: key1 val1_key1 val3_key1 val2_key1 useheader ALL => KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1' ) ;
15099
15100 $mysync->{skipheader} = 'key1' ;
15101 is( undef, header_construct( $mysync, $head, 'Host1', '1' ),
15102 'header_construct: key1 val1_key1 val3_key1 val2_key1 useheader ALL => undef' ) ;
15103
15104 $head = {
15105 'key1' => [ 'val1_key1', 'val3_key1', ' val2_key1' ],
15106 'key2' => [ 'val1_key2', 'val3_key2', ' val2_key2' ]
15107 } ;
15108 is( 'KEY2: VAL1_KEY2KEY2: VAL2_KEY2KEY2: VAL3_KEY2', header_construct( $mysync, $head, 'Host1', '1' ),
15109 'header_construct: ... useheader ALL skipheader key1 => KEY2: VAL1_KEY2KEY2: VAL2_KEY2KEY2: VAL3_KEY2' ) ;
15110
15111
15112 note( 'Leaving tests_header_construct()' ) ;
15113 return ;
15114}
15115
15116
15117# No global in header_construct
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015118sub header_construct
15119{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015120 my( $mysync, $head, $side, $m_uid ) = @_ ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015121
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015122 my @headstr ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015123 foreach my $h ( sort keys %{ $head } ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015124 next if ( not ( exists $mysync->{useheader}->{ uc $h } )
15125 and ( not exists $mysync->{useheader}->{ 'ALL' } )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015126 ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015127 foreach my $val ( @{$head->{$h}} ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015128
15129 my $H = header_line_normalize( $h, $val ) ;
15130
15131 # show stuff in debug mode
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015132 $mysync->{ debug } and myprint( "$side uid $m_uid header [$H]", "\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015133
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015134 if ( $mysync->{skipheader} and $H =~ m/$mysync->{skipheader}/xi) {
15135 $mysync->{ debug } and myprint( "$side uid $m_uid skipping header [$H]\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015136 next ;
15137 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015138 push @headstr, $H ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015139 }
15140 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015141 my $headstr = join( '', sort @headstr ) || undef ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015142 return( $headstr ) ;
15143}
15144
15145
15146sub header_line_normalize
15147{
15148 my( $header_key, $header_val ) = @_ ;
15149
15150 # no 8-bit data in headers !
15151 $header_val =~ s/[\x80-\xff]/X/xog;
15152
15153 # change tabulations to space (Gmail bug on with "Received:" on multilines)
15154 $header_val =~ s/\t/\ /xgo ;
15155
15156 # remove the first blanks ( dbmail bug? )
15157 $header_val =~ s/^\s*//xo;
15158
15159 # remove the last blanks ( Gmail bug )
15160 $header_val =~ s/\s*$//xo;
15161
15162 # remove successive blanks ( Mailenable does it )
15163 $header_val =~ s/\s+/ /xgo;
15164
15165 # remove Message-Id value domain part ( Mailenable changes it )
15166 if ( ( $messageidnodomain ) and ( 'MESSAGE-ID' eq uc $header_key ) ) { $header_val =~ s/^([^@]+).*$/$1/xo ; }
15167
15168 # and uppercase header line
15169 # (dbmail and dovecot)
15170
15171 my $header_line = uc "$header_key: $header_val" ;
15172
15173 return( $header_line ) ;
15174}
15175
15176sub tests_header_line_normalize
15177{
15178 note( 'Entering tests_header_line_normalize()' ) ;
15179
15180
15181 ok( ': ' eq header_line_normalize( q{}, q{} ), 'header_line_normalize: empty args' ) ;
15182 ok( 'HHH: VVV' eq header_line_normalize( 'hhh', 'vvv' ), 'header_line_normalize: hhh vvv ' ) ;
15183 ok( 'HHH: VVV' eq header_line_normalize( 'hhh', ' vvv' ), 'header_line_normalize: remove first blancs' ) ;
15184 ok( 'HHH: AA BB CCC D' eq header_line_normalize( 'hhh', 'aa bb ccc d' ), 'header_line_normalize: remove succesive blanks' ) ;
15185 ok( 'HHH: AA BB CCC' eq header_line_normalize( 'hhh', 'aa bb ccc ' ), 'header_line_normalize: remove last blanks' ) ;
15186 ok( 'HHH: VVV XX YY' eq header_line_normalize( 'hhh', "vvv\t\txx\tyy" ), 'header_line_normalize: tabs' ) ;
15187 ok( 'HHH: XABX' eq header_line_normalize( 'hhh', "\x80AB\xff" ), 'header_line_normalize: 8bit' ) ;
15188
15189 note( 'Leaving tests_header_line_normalize()' ) ;
15190 return ;
15191}
15192
15193
15194sub tests_firstline
15195{
15196 note( 'Entering tests_firstline()' ) ;
15197
15198 is( q{}, firstline( 'W/tmp/tests/noexist.txt' ), 'firstline: getting empty string from inexisting W/tmp/tests/noexist.txt' ) ;
15199
15200 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'firstline: mkpath W/tmp/tests/' ) ;
15201
15202 is( "blabla\n" , string_to_file( "blabla\n", 'W/tmp/tests/firstline.txt' ), 'firstline: put blabla in W/tmp/tests/firstline.txt' ) ;
15203 is( 'blabla' , firstline( 'W/tmp/tests/firstline.txt' ), 'firstline: get blabla from W/tmp/tests/firstline.txt' ) ;
15204
15205 is( q{} , string_to_file( q{}, 'W/tmp/tests/firstline2.txt' ), 'firstline: put empty string in W/tmp/tests/firstline2.txt' ) ;
15206 is( q{} , firstline( 'W/tmp/tests/firstline2.txt' ), 'firstline: get empty string from W/tmp/tests/firstline2.txt' ) ;
15207
15208 is( "\n" , string_to_file( "\n", 'W/tmp/tests/firstline3.txt' ), 'firstline: put CR in W/tmp/tests/firstline3.txt' ) ;
15209 is( q{} , firstline( 'W/tmp/tests/firstline3.txt' ), 'firstline: get empty string from W/tmp/tests/firstline3.txt' ) ;
15210
15211 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' ) ;
15212 is( 'blabla' , firstline( 'W/tmp/tests/firstline4.txt' ), 'firstline: get blabla from W/tmp/tests/firstline4.txt' ) ;
15213
15214 note( 'Leaving tests_firstline()' ) ;
15215 return ;
15216}
15217
15218sub firstline
15219{
15220 # extract the first line of a file (without \n)
15221 # return empty string if error or empty string
15222
15223 my $file = shift ;
15224 my $line ;
15225
15226 $line = nthline( $file, 1 ) ;
15227 return $line ;
15228}
15229
15230
15231
15232sub tests_secondline
15233{
15234 note( 'Entering tests_secondline()' ) ;
15235
15236 is( q{}, secondline( 'W/tmp/tests/noexist.txt' ), 'secondline: getting empty string from inexisting W/tmp/tests/noexist.txt' ) ;
15237 is( q{}, secondline( 'W/tmp/tests/noexist.txt', 2 ), 'secondline: 2nd getting empty string from inexisting W/tmp/tests/noexist.txt' ) ;
15238
15239 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'secondline: mkpath W/tmp/tests/' ) ;
15240
15241 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' ) ;
15242 is( 'L2' , secondline( 'W/tmp/tests/secondline.txt' ), 'secondline: get L2 from W/tmp/tests/secondline.txt' ) ;
15243
15244
15245 note( 'Leaving tests_secondline()' ) ;
15246 return ;
15247}
15248
15249
15250sub secondline
15251{
15252 # extract the second line of a file (without \n)
15253 # return empty string if error or empty string
15254
15255 my $file = shift ;
15256 my $line ;
15257
15258 $line = nthline( $file, 2 ) ;
15259 return $line ;
15260}
15261
15262
15263
15264
15265sub tests_nthline
15266{
15267 note( 'Entering tests_nthline()' ) ;
15268
15269 is( q{}, nthline( 'W/tmp/tests/noexist.txt' ), 'nthline: getting empty string from inexisting W/tmp/tests/noexist.txt' ) ;
15270 is( q{}, nthline( 'W/tmp/tests/noexist.txt', 2 ), 'nthline: 2nd getting empty string from inexisting W/tmp/tests/noexist.txt' ) ;
15271
15272 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'nthline: mkpath W/tmp/tests/' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015273 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' ) ;
15274 is( 'L3' , nthline( 'W/tmp/tests/nthline.txt', 3 ), 'nthline: get L3 from W/tmp/tests/nthline.txt' ) ;
15275
15276
15277 note( 'Leaving tests_nthline()' ) ;
15278 return ;
15279}
15280
15281
15282sub nthline
15283{
15284 # extract the nth line of a file (without \n)
15285 # return empty string if error or empty string
15286
15287 my $file = shift ;
15288 my $num = shift ;
15289
15290 if ( ! all_defined( $file, $num ) ) { return q{} ; }
15291
15292 my $line ;
15293
15294 $line = ( file_to_array( $file ) )[$num - 1] ;
15295 if ( ! defined $line )
15296 {
15297 return q{} ;
15298 }
15299 else
15300 {
15301 chomp $line ;
15302 return $line ;
15303 }
15304}
15305
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015306sub tests_file_to_array
15307{
15308 note( 'Entering tests_file_to_array()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015309
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015310 is( undef, file_to_array( ), 'file_to_array: no args => undef' ) ;
15311 is( undef, file_to_array( '/noexist' ), 'file_to_array: /noexist => undef' ) ;
15312 is( undef, file_to_array( '/' ), 'file_to_array: reading a directory => undef' ) ;
15313
15314 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'file_to_array: mkpath W/tmp/tests/' ) ;
15315 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' ) ;
15316 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' ) ;
15317
15318 note( 'Leaving tests_file_to_array()' ) ;
15319 return ;
15320}
15321
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015322sub file_to_array
15323{
15324
15325 my( $file ) = shift ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015326 if ( ! $file ) { return ; }
15327 if ( ! -e $file ) { return ; }
15328 if ( ! -f $file ) { return ; }
15329 if ( ! -r $file ) { return ; }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010015330
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015331 my @string ;
15332
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015333 if ( open my $FILE, '<', $file )
15334 {
15335 @string = <$FILE> ;
15336 close $FILE ;
15337 return( @string ) ;
15338 }
15339 else
15340 {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015341 myprint( "Error reading file $file : $OS_ERROR\n" ) ;
15342 return ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015343 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015344}
15345
15346
15347sub tests_file_to_string
15348{
15349 note( 'Entering tests_file_to_string()' ) ;
15350
15351 is( undef, file_to_string( ), 'file_to_string: no args => undef' ) ;
15352 is( undef, file_to_string( '/noexist' ), 'file_to_string: /noexist => undef' ) ;
15353 is( undef, file_to_string( '/' ), 'file_to_string: reading a directory => undef' ) ;
15354 ok( file_to_string( $PROGRAM_NAME ), 'file_to_string: reading myself' ) ;
15355
15356 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'file_to_string: mkpath W/tmp/tests/' ) ;
15357
15358 is( 'lilili', string_to_file( 'lilili', 'W/tmp/tests/canbewritten' ), 'file_to_string: string_to_file filling W/tmp/tests/canbewritten with lilili' ) ;
15359 is( 'lilili', file_to_string( 'W/tmp/tests/canbewritten' ), 'file_to_string: reading W/tmp/tests/canbewritten is lilili' ) ;
15360
15361 is( q{}, string_to_file( q{}, 'W/tmp/tests/empty' ), 'file_to_string: string_to_file filling W/tmp/tests/empty with empty string' ) ;
15362 is( q{}, file_to_string( 'W/tmp/tests/empty' ), 'file_to_string: reading W/tmp/tests/empty is empty' ) ;
15363
15364 note( 'Leaving tests_file_to_string()' ) ;
15365 return ;
15366}
15367
15368sub file_to_string
15369{
15370 my $file = shift ;
15371 if ( ! $file ) { return ; }
15372 if ( ! -e $file ) { return ; }
15373 if ( ! -f $file ) { return ; }
15374 if ( ! -r $file ) { return ; }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010015375
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015376 return( join q{}, file_to_array( $file ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015377}
15378
15379
15380sub tests_string_to_file
15381{
15382 note( 'Entering tests_string_to_file()' ) ;
15383
15384 is( undef, string_to_file( ), 'string_to_file: no args => undef' ) ;
15385 is( undef, string_to_file( 'lalala' ), 'string_to_file: one arg => undef' ) ;
15386 is( undef, string_to_file( 'lalala', '.' ), 'string_to_file: writing a directory => undef' ) ;
15387 ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'string_to_file: mkpath W/tmp/tests/' ) ;
15388 is( 'lalala', string_to_file( 'lalala', 'W/tmp/tests/canbewritten' ), 'string_to_file: W/tmp/tests/canbewritten with lalala' ) ;
15389 is( q{}, string_to_file( q{}, 'W/tmp/tests/empty' ), 'string_to_file: W/tmp/tests/empty with empty string' ) ;
15390
15391 SKIP: {
15392 Readonly my $NB_UNX_tests_string_to_file => 1 ;
15393 skip( 'Not on Unix non-root', $NB_UNX_tests_string_to_file ) if ('MSWin32' eq $OSNAME or '0' eq $EFFECTIVE_USER_ID ) ;
15394 is( undef, string_to_file( 'lalala', '/cantouch' ), 'string_to_file: /cantouch denied => undef' ) ;
15395 }
15396
15397 note( 'Leaving tests_string_to_file()' ) ;
15398 return ;
15399}
15400
15401sub string_to_file
15402{
15403 my( $string, $file ) = @_ ;
15404 if( ! defined $string ) { return ; }
15405 if( ! defined $file ) { return ; }
15406
15407 if ( ! -e $file && ! -w dirname( $file ) ) {
15408 myprint( "string_to_file: directory of $file is not writable\n" ) ;
15409 return ;
15410 }
15411
15412 if ( ! sysopen( FILE, $file, O_WRONLY|O_TRUNC|O_CREAT, 0600) ) {
15413 myprint( "string_to_file: failure writing to $file with error: $OS_ERROR\n" ) ;
15414 return ;
15415 }
15416 print FILE $string ;
15417 close FILE ;
15418 return $string ;
15419}
15420
154210 and <<'MULTILINE_COMMENT' ;
15422This is a multiline comment.
15423Based on David Carter discussion, to do:
15424* Call parameters stay the same.
15425* Now always "return( $string, $error )". Descriptions below.
15426OK * Still capture STDOUT via "1> $output_tmpfile" to finish in $string and "return( $string, $error )"
15427OK * Now also capture STDERR via "2> $error_tmpfile" to finish in $error and "return( $string, $error )"
15428OK * in case of CHILD_ERROR, return( undef, $error )
15429 and print $error, with folder/UID/maybeSubject context,
15430 on console and at the end with the final error listing. Count this as a sync error.
15431* in case of good command, take final $string as is, unless void. In case $error with value then print it.
15432* in case of good command and final $string empty, consider it like CHILD_ERROR =>
15433 return( undef, $error ) and print $error, with folder/UID/maybeSubject context,
15434 on console and at the end with the final error listing. Count this as a sync error.
15435MULTILINE_COMMENT
15436# End of multiline comment.
15437
15438sub pipemess
15439{
15440 my ( $string, @commands ) = @_ ;
15441 my $error = q{} ;
15442 foreach my $command ( @commands ) {
15443 my $input_tmpfile = "$sync->{ tmpdir }/imapsync_tmp_file.$PROCESS_ID.inp.txt" ;
15444 my $output_tmpfile = "$sync->{ tmpdir }/imapsync_tmp_file.$PROCESS_ID.out.txt" ;
15445 my $error_tmpfile = "$sync->{ tmpdir }/imapsync_tmp_file.$PROCESS_ID.err.txt" ;
15446 string_to_file( $string, $input_tmpfile ) ;
15447 ` $command < $input_tmpfile 1> $output_tmpfile 2> $error_tmpfile ` ;
15448 my $is_command_ko = $CHILD_ERROR ;
15449 my $error_cmd = file_to_string( $error_tmpfile ) ;
15450 chomp( $error_cmd ) ;
15451 $string = file_to_string( $output_tmpfile ) ;
15452 my $string_len = length( $string ) ;
15453 unlink $input_tmpfile, $output_tmpfile, $error_tmpfile ;
15454
15455 if ( $is_command_ko or ( ! $string_len ) ) {
15456 my $cmd_exit_value = $CHILD_ERROR >> 8 ;
15457 my $cmd_end_signal = $CHILD_ERROR & 127 ;
15458 my $signal_log = ( $cmd_end_signal ) ? " signal $cmd_end_signal and" : q{} ;
15459 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} ;
15460 myprint( $error_log ) ;
15461 if ( wantarray ) {
15462 return @{ [ undef, $error_log ] }
15463 }else{
15464 return ;
15465 }
15466 }
15467 if ( $error_cmd ) {
15468 $error .= qq{STDERR of --pipemess "$command": $error_cmd\n} ;
15469 myprint( qq{STDERR of --pipemess "$command": $error_cmd\n} ) ;
15470 }
15471 }
15472 #myprint( "[$string]\n" ) ;
15473 if ( wantarray ) {
15474 return ( $string, $error ) ;
15475 }else{
15476 return $string ;
15477 }
15478}
15479
15480
15481
15482sub tests_pipemess
15483{
15484 note( 'Entering tests_pipemess()' ) ;
15485
15486
15487 SKIP: {
15488 Readonly my $NB_WIN_tests_pipemess => 3 ;
15489 skip( 'Not on MSWin32', $NB_WIN_tests_pipemess ) if ('MSWin32' ne $OSNAME) ;
15490 # Windows
15491 # "type" command does not accept redirection of STDIN with <
15492 # "sort" does
15493 ok( "nochange\n" eq pipemess( 'nochange', 'sort' ), 'pipemess: nearly no change by sort' ) ;
15494 ok( "nochange2\n" eq pipemess( 'nochange2', qw( sort sort ) ), 'pipemess: nearly no change by sort,sort' ) ;
15495 # command not found
15496 #diag( 'Warning and failure about cacaprout are on purpose' ) ;
15497 ok( ! defined( pipemess( q{}, 'cacaprout' ) ), 'pipemess: command not found' ) ;
15498
15499 } ;
15500
15501 my ( $stringT, $errorT ) ;
15502
15503 SKIP: {
15504 Readonly my $NB_UNX_tests_pipemess => 25 ;
15505 skip( 'Not on Unix', $NB_UNX_tests_pipemess ) if ('MSWin32' eq $OSNAME) ;
15506 # Unix
15507 ok( 'nochange' eq pipemess( 'nochange', 'cat' ), 'pipemess: no change by cat' ) ;
15508
15509 ok( 'nochange2' eq pipemess( 'nochange2', 'cat', 'cat' ), 'pipemess: no change by cat,cat' ) ;
15510
15511 ok( " 1\tnumberize\n" eq pipemess( "numberize\n", 'cat -n' ), 'pipemess: numberize by cat -n' ) ;
15512 ok( " 1\tnumberize\n 2\tnumberize\n" eq pipemess( "numberize\nnumberize\n", 'cat -n' ), 'pipemess: numberize by cat -n' ) ;
15513
15514 ok( "A\nB\nC\n" eq pipemess( "A\nC\nB\n", 'sort' ), 'pipemess: sort' ) ;
15515
15516 # command not found
15517 #diag( 'Warning and failure about cacaprout are on purpose' ) ;
15518 is( undef, pipemess( q{}, 'cacaprout' ), 'pipemess: command not found' ) ;
15519
15520 # success with true but no output at all
15521 is( undef, pipemess( q{blabla}, 'true' ), 'pipemess: true but no output' ) ;
15522
15523 # failure with false and no output at all
15524 is( undef, pipemess( q{blabla}, 'false' ), 'pipemess: false and no output' ) ;
15525
15526 # Failure since pipemess is not a real pipe, so first cat wait for standard input
15527 is( q{blabla}, pipemess( q{blabla}, '( cat|cat ) ' ), 'pipemess: ok by ( cat|cat )' ) ;
15528
15529
15530 ( $stringT, $errorT ) = pipemess( 'nochange', 'cat' ) ;
15531 is( $stringT, 'nochange', 'pipemess: list context, no change by cat, string' ) ;
15532 is( $errorT, q{}, 'pipemess: list context, no change by cat, no error' ) ;
15533
15534 ( $stringT, $errorT ) = pipemess( 'dontcare', 'true' ) ;
15535 is( $stringT, undef, 'pipemess: list context, true but no output, string' ) ;
15536 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' ) ;
15537
15538 ( $stringT, $errorT ) = pipemess( 'dontcare', 'false' ) ;
15539 is( $stringT, undef, 'pipemess: list context, false and no output, string' ) ;
15540 like( $errorT, qr{\QFailure: --pipemess command "false" ended with "0" characters exit value "1" and STDERR ""\E}xm,
15541 'pipemess: list context, false and no output, error' ) ;
15542
15543 ( $stringT, $errorT ) = pipemess( 'dontcare', '/bin/echo -n blablabla' ) ;
15544 is( $stringT, q{blablabla}, 'pipemess: list context, "echo -n blablabla", string' ) ;
15545 is( $errorT, q{}, 'pipemess: list context, "echo blablabla", error' ) ;
15546
15547
15548 ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo -n blablabla 3>&1 1>&2 2>&3 )' ) ;
15549 is( $stringT, undef, 'pipemess: list context, "no output STDERR blablabla", string' ) ;
15550 like( $errorT, qr{blablabla"}xm, 'pipemess: list context, "no output STDERR blablabla", error' ) ;
15551
15552
15553 ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo -n blablabla 3>&1 1>&2 2>&3 )', 'false' ) ;
15554 is( $stringT, undef, 'pipemess: list context, "no output STDERR blablabla then false", string' ) ;
15555 like( $errorT, qr{blablabla"}xm, 'pipemess: list context, "no output STDERR blablabla then false", error' ) ;
15556
15557 ( $stringT, $errorT ) = pipemess( 'dontcare', 'false', '( echo -n blablabla 3>&1 1>&2 2>&3 )' ) ;
15558 is( $stringT, undef, 'pipemess: list context, "false then STDERR blablabla", string' ) ;
15559 like( $errorT, qr{\QFailure: --pipemess command "false" ended with "0" characters exit value "1" and STDERR ""\E}xm,
15560 'pipemess: list context, "false then STDERR blablabla", error' ) ;
15561
15562 ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo rrrrr ; echo -n error_blablabla 3>&1 1>&2 2>&3 )' ) ;
15563 like( $stringT, qr{rrrrr}xm, 'pipemess: list context, "STDOUT rrrrr STDERR error_blablabla", string' ) ;
15564 like( $errorT, qr{STDERR.*error_blablabla}xm, 'pipemess: list context, "STDOUT rrrrr STDERR error_blablabla", error' ) ;
15565
15566 }
15567
15568 ( $stringT, $errorT ) = pipemess( 'dontcare', 'cacaprout' ) ;
15569 is( $stringT, undef, 'pipemess: list context, cacaprout not found, string' ) ;
15570 like( $errorT, qr{\QFailure: --pipemess command "cacaprout" ended with "0" characters exit value\E}xm,
15571 'pipemess: list context, cacaprout not found, error' ) ;
15572
15573 note( 'Leaving tests_pipemess()' ) ;
15574 return ;
15575}
15576
15577
15578
15579sub tests_is_a_release_number
15580{
15581 note( 'Entering tests_is_a_release_number()' ) ;
15582
15583 is( undef, is_a_release_number( ), 'is_a_release_number: no args => undef' ) ;
15584 ok( is_a_release_number( $RELEASE_NUMBER_EXAMPLE_1 ), 'is_a_release_number 1.351' ) ;
15585 ok( is_a_release_number( $RELEASE_NUMBER_EXAMPLE_2 ), 'is_a_release_number 42.4242' ) ;
15586 ok( is_a_release_number( imapsync_version( $sync ) ), 'is_a_release_number imapsync_version( )' ) ;
15587 ok( ! is_a_release_number( 'blabla' ), '! is_a_release_number blabla' ) ;
15588
15589 note( 'Leaving tests_is_a_release_number()' ) ;
15590 return ;
15591}
15592
15593sub is_a_release_number
15594{
15595 my $number = shift ;
15596 if ( ! defined $number ) { return ; }
15597 return( $number =~ m{^\d+\.\d+$}xo ) ;
15598}
15599
15600
15601
15602sub imapsync_version_public
15603{
15604
15605 my $local_version = imapsync_version( $sync ) ;
15606 my $imapsync_basename = imapsync_basename( ) ;
15607 my $context = imapsync_context( ) ;
15608 my $agent_info = "$OSNAME system, perl "
15609 . mysprintf( '%vd', $PERL_VERSION)
15610 . ", Mail::IMAPClient $Mail::IMAPClient::VERSION"
15611 . " $imapsync_basename"
15612 . " $context" ;
15613 my $sock = IO::Socket::INET->new(
15614 PeerAddr => 'imapsync.lamiral.info',
15615 PeerPort => 80,
15616 Proto => 'tcp',
15617 ) ;
15618 return( 'unknown' ) if not $sock ;
15619 print $sock
15620 "GET /prj/imapsync/VERSION HTTP/1.0\r\n",
15621 "User-Agent: imapsync/$local_version ($agent_info)\r\n",
15622 "Host: ks.lamiral.info\r\n\r\n" ;
15623 my @line = <$sock> ;
15624 close $sock ;
15625 my $last_release = $line[$LAST] ;
15626 chomp $last_release ;
15627 return( $last_release ) ;
15628}
15629
15630sub not_long_imapsync_version_public
15631{
15632 #myprint( "Entering not_long_imapsync_version_public\n" ) ;
15633
15634 my $fake = shift ;
15635 if ( $fake ) { return $fake }
15636
15637 my $val ;
15638
15639 # Doesn't work with gethostbyname (see perlipc)
15640 #local $SIG{ALRM} = sub { die "alarm\n" } ;
15641
15642 if ('MSWin32' eq $OSNAME) {
15643 local $SIG{ALRM} = sub { die "alarm\n" } ;
15644 }else{
15645
15646 POSIX::sigaction(SIGALRM,
15647 POSIX::SigAction->new(sub { croak 'alarm' } ) )
15648 or myprint( "Error setting SIGALRM handler: $OS_ERROR\n" ) ;
15649 }
15650
15651 my $ret = eval {
15652 alarm 3 ;
15653 {
15654 $val = imapsync_version_public( ) ;
15655 #sleep 4 ;
15656 #myprint( "End of imapsync_version_public\n" ) ;
15657 }
15658 alarm 0 ;
15659 1 ;
15660 } ;
15661 #myprint( "eval [$ret]\n" ) ;
15662 if ( ( not $ret ) or $EVAL_ERROR ) {
15663 #myprint( "$EVAL_ERROR" ) ;
15664 if ($EVAL_ERROR =~ /alarm/) {
15665 # timed out
15666 return('timeout') ;
15667 }else{
15668 alarm 0 ;
15669 return( 'unknown' ) ; # propagate unexpected errors
15670 }
15671 }else {
15672 # Good!
15673 return( $val ) ;
15674 }
15675}
15676
15677sub tests_not_long_imapsync_version_public
15678{
15679 note( 'Entering tests_not_long_imapsync_version_public()' ) ;
15680
15681
15682 is( 1, is_a_release_number( not_long_imapsync_version_public( ) ),
15683 'not_long_imapsync_version_public: public release is a number' ) ;
15684
15685 note( 'Leaving tests_not_long_imapsync_version_public()' ) ;
15686 return ;
15687}
15688
15689sub check_last_release
15690{
15691 my $fake = shift ;
15692 my $public_release = not_long_imapsync_version_public( $fake ) ;
15693 $sync->{ debug } and myprint( "check_last_release: [$public_release]\n" ) ;
15694 my $inline_help_when_on = '( Use --noreleasecheck to avoid this release check. )' ;
15695
15696 if ( $public_release eq 'unknown' ) {
15697 return( 'Imapsync public release is unknown.' . $inline_help_when_on ) ;
15698 }
15699
15700 if ( $public_release eq 'timeout' ) {
15701 return( 'Imapsync public release is unknown (timeout).' . $inline_help_when_on ) ;
15702 }
15703
15704 if ( ! is_a_release_number( $public_release ) ) {
15705 return( "Imapsync public release is unknown ($public_release)." . $inline_help_when_on ) ;
15706 }
15707
15708 my $imapsync_here = imapsync_version( $sync ) ;
15709
15710 if ( $public_release > $imapsync_here ) {
15711 return( 'This imapsync is not up to date. ' . "( local $imapsync_here < official $public_release )" . $inline_help_when_on ) ;
15712 }else{
15713 return( 'This imapsync is up to date. ' . "( local $imapsync_here >= official $public_release )" . $inline_help_when_on ) ;
15714 }
15715
15716 return( 'really unknown' ) ; # Should never arrive here
15717}
15718
15719sub tests_check_last_release
15720{
15721 note( 'Entering tests_check_last_release()' ) ;
15722
15723 diag( check_last_release( 1.1 ) ) ;
15724 # \Q \E here to avoid putting \ before each space
15725 like( check_last_release( 1.1 ), qr/\Qis up to date\E/mxs, 'check_last_release: up to date' ) ;
15726 like( check_last_release( 1.1 ), qr/1\.1/mxs, 'check_last_release: up to date, include number' ) ;
15727 diag( check_last_release( 999.999 ) ) ;
15728 like( check_last_release( 999.999 ), qr/\Qnot up to date\E/mxs, 'check_last_release: not up to date' ) ;
15729 like( check_last_release( 999.999 ), qr/999\.999/mxs, 'check_last_release: not up to date, include number' ) ;
15730 like( check_last_release( 'unknown' ), qr/\QImapsync public release is unknown\E/mxs, 'check_last_release: unknown' ) ;
15731 like( check_last_release( 'timeout' ), qr/\QImapsync public release is unknown (timeout)\E/mxs, 'check_last_release: timeout' ) ;
15732 like( check_last_release( 'lalala' ), qr/\QImapsync public release is unknown (lalala)\E/mxs, 'check_last_release: lalala' ) ;
15733 diag( check_last_release( ) ) ;
15734
15735 note( 'Leaving tests_check_last_release()' ) ;
15736 return ;
15737}
15738
15739sub tests_imapsync_context
15740{
15741 note( 'Entering tests_imapsync_context()' ) ;
15742
15743 like( imapsync_context( ), qr/^CGI|^Docker|^DockerCGI|^Standard/, 'imapsync_context: CGI or Docker or DockerCGI or Standard' ) ;
15744 note( 'Leaving tests_imapsync_context()' ) ;
15745 return ;
15746}
15747
15748sub imapsync_context
15749{
15750 my $mysync = shift ;
15751
15752 my $context = q{} ;
15753
15754 if ( under_docker_context( $mysync ) && under_cgi_context( $mysync ) )
15755 {
15756 $context = 'DockerCGI' ;
15757 }
15758 elsif ( under_docker_context( $mysync ) )
15759 {
15760 $context = 'Docker' ;
15761 }
15762 elsif ( under_cgi_context( $mysync ) )
15763 {
15764 $context = 'CGI' ;
15765 }
15766 else
15767 {
15768 $context = 'Standard' ;
15769 }
15770
15771 return $context ;
15772
15773}
15774
15775sub imapsync_version
15776{
15777 my $mysync = shift ;
15778 my $rcs = $mysync->{rcs} ;
15779 my $version ;
15780
15781 $version = version_from_rcs( $rcs ) ;
15782 return( $version ) ;
15783}
15784
15785
15786sub tests_version_from_rcs
15787{
15788 note( 'Entering tests_version_from_rcs()' ) ;
15789
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015790 is( undef, version_from_rcs( ), 'version_from_rcs: no args => undef' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015791 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' ) ;
15792 is( 'UNKNOWN', version_from_rcs( 1.831 ), 'version_from_rcs: 1.831 => UNKNOWN' ) ;
15793
15794 note( 'Leaving tests_version_from_rcs()' ) ;
15795 return ;
15796}
15797
15798
15799sub version_from_rcs
15800{
15801
15802 my $rcs = shift ;
15803 if ( ! $rcs ) { return ; }
15804
15805 my $version = 'UNKNOWN' ;
15806
15807 if ( $rcs =~ m{,v\s+(\d+\.\d+)}mxso ) {
15808 $version = $1
15809 }
15810
15811 return( $version ) ;
15812}
15813
15814
15815sub tests_imapsync_basename
15816{
15817 note( 'Entering tests_imapsync_basename()' ) ;
15818
15819 ok( imapsync_basename() =~ m/imapsync/, 'imapsync_basename: match imapsync');
15820 ok( 'blabla' ne imapsync_basename(), 'imapsync_basename: do not equal blabla');
15821
15822 note( 'Leaving tests_imapsync_basename()' ) ;
15823 return ;
15824}
15825
15826sub imapsync_basename
15827{
15828
15829 return basename( $PROGRAM_NAME ) ;
15830
15831}
15832
15833
15834sub localhost_info
15835{
15836 my $mysync = shift ;
15837 my( $infos ) = join( q{},
15838 "Here is imapsync ", imapsync_version( $mysync ),
15839 " on host " . hostname(),
15840 ", a $OSNAME system with ",
15841 ram_memory_info( ),
15842 "\n",
15843 'with Perl ',
15844 mysprintf( '%vd ', $PERL_VERSION),
15845 "and Mail::IMAPClient $Mail::IMAPClient::VERSION",
15846 ) ;
15847 return( $infos ) ;
15848}
15849
15850sub tests_cpu_number
15851{
15852 note( 'Entering tests_cpu_number()' ) ;
15853
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015854 is( 1, is_integer( cpu_number( ) ), "cpu_number: is_integer" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015855 ok( 1 <= cpu_number( ), "cpu_number: 1 or more" ) ;
15856 is( 1, cpu_number( 1 ), "cpu_number: 1 => 1" ) ;
15857 is( 1, cpu_number( $MINUS_ONE ), "cpu_number: -1 => 1" ) ;
15858 is( 1, cpu_number( 'lalala' ), "cpu_number: lalala => 1" ) ;
15859 is( $NUMBER_42, cpu_number( $NUMBER_42 ), "cpu_number: $NUMBER_42 => $NUMBER_42" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015860
15861 note( "cpu_number = " . cpu_number( ) . "\n" ) ;
15862 note( "hostname = " . hostname( ) . "\n" ) ;
15863 SKIP: {
15864 if ( ! ( 'i005' eq hostname() ) )
15865 {
15866 skip( 'cpu_number on host != i005 (FreeBSD)', 1 ) ;
15867 }
15868 is( 4, cpu_number( ), "cpu_number: on i005 (FreeBSD) => 4" ) ;
15869 } ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010015870
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015871 SKIP: {
15872 if ( ! ( 'petite' eq hostname() ) )
15873 {
15874 skip( 'cpu_number on host != petite (Linux)', 1 ) ;
15875 }
15876 is( 2, cpu_number( ), "cpu_number: on petite (Linux) => 2" ) ;
15877 } ;
15878
15879 SKIP: {
15880 if ( ! ( skip_macosx( ) ) )
15881 {
15882 skip( 'cpu_number on host != polarhome macosx (Darwin MacOS X 10.7.5 Lion)', 1 ) ;
15883 }
15884 is( 2, cpu_number( ), "cpu_number: on polarhome macosx (Darwin MacOS X 10.7.5 Lion) => 2" ) ;
15885 } ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010015886
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015887 SKIP: {
15888 if ( ! ( 'pcHPDV7-HP' eq hostname() ) )
15889 {
15890 skip( 'cpu_number on host != pcHPDV7-HP (Windows 7, 64bits)', 1 ) ;
15891 }
15892 is( 2, cpu_number( ), "cpu_number: on pcHPDV7-HP (Windows 7, 64bits) => 2" ) ;
15893 } ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010015894
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015895 SKIP: {
15896 if ( ! ( 'CUILLERE' eq hostname() ) )
15897 {
15898 skip( 'cpu_number on host != CUILLERE (Windows XP, 32bits)', 1 ) ;
15899 }
15900 is( 1, cpu_number( ), "cpu_number: on CUILLERE (Windows XP, 32bits) => 1" ) ;
15901 } ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010015902
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015903
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015904 note( 'Leaving tests_cpu_number()' ) ;
15905 return ;
15906}
15907
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015908
15909sub cpu_number {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015910
15911 my $cpu_number_forced = shift ;
15912 # Well, here 1 is better than 0 or undef
15913 my $cpu_number = 1 ; # Default value, erased if better found
15914
15915 my @cpuinfo ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015916 if ( $ENV{"NUMBER_OF_PROCESSORS"} )
15917 {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015918 # might be under a Windows system
15919 $cpu_number = $ENV{"NUMBER_OF_PROCESSORS"} ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015920 #myprint( "Number of processors found by env var NUMBER_OF_PROCESSORS: $cpu_number\n" ) ;
15921 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010015922
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015923 if ( 'darwin' eq $OSNAME )
15924 {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015925 $cpu_number = backtick( "sysctl -n hw.ncpu" ) ;
15926 chomp( $cpu_number ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015927 #myprint( "Number of processors found by cmd 'sysctl -n hw.ncpu': $cpu_number\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015928 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010015929
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015930 if ( 'freebsd' eq $OSNAME )
15931 {
15932 $cpu_number = backtick( "sysctl -n kern.smp.cpus" ) ;
15933 chomp( $cpu_number ) ;
15934 #myprint( "Number of processors found by cmd 'sysctl -n kern.smp.cpus': $cpu_number\n" ) ;
15935 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010015936
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015937 if ( 'linux' eq $OSNAME && -e '/proc/cpuinfo' )
15938 {
15939 @cpuinfo = file_to_array( '/proc/cpuinfo' ) ;
15940 $cpu_number = grep { /^processor/mxs } @cpuinfo ;
15941 #myprint( "Number of processors found via /proc/cpuinfo: $cpu_number\n" ) ;
15942 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010015943
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015944 if ( defined $cpu_number_forced )
15945 {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015946 $cpu_number = $cpu_number_forced ;
15947 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010015948
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015949 return( integer_or_1( $cpu_number ) ) ;
15950}
15951
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015952sub tests_integer_or_1
15953{
15954 note( 'Entering tests_integer_or_1()' ) ;
15955
15956 is( 1, integer_or_1( ), 'integer_or_1: no args => 1' ) ;
15957 is( 1, integer_or_1( undef ), 'integer_or_1: undef => 1' ) ;
15958 is( $NUMBER_10, integer_or_1( $NUMBER_10 ), 'integer_or_1: 10 => 10' ) ;
15959 is( 1, integer_or_1( q{} ), 'integer_or_1: empty string => 1' ) ;
15960 is( 1, integer_or_1( 'lalala' ), 'integer_or_1: lalala => 1' ) ;
15961
15962 note( 'Leaving tests_integer_or_1()' ) ;
15963 return ;
15964}
15965
15966sub integer_or_1
15967{
15968 my $number = shift ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015969 if ( is_integer( $number ) ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015970 return $number ;
15971 }
15972 # else
15973 return 1 ;
15974}
15975
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015976sub tests_is_integer
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015977{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015978 note( 'Entering tests_is_integer()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015979
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015980 is( undef, is_integer( ), 'is_integer: no args => undef ' ) ;
15981 ok( is_integer( 1 ), 'is_integer: 1 => yes ') ;
15982 ok( is_integer( $NUMBER_42 ), 'is_integer: 42 => yes ') ;
15983 ok( is_integer( "$NUMBER_42" ), 'is_integer: "$NUMBER_42" => yes ') ;
15984 ok( is_integer( '42' ), 'is_integer: "42" => yes ') ;
15985 ok( is_integer( $NUMBER_104_857_600 ), 'is_integer: 104_857_600 => yes') ;
15986 ok( is_integer( "$NUMBER_104_857_600" ), 'is_integer: "$NUMBER_104_857_600" => yes') ;
15987 ok( is_integer( '104857600' ), 'is_integer: 104857600 => yes') ;
15988 ok( ! is_integer( 'blabla' ), 'is_integer: blabla => no' ) ;
15989 ok( ! is_integer( q{} ), 'is_integer: empty string => no' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015990
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015991 note( 'Leaving tests_is_integer()' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015992 return ;
15993}
15994
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015995sub is_integer
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010015996{
15997 my $number = shift ;
15998 if ( ! defined $number ) { return ; }
15999 return( $number =~ m{^\d+$}xo ) ;
16000}
16001
16002
16003
16004
16005sub tests_loadavg
16006{
16007 note( 'Entering tests_loadavg()' ) ;
16008
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016009 SKIP: {
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016010 skip( 'Tests for darwin', 3 ) if ('darwin' ne $OSNAME) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016011 is( undef, loadavg( '/noexist' ), 'loadavg: /noexist => undef' ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016012 is_deeply(
16013 [ '0.11', '0.22', '0.33' ],
16014 [ loadavg( 'vm.loadavg: { 0.11 0.22 0.33 }' ) ],
16015 'loadavg: "vm.loadavg: { 0.11 0.22 0.33 }" => 0.11 0.22 0.33'
16016 ) ;
16017 note( join( " ", "loadavg:", loadavg( ) ) ) ;
16018 is( 3, scalar( my @loadavg = loadavg( ) ), 'loadavg: 3 values' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016019 } ;
16020
16021 SKIP: {
16022 skip( 'Tests for linux', 3 ) if ('linux' ne $OSNAME) ;
16023 is( undef, loadavg( '/noexist' ), 'loadavg: /noexist => undef' ) ;
16024 ok( loadavg( ), 'loadavg: no args' ) ;
16025
16026 is_deeply( [ '0.39', '0.30', '0.37', '1/602' ],
16027 [ loadavg( '0.39 0.30 0.37 1/602 6073' ) ],
16028 'loadavg 0.39 0.30 0.37 1/602 6073 => [0.39, 0.30, 0.37, 1/602]' ) ;
16029 } ;
16030
16031 SKIP: {
16032 skip( 'Tests for Windows', 1 ) if ('MSWin32' ne $OSNAME) ;
16033 is_deeply( [ 0 ],
16034 [ loadavg( ) ],
16035 'loadavg on MSWin32 => 0' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016036 } ;
16037
16038 note( 'Leaving tests_loadavg()' ) ;
16039 return ;
16040}
16041
16042
16043sub loadavg
16044{
16045 if ( 'linux' eq $OSNAME ) {
16046 return ( loadavg_linux( @ARG ) ) ;
16047 }
16048 if ( 'freebsd' eq $OSNAME ) {
16049 return ( loadavg_freebsd( @ARG ) ) ;
16050 }
16051 if ( 'darwin' eq $OSNAME ) {
16052 return ( loadavg_darwin( @ARG ) ) ;
16053 }
16054 if ( 'MSWin32' eq $OSNAME ) {
16055 return ( loadavg_windows( @ARG ) ) ;
16056 }
16057 return( 'unknown' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016058}
16059
16060sub loadavg_linux
16061{
16062 my $line = shift ;
16063
16064 if ( ! $line ) {
16065 $line = firstline( '/proc/loadavg' ) or return ;
16066 }
16067
16068 my ( $avg_1_min, $avg_5_min, $avg_15_min, $current_runs ) = split /\s/mxs, $line ;
16069 if ( all_defined( $avg_1_min, $avg_5_min, $avg_15_min ) ) {
16070 $sync->{ debug } and myprint( "System load: $avg_1_min $avg_5_min $avg_15_min $current_runs\n" ) ;
16071 return ( $avg_1_min, $avg_5_min, $avg_15_min, $current_runs ) ;
16072 }
16073 return ;
16074}
16075
16076sub loadavg_freebsd
16077{
16078 my $file = shift ;
16079 # Example of output of command "sysctl vm.loadavg":
16080 # vm.loadavg: { 0.15 0.08 0.08 }
16081 my $loadavg ;
16082
16083 if ( ! defined $file ) {
16084 eval {
16085 $loadavg = `/sbin/sysctl vm.loadavg` ;
16086 #myprint( "LOADAVG FREEBSD: $loadavg\n" ) ;
16087 } ;
16088 if ( $EVAL_ERROR ) { myprint( "[$EVAL_ERROR]\n" ) ; return ; }
16089 }else{
16090 $loadavg = firstline( $file ) or return ;
16091 }
16092
16093 my ( $avg_1_min, $avg_5_min, $avg_15_min )
16094 = $loadavg =~ /vm\.loadavg\s*[:=]\s*\{?\s*(\d+\.?\d*)\s+(\d+\.?\d*)\s+(\d+\.?\d*)/mxs ;
16095 $sync->{ debug } and myprint( "System load: $avg_1_min $avg_5_min $avg_15_min\n" ) ;
16096 return ( $avg_1_min, $avg_5_min, $avg_15_min ) ;
16097}
16098
16099sub loadavg_darwin
16100{
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016101 my $line = shift ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016102 # Example of output of command "sysctl vm.loadavg":
16103 # vm.loadavg: { 0.15 0.08 0.08 }
16104 my $loadavg ;
16105
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016106 if ( ! defined $line ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016107 eval {
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016108 # $loadavg = `/usr/sbin/sysctl vm.loadavg` ;
16109 $loadavg = `LANG= /usr/sbin/sysctl vm.loadavg` ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016110 #myprint( "LOADAVG DARWIN: $loadavg\n" ) ;
16111 } ;
16112 if ( $EVAL_ERROR ) { myprint( "[$EVAL_ERROR]\n" ) ; return ; }
16113 }else{
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016114 $loadavg = $line ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016115 }
16116
16117 my ( $avg_1_min, $avg_5_min, $avg_15_min )
16118 = $loadavg =~ /vm\.loadavg\s*[:=]\s*\{?\s*(\d+\.?\d*)\s+(\d+\.?\d*)\s+(\d+\.?\d*)/mxs ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016119 #$sync->{ debug } and myprint( "System load: $avg_1_min $avg_5_min $avg_15_min\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016120 return ( $avg_1_min, $avg_5_min, $avg_15_min ) ;
16121}
16122
16123sub loadavg_windows
16124{
16125 my $file = shift ;
16126 # Example of output of command "wmic cpu get loadpercentage":
16127 # LoadPercentage
16128 # 12
16129 my $loadavg ;
16130
16131 if ( ! defined $file ) {
16132 eval {
16133 #$loadavg = `CMD wmic cpu get loadpercentage` ;
16134 $loadavg = "LoadPercentage\n0\n" ;
16135 #myprint( "LOADAVG WIN: $loadavg\n" ) ;
16136 } ;
16137 if ( $EVAL_ERROR ) { myprint( "[$EVAL_ERROR]\n" ) ; return ; }
16138 }else{
16139 $loadavg = file_to_string( $file ) or return ;
16140 #myprint( "$loadavg" ) ;
16141 }
16142 $loadavg =~ /LoadPercentage\n(\d+)/xms ;
16143 my $num = $1 ;
16144 $num /= 100 ;
16145
16146 $sync->{ debug } and myprint( "System load: $num\n" ) ;
16147 return ( $num ) ;
16148}
16149
16150
16151
16152
16153
16154
16155sub tests_load_and_delay
16156{
16157 note( 'Entering tests_load_and_delay()' ) ;
16158
16159 is( undef, load_and_delay( ), 'load_and_delay: no args => undef ' ) ;
16160 is( undef, load_and_delay( 1 ), 'load_and_delay: not 4 args => undef ' ) ;
16161 is( undef, load_and_delay( 0, 1, 1, 1 ), 'load_and_delay: division per 0 => undef ' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016162
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016163# ( $cpu_num, $avg_1_min, $avg_5_min, $avg_15_min )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016164
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016165 is( 0, load_and_delay( 1, 1, 1, 1 ), 'load_and_delay: one core, loads are all 1 => ok ' ) ;
16166 is( 0, load_and_delay( 1, 1, 1, 1, 'lalala' ), 'load_and_delay: five arguments is ok' ) ;
16167 is( 0, load_and_delay( 2, 2, 2, 2 ), 'load_and_delay: two core, loads are all 2 => ok ' ) ;
16168 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 +010016169
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016170
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016171 is( 0, load_and_delay( 1, 0, 0, 0 ), 'load_and_delay: one core, load1m=0 load5m=0 load15m=0 => 0 ' ) ;
16172 is( 0, load_and_delay( 1, 0, 0, 2 ), 'load_and_delay: one core, load1m=0 load5m=0 load15m=2 => 0 ' ) ;
16173 is( 0, load_and_delay( 1, 0, 2, 0 ), 'load_and_delay: one core, load1m=0 load5m=2 load15m=0 => 0 ' ) ;
16174 is( 0, load_and_delay( 1, 0, 2, 2 ), 'load_and_delay: one core, load1m=0 load5m=2 load15m=2 => 0 ' ) ;
16175 is( 0, load_and_delay( 1, 0, 3, 3 ), 'load_and_delay: one core, load1m=0 load5m=3 load15m=3 => 0 ' ) ;
16176 is( 0, load_and_delay( 1, 0, 4, 4 ), 'load_and_delay: one core, load1m=0 load5m=3 load15m=3 => 0 ' ) ;
16177 is( 0, load_and_delay( 1, 2, 0, 0 ), 'load_and_delay: one core, load1m=2 load5m=0 load15m=0 => 0 ' ) ;
16178 is( 0, load_and_delay( 1, 2, 0, 2 ), 'load_and_delay: one core, load1m=2 load5m=0 load15m=2 => 0 ' ) ;
16179 is( 0, load_and_delay( 1, 2, 2, 0 ), 'load_and_delay: one core, load1m=2 load5m=2 load15m=0 => 0 ' ) ;
16180 is( 0, load_and_delay( 1, 2, 2, 2 ), 'load_and_delay: one core, load1m=2 load5m=2 load15m=2 => 0 ' ) ;
16181 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 +010016182
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016183 is( 0, load_and_delay( 1, 3, 0, 0 ), 'load_and_delay: one core, load1m=3 load5m=0 load15m=0 => 0 ' ) ;
16184 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 ' ) ;
16185 is( 0, load_and_delay( 1, 3, 3, 2.9 ), 'load_and_delay: one core, load1m=3 load5m=3 load15m=2.9 => 0 ' ) ;
16186 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 +010016187
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016188 is( 1, load_and_delay( 1, 6, 0, 0 ), 'load_and_delay: one core, load1m=3 load5m=0 load15m=0 => 1 ' ) ;
16189 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 ' ) ;
16190 is( 5, load_and_delay( 1, 6, 6, 5.9 ), 'load_and_delay: one core, load1m=3 load5m=3 load15m=2.9 => 5 ' ) ;
16191 is( 15, load_and_delay( 1, 6, 6, 6 ), 'load_and_delay: one core, load1m=3 load5m=3 load15m=3 => 15 ' ) ;
16192
16193
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016194
16195 note( 'Leaving tests_load_and_delay()' ) ;
16196 return ;
16197}
16198
16199sub load_and_delay
16200{
16201 # Basically return 0 if load is not heavy, ie <= 1 per processor
16202
16203 # Not enough arguments
16204 if ( 4 > scalar @ARG ) { return ; }
16205
16206 my ( $cpu_num, $avg_1_min, $avg_5_min, $avg_15_min ) = @ARG ;
16207
16208 if ( 0 == $cpu_num ) { return ; }
16209
16210 # Let divide by number of cores
16211 ( $avg_1_min, $avg_5_min, $avg_15_min ) = map { $_ / $cpu_num } ( $avg_1_min, $avg_5_min, $avg_15_min ) ;
16212 # One of avg ok => ok, for now it is a OR
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016213 if ( $avg_1_min < 6 ) { return 0 ; }
16214 if ( $avg_5_min < 6 ) { return 1 ; } # Retry in 1 minute
16215 if ( $avg_15_min < 6 ) { return 5 ; } # Retry in 5 minutes
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016216 return 15 ; # Retry in 15 minutes
16217}
16218
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016219
16220sub tests_cpu_time
16221{
16222 note( 'Entering tests_cpu_time()' ) ;
16223
16224 ok( is_number( cpu_time( ) ), 'cpu_time: no args => a number' ) ;
16225
16226 my $mysync = { } ;
16227 $mysync->{ debug } = 1 ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016228 ok( is_number( cpu_time( $mysync ) ), 'cpu_time: {} => a number' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016229
16230 note( 'Leaving tests_cpu_time()' ) ;
16231 return ;
16232}
16233
16234sub cpu_time
16235{
16236 my $mysync = shift ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016237
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016238 my @cpu_times = times ;
16239 if ( ! @cpu_times ) { return ; }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016240
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016241 my $cpu_time = 0 ;
16242 # last element is the sum of all elements
16243 $cpu_time = ( map { $cpu_time += $_ } @cpu_times )[ -1 ] ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016244 my $cpu_time_round = mysprintf( '%.2f', $cpu_time ) ;
16245 $mysync->{ debug } and myprint( join(' + ', @cpu_times), " = $cpu_time ~ $cpu_time_round\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016246 return $cpu_time ;
16247}
16248
16249
16250sub tests_cpu_percent
16251{
16252 note( 'Entering tests_cpu_percent()' ) ;
16253
16254 is( '0.0', cpu_percent( ), 'cpu_percent: no args => 0.0' ) ;
16255 my $mysync = { } ;
16256 $mysync->{ debug } = 1 ;
16257 is( '0.0', cpu_percent( $mysync ), 'cpu_percent: {} => 0.0' ) ;
16258 is( '0.0', cpu_percent( $mysync, 0 ), 'cpu_percent: {} 0 => 0.0' ) ;
16259 is( '300.0', cpu_percent( $mysync, 3 ), 'cpu_percent: {} 3 => 300.0' ) ;
16260 is( '30.0', cpu_percent( $mysync, 3, 10 ), 'cpu_percent: {} 3 10 => 30.0' ) ;
16261 is( '0.0', cpu_percent( $mysync, 0, 10 ), 'cpu_percent: {} 0 10 => 0.0' ) ;
16262
16263 note( 'Leaving tests_cpu_percent()' ) ;
16264 return ;
16265}
16266
16267sub cpu_percent
16268{
16269 my $mysync = shift ;
16270 my $cpu_time = shift || 0 ;
16271 my $timediff = shift || 1 ; # no division by 0
16272
16273 if ( $cpu_time > $timediff )
16274 {
16275 myprint( "Strange: cpu_time $cpu_time > timediff $timediff\n" ) ;
16276 }
16277 my $cpu_percent = 0 ;
16278 $cpu_percent = mysprintf( '%.1f', 100 * $cpu_time / $timediff ) ;
16279 $mysync->{ debug } and myprint( "cpu_percent: $cpu_percent \n" ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016280
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016281 return $cpu_percent ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016282
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016283}
16284
16285sub tests_cpu_percent_global
16286{
16287 note( 'Entering tests_cpu_percent_global()' ) ;
16288
16289 is( '0.0', cpu_percent_global( ), 'cpu_percent_global: no args => 0' ) ;
16290 my $mysync = { } ;
16291 $mysync->{ debug } = 1 ;
16292 is( '0.0', cpu_percent_global( $mysync ), 'cpu_percent_global: {} => 0' ) ;
16293 is( '0.0', cpu_percent_global( $mysync, 0 ), 'cpu_percent_global: {} 0 => 0' ) ;
16294
16295 SKIP: {
16296 if ( ! ( 'i005' eq hostname() ) )
16297 {
16298 skip( 'cpu_percent_global on host != i005', 1 ) ;
16299 }
16300 is( '25.0', cpu_percent_global( $mysync, 100 ), 'cpu_percent_global: {} 100 => 25 on host i005' ) ;
16301 } ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016302
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016303 SKIP: {
16304 if ( ! ( 'petite' eq hostname() ) )
16305 {
16306 skip( 'cpu_percent_global on host != petite', 1 ) ;
16307 }
16308 is( '50.0', cpu_percent_global( $mysync, 100 ), 'cpu_percent_global: {} 100 => 50 on host petite' ) ;
16309 } ;
16310
16311 note( 'Leaving tests_cpu_percent_global()' ) ;
16312 return ;
16313}
16314
16315sub cpu_percent_global
16316{
16317 my $mysync = shift ;
16318 my $cpu_percent = shift || 0 ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016319
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016320 my $cpu_number = cpu_number( ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016321
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016322 my $cpu_percent_global ;
16323 $cpu_percent_global = mysprintf( '%.1f', $cpu_percent / $cpu_number ) ;
16324 $mysync->{ debug } and myprint( "cpu_percent_global: $cpu_percent_global \n" ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016325
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016326 return( $cpu_percent_global ) ;
16327}
16328
16329
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016330sub ram_memory_info
16331{
16332 # In GigaBytes so division by 1024 * 1024 * 1024
16333 #
16334 return(
16335 sprintf( "%.1f/%.1f free GiB of RAM",
16336 Sys::MemInfo::get("freemem") / ( $KIBI ** 3 ),
16337 Sys::MemInfo::get("totalmem") / ( $KIBI ** 3 ),
16338 )
16339 ) ;
16340}
16341
16342
16343
16344sub tests_memory_stress
16345{
16346 note( 'Entering tests_memory_stress()' ) ;
16347
16348 is( undef, memory_stress( ), 'memory_stress: => undef' ) ;
16349
16350 note( 'Leaving tests_memory_stress()' ) ;
16351 return ;
16352}
16353
16354sub memory_stress
16355{
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016356 my $total_ram_in_MB = Sys::MemInfo::get("totalmem") / ( $KIBI * $KIBI ) ;
16357 my $i = 1 ;
16358
16359 myprintf("Stress memory consumption before: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI ) ;
16360 while ( $i < $total_ram_in_MB / 1.7 ) { $a .= "A" x 1000_000; $i++ } ;
16361 myprintf("Stress memory consumption after: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI ) ;
16362 return ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016363}
16364
16365sub tests_memory_consumption
16366{
16367 note( 'Entering tests_memory_consumption()' ) ;
16368
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016369 note( "memory_consumption: " . memory_consumption() . " bytes aka " . bytes_display_string_dec( memory_consumption() ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016370 like( memory_consumption( ), qr{\d+}xms,'memory_consumption no args') ;
16371 like( memory_consumption( 1 ), qr{\d+}xms,'memory_consumption 1') ;
16372 like( memory_consumption( $PROCESS_ID ), qr{\d+}xms,"memory_consumption_of_pids $PROCESS_ID") ;
16373
16374 like( memory_consumption_ratio(), qr{\d+}xms, 'memory_consumption_ratio' ) ;
16375 like( memory_consumption_ratio(1), qr{\d+}xms, 'memory_consumption_ratio 1' ) ;
16376 like( memory_consumption_ratio(10), qr{\d+}xms, 'memory_consumption_ratio 10' ) ;
16377
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016378
16379 note( 'Leaving tests_memory_consumption()' ) ;
16380 return ;
16381}
16382
16383sub memory_consumption
16384{
16385 # memory consumed by imapsync until now in bytes
16386 return( ( memory_consumption_of_pids( ) )[0] );
16387}
16388
16389sub debugmemory
16390{
16391 my $mysync = shift ;
16392 if ( ! $mysync->{debugmemory} ) { return q{} ; }
16393
16394 my $precision = shift ;
16395 return( mysprintf( "Memory consumption$precision: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI ) ) ;
16396}
16397
16398sub memory_consumption_of_pids
16399{
16400
16401 my @pid = @_;
16402 @pid = ( @pid ) ? @pid : ( $PROCESS_ID ) ;
16403
16404 $sync->{ debug } and myprint( "memory_consumption_of_pids PIDs: @pid\n" ) ;
16405 my @val ;
16406 if ( ( 'MSWin32' eq $OSNAME ) or ( 'cygwin' eq $OSNAME ) ) {
16407 @val = memory_consumption_of_pids_win32( @pid ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016408 }
16409 elsif ( 'darwin' eq $OSNAME )
16410 {
16411 @val = memory_consumption_of_pids_mac( @pid ) ;
16412 }
16413 else
16414 {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016415 # Unix
16416 my @ps = qx{ ps -o vsz -p @pid } ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016417 shift @ps ; # First line is column name "VSZ"
16418 chomp @ps ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016419 # convert to octets
16420
16421 @val = map { $_ * $KIBI } @ps ;
16422 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016423 return( @val ) ;
16424}
16425
16426
16427sub memory_consumption_of_pids_mac
16428{
16429 my @pid = @_ ;
16430 # Use IPC::Open3 from perlcrit -3
16431 # But it stalls on Darwin, I don't understand why!
16432 #my @ps = backtick( "ps -o rss -p @pid" ) ;
16433 #myprint( "ps: @ps" ) ;
16434 my @ps = qx{ ps -o rss -p @pid } ;
16435 shift @ps ; # First line is column name "RSS"
16436 chomp @ps ;
16437 my @val = map { $_ * $KIBI } @ps ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016438 return( @val ) ;
16439}
16440
16441sub memory_consumption_of_pids_win32
16442{
16443 # Windows
16444 my @PID = @_;
16445 my %PID;
16446 # hash of pids as key values
16447 map { $PID{$_}++ } @PID;
16448
16449 # Does not work but should work reading the tasklist documentation
16450 #@ps = qx{ tasklist /FI "PID eq @PID" };
16451
16452 my @ps = qx{ tasklist /NH /FO CSV } ;
16453 #my @ps = backtick( 'tasklist /NH /FO CSV' ) ;
16454 #myprint( "-" x $STD_CHAR_PER_LINE, "\n", @ps, "-" x $STD_CHAR_PER_LINE, "\n" ) ;
16455 my @val;
16456 foreach my $line (@ps) {
16457 my($name, $pid, $mem) = (split ',', $line )[0,1,4];
16458 next if (! $pid);
16459 #myprint( "[$name][$pid][$mem]" ) ;
16460 if ($PID{remove_qq($pid)}) {
16461 #myprint( "MATCH !\n" ) ;
16462 chomp $mem ;
16463 $mem = remove_qq($mem);
16464 $mem = remove_Ko($mem);
16465 $mem = remove_not_num($mem);
16466 #myprint( "[$mem]\n" ) ;
16467 push @val, $mem * $KIBI;
16468 }
16469 }
16470 return(@val);
16471}
16472
16473
16474sub tests_backtick
16475{
16476 note( 'Entering tests_backtick()' ) ;
16477
16478 is( undef, backtick( ), 'backtick: no args' ) ;
16479 is( undef, backtick( q{} ), 'backtick: empty command' ) ;
16480
16481 SKIP: {
16482 skip( 'test for MSWin32', 5 ) if ('MSWin32' ne $OSNAME) ;
16483 my @output ;
16484 @output = backtick( 'echo Hello World!' ) ;
16485 # Add \r on Windows.
16486 ok( "Hello World!\r\n" eq $output[0], 'backtick: echo Hello World!' ) ;
16487 $sync->{ debug } and myprint( "[@output]" ) ;
16488 @output = backtick( 'echo Hello & echo World!' ) ;
16489 ok( "Hello \r\n" eq $output[0], 'backtick: echo Hello & echo World! line 1' ) ;
16490 ok( "World!\r\n" eq $output[1], 'backtick: echo Hello & echo World! line 2' ) ;
16491 $sync->{ debug } and myprint( "[@output][$output[0]][$output[1]]" ) ;
16492 # Scalar context
16493 ok( "Hello World!\r\n" eq backtick( 'echo Hello World!' ),
16494 'backtick: echo Hello World! scalar' ) ;
16495 ok( "Hello \r\nWorld!\r\n" eq backtick( 'echo Hello & echo World!' ),
16496 'backtick: echo Hello & echo World! scalar 2 lines' ) ;
16497 } ;
16498 SKIP: {
16499 skip( 'test for Unix', 7 ) if ('MSWin32' eq $OSNAME) ;
16500 is( undef, backtick( 'aaaarrrg' ), 'backtick: aaaarrrg command not found' ) ;
16501 # Array context
16502 my @output ;
16503 @output = backtick( 'echo Hello World!' ) ;
16504 ok( "Hello World!\n" eq $output[0], 'backtick: echo Hello World!' ) ;
16505 $sync->{ debug } and myprint( "[@output]" ) ;
16506 @output = backtick( "echo Hello\necho World!" ) ;
16507 ok( "Hello\n" eq $output[0], 'backtick: echo Hello; echo World! line 1' ) ;
16508 ok( "World!\n" eq $output[1], 'backtick: echo Hello; echo World! line 2' ) ;
16509 $sync->{ debug } and myprint( "[@output]" ) ;
16510 # Scalar context
16511 ok( "Hello World!\n" eq backtick( 'echo Hello World!' ),
16512 'backtick: echo Hello World! scalar' ) ;
16513 ok( "Hello\nWorld!\n" eq backtick( "echo Hello\necho World!" ),
16514 'backtick: echo Hello; echo World! scalar 2 lines' ) ;
16515 # Return error positive value, that's ok
16516 is( undef, backtick( 'false' ), 'backtick: false returns no output' ) ;
16517 my $mem = backtick( "ps -o vsz -p $PROCESS_ID" ) ;
16518 $sync->{ debug } and myprint( "MEM=$mem\n" ) ;
16519
16520 }
16521
16522 note( 'Leaving tests_backtick()' ) ;
16523 return ;
16524}
16525
16526
16527sub backtick
16528{
16529 my $command = shift ;
16530
16531 if ( ! $command ) { return ; }
16532
16533 my ( $writer, $reader, $err ) ;
16534 my @output ;
16535 my $pid ;
16536 my $eval = eval {
16537 $pid = IPC::Open3::open3( $writer, $reader, $err, $command ) ;
16538 } ;
16539 if ( $EVAL_ERROR ) {
16540 myprint( $EVAL_ERROR ) ;
16541 return ;
16542 }
16543 if ( ! $eval ) { return ; }
16544 if ( ! $pid ) { return ; }
16545 waitpid( $pid, 0 ) ;
16546 @output = <$reader>; # Output here
16547 #
16548 #my @errors = <$err>; #Errors here, instead of the console
16549 if ( not @output ) { return ; }
16550 #myprint( @output ) ;
16551
16552 if ( $output[0] =~ /\Qopen3: exec of $command failed\E/mxs ) { return ; }
16553 if ( wantarray ) {
16554 return( @output ) ;
16555 } else {
16556 return( join( q{}, @output) ) ;
16557 }
16558}
16559
16560
16561
16562sub tests_check_binary_embed_all_dyn_libs
16563{
16564 note( 'Entering tests_check_binary_embed_all_dyn_libs()' ) ;
16565
16566 is( 1, check_binary_embed_all_dyn_libs( ), 'check_binary_embed_all_dyn_libs: no args => 1' ) ;
16567
16568 note( 'Leaving tests_check_binary_embed_all_dyn_libs()' ) ;
16569
16570 return ;
16571}
16572
16573
16574sub check_binary_embed_all_dyn_libs
16575{
16576 my @search_dyn_lib_locale = search_dyn_lib_locale( ) ;
16577
16578 if ( @search_dyn_lib_locale )
16579 {
16580 myprint( "Found myself $PROGRAM_NAME pid $PROCESS_ID using locale dynamic libraries that seems out of myself:\n" ) ;
16581 myprint( @search_dyn_lib_locale ) ;
16582 if ( $PROGRAM_NAME =~ m{imapsync_bin_Darwin} )
16583 {
16584 return 0 ;
16585 }
16586 elsif ( $PROGRAM_NAME =~ m{imapsync.*\.exe} )
16587 {
16588 return 0 ;
16589 }
16590 else
16591 {
16592 # is always ok for non binary
16593 return 1 ;
16594 }
16595 }
16596 else
16597 {
16598 # Found only embedded dynamic lib
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016599 myprint( "Found only embedded dynamic lib. Good!\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016600 return 1 ;
16601 }
16602}
16603
16604sub search_dyn_lib_locale
16605{
16606 if ( 'darwin' eq $OSNAME )
16607 {
16608 return search_dyn_lib_locale_darwin( ) ;
16609 }
16610 if ( 'linux' eq $OSNAME )
16611 {
16612 return search_dyn_lib_locale_linux( ) ;
16613 }
16614 if ( 'MSWin32' eq $OSNAME )
16615 {
16616 return search_dyn_lib_locale_MSWin32( ) ;
16617 }
16618
16619}
16620
16621sub search_dyn_lib_locale_darwin
16622{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016623 my $command = qq{ lsof -p $PROCESS_ID | grep ' REG ' | grep .dylib | grep -v '/par-' } ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016624 myprint( "Search non embeded dynamic libs with the command: $command\n" ) ;
16625 return backtick( $command ) ;
16626}
16627
16628sub search_dyn_lib_locale_linux
16629{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016630 my $command = qq{ lsof -p $PROCESS_ID | grep ' REG ' | grep -v '/tmp/par-' | grep '\.so' } ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016631 myprint( "Search non embeded dynamic libs with the command: $command\n" ) ;
16632 return backtick( $command ) ;
16633}
16634
16635sub search_dyn_lib_locale_MSWin32
16636{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020016637 my $command = qq{ Listdlls.exe $PROCESS_ID|findstr Strawberry } ;
16638 # $command = qq{ Listdlls.exe $PROCESS_ID|findstr Strawberry } ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016639 myprint( "Search non embeded dynamic libs with the command: $command\n" ) ;
16640 return qx( $command ) ;
16641}
16642
16643
16644
16645sub remove_not_num
16646{
16647
16648 my $string = shift ;
16649 $string =~ tr/0-9//cd ;
16650 #myprint( "tr [$string]\n" ) ;
16651 return( $string ) ;
16652}
16653
16654sub tests_remove_not_num
16655{
16656 note( 'Entering tests_remove_not_num()' ) ;
16657
16658 ok( '123' eq remove_not_num( 123 ), 'remove_not_num( 123 )' ) ;
16659 ok( '123' eq remove_not_num( '123' ), q{remove_not_num( '123' )} ) ;
16660 ok( '123' eq remove_not_num( '12 3' ), q{remove_not_num( '12 3' )} ) ;
16661 ok( '123' eq remove_not_num( 'a 12 3 Ko' ), q{remove_not_num( 'a 12 3 Ko' )} ) ;
16662
16663 note( 'Leaving tests_remove_not_num()' ) ;
16664 return ;
16665}
16666
16667sub remove_Ko
16668{
16669 my $string = shift;
16670 if ($string =~ /^(.*)\sKo$/xo) {
16671 return($1);
16672 }else{
16673 return($string);
16674 }
16675}
16676
16677sub remove_qq
16678{
16679 my $string = shift;
16680 if ($string =~ /^"(.*)"$/xo) {
16681 return($1);
16682 }else{
16683 return($string);
16684 }
16685}
16686
16687sub memory_consumption_ratio
16688{
16689
16690 my ($base) = @_;
16691 $base ||= 1;
16692 my $consu = memory_consumption();
16693 return($consu / $base);
16694}
16695
16696
16697sub date_from_rcs
16698{
16699 my $d = shift ;
16700
16701 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 ) ;
16702 if ($d =~ m{(\d{4})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) {
16703 # Handles the following format
16704 # 2015/07/10 11:05:59 -- Generated by RCS Date tag.
16705 #myprint( "$d\n" ) ;
16706 #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
16707 my ($year, $month, $day, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6) ;
16708 $month = $num2mon{$month} ;
16709 $d = "$day-$month-$year $hour:$min:$sec +0000" ;
16710 #myprint( "$d\n" ) ;
16711 }
16712 return( $d ) ;
16713}
16714
16715sub tests_date_from_rcs
16716{
16717 note( 'Entering tests_date_from_rcs()' ) ;
16718
16719 ok('19-Sep-2015 16:11:07 +0000'
16720 eq date_from_rcs('Date: 2015/09/19 16:11:07 '), 'date_from_rcs from RCS date' ) ;
16721
16722 note( 'Leaving tests_date_from_rcs()' ) ;
16723 return ;
16724}
16725
16726sub good_date
16727{
16728 # two incoming formats:
16729 # header Tue, 24 Aug 2010 16:00:00 +0200
16730 # internal 24-Aug-2010 16:00:00 +0200
16731
16732 # outgoing format: internal date format
16733 # 24-Aug-2010 16:00:00 +0200
16734
16735 my $d = shift ;
16736 return(q{}) if not defined $d;
16737
16738 SWITCH: {
16739 if ( $d =~ m{(\d?)(\d-...-\d{4})(\s\d{2}:\d{2}:\d{2})(\s(?:\+|-)\d{4})?}xo ) {
16740 #myprint( "internal: [$1][$2][$3][$4]\n" ) ;
16741 my ($day_1, $date_rest, $hour, $zone) = ($1,$2,$3,$4) ;
16742 $day_1 = '0' if ($day_1 eq q{}) ;
16743 $zone = ' +0000' if not defined $zone ;
16744 $d = $day_1 . $date_rest . $hour . $zone ;
16745 last SWITCH ;
16746 }
16747
16748 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 ) {
16749 # Handles any combination of following formats
16750 # Tue, 24 Aug 2010 16:00:00 +0200 -- Standard
16751 # 24 Aug 2010 16:00:00 +0200 -- Missing Day of Week
16752 # Tue, 24 Aug 97 16:00:00 +0200 -- Two digit year
16753 # Tue, 24 Aug 1997 16.00.00 +0200 -- Periods instead of colons
16754 # Tue, 24 Aug 1997 16:00:00 +0200 -- Extra whitespace between year and hour
16755 # Tue, 24 Aug 1997 6:5:2 +0200 -- Single digit hour, min, or second
16756 # Tue, 24, Aug 1997 16:00:00 +0200 -- Extra comma
16757
16758 #myprint( "header: [$1][$2][$3][$4][$5][$6][$7][$8]\n" ) ;
16759 my ($day, $month, $year, $hour, $min, $sec, $zone) = ($1,$2,$3,$4,$5,$6,$7,$8);
16760 $year = '19' . $year if length($year) == 2 && $year =~ m/^[789]/xo;
16761 $year = '20' . $year if length($year) == 2;
16762
16763 $month = substr $month, 0, 3 if length($month) > 4;
16764 $day = mysprintf( '%02d', $day);
16765 $hour = mysprintf( '%02d', $hour);
16766 $min = mysprintf( '%02d', $min);
16767 $sec = '00' if not defined $sec ;
16768 $sec = mysprintf( '%02d', $sec ) ;
16769 $zone = '+0000' if not defined $zone ;
16770 $d = "$day-$month-$year $hour:$min:$sec $zone" ;
16771 last SWITCH ;
16772 }
16773
16774 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 ) {
16775 # Handles any combination of following formats
16776 # Sun Aug 20 11:55:09 2006
16777 # Wed Jan 24 11:58:38 MST 2007
16778 # Wed Jan 2 08:40:57 2008
16779
16780 #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
16781 my ($month, $day, $hour, $min, $sec, $year) = ($1,$2,$3,$4,$5,$6);
16782 $day = mysprintf( '%02d', $day ) ;
16783 $hour = mysprintf( '%02d', $hour ) ;
16784 $min = mysprintf( '%02d', $min ) ;
16785 $sec = mysprintf( '%02d', $sec ) ;
16786 $d = "$day-$month-$year $hour:$min:$sec +0000" ;
16787 last SWITCH ;
16788 }
16789 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 ) ;
16790
16791 if ($d =~ m{(\d{4})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) {
16792 # Handles the following format
16793 # 2015/07/10 11:05:59 -- Generated by RCS Date tag.
16794 #myprint( "$d\n" ) ;
16795 #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
16796 my ($year, $month, $day, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6) ;
16797 $month = $num2mon{$month} ;
16798 $d = "$day-$month-$year $hour:$min:$sec +0000" ;
16799 #myprint( "$d\n" ) ;
16800 last SWITCH ;
16801 }
16802
16803 if ($d =~ m{(\d{2})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) {
16804 # Handles the following format
16805 # 02/06/09 22:18:08 -- Generated by AVTECH TemPageR devices
16806
16807 #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
16808 my ($month, $day, $year, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6);
16809 $year = '20' . $year;
16810 $month = $num2mon{$month};
16811 $d = "$day-$month-$year $hour:$min:$sec +0000";
16812 last SWITCH ;
16813 }
16814
16815 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 ) {
16816 # Handles the following format
16817 # Saturday, December 14, 2002 05:00 PM - KBtoys.com order confirmations
16818
16819 my ($month, $day, $year, $hour, $min, $apm) = ($1,$2,$3,$4,$5,$6);
16820
16821 $hour += 12 if $apm eq 'PM' ;
16822 $day = mysprintf( '%02d', $day ) ;
16823 $d = "$day-$month-$year $hour:$min:00 +0000" ;
16824 last SWITCH ;
16825 }
16826
16827 if ($d =~ m{(\w{3})\s(\d{1,2})\s(\d{4})\s(\d{2}):(\d{2}):(\d{2})\s((?:\+|-)\d{4})}xo ) {
16828 # Handles the following format
16829 # Saturday, December 14, 2002 05:00 PM - jr.com order confirmations
16830
16831 my ($month, $day, $year, $hour, $min, $sec, $zone) = ($1,$2,$3,$4,$5,$6,$7);
16832
16833 $day = mysprintf( '%02d', $day ) ;
16834 $d = "$day-$month-$year $hour:$min:$sec $zone";
16835 last SWITCH ;
16836 }
16837
16838 if ($d =~ m{(\d{1,2})-(\w{3})-(\d{4})}xo ) {
16839 # Handles the following format
16840 # 21-Jun-2001 - register.com domain transfer email circa 2001
16841
16842 my ($day, $month, $year) = ($1,$2,$3);
16843 $day = mysprintf( '%02d', $day);
16844 $d = "$day-$month-$year 11:11:11 +0000";
16845 last SWITCH ;
16846 }
16847
16848 # unknown or unmatch => return same string
16849 return($d);
16850 }
16851
16852 $d = qq("$d") ;
16853 return( $d ) ;
16854}
16855
16856
16857sub tests_good_date
16858{
16859 note( 'Entering tests_good_date()' ) ;
16860
16861 ok(q{} eq good_date(), 'good_date no arg');
16862 ok('"24-Aug-2010 16:00:00 +0200"' eq good_date('24-Aug-2010 16:00:00 +0200'), 'good_date internal 2digit zone');
16863 ok('"24-Aug-2010 16:00:00 +0000"' eq good_date('24-Aug-2010 16:00:00'), 'good_date internal 2digit no zone');
16864 ok('"01-Sep-2010 16:00:00 +0200"' eq good_date( '1-Sep-2010 16:00:00 +0200'), 'good_date internal SP 1digit');
16865 ok('"24-Aug-2010 16:00:00 +0200"' eq good_date('Tue, 24 Aug 2010 16:00:00 +0200'), 'good_date header 2digit zone');
16866 ok('"01-Sep-2010 16:00:00 +0000"' eq good_date('Wed, 1 Sep 2010 16:00:00'), 'good_date header SP 1digit zone');
16867 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');
16868 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');
16869 ok('"06-Feb-2009 22:18:08 +0000"' eq good_date('02/06/09 22:18:08'), 'good_date header TemPageR');
16870 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');
16871 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');
16872 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');
16873 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');
16874 ok('"24-Aug-2067 16:00:00 +0200"' eq good_date('Tue, 24 Aug 67 16:00:00 +0200'), 'good_date header 2digit year');
16875 ok('"24-Aug-1977 16:00:00 +0200"' eq good_date('Tue, 24 Aug 77 16:00:00 +0200'), 'good_date header 2digit year');
16876 ok('"24-Aug-1987 16:00:00 +0200"' eq good_date('Tue, 24 Aug 87 16:00:00 +0200'), 'good_date header 2digit year');
16877 ok('"24-Aug-1997 16:00:00 +0200"' eq good_date('Tue, 24 Aug 97 16:00:00 +0200'), 'good_date header 2digit year');
16878 ok('"24-Aug-2004 16:00:00 +0200"' eq good_date('Tue, 24 Aug 04 16:00:00 +0200'), 'good_date header 2digit year');
16879 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');
16880 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');
16881 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');
16882 ok('"24-Aug-1997 05:06:02 +0200"' eq good_date('Tue, 24, Aug 1997 05:06:02 +0200'), 'good_date header extra commas');
16883 ok('"01-Oct-2003 12:45:24 +0000"' eq good_date('Wednesday, 01 October 2003 12:45:24 CDT'), 'good_date header no abbrev');
16884 ok('"11-Jan-2005 17:58:27 -0500"' eq good_date('Tue, 11 Jan 2005 17:58:27 -0500'), 'good_date extra white space');
16885 ok('"18-Dec-2002 15:07:00 +0000"' eq good_date('Wednesday, December 18, 2002 03:07 PM'), 'good_date kbtoys.com orders');
16886 ok('"16-Dec-2004 02:01:49 -0500"' eq good_date('Dec 16 2004 02:01:49 -0500'), 'good_date jr.com orders');
16887 ok('"21-Jun-2001 11:11:11 +0000"' eq good_date('21-Jun-2001'), 'good_date register.com domain transfer');
16888 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)');
16889 ok('"19-Sep-2015 16:11:07 +0000"' eq good_date('Date: 2015/09/19 16:11:07 '), 'good_date from RCS date' ) ;
16890
16891 note( 'Leaving tests_good_date()' ) ;
16892 return ;
16893}
16894
16895
16896sub tests_list_keys_in_2_not_in_1
16897{
16898 note( 'Entering tests_list_keys_in_2_not_in_1()' ) ;
16899
16900
16901 my @list;
16902 ok( ! list_keys_in_2_not_in_1( {}, {}), 'list_keys_in_2_not_in_1: {} {}');
16903 ok( 0 == compare_lists( [], [ list_keys_in_2_not_in_1( {}, {} ) ] ), 'list_keys_in_2_not_in_1: {} {}');
16904 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}');
16905 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}');
16906 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}');
16907 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}');
16908 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}');
16909
16910 note( 'Leaving tests_list_keys_in_2_not_in_1()' ) ;
16911 return ;
16912}
16913
16914sub list_keys_in_2_not_in_1
16915{
16916 my $hash_1_ref = shift;
16917 my $hash_2_ref = shift;
16918 my @list;
16919
16920 foreach my $key ( sort keys %{ $hash_2_ref } ) {
16921 #$sync->{ debug } and print "$key\n" ;
16922 if ( exists $hash_1_ref->{$key} )
16923 {
16924 next ;
16925 }
16926 #$sync->{ debug } and print "list_keys_in_2_not_in_1: $key\n" ;
16927 push @list, $key ;
16928 }
16929 #$sync->{ debug } and print "@list\n" ;
16930 return( @list ) ;
16931}
16932
16933
16934sub list_folders_in_2_not_in_1
16935{
16936
16937 my ( @h2_folders_not_in_h1, %h2_folders_not_in_h1 ) ;
16938 @h2_folders_not_in_h1 = list_keys_in_2_not_in_1( \%h1_folders_all, \%h2_folders_all ) ;
16939 map { $h2_folders_not_in_h1{$_} = 1} @h2_folders_not_in_h1 ;
16940 @h2_folders_not_in_h1 = list_keys_in_2_not_in_1( \%h2_folders_from_1_all, \%h2_folders_not_in_h1 ) ;
16941 #$sync->{ debug } and print "h2_folders_not_in_h1: @h2_folders_not_in_h1\n" ;
16942 return( reverse @h2_folders_not_in_h1 ) ;
16943}
16944
16945sub tests_nb_messages_in_2_not_in_1
16946{
16947 note( 'Entering tests_stats_across_folders()' ) ;
16948 is( undef, nb_messages_in_2_not_in_1( ), 'nb_messages_in_2_not_in_1: no args => undef' ) ;
16949
16950 my $mysync->{ h1_folders_of_md5 }->{ 'some_id_01' }->{ 'some_folder_01' } = 1 ;
16951 is( 0, nb_messages_in_2_not_in_1( $mysync ), 'nb_messages_in_2_not_in_1: no messages in 2 => 0' ) ;
16952
16953 $mysync->{ h1_folders_of_md5 }->{ 'some_id_in_1_and_2' }->{ 'some_folder_01' } = 2 ;
16954 $mysync->{ h2_folders_of_md5 }->{ 'some_id_in_1_and_2' }->{ 'some_folder_02' } = 4 ;
16955
16956 is( 0, nb_messages_in_2_not_in_1( $mysync ), 'nb_messages_in_2_not_in_1: a common message => 0' ) ;
16957
16958 $mysync->{ h2_folders_of_md5 }->{ 'some_id_in_2_not_in_1' }->{ 'some_folder_02' } = 1 ;
16959 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' ) ;
16960
16961 $mysync->{ h2_folders_of_md5 }->{ 'some_other_id_in_2_not_in_1' }->{ 'some_folder_02' } = 3 ;
16962 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' ) ;
16963
16964 note( 'Leaving tests_stats_across_folders()' ) ;
16965 return ;
16966}
16967
16968sub nb_messages_in_2_not_in_1
16969{
16970 my $mysync = shift ;
16971 if ( not defined $mysync ) { return ; }
16972
16973 $mysync->{ nb_messages_in_2_not_in_1 } = scalar(
16974 list_keys_in_2_not_in_1(
16975 $mysync->{ h1_folders_of_md5 },
16976 $mysync->{ h2_folders_of_md5 } ) ) ;
16977
16978 return $mysync->{ nb_messages_in_2_not_in_1 } ;
16979}
16980
16981
16982sub nb_messages_in_1_not_in_2
16983{
16984 my $mysync = shift ;
16985 if ( not defined $mysync ) { return ; }
16986
16987 $mysync->{ nb_messages_in_1_not_in_2 } = scalar(
16988 list_keys_in_2_not_in_1(
16989 $mysync->{ h2_folders_of_md5 },
16990 $mysync->{ h1_folders_of_md5 } ) ) ;
16991
16992 return $mysync->{ nb_messages_in_1_not_in_2 } ;
16993}
16994
16995
16996
16997sub comment_on_final_diff_in_1_not_in_2
16998{
16999 my $mysync = shift ;
17000
17001 if ( not defined $mysync
17002 or $mysync->{ justfolders }
17003 or $mysync->{ useuid }
17004 )
17005 {
17006 return ;
17007 }
17008
17009 my $nb_identified_h1_messages = scalar( keys %{ $mysync->{ h1_folders_of_md5 } } ) ;
17010 my $nb_identified_h2_messages = scalar( keys %{ $mysync->{ h2_folders_of_md5 } } ) ;
17011 $mysync->{ debug } and myprint( "nb_keys h1_folders_of_md5 $nb_identified_h1_messages\n" ) ;
17012 $mysync->{ debug } and myprint( "nb_keys h2_folders_of_md5 $nb_identified_h2_messages\n" ) ;
17013
17014 if ( 0 == $nb_identified_h1_messages ) { return ; }
17015
17016 # Calculate if not yet done
17017 if ( not defined $mysync->{ nb_messages_in_1_not_in_2 } )
17018 {
17019 nb_messages_in_1_not_in_2( $mysync ) ;
17020 }
17021
17022
17023 if ( 0 == $mysync->{ nb_messages_in_1_not_in_2 } )
17024 {
17025 myprint( "The sync looks good, all ",
17026 $nb_identified_h1_messages,
17027 " identified messages in host1 are on host2.\n" ) ;
17028 }
17029 else
17030 {
17031 myprint( "The sync is not finished, there are ",
17032 $mysync->{ nb_messages_in_1_not_in_2 },
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017033 " among ",
17034 $nb_identified_h1_messages,
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017035 " identified messages in host1 that are not on host2.\n" ) ;
17036 }
17037
17038
17039 if ( 1 <= $mysync->{ h1_nb_msg_noheader } )
17040 {
17041 myprint( "There are ",
17042 $mysync->{ h1_nb_msg_noheader },
17043 " unidentified messages (usually Sent or Draft messages).",
17044 " To sync them add option --addheader\n" ) ;
17045 }
17046 else
17047 {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017048 myprint( "There is no unidentified message on host1.\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017049 }
17050
17051 return ;
17052}
17053
17054sub comment_on_final_diff_in_2_not_in_1
17055{
17056 my $mysync = shift ;
17057
17058 if ( not defined $mysync
17059 or $mysync->{ justfolders }
17060 or $mysync->{ useuid }
17061 )
17062 {
17063 return ;
17064 }
17065
17066 my $nb_identified_h2_messages = scalar( keys %{ $mysync->{ h2_folders_of_md5 } } ) ;
17067 # Calculate if not done yet
17068 if ( not defined $mysync->{ nb_messages_in_2_not_in_1 } )
17069 {
17070 nb_messages_in_2_not_in_1( $mysync ) ;
17071 }
17072
17073 if ( 0 == $mysync->{ nb_messages_in_2_not_in_1 } )
17074 {
17075 myprint( "The sync is strict, all ",
17076 $nb_identified_h2_messages,
17077 " identified messages in host2 are on host1.\n" ) ;
17078 }
17079 else
17080 {
17081 myprint( "The sync is not strict, there are ",
17082 $mysync->{ nb_messages_in_2_not_in_1 },
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017083 " among ",
17084 $nb_identified_h2_messages,
17085 " identified messages in host2 that are not on host1.",
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010017086 " Use --delete2 and sync again to delete them and have a strict sync.\n"
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017087 ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017088 }
17089 return ;
17090}
17091
17092
17093sub tests_match
17094{
17095 note( 'Entering tests_match()' ) ;
17096
17097 # undef serie
17098 is( undef, match( ), 'match: no args => undef' ) ;
17099 is( undef, match( 'lalala' ), 'match: one args => undef' ) ;
17100
17101 # This one gives 0 under a binary made by pp
17102 # but 1 under "normal" Perl interpreter. So a PAR bug?
17103 #is( 1, match( q{}, q{} ), 'match: q{} =~ q{} => 1' ) ;
17104
17105 is( 'lalala', match( 'lalala', 'lalala' ), 'match: lalala =~ lalala => lalala' ) ;
17106 is( 'lalala', match( 'lalala', '^lalala' ), 'match: lalala =~ ^lalala => lalala' ) ;
17107 is( 'lalala', match( 'lalala', 'lalala$' ), 'match: lalala =~ lalala$ => lalala' ) ;
17108 is( 'lalala', match( 'lalala', '^lalala$' ), 'match: lalala =~ ^lalala$ => lalala' ) ;
17109 is( '_lalala_', match( '_lalala_', 'lalala' ), 'match: _lalala_ =~ lalala => _lalala_' ) ;
17110 is( 'lalala', match( 'lalala', '.*' ), 'match: lalala =~ .* => lalala' ) ;
17111 is( 'lalala', match( 'lalala', '.' ), 'match: lalala =~ . => lalala' ) ;
17112 is( '/lalala/', match( '/lalala/', '/lalala/' ), 'match: /lalala/ =~ /lalala/ => /lalala/' ) ;
17113
17114 is( 0, match( 'foo', 's/foo/bar/g' ), 'match: foo =~ s/foo/bar/g => 0' ) ;
17115 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' ) ;
17116
17117
17118 is( 0, match( 'lalala', 'ooo' ), 'match: lalala =~ ooo => 0' ) ;
17119 is( 0, match( 'lalala', 'lal_ala' ), 'match: lalala =~ lal_ala => 0' ) ;
17120 is( 0, match( 'lalala', '\.' ), 'match: lalala =~ \. => 0' ) ;
17121 is( 0, match( 'lalalaX', '^lalala$' ), 'match: lalalaX =~ ^lalala$ => 0' ) ;
17122 is( 0, match( 'lalala', '/lalala/' ), 'match: lalala =~ /lalala/ => 0' ) ;
17123
17124 is( 'LALALA', match( 'LALALA', '(?i:lalala)' ), 'match: LALALA =~ (?i:lalala) => 1' ) ;
17125
17126 is( undef, match( 'LALALA', '(?{`ls /`})' ), 'match: LALALA =~ (?{`ls /`}) => undef' ) ;
17127 is( undef, match( 'LALALA', '(?{print "CACA"})' ), 'match: LALALA =~ (?{print "CACA"}) => undef' ) ;
17128 is( undef, match( 'CACA', '(??{print "CACA"})' ), 'match: CACA =~ (??{print "CACA"}) => undef' ) ;
17129
17130 note( 'Leaving tests_match()' ) ;
17131
17132 return ;
17133}
17134
17135sub match
17136{
17137 my( $var, $regex ) = @ARG ;
17138
17139 # undef cases
17140 if ( ( ! defined $var ) or ( ! defined $regex ) ) { return ; }
17141
17142 # normal cases
17143 if ( eval { $var =~ qr{$regex} } ) {
17144 return $var ;
17145 }elsif ( $EVAL_ERROR ) {
17146 myprint( "Fatal regex $regex\n" ) ;
17147 return ;
17148 } else {
17149 return 0 ;
17150 }
17151 return ;
17152}
17153
17154
17155sub tests_notmatch
17156{
17157 note( 'Entering tests_notmatch()' ) ;
17158
17159 # undef serie
17160 is( undef, notmatch( ), 'notmatch: no args => undef' ) ;
17161 is( undef, notmatch( 'lalala' ), 'notmatch: one args => undef' ) ;
17162
17163 is( 1, notmatch( 'lalala', '/lalala/' ), 'notmatch: lalala !~ /lalala/ => 1' ) ;
17164 is( 0, notmatch( '/lalala/', '/lalala/' ), 'notmatch: /lalala/ !~ /lalala/ => 0' ) ;
17165 is( 1, notmatch( 'lalala', '/ooo/' ), 'notmatch: lalala !~ /ooo/ => 1' ) ;
17166
17167 # This one gives 1 under a binary made by pp
17168 # but 0 under "normal" Perl interpreter. So a PAR bug, same in tests_match .
17169 #is( 0, notmatch( q{}, q{} ), 'notmatch: q{} !~ q{} => 0' ) ;
17170
17171 is( 0, notmatch( 'lalala', 'lalala' ), 'notmatch: lalala !~ lalala => 0' ) ;
17172 is( 0, notmatch( 'lalala', '^lalala' ), 'notmatch: lalala !~ ^lalala => 0' ) ;
17173 is( 0, notmatch( 'lalala', 'lalala$' ), 'notmatch: lalala !~ lalala$ => 0' ) ;
17174 is( 0, notmatch( 'lalala', '^lalala$' ), 'notmatch: lalala !~ ^lalala$ => 0' ) ;
17175 is( 0, notmatch( '_lalala_', 'lalala' ), 'notmatch: _lalala_ !~ lalala => 0' ) ;
17176 is( 0, notmatch( 'lalala', '.*' ), 'notmatch: lalala !~ .* => 0' ) ;
17177 is( 0, notmatch( 'lalala', '.' ), 'notmatch: lalala !~ . => 0' ) ;
17178
17179
17180 is( 1, notmatch( 'lalala', 'ooo' ), 'notmatch: does not match regex => 1' ) ;
17181 is( 1, notmatch( 'lalala', 'lal_ala' ), 'notmatch: does not match regex => 1' ) ;
17182 is( 1, notmatch( 'lalala', '\.' ), 'notmatch: matches regex => 0' ) ;
17183 is( 1, notmatch( 'lalalaX', '^lalala$' ), 'notmatch: does not match regex => 1' ) ;
17184
17185 note( 'Leaving tests_notmatch()' ) ;
17186
17187 return ;
17188}
17189
17190sub notmatch
17191{
17192 my( $var, $regex ) = @ARG ;
17193
17194 # undef cases
17195 if ( ( ! defined $var ) or ( ! defined $regex ) ) { return ; }
17196
17197 # normal cases
17198 if ( eval { $var !~ $regex } ) {
17199 return 1 ;
17200 }elsif ( $EVAL_ERROR ) {
17201 myprint( "Fatal regex $regex\n" ) ;
17202 return ;
17203 }else{
17204 return 0 ;
17205 }
17206 return ;
17207}
17208
17209
17210sub delete_folders_in_2_not_in_1
17211{
17212
17213 foreach my $folder ( @h2_folders_not_in_1 ) {
17214 if ( defined $delete2foldersonly and eval "\$folder !~ $delete2foldersonly" ) {
17215 myprint( "Not deleting $folder because of --delete2foldersonly $delete2foldersonly\n" ) ;
17216 next ;
17217 }
17218 if ( defined $delete2foldersbutnot and eval "\$folder =~ $delete2foldersbutnot" ) {
17219 myprint( "Not deleting $folder because of --delete2foldersbutnot $delete2foldersbutnot\n" ) ;
17220 next ;
17221 }
17222 my $res = $sync->{dry} ; # always success in dry mode!
17223 $sync->{imap2}->unsubscribe( $folder ) if ( ! $sync->{dry} ) ;
17224 $res = $sync->{imap2}->delete( $folder ) if ( ! $sync->{dry} ) ;
17225 if ( $res ) {
17226 myprint( "Deleted $folder", "$sync->{dry_message}", "\n" ) ;
17227 }else{
17228 myprint( "Deleting $folder failed", "\n" ) ;
17229 }
17230 }
17231 return ;
17232}
17233
17234sub delete_folder
17235{
17236 my ( $mysync, $imap, $folder, $Side ) = @_ ;
17237 if ( ! $mysync ) { return ; }
17238 if ( ! $imap ) { return ; }
17239 if ( ! $folder ) { return ; }
17240 $Side ||= 'HostX' ;
17241
17242 my $res = $mysync->{dry} ; # always success in dry mode!
17243 if ( ! $mysync->{dry} ) {
17244 $imap->unsubscribe( $folder ) ;
17245 $res = $imap->delete( $folder ) ;
17246 }
17247 if ( $res ) {
17248 myprint( "$Side deleted $folder", $mysync->{dry_message}, "\n" ) ;
17249 return 1 ;
17250 }else{
17251 myprint( "$Side deleting $folder failed", "\n" ) ;
17252 return ;
17253 }
17254}
17255
17256sub delete1emptyfolders
17257{
17258 my $mysync = shift ;
17259 if ( ! $mysync ) { return ; } # abort if no parameter
17260 if ( ! $mysync->{delete1emptyfolders} ) { return ; } # abort if --delete1emptyfolders off
17261 my $imap = $mysync->{imap1} ;
17262 if ( ! $imap ) { return ; } # abort if no imap
17263 if ( $imap->IsUnconnected( ) ) { return ; } # abort if disconnected
17264
17265 my %folders_kept ;
17266 myprint( qq{Host1 deleting empty folders\n} ) ;
17267 foreach my $folder ( reverse sort @{ $mysync->{h1_folders_wanted} } ) {
17268 my $parenthood = $imap->is_parent( $folder ) ;
17269 if ( defined $parenthood and $parenthood ) {
17270 myprint( "Host1: folder $folder has subfolders\n" ) ;
17271 $folders_kept{ $folder }++ ;
17272 next ;
17273 }
17274 my $nb_messages_select = examine_folder_and_count( $mysync, $imap, $folder, 'Host1' ) ;
17275 if ( ! defined $nb_messages_select ) { next ; } # Select failed => Neither continue nor keep this folder }
17276 my $nb_messages_search = scalar( @{ $imap->messages( ) } ) ;
17277 if ( 0 != $nb_messages_select and 0 != $nb_messages_search ) {
17278 myprint( "Host1: folder $folder has messages: $nb_messages_search (search) $nb_messages_select (select)\n" ) ;
17279 $folders_kept{ $folder }++ ;
17280 next ;
17281 }
17282 if ( 0 != $nb_messages_select + $nb_messages_search ) {
17283 myprint( "Host1: folder $folder odd messages count: $nb_messages_search (search) $nb_messages_select (select)\n" ) ;
17284 $folders_kept{ $folder }++ ;
17285 next ;
17286 }
17287 # Here we must have 0 messages by messages() aka "SEARCH ALL" and also "EXAMINE"
17288 if ( uc $folder eq 'INBOX' ) {
17289 myprint( "Host1: Not deleting $folder\n" ) ;
17290 $folders_kept{ $folder }++ ;
17291 next ;
17292 }
17293 myprint( "Host1: deleting empty folder $folder\n" ) ;
17294 # can not delete a SELECTed or EXAMINEd folder so closing it
17295 # could changed be SELECT INBOX
17296 $imap->close( ) ; # close after examine does not expunge; anyway expunging an empty folder...
17297 if ( delete_folder( $mysync, $imap, $folder, 'Host1' ) ) {
17298 next ; # Deleted, good!
17299 }else{
17300 $folders_kept{ $folder }++ ;
17301 next ; # Not deleted, bad!
17302 }
17303 }
17304 remove_deleted_folders_from_wanted_list( $mysync, %folders_kept ) ;
17305 myprint( qq{Host1 ended deleting empty folders\n} ) ;
17306 return ;
17307}
17308
17309sub remove_deleted_folders_from_wanted_list
17310{
17311 my ( $mysync, %folders_kept ) = @ARG ;
17312
17313 my @h1_folders_wanted_init = @{ $mysync->{h1_folders_wanted} } ;
17314 my @h1_folders_wanted_last ;
17315 foreach my $folder ( @h1_folders_wanted_init ) {
17316 if ( $folders_kept{ $folder } ) {
17317 push @h1_folders_wanted_last, $folder ;
17318 }
17319 }
17320 @{ $mysync->{h1_folders_wanted} } = @h1_folders_wanted_last ;
17321 return ;
17322}
17323
17324
17325sub examine_folder_and_count
17326{
17327 my ( $mysync, $imap, $folder, $Side ) = @_ ;
17328 $Side ||= 'HostX' ;
17329
17330 if ( ! examine_folder( $mysync, $imap, $folder, $Side ) ) {
17331 return ;
17332 }
17333 my $nb_messages_select = count_from_select( $imap->History ) ;
17334 return $nb_messages_select ;
17335}
17336
17337
17338sub tests_delete1emptyfolders
17339{
17340 note( 'Entering tests_delete1emptyfolders()' ) ;
17341
17342
17343 is( undef, delete1emptyfolders( ), q{delete1emptyfolders: undef} ) ;
17344 my $syncT ;
17345 is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: undef 2} ) ;
17346 my $imapT ;
17347 $syncT->{imap1} = $imapT ;
17348 is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: undef imap} ) ;
17349
17350 require_ok( "Test::MockObject" ) ;
17351 $imapT = Test::MockObject->new( ) ;
17352 $syncT->{imap1} = $imapT ;
17353
17354 $imapT->set_true( 'IsUnconnected' ) ;
17355 is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: Unconnected imap} ) ;
17356
17357 # Now connected tests
17358 $imapT->set_false( 'IsUnconnected' ) ;
17359 $imapT->mock( 'LastError', sub { q{LastError mocked} } ) ;
17360
17361 $syncT->{delete1emptyfolders} = 0 ;
17362 tests_delete1emptyfolders_unit(
17363 $syncT,
17364 [ qw{ INBOX DELME1 DELME2 } ],
17365 [ qw{ INBOX DELME1 DELME2 } ],
17366 q{tests_delete1emptyfolders: --delete1emptyfolders OFF}
17367 ) ;
17368
17369 # All are parents => no deletion at all
17370 $imapT->set_true( 'is_parent' ) ;
17371 $syncT->{delete1emptyfolders} = 1 ;
17372 tests_delete1emptyfolders_unit(
17373 $syncT,
17374 [ qw{ INBOX DELME1 DELME2 } ],
17375 [ qw{ INBOX DELME1 DELME2 } ],
17376 q{tests_delete1emptyfolders: --delete1emptyfolders ON}
17377 ) ;
17378
17379 # No parents but examine false for all => skip all
17380 $imapT->set_false( 'is_parent', 'examine' ) ;
17381
17382 tests_delete1emptyfolders_unit(
17383 $syncT,
17384 [ qw{ INBOX DELME1 DELME2 } ],
17385 [ ],
17386 q{tests_delete1emptyfolders: EXAMINE fails}
17387 ) ;
17388
17389 # examine ok for all but History bad => skip all
17390 $imapT->set_true( 'examine' ) ;
17391 $imapT->mock( 'History', sub { ( q{History badly mocked} ) } ) ;
17392 tests_delete1emptyfolders_unit(
17393 $syncT,
17394 [ qw{ INBOX DELME1 DELME2 } ],
17395 [ ],
17396 q{tests_delete1emptyfolders: examine ok but History badly mocked so count messages fails}
17397 ) ;
17398
17399 # History good but some messages EXISTS == messages() => no deletion
17400 $imapT->mock( 'History', sub { ( q{* 2 EXISTS} ) } ) ;
17401 $imapT->mock( 'messages', sub { [ qw{ UID_1 UID_2 } ] } ) ;
17402 tests_delete1emptyfolders_unit(
17403 $syncT,
17404 [ qw{ INBOX DELME1 DELME2 } ],
17405 [ qw{ INBOX DELME1 DELME2 } ],
17406 q{tests_delete1emptyfolders: History EXAMINE ok, several messages}
17407 ) ;
17408
17409 # 0 EXISTS but != messages() => no deletion
17410 $imapT->mock( 'History', sub { ( q{* 0 EXISTS} ) } ) ;
17411 $imapT->mock( 'messages', sub { [ qw{ UID_1 UID_2 } ] } ) ;
17412 tests_delete1emptyfolders_unit(
17413 $syncT,
17414 [ qw{ INBOX DELME1 DELME2 } ],
17415 [ qw{ INBOX DELME1 DELME2 } ],
17416 q{tests_delete1emptyfolders: 0 EXISTS but 2 by messages()}
17417 ) ;
17418
17419 # 1 EXISTS but != 0 == messages() => no deletion
17420 $imapT->mock( 'History', sub { ( q{* 1 EXISTS} ) } ) ;
17421 $imapT->mock( 'messages', sub { [ ] } ) ;
17422 tests_delete1emptyfolders_unit(
17423 $syncT,
17424 [ qw{ INBOX DELME1 DELME2 } ],
17425 [ qw{ INBOX DELME1 DELME2 } ],
17426 q{tests_delete1emptyfolders: 1 EXISTS but 0 by messages()}
17427 ) ;
17428
17429 # 0 EXISTS and 0 == messages() => deletion except INBOX
17430 $imapT->mock( 'History', sub { ( q{* 0 EXISTS} ) } ) ;
17431 $imapT->mock( 'messages', sub { [ ] } ) ;
17432 $imapT->set_true( qw{ delete close unsubscribe } ) ;
17433 $syncT->{dry_message} = q{ (not really since in a mocked test)} ;
17434 tests_delete1emptyfolders_unit(
17435 $syncT,
17436 [ qw{ INBOX DELME1 DELME2 } ],
17437 [ qw{ INBOX } ],
17438 q{tests_delete1emptyfolders: 0 EXISTS 0 by messages() delete folders, keep INBOX}
17439 ) ;
17440
17441 note( 'Leaving tests_delete1emptyfolders()' ) ;
17442 return ;
17443}
17444
17445sub tests_delete1emptyfolders_unit
17446{
17447 note( 'Entering tests_delete1emptyfolders_unit()' ) ;
17448
17449 my $syncT = shift ;
17450 my $folders1wanted_init_ref = shift ;
17451 my $folders1wanted_after_ref = shift ;
17452 my $comment = shift || q{delete1emptyfolders:} ;
17453
17454 my @folders1wanted_init = @{ $folders1wanted_init_ref } ;
17455 my @folders1wanted_after = @{ $folders1wanted_after_ref } ;
17456
17457 @{ $syncT->{h1_folders_wanted} } = @folders1wanted_init ;
17458
17459 is_deeply( $syncT->{h1_folders_wanted}, \@folders1wanted_init, qq{$comment, init check} ) ;
17460 delete1emptyfolders( $syncT ) ;
17461 is_deeply( $syncT->{h1_folders_wanted}, \@folders1wanted_after, qq{$comment, after check} ) ;
17462
17463 note( 'Leaving tests_delete1emptyfolders_unit()' ) ;
17464 return ;
17465}
17466
17467sub extract_header
17468{
17469 my $string = shift ;
17470
17471 my ( $header ) = split /\n\n/x, $string ;
17472 if ( ! $header ) { return( q{} ) ; }
17473 #myprint( "[$header]\n" ) ;
17474 return( $header ) ;
17475}
17476
17477sub tests_extract_header
17478{
17479 note( 'Entering tests_extract_header()' ) ;
17480
17481my $h = <<'EOM';
17482Message-Id: <20100428101817.A66CB162474E@plume.est.belle>
17483Date: Wed, 28 Apr 2010 12:18:17 +0200 (CEST)
17484From: gilles@louloutte.dyndns.org (Gilles LAMIRAL)
17485EOM
17486chomp $h ;
17487ok( $h eq extract_header(
17488<<'EOM'
17489Message-Id: <20100428101817.A66CB162474E@plume.est.belle>
17490Date: Wed, 28 Apr 2010 12:18:17 +0200 (CEST)
17491From: gilles@louloutte.dyndns.org (Gilles LAMIRAL)
17492
17493body
17494lalala
17495EOM
17496), 'extract_header: 1') ;
17497
17498
17499
17500 note( 'Leaving tests_extract_header()' ) ;
17501 return ;
17502}
17503
17504sub decompose_header{
17505 my $string = shift ;
17506
17507 # a hash, for a keyword header KEY value are list of strings [VAL1, VAL1_other, etc]
17508 # Think of multiple "Received:" header lines.
17509 my $header = { } ;
17510
17511 my ($key, $val ) ;
17512 my @line = split /\n|\r\n/x, $string ;
17513 foreach my $line ( @line ) {
17514 #myprint( "DDD $line\n" ) ;
17515 # End of header
17516 last if ( $line =~ m{^$}xo ) ;
17517 # Key: value
17518 if ( $line =~ m/(^[^:]+):\s(.*)/xo ) {
17519 $key = $1 ;
17520 $val = $2 ;
17521 $debugdev and myprint( "DDD KV [$key] [$val]\n" ) ;
17522 push @{ $header->{ $key } }, $val ;
17523 # blanc and value => value from previous line continues
17524 }elsif( $line =~ m/^(\s+)(.*)/xo ) {
17525 $val = $2 ;
17526 $debugdev and myprint( "DDD V [$val]\n" ) ;
17527 @{ $header->{ $key } }[ $LAST ] .= " $val" if $key ;
17528 # dirty line?
17529 }else{
17530 next ;
17531 }
17532 }
17533
17534 #myprint( Data::Dumper->Dump( [ $header ] ) ) ;
17535
17536 return( $header ) ;
17537}
17538
17539
17540sub tests_decompose_header{
17541 note( 'Entering tests_decompose_header()' ) ;
17542
17543
17544 my $header_dec ;
17545
17546 $header_dec = decompose_header(
17547<<'EOH'
17548KEY_1: VAL_1
17549KEY_2: VAL_2
17550 VAL_2_+
17551 VAL_2_++
17552KEY_3: VAL_3
17553KEY_1: VAL_1_other
17554KEY_4: VAL_4
17555 VAL_4_+
17556KEY_5 BLANC: VAL_5
17557
17558KEY_6_BAD_BODY: VAL_6
17559EOH
17560 ) ;
17561
17562 ok( 'VAL_3'
17563 eq $header_dec->{ 'KEY_3' }[0], 'decompose_header: VAL_3' ) ;
17564
17565 ok( 'VAL_1'
17566 eq $header_dec->{ 'KEY_1' }[0], 'decompose_header: VAL_1' ) ;
17567
17568 ok( 'VAL_1_other'
17569 eq $header_dec->{ 'KEY_1' }[1], 'decompose_header: VAL_1_other' ) ;
17570
17571 ok( 'VAL_2 VAL_2_+ VAL_2_++'
17572 eq $header_dec->{ 'KEY_2' }[0], 'decompose_header: VAL_2 VAL_2_+ VAL_2_++' ) ;
17573
17574 ok( 'VAL_4 VAL_4_+'
17575 eq $header_dec->{ 'KEY_4' }[0], 'decompose_header: VAL_4 VAL_4_+' ) ;
17576
17577 ok( ' VAL_5'
17578 eq $header_dec->{ 'KEY_5 BLANC' }[0], 'decompose_header: KEY_5 BLANC' ) ;
17579
17580 ok( not( defined $header_dec->{ 'KEY_6_BAD_BODY' }[0] ), 'decompose_header: KEY_6_BAD_BODY' ) ;
17581
17582
17583 $header_dec = decompose_header(
17584<<'EOH'
17585Message-Id: <20100428101817.A66CB162474E@plume.est.belle>
17586Date: Wed, 28 Apr 2010 12:18:17 +0200 (CEST)
17587From: gilles@louloutte.dyndns.org (Gilles LAMIRAL)
17588EOH
17589 ) ;
17590
17591 ok( '<20100428101817.A66CB162474E@plume.est.belle>'
17592 eq $header_dec->{ 'Message-Id' }[0], 'decompose_header: 1' ) ;
17593
17594 $header_dec = decompose_header(
17595<<'EOH'
17596Return-Path: <gilles@louloutte.dyndns.org>
17597Received: by plume.est.belle (Postfix, from userid 1000)
17598 id 120A71624742; Wed, 28 Apr 2010 01:46:40 +0200 (CEST)
17599Subject: test:eekahceishukohpe
17600EOH
17601) ;
17602 ok(
17603'by plume.est.belle (Postfix, from userid 1000) id 120A71624742; Wed, 28 Apr 2010 01:46:40 +0200 (CEST)'
17604 eq $header_dec->{ 'Received' }[0], 'decompose_header: 2' ) ;
17605
17606 $header_dec = decompose_header(
17607<<'EOH'
17608Received: from plume (localhost [127.0.0.1])
17609 by plume.est.belle (Postfix) with ESMTP id C6EB73F6C9
17610 for <gilles@localhost>; Mon, 26 Nov 2007 10:39:06 +0100 (CET)
17611Received: from plume [192.168.68.7]
17612 by plume with POP3 (fetchmail-6.3.6)
17613 for <gilles@localhost> (single-drop); Mon, 26 Nov 2007 10:39:06 +0100 (CET)
17614EOH
17615 ) ;
17616 ok(
17617 '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)'
17618 eq $header_dec->{ 'Received' }[0], 'decompose_header: 3' ) ;
17619 ok(
17620 '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)'
17621 eq $header_dec->{ 'Received' }[1], 'decompose_header: 3' ) ;
17622
17623# Bad header beginning with a blank character
17624 $header_dec = decompose_header(
17625<<'EOH'
17626 KEY_1: VAL_1
17627KEY_2: VAL_2
17628 VAL_2_+
17629 VAL_2_++
17630KEY_3: VAL_3
17631KEY_1: VAL_1_other
17632EOH
17633 ) ;
17634
17635 ok( 'VAL_3'
17636 eq $header_dec->{ 'KEY_3' }[0], 'decompose_header: Bad header VAL_3' ) ;
17637
17638 ok( 'VAL_1_other'
17639 eq $header_dec->{ 'KEY_1' }[0], 'decompose_header: Bad header VAL_1_other' ) ;
17640
17641 ok( 'VAL_2 VAL_2_+ VAL_2_++'
17642 eq $header_dec->{ 'KEY_2' }[0], 'decompose_header: Bad header VAL_2 VAL_2_+ VAL_2_++' ) ;
17643
17644 note( 'Leaving tests_decompose_header()' ) ;
17645 return ;
17646}
17647
17648sub tests_epoch
17649{
17650 note( 'Entering tests_epoch()' ) ;
17651
17652 ok( '1282658400' eq epoch( '24-Aug-2010 16:00:00 +0200' ), 'epoch 24-Aug-2010 16:00:00 +0200 -> 1282658400' ) ;
17653 ok( '1282658400' eq epoch( '24-Aug-2010 14:00:00 +0000' ), 'epoch 24-Aug-2010 14:00:00 +0000 -> 1282658400' ) ;
17654 ok( '1282658400' eq epoch( '24-Aug-2010 12:00:00 -0200' ), 'epoch 24-Aug-2010 12:00:00 -0200 -> 1282658400' ) ;
17655 ok( '1282658400' eq epoch( '24-Aug-2010 16:01:00 +0201' ), 'epoch 24-Aug-2010 16:01:00 +0201 -> 1282658400' ) ;
17656 ok( '1282658400' eq epoch( '24-Aug-2010 14:01:00 +0001' ), 'epoch 24-Aug-2010 14:01:00 +0001 -> 1282658400' ) ;
17657
17658 ok( '1280671200' eq epoch( '1-Aug-2010 16:00:00 +0200' ), 'epoch 1-Aug-2010 16:00:00 +0200 -> 1280671200' ) ;
17659 ok( '1280671200' eq epoch( '1-Aug-2010 14:00:00 +0000' ), 'epoch 1-Aug-2010 14:00:00 +0000 -> 1280671200' ) ;
17660 ok( '1280671200' eq epoch( '1-Aug-2010 12:00:00 -0200' ), 'epoch 1-Aug-2010 12:00:00 -0200 -> 1280671200' ) ;
17661 ok( '1280671200' eq epoch( '1-Aug-2010 16:01:00 +0201' ), 'epoch 1-Aug-2010 16:01:00 +0201 -> 1280671200' ) ;
17662 ok( '1280671200' eq epoch( '1-Aug-2010 14:01:00 +0001' ), 'epoch 1-Aug-2010 14:01:00 +0001 -> 1280671200' ) ;
17663
17664 is( '1280671200', epoch( '1-Aug-2010 14:01:00 +0001' ), 'epoch 1-Aug-2010 14:01:00 +0001 -> 1280671200' ) ;
17665 is( '946684800', epoch( '00-Jan-0000 00:00:00 +0000' ), 'epoch 1-Aug-2010 14:01:00 +0001 -> 1280671200' ) ;
17666
17667 note( 'Leaving tests_epoch()' ) ;
17668 return ;
17669}
17670
17671sub epoch
17672{
17673 # incoming format:
17674 # internal date 24-Aug-2010 16:00:00 +0200
17675
17676 # outgoing format: epoch
17677
17678
17679 my $d = shift ;
17680 return(q{}) if not defined $d;
17681
17682 my ( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m ) ;
17683 my $time ;
17684
17685 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 ) {
17686 #myprint( "internal: [$1][$2][$3][$4][$5][$6][$7][$8][$9]\n" ) ;
17687 ( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m )
17688 = ( $1, $2, $3, $4, $5, $6, $7, $8, $9 ) ;
17689 #myprint( "( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m )\n" ) ;
17690
17691 $sign = +1 if ( '+' eq $sign ) ;
17692 $sign = $MINUS_ONE if ( '-' eq $sign ) ;
17693
17694 if ( 0 == $mday ) {
17695 myprint( "buggy day in $d. Fixed to 01\n" ) ;
17696 $mday = '01' ;
17697 }
17698 $time = timegm( $sec, $min, $hour, $mday, $month_abrev{$month}, $year )
17699 - $sign * ( 3600 * $zone_h + 60 * $zone_m ) ;
17700
17701 #myprint( "$time ", scalar localtime($time), "\n");
17702 }
17703 return( $time ) ;
17704}
17705
17706sub tests_add_header
17707{
17708 note( 'Entering tests_add_header()' ) ;
17709
17710 ok( 'Message-Id: <mistake@imapsync>' eq add_header(), 'add_header no arg' ) ;
17711 ok( 'Message-Id: <123456789@imapsync>' eq add_header( '123456789' ), 'add_header 123456789' ) ;
17712
17713 note( 'Leaving tests_add_header()' ) ;
17714 return ;
17715}
17716
17717sub add_header
17718{
17719 my $header_uid = shift || 'mistake' ;
17720 my $header_Message_Id = 'Message-Id: <' . $header_uid . '@imapsync>' ;
17721 return( $header_Message_Id ) ;
17722}
17723
17724
17725
17726
17727sub tests_max_line_length
17728{
17729 note( 'Entering tests_max_line_length()' ) ;
17730
17731 ok( 0 == max_line_length( q{} ), 'max_line_length: 0 == null string' ) ;
17732 ok( 1 == max_line_length( "\n" ), 'max_line_length: 1 == \n' ) ;
17733 ok( 1 == max_line_length( "\n\n" ), 'max_line_length: 1 == \n\n' ) ;
17734 ok( 1 == max_line_length( "\n" x 500 ), 'max_line_length: 1 == 500 \n' ) ;
17735 ok( 1 == max_line_length( 'a' ), 'max_line_length: 1 == a' ) ;
17736 ok( 2 == max_line_length( "a\na" ), 'max_line_length: 2 == a\na' ) ;
17737 ok( 2 == max_line_length( "a\na\n" ), 'max_line_length: 2 == a\na\n' ) ;
17738 ok( 3 == max_line_length( "a\nab\n" ), 'max_line_length: 3 == a\nab\n' ) ;
17739 ok( 3 == max_line_length( "a\nab\n" x 1_000 ), 'max_line_length: 3 == 1_000 a\nab\n' ) ;
17740 ok( 3 == max_line_length( "a\nab\nabc" ), 'max_line_length: 3 == a\nab\nabc' ) ;
17741
17742 ok( 4 == max_line_length( "a\nab\nabc\n" ), 'max_line_length: 4 == a\nab\nabc\n' ) ;
17743 ok( 5 == max_line_length( "a\nabcd\nabc\n" ), 'max_line_length: 5 == a\nabcd\nabc\n' ) ;
17744 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' ) ;
17745
17746 note( 'Leaving tests_max_line_length()' ) ;
17747 return ;
17748}
17749
17750sub max_line_length
17751{
17752 my $string = shift ;
17753 my $max = 0 ;
17754
17755 while ( $string =~ m/([^\n]*\n?)/msxg ) {
17756 $max = max( $max, length $1 ) ;
17757 }
17758 return( $max ) ;
17759}
17760
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010017761sub set_checknoabletosearch
17762{
17763 my $mysync = shift @ARG ;
17764 if ( defined $mysync->{ checknoabletosearch } )
17765 {
17766 return ;
17767 }
17768 elsif ( $mysync->{ justfolders } )
17769 {
17770 $mysync->{ checknoabletosearch } = 0 ;
17771 }
17772 else
17773 {
17774 $mysync->{ checknoabletosearch } = 1 ;
17775 }
17776 return ;
17777}
17778
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017779
17780sub tests_setlogfile
17781{
17782 note( 'Entering tests_setlogfile()' ) ;
17783
17784 my $mysync = {} ;
17785 $mysync->{logdir} = 'vallogdir' ;
17786 $mysync->{logfile} = 'vallogfile.txt' ;
17787 is( 'vallogdir/vallogfile.txt', setlogfile( $mysync ),
17788 'setlogfile: logdir vallogdir, logfile vallogfile.txt, vallogdir/vallogfile.txt' ) ;
17789
17790 SKIP: {
17791 skip( 'Too hard to have a well known timezone on Windows', 9 ) if ( 'MSWin32' eq $OSNAME ) ;
17792
17793 local $ENV{TZ} = 'GMT' ;
17794
17795 $mysync = {
17796 timestart => 2,
17797 } ;
17798
17799 is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000__.txt", setlogfile( $mysync ),
17800 "setlogfile: default is like $DEFAULT_LOGDIR/1970_01_01_00_00_02_000__.txt" ) ;
17801
17802 $mysync = {
17803 timestart => 2,
17804 user1 => 'user1',
17805 user2 => 'user2',
17806 abort => 1,
17807 } ;
17808
17809 is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_abort.txt", setlogfile( $mysync ),
17810 "setlogfile: default is like $DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_abort.txt" ) ;
17811
17812 $mysync = {
17813 timestart => 2,
17814 user1 => 'user1',
17815 user2 => 'user2',
17816 remote => 'zzz',
17817 } ;
17818
17819 is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_remote.txt", setlogfile( $mysync ),
17820 "setlogfile: default is like $DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_remote.txt" ) ;
17821
17822 $mysync = {
17823 timestart => 2,
17824 user1 => 'user1',
17825 user2 => 'user2',
17826 remote => 'zzz',
17827 abort => 1,
17828 } ;
17829
17830 is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_remote_abort.txt", setlogfile( $mysync ),
17831 "setlogfile: default is like $DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_remote_abort.txt" ) ;
17832
17833
17834 $mysync = {
17835 timestart => 2,
17836 user1 => 'user1',
17837 user2 => 'user2',
17838 } ;
17839
17840 is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2.txt", setlogfile( $mysync ),
17841 "setlogfile: default is like $DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2.txt" ) ;
17842
17843 $mysync->{logdir} = undef ;
17844 $mysync->{logfile} = undef ;
17845 is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2.txt", setlogfile( $mysync ),
17846 "setlogfile: logdir undef, $DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2.txt" ) ;
17847
17848 $mysync->{logdir} = q{} ;
17849 $mysync->{logfile} = undef ;
17850 is( '1970_01_01_00_00_02_000_user1_user2.txt', setlogfile( $mysync ),
17851 'setlogfile: logdir empty, 1970_01_01_00_00_02_000_user1_user2.txt' ) ;
17852
17853 $mysync->{logdir} = 'vallogdir' ;
17854 $mysync->{logfile} = undef ;
17855 is( 'vallogdir/1970_01_01_00_00_02_000_user1_user2.txt', setlogfile( $mysync ),
17856 'setlogfile: logdir vallogdir, vallogdir/1970_01_01_00_00_02_000_user1_user2.txt' ) ;
17857
17858 $mysync = {
17859 user1 => 'us/er1a*|?:"<>b',
17860 user2 => 'u/ser2a*|?:"<>b',
17861 } ;
17862
17863 is( "$DEFAULT_LOGDIR/1970_01_01_00_00_00_000_us_er1a_______b_u_ser2a_______b.txt", setlogfile( $mysync ),
17864 "setlogfile: logdir undef, $DEFAULT_LOGDIR/1970_01_01_00_00_00_000_us_er1a_______b_u_ser2a_______b.txt" ) ;
17865
17866
17867
17868 } ;
17869
17870 note( 'Leaving tests_setlogfile()' ) ;
17871 return ;
17872}
17873
17874sub setlogfile
17875{
17876 my( $mysync ) = shift ;
17877
17878 # When aborting another process the log file name finishes with "_abort.txt"
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017879 my $abort_suffix = ( $mysync->{ abort } ) ? '_abort' : q{} ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010017880
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017881 # When acting as a proxy the log file name finishes with "_remote.txt"
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017882 # proxy mode is not done in imapsync, it is done by proximapsync
17883 my $remote_suffix = ( $mysync->{ remote } ) ? '_remote' : q{} ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017884
17885 my $suffix = (
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017886 filter_forbidden_characters( slash_to_underscore( $mysync->{ user1 } ) ) || q{} )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017887 . '_'
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017888 . ( filter_forbidden_characters( slash_to_underscore( $mysync->{ user2 } ) ) || q{} )
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017889 . $remote_suffix . $abort_suffix ;
17890
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017891 $mysync->{ logdir } = defined $mysync->{ logdir } ? $mysync->{ logdir } : $DEFAULT_LOGDIR ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017892
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017893 $mysync->{ logfile } = defined $mysync->{ logfile }
17894 ? "$mysync->{ logdir }/$mysync->{ logfile }"
17895 : logfile( $mysync->{ timestart }, $suffix, $mysync->{ logdir } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017896
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017897 return( $mysync->{ logfile } ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017898}
17899
17900sub tests_logfile
17901{
17902 note( 'Entering tests_logfile()' ) ;
17903
17904 SKIP: {
17905 # Too hard to have a well known timezone on Windows
17906 skip( 'Too hard to have a well known timezone on Windows', 10 ) if ( 'MSWin32' eq $OSNAME ) ;
17907
17908 local $ENV{TZ} = 'GMT' ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017909 {
17910 POSIX::tzset unless ('MSWin32' eq $OSNAME) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017911 is( '1970_01_01_00_00_00_000.txt', logfile( ), 'logfile: no args => 1970_01_01_00_00_00.txt' ) ;
17912 is( '1970_01_01_00_00_00_000.txt', logfile( 0 ), 'logfile: 0 => 1970_01_01_00_00_00.txt' ) ;
17913 is( '1970_01_01_00_01_01_000.txt', logfile( 61 ), 'logfile: 0 => 1970_01_01_00_01_01.txt' ) ;
17914 is( '1970_01_01_00_01_01_234.txt', logfile( 61.234 ), 'logfile: 0 => 1970_01_01_00_01_01.txt' ) ;
17915 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' ) ;
17916 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' ) ;
17917 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' ) ;
17918 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' ) ;
17919
17920 is( '2010_08_24_14_01_01_234_poup.txt', logfile( 1_282_658_461.2347, 'poup' ),
17921 'logfile: 1_282_658_461.2347 poup => 2010_08_24_14_01_01_234_poup.txt' ) ;
17922
17923 is( 'dirdir/2010_08_24_14_01_01_234_poup.txt', logfile( 1_282_658_461.2347, 'poup', 'dirdir' ),
17924 'logfile: 1_282_658_461.2347 poup dirdir => dirdir/2010_08_24_14_01_01_234_poup.txt' ) ;
17925
17926
17927
17928 }
17929 POSIX::tzset unless ('MSWin32' eq $OSNAME) ;
17930 } ;
17931
17932 note( 'Leaving tests_logfile()' ) ;
17933 return ;
17934}
17935
17936
17937sub logfile
17938{
17939 my ( $time, $suffix, $dir ) = @_ ;
17940
17941 $time ||= 0 ;
17942 $suffix ||= q{} ;
17943 $suffix =~ tr/ //ds ;
17944 my $sep_suffix = ( $suffix ) ? '_' : q{} ;
17945 $dir ||= q{} ;
17946 my $sep_dir = ( $dir ) ? '/' : q{} ;
17947
17948 my $date_str = POSIX::strftime( '%Y_%m_%d_%H_%M_%S', localtime $time ) ;
17949 # Because of ab tests or web accesses, more than one sync withing one second is possible
17950 # so we add also milliseconds
17951 $date_str .= sprintf "_%03d", ($time - int( $time ) ) * 1000 ; # without rounding
17952 my $logfile = "${dir}${sep_dir}${date_str}${sep_suffix}${suffix}.txt" ;
17953 return( $logfile ) ;
17954}
17955
17956
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017957sub tests_localtimez
17958{
17959 note( 'Entering tests_localtimez()' ) ;
17960
17961 SKIP: {
17962 # Too hard to have a well known timezone on Windows
17963 skip( 'Too hard to have a well known timezone on Windows', 1 ) if ( 'MSWin32' eq $OSNAME ) ;
17964 local $ENV{TZ} = 'GMT' ;
17965 like( localtimez( 0 ), qr'1970-01-01 00:00:00 \+0000 (GMT|UTC)', 'localtimez: 0 => match 1970-01-01 00:00:00 +0000 GMT' ) ;
17966 }
17967
17968 is( localtimez( ), localtimez( time ), 'localtimez: undef => equals currrent' ) ;
17969 note( 'Leaving tests_localtimez()' ) ;
17970 return ;
17971}
17972
17973
17974
17975sub localtimez
17976{
17977 my $time = shift ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010017978
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017979 $time = defined( $time ) ? $time : time ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010017980
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017981 my $datetimestr = POSIX::strftime( '%A %e %B %Y-%m-%d %H:%M:%S %z %Z', localtime( $time ) ) ;
17982
17983 #myprint( "$datetimestr\n" ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010017984 return $datetimestr ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020017985}
17986
17987
17988
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010017989
17990sub tests_slash_to_underscore
17991{
17992 note( 'Entering tests_slash_to_underscore()' ) ;
17993
17994 is( undef, slash_to_underscore( ), 'slash_to_underscore: no parameters => undef' ) ;
17995 is( '_', slash_to_underscore( '/' ), 'slash_to_underscore: / => _' ) ;
17996 is( '_abc_def_', slash_to_underscore( '/abc/def/' ), 'slash_to_underscore: /abc/def/ => _abc_def_' ) ;
17997 note( 'Leaving tests_slash_to_underscore()' ) ;
17998 return ;
17999}
18000
18001sub slash_to_underscore
18002{
18003 my $string = shift ;
18004
18005 if ( ! defined $string ) { return ; }
18006
18007 $string =~ tr{/}{_} ;
18008
18009 return( $string ) ;
18010}
18011
18012
18013
18014
18015sub tests_million_folders_baby_2
18016{
18017 note( 'Entering tests_million_folders_baby_2()' ) ;
18018
18019 my %long ;
18020 @long{ 1 .. 900_000 } = (1) x 900_000 ;
18021 #myprint( %long, "\n" ) ;
18022 my $pasglop = 0 ;
18023 foreach my $elem ( 1 .. 900_000 ) {
18024 #$debug and myprint( "$elem " ) ;
18025 if ( not exists $long{ $elem } ) {
18026 $pasglop++ ;
18027 }
18028 }
18029 ok( 0 == $pasglop, 'tests_million_folders_baby_2: search among 900_000' ) ;
18030 # myprint( "$pasglop\n" ) ;
18031
18032 note( 'Leaving tests_million_folders_baby_2()' ) ;
18033 return ;
18034}
18035
18036
18037
18038sub tests_always_fail
18039{
18040 note( 'Entering tests_always_fail()' ) ;
18041
18042 is( 0, 1, 'always_fail: 0 is 1' ) ;
18043
18044 note( 'Leaving tests_always_fail()' ) ;
18045 return ;
18046}
18047
18048
18049sub tests_logfileprepa
18050{
18051 note( 'Entering tests_logfileprepa()' ) ;
18052
18053 is( undef, logfileprepa( ), 'logfileprepa: no args => undef' ) ;
18054 my $logfile = 'W/tmp/tests/tests_logfileprepa.txt' ;
18055 is( 1, logfileprepa( $logfile ), 'logfileprepa: W/tmp/tests/tests_logfileprepa.txt => 1' ) ;
18056
18057 note( 'Leaving tests_logfileprepa()' ) ;
18058 return ;
18059}
18060
18061sub logfileprepa
18062{
18063 my $logfile = shift ;
18064
18065 if ( ! defined( $logfile ) )
18066 {
18067 return ;
18068 }else
18069 {
18070 #myprint( "[$logfile]\n" ) ;
18071 my $dirname = dirname( $logfile ) ;
18072 do_valid_directory( $dirname ) || return( 0 ) ;
18073 return( 1 ) ;
18074 }
18075}
18076
18077
18078sub tests_teelaunch
18079{
18080 note( 'Entering tests_teelaunch()' ) ;
18081
18082 is( undef, teelaunch( ), 'teelaunch: no args => undef' ) ;
18083 my $mysync = {} ;
18084 is( undef, teelaunch( $mysync ), 'teelaunch: arg empty {} => undef' ) ;
18085 $mysync->{logfile} = q{} ;
18086 is( undef, teelaunch( $mysync ), 'teelaunch: logfile empty string => undef' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018087
18088 # First time, learning IO::Tee intrasics
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018089 $mysync->{logfile} = 'W/tmp/tests/tests_teelaunch.txt' ;
18090 isa_ok( my $tee = teelaunch( $mysync ), 'IO::Tee' , 'teelaunch: logfile W/tmp/tests/tests_teelaunch.txt' ) ;
18091 is( 1, print( $tee "Hi!\n" ), 'teelaunch: write Hi!') ;
18092 is( "Hi!\n", file_to_string( 'W/tmp/tests/tests_teelaunch.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch.txt is Hi!\n' ) ;
18093 is( 1, print( $tee "Hoo\n" ), 'teelaunch: write Hoo') ;
18094 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' ) ;
18095
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018096 # closing so tee won't be happy
18097 close $mysync->{logfile_handle} ;
18098 is( undef, print( $tee "Argh1\n" ), 'teelaunch: write Argh1') ;
18099 is( undef, print( $tee "Argh2\n" ), 'teelaunch: write Argh2') ;
18100 # write not done
18101 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' ) ;
18102 print join( ' ', $tee->handles ), "\n";
18103 is( 2, scalar $tee->handles, 'teelaunch: 2 handles') ;
18104 shift @{*{$tee}};
18105 print join(' ', $tee->handles), "\n" ;
18106 is( 1, scalar $tee->handles, 'teelaunch: 1 handle') ;
18107 is( 1, print( $tee "Argh3\n" ), 'teelaunch: write Argh3 yeah') ;
18108
18109 shift @{*{$tee}};
18110 # will not print anything now
18111 is( 0, scalar $tee->handles, 'teelaunch: 0 handle') ;
18112 is( 1, print( $tee "Argh 4\n" ), 'teelaunch: write Argh4 no') ;
18113
18114 # Second time, lesson learnt IO::Tee
18115 $mysync->{logfile} = 'W/tmp/tests/tests_teelaunch2.txt' ;
18116 isa_ok( $tee = teelaunch( $mysync ), 'IO::Tee' , 'teelaunch: logfile W/tmp/tests/tests_teelaunch2.txt' ) ;
18117 is( 1, print( $tee "Hi!\n" ), 'teelaunch: write Hi!') ;
18118 is( "Hi!\n", file_to_string( 'W/tmp/tests/tests_teelaunch2.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch2.txt is Hi!\n' ) ;
18119 is( 1, print( $tee "Hoo\n" ), 'teelaunch: write Hoo') ;
18120 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' ) ;
18121
18122 is( 1, teefinish( $mysync ), 'teefinish: return 1') ;
18123 is( 1, print( $tee "Argh1\n" ), 'teelaunch: write Argh1') ;
18124 is( 1, print( $tee "Argh2\n" ), 'teelaunch: write Argh2') ;
18125 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' ) ;
18126 is( 1, teefinish( $mysync ), 'teefinish: still return 1') ;
18127
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018128 note( 'Leaving tests_teelaunch()' ) ;
18129 return ;
18130}
18131
18132sub teelaunch
18133{
18134 my $mysync = shift ;
18135
18136 if ( ! defined( $mysync ) )
18137 {
18138 return ;
18139 }
18140
18141 my $logfile = $mysync->{logfile} ;
18142
18143 if ( ! $logfile )
18144 {
18145 return ;
18146 }
18147
18148 logfileprepa( $logfile ) || croak "Error no valid directory to write log file $logfile : $OS_ERROR" ;
18149
18150 # This is a log file opened during the whole sync
18151 ## no critic (InputOutput::RequireBriefOpen)
18152 open my $logfile_handle, '>', $logfile
18153 or croak( "Can not open $logfile for write: $OS_ERROR" ) ;
18154 binmode $logfile_handle, ":encoding(UTF-8)" ;
18155 my $tee = IO::Tee->new( $logfile_handle, \*STDOUT ) ;
18156 $tee->autoflush( 1 ) ;
18157 $mysync->{logfile_handle} = $logfile_handle ;
18158 $mysync->{tee} = $tee ;
18159 return $tee ;
18160}
18161
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018162sub teefinish
18163{
18164 my $mysync = shift ;
18165
18166 if ( ! defined( $mysync ) ) { return ; }
18167
18168 my $tee = $mysync->{tee} ;
18169
18170 if ( ! defined( $tee ) ) { return ; }
18171
18172 if ( 2 == scalar $tee->handles )
18173 {
18174 shift @{*{$tee}};
18175 }
18176 else
18177 {
18178 # nothing
18179 }
18180 return scalar $tee->handles ;
18181}
18182
18183
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018184sub getpwuid_any_os
18185{
18186 my $uid = shift ;
18187
18188 return( scalar getlogin ) if ( 'MSWin32' eq $OSNAME ) ; # Windows system
18189 return( scalar getpwuid $uid ) ; # Unix system
18190
18191
18192}
18193
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018194
18195
18196sub abortifneeded
18197{
18198 my $mysync = shift ;
18199 if ( -e $mysync->{ abortfile } )
18200 {
18201 myprint( "Asked to terminate by file $mysync->{ abortfile }\n" ) ;
18202 do_and_print_stats( $mysync ) ;
18203 myprint( "You should resynchronize those accounts by running a sync again,\n",
18204 "since some messages and entire folders might still be missing on host2.\n"
18205 ) ;
18206 exit_clean( $mysync, $EXIT_BY_FILE ) ;
18207 return ;
18208 }
18209 else
18210 {
18211 return ;
18212 }
18213}
18214
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018215sub simulong
18216{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018217 my $mysync = shift ;
18218
18219 my $max_seconds = $mysync->{ simulong } ;
18220
18221 if ( ! $max_seconds ) { return ; }
18222
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018223 my $division = 5 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018224 my $last_count = int( $division * $max_seconds ) ;
18225 $mysync->{ debug } and myprint "last_count $last_count = int( division $division * max_seconds $max_seconds)\n" ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018226 foreach my $i ( 1 .. ( $last_count ) ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018227 myprint( "Are you still here ETA: " . ( $last_count - $i ) . "/$last_count msgs left\n" ) ;
18228 #this one is for testing huge page behavior
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018229 #myprint( "Are you still here ETA: " . ($last_count - $i) . "/$last_count msgs left\n" . ( "Ah" x 40 . "\n") x 4000 ) ;
18230 sleep( 1 / $division ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018231 abortifneeded( $mysync ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018232 }
18233
18234 return ;
18235}
18236
18237
18238
18239sub printenv
18240{
18241 myprint( "Environment variables listing:\n",
18242 ( map { "$_ => $ENV{$_}\n" } sort keys %ENV),
18243 "Environment variables listing end\n" ) ;
18244 return ;
18245}
18246
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018247
18248sub unittestssuite
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018249{
18250 my $mysync = shift ;
18251 if ( ! ( $mysync->{ tests } or $mysync->{ testsdebug } or $mysync->{ testsunit } ) ) {
18252 return ;
18253 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018254
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018255 my $test_builder = Test::More->builder ;
18256 tests( $mysync ) ;
18257 testsdebug( $mysync ) ;
18258 testunitsession( $mysync ) ;
18259
18260 my @summary = $test_builder->summary() ;
18261 my @details = $test_builder->details() ;
18262 my $nb_tests_run = scalar( @summary ) ;
18263 my $nb_tests_expected = $test_builder->expected_tests() ;
18264 my $nb_tests_failed = count_0s( @summary ) ;
18265 my $tests_failed = report_failures( @details ) ;
18266 if ( $nb_tests_failed or ( $nb_tests_run != $nb_tests_expected ) ) {
18267 #$test_builder->reset( ) ;
18268 myprint( "Summary of tests: failed $nb_tests_failed tests, run $nb_tests_run tests, expected to run $nb_tests_expected tests.\n",
18269 "List of failed tests:\n", $tests_failed ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018270 return $EXIT_TESTS_FAILED ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018271 }
18272
18273 cleanup_mess_from_tests( ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018274
18275 return 0 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018276}
18277
18278sub cleanup_mess_from_tests
18279{
18280 undef @pipemess ;
18281 return ;
18282}
18283
18284sub after_get_options
18285{
18286 my $mysync = shift ;
18287 my $numopt = shift ;
18288
18289
18290 # exit with --help option or no option at all
18291 $mysync->{ debug } and myprint( "numopt:$numopt\n" ) ;
18292
18293 if ( $help or not $numopt ) {
18294 myprint( usage( $mysync ) ) ;
18295 exit ;
18296 }
18297
18298 return ;
18299}
18300
18301sub tests_remove_edging_blanks
18302{
18303 note( 'Entering tests_remove_edging_blanks()' ) ;
18304
18305 is( undef, remove_edging_blanks( ), 'remove_edging_blanks: no args => undef' ) ;
18306 is( 'abcd', remove_edging_blanks( 'abcd' ), 'remove_edging_blanks: abcd => abcd' ) ;
18307 is( 'ab cd', remove_edging_blanks( ' ab cd ' ), 'remove_edging_blanks: " ab cd " => "ab cd"' ) ;
18308
18309 note( 'Leaving tests_remove_edging_blanks()' ) ;
18310 return ;
18311}
18312
18313
18314
18315sub remove_edging_blanks
18316{
18317 my $string = shift ;
18318 if ( ! defined $string )
18319 {
18320 return ;
18321 }
18322 $string =~ s,^ +| +$,,g ;
18323 return $string ;
18324}
18325
18326
18327sub tests_sanitize
18328{
18329 note( 'Entering tests_remove_edging_blanks()' ) ;
18330
18331 is( undef, sanitize( ), 'sanitize: no args => undef' ) ;
18332 my $mysync = {} ;
18333
18334 $mysync->{ host1 } = ' example.com ' ;
18335 $mysync->{ user1 } = ' to to ' ;
18336 $mysync->{ password1 } = ' sex is good! ' ;
18337 is( undef, sanitize( $mysync ), 'sanitize: => undef' ) ;
18338 is( 'example.com', $mysync->{ host1 }, 'sanitize: host1 " example.com " => "example.com"' ) ;
18339 is( 'to to', $mysync->{ user1 }, 'sanitize: user1 " to to " => "to to"' ) ;
18340 is( 'sex is good!', $mysync->{ password1 }, 'sanitize: password1 " sex is good! " => "sex is good!"' ) ;
18341 note( 'Leaving tests_remove_edging_blanks()' ) ;
18342 return ;
18343}
18344
18345
18346sub sanitize
18347{
18348 my $mysync = shift ;
18349 if ( ! defined $mysync )
18350 {
18351 return ;
18352 }
18353
18354 foreach my $parameter ( qw( host1 host2 user1 user2 password1 password2 ) )
18355 {
18356 $mysync->{ $parameter } = remove_edging_blanks( $mysync->{ $parameter } ) ;
18357 }
18358 return ;
18359}
18360
18361sub easyany
18362{
18363 my $mysync = shift ;
18364
18365 # Gmail
18366 if ( $mysync->{gmail1} and $mysync->{gmail2} ) {
18367 $mysync->{ debug } and myprint( "gmail1 gmail2\n") ;
18368 gmail12( $mysync ) ;
18369 return ;
18370 }
18371 if ( $mysync->{gmail1} ) {
18372 $mysync->{ debug } and myprint( "gmail1\n" ) ;
18373 gmail1( $mysync ) ;
18374 }
18375 if ( $mysync->{gmail2} ) {
18376 $mysync->{ debug } and myprint( "gmail2\n" ) ;
18377 gmail2( $mysync ) ;
18378 }
18379 # Office 365
18380 if ( $mysync->{office1} ) {
18381 office1( $mysync ) ;
18382 }
18383
18384 if ( $mysync->{office2} ) {
18385 office2( $mysync ) ;
18386 }
18387
18388 # Exchange
18389 if ( $mysync->{exchange1} ) {
18390 exchange1( $mysync ) ;
18391 }
18392
18393 if ( $mysync->{exchange2} ) {
18394 exchange2( $mysync ) ;
18395 }
18396
18397
18398 # Domino
18399 if ( $mysync->{domino1} ) {
18400 domino1( $mysync ) ;
18401 }
18402
18403 if ( $mysync->{domino2} ) {
18404 domino2( $mysync ) ;
18405 }
18406
18407 return ;
18408}
18409
18410# From and for https://imapsync.lamiral.info/FAQ.d/FAQ.Gmail.txt
18411sub gmail12
18412{
18413 my $mysync = shift ;
18414 # Gmail at host1 and host2
18415 $mysync->{host1} ||= 'imap.gmail.com' ;
18416 $mysync->{ssl1} = ( defined $mysync->{ssl1} ) ? $mysync->{ssl1} : 1 ;
18417 $mysync->{host2} ||= 'imap.gmail.com' ;
18418 $mysync->{ssl2} = ( defined $mysync->{ssl2} ) ? $mysync->{ssl2} : 1 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018419 $mysync->{maxbytespersecond} ||= 20_000 ; # should be less than 10_000 when computed from Gmail documentation
18420 $mysync->{maxbytesafter} ||= 1_000_000_000 ; # In fact it is documented as half: 500_000_000
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018421 $mysync->{automap} = ( defined $mysync->{automap} ) ? $mysync->{automap} : 1 ;
18422 $mysync->{maxsleep} = ( defined $mysync->{maxsleep} ) ? $mysync->{maxsleep} : $MAX_SLEEP ; ;
18423 $skipcrossduplicates = ( defined $skipcrossduplicates ) ? $skipcrossduplicates : 0 ;
18424 $mysync->{ synclabels } = ( defined $mysync->{ synclabels } ) ? $mysync->{ synclabels } : 1 ;
18425 $mysync->{ resynclabels } = ( defined $mysync->{ resynclabels } ) ? $mysync->{ resynclabels } : 1 ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010018426 push @useheader, 'X-Gmail-Received', 'Message-Id' ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018427 push @exclude, '\[Gmail\]$' ;
18428 push @folderlast, '[Gmail]/All Mail' ;
18429 return ;
18430}
18431
18432
18433sub gmail1
18434{
18435 my $mysync = shift ;
18436 # Gmail at host2
18437 $mysync->{host1} ||= 'imap.gmail.com' ;
18438 $mysync->{ssl1} = ( defined $mysync->{ssl1} ) ? $mysync->{ssl1} : 1 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018439 $mysync->{maxbytespersecond} ||= 40_000 ; # should be 30_000 computed from by Gmail documentation
18440 $mysync->{maxbytesafter} ||= 3_000_000_000 ; #
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018441 $mysync->{automap} = ( defined $mysync->{automap} ) ? $mysync->{automap} : 1 ;
18442 $mysync->{maxsleep} = ( defined $mysync->{maxsleep} ) ? $mysync->{maxsleep} : $MAX_SLEEP ; ;
18443 $skipcrossduplicates = ( defined $skipcrossduplicates ) ? $skipcrossduplicates : 1 ;
18444
18445 push @useheader, 'X-Gmail-Received', 'Message-Id' ;
18446 push @{ $mysync->{ regextrans2 } }, 's,\[Gmail\].,,' ;
18447 push @folderlast, '[Gmail]/All Mail' ;
18448 return ;
18449}
18450
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010018451sub gmail2
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018452{
18453 my $mysync = shift ;
18454 # Gmail at host2
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018455 $mysync->{ host2 } ||= 'imap.gmail.com' ;
18456 $mysync->{ ssl2 } = ( defined $mysync->{ ssl2 } ) ? $mysync->{ ssl2 } : 1 ;
18457 $mysync->{ maxbytespersecond } ||= 20_000 ; # should be less than 10_000 computed from by Gmail documentation
18458 $mysync->{ maxbytesafter } ||= 1_000_000_000 ; # In fact it is documented as half: 500_000_000
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018459
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018460 $mysync->{ automap } = ( defined $mysync->{ automap } ) ? $mysync->{ automap } : 1 ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018461 #$skipcrossduplicates = ( defined $skipcrossduplicates ) ? $skipcrossduplicates : 1 ;
18462 $mysync->{ expunge1 } = ( defined $mysync->{ expunge1 } ) ? $mysync->{ expunge1 } : 1 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018463 $mysync->{ addheader } = ( defined $mysync->{ addheader } ) ? $mysync->{ addheader } : 1 ;
18464 $mysync->{ maxsleep } = ( defined $mysync->{ maxsleep } ) ? $mysync->{ maxsleep } : $MAX_SLEEP ; ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018465
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018466 #$mysync->{ maxsize } = ( defined $mysync->{ maxsize } ) ? $mysync->{ maxsize } : $GMAIL_MAXSIZE ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018467
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018468 if ( ! $mysync->{ noexclude } ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018469 push @exclude, '\[Gmail\]$' ;
18470 }
18471 push @useheader, 'Message-Id' ;
18472 push @{ $mysync->{ regextrans2 } }, 's,\[Gmail\].,,' ;
18473
18474 # push @{ $mysync->{ regextrans2 } }, 's/[ ]+/_/g' ; # is now replaced
18475 # by the two more specific following regexes,
18476 # they remove just the beginning and trailing blanks, not all.
18477 push @{ $mysync->{ regextrans2 } }, 's,^ +| +$,,g' ;
18478 push @{ $mysync->{ regextrans2 } }, 's,/ +| +/,/,g' ;
18479 #
18480 push @{ $mysync->{ regextrans2 } }, q{s/['\\^"]/_/g} ; # Verified this
18481 push @folderlast, '[Gmail]/All Mail' ;
18482 return ;
18483}
18484
18485
18486# From https://imapsync.lamiral.info/FAQ.d/FAQ.Exchange.txt
18487sub office1
18488{
18489 # Office 365 at host1
18490 my $mysync = shift ;
18491
18492 output( $mysync, q{Option --office1 is like: --host1 outlook.office365.com --ssl1 --exclude "^Files$"} . "\n" ) ;
18493 output( $mysync, "Option --office1 (cont) : unless overrided with --host1 otherhost --nossl1 --noexclude\n" ) ;
18494 $mysync->{host1} ||= 'outlook.office365.com' ;
18495 $mysync->{ssl1} = ( defined $mysync->{ssl1} ) ? $mysync->{ssl1} : 1 ;
18496 if ( ! $mysync->{noexclude} ) {
18497 push @exclude, '^Files$' ;
18498 }
18499 return ;
18500}
18501
18502
18503sub office2
18504{
18505 # Office 365 at host2
18506 my $mysync = shift ;
18507 output( $mysync, qq{Option --office2 is like: --host2 outlook.office365.com --ssl2 --maxsize 45_000_000 --maxmessagespersecond 4\n} ) ;
18508 output( $mysync, qq{Option --office2 (cont) : --disarmreadreceipts --regexmess "wrap 10500" --f1f2 "Files=Files_renamed_by_imapsync"\n} ) ;
18509 output( $mysync, qq{Option --office2 (cont) : unless overrided with --host2 otherhost --nossl2 ... --nodisarmreadreceipts --noregexmess\n} ) ;
18510 output( $mysync, qq{Option --office2 (cont) : and --nof1f2 to avoid Files folder renamed to Files_renamed_by_imapsync\n} ) ;
18511 $mysync->{host2} ||= 'outlook.office365.com' ;
18512 $mysync->{ssl2} = ( defined $mysync->{ssl2} ) ? $mysync->{ssl2} : 1 ;
18513 $mysync->{ maxsize } ||= 45_000_000 ;
18514 $mysync->{maxmessagespersecond} ||= 4 ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018515 #push @{ $mysync->{ regexflag } }, 's/\\\\Flagged//g' ; # No problem without! tested 2018_09_10
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018516 $disarmreadreceipts = ( defined $disarmreadreceipts ) ? $disarmreadreceipts : 1 ;
18517 # I dislike double negation but here is one
18518 if ( ! $mysync->{noregexmess} )
18519 {
18520 push @regexmess, 's,(.{10239}),$1\r\n,g' ;
18521 }
18522 # and another...
18523 if ( ! $mysync->{nof1f2} )
18524 {
18525 push @{ $mysync->{f1f2} }, 'Files=Files_renamed_by_imapsync' ;
18526 }
18527 return ;
18528}
18529
18530sub exchange1
18531{
18532 # Exchange 2010/2013 at host1
18533 my $mysync = shift ;
18534 output( $mysync, "Option --exchange1 does nothing (except printing this line...)\n" ) ;
18535 # Well nothing to do so far
18536 return ;
18537}
18538
18539sub exchange2
18540{
18541 # Exchange 2010/2013 at host2
18542 my $mysync = shift ;
18543 output( $mysync, "Option --exchange2 is like: --maxsize 10_000_000 --maxmessagespersecond 4 --disarmreadreceipts\n" ) ;
18544 output( $mysync, "Option --exchange2 (cont) : --regexflag del Flagged --regexmess wrap 10500\n" ) ;
18545 output( $mysync, "Option --exchange2 (cont) : unless overrided with --maxsize xxx --nodisarmreadreceipts --noregexflag --noregexmess\n" ) ;
18546 $mysync->{ maxsize } ||= 10_000_000 ;
18547 $mysync->{maxmessagespersecond} ||= 4 ;
18548 $disarmreadreceipts = ( defined $disarmreadreceipts ) ? $disarmreadreceipts : 1 ;
18549 # I dislike double negation but here are two
18550 if ( ! $mysync->{noregexflag} ) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018551 push @{ $mysync->{ regexflag } }, 's/\\\\Flagged//g' ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018552 }
18553 if ( ! $mysync->{noregexmess} ) {
18554 push @regexmess, 's,(.{10239}),$1\r\n,g' ;
18555 }
18556 return ;
18557}
18558
18559sub domino1
18560{
18561 # Domino at host1
18562 my $mysync = shift ;
18563
18564 $mysync->{ sep1 } = q{\\} ;
18565 $prefix1 = q{} ;
18566 $messageidnodomain = ( defined $messageidnodomain ) ? $messageidnodomain : 1 ;
18567 return ;
18568}
18569
18570sub domino2
18571{
18572 # Domino at host1
18573 my $mysync = shift ;
18574
18575 $mysync->{ sep2 } = q{\\} ;
18576 $prefix2 = q{} ;
18577 $messageidnodomain = ( defined $messageidnodomain ) ? $messageidnodomain : 1 ;
18578 push @{ $mysync->{ regextrans2 } }, 's,^Inbox\\\\(.*),$1,i' ;
18579 return ;
18580}
18581
18582
18583sub tests_resolv
18584{
18585 note( 'Entering tests_resolv()' ) ;
18586
18587 # is( , resolv( ), 'resolv: => ' ) ;
18588 is( undef, resolv( ), 'resolv: no args => undef' ) ;
18589 is( undef, resolv( q{} ), 'resolv: empty string => undef' ) ;
18590 is( undef, resolv( 'hostnotexist' ), 'resolv: hostnotexist => undef' ) ;
18591 is( '127.0.0.1', resolv( '127.0.0.1' ), 'resolv: 127.0.0.1 => 127.0.0.1' ) ;
18592 is( '127.0.0.1', resolv( 'localhost' ), 'resolv: localhost => 127.0.0.1' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018593 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 +010018594
18595 # ip6-localhost ( in /etc/hosts )
18596 is( '::1', resolv( 'ip6-localhost' ), 'resolv: ip6-localhost => ::1' ) ;
18597 is( '::1', resolv( '::1' ), 'resolv: ::1 => ::1' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018598 # ks2ipv6 now has CNAME ks6ipv6
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018599 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 +020018600 is( '2001:41d0:8:9951::1', resolv( 'ks6ipv6.lamiral.info' ), 'resolv: ks6ipv6.lamiral.info => 2001:41d0:8:9951::1' ) ;
18601 # ks6
18602 is( '2001:41d0:8:9951::1', resolv( '2001:41d0:8:9951::1' ), 'resolv: 2001:41d0:8:9951::1 => 2001:41d0:8:9951::1' ) ;
18603 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 +010018604 # ks3
18605 is( '2001:41d0:8:bebd::1', resolv( '2001:41d0:8:bebd::1' ), 'resolv: 2001:41d0:8:bebd::1 => 2001:41d0:8:bebd::1' ) ;
18606 is( '2001:41d0:8:bebd::1', resolv( 'ks3ipv6.lamiral.info' ), 'resolv: ks3ipv6.lamiral.info => 2001:41d0:8:bebd::1' ) ;
18607
18608
18609 note( 'Leaving tests_resolv()' ) ;
18610 return ;
18611}
18612
18613
18614
18615sub resolv
18616{
18617 my $host = shift @ARG ;
18618
18619 if ( ! $host ) { return ; }
18620 my $addr ;
18621 if ( defined &Socket::getaddrinfo ) {
18622 $addr = resolv_with_getaddrinfo( $host ) ;
18623 return( $addr ) ;
18624 }
18625
18626
18627
18628 my $iaddr = inet_aton( $host ) ;
18629 if ( ! $iaddr ) { return ; }
18630 $addr = inet_ntoa( $iaddr ) ;
18631
18632 return $addr ;
18633}
18634
18635sub resolv_with_getaddrinfo
18636{
18637 my $host = shift @ARG ;
18638
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018639 $sync->{ debug } and myprint( "Entering resolv_with_getaddrinfo( $host )\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018640 if ( ! $host ) { return ; }
18641
18642 my ( $err_getaddrinfo, @res ) = Socket::getaddrinfo( $host, "", { socktype => Socket::SOCK_RAW } ) ;
18643 if ( $err_getaddrinfo ) {
18644 myprint( "Cannot getaddrinfo of $host: $err_getaddrinfo\n" ) ;
18645 return ;
18646 }
18647
18648 my @addr ;
18649 while( my $ai = shift @res ) {
18650 my ( $err_getnameinfo, $ipaddr ) = Socket::getnameinfo( $ai->{addr}, Socket::NI_NUMERICHOST(), Socket::NIx_NOSERV() ) ;
18651 if ( $err_getnameinfo ) {
18652 myprint( "Cannot getnameinfo of $host: $err_getnameinfo\n" ) ;
18653 return ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018654 }else{
18655 $sync->{ debug } and myprint( "$host => $ipaddr\n" ) ;
18656 push @addr, $ipaddr ;
18657 my $reverse ;
18658 ( $err_getnameinfo, $reverse ) = Socket::getnameinfo( $ai->{addr}, 0, Socket::NIx_NOSERV() ) ;
18659 $sync->{ debug } and myprint( "$host => $ipaddr => $reverse\n" ) ;
18660 }
18661 $sync->{ debug } and myprint( "$host => $ipaddr\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018662
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018663 }
18664 $sync->{ debug } and myprint( "Leaving resolv_with_getaddrinfo( $host => $addr[0])\n" ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018665 return $addr[0] ;
18666}
18667
18668sub tests_resolvrev
18669{
18670 note( 'Entering tests_resolvrev()' ) ;
18671
18672 # is( , resolvrev( ), 'resolvrev: => ' ) ;
18673 is( undef, resolvrev( ), 'resolvrev: no args => undef' ) ;
18674 is( undef, resolvrev( q{} ), 'resolvrev: empty string => undef' ) ;
18675 is( undef, resolvrev( 'hostnotexist' ), 'resolvrev: hostnotexist => undef' ) ;
18676 is( 'localhost', resolvrev( '127.0.0.1' ), 'resolvrev: 127.0.0.1 => localhost' ) ;
18677 is( 'localhost', resolvrev( 'localhost' ), 'resolvrev: localhost => localhost' ) ;
18678 is( 'ks.lamiral.info', resolvrev( 'imapsync.lamiral.info' ), 'resolvrev: imapsync.lamiral.info => ks.lamiral.info' ) ;
18679
18680 # ip6-localhost ( in /etc/hosts )
18681 is( 'ip6-localhost', resolvrev( 'ip6-localhost' ), 'resolvrev: ip6-localhost => ip6-localhost' ) ;
18682 is( 'ip6-localhost', resolvrev( '::1' ), 'resolvrev: ::1 => ip6-localhost' ) ;
18683 # ks2
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018684 is( 'ks6ipv6.lamiral.info', resolvrev( '2001:41d0:8:d8b6::1' ), 'resolvrev: 2001:41d0:8:d8b6::1 => ks6ipv6.lamiral.info' ) ;
18685 is( 'ks6ipv6.lamiral.info', resolvrev( 'ks6ipv6.lamiral.info' ), 'resolvrev: ks6ipv6.lamiral.info => ks6ipv6.lamiral.info' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018686 # ks3
18687 is( 'ks3ipv6.lamiral.info', resolvrev( '2001:41d0:8:bebd::1' ), 'resolvrev: 2001:41d0:8:bebd::1 => ks3ipv6.lamiral.info' ) ;
18688 is( 'ks3ipv6.lamiral.info', resolvrev( 'ks3ipv6.lamiral.info' ), 'resolvrev: ks3ipv6.lamiral.info => ks3ipv6.lamiral.info' ) ;
18689
18690
18691 note( 'Leaving tests_resolvrev()' ) ;
18692 return ;
18693}
18694
18695sub resolvrev
18696{
18697 my $host = shift @ARG ;
18698
18699 if ( ! $host ) { return ; }
18700
18701 if ( defined &Socket::getaddrinfo ) {
18702 my $name = resolvrev_with_getaddrinfo( $host ) ;
18703 return( $name ) ;
18704 }
18705
18706 return ;
18707}
18708
18709sub resolvrev_with_getaddrinfo
18710{
18711 my $host = shift @ARG ;
18712
18713 if ( ! $host ) { return ; }
18714
18715 my ( $err, @res ) = Socket::getaddrinfo( $host, "", { socktype => Socket::SOCK_RAW } ) ;
18716 if ( $err ) {
18717 myprint( "Cannot getaddrinfo of $host: $err\n" ) ;
18718 return ;
18719 }
18720
18721 my @name ;
18722 while( my $ai = shift @res ) {
18723 my ( $err, $reverse ) = Socket::getnameinfo( $ai->{addr}, 0, Socket::NIx_NOSERV() ) ;
18724 if ( $err ) {
18725 myprint( "Cannot getnameinfo of $host: $err\n" ) ;
18726 return ;
18727 }
18728 $sync->{ debug } and myprint( "$host => $reverse\n" ) ;
18729 push @name, $reverse ;
18730 }
18731
18732 return $name[0] ;
18733}
18734
18735
18736
18737sub tests_imapsping
18738{
18739 note( 'Entering tests_imapsping()' ) ;
18740
18741 is( undef, imapsping( ), 'imapsping: no args => undef' ) ;
18742 is( undef, imapsping( 'hostnotexist' ), 'imapsping: hostnotexist => undef' ) ;
18743 is( 1, imapsping( 'imapsync.lamiral.info' ), 'imapsping: imapsync.lamiral.info => 1' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018744 is( 1, imapsping( 'ks6ipv6.lamiral.info' ), 'imapsping: ks6ipv6.lamiral.info => 1' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018745 note( 'Leaving tests_imapsping()' ) ;
18746 return ;
18747}
18748
18749sub imapsping
18750{
18751 my $host = shift ;
18752 return tcpping( $host, $IMAP_SSL_PORT ) ;
18753}
18754
18755sub tests_tcpping
18756{
18757 note( 'Entering tests_tcpping()' ) ;
18758
18759 is( undef, tcpping( ), 'tcpping: no args => undef' ) ;
18760 is( undef, tcpping( 'hostnotexist' ), 'tcpping: one arg => undef' ) ;
18761 is( undef, tcpping( undef, 888 ), 'tcpping: arg undef, port => undef' ) ;
18762 is( undef, tcpping( 'hostnotexist', 993 ), 'tcpping: hostnotexist 993 => undef' ) ;
18763 is( undef, tcpping( 'hostnotexist', 888 ), 'tcpping: hostnotexist 888 => undef' ) ;
18764 is( 1, tcpping( 'imapsync.lamiral.info', 993 ), 'tcpping: imapsync.lamiral.info 993 => 1' ) ;
18765 is( 0, tcpping( 'imapsync.lamiral.info', 888 ), 'tcpping: imapsync.lamiral.info 888 => 0' ) ;
18766 is( 1, tcpping( '5.135.158.182', 993 ), 'tcpping: 5.135.158.182 993 => 1' ) ;
18767 is( 0, tcpping( '5.135.158.182', 888 ), 'tcpping: 5.135.158.182 888 => 0' ) ;
18768
18769 # Net::Ping supports ipv6 only after release 1.50
18770 # http://cpansearch.perl.org/src/RURBAN/Net-Ping-2.59/Changes
18771 # Anyway I plan to avoid Net-Ping for that too long standing feature
18772 # Net-Ping is integrated in Perl itself, who knows ipv6 for a long time
18773 is( 1, tcpping( '2001:41d0:8:d8b6::1', 993 ), 'tcpping: 2001:41d0:8:d8b6::1 993 => 1' ) ;
18774 is( 0, tcpping( '2001:41d0:8:d8b6::1', 888 ), 'tcpping: 2001:41d0:8:d8b6::1 888 => 0' ) ;
18775
18776 note( 'Leaving tests_tcpping()' ) ;
18777 return ;
18778}
18779
18780sub tcpping
18781{
18782 if ( 2 != scalar( @ARG ) ) {
18783 return ;
18784 }
18785 my ( $host, $port ) = @ARG ;
18786 if ( ! $host ) { return ; }
18787 if ( ! $port ) { return ; }
18788
18789 my $mytimeout = $TCP_PING_TIMEOUT ;
18790 require Net::Ping ;
18791 #my $p = Net::Ping->new( 'tcp' ) ;
18792 my $p = Net::Ping->new( ) ;
18793 $p->{port_num} = $port ;
18794 $p->service_check( 1 ) ;
18795 $p->hires( 1 ) ;
18796 my ($ping_ok, $rtt, $ip ) = $p->ping( $host, $mytimeout ) ;
18797 if ( ! defined $ping_ok ) { return ; }
18798 my $rtt_approx = sprintf( "%.3f", $rtt ) ;
18799 $sync->{ debug } and myprint( "Host $host timeout $mytimeout port $port ok $ping_ok ip $ip acked in $rtt_approx s\n" ) ;
18800 $p->close( ) ;
18801 if( $ping_ok ) {
18802 return 1 ;
18803 }else{
18804 return 0 ;
18805 }
18806}
18807
18808sub tests_sslcheck
18809{
18810 note( 'Entering tests_sslcheck()' ) ;
18811
18812 my $mysync ;
18813
18814 is( undef, sslcheck( $mysync ), 'sslcheck: no sslcheck => undef' ) ;
18815
18816 $mysync = {
18817 sslcheck => 1,
18818 } ;
18819
18820 is( 0, sslcheck( $mysync ), 'sslcheck: no host => 0' ) ;
18821
18822 $mysync = {
18823 sslcheck => 1,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018824 host1 => 'test1.lamiral.info',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018825 tls1 => 1,
18826 } ;
18827
18828 is( 0, sslcheck( $mysync ), 'sslcheck: tls1 => 0' ) ;
18829
18830 $mysync = {
18831 sslcheck => 1,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018832 host1 => 'test1.lamiral.info',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018833 } ;
18834
18835
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018836 is( 1, sslcheck( $mysync ), 'sslcheck: test1.lamiral.info => 1' ) ;
18837 is( 1, $mysync->{ssl1}, 'sslcheck: test1.lamiral.info => ssl1 1' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018838
18839 $mysync->{sslcheck} = 0 ;
18840 is( undef, sslcheck( $mysync ), 'sslcheck: sslcheck off => undef' ) ;
18841
18842 $mysync = {
18843 sslcheck => 1,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018844 host1 => 'test1.lamiral.info',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018845 host2 => 'test2.lamiral.info',
18846 } ;
18847
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018848 is( 2, sslcheck( $mysync ), 'sslcheck: test1.lamiral.info + test2.lamiral.info => 2' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018849
18850 $mysync = {
18851 sslcheck => 1,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018852 host1 => 'test1.lamiral.info',
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018853 host2 => 'test2.lamiral.info',
18854 tls1 => 1,
18855 } ;
18856
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018857 is( 1, sslcheck( $mysync ), 'sslcheck: test1.lamiral.info + test2.lamiral.info + tls1 => 1' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018858
18859 note( 'Leaving tests_sslcheck()' ) ;
18860 return ;
18861}
18862
18863sub sslcheck
18864{
18865 my $mysync = shift ;
18866
18867 if ( ! $mysync->{sslcheck} ) {
18868 return ;
18869 }
18870 my $nb_on = 0 ;
18871 $mysync->{ debug } and myprint( "sslcheck\n" ) ;
18872 if (
18873 ( ! defined $mysync->{port1} )
18874 and
18875 ( ! defined $mysync->{tls1} )
18876 and
18877 ( ! defined $mysync->{ssl1} )
18878 and
18879 ( defined $mysync->{host1} )
18880 ) {
18881 myprint( "Host1: probing ssl on port $IMAP_SSL_PORT ( use --nosslcheck to avoid this ssl probe ) \n" ) ;
18882 if ( probe_imapssl( $mysync->{host1} ) ) {
18883 $mysync->{ssl1} = 1 ;
18884 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" ) ;
18885 $nb_on++ ;
18886 }else{
18887 myprint( "Host1: sslcheck did not detected open ssl port $IMAP_SSL_PORT. Will use standard $IMAP_PORT port.\n" ) ;
18888 }
18889 }
18890
18891 if (
18892 ( ! defined $mysync->{port2} )
18893 and
18894 ( ! defined $mysync->{tls2} )
18895 and
18896 ( ! defined $mysync->{ssl2} )
18897 and
18898 ( defined $mysync->{host2} )
18899 ) {
18900 myprint( "Host2: probing ssl on port $IMAP_SSL_PORT ( use --nosslcheck to avoid this ssl probe ) \n" ) ;
18901 if ( probe_imapssl( $mysync->{host2} ) ) {
18902 $mysync->{ssl2} = 1 ;
18903 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" ) ;
18904 $nb_on++ ;
18905 }else{
18906 myprint( "Host2: sslcheck did not detected open ssl port $IMAP_SSL_PORT. Will use standard $IMAP_PORT port.\n" ) ;
18907 }
18908 }
18909 return $nb_on ;
18910}
18911
18912
18913sub testslive_init
18914{
18915 my $mysync = shift ;
18916 $mysync->{host1} ||= 'test1.lamiral.info' ;
18917 $mysync->{user1} ||= 'test1' ;
18918 $mysync->{password1} ||= 'secret1' ;
18919 $mysync->{host2} ||= 'test2.lamiral.info' ;
18920 $mysync->{user2} ||= 'test2' ;
18921 $mysync->{password2} ||= 'secret2' ;
18922 return ;
18923}
18924
18925sub testslive6_init
18926{
18927 my $mysync = shift ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018928 $mysync->{host1} ||= 'ks6ipv6.lamiral.info' ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018929 $mysync->{user1} ||= 'test1' ;
18930 $mysync->{password1} ||= 'secret1' ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018931 $mysync->{host2} ||= 'ks6ipv6.lamiral.info' ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018932 $mysync->{user2} ||= 'test2' ;
18933 $mysync->{password2} ||= 'secret2' ;
18934 return ;
18935}
18936
18937
18938sub tests_backslash_caret
18939{
18940 note( 'Entering tests_backslash_caret()' ) ;
18941
18942 is( "lalala", backslash_caret( "lalala" ), 'backslash_caret: lalala => lalala' ) ;
18943 is( "lalala\n", backslash_caret( "lalala\n" ), 'backslash_caret: lalala => lalala 2nd' ) ;
18944 is( '^', backslash_caret( '\\' ), 'backslash_caret: \\ => ^' ) ;
18945 is( "^\n", backslash_caret( "\\\n" ), 'backslash_caret: \\ => ^' ) ;
18946 is( "\\lalala", backslash_caret( "\\lalala" ), 'backslash_caret: \\lalala => \\lalala' ) ;
18947 is( "\\lal\\ala", backslash_caret( "\\lal\\ala" ), 'backslash_caret: \\lal\\ala => \\lal\\ala' ) ;
18948 is( "\\lalala\n", backslash_caret( "\\lalala\n" ), 'backslash_caret: \\lalala => \\lalala 2nd' ) ;
18949 is( "lalala^\n", backslash_caret( "lalala\\\n" ), 'backslash_caret: lalala\\\n => lalala^\n' ) ;
18950 is( "lalala^\nlalala^\n", backslash_caret( "lalala\\\nlalala\\\n" ), 'backslash_caret: lalala\\\nlalala\\\n => lalala^\nlalala^\n' ) ;
18951 is( "lal\\ala^\nlalala^\n", backslash_caret( "lal\\ala\\\nlalala\\\n" ), 'backslash_caret: lal\\ala\\\nlalala\\\n => lal\\ala^\nlalala^\n' ) ;
18952
18953 note( 'Leaving tests_backslash_caret()' ) ;
18954 return ;
18955}
18956
18957sub backslash_caret
18958{
18959 my $string = shift ;
18960
18961 $string =~ s{\\ $ }{^}gxms ;
18962
18963 return $string ;
18964}
18965
18966sub tests_split_around_equal
18967{
18968 note( 'Entering tests_split_around_equal()' ) ;
18969
18970 is( undef, split_around_equal( ), 'split_around_equal: no args => undef' ) ;
18971 is_deeply( { toto => 'titi' }, { split_around_equal( 'toto=titi' ) }, 'split_around_equal: toto=titi => toto => titi' ) ;
18972 is_deeply( { A => 'B', C => 'D' }, { split_around_equal( 'A=B=C=D' ) }, 'split_around_equal: toto=titi => toto => titi' ) ;
18973 is_deeply( { A => 'B', C => 'D' }, { split_around_equal( 'A=B', 'C=D' ) }, 'split_around_equal: A=B C=D => A => B, C=>D' ) ;
18974
18975 note( 'Leaving tests_split_around_equal()' ) ;
18976 return ;
18977}
18978
18979sub split_around_equal
18980{
18981 if ( ! @ARG ) { return ; } ;
18982 return map { split /=/mxs, $_ } @ARG ;
18983
18984}
18985
18986
18987
18988sub tests_sig_install
18989{
18990 note( 'Entering tests_sig_install()' ) ;
18991
18992 my $mysync ;
18993 is( undef, sig_install( ), 'sig_install: no args => undef' ) ;
18994 is( undef, sig_install( $mysync ), 'sig_install: arg undef => undef' ) ;
18995 $mysync = { } ;
18996 is( undef, sig_install( $mysync ), 'sig_install: empty hash => undef' ) ;
18997
18998 SKIP: {
18999 Readonly my $SKIP_15 => 15 ;
19000 if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests only for Unix', $SKIP_15 ) ; }
19001 # Default to ignore USR1 USR2 in case future install fails
19002 local $SIG{ USR1 } = local $SIG{ USR2 } = sub { } ;
19003 kill( 'USR1', $PROCESS_ID ) ;
19004
19005 $mysync->{ debugsig } = 1 ;
19006 # Assign USR1 to call sub tototo
19007 # Surely a better value than undef should be returned when doing real signal stuff
19008 is( undef, sig_install( $mysync, 'tototo', 'USR1' ), 'sig_install: USR1 tototo' ) ;
19009
19010 is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 1' ) ;
19011 is( 1, $mysync->{ tototo_calls }, 'sig_install: tototo call nb 1' ) ;
19012
19013 #return ;
19014 # Assign USR2 to call sub tototo
19015 is( undef, sig_install( $mysync, 'tototo', 'USR2' ), 'sig_install: USR2 tototo' ) ;
19016
19017 is( 1, kill( 'USR2', $PROCESS_ID ), 'sig_install: kill USR2 myself 1' ) ;
19018 is( 2, $mysync->{ tototo_calls }, 'sig_install: tototo call nb 2' ) ;
19019
19020 is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 2' ) ;
19021 is( 3, $mysync->{ tototo_calls }, 'sig_install: tototo call nb 3' ) ;
19022
19023
19024 local $SIG{ USR1 } = local $SIG{ USR2 } = sub { } ;
19025 is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 3' ) ;
19026 is( 3, $mysync->{ tototo_calls }, 'sig_install: tototo call still nb 3' ) ;
19027
19028 # Assign USR1 + USR2 to call sub tototo
19029 is( undef, sig_install( $mysync, 'tototo', 'USR1', 'USR2' ), 'sig_install: USR1 USR2 tototo' ) ;
19030 is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 4' ) ;
19031 is( 4, $mysync->{ tototo_calls }, 'sig_install: tototo call now nb 4' ) ;
19032
19033 is( 1, kill( 'USR2', $PROCESS_ID ), 'sig_install: kill USR1 myself 2' ) ;
19034 is( 5, $mysync->{ tototo_calls }, 'sig_install: tototo call now nb 5' ) ;
19035 }
19036
19037
19038 note( 'Leaving tests_sig_install()' ) ;
19039 return ;
19040}
19041
19042
19043#
19044sub sig_install
19045{
19046 my $mysync = shift ;
19047 if ( ! $mysync ) { return ; }
19048 my $mysubname = shift ;
19049 if ( ! $mysubname ) { return ; }
19050
19051 if ( ! @ARG ) { return ; }
19052
19053 my @signals = @ARG ;
19054
19055 my $mysub = \&$mysubname ;
19056 #$mysync->{ debugsig } = 1 ;
19057 $mysync->{ debugsig } and myprint( "In sig_install with sub $mysubname and signal @ARG\n" ) ;
19058
19059 my $subsignal = sub {
19060 my $signame = shift ;
19061 $mysync->{ debugsig } and myprint( "In subsignal with $signame and $mysubname\n" ) ;
19062 &$mysub( $mysync, $signame ) ;
19063 } ;
19064
19065 foreach my $signal ( @signals ) {
19066 $mysync->{ debugsig } and myprint( "Installing signal $signal to call sub $mysubname\n") ;
19067 output( $mysync, "kill -$signal $PROCESS_ID # special behavior: call to sub $mysubname\n" ) ;
19068 ## no critic (RequireLocalizedPunctuationVars)
19069 $SIG{ $signal } = $subsignal ;
19070 }
19071 return ;
19072}
19073
19074
19075sub tototo
19076{
19077 my $mysync = shift ;
19078 myprint("In tototo with @ARG\n" ) ;
19079 $mysync->{ tototo_calls } += 1 ;
19080 return ;
19081}
19082
19083sub mygetppid
19084{
19085 if ( 'MSWin32' eq $OSNAME ) {
19086 return( 'unknown under MSWin32 (too complicated)' ) ;
19087 } else {
19088 # Unix
19089 return( getppid( ) ) ;
19090 }
19091}
19092
19093
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019094sub tests_toggle_sleep
19095{
19096 note( 'Entering tests_toggle_sleep()' ) ;
19097
19098 is( undef, toggle_sleep( ), 'toggle_sleep: no args => undef' ) ;
19099 my $mysync ;
19100 is( undef, toggle_sleep( $mysync ), 'toggle_sleep: undef => undef' ) ;
19101 $mysync = { } ;
19102 is( undef, toggle_sleep( $mysync ), 'toggle_sleep: no maxsleep => undef' ) ;
19103
19104 $mysync->{maxsleep} = 3 ;
19105 is( 0, toggle_sleep( $mysync ), 'toggle_sleep: 3 => 0' ) ;
19106
19107 is( $MAX_SLEEP, toggle_sleep( $mysync ), "toggle_sleep: 0 => $MAX_SLEEP" ) ;
19108 is( 0, toggle_sleep( $mysync ), "toggle_sleep: $MAX_SLEEP => 0" ) ;
19109 is( $MAX_SLEEP, toggle_sleep( $mysync ), "toggle_sleep: 0 => $MAX_SLEEP" ) ;
19110 is( 0, toggle_sleep( $mysync ), "toggle_sleep: $MAX_SLEEP => 0" ) ;
19111
19112 SKIP: {
19113 Readonly my $SKIP_9 => 9 ;
19114 if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests only for Unix', $SKIP_9 ) ; }
19115 # Default to ignore USR1 USR2 in case future install fails
19116 local $SIG{ USR1 } = sub { } ;
19117 kill( 'USR1', $PROCESS_ID ) ;
19118
19119 $mysync->{ debugsig } = 1 ;
19120 # Assign USR1 to call sub toggle_sleep
19121 is( undef, sig_install( $mysync, \&toggle_sleep, 'USR1' ), 'toggle_sleep: install USR1 toggle_sleep' ) ;
19122
19123
19124 $mysync->{maxsleep} = 4 ;
19125 is( 1, kill( 'USR1', $PROCESS_ID ), 'toggle_sleep: kill USR1 myself' ) ;
19126 is( 0, $mysync->{ maxsleep }, 'toggle_sleep: toggle_sleep called => sleeps are 0s' ) ;
19127
19128 is( 1, kill( 'USR1', $PROCESS_ID ), 'toggle_sleep: kill USR1 myself again' ) ;
19129 is( $MAX_SLEEP, $mysync->{ maxsleep }, "toggle_sleep: toggle_sleep called => sleeps are ${MAX_SLEEP}s" ) ;
19130
19131 is( 1, kill( 'USR1', $PROCESS_ID ), 'toggle_sleep: kill USR1 myself' ) ;
19132 is( 0, $mysync->{ maxsleep }, 'toggle_sleep: toggle_sleep called => sleeps are 0s' ) ;
19133
19134 is( 1, kill( 'USR1', $PROCESS_ID ), 'toggle_sleep: kill USR1 myself again' ) ;
19135 is( $MAX_SLEEP, $mysync->{ maxsleep }, "toggle_sleep: toggle_sleep called => sleeps are ${MAX_SLEEP}s" ) ;
19136 }
19137
19138 note( 'Leaving tests_toggle_sleep()' ) ;
19139 return ;
19140}
19141
19142
19143sub toggle_sleep
19144{
19145 my $mysync = shift ;
19146
19147 myprint("In toggle_sleep with @ARG\n" ) ;
19148
19149 if ( !defined( $mysync ) ) { return ; }
19150 if ( !defined( $mysync->{maxsleep} ) ) { return ; }
19151
19152 $mysync->{ maxsleep } = max( 0, $MAX_SLEEP - $mysync->{maxsleep} ) ;
19153 myprint("Resetting maxsleep to ", $mysync->{maxsleep}, "s\n" ) ;
19154 return $mysync->{maxsleep} ;
19155}
19156
19157sub mypod2usage
19158{
19159 my $fh_pod2usage = shift ;
19160
19161 pod2usage(
19162 -exitval => 'NOEXIT',
19163 -noperldoc => 1,
19164 -verbose => 99,
19165 -sections => [ qw(NAME VERSION USAGE OPTIONS) ],
19166 -indent => 1,
19167 -loose => 1,
19168 -output => $fh_pod2usage,
19169 ) ;
19170
19171 return ;
19172}
19173
19174sub usage
19175{
19176 my $mysync = shift ;
19177
19178 if ( ! defined $mysync ) { return ; }
19179
19180 my $usage = q{} ;
19181 my $usage_from_pod ;
19182 my $usage_footer = usage_footer( $mysync ) ;
19183
19184 # pod2usage writes on a filehandle only and I want a variable
19185 open my $fh_pod2usage, ">", \$usage_from_pod
19186 or do { warn $OS_ERROR ; return ; } ;
19187 mypod2usage( $fh_pod2usage ) ;
19188 close $fh_pod2usage ;
19189
19190 if ( 'MSWin32' eq $OSNAME ) {
19191 $usage_from_pod = backslash_caret( $usage_from_pod ) ;
19192 }
19193 $usage = join( q{}, $usage_from_pod, $usage_footer ) ;
19194
19195 return( $usage ) ;
19196}
19197
19198sub tests_usage
19199{
19200 note( 'Entering tests_usage()' ) ;
19201
19202 my $usage ;
19203 like( $usage = usage( $sync ), qr/Name:/, 'usage: contains Name:' ) ;
19204 myprint( $usage ) ;
19205 like( $usage, qr/Version:/, 'usage: contains Version:' ) ;
19206 like( $usage, qr/Usage:/, 'usage: contains Usage:' ) ;
19207 like( $usage, qr/imapsync/, 'usage: contains imapsync' ) ;
19208
19209 is( undef, usage( ), 'usage: no args => undef' ) ;
19210
19211 note( 'Leaving tests_usage()' ) ;
19212 return ;
19213}
19214
19215
19216sub usage_footer
19217{
19218 my $mysync = shift ;
19219
19220 my $footer = q{} ;
19221
19222 my $localhost_info = localhost_info( $mysync ) ;
19223 my $rcs = $mysync->{rcs} ;
19224 my $homepage = homepage( ) ;
19225
19226 my $imapsync_release = $STR_use_releasecheck ;
19227
19228 if ( $mysync->{releasecheck} ) {
19229 $imapsync_release = check_last_release( ) ;
19230 }
19231
19232 $footer = qq{$localhost_info
19233$rcs
19234$imapsync_release
19235$homepage
19236} ;
19237 return( $footer ) ;
19238}
19239
19240
19241
19242sub usage_complete
19243{
19244 # Unused, I guess this function could be deleted
19245 my $usage = <<'EOF' ;
19246--skipheader reg : Don't take into account header keyword
19247 matching reg ex: --skipheader 'X.*'
19248
19249--skipsize : Don't take message size into account to compare
19250 messages on both sides. On by default.
19251 Use --no-skipsize for using size comparaison.
19252--allowsizemismatch : allow RFC822.SIZE != fetched msg size
19253 consider also --skipsize to avoid duplicate messages
19254 when running syncs more than one time per mailbox
19255
19256--reconnectretry1 int : reconnect to host1 if connection is lost up to
19257 int times per imap command (default is 3)
19258--reconnectretry2 int : same as --reconnectretry1 but for host2
19259--split1 int : split the requests in several parts on host1.
19260 int is the number of messages handled per request.
19261 default is like --split1 100.
19262--split2 int : same thing on host2.
19263--nofixInboxINBOX : Don't fix Inbox INBOX mapping.
19264EOF
19265 return( $usage ) ;
19266}
19267
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019268
19269
19270
19271sub setvalfromcgikey
19272{
19273 my ( $mysync, $mycgi, $key, $val ) = @ARG ;
19274
19275 my $badthings = 0 ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010019276
19277
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019278 my ( $name, $type, $struct ) ;
19279 if ( $key !~ m/^([\w\d\|]+)([=:][isf])?([\+!\@\%])?$/mxs )
19280 {
19281 $badthings++ ;
19282 next ; # Unknown item
19283 }
19284 else
19285 {
19286 $name = [ split '|', $1, 1 ]->[0] ; # option name ab|cd|ef => keep only ab
19287 $type = $2 ; # = or : followed by i or s or f
19288 $struct = $3 ; # + or ! or @ or %
19289 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010019290
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019291 if ( ( $struct || q{} ) eq '+' )
19292 {
19293 ${$val} = $mycgi->param( $name ) ; # "Incremental" integer
19294 }
19295 elsif ( $type )
19296 {
19297 my @values = $mycgi->multi_param( $name ) ;
19298
19299 #myprint( "type[$type]values[@values]\$struct[", $struct || q{}, "]val[$val]ref(val)[", ref($val), "]\n" ) ;
19300 if ( ( $struct || q{} ) eq '%' or ref( $val ) eq 'HASH' )
19301 {
19302 setvalfromhash( $val, $type, @values ) ;
19303 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010019304 else
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019305 {
19306 setvalfromlist( $mysync, $val, $name, $type, $struct, @values ) ;
19307 }
19308 }
19309 else
19310 {
19311 setvalfromcheckbox( $mysync, $mycgi, $key, $name, $val ) ;
19312 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010019313
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019314 return $badthings ;
19315}
19316
19317sub setvalfromlist
19318{
19319 my ( $mysync, $val, $name, $type, $struct, @values ) = @ARG ;
19320 if ( $type =~ m/i$/mxs )
19321 {
19322 @values = map { q{} ne $_ ? int $_ : undef } @values ;
19323 }
19324 elsif ( $type =~ m/f$/mxs )
19325 {
19326 @values = map { 0 + $_ } @values ;
19327 }
19328
19329 if ( ( $struct || q{} ) eq '@' )
19330 {
19331 @{ ${$val} } = @values ;
19332 my @option = map { +( "--$name", "$_" ) } @values ;
19333 push @{ $mysync->{ cmdcgi } }, @option ;
19334 }
19335 elsif ( ref( $val ) eq 'ARRAY' )
19336 {
19337 @{$val} = @values ;
19338 }
19339 elsif ( my $value = $values[0] )
19340 {
19341 ${$val} = $value ;
19342 push @{ $mysync->{ cmdcgi } }, "--$name", $value ;
19343 }
19344 else
19345 {
19346 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010019347
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019348 return ;
19349}
19350sub setvalfromhash
19351{
19352 my ( $val, $type, @values ) = @ARG ;
19353
19354 my %values = map { split /=/mxs, $_ } @values ;
19355
19356 if ( $type =~ m/i$/mxs )
19357 {
19358 foreach my $k ( keys %values )
19359 {
19360 $values{$k} = int $values{$k} ;
19361 }
19362 }
19363 elsif ( $type =~ m/f$/mxs )
19364 {
19365 foreach my $k ( keys %values ) {
19366 $values{$k} = 0 + $values{$k};
19367 }
19368 }
19369
19370 if ( 'REF' eq ref $val )
19371 {
19372 %{ ${$val} } = %values ;
19373 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010019374 else
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019375 {
19376 %{$val} = %values ;
19377 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010019378
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019379 return ;
19380}
19381
19382
19383sub setvalfromcheckbox
19384{
19385 my ( $mysync, $mycgi, $key, $name, $val ) = @ARG ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010019386
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019387 # Checkbox
19388 # --noname is set by name=0 or name=
19389 my $value = $mycgi->param( $name ) ;
19390 if ( defined $value )
19391 {
19392 ${$val} = $value ;
19393 if ( $value )
19394 {
19395 push @{ $mysync->{ cmdcgi } }, "--$name" ;
19396 }
19397 else
19398 {
19399 push @{ $mysync->{ cmdcgi } }, "--no$name" ;
19400 }
19401 }
19402 else
19403 {
19404 ${$val} = undef ;
19405 }
19406 return ;
19407}
19408
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019409sub myGetOptions
19410{
19411
19412 # Started as a copy of Luke Ross Getopt::Long::CGI
19413 # https://metacpan.org/release/Getopt-Long-CGI
19414 # So this sub function is under the same license as Getopt-Long-CGI Luke Ross wants it,
19415 # which was Perl 5.6 or later licenses at the date of the copy.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019416 # It also applies for the sub functions called from this one.
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019417
19418 my $mysync = shift @ARG ;
19419 my $arguments_ref = shift @ARG ;
19420 my %options = @ARG ;
19421
19422 my $mycgi = $mysync->{cgi} ;
19423
19424 if ( not under_cgi_context() ) {
19425
19426 # Not CGI - pass upstream for normal command line handling
19427 return Getopt::Long::GetOptionsFromArray( $arguments_ref, %options ) ;
19428 }
19429
19430 # We must be in CGI context now
19431 if ( ! defined( $mycgi ) ) { return ; }
19432
19433 my $badthings = 0 ;
19434 foreach my $key ( sort keys %options ) {
19435 my $val = $options{$key} ;
19436
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019437 $badthings += setvalfromcgikey( $mysync, $mycgi, $key, $val ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019438
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019439 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010019440
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019441 if ( $badthings ) {
19442 return ; # undef or ()
19443 }
19444 else {
19445 return ( 1 ) ;
19446 }
19447}
19448
19449
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019450
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010019451sub tests_get_options_extra
19452{
19453 note( 'Entering tests_get_options_extra()' ) ;
19454
19455 is( undef, get_options_extra( ), 'get_options_extra: no args => undef' ) ;
19456
19457 my $mysync = { } ;
19458 is( undef, get_options_extra( $mysync ), 'get_options_extra: undef => undef' ) ;
19459
19460 my $cwd_save = getcwd( ) ;
19461
19462 ok( (-d 'W/tmp/tests/options_extra/' or mkpath( 'W/tmp/tests/options_extra/' )), 'get_options_extra: mkpath W/tmp/tests/options_extra/' ) ;
19463
19464 chdir 'W/tmp/tests/options_extra/' ;
19465
19466 is( '--debugimap1', string_to_file( '--debugimap1', 'options_extra.txt' ), 'get_options_extra: string_to_file filling options_extra.txt with --debugimap1' ) ;
19467
19468 is( '--debugimap1', file_to_string( 'options_extra.txt' ), 'get_options_extra: reading options_extra.txt is --debugimap1' ) ;
19469
19470 is( '', get_options_extra( $mysync ), 'get_options_extra: --debugimap1 in options_extra.txt => nothing left, empty string return' ) ;
19471
19472 is( 1, $mysync->{ acc1 }->{ debugimap }, 'get_options_extra: --debugimap1 in options_extra.txt => ok, acc1->debugimap = 1' ) ;
19473
19474 is( '--tls1 proutcaca', string_to_file( '--tls1 proutcaca', 'options_extra.txt' ), 'get_options_extra: string_to_file filling options_extra.txt with --tls1 proutcaca' ) ;
19475
19476 is( 'proutcaca', get_options_extra( $mysync ), 'get_options_extra: --tls1 proutcaca in options_extra.txt => proutcaca left, proutcaca return' ) ;
19477
19478 chdir $cwd_save ;
19479
19480 note( 'Leaving tests_get_options_extra()' ) ;
19481 return ;
19482}
19483
19484sub get_options_extra
19485{
19486 my $mysync = shift @ARG ;
19487
19488 if ( ! defined $mysync ) { return ; }
19489
19490 if ( -f -r 'options_extra.txt' )
19491 {
19492 my $cwd = getcwd( ) ;
19493 my $string = firstline( 'options_extra.txt' ) ;
19494 my $rest = get_options_from_string( $mysync, $string ) ;
19495 output( $mysync, "Reading extra options from file options_extra.txt (cwd: $cwd) : $string\n" ) ;
19496 return $rest ;
19497 }
19498 else
19499 {
19500 return ;
19501 }
19502}
19503
19504
19505sub tests_get_options_from_string
19506{
19507 note( 'Entering tests_get_options_from_string()' ) ;
19508
19509 is( undef, get_options_from_string( ), 'get_options_from_string: no args => undef' ) ;
19510 my $mysync = { } ;
19511 is( undef, get_options_from_string( $mysync ), 'get_options_from_string: undef => undef' ) ;
19512
19513 is( '', get_options_from_string( $mysync, '--debugimap1' ),
19514 'get_options_from_string: --debugimap1 => ok, nothing left, empty string return' ) ;
19515 is( 1, $mysync->{ acc1 }->{ debugimap }, 'get_options_from_string: --debugimap1 => ok, acc1->debugimap = 1' ) ;
19516
19517 $mysync = { } ; # reset
19518 is( 'caca', get_options_from_string( $mysync, '--debugimap1 caca' ),
19519 'get_options_from_string: --debugimap1 caca => ok, caca left, caca return' ) ;
19520 is( 1, $mysync->{ acc1 }->{ debugimap }, 'get_options_from_string: --debugimap1 => ok, acc1->debugimap = 1' ) ;
19521
19522 is( 'popo roro', get_options_from_string( $mysync, '--debugimap2 popo roro' ),
19523 'get_options_from_string: --debugimap1 popo roro => ok, popo roro left, popo roro return' ) ;
19524 is( 1, $mysync->{ acc2 }->{ debugimap }, 'get_options_from_string: --debugimap2 popo roro => ok, acc2->debugimap = 1' ) ;
19525 is( 1, $mysync->{ acc1 }->{ debugimap }, 'get_options_from_string: acc1->debugimap = 1 still' ) ;
19526
19527 is( '', get_options_from_string( $mysync, '--nodebugimap1 --debugflags --errorsmax 2' ),
19528 'get_options_from_string: --nodebugimap1 --debugflags --errorsmax 2 => ok, empty string return' ) ;
19529
19530 is( 0, $mysync->{ acc1 }->{ debugimap }, 'get_options_from_string: acc1->debugimap = 0 now' ) ;
19531 is( 1, $debugflags, 'get_options_from_string: debugflags = 1 now' ) ;
19532 is( 2, $mysync->{ errorsmax }, 'get_options_from_string: mysync->errorsmax = 2 now' ) ;
19533
19534 is( '', get_options_from_string( $mysync, '--folder "IN BOX" --folder JOE' ),
19535 'get_options_from_string: --folder "IN BOX" --folder JOE => ok, empty string return' ) ;
19536
19537 is_deeply( [ 'IN BOX', 'JOE' ], [@{$mysync->{ folder }}], 'get_options_from_string: "IN BOX" "JOE"' ) ;
19538
19539 is( '', get_options_from_string( $mysync, '--debugflags --koko' ),
19540 'get_options_from_string: --debugflags --koko => ok, empty string return, with "Unknown option: koko" on STDERR' ) ;
19541
19542 note( 'Leaving tests_get_options_from_string()' ) ;
19543 return ;
19544}
19545
19546sub get_options_from_string
19547{
19548 my $mysync = shift @ARG ;
19549 my $mystring = shift @ARG ;
19550
19551 if ( ! defined $mystring ) { return ; }
19552
19553 my ( $ret, $args ) = Getopt::Long::GetOptionsFromString( $mystring,
19554 'debugimap!' => \$mysync->{ debugimap },
19555 'debugimap1!' => \$mysync->{ acc1 }->{ debugimap },
19556 'debugimap2!' => \$mysync->{ acc2 }->{ debugimap },
19557 'debugflags!' => \$debugflags,
19558 'debugsleep=f' => \$mysync->{ debugsleep },
19559 'errorsmax=i' => \$mysync->{ errorsmax },
19560 'folder=s@' => \$mysync->{ folder },
19561 'timeout=f' => \$mysync->{ timeout },
19562 'timeout1=f' => \$mysync->{ acc1 }->{ timeout },
19563 'timeout2=f' => \$mysync->{ acc2 }->{ timeout },
19564 'keepalive1!' => \$mysync->{ acc1 }->{ keepalive },
19565 'keepalive2!' => \$mysync->{ acc2 }->{ keepalive },
19566 'reconnectretry1=i' => \$mysync->{ acc1 }->{ reconnectretry },
19567 'reconnectretry2=i' => \$mysync->{ acc2 }->{ reconnectretry },
19568 'ssl1!' => \$mysync->{ ssl1 },
19569 'ssl2!' => \$mysync->{ ssl2 },
19570 'tls1!' => \$mysync->{ tls1 },
19571 'tls2!' => \$mysync->{ tls2 },
19572 'compress1!' => \$mysync->{ acc1 }->{ compress },
19573 'compress2!' => \$mysync->{ acc2 }->{ compress },
19574 ) ;
19575 my $left = join( ' ', @$args ) ;
19576 return $left ;
19577}
19578
19579
19580
19581
19582
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019583
19584sub tests_get_options_cgi_context
19585{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019586 note( 'Entering tests_get_options_cgi_context()' ) ;
19587
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019588
19589# Temporary, have to think harder about testing CGI context in command line --tests
19590 # API:
19591 # * input arguments: two ways, command line or CGI
19592 # * the program arguments
19593 # * QUERY_STRING env variable
19594 # * return
19595 # * QUERY_STRING length
19596
19597 # CGI context
19598 local $ENV{SERVER_SOFTWARE} = 'Votre serviteur' ;
19599
19600 # Real full test
19601 # = 'host1=test1.lamiral.info&user1=test1&password1=secret1&host2=test2.lamiral.info&user2=test2&password2=secret2&debugenv=on'
19602 my $mysync ;
19603 is( undef, get_options( $mysync ), 'get_options cgi context: no CGI module => undef' ) ;
19604
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019605 # skip all next tests if the CGI module is not available
19606
19607 SKIP: {
19608 if ( ! eval { require CGI ; } ) {
19609 skip( "CGI Perl module is not installed", 19 ) ;
19610 }
19611
19612 CGI->import( qw( -no_debug -utf8 ) ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019613
19614 is( undef, get_options( $mysync ), 'get_options cgi context: no CGI param => undef' ) ;
19615 # Testing boolean
19616 $mysync->{cgi} = CGI->new( 'version=on&debugenv=on' ) ;
19617 local $ENV{'QUERY_STRING'} = 'version=on&debugenv=on' ;
19618 is( 22, get_options( $mysync ), 'get_options cgi context: QUERY_STRING => 22' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019619 is( 'on', $mysync->{ version }, 'get_options cgi context: --version => on' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019620 # debugenv is not allowed in cgi context
19621 is( undef, $mysync->{debugenv}, 'get_options cgi context: $mysync->{debugenv} => undef' ) ;
19622
19623 # QUERY_STRING in this test is only for return value of get_options
19624 # Have to think harder, GET/POST context, is this return value a good thing?
19625 local $ENV{'QUERY_STRING'} = 'host1=test1.lamiral.info&user1=test1' ;
19626 $mysync->{cgi} = CGI->new( 'host1=test1.lamiral.info&user1=test1' ) ;
19627 is( 36, get_options( $mysync, ), 'get_options cgi context: QUERY_STRING => 36' ) ;
19628 is( 'test1', $mysync->{user1}, 'get_options cgi context: $mysync->{user1} => test1' ) ;
19629 #local $ENV{'QUERY_STRING'} = undef ;
19630
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019631 # Testing s@ as ref
19632 $mysync->{cgi} = CGI->new( 'folder=fd1' ) ;
19633 get_options( $mysync ) ;
19634 is_deeply( [ 'fd1' ], $mysync->{ folder }, 'get_options cgi context: $mysync->{ folder } => fd1' ) ;
19635 $mysync->{cgi} = CGI->new( 'folder=fd1&folder=fd2' ) ;
19636 get_options( $mysync ) ;
19637 is_deeply( [ 'fd1', 'fd2' ], $mysync->{ folder }, 'get_options cgi context: $mysync->{ folder } => fd1, fd2' ) ;
19638
19639 # Testing %
19640 $mysync->{cgi} = CGI->new( 'f1f2h=s1=d1&f1f2h=s2=d2&f1f2h=s3=d3' ) ;
19641 get_options( $mysync ) ;
19642
19643 is_deeply( { 's1' => 'd1', 's2' => 'd2', 's3' => 'd3' },
19644 $mysync->{f1f2h}, 'get_options cgi context: f1f2h => s1=d1 s2=d2 s3=d3' ) ;
19645
19646 # Testing boolean ! with --noxxx, doesnot work
19647 $mysync->{cgi} = CGI->new( 'nodry=on' ) ;
19648 get_options( $mysync ) ;
19649 is( undef, $mysync->{dry}, 'get_options cgi context: --nodry => $mysync->{dry} => undef' ) ;
19650
19651 $mysync->{cgi} = CGI->new( 'host1=example.com' ) ;
19652 get_options( $mysync ) ;
19653 is( 'example.com', $mysync->{host1}, 'get_options cgi context: --host1=example.com => $mysync->{host1} => example.com' ) ;
19654
19655 #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
19656 $mysync->{cgi} = CGI->new( 'simulong=' ) ;
19657 get_options( $mysync ) ;
19658 is( undef, $mysync->{simulong}, 'get_options cgi context: --simulong= => $mysync->{simulong} => undef' ) ;
19659
19660 $mysync->{cgi} = CGI->new( 'simulong' ) ;
19661 get_options( $mysync ) ;
19662 is( undef, $mysync->{simulong}, 'get_options cgi context: --simulong => $mysync->{simulong} => undef' ) ;
19663
19664 $mysync->{cgi} = CGI->new( 'simulong=4' ) ;
19665 get_options( $mysync ) ;
19666 is( 4, $mysync->{simulong}, 'get_options cgi context: --simulong=4 => $mysync->{simulong} => 4' ) ;
19667 is( undef, $mysync->{ folder }, 'get_options cgi context: --simulong=4 => $mysync->{ folder } => undef' ) ;
19668 #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
19669
19670 $mysync ={} ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019671 $mysync->{cgi} = CGI->new( 'testslive=on' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019672 get_options( $mysync ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019673 is( 'on', $mysync->{ testslive }, 'get_options cgi context: --testslive=on => testslive => on' ) ;
19674 #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
19675
19676 $mysync ={} ;
19677 $mysync->{cgi} = CGI->new( 'log=0' ) ;
19678 get_options( $mysync ) ;
19679 is( 0, $mysync->{ log }, 'get_options cgi context: --log=0 => log => 0' ) ;
19680 #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
19681
19682
19683 # What is this fucked up indentation?
19684 }
19685
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019686
19687 note( 'Leaving tests_get_options_cgi_context()' ) ;
19688 return ;
19689}
19690
19691
19692
19693sub get_options_cgi
19694{
19695 # In CGI context arguments are not in @ARGV but in QUERY_STRING variable (with GET).
19696 my $mysync = shift @ARG ;
19697 $mysync->{cgi} || return ;
19698 my @arguments = @ARG ;
19699 # final 0 is used to print usage when no option is given
19700 my $numopt = length $ENV{'QUERY_STRING'} || 1 ;
19701 $mysync->{f1f2h} = {} ;
19702 my $opt_ret = myGetOptions(
19703 $mysync,
19704 \@arguments,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019705 'abort' => \$mysync->{ abort },
19706 'abortbyfile' => \$mysync->{ abortbyfile },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019707 'host1=s' => \$mysync->{ host1 },
19708 'host2=s' => \$mysync->{ host2 },
19709 'user1=s' => \$mysync->{ user1 },
19710 'user2=s' => \$mysync->{ user2 },
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019711 'password1=s' => \$mysync->{ password1 },
19712 'password2=s' => \$mysync->{ password2 },
19713 'dry!' => \$mysync->{ dry },
19714 'dry1!' => \$mysync->{ dry1 },
19715 'version' => \$mysync->{ version },
19716 'ssl1!' => \$mysync->{ ssl1 },
19717 'ssl2!' => \$mysync->{ ssl2 },
19718 'tls1!' => \$mysync->{ tls1 },
19719 'tls2!' => \$mysync->{ tls2 },
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010019720 'compress1!' => \$mysync->{ acc1 }->{ compress },
19721 'compress2!' => \$mysync->{ acc2 }->{ compress },
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019722 'justbanner!' => \$mysync->{ justbanner },
19723 'justlogin!' => \$mysync->{ justlogin },
19724 'justconnect!' => \$mysync->{ justconnect },
19725 'addheader!' => \$mysync->{ addheader },
19726 'automap!' => \$mysync->{ automap },
19727 'justautomap!' => \$mysync->{ justautomap },
19728 'gmail1' => \$mysync->{ gmail1 },
19729 'gmail2' => \$mysync->{ gmail2 },
19730 'office1' => \$mysync->{ office1 },
19731 'office2' => \$mysync->{ office2 },
19732 'exchange1' => \$mysync->{ exchange1 },
19733 'exchange2' => \$mysync->{ exchange2 },
19734 'domino1' => \$mysync->{ domino1 },
19735 'domino2' => \$mysync->{ domino2 },
19736 'f1f2=s@' => \$mysync->{ f1f2 },
19737 'f1f2h=s%' => \$mysync->{ f1f2h },
19738 'folder=s@' => \$mysync->{ folder },
19739 'testslive!' => \$mysync->{ testslive },
19740 'testslive6!' => \$mysync->{ testslive6 },
19741 'releasecheck!' => \$mysync->{ releasecheck },
19742 'simulong=f' => \$mysync->{ simulong },
19743 'debugsleep=f' => \$mysync->{ debugsleep },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019744 'subfolder1=s' => \$mysync->{ subfolder1 },
19745 'subfolder2=s' => \$mysync->{ subfolder2 },
19746 'justfolders!' => \$mysync->{ justfolders },
19747 'justfoldersizes!' => \$mysync->{ justfoldersizes },
19748 'delete1!' => \$mysync->{ delete1 },
19749 'delete2!' => \$mysync->{ delete2 },
19750 'delete2duplicates!' => \$mysync->{ delete2duplicates },
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019751 'tail!' => \$mysync->{ tail },
19752 'tmphash=s' => \$mysync->{ tmphash },
19753 'exitwhenover=i' => \$mysync->{ exitwhenover },
19754 'syncduplicates!' => \$mysync->{ syncduplicates },
19755 'log!' => \$mysync->{ log },
19756 'loglogfile!' => \$mysync->{ loglogfile },
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010019757
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019758
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019759# f1f2h=s% could be removed but
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019760# tests_get_options_cgi() should be split before
19761# with a sub tests_myGetOptions()
19762 ) ;
19763
19764 $mysync->{ debug } and output( $mysync, "get options: [$opt_ret][$numopt]\n" ) ;
19765
19766 if ( ! $opt_ret ) {
19767 return ;
19768 }
19769 return $numopt ;
19770}
19771
19772sub get_options_cmd
19773{
19774 my $mysync = shift @ARG ;
19775 my @arguments = @ARG ;
19776 my $mycgi = $mysync->{cgi} ;
19777 # final 0 is used to print usage when no option is given on command line
19778 my $numopt = scalar @arguments || 0 ;
19779 my $argv = join "\x00", @arguments ;
19780
19781 if ( $argv =~ m/-delete\x002/x ) {
19782 output( $mysync, "May be you mean --delete2 instead of --delete 2\n" ) ;
19783 return ;
19784 }
19785 $mysync->{f1f2h} = {} ;
19786 my $opt_ret = myGetOptions(
19787 $mysync,
19788 \@arguments,
19789 'debug!' => \$mysync->{ debug },
19790 'debuglist!' => \$debuglist,
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010019791 'debugcontent!' => \$mysync->{ debugcontent },
19792 'debugsleep=f' => \$mysync->{ debugsleep },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019793 'debugflags!' => \$debugflags,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019794 'debugimap!' => \$mysync->{ debugimap },
19795 'debugimap1!' => \$mysync->{ acc1 }->{ debugimap },
19796 'debugimap2!' => \$mysync->{ acc2 }->{ debugimap },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019797 'debugdev!' => \$debugdev,
19798 'debugmemory!' => \$mysync->{debugmemory},
19799 'debugfolders!' => \$mysync->{debugfolders},
19800 'debugssl=i' => \$mysync->{debugssl},
19801 'debugcgi!' => \$debugcgi,
19802 'debugenv!' => \$mysync->{debugenv},
19803 'debugsig!' => \$mysync->{debugsig},
19804 'debuglabels!' => \$mysync->{debuglabels},
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019805
19806 'simulong=f' => \$mysync->{simulong},
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019807 'abort' => \$mysync->{abort},
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019808 'abortbyfile' => \$mysync->{abortbyfile},
19809
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019810 'host1=s' => \$mysync->{ host1 },
19811 'host2=s' => \$mysync->{ host2 },
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010019812 'port1=i' => \$mysync->{ port1 },
19813 'port2=i' => \$mysync->{ port2 },
19814 'inet4|ipv4' => \$mysync->{ inet4 },
19815 'inet6|ipv6' => \$mysync->{ inet6 },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019816 'user1=s' => \$mysync->{ user1 },
19817 'user2=s' => \$mysync->{ user2 },
19818 'gmail1' => \$mysync->{gmail1},
19819 'gmail2' => \$mysync->{gmail2},
19820 'office1' => \$mysync->{office1},
19821 'office2' => \$mysync->{office2},
19822 'exchange1' => \$mysync->{exchange1},
19823 'exchange2' => \$mysync->{exchange2},
19824 'domino1' => \$mysync->{domino1},
19825 'domino2' => \$mysync->{domino2},
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019826 'domain1=s' => \$mysync->{ acc1 }->{ domain },
19827 'domain2=s' => \$mysync->{ acc2 }->{ domain },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019828 'password1=s' => \$mysync->{password1},
19829 'password2=s' => \$mysync->{password2},
19830 'passfile1=s' => \$mysync->{ passfile1 },
19831 'passfile2=s' => \$mysync->{ passfile2 },
19832 'authmd5!' => \$authmd5,
19833 'authmd51!' => \$authmd51,
19834 'authmd52!' => \$authmd52,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019835
19836 'trylogin!' => \$mysync->{ trylogin },
19837
19838 'oauthdirect1=s' => \$mysync->{ acc1 }->{ oauthdirect },
19839 'oauthdirect2=s' => \$mysync->{ acc2 }->{ oauthdirect },
19840
19841 'oauthaccesstoken1=s' => \$mysync->{ acc1 }->{ oauthaccesstoken },
19842 'oauthaccesstoken2=s' => \$mysync->{ acc2 }->{ oauthaccesstoken },
19843
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019844 'sep1=s' => \$mysync->{ sep1 },
19845 'sep2=s' => \$mysync->{ sep2 },
19846 'sanitize!' => \$mysync->{ sanitize },
19847 'folder=s@' => \$mysync->{ folder },
19848 'folderrec=s' => \@folderrec,
19849 'include=s' => \@include,
19850 'exclude=s' => \@exclude,
19851 'noexclude' => \$mysync->{noexclude},
19852 'folderfirst=s' => \@folderfirst,
19853 'folderlast=s' => \@folderlast,
19854 'prefix1=s' => \$prefix1,
19855 'prefix2=s' => \$prefix2,
19856 'subfolder1=s' => \$mysync->{ subfolder1 },
19857 'subfolder2=s' => \$mysync->{ subfolder2 },
19858 'fixslash2!' => \$mysync->{ fixslash2 },
19859 'fixInboxINBOX!' => \$fixInboxINBOX,
19860 'regextrans2=s@' => \$mysync->{ regextrans2 },
19861 'mixfolders!' => \$mixfolders,
19862 'skipemptyfolders!' => \$mysync->{ skipemptyfolders },
19863 'regexmess=s' => \@regexmess,
19864 'noregexmess' => \$mysync->{noregexmess},
19865 'skipmess=s' => \@skipmess,
19866 'pipemess=s' => \@pipemess,
19867 'pipemesscheck!' => \$pipemesscheck,
19868 'disarmreadreceipts!' => \$disarmreadreceipts,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019869 'regexflag=s@' => \$mysync->{ regexflag },
19870 'noregexflag' => \$mysync->{ noregexflag },
19871 'filterflags!' => \$mysync->{ filterflags },
19872 'filterbuggyflags!' => \$mysync->{ filterbuggyflags },
19873 'flagscase!' => \$mysync->{ flagscase },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019874 'syncflagsaftercopy!' => \$syncflagsaftercopy,
19875 'resyncflags!' => \$mysync->{ resyncflags },
19876 'synclabels!' => \$mysync->{ synclabels },
19877 'resynclabels!' => \$mysync->{ resynclabels },
19878 'delete|delete1!' => \$mysync->{ delete1 },
19879 'delete2!' => \$mysync->{ delete2 },
19880 'delete2duplicates!' => \$mysync->{ delete2duplicates },
19881 'delete2folders!' => \$delete2folders,
19882 'delete2foldersonly=s' => \$delete2foldersonly,
19883 'delete2foldersbutnot=s' => \$delete2foldersbutnot,
19884 'syncinternaldates!' => \$syncinternaldates,
19885 'idatefromheader!' => \$idatefromheader,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019886 'syncacls!' => \$mysync->{ syncacls },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019887 'maxsize=i' => \$mysync->{ maxsize },
19888 'appendlimit=i' => \$mysync->{ appendlimit },
19889 'truncmess=i' => \$mysync->{ truncmess },
19890 'minsize=i' => \$minsize,
19891 'maxage=f' => \$maxage,
19892 'minage=f' => \$minage,
19893 'search=s' => \$search,
19894 'search1=s' => \$mysync->{ search1 },
19895 'search2=s' => \$mysync->{ search2 },
19896 'foldersizes!' => \$mysync->{ foldersizes },
19897 'foldersizesatend!' => \$mysync->{ foldersizesatend },
19898 'dry!' => \$mysync->{dry},
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019899 'dry1!' => \$mysync->{dry1},
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019900 'expunge1|expunge!' => \$mysync->{ expunge1 },
19901 'expunge2!' => \$mysync->{ expunge2 },
19902 'uidexpunge2!' => \$mysync->{ uidexpunge2 },
19903 'subscribed' => \$subscribed,
19904 'subscribe!' => \$subscribe,
19905 'subscribeall|subscribe_all!' => \$subscribeall,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019906 'justbanner!' => \$mysync->{ justbanner },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019907 'justfolders!'=> \$mysync->{ justfolders },
19908 'justfoldersizes!' => \$mysync->{ justfoldersizes },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019909 'version' => \$mysync->{version},
19910 'help' => \$help,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019911 'timeout=f' => \$mysync->{timeout},
19912 'timeout1=f' => \$mysync->{ acc1 }->{timeout},
19913 'timeout2=f' => \$mysync->{ acc2 }->{timeout},
19914 'skipheader=s' => \$mysync->{ skipheader },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019915 'useheader=s' => \@useheader,
19916 'wholeheaderifneeded!' => \$wholeheaderifneeded,
19917 'messageidnodomain!' => \$messageidnodomain,
19918 'skipsize!' => \$skipsize,
19919 'allowsizemismatch!' => \$allowsizemismatch,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019920 'fastio1!' => \$mysync->{ acc1 }->{ fastio },
19921 'fastio2!' => \$mysync->{ acc2 }->{ fastio },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019922 'sslcheck!' => \$mysync->{sslcheck},
19923 'ssl1!' => \$mysync->{ssl1},
19924 'ssl2!' => \$mysync->{ssl2},
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019925 'ssl1_ssl_version=s' => \$mysync->{ acc1 }->{sslargs}->{SSL_version},
19926 'ssl2_ssl_version=s' => \$mysync->{ acc2 }->{sslargs}->{SSL_version},
19927 'sslargs1=s%' => \$mysync->{ acc1 }->{sslargs},
19928 'sslargs2=s%' => \$mysync->{ acc2 }->{sslargs},
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019929 'tls1!' => \$mysync->{tls1},
19930 'tls2!' => \$mysync->{tls2},
19931 'uid1!' => \$uid1,
19932 'uid2!' => \$uid2,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019933 'authmech1=s' => \$mysync->{ acc1 }->{ authmech },
19934 'authmech2=s' => \$mysync->{ acc2 }->{ authmech },
19935 'authuser1=s' => \$mysync->{ acc1 }->{ authuser },
19936 'authuser2=s' => \$mysync->{ acc2 }->{ authuser },
19937 'proxyauth1' => \$mysync->{ acc1 }->{ proxyauth },
19938 'proxyauth2' => \$mysync->{ acc2 }->{ proxyauth },
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010019939 'compress1!' => \$mysync->{ acc1 }->{ compress },
19940 'compress2!' => \$mysync->{ acc2 }->{ compress },
19941 'keepalive1!' => \$mysync->{ acc1 }->{ keepalive },
19942 'keepalive2!' => \$mysync->{ acc2 }->{ keepalive },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019943 'split1=i' => \$split1,
19944 'split2=i' => \$split2,
19945 'buffersize=i' => \$buffersize,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019946 'reconnectretry1=i' => \$mysync->{ acc1 }->{ reconnectretry },
19947 'reconnectretry2=i' => \$mysync->{ acc2 }->{ reconnectretry },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019948 'tests!' => \$mysync->{ tests },
19949 'testsdebug|tests_debug!' => \$mysync->{ testsdebug },
19950 'testsunit=s@' => \$mysync->{testsunit},
19951 'testslive!' => \$mysync->{testslive},
19952 'testslive6!' => \$mysync->{testslive6},
19953 'justlogin!' => \$mysync->{justlogin},
19954 'justconnect!' => \$mysync->{justconnect},
19955 'tmpdir=s' => \$mysync->{ tmpdir },
19956 'pidfile=s' => \$mysync->{pidfile},
19957 'pidfilelocking!' => \$mysync->{pidfilelocking},
19958 'sigexit=s@' => \$mysync->{ sigexit },
19959 'sigreconnect=s@' => \$mysync->{ sigreconnect },
19960 'sigignore=s@' => \$mysync->{ sigignore },
19961 'releasecheck!' => \$mysync->{releasecheck},
19962 'modulesversion|modules_version!' => \$modulesversion,
19963 'usecache!' => \$usecache,
19964 'cacheaftercopy!' => \$cacheaftercopy,
19965 'debugcache!' => \$debugcache,
19966 'useuid!' => \$useuid,
19967 'addheader!' => \$mysync->{addheader},
19968 'exitwhenover=i' => \$mysync->{ exitwhenover },
19969 'checkselectable!' => \$mysync->{ checkselectable },
19970 'checkfoldersexist!' => \$mysync->{ checkfoldersexist },
19971 'checkmessageexists!' => \$checkmessageexists,
19972 'expungeaftereach!' => \$mysync->{ expungeaftereach },
19973 'abletosearch!' => \$mysync->{abletosearch},
19974 'abletosearch1!' => \$mysync->{abletosearch1},
19975 'abletosearch2!' => \$mysync->{abletosearch2},
19976 'showpasswords!' => \$mysync->{showpasswords},
19977 'maxlinelength=i' => \$maxlinelength,
19978 'maxlinelengthcmd=s' => \$maxlinelengthcmd,
19979 'minmaxlinelength=i' => \$minmaxlinelength,
19980 'debugmaxlinelength!' => \$debugmaxlinelength,
19981 'fixcolonbug!' => \$fixcolonbug,
19982 'create_folder_old!' => \$create_folder_old,
19983 'maxmessagespersecond=f' => \$mysync->{maxmessagespersecond},
19984 'maxbytespersecond=i' => \$mysync->{maxbytespersecond},
19985 'maxbytesafter=i' => \$mysync->{maxbytesafter},
19986 'maxsleep=f' => \$mysync->{maxsleep},
19987 'skipcrossduplicates!' => \$skipcrossduplicates,
19988 'debugcrossduplicates!' => \$debugcrossduplicates,
19989 'log!' => \$mysync->{log},
19990 'tail!' => \$mysync->{tail},
19991 'logfile=s' => \$mysync->{logfile},
19992 'logdir=s' => \$mysync->{logdir},
19993 'errorsmax=i' => \$mysync->{errorsmax},
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010019994 'errorsdump!' => \$mysync->{ errorsdump },
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010019995 'fetch_hash_set=s' => \$fetch_hash_set,
19996 'automap!' => \$mysync->{automap},
19997 'justautomap!' => \$mysync->{justautomap},
19998 'id!' => \$mysync->{id},
19999 'f1f2=s@' => \$mysync->{f1f2},
20000 'nof1f2' => \$mysync->{nof1f2},
20001 'f1f2h=s%' => \$mysync->{f1f2h},
20002 'justfolderlists!' => \$mysync->{justfolderlists},
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020003 'delete1emptyfolders' => \$mysync->{delete1emptyfolders},
20004 'checknoabletosearch!' => \$mysync->{checknoabletosearch},
20005 'syncduplicates!' => \$mysync->{ syncduplicates },
20006 'dockercontext!' => \$mysync->{ dockercontext },
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010020007
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020008
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010020009 ) ;
20010 #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
20011 $mysync->{ debug } and output( $mysync, "get options: [$opt_ret][$numopt]\n" ) ;
20012 my $numopt_after = scalar @arguments ;
20013 #myprint( "get options: [$opt_ret][$numopt][$numopt_after]\n" ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020014
20015 # The $arguments[0] test is just because parallel adds "" when it is
20016 # used with {=7=} in sync_parallel_unix.sh
20017 if ( $numopt_after and $arguments[0] ) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010020018 myprint(
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020019 "Found ", scalar( @arguments ), " extra arguments : [@arguments]\n",
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010020020 "It usually means a quoting issue in the command line ",
20021 "or some misspelling options.\n",
20022 ) ;
20023 return ;
20024 }
20025 if ( ! $opt_ret ) {
20026 return ;
20027 }
20028 return $numopt ;
20029}
20030
20031
20032
20033sub tests_get_options
20034{
20035 note( 'Entering tests_get_options()' ) ;
20036
20037 # CAVEAT: still setting global variables, be careful
20038 # with tests, the context increases! $debug stays on for example.
20039 # API:
20040 # * input arguments: two ways, command line or CGI
20041 # * the program arguments
20042 # * QUERY_STRING env variable
20043 # * return
20044 # * undef if bad things happened like
20045 # * options not known
20046 # * --delete 2 input
20047 # * number of arguments or QUERY_STRING length
20048 my $mysync = { } ;
20049 is( undef, get_options( $mysync, qw( --noexist ) ), 'get_options: --noexist => undef' ) ;
20050 is( undef, $mysync->{ noexist }, 'get_options: --noexist => undef' ) ;
20051 $mysync = { } ;
20052 is( undef, get_options( $mysync, qw( --lalala --noexist --version ) ), 'get_options: --lalala --noexist --version => undef' ) ;
20053 is( 1, $mysync->{ version }, 'get_options: --version => 1' ) ;
20054 is( undef, $mysync->{ noexist }, 'get_options: --noexist => undef' ) ;
20055 $mysync = { } ;
20056 is( 1, get_options( $mysync, qw( --delete2 ) ), 'get_options: --delete2 => 1' ) ;
20057 is( 1, $mysync->{ delete2 }, 'get_options: --delete2 => var delete2 = 1' ) ;
20058 $mysync = { } ;
20059 is( undef, get_options( $mysync, qw( --delete 2 ) ), 'get_options: --delete 2 => var undef' ) ;
20060 is( undef, $mysync->{ delete1 }, 'get_options: --delete 2 => var still undef ; good!' ) ;
20061 $mysync = { } ;
20062 is( undef, get_options( $mysync, "--delete 2" ), 'get_options: --delete 2 => undef' ) ;
20063
20064 is( 1, get_options( $mysync, "--version" ), 'get_options: --version => 1' ) ;
20065 is( 1, get_options( $mysync, "--help" ), 'get_options: --help => 1' ) ;
20066
20067 is( undef, get_options( $mysync, qw( --noexist --version ) ), 'get_options: --debug --noexist --version => undef' ) ;
20068 is( 1, get_options( $mysync, qw( --version ) ), 'get_options: --version => 1' ) ;
20069 is( undef, get_options( $mysync, qw( extra ) ), 'get_options: extra => undef' ) ;
20070 is( undef, get_options( $mysync, qw( extra1 --version extra2 ) ), 'get_options: extra1 --version extra2 => undef' ) ;
20071
20072 $mysync = { } ;
20073 is( 2, get_options( $mysync, qw( --host1 HOST_01) ), 'get_options: --host1 HOST_01 => 1' ) ;
20074 is( 'HOST_01', $mysync->{ host1 }, 'get_options: --host1 HOST_01 => HOST_01' ) ;
20075 #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
20076
20077 note( 'Leaving tests_get_options()' ) ;
20078 return ;
20079}
20080
20081
20082
20083sub get_options
20084{
20085 my $mysync = shift @ARG ;
20086 my @arguments = @ARG ;
20087 #myprint( "1 mysync: ", Data::Dumper->Dump( [ $mysync ] ) ) ;
20088 my $ret ;
20089 if ( under_cgi_context( ) ) {
20090 # CGI context
20091 $ret = get_options_cgi( $mysync, @arguments ) ;
20092 }else{
20093 # Command line context ;
20094 $ret = get_options_cmd( $mysync, @arguments ) ;
20095 } ;
20096 #myprint( "2 mysync: ", Data::Dumper->Dump( [ $mysync ] ) ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010020097
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010020098 foreach my $key ( sort keys %{ $mysync } ) {
20099 if ( ! defined $mysync->{$key} ) {
20100 delete $mysync->{$key} ;
20101 next ;
20102 }
20103 if ( 'ARRAY' eq ref( $mysync->{$key} )
20104 and 0 == scalar( @{ $mysync->{$key} } ) ) {
20105 delete $mysync->{$key} ;
20106 }
20107 }
20108 return $ret ;
20109}
20110
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020111
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010020112sub tests_infos
20113{
20114 note( 'Entering tests_infos()' ) ;
20115 note( "OSNAME=$OSNAME" ) ;
20116 note( "hostname=". hostname() ) ;
20117 note( "cwd=" . getcwd( ) ) ;
20118 note( "PROGRAM_NAME=$PROGRAM_NAME" ) ;
20119 my $stat = stat("$PROGRAM_NAME") ;
20120 my $perms = sprintf( "%04o\n", $stat->mode & oct($PERMISSION_FILTER) ) ;
20121 note( "permissions=$perms" ) ;
20122 note( "PROCESS_ID=$PROCESS_ID" ) ;
20123 note( "REAL_USER_ID=$REAL_USER_ID" ) ;
20124 note( "EFFECTIVE_USER_ID=$EFFECTIVE_USER_ID" ) ;
20125 note( "context: " . imapsync_context( $sync ) ) ;
20126 note( "memory_consumption: " . memory_consumption() . " bytes aka " . bytes_display_string_dec( memory_consumption() ) ) ;
20127 cpu_number
20128 note( "cpu_number: " . cpu_number() ) ;
20129 note( $sync->{rcs} ) ;
20130
20131 note( 'Leaving tests_infos()' ) ;
20132 return ;
20133}
20134
20135
20136
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020137sub condition_to_leave_after_tests
20138{
20139 my $mysync = shift ;
20140 if ( $mysync->{ testslive } or $mysync->{ testslive6 } )
20141 {
20142 return 0 ;
20143 }
20144
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010020145 if ( $mysync->{ tests }
20146 or $mysync->{ testsdebug }
20147 or $mysync->{ testsunit }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020148 )
20149 {
20150 return 1 ;
20151 }
20152}
20153
20154
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010020155sub testunitsession
20156{
20157 my $mysync = shift ;
20158
20159 if ( ! $mysync ) { return ; }
20160 if ( ! $mysync->{ testsunit } ) { return ; }
20161
20162 my @functions = @{ $mysync->{ testsunit } } ;
20163
20164 if ( ! @functions ) { return ; }
20165
20166 SKIP: {
20167 if ( ! @functions ) { skip 'No test in normal run' ; }
20168 testsunit( @functions ) ;
20169 done_testing( ) ;
20170 }
20171 return ;
20172}
20173
20174sub tests_count_0s
20175{
20176 note( 'Entering tests_count_zeros()' ) ;
20177 is( 0, count_0s( ), 'count_0s: no parameters => 0' ) ;
20178 is( 1, count_0s( 0 ), 'count_0s: 0 => 1' ) ;
20179 is( 0, count_0s( 1 ), 'count_0s: 1 => 0' ) ;
20180 is( 1, count_0s( 1, 0, 1 ), 'count_0s: 1, 0, 1 => 1' ) ;
20181 is( 2, count_0s( 1, 0, 1, 0 ), 'count_0s: 1, 0, 1, 0 => 2' ) ;
20182 note( 'Leaving tests_count_zeros()' ) ;
20183 return ;
20184}
20185sub count_0s
20186{
20187 my @array = @ARG ;
20188
20189 if ( ! @array ) { return 0 ; }
20190 my $nb_zeros = 0 ;
20191 map { $_ == 0 and $nb_zeros += 1 } @array ;
20192 return $nb_zeros ;
20193}
20194
20195sub tests_report_failures
20196{
20197 note( 'Entering tests_report_failures()' ) ;
20198
20199 is( undef, report_failures( ), 'report_failures: no parameters => undef' ) ;
20200 is( "nb 1 - first\n", report_failures( ({'ok' => 0, name => 'first'}) ), 'report_failures: "first" failed => nb 1 - first' ) ;
20201 is( q{}, report_failures( ( {'ok' => 1, name => 'first'} ) ), 'report_failures: "first" success =>' ) ;
20202 is( "nb 2 - second\n", report_failures( ( {'ok' => 1, name => 'second'}, {'ok' => 0, name => 'second'} ) ), 'report_failures: "second" failed => nb 2 - second' ) ;
20203 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' ) ;
20204 note( 'Leaving tests_report_failures()' ) ;
20205 return ;
20206}
20207
20208sub report_failures
20209{
20210 my @details = @ARG ;
20211
20212 if ( ! @details ) { return ; }
20213
20214 my $counter = 1 ;
20215 my $report = q{} ;
20216 foreach my $details ( @details ) {
20217 if ( ! $details->{ 'ok' } ) {
20218 my $name = $details->{ 'name' } || 'NONAME' ;
20219 $report .= "nb $counter - $name\n" ;
20220 }
20221 $counter += 1 ;
20222 }
20223 return $report ;
20224
20225}
20226
20227sub tests_true
20228{
20229 note( 'Entering tests_true()' ) ;
20230
20231 is( 1, 1, 'true: 1 is 1' ) ;
20232 note( 'Leaving tests_true()' ) ;
20233 return ;
20234}
20235
20236sub tests_testsunit
20237{
20238 note( 'Entering tests_testunit()' ) ;
20239
20240 is( undef, testsunit( ), 'testsunit: no parameters => undef' ) ;
20241 is( undef, testsunit( undef ), 'testsunit: an undef parameter => undef' ) ;
20242 is( undef, testsunit( q{} ), 'testsunit: an empty parameter => undef' ) ;
20243 is( undef, testsunit( 'idonotexist' ), 'testsunit: a do not exist function as parameter => undef' ) ;
20244 is( undef, testsunit( 'tests_true' ), 'testsunit: tests_true => undef' ) ;
20245 note( 'Leaving tests_testunit()' ) ;
20246 return ;
20247}
20248
20249sub testsunit
20250{
20251 my @functions = @ARG ;
20252
20253 if ( ! @functions ) { #
20254 myprint( "testsunit warning: no argument given\n" ) ;
20255 return ;
20256 }
20257
20258 foreach my $function ( @functions ) {
20259 if ( ! $function ) {
20260 myprint( "testsunit warning: argument is empty\n" ) ;
20261 next ;
20262 }
20263 if ( ! exists &$function ) {
20264 myprint( "testsunit warning: function $function does not exist\n" ) ;
20265 next ;
20266 }
20267 if ( ! defined &$function ) {
20268 myprint( "testsunit warning: function $function is not defined\n" ) ;
20269 next ;
20270 }
20271 my $function_ref = \&{ $function } ;
20272 &$function_ref() ;
20273 }
20274 return ;
20275}
20276
20277sub testsdebug
20278{
20279 # Now a little obsolete since there is
20280 # imapsync ... --testsunit "anyfunction"
20281 my $mysync = shift ;
20282 if ( ! $mysync->{ testsdebug } ) { return ; }
20283 SKIP: {
20284 if ( ! $mysync->{ testsdebug } ) {
20285 skip 'No test in normal run' ;
20286 }
20287
20288 note( 'Entering testsdebug()' ) ;
20289 #ok( ( ( not -d 'W/tmp/tests' ) or rmtree( 'W/tmp/tests/' ) ), 'testsdebug: rmtree W/tmp/tests' ) ;
20290 #tests_check_binary_embed_all_dyn_libs( ) ;
20291 #tests_killpid_by_parent( ) ;
20292 #tests_killpid_by_brother( ) ;
20293 #tests_kill_zero( ) ;
20294 #tests_connect_socket( ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020295 #tests_probe_imapssl( ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010020296 #tests_cpu_number( ) ;
20297 #tests_mailimapclient_connect( ) ;
20298 tests_loadavg( ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010020299 #tests_always_fail( ) ;
20300
20301 note( 'Leaving testsdebug()' ) ;
20302 done_testing( ) ;
20303 }
20304 return ;
20305}
20306
20307
20308sub tests
20309{
20310 my $mysync = shift ;
20311 if ( ! $mysync->{ tests } ) { return ; }
20312
20313 SKIP: {
20314 skip 'No test in normal run' if ( ! $mysync->{ tests } ) ;
20315 note( 'Entering tests()' ) ;
20316 tests_folder_routines( ) ;
20317 tests_compare_lists( ) ;
20318 tests_regexmess( ) ;
20319 tests_skipmess( ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020320 tests_regexflags( );
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010020321 tests_ucsecond( ) ;
20322 tests_permanentflags();
20323 tests_flags_filter( ) ;
20324 tests_separator_invert( ) ;
20325 tests_imap2_folder_name( ) ;
20326 tests_command_line_nopassword( ) ;
20327 tests_good_date( ) ;
20328 tests_max( ) ;
20329 tests_remove_not_num();
20330 tests_memory_consumption( ) ;
20331 tests_is_a_release_number();
20332 tests_imapsync_basename();
20333 tests_list_keys_in_2_not_in_1();
20334 tests_convert_sep_to_slash( ) ;
20335 tests_match_a_cache_file( ) ;
20336 tests_cache_map( ) ;
20337 tests_get_cache( ) ;
20338 tests_clean_cache( ) ;
20339 tests_clean_cache_2( ) ;
20340 tests_touch( ) ;
20341 tests_flagscase( ) ;
20342 tests_mkpath( ) ;
20343 tests_extract_header( ) ;
20344 tests_decompose_header( ) ;
20345 tests_epoch( ) ;
20346 tests_add_header( ) ;
20347 tests_cache_dir_fix( ) ;
20348 tests_cache_dir_fix_win( ) ;
20349 tests_filter_forbidden_characters( ) ;
20350 tests_cache_folder( ) ;
20351 tests_time_remaining( ) ;
20352 tests_decompose_regex( ) ;
20353 tests_backtick( ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010020354 tests_bytes_display_string_bin( ) ;
20355 tests_bytes_display_string_dec( ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010020356 tests_header_line_normalize( ) ;
20357 tests_fix_Inbox_INBOX_mapping( ) ;
20358 tests_max_line_length( ) ;
20359 tests_subject( ) ;
20360 tests_msgs_from_maxmin( ) ;
20361 tests_tmpdir_has_colon_bug( ) ;
20362 tests_sleep_max_messages( ) ;
20363 tests_sleep_max_bytes( ) ;
20364 tests_logfile( ) ;
20365 tests_setlogfile( ) ;
20366 tests_jux_utf8_old( ) ;
20367 tests_jux_utf8( ) ;
20368 tests_pipemess( ) ;
20369 tests_jux_utf8_list( ) ;
20370 tests_guess_prefix( ) ;
20371 tests_guess_separator( ) ;
20372 tests_format_for_imap_arg( ) ;
20373 tests_imapsync_id( ) ;
20374 tests_date_from_rcs( ) ;
20375 tests_quota_extract_storage_limit_in_bytes( ) ;
20376 tests_quota_extract_storage_current_in_bytes( ) ;
20377 tests_guess_special( ) ;
20378 tests_do_valid_directory( ) ;
20379 tests_delete1emptyfolders( ) ;
20380 tests_message_for_host2( ) ;
20381 tests_length_ref( ) ;
20382 tests_firstline( ) ;
20383 tests_diff_or_NA( ) ;
20384 tests_match_number( ) ;
20385 tests_all_defined( ) ;
20386 tests_special_from_folders_hash( ) ;
20387 tests_notmatch( ) ;
20388 tests_match( ) ;
20389 tests_get_options( ) ;
20390 tests_get_options_cgi_context( ) ;
20391 tests_rand32( ) ;
20392 tests_hashsynclocal( ) ;
20393 tests_hashsync( ) ;
20394 tests_output( ) ;
20395 tests_output_reset_with( ) ;
20396 tests_output_start( ) ;
20397 tests_check_last_release( ) ;
20398 tests_loadavg( ) ;
20399 tests_cpu_number( ) ;
20400 tests_load_and_delay( ) ;
20401 #tests_imapsping( ) ;
20402 #tests_tcpping( ) ;
20403 tests_sslcheck( ) ;
20404 tests_not_long_imapsync_version_public( ) ;
20405 tests_reconnect_if_needed( ) ;
20406 tests_reconnect_12_if_needed( ) ;
20407 tests_sleep_if_needed( ) ;
20408 tests_string_to_file( ) ;
20409 tests_file_to_string( ) ;
20410 tests_under_cgi_context( ) ;
20411 tests_umask( ) ;
20412 tests_umask_str( ) ;
20413 tests_set_umask( ) ;
20414 tests_createhashfileifneeded( ) ;
20415 tests_slash_to_underscore( ) ;
20416 tests_testsunit( ) ;
20417 tests_count_0s( ) ;
20418 tests_report_failures( ) ;
20419 tests_min( ) ;
20420 #tests_connect_socket( ) ;
20421 #tests_resolvrev( ) ;
20422 tests_usage( ) ;
20423 tests_version_from_rcs( ) ;
20424 tests_backslash_caret( ) ;
20425 #tests_mailimapclient_connect_bug( ) ; # it fails with Mail-IMAPClient <= 3.39
20426 tests_write_pidfile( ) ;
20427 tests_remove_pidfile_not_running( ) ;
20428 tests_match_a_pid_number( ) ;
20429 tests_prefix_seperator_invertion( ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020430 tests_is_integer( ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010020431 tests_integer_or_1( ) ;
20432 tests_is_number( ) ;
20433 tests_sig_install( ) ;
20434 tests_template( ) ;
20435 tests_split_around_equal( ) ;
20436 tests_toggle_sleep( ) ;
20437 tests_labels( ) ;
20438 tests_synclabels( ) ;
20439 tests_uidexpunge_or_expunge( ) ;
20440 tests_appendlimit_from_capability( ) ;
20441 tests_maxsize_setting( ) ;
20442 tests_mock_capability( ) ;
20443 tests_appendlimit( ) ;
20444 tests_capability_of( ) ;
20445 tests_search_in_array( ) ;
20446 tests_operators_and_exclam_precedence( ) ;
20447 tests_teelaunch( ) ;
20448 tests_logfileprepa( ) ;
20449 tests_useheader_suggestion( ) ;
20450 tests_nb_messages_in_2_not_in_1( ) ;
20451 tests_labels_add_subfolder2( ) ;
20452 tests_labels_remove_subfolder1( ) ;
20453 tests_resynclabels( ) ;
20454 tests_labels_remove_special( ) ;
20455 tests_uniq( ) ;
20456 tests_remove_from_requested_folders( ) ;
20457 tests_errors_log( ) ;
20458 tests_add_subfolder1_to_folderrec( ) ;
20459 tests_sanitize_subfolder( ) ;
20460 tests_remove_edging_blanks( ) ;
20461 tests_sanitize( ) ;
20462 tests_remove_last_char_if_is( ) ;
20463 tests_check_binary_embed_all_dyn_libs( ) ;
20464 tests_nthline( ) ;
20465 tests_secondline( ) ;
20466 tests_tail( ) ;
20467 tests_truncmess( ) ;
20468 tests_eta( ) ;
20469 tests_timesince( ) ;
20470 tests_timenext( ) ;
20471 tests_foldersize( ) ;
20472 tests_imapsync_context( ) ;
20473 tests_abort( ) ;
20474 tests_probe_imapssl( ) ;
20475 tests_mailimapclient_connect( ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020476 tests_checknoabletosearch( ) ;
20477 tests_errorsdump( ) ;
20478 tests_errorsanalyse( ) ;
20479 tests_most_common_error( ) ;
20480 tests_errorclassify( ) ;
20481 tests_error_type( ) ;
20482 tests_sanitize_host( ) ;
20483 tests_hmac_sha1_hex( ) ;
20484 tests_total_bytes_max_reached( ) ;
20485 tests_header_construct( ) ;
20486 tests_remove_doublequotes_if_any( ) ;
20487 tests_login_imap( ) ;
20488 tests_login_imap_oauth( ) ;
20489 tests_skipmess_neg( ) ;
20490 tests_localtimez( ) ;
20491 tests_file_to_array( ) ;
20492 tests_cpu_time( ) ;
20493 tests_cpu_percent( ) ;
20494 tests_cpu_percent_global( ) ;
20495 tests_flags_for_host2( ) ;
20496 tests_under_docker_context( ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010020497 tests_exit_value( ) ;
20498 tests_comment_of_error_type( ) ;
20499 tests_debugcontent( ) ;
20500 tests_compress_ssl( ) ;
20501 tests_compress( ) ;
20502 tests_get_options_extra( ) ;
20503 tests_get_options_from_string( ) ;
20504 tests_infos( ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010020505 #tests_resolv( ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020506
20507 # Those three are for later use, when webserver will be inside imapsync
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010020508 # or will be deleted them if I abandon the project.
20509 #tests_killpid_by_parent( ) ;
20510 #tests_killpid_by_brother( ) ;
20511 #tests_kill_zero( ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020512
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010020513 #tests_always_fail( ) ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010020514 done_testing( 1860 ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010020515 note( 'Leaving tests()' ) ;
20516 }
20517 return ;
20518}
20519
20520sub tests_template
20521{
20522 note( 'Entering tests_template()' ) ;
20523
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020524 is( undef, template( ), 'template: no args => undef' ) ;
20525 my $mysync = { } ;
20526 is( undef, template( $mysync ), 'template: undef => undef' ) ;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010020527 is_deeply( {}, {}, 'template: a hash is a hash' ) ;
20528 is_deeply( [], [], 'template: an array is an array' ) ;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020529
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010020530 note( 'Leaving tests_template()' ) ;
20531 return ;
20532}
20533
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020534sub template
20535{
20536 my $mysync = shift @ARG ;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010020537
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020020538 return ;
20539}