imapsync 442 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940
  1. #!/usr/bin/perl
  2. # $Id: imapsync,v 1.836 2017/09/05 16:14:53 gilles Exp gilles $
  3. # structure
  4. # pod documentation
  5. # pragmas
  6. # main program
  7. # global variables initialisation
  8. # get_options( ) ;
  9. # default values
  10. # folder loop
  11. # subroutines
  12. # sub usage {
  13. # pod documentation
  14. =pod
  15. =head1 NAME
  16. imapsync - Email IMAP tool for syncing, copying and migrating
  17. email mailboxes between two imap servers, one way,
  18. and without duplicates.
  19. =head1 VERSION
  20. This documentation refers to Imapsync $Revision: 1.836 $
  21. =head1 USAGE
  22. To synchronize the source imap account
  23. "test1" on server "test1.lamiral.info" with password "secret1"
  24. to the destination imap account
  25. "test2" on server "test2.lamiral.info" with password "secret2"
  26. do:
  27. imapsync \
  28. --host1 test1.lamiral.info --user1 test1 --password1 secret1 \
  29. --host2 test2.lamiral.info --user2 test2 --password2 secret2
  30. =head1 DESCRIPTION
  31. We sometimes need to transfer mailboxes from one imap server to
  32. another.
  33. Imapsync command is a tool allowing incremental and
  34. recursive imap transfers from one mailbox to another.
  35. By default all folders are transferred, recursively, meaning
  36. the whole folder hierarchy is taken, all messages in them,
  37. and all messages flags (\Seen \Answered \Flagged etc.)
  38. are synced too.
  39. Imapsync reduces the amount
  40. of data transferred by not transferring a given message
  41. if it resides already on both sides. Same specific headers
  42. and the transfer is done only once (by default it's
  43. "Message-Id:" and "Received:" lines but it can be changed with
  44. --useheader option).
  45. All flags are preserved, unread will stay unread, read will stay read,
  46. deleted will stay deleted.
  47. You can stop the transfer at any
  48. time and restart it later, imapsync works well with bad
  49. connections and interruptions.
  50. You can decide to delete the messages from the source mailbox
  51. after a successful transfer, it can be a good feature when migrating
  52. live mailboxes since messages will be only on one side.
  53. In that case, use the --delete1 option. Option --delete1 implies
  54. also option --expunge1 so all messages marked deleted on host1
  55. will be really deleted.
  56. A different scenario is synchronizing a mailbox B from another mailbox A
  57. in case you just want to keep a "live" copy of A in B.
  58. In that case --delete2 has to be used, it deletes messages in host2
  59. folder B that are not in host1 folder A. If you also need to destroy
  60. host2 folders that are not in host1 then use --delete2folders (see also
  61. --delete2foldersonly and --delete2foldersbutnot).
  62. Imapsync is not adequate for maintaining two active imap accounts
  63. in synchronization when the user plays independently on both sides.
  64. Use offlineimap (written by John Goerzen) or mbsync (written by
  65. Michael R. Elkins) for a 2 ways synchronization.
  66. =head1 OPTIONS
  67. usage: imapsync [options]
  68. Mandatory options are the six values, three on each sides,
  69. needed to log in into the IMAP servers, ie,
  70. a host, a username, and a password, two times.
  71. Conventions used:
  72. str means string
  73. int means integer
  74. reg means regular expression
  75. cmd means command
  76. --dry : Makes imapsync doing nothing for real, just print what
  77. would be done without --dry.
  78. =head2 OPTIONS/credentials
  79. --host1 str : Source or "from" imap server. Mandatory.
  80. --port1 int : Port to connect on host1. Default is 143, 993 if --ssl1
  81. --user1 str : User to login on host1. Mandatory.
  82. --password1 str : Password for the user1.
  83. --host2 str : "destination" imap server. Mandatory.
  84. --port2 int : Port to connect on host2. Default is 143, 993 if --ssl2
  85. --user2 str : User to login on host2. Mandatory.
  86. --password2 str : Password for the user2.
  87. --showpasswords : Shows passwords on output instead of "MASKED".
  88. Useful to restart a complete run by just reading the log,
  89. or to debug passwords. It's not a secure practice.
  90. --passfile1 str : Password file for the user1. It must contain the
  91. password on the first line. This option avoids to show
  92. the password on the command line like --password1 does.
  93. --passfile2 str : Password file for the user2. Contains the password.
  94. =head2 OPTIONS/encryption
  95. --nossl1 : Do not use a SSL connection on host1.
  96. --ssl1 : Use a SSL connection on host1. On by default if possible.
  97. --nossl2 : Do not use a SSL connection on host2.
  98. --ssl2 : Use a SSL connection on host2. On by default if possible.
  99. --notls1 : Do not use a TLS connection on host1.
  100. --tls1 : Use a TLS connection on host1. On by default if possible.
  101. --notls2 : Do not use a TLS connection on host2.
  102. --tls2 : Use a TLS connection on host2. On by default if possible.
  103. --debugssl int : SSL debug mode from 0 to 4.
  104. --sslargs1 str : Pass any ssl parameter for host1 ssl or tls connection. Example:
  105. --sslargs1 SSL_verify_mode=1 --sslargs1 SSL_version=SSLv3
  106. See all possibilities in the new() method of IO::Socket::SSL
  107. http://search.cpan.org/perldoc?IO::Socket::SSL#Description_Of_Methods
  108. --sslargs2 str : Pass any ssl parameter for host2 ssl or tls connection.
  109. See --sslargs1
  110. --timeout1 int : Connection timeout in seconds for host1.
  111. Default is 120 and 0 means no timeout at all.
  112. --timeout2 int : Connection timeout in seconds for host2.
  113. Default is 120 and 0 means no timeout at all.
  114. =head2 OPTIONS/authentication
  115. --authmech1 str : Auth mechanism to use with host1:
  116. PLAIN, LOGIN, CRAM-MD5 etc. Use UPPERCASE.
  117. --authmech2 str : Auth mechanism to use with host2. See --authmech1
  118. --authuser1 str : User to auth with on host1 (admin user).
  119. Avoid using --authmech1 SOMETHING with --authuser1.
  120. --authuser2 str : User to auth with on host2 (admin user).
  121. --proxyauth1 : Use proxyauth on host1. Requires --authuser1.
  122. Required by Sun/iPlanet/Netscape IMAP servers to
  123. be able to use an administrative user.
  124. --proxyauth2 : Use proxyauth on host2. Requires --authuser2.
  125. --authmd51 : Use MD5 authentication for host1.
  126. --authmd52 : Use MD5 authentication for host2.
  127. --domain1 str : Domain on host1 (NTLM authentication).
  128. --domain2 str : Domain on host2 (NTLM authentication).
  129. =head2 OPTIONS/folders
  130. --folder str : Sync this folder.
  131. --folder str : and this one, etc.
  132. --folderrec str : Sync this folder recursively.
  133. --folderrec str : and this one, etc.
  134. --folderfirst str : Sync this folder first. --folderfirst "Work"
  135. --folderfirst str : then this one, etc.
  136. --folderlast str : Sync this folder last. --folderlast "[Gmail]/All Mail"
  137. --folderlast str : then this one, etc.
  138. --nomixfolders : Do not merge folders when host1 is case-sensitive
  139. while host2 is not (like Exchange). Only the first
  140. similar folder is synced (ex: Sent SENT sent -> Sent).
  141. --skipemptyfolders : Empty host1 folders are not created on host2.
  142. --include reg : Sync folders matching this regular expression
  143. --include reg : or this one, etc.
  144. If both --include --exclude options are used, then
  145. include is done before.
  146. --exclude reg : Skips folders matching this regular expression
  147. Several folders to avoid:
  148. --exclude 'fold1|fold2|f3' skips fold1, fold2 and f3.
  149. --exclude reg : or this one, etc.
  150. --subfolder2 str : Move whole host1 folders hierarchy under this
  151. host2 folder str .
  152. It does it by adding two --regextrans2 options before
  153. all others. Add --debug to see what's really going on.
  154. --automap : guesses folders mapping, for folders like
  155. "Sent", "Junk", "Drafts", "All", "Archive", "Flagged".
  156. --f1f2 str1=str2 : Force folder str1 to be synced to str2,
  157. --f1f2 overrides --automap and --regextrans2.
  158. --nomixfolders : Avoid merging folders that are considered different on
  159. host1 but the same on destination host2 because of
  160. case sensitivities and insensitivities.
  161. --subscribed : Transfers subscribed folders.
  162. --subscribe : Subscribe to the folders transferred on the
  163. host2 that are subscribed on host1. On by default.
  164. --subscribeall : Subscribe to the folders transferred on the
  165. host2 even if they are not subscribed on host1.
  166. --prefix1 str : Remove prefix str to all destination folders,
  167. usually INBOX. or INBOX/ or an empty string "".
  168. imapsync guesses the prefix if host1 imap server
  169. does not have NAMESPACE capability. This option
  170. should not be used, most of the time.
  171. --prefix2 str : Add prefix to all host2 folders. See --prefix1
  172. --sep1 str : Host1 separator in case NAMESPACE is not supported.
  173. --sep2 str : Host2 separator in case NAMESPACE is not supported.
  174. --regextrans2 reg : Apply the whole regex to each destination folders.
  175. --regextrans2 reg : and this one. etc.
  176. When you play with the --regextrans2 option, first
  177. add also the safe options --dry --justfolders
  178. Then, when happy, remove --dry, remove --justfolders.
  179. Have in mind that --regextrans2 is applied after prefix
  180. and separator inversion. For examples see
  181. http://imapsync.lamiral.info/FAQ.d/FAQ.Folders_Mapping.txt
  182. =head2 OPTIONS/folders sizes
  183. --nofoldersizes : Do not calculate the size of each folder at the
  184. beginning of the sync. Default is to calculate them.
  185. --nofoldersizesatend: Do not calculate the size of each folder at the
  186. end of the sync. Default is to calculate them.
  187. --justfoldersizes : Exit after having printed the initial folder sizes.
  188. =head2 OPTIONS/tmp
  189. --tmpdir str : Where to store temporary files and subdirectories.
  190. Will be created if it doesn't exist.
  191. Default is system specific, Unix is /tmp but
  192. /tmp is often too small and deleted at reboot.
  193. --tmpdir /var/tmp should be better.
  194. --pidfile str : The file where imapsync pid is written,
  195. it can be dirname/filename.
  196. Default name is imapsync.pid in tmpdir.
  197. --pidfilelocking : Abort if pidfile already exists. Useful to avoid
  198. concurrent transfers on the same mailbox.
  199. =head2 OPTIONS/log
  200. --nolog : Turn off logging on file
  201. --logfile str : Change the default log filename (can be dirname/filename).
  202. --logdir str : Change the default log directory. Default is LOG_imapsync/
  203. =head2 OPTIONS/messages
  204. --skipmess reg : Skips messages matching the regex.
  205. Example: 'm/[\x80-ff]/' # to avoid 8bits messages.
  206. --skipmess is applied before --regexmess
  207. --skipmess reg : or this one, etc.
  208. --pipemess cmd : Apply this cmd command to each message content
  209. before the copy.
  210. --pipemess cmd : and this one, etc.
  211. --disarmreadreceipts : Disarms read receipts (host2 Exchange issue)
  212. --regexmess reg : Apply the whole regex to each message before transfer.
  213. Example: 's/\000/ /g' # to replace null by space.
  214. --regexmess reg : and this one, etc.
  215. =head2 OPTIONS/flags
  216. --regexflag reg : Apply the whole regex to each flags list.
  217. Example: 's/"Junk"//g' # to remove "Junk" flag.
  218. --regexflag reg : then this one, etc.
  219. =head2 OPTIONS/deletions
  220. --delete1 : Deletes messages on host1 server after a successful
  221. transfer. Option --delete1 has the following behavior:
  222. it marks messages as deleted with the IMAP flag
  223. \Deleted, then messages are really deleted with an
  224. EXPUNGE IMAP command. If expunging after each message
  225. slows down too much the sync then use
  226. --noexpungeaftereach to speed up.
  227. --expunge1 : Expunge messages on host1 just before syncing a folder.
  228. Expunge is done per folder.
  229. Expunge aims is to really delete messages marked deleted.
  230. An expunge is also done after each message copied
  231. if option --delete1 is set.
  232. --noexpunge1 : Do not expunge messages on host1.
  233. --delete1emptyfolders : Deletes empty folders on host1, INBOX excepted.
  234. Useful with --delete1 since what remains on host1
  235. is only what failed to be synced.
  236. --delete2 : Delete messages in host2 that are not in
  237. host1 server. Useful for backup or pre-sync.
  238. --delete2duplicates : Delete messages in host2 that are duplicates.
  239. Works only without --useuid since duplicates are
  240. detected with an header part of each message.
  241. --delete2folders : Delete folders in host2 that are not in host1 server.
  242. For safety, first try it like this (it is safe):
  243. --delete2folders --dry --justfolders --nofoldersizes
  244. --delete2foldersonly reg : Deleted only folders matching regex.
  245. Example: --delete2foldersonly "/^Junk$|^INBOX.Junk$/"
  246. --delete2foldersbutnot reg : Do not delete folders matching regex.
  247. Example: --delete2foldersbutnot "/Tasks$|Contacts$|Foo$/"
  248. --expunge2 : Expunge messages on host2 after messages transfer.
  249. --uidexpunge2 : uidexpunge messages on the host2 account
  250. that are not on the host1 account, requires --delete2
  251. =head2 OPTIONS/dates
  252. --syncinternaldates : Sets the internal dates on host2 same as host1.
  253. Turned on by default. Internal date is the date
  254. a message arrived on a host (mtime).
  255. --idatefromheader : Sets the internal dates on host2 same as the
  256. "Date:" headers.
  257. =head2 OPTIONS/message selection
  258. --maxsize int : Skip messages larger (or equal) than int bytes
  259. --minsize int : Skip messages smaller (or equal) than int bytes
  260. --maxage int : Skip messages older than int days.
  261. final stats (skipped) don't count older messages
  262. see also --minage
  263. --minage int : Skip messages newer than int days.
  264. final stats (skipped) don't count newer messages
  265. You can do (+ are the messages selected):
  266. past|----maxage+++++++++++++++>now
  267. past|+++++++++++++++minage---->now
  268. past|----maxage+++++minage---->now (intersection)
  269. past|++++minage-----maxage++++>now (union)
  270. --search str : Selects only messages returned by this IMAP SEARCH
  271. command. Applied on both sides.
  272. --search1 str : Same as --search for selecting host1 messages only.
  273. --search2 str : Same as --search for selecting host2 messages only.
  274. --search CRIT equals --search1 CRIT --search2 CRIT
  275. --maxlinelength int : skip messages with a line length longer than int bytes.
  276. RFC 2822 says it must be no more than 1000 bytes.
  277. --useheader str : Use this header to compare messages on both sides.
  278. Ex: Message-ID or Subject or Date.
  279. --useheader str and this one, etc.
  280. --usecache : Use cache to speed up the sync.
  281. --nousecache : Do not use cache. Caveat: --useuid --nousecache creates
  282. duplicates on multiple runs.
  283. --useuid : Use uid instead of header as a criterium to recognize
  284. messages. Option --usecache is then implied unless
  285. --nousecache is used.
  286. =head2 OPTIONS/miscelaneous
  287. --syncacls : Synchronizes acls (Access Control Lists).
  288. --nosyncacls : Does not synchronize acls. This is the default.
  289. Acls in IMAP are not standardized, be careful.
  290. =head2 OPTIONS/debugging
  291. --debug : Debug mode.
  292. --debugfolders : Debug mode for the folders part only.
  293. --debugcontent : Debug content of the messages transferred. Huge output.
  294. --debugflags : Debug mode for flags.
  295. --debugimap1 : IMAP debug mode for host1. Very verbose.
  296. --debugimap2 : IMAP debug mode for host2. Very verbose.
  297. --debugimap : IMAP debug mode for host1 and host2.
  298. --debugmemory : Debug mode showing memory consumption after each copy.
  299. --errorsmax int : Exit when int number of errors is reached. Default is 50.
  300. --tests : Run local non-regression tests. Exit code 0 means all ok.
  301. --testslive : Run a live test with test1.lamiral.info imap server.
  302. Useful to check the basics. Needs internet connexion.
  303. --testslive6 : Run a live test with ks2ipv6.lamiral.info imap server.
  304. Useful to check the ipv6 connectivity. Needs internet.
  305. =head2 OPTIONS/specific
  306. --gmail1 : sets --host1 to Gmail and options from FAQ.Gmail.txt
  307. --gmail2 : sets --host2 to Gmail and options from FAQ.Gmail.txt
  308. --office1 : sets --host1 to Office365 options from FAQ.Exchange.txt
  309. --office2 : sets --host2 to Office365 options from FAQ.Exchange.txt
  310. --exchange1 : sets options from FAQ.Exchange.txt, account1 part
  311. --exchange2 : sets options from FAQ.Exchange.txt, account2 part
  312. --domino1 : sets options from FAQ.Domino.txt, account1 part
  313. --domino2 : sets options from FAQ.Domino.txt, account2 part
  314. =head2 OPTIONS/behavior
  315. --maxmessagespersecond int : limits the number of messages transferred per second.
  316. --maxbytespersecond int : limits the average transfer rate per second.
  317. --maxbytesafter int : starts --maxbytespersecond limitation only after
  318. --maxbytesafter amount of data transferred.
  319. --maxsleep int : do not sleep more than int seconds.
  320. On by default, 2 seconds max, like --maxsleep 2
  321. --abort : terminates a previous call still running.
  322. It uses the pidfile to know what processus to abort.
  323. --exitwhenover int : Stop syncing when total bytes transferred reached.
  324. --version : Print only software version.
  325. --noreleasecheck : Do not check for new imapsync release (a http request).
  326. --releasecheck : Check for new imapsync release (a http request).
  327. --noid : Do not send/receive ID command to imap servers.
  328. --justconnect : Just connect to both servers and print useful
  329. information. Need only --host1 and --host2 options.
  330. --justlogin : Just login to both host1 and host2 with users
  331. credentials, then exit.
  332. --justfolders : Do only things about folders (ignore messages).
  333. --help : print this help.
  334. Example: to synchronize imap account "test1" on "test1.lamiral.info"
  335. to imap account "test2" on "test2.lamiral.info"
  336. with test1 password "secret1"
  337. and test2 password "secret2"
  338. imapsync \
  339. --host1 test1.lamiral.info --user1 test1 --password1 secret1 \
  340. --host2 test2.lamiral.info --user2 test2 --password2 secret2
  341. =cut
  342. # comment
  343. =pod
  344. =head1 SECURITY
  345. You can use --passfile1 instead of --password1 to give the
  346. password since it is safer. With --password1 option any user
  347. on your host can see the password by using the 'ps auxwwww'
  348. command. Using a variable (like $PASSWORD1) is also
  349. dangerous because of the 'ps auxwwwwe' command. So, saving
  350. the password in a well protected file (600 or rw-------) is
  351. the best solution.
  352. Imapsync activates ssl or tls encryption by default, if possible.
  353. What details are under this "if possible"?
  354. Imapsync activates ssl if the well known port imaps port (993) is open
  355. on the imap servers. If the imaps port is closed then it open a
  356. normal (clear) connection on port 143 but it looks for TLS support
  357. in the CAPABILITY list of the servers. If TLS is supported
  358. then imapsync goes to encryption.
  359. If the automatic ssl/tls detection fails then imapsync will
  360. not protect against sniffing activities on the
  361. network, especially for passwords.
  362. See also the document FAQ.Security.txt in the FAQ.d/ directory
  363. or at https://imapsync.lamiral.info/FAQ.d/FAQ.Security.txt
  364. =head1 EXIT STATUS
  365. Imapsync will exit with a 0 status (return code) if everything went good.
  366. Otherwise, it exits with a non-zero status.
  367. =head1 LICENSE AND COPYRIGHT
  368. Imapsync is free, open, public but not always gratis software
  369. cover by the NOLIMIT Public License.
  370. See the LICENSE file included in the distribution or just read this
  371. simple sentence as it IS the licence text:
  372. "No limit to do anything with this work and this license."
  373. In case it is not long enough, I repeat:
  374. "No limit to do anything with this work and this license."
  375. https://imapsync.lamiral.info/LICENSE
  376. =head1 AUTHOR
  377. Gilles LAMIRAL <gilles.lamiral@laposte.net>
  378. Feedback good or bad is very often welcome.
  379. Gilles LAMIRAL earns his living by writing, installing,
  380. configuring and teaching free, open and often gratis
  381. software. Imapsync used to be "always gratis" but now it is
  382. only "often gratis" because imapsync is sold by its author,
  383. a good way to maintain and support free open public
  384. software over decades.
  385. =head1 BUGS AND LIMITATIONS
  386. See https://imapsync.lamiral.info/FAQ.d/FAQ.Reporting_Bugs.txt
  387. =head1 IMAP SERVERS supported
  388. See https://imapsync.lamiral.info/S/imapservers.shtml
  389. =head1 HUGE MIGRATION
  390. Pay special attention to options
  391. --subscribed
  392. --subscribe
  393. --delete1
  394. --delete2
  395. --delete2folders
  396. --maxage
  397. --minage
  398. --maxsize
  399. --useuid
  400. --usecache
  401. If you have many mailboxes to migrate think about a little
  402. shell program. Write a file called file.txt (for example)
  403. containing users and passwords.
  404. The separator used in this example is ';'
  405. The file.txt file contains:
  406. user001_1;password001_1;user001_2;password001_2
  407. user002_1;password002_1;user002_2;password002_2
  408. user003_1;password003_1;user003_2;password003_2
  409. user004_1;password004_1;user004_2;password004_2
  410. user005_1;password005_1;user005_2;password005_2
  411. ...
  412. On Unix the shell program can be:
  413. { while IFS=';' read u1 p1 u2 p2; do
  414. imapsync --host1 imap.side1.org --user1 "$u1" --password1 "$p1" \
  415. --host2 imap.side2.org --user2 "$u2" --password2 "$p2" ...
  416. done ; } < file.txt
  417. On Windows the batch program can be:
  418. FOR /F "tokens=1,2,3,4 delims=; eol=#" %%G IN (file.txt) DO imapsync ^
  419. --host1 imap.side1.org --user1 %%G --password1 %%H ^
  420. --host2 imap.side2.org --user2 %%I --password2 %%J ...
  421. The ... have to be replaced by nothing or any imapsync option.
  422. Welcome in shell or batch programming !
  423. You will find already written scripts at
  424. http://imapsync.lamiral.info/examples/
  425. =head1 INSTALL
  426. Imapsync works under any Unix with perl.
  427. Imapsync works under Windows (2000, XP, Vista, Seven)
  428. as a standalone binary software called imapsync.exe
  429. Imapsync works under OS X as a standalone binary
  430. software called imapsync_bin_Darwin.
  431. Purchase latest imapsync at
  432. http://imapsync.lamiral.info/
  433. You'll receive a link to a compressed tarball called imapsync-x.xx.tgz
  434. where x.xx is the version number. Untar the tarball where
  435. you want (on Unix):
  436. tar xzvf imapsync-x.xx.tgz
  437. Go into the directory imapsync-x.xx and read the INSTALL file.
  438. As mentioned at http://imapsync.lamiral.info/#install
  439. the INSTALL file can also be found at
  440. http://imapsync.lamiral.info/INSTALL
  441. It is now split in several files for each system
  442. http://imapsync.lamiral.info/INSTALL.d/
  443. =head1 CONFIGURATION
  444. There is no specific configuration file for imapsync,
  445. everything is specified by the command line parameters
  446. and the default behavior.
  447. =head1 HACKING
  448. Feel free to hack imapsync as the NOLIMIT license permits it.
  449. =head1 SIMILAR SOFTWARES
  450. imap_tools : http://www.athensfbc.com/imap_tools
  451. offlineimap : https://github.com/nicolas33/offlineimap
  452. mbsync : http://isync.sourceforge.net/
  453. mailsync : http://mailsync.sourceforge.net/
  454. mailutil : http://www.washington.edu/imap/
  455. part of the UW IMAP tookit.
  456. imaprepl : http://www.bl0rg.net/software/
  457. http://freecode.com/projects/imap-repl/
  458. imapcopy : http://home.arcor.de/armin.diehl/imapcopy/imapcopy.html
  459. migrationtool : http://sourceforge.net/projects/migrationtool/
  460. imapmigrate : http://sourceforge.net/projects/cyrus-utils/
  461. wonko_imapsync: http://wonko.com/article/554
  462. see also file W/tools/wonko_ruby_imapsync
  463. exchange-away : http://exchange-away.sourceforge.net/
  464. pop2imap : http://www.linux-france.org/prj/pop2imap/
  465. Feedback (good or bad) will often be welcome.
  466. =head1 HISTORY
  467. I wrote imapsync because an enterprise (basystemes) paid me to install
  468. a new imap server without losing huge old mailboxes located in a far
  469. away remote imap server, accessible by a low-bandwidth link. The tool
  470. imapcp (written in python) could not help me because I had to verify
  471. every mailbox was well transferred, and then delete it after a good
  472. transfer. Imapsync started its life as a patch of the copy_folder.pl
  473. script. The script copy_folder.pl comes from the Mail-IMAPClient-2.1.3 perl
  474. module tarball source (more precisely in the examples/ directory of the
  475. Mail-IMAPClient tarball).
  476. =cut
  477. # pragmas
  478. use strict ;
  479. use warnings ;
  480. use Carp ;
  481. use Data::Dumper ;
  482. use Digest::HMAC_SHA1 qw( hmac_sha1 hmac_sha1_hex ) ;
  483. use Digest::MD5 qw( md5 md5_hex md5_base64 ) ;
  484. use English qw( -no_match_vars ) ;
  485. use Errno qw(EAGAIN EPIPE ECONNRESET) ;
  486. use Fcntl ;
  487. use File::Basename ;
  488. use File::Copy::Recursive ;
  489. use File::Glob qw( :glob ) ;
  490. use File::Path qw( mkpath rmtree ) ;
  491. use File::Spec ;
  492. use File::stat ;
  493. use Getopt::Long ( ) ;
  494. use IO::File ;
  495. use IO::Socket qw( :crlf SOL_SOCKET SO_KEEPALIVE ) ;
  496. use IO::Socket::INET6 ;
  497. use IO::Socket::SSL ;
  498. use IO::Tee ;
  499. use IPC::Open3 'open3' ;
  500. use Mail::IMAPClient 3.30 ;
  501. use MIME::Base64 ;
  502. use Pod::Usage qw(pod2usage) ;
  503. use POSIX qw(uname SIGALRM) ;
  504. use Sys::Hostname ;
  505. use Term::ReadKey ;
  506. use Test::More ;
  507. use Time::HiRes qw( time sleep ) ;
  508. use Time::Local ;
  509. use Unicode::String ;
  510. use Cwd ;
  511. use Readonly ;
  512. #use Net::Ping ;
  513. use Sys::MemInfo ;
  514. local $OUTPUT_AUTOFLUSH = 1 ;
  515. # constants
  516. # Let us do like sysexits.h
  517. # /usr/include/sysexits.h
  518. Readonly my $EX_OK => 0 ; #/* successful termination */
  519. Readonly my $EX_USAGE => 64 ; #/* command line usage error */
  520. #Readonly my $EX_DATAERR => 65 ; #/* data format error */
  521. Readonly my $EX_NOINPUT => 66 ; #/* cannot open input */
  522. #Readonly my $EX_NOUSER => 67 ; #/* addressee unknown */
  523. #Readonly my $EX_NOHOST => 68 ; #/* host name unknown */
  524. Readonly my $EX_UNAVAILABLE => 69 ; #/* service unavailable */
  525. Readonly my $EX_SOFTWARE => 70 ; #/* internal software error */
  526. #Readonly my $EX_OSERR => 71 ; #/* system error (e.g., can't fork) */
  527. #Readonly my $EX_OSFILE => 72 ; #/* critical OS file missing */
  528. #Readonly my $EX_CANTCREAT => 73 ; #/* can't create (user) output file */
  529. #Readonly my $EX_IOERR => 74 ; #/* input/output error */
  530. #Readonly my $EX_TEMPFAIL => 75 ; #/* temp failure; user is invited to retry */
  531. #Readonly my $EX_PROTOCOL => 76 ; #/* remote error in protocol */
  532. #Readonly my $EX_NOPERM => 77 ; #/* permission denied */
  533. #Readonly my $EX_CONFIG => 78 ; #/* configuration error */
  534. # Mine
  535. Readonly my $EXIT_BY_SIGNAL => 6 ;
  536. Readonly my $EXIT_PID_FILE_ALREADY_EXIST => 8 ;
  537. Readonly my $EXIT_WITH_ERRORS => 111 ;
  538. Readonly my $EXIT_WITH_ERRORS_MAX => 112 ;
  539. Readonly my $EXIT_UNKNOWN => 126 ;
  540. Readonly my $EXIT_TESTS_FAILED => 254 ; # Like Test::More API
  541. Readonly my $ERRORS_MAX => 50 ; # exit after 50 errors.
  542. Readonly my $ERRORS_MAX_CGI => 20 ; # exit after 20 errors in CGI context.
  543. Readonly my $INTERVAL_TO_EXIT => 2 ; # interval max to exit instead of reconnect
  544. Readonly my $SPLIT => 100 ; # By default, 100 at a time, not more.
  545. Readonly my $SPLIT_FACTOR => 10 ; # init_imap() calls Maxcommandlength( $SPLIT_FACTOR * $split )
  546. # which means default Maxcommandlength is 10*100 = 1000 characters ;
  547. Readonly my $IMAP_PORT => 143 ; # Well know port for IMAP
  548. Readonly my $IMAP_SSL_PORT => 993 ; # Well know port for IMAP over SSL
  549. Readonly my $LAST => -1 ;
  550. Readonly my $MINUS_ONE => -1 ;
  551. Readonly my $RELEASE_NUMBER_EXAMPLE_1 => '1.351' ;
  552. Readonly my $RELEASE_NUMBER_EXAMPLE_2 => 42.4242 ;
  553. Readonly my $TCP_PING_TIMEOUT => 5 ;
  554. Readonly my $DEFAULT_TIMEOUT => 120 ;
  555. Readonly my $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND => 3 ;
  556. Readonly my $DEFAULT_UIDNEXT => 999_999 ;
  557. Readonly my $DEFAULT_BUFFER_SIZE => 4096 ;
  558. Readonly my $MAX_SLEEP => 2 ; # 2 seconds max for limiting too long sleeps from --maxbytespersecond and --maxmessagespersecond
  559. Readonly my $DEFAULT_EXPIRATION_TIME_OAUTH2_PK12 => 3600 ;
  560. Readonly my $PERMISSION_FILTER => 7777 ;
  561. Readonly my $KIBI => 1024 ;
  562. Readonly my $NUMBER_10 => 10 ;
  563. Readonly my $NUMBER_42 => 42 ;
  564. Readonly my $NUMBER_100 => 100 ;
  565. Readonly my $NUMBER_200 => 200 ;
  566. Readonly my $NUMBER_300 => 300 ;
  567. Readonly my $NUMBER_20_000 => 20_000 ;
  568. Readonly my $QUOTA_PERCENT_LIMIT => 90 ;
  569. Readonly my $NUMBER_104_857_600 => 104_857_600 ;
  570. Readonly my $SIZE_MAX_STR => 64 ;
  571. Readonly my $NB_SECONDS_IN_A_DAY => 86_400 ;
  572. Readonly my $STD_CHAR_PER_LINE => 80 ;
  573. Readonly my $TRUE => 1 ;
  574. Readonly my $FALSE => 0 ;
  575. Readonly my $LAST_RESSORT_SEPARATOR => q{/} ;
  576. Readonly my $CGI_TMPDIR_TOP => '/var/tmp/imapsync_cgi' ;
  577. Readonly my $CGI_HASHFILE => '/var/tmp/imapsync_hash' ;
  578. Readonly my $UMASK_PARANO => '0077' ;
  579. # global variables
  580. my(
  581. $sync,
  582. $debug, $debugimap, $debugimap1, $debugimap2, $debugcontent, $debugflags,
  583. $debuglist, $debugdev, $debugmaxlinelength, @debugbasket, $debugcgi,
  584. $domain1, $domain2,
  585. $passfile1, $passfile2,
  586. @folder, @include, @exclude, @folderrec,
  587. @folderfirst, @folderlast,
  588. $prefix1, $prefix2,
  589. $subfolder2,
  590. @regextrans2, @regexmess, @regexflag, @skipmess, @pipemess, $pipemesscheck,
  591. $flagscase, $filterflags, $syncflagsaftercopy,
  592. $sep1, $sep2,
  593. $syncinternaldates,
  594. $idatefromheader,
  595. $syncacls,
  596. $fastio1, $fastio2,
  597. $maxsize, $minsize, $maxage, $minage,
  598. $exitwhenover,
  599. $search, $search1, $search2,
  600. $skipheader, @useheader,
  601. $skipsize, $allowsizemismatch, $foldersizes, $foldersizesatend, $buffersize,
  602. $delete1, $delete2, $delete2duplicates,
  603. $expunge1, $expunge2, $uidexpunge2,
  604. $justfoldersizes,
  605. $authmd5, $authmd51, $authmd52,
  606. $subscribed, $subscribe, $subscribeall,
  607. $version, $help,
  608. $justconnect, $justfolders, $justbanner,
  609. $fast,
  610. $total_bytes_skipped,
  611. $total_bytes_error,
  612. $nb_msg_skipped,
  613. $nb_msg_skipped_dry_mode,
  614. $h1_nb_msg_duplicate,
  615. $h2_nb_msg_duplicate,
  616. $h1_nb_msg_noheader,
  617. $h2_nb_msg_noheader,
  618. $h1_total_bytes_duplicate,
  619. $h2_total_bytes_duplicate,
  620. $h1_nb_msg_deleted,
  621. $h2_nb_msg_deleted,
  622. $h1_bytes_processed,
  623. $h1_nb_msg_processed,
  624. $h1_nb_msg_start, $h1_bytes_start,
  625. $h2_nb_msg_start, $h2_bytes_start,
  626. $h1_nb_msg_end, $h1_bytes_end,
  627. $h2_nb_msg_end, $h2_bytes_end,
  628. $timeout,
  629. $timestart_int,
  630. $timebefore,
  631. $uid1, $uid2,
  632. $authuser1, $authuser2,
  633. $proxyauth1, $proxyauth2,
  634. $authmech1, $authmech2,
  635. $split1, $split2,
  636. $reconnectretry1, $reconnectretry2,
  637. $justlogin,
  638. $tmpdir,
  639. $releasecheck,
  640. $max_msg_size_in_bytes,
  641. $modulesversion,
  642. $delete2folders, $delete2foldersonly, $delete2foldersbutnot,
  643. $usecache, $debugcache, $cacheaftercopy,
  644. $wholeheaderifneeded, %h1_msgs_copy_by_uid, $useuid, $h2_uidguess,
  645. $addheader,
  646. %h1, %h2,
  647. $checkselectable, $checkmessageexists,
  648. $expungeaftereach,
  649. $fixslash2,
  650. $messageidnodomain,
  651. $fixInboxINBOX,
  652. $maxlinelength, $maxlinelengthcmd,
  653. $minmaxlinelength,
  654. $uidnext_default,
  655. $fixcolonbug,
  656. $create_folder_old,
  657. $skipcrossduplicates, $debugcrossduplicates,
  658. $disarmreadreceipts,
  659. $mixfolders, $skipemptyfolders,
  660. $fetch_hash_set,
  661. );
  662. # main program
  663. # global variables initialisation
  664. # Currently removing all global variables except $sync
  665. # passing each of them under $sync->{variable_name}
  666. $sync->{timestart} = time ; # Is a float because of use Time::HiRres
  667. $sync->{rcs} = q{$Id: imapsync,v 1.836 2017/09/05 16:14:53 gilles Exp gilles $} ;
  668. my @loadavg = loadavg( ) ;
  669. $sync->{cpu_number} = cpu_number( ) ;
  670. $sync->{loaddelay} = load_and_delay( $sync->{cpu_number}, @loadavg ) ;
  671. $sync->{loadavg} = join( q{ }, @loadavg ) . " on $sync->{cpu_number} cores." ;
  672. $sync->{total_bytes_transferred} = 0 ;
  673. $total_bytes_skipped = 0;
  674. $total_bytes_error = 0;
  675. $sync->{nb_msg_transferred} = 0;
  676. $nb_msg_skipped = $nb_msg_skipped_dry_mode = 0;
  677. $h1_nb_msg_deleted = $h2_nb_msg_deleted = 0;
  678. $h1_nb_msg_duplicate = $h2_nb_msg_duplicate = 0;
  679. $h1_nb_msg_noheader = $h2_nb_msg_noheader = 0;
  680. $h1_total_bytes_duplicate = $h2_total_bytes_duplicate = 0;
  681. $h1_nb_msg_start = $h1_bytes_start = 0 ;
  682. $h2_nb_msg_start = $h2_bytes_start = 0 ;
  683. $h1_nb_msg_processed = $h1_bytes_processed = 0 ;
  684. #$h1_nb_msg_end = $h1_bytes_end = 0 ;
  685. #$h2_nb_msg_end = $h2_bytes_end = 0 ;
  686. $sync->{nb_errors} = 0;
  687. $max_msg_size_in_bytes = 0;
  688. my %month_abrev = (
  689. Jan => '00',
  690. Feb => '01',
  691. Mar => '02',
  692. Apr => '03',
  693. May => '04',
  694. Jun => '05',
  695. Jul => '06',
  696. Aug => '07',
  697. Sep => '08',
  698. Oct => '09',
  699. Nov => '10',
  700. Dec => '11',
  701. );
  702. my $cgidir ;
  703. # CGI environment in case
  704. cgibegin( $sync ) ;
  705. # In cgi context, printing must start by the header so we delay other prints by using output() storage
  706. my $options_good = get_options( $sync, @ARGV ) ;
  707. docker_context( $sync ) ;
  708. cgibuildheader( $sync ) ;
  709. myprint( output( $sync ) ) ;
  710. output_reset_with( $sync ) ;
  711. # Can break here if load is too heavy
  712. cgiload( $sync ) ;
  713. # don't go on if options are not all known.
  714. if ( ! defined $options_good ) { exit $EX_USAGE ; }
  715. # just the version
  716. myprint( imapsync_version( $sync ), "\n" ) and exit 0 if ( $version ) ;
  717. $sync->{debugenv} and printenv( $sync ) ; # if option --debugenv
  718. load_modules( ) ;
  719. # after_get_options call usage and exit if --help or options were not well got
  720. after_get_options( $options_good ) ;
  721. # Under CGI environment, fix caveat emptor potentiel issues
  722. cgisetcontext( $sync ) ;
  723. easyany( $sync ) ;
  724. $tmpdir ||= File::Spec->tmpdir( ) ;
  725. # Unit tests
  726. testsexit( $sync ) ;
  727. # init live varaiables
  728. testslive( $sync ) if ( $sync->{testslive} ) ;
  729. testslive6( $sync ) if ( $sync->{testslive6} ) ;
  730. #
  731. $sync->{pidfile} = defined $sync->{pidfile} ? $sync->{pidfile} : $tmpdir . '/imapsync.pid' ;
  732. $sync->{pidfilelocking} = defined $sync->{pidfilelocking} ? $sync->{pidfilelocking} : 0 ;
  733. if ( $sync->{abort} ) {
  734. abort( $sync ) ;
  735. }
  736. local $SIG{ INT } = sub {
  737. my $signame = shift ;
  738. catch_reconnect( $sync, $signame ) ;
  739. } ;
  740. local $SIG{ QUIT } = local $SIG{ TERM } = sub {
  741. my $signame = shift ;
  742. catch_exit( $sync, $signame ) ;
  743. } ;
  744. $sync->{log} = defined $sync->{log} ? $sync->{log} : 1 ;
  745. $sync->{errorsdump} = defined $sync->{errorsdump} ? $sync->{errorsdump} : 1 ;
  746. $sync->{errorsmax} = defined $sync->{errorsmax} ? $sync->{errorsmax} : $ERRORS_MAX ;
  747. if ( $sync->{log} ) {
  748. setlogfile( $sync ) ;
  749. teelaunch( $sync ) ;
  750. }
  751. $timestart_int = int( $sync->{timestart} ) ;
  752. $timebefore = $sync->{timestart} ;
  753. my $timestart_str = localtime( $sync->{timestart} ) ;
  754. myprint( "Transfer started at $timestart_str\n" ) ;
  755. myprint( "PID is $PROCESS_ID\n" ) ;
  756. myprint( "Log file is $sync->{logfile} ( to change it, use --logfile path ; or use --nolog to turn off logging )\n" ) if ( $sync->{log} ) ;
  757. myprint( "Load is " . ( join( q{ }, loadavg( ) ) || 'unknown' ), " on $sync->{cpu_number} cores\n" ) ;
  758. myprint( 'Current directory is ' . getcwd( ) . "\n" ) ;
  759. myprint( 'Real user id is ' . getpwuid_any_os( $REAL_USER_ID ) . " (uid $REAL_USER_ID)\n" ) ;
  760. myprint( 'Effective user id is ' . getpwuid_any_os( $EFFECTIVE_USER_ID ). " (euid $EFFECTIVE_USER_ID)\n" ) ;
  761. $modulesversion = defined $modulesversion ? $modulesversion : 1 ;
  762. # If you want releasecheck not to be done by default (like the github maintainer),
  763. # then just uncomment the first "$releasecheck =" line, the line ending with "0 ;",
  764. # the second line (ending with "1 ;") can then stay active or be commented,
  765. # the result will be the same: no releasecheck by default (because 0 is then defined value).
  766. $releasecheck = defined $releasecheck ? $releasecheck : 0 ;
  767. #$releasecheck = defined $releasecheck ? $releasecheck : 1 ;
  768. my $warn_release = ( $releasecheck ) ? check_last_release( ) : q{} ;
  769. $wholeheaderifneeded = defined $wholeheaderifneeded ? $wholeheaderifneeded : 1;
  770. # turn on RFC standard flags correction like \SEEN -> \Seen
  771. $flagscase = defined $flagscase ? $flagscase : 1 ;
  772. # Use PERMANENTFLAGS if available
  773. $filterflags = defined $filterflags ? $filterflags : 1 ;
  774. # sync flags just after an APPEND, some servers ignore the flags given in the APPEND
  775. # like MailEnable IMAP server.
  776. # Off by default since it takes time.
  777. $syncflagsaftercopy = defined $syncflagsaftercopy ? $syncflagsaftercopy : 0 ;
  778. # Activate --usecache if --useuid is set and no --nousecache
  779. $usecache = 1 if ( $useuid and ( ! defined $usecache ) ) ;
  780. $cacheaftercopy = 1 if ( $usecache and ( ! defined $cacheaftercopy ) ) ;
  781. $checkselectable = defined $checkselectable ? $checkselectable : 1 ;
  782. $checkmessageexists = defined $checkmessageexists ? $checkmessageexists : 0 ;
  783. $expungeaftereach = defined $expungeaftereach ? $expungeaftereach : 1 ;
  784. # abletosearch is on by default
  785. $sync->{abletosearch} = defined $sync->{abletosearch} ? $sync->{abletosearch} : 1 ;
  786. $sync->{abletosearch1} = defined $sync->{abletosearch1} ? $sync->{abletosearch1} : $sync->{abletosearch} ;
  787. $sync->{abletosearch2} = defined $sync->{abletosearch2} ? $sync->{abletosearch2} : $sync->{abletosearch} ;
  788. $checkmessageexists = 0 if ( not $sync->{abletosearch1} ) ;
  789. $sync->{showpasswords} = defined $sync->{showpasswords} ? $sync->{showpasswords} : 0 ;
  790. $fixslash2 = defined $fixslash2 ? $fixslash2 : 1 ;
  791. $fixInboxINBOX = defined $fixInboxINBOX ? $fixInboxINBOX : 1 ;
  792. $create_folder_old = defined $create_folder_old ? $create_folder_old : 0 ;
  793. $mixfolders = defined $mixfolders ? $mixfolders : 1 ;
  794. $sync->{automap} = defined $sync->{automap} ? $sync->{automap} : 0 ;
  795. $delete2duplicates = 1 if ( $delete2 and ( ! defined $delete2duplicates ) ) ;
  796. $sync->{maxmessagespersecond} = defined $sync->{maxmessagespersecond} ? $sync->{maxmessagespersecond} : 0 ;
  797. $sync->{maxbytespersecond} = defined $sync->{maxbytespersecond} ? $sync->{maxbytespersecond} : 0 ;
  798. $sync->{sslcheck} = defined $sync->{sslcheck} ? $sync->{sslcheck} : 1 ;
  799. myprint( banner_imapsync( @ARGV ) ) ;
  800. myprint( "Temp directory is $tmpdir ( to change it use --tmpdir dirpath )\n") ;
  801. myprint( output( $sync ) ) ;
  802. do_valid_directory( $tmpdir ) || croak "Error creating tmpdir $tmpdir : $OS_ERROR" ;
  803. if ( $sync->{pidfile} ) {
  804. write_pidfile( $sync->{pidfile}, $sync->{pidfilelocking} ) ;
  805. }
  806. if ( $sync->{simulong} ) { simulong( $sync->{simulong} ) ; }
  807. $fixcolonbug = defined $fixcolonbug ? $fixcolonbug : 1 ;
  808. if ( $usecache and $fixcolonbug ) { tmpdir_fix_colon_bug( ) } ;
  809. $modulesversion and myprint( "Modules version list:\n", modulesversion(), "( use --no-modulesversion to turn off printing this Perl modules list )\n" ) ;
  810. check_lib_version( ) or
  811. croak "imapsync needs perl lib Mail::IMAPClient release 3.30 or superior.\n";
  812. exit_clean( $sync, $EX_OK ) if ( $justbanner ) ;
  813. sslcheck( $sync ) ;
  814. $split1 ||= $SPLIT ;
  815. $split2 ||= $SPLIT ;
  816. $sync->{host1} || missing_option( '--host1' ) ;
  817. $sync->{port1} ||= ( $sync->{ssl1} ) ? $IMAP_SSL_PORT : $IMAP_PORT ;
  818. $sync->{host2} || missing_option( '--host2' ) ;
  819. $sync->{port2} ||= ( $sync->{ssl2} ) ? $IMAP_SSL_PORT : $IMAP_PORT ;
  820. $debugimap1 = $debugimap2 = 1 if ( $debugimap ) ;
  821. $debug = 1 if ( $debugimap1 or $debugimap2 ) ;
  822. # By default, don't take size to compare
  823. $skipsize = (defined $skipsize) ? $skipsize : 1;
  824. $uid1 = defined $uid1 ? $uid1 : 1;
  825. $uid2 = defined $uid2 ? $uid2 : 1;
  826. $subscribe = defined $subscribe ? $subscribe : 1;
  827. # Allow size mismatch by default
  828. $allowsizemismatch = defined $allowsizemismatch ? $allowsizemismatch : 1;
  829. if ( defined $delete2foldersbutnot or defined $delete2foldersonly ) {
  830. $delete2folders = 1 ;
  831. }
  832. my $DEFAULT_SSL_VERIFY ;
  833. my %SSL_VERIFY_STR ;
  834. Readonly $DEFAULT_SSL_VERIFY => IO::Socket::SSL::SSL_VERIFY_NONE( ) ;
  835. Readonly %SSL_VERIFY_STR => (
  836. IO::Socket::SSL::SSL_VERIFY_NONE( ) => 'SSL_VERIFY_NONE' ,
  837. IO::Socket::SSL::SSL_VERIFY_PEER( ) => 'SSL_VERIFY_PEER' ,
  838. ) ;
  839. $IO::Socket::SSL::DEBUG = $sync->{debugssl} || 1 ;
  840. if ( $sync->{ssl1} or $sync->{ssl2} or $sync->{tls1} or $sync->{tls2}) {
  841. 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" ) ;
  842. }
  843. if ( $sync->{ssl1} ) {
  844. myprint( 'Host1: SSL default mode is like --sslargs1 SSL_verify_mode=' . $DEFAULT_SSL_VERIFY . " meaning $SSL_VERIFY_STR{$DEFAULT_SSL_VERIFY} on host1 (do not check the certificate server)\n" ) ;
  845. myprint( 'Host1: Use --sslargs1 SSL_verify_mode=' . IO::Socket::SSL::SSL_VERIFY_PEER( ) . " for $SSL_VERIFY_STR{IO::Socket::SSL::SSL_VERIFY_PEER( )} on host1\n" ) ;
  846. }
  847. if ( $sync->{ssl2} ) {
  848. myprint( 'Host2: SSL default mode is like --sslargs2 SSL_verify_mode=' . $DEFAULT_SSL_VERIFY . " meaning $SSL_VERIFY_STR{$DEFAULT_SSL_VERIFY} on host2 (do not check the certificate server)\n" ) ;
  849. myprint( 'Host2: Use --sslargs2 SSL_verify_mode=' . IO::Socket::SSL::SSL_VERIFY_PEER( ) . " for $SSL_VERIFY_STR{IO::Socket::SSL::SSL_VERIFY_PEER( )} on host2\n" ) ;
  850. }
  851. if ( $justconnect ) {
  852. justconnect( ) ;
  853. exit_clean( $sync, $EX_OK ) ;
  854. }
  855. $sync->{user1} || missing_option( '--user1' ) ;
  856. $sync->{user2} || missing_option( '--user2' ) ;
  857. $syncinternaldates = defined $syncinternaldates ? $syncinternaldates : 1;
  858. # Turn on expunge if there is not explicit option --noexpunge1 and option
  859. # --delete1 is given.
  860. # Done because --delete1 --noexpunge1 is very dangerous on the second run:
  861. # the Deleted flag is then synced to all previously transferred messages.
  862. # So --delete1 implies --expunge1 is a better usability default behavior.
  863. if ( $delete1 ) {
  864. if ( ! defined $expunge1 ) {
  865. myprint( "Info: turning on --expunge1 because --delete1 --noexpunge1 is very dangerous on the second run.\n" ) ;
  866. $expunge1 = 1 ;
  867. }
  868. myprint( "Info: if expunging after each message slows down too much the sync then use --noexpungeaftereach to speed up\n" ) ;
  869. }
  870. if ( $uidexpunge2 and not Mail::IMAPClient->can( 'uidexpunge' ) ) {
  871. myprint( "Failure: uidexpunge not supported (IMAPClient release < 3.17), use --expunge2 instead\n" ) ;
  872. exit_clean( $sync, $EX_SOFTWARE ) ;
  873. }
  874. if ( ( $delete2 or $delete2duplicates ) and not defined $uidexpunge2 ) {
  875. if ( Mail::IMAPClient->can( 'uidexpunge' ) ) {
  876. myprint( "Info: will act as --uidexpunge2\n" ) ;
  877. $uidexpunge2 = 1 ;
  878. }elsif ( not defined $expunge2 ) {
  879. myprint( "Info: will act as --expunge2 (no uidexpunge support)\n" ) ;
  880. $expunge2 = 1 ;
  881. }
  882. }
  883. if ( $delete1 and $delete2 ) {
  884. myprint( "Warning: using --delete1 and --delete2 together is almost always a bad idea, exiting imapsync\n" ) ;
  885. exit_clean( $sync, $EX_USAGE ) ;
  886. }
  887. if ( $idatefromheader ) {
  888. myprint( 'Turned ON idatefromheader, ',
  889. "will set the internal dates on host2 from the 'Date:' header line.\n" ) ;
  890. $syncinternaldates = 0 ;
  891. }
  892. if ( $syncinternaldates ) {
  893. myprint( 'Info: turned ON syncinternaldates, ',
  894. "will set the internal dates (arrival dates) on host2 same as host1.\n" ) ;
  895. }else{
  896. myprint( "Info: turned OFF syncinternaldates\n" ) ;
  897. }
  898. if ( defined $authmd5 and $authmd5 ) {
  899. $authmd51 = 1 ;
  900. $authmd52 = 1 ;
  901. }
  902. if ( defined $authmd51 and $authmd51 ) {
  903. $authmech1 ||= 'CRAM-MD5';
  904. }
  905. else{
  906. $authmech1 ||= $authuser1 ? 'PLAIN' : 'LOGIN';
  907. }
  908. if ( defined $authmd52 and $authmd52 ) {
  909. $authmech2 ||= 'CRAM-MD5';
  910. }
  911. else{
  912. $authmech2 ||= $authuser2 ? 'PLAIN' : 'LOGIN';
  913. }
  914. $authmech1 = uc $authmech1;
  915. $authmech2 = uc $authmech2;
  916. if (defined $proxyauth1 && !$authuser1) {
  917. missing_option( 'With --proxyauth1, --authuser1' ) ;
  918. }
  919. if (defined $proxyauth2 && !$authuser2) {
  920. missing_option( 'With --proxyauth2, --authuser2' ) ;
  921. }
  922. $authuser1 ||= $sync->{user1};
  923. $authuser2 ||= $sync->{user2};
  924. myprint( "Host1: will try to use $authmech1 authentication on host1\n") ;
  925. myprint( "Host2: will try to use $authmech2 authentication on host2\n") ;
  926. $timeout = defined $timeout ? $timeout : $DEFAULT_TIMEOUT ;
  927. $sync->{h1}->{timeout} = defined $sync->{h1}->{timeout} ? $sync->{h1}->{timeout} : $timeout ;
  928. myprint( "Host1: imap connexion timeout is $sync->{h1}->{timeout} seconds\n") ;
  929. $sync->{h2}->{timeout} = defined $sync->{h2}->{timeout} ? $sync->{h2}->{timeout} : $timeout ;
  930. myprint( "Host2: imap connexion timeout is $sync->{h2}->{timeout} seconds\n" ) ;
  931. $syncacls = defined $syncacls ? $syncacls : 0 ;
  932. # No folders sizes if --justfolders, unless really wanted.
  933. if ( $justfolders and not defined $foldersizes ) { $foldersizes = 0 ; }
  934. $foldersizes = ( defined $foldersizes ) ? $foldersizes : 1 ;
  935. $foldersizesatend = ( defined $foldersizesatend ) ? $foldersizesatend : $foldersizes ;
  936. $fastio1 = defined $fastio1 ? $fastio1 : 0 ;
  937. $fastio2 = defined $fastio2 ? $fastio2 : 0 ;
  938. $reconnectretry1 = defined $reconnectretry1 ? $reconnectretry1 : $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ;
  939. $reconnectretry2 = defined $reconnectretry2 ? $reconnectretry2 : $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ;
  940. # Since select_msgs() returns no messages when uidnext does not return something
  941. # then $uidnext_default is never used. So I have to remove it.
  942. $uidnext_default = $DEFAULT_UIDNEXT ;
  943. if ( ! @useheader ) { @useheader = qw( Message-Id Received ) ; }
  944. my %useheader ;
  945. # Make a hash %useheader of each --useheader 'key' in uppercase
  946. for ( @useheader ) { $useheader{ uc $_ } = undef } ;
  947. #myprint( Data::Dumper->Dump( [ \%useheader ] ) ) ;
  948. #exit ;
  949. myprint( "Host1: IMAP server [$sync->{host1}] port [$sync->{port1}] user [$sync->{user1}]\n" ) ;
  950. myprint( "Host2: IMAP server [$sync->{host2}] port [$sync->{port2}] user [$sync->{user2}]\n" ) ;
  951. get_password1( $sync ) ;
  952. get_password2( $sync ) ;
  953. $sync->{dry_message} = q{} ;
  954. if( $sync->{dry} ) {
  955. $sync->{dry_message} = "\t(not really since --dry mode)" ;
  956. }
  957. $search1 ||= $search if ( $search ) ;
  958. $search2 ||= $search if ( $search ) ;
  959. if ( $disarmreadreceipts ) {
  960. push @regexmess, q{s{\A((?:[^\n]+\r\n)+|)(^Disposition-Notification-To:[^\n]*\n)(\r?\n|.*\n\r?\n)}{$1X-$2$3}ims} ;
  961. }
  962. $pipemesscheck = ( defined $pipemesscheck ) ? $pipemesscheck : 1 ;
  963. if ( @pipemess and $pipemesscheck ) {
  964. myprint( 'Checking each --pipemess command, '
  965. . join( q{, }, @pipemess )
  966. . ", with an space string. ( Can avoid this check with --nopipemesscheck )\n" ) ;
  967. my $string = pipemess( q{ }, @pipemess ) ;
  968. # string undef means something was bad.
  969. if ( not ( defined $string ) ) {
  970. die_clean( "Error: one of --pipemess command is bad, check it\n" ) ;
  971. }
  972. myprint( "Ok with each --pipemess @pipemess\n" ) ;
  973. }
  974. if ( $maxlinelengthcmd ) {
  975. myprint( "Checking --maxlinelengthcmd command, $maxlinelengthcmd, with an space string.\n" ) ;
  976. my $string = pipemess( q{ }, $maxlinelengthcmd ) ;
  977. # string undef means something was bad.
  978. if ( not ( defined $string ) ) {
  979. die_clean( "Error: --maxlinelengthcmd command is bad, check it\n" ) ;
  980. }
  981. myprint( "Ok with --maxlinelengthcmd $maxlinelengthcmd\n" ) ;
  982. }
  983. if ( @regexmess ) {
  984. my $string = regexmess( q{ } ) ;
  985. myprint( "Checking each --regexmess command with an space string.\n" ) ;
  986. # string undef means one of the eval regex was bad.
  987. if ( not ( defined $string ) ) {
  988. die_clean( 'Error: one of --regexmess option is bad, check it' ) ;
  989. }
  990. myprint( "Ok with each --regexmess\n" ) ;
  991. }
  992. if ( @skipmess ) {
  993. myprint( "Checking each --skipmess command with an space string.\n" ) ;
  994. my $match = skipmess( q{ } ) ;
  995. # match undef means one of the eval regex was bad.
  996. if ( not ( defined $match ) ) {
  997. die_clean( 'Error: one of --skipmess option is bad, check it' ) ;
  998. }
  999. myprint( "Ok with each --skipmess\n" ) ;
  1000. }
  1001. if ( @regexflag ) {
  1002. myprint( "Checking each --regexflag command with an space string.\n" ) ;
  1003. my $string = flags_regex( q{ } ) ;
  1004. # string undef means one of the eval regex was bad.
  1005. if ( not ( defined $string ) ) {
  1006. die_clean( 'Error: one of --regexflag option is bad, check it' ) ;
  1007. }
  1008. myprint( "Ok with each --regexflag\n" ) ;
  1009. }
  1010. $sync->{imap1} = my $imap1 = login_imap( $sync->{host1}, $sync->{port1}, $sync->{user1}, $domain1, $sync->{password1},
  1011. $debugimap1, $sync->{h1}->{timeout}, $fastio1, $sync->{ssl1}, $sync->{tls1},
  1012. $authmech1, $authuser1, $reconnectretry1,
  1013. $proxyauth1, $uid1, $split1, 'Host1', $sync->{h1}, $sync ) ;
  1014. $sync->{imap2} = my $imap2 = login_imap( $sync->{host2}, $sync->{port2}, $sync->{user2}, $domain2, $sync->{password2},
  1015. $debugimap2, $sync->{h2}->{timeout}, $fastio2, $sync->{ssl2}, $sync->{tls2},
  1016. $authmech2, $authuser2, $reconnectretry2,
  1017. $proxyauth2, $uid2, $split2, 'Host2', $sync->{h2}, $sync ) ;
  1018. $debug and myprint( 'Host1 Buffer I/O: ', $imap1->Buffer(), "\n" ) ;
  1019. $debug and myprint( 'Host2 Buffer I/O: ', $imap2->Buffer(), "\n" ) ;
  1020. if ( ! $imap1->IsAuthenticated( ) ) { die_clean( 'Not authenticated on host1' ) ; }
  1021. myprint( "Host1: state Authenticated\n" ) ;
  1022. if ( ! $imap2->IsAuthenticated( ) ) { die_clean( 'Not authenticated on host2' ) ; }
  1023. myprint( "Host2: state Authenticated\n" ) ;
  1024. myprint( 'Host1 capability once authenticated: ', join(q{ }, @{ $imap1->capability() || [] }), "\n" ) ;
  1025. myprint( 'Host2 capability once authenticated: ', join(q{ }, @{ $imap2->capability() || [] }), "\n" ) ;
  1026. # ID on by default since 1.832
  1027. $sync->{id} = defined $sync->{id} ? $sync->{id} : 1 ;
  1028. imap_id_stuff( $sync ) ;
  1029. #quota( $imap1, 'h1', $sync ) ; # quota on host1 is useless and pollute host2 output.
  1030. quota( $imap2, 'h2', $sync ) ;
  1031. if ( $justlogin ) {
  1032. $imap1->logout( ) ;
  1033. $imap2->logout( ) ;
  1034. exit_clean( $sync, $EX_OK ) ;
  1035. }
  1036. #
  1037. # Folder stuff
  1038. #
  1039. my (
  1040. @h1_folders_all , %h1_folders_all , @h1_folders_wanted , %requested_folder ,
  1041. %h1_subscribed_folder , %h2_subscribed_folder ,
  1042. @h2_folders_all , %h2_folders_all , %h2_folders_all_UPPER ,
  1043. @h2_folders_from_1_wanted , %h2_folders_from_1_wanted ,
  1044. %h2_folders_from_1_several ,
  1045. %h2_folders_from_1_all ,
  1046. ) ;
  1047. my $h1_folders_wanted_nb = 0 ;
  1048. my $h1_folders_wanted_ct = 0 ; # counter of folders done.
  1049. # All folders on host1 and host2
  1050. @h1_folders_all = sort $imap1->folders( ) ;
  1051. @h2_folders_all = sort $imap2->folders( ) ;
  1052. myprint( 'Host1: found ', scalar @h1_folders_all , " folders.\n" ) ;
  1053. myprint( 'Host2: found ', scalar @h2_folders_all , " folders.\n" ) ;
  1054. foreach my $f ( @h1_folders_all ) {
  1055. $h1_folders_all{ $f } = 1
  1056. }
  1057. foreach my $f ( @h2_folders_all ) {
  1058. $h2_folders_all{ $f } = 1 ;
  1059. $h2_folders_all_UPPER{ uc $f } = 1 ;
  1060. }
  1061. $sync->{h1_folders_all} = \%h1_folders_all ;
  1062. $sync->{h2_folders_all} = \%h2_folders_all ;
  1063. $sync->{h2_folders_all_UPPER} = \%h2_folders_all_UPPER ;
  1064. # Make a hash of subscribed folders in both servers.
  1065. for ( $imap1->subscribed( ) ) { $h1_subscribed_folder{ $_ } = 1 } ;
  1066. for ( $imap2->subscribed( ) ) { $h2_subscribed_folder{ $_ } = 1 } ;
  1067. if ( defined $subfolder2 ) {
  1068. unshift @regextrans2,
  1069. q(s,^${h2_prefix}(.*),${h2_prefix}${subfolder2}${h2_sep}$1,),
  1070. q(s,^INBOX$,${h2_prefix}${subfolder2}${h2_sep}INBOX,) ;
  1071. }
  1072. if ( $fixInboxINBOX and ( my $reg = fix_Inbox_INBOX_mapping( \%h1_folders_all, \%h2_folders_all ) ) ) {
  1073. push @regextrans2, $reg ;
  1074. }
  1075. if (scalar @folder or $subscribed or scalar @folderrec) {
  1076. # folders given by option --folder
  1077. if (scalar @folder) {
  1078. add_to_requested_folders(@folder);
  1079. }
  1080. # option --subscribed
  1081. if ( $subscribed ) {
  1082. add_to_requested_folders( keys %h1_subscribed_folder ) ;
  1083. }
  1084. # option --folderrec
  1085. if (scalar @folderrec) {
  1086. foreach my $folderrec (@folderrec) {
  1087. add_to_requested_folders($imap1->folders($folderrec));
  1088. }
  1089. }
  1090. }
  1091. else {
  1092. # no include, no folder/subscribed/folderrec options => all folders
  1093. if (not scalar @include) {
  1094. 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" ) ;
  1095. add_to_requested_folders(@h1_folders_all);
  1096. }
  1097. }
  1098. # consider (optional) includes and excludes
  1099. if ( scalar @include ) {
  1100. foreach my $include ( @include ) {
  1101. my @included_folders = grep { /$include/ } @h1_folders_all ;
  1102. add_to_requested_folders( @included_folders ) ;
  1103. myprint( "Including folders matching pattern $include\n" . jux_utf8_list( @included_folders ) . "\n" ) ;
  1104. }
  1105. }
  1106. if ( scalar @exclude ) {
  1107. foreach my $exclude ( @exclude ) {
  1108. my @requested_folder = sort keys %requested_folder ;
  1109. my @excluded_folders = grep { /$exclude/ } @requested_folder ;
  1110. remove_from_requested_folders( @excluded_folders ) ;
  1111. myprint( "Excluding folders matching pattern $exclude\n" . jux_utf8_list( @excluded_folders ) . "\n" ) ;
  1112. }
  1113. }
  1114. # sort before is not very powerful
  1115. # it adds --folderfirst and --folderlast even if they don't exist on host1
  1116. @h1_folders_wanted = sort_requested_folders( ) ;
  1117. # Remove no selectable folders
  1118. my @h1_folders_wanted_exist ;
  1119. myprint( "Host1: checking all wanted folders exist.\n" ) ;
  1120. foreach my $folder ( @h1_folders_wanted ) {
  1121. ( $debug or $sync->{debugfolders} ) and myprint( "Checking $folder exists on host1\n" ) ;
  1122. if ( ! exists $h1_folders_all{ $folder } ) {
  1123. myprint( "Host1: warning! ignoring folder $folder because it is not in host1 whole folders list.\n" ) ;
  1124. next ;
  1125. }else{
  1126. push @h1_folders_wanted_exist, $folder ;
  1127. }
  1128. }
  1129. @h1_folders_wanted = @h1_folders_wanted_exist ;
  1130. $checkselectable and do {
  1131. my @h1_folders_wanted_selectable ;
  1132. myprint( "Host1: checking all wanted folders are selectable. Use --nocheckselectable to avoid this check.\n" ) ;
  1133. foreach my $folder ( @h1_folders_wanted ) {
  1134. ( $debug or $sync->{debugfolders} ) and myprint( "Checking $folder is selectable on host1\n" ) ;
  1135. if ( ! $imap1->selectable( $folder ) ) {
  1136. myprint( "Host1: warning! ignoring folder $folder because it is not selectable\n" ) ;
  1137. }else{
  1138. push @h1_folders_wanted_selectable, $folder ;
  1139. }
  1140. }
  1141. @h1_folders_wanted = @h1_folders_wanted_selectable ;
  1142. ( $debug or $sync->{debugfolders} ) and myprint( 'Host1: checking folders took ', timenext( ), " s\n" ) ;
  1143. } ;
  1144. $sync->{h1_folders_wanted} = \@h1_folders_wanted ;
  1145. my( $h1_sep, $h2_sep ) ;
  1146. # what are the private folders separators for each server ?
  1147. ( $debug or $sync->{debugfolders} ) and myprint( "Getting separators\n" ) ;
  1148. $h1_sep = get_separator( $imap1, $sep1, '--sep1', 'Host1', \@h1_folders_all ) ;
  1149. $h2_sep = get_separator( $imap2, $sep2, '--sep2', 'Host2', \@h2_folders_all ) ;
  1150. my( $h1_prefix, $h2_prefix ) ;
  1151. $sync->{ h1_prefix } = $h1_prefix = get_prefix( $imap1, $prefix1, '--prefix1', 'Host1', \@h1_folders_all ) ;
  1152. $sync->{ h2_prefix } = $h2_prefix = get_prefix( $imap2, $prefix2, '--prefix2', 'Host2', \@h2_folders_all ) ;
  1153. myprint( "Host1 separator and prefix: [$h1_sep][$h1_prefix]\n" ) ;
  1154. myprint( "Host2 separator and prefix: [$h2_sep][$h2_prefix]\n" ) ;
  1155. automap( $sync ) ;
  1156. foreach my $h1_fold ( @h1_folders_wanted ) {
  1157. my $h2_fold ;
  1158. $h2_fold = imap2_folder_name( $h1_fold ) ;
  1159. $h2_folders_from_1_wanted{ $h2_fold }++ ;
  1160. if ( 1 < $h2_folders_from_1_wanted{ $h2_fold } ) {
  1161. $h2_folders_from_1_several{ $h2_fold }++ ;
  1162. }
  1163. }
  1164. @h2_folders_from_1_wanted = sort keys %h2_folders_from_1_wanted;
  1165. foreach my $h1_fold ( @h1_folders_all ) {
  1166. my $h2_fold ;
  1167. $h2_fold = imap2_folder_name( $h1_fold ) ;
  1168. $h2_folders_from_1_all{ $h2_fold }++ ;
  1169. }
  1170. myprint( << 'END_LISTING' ) ;
  1171. ++++ Listing folders
  1172. All foldernames are presented between brackets like [X] where X is the foldername.
  1173. When a foldername contains non-ASCII characters it is presented in the form
  1174. [X] = [Y] where
  1175. X is the imap foldername you have to use in command line options and
  1176. Y is the uft8 output just printed for convenience, to recognize it.
  1177. END_LISTING
  1178. print
  1179. "Host1 folders list (first the raw imap format then the [X] = [Y]):\n",
  1180. $imap1->list( ),
  1181. "\n",
  1182. jux_utf8_list( @h1_folders_all ),
  1183. "\n",
  1184. "Host2 folders list (first the raw imap format then the [X] = [Y]):\n",
  1185. $imap2->list( ),
  1186. "\n",
  1187. jux_utf8_list( @h2_folders_all ),
  1188. "\n",
  1189. q{} ;
  1190. print
  1191. 'Host1 subscribed folders list: ',
  1192. jux_utf8_list( sort keys %h1_subscribed_folder ), "\n"
  1193. if ( $subscribed ) ;
  1194. my @h2_folders_not_in_1;
  1195. @h2_folders_not_in_1 = list_folders_in_2_not_in_1( ) ;
  1196. if ( @h2_folders_not_in_1 ) {
  1197. myprint( "Folders in host2 not in host1:\n",
  1198. jux_utf8_list( @h2_folders_not_in_1 ), "\n" ) ;
  1199. }
  1200. if ( keys %{ $sync->{f1f2auto} } ) {
  1201. myprint( "Folders mapping from --automap feature (use --f1f2 to override any mapping):\n" ) ;
  1202. foreach my $h1_fold ( keys %{ $sync->{f1f2auto} } ) {
  1203. my $h2_fold = $sync->{f1f2auto}{$h1_fold} ;
  1204. myprintf( "%-40s -> %-40s\n",
  1205. jux_utf8( $h1_fold ), jux_utf8( $h2_fold ) ) ;
  1206. }
  1207. myprint( "\n" ) ;
  1208. }
  1209. if ( keys %{ $sync->{f1f2} } ) {
  1210. myprint( "Folders mapping from --f1f2 options, it overrides --automap:\n" ) ;
  1211. foreach my $h1_fold ( keys %{ $sync->{f1f2} } ) {
  1212. my $h2_fold = $sync->{f1f2}{$h1_fold} ;
  1213. my $warn = q{} ;
  1214. if ( not exists $h1_folders_all{ $h1_fold } ) {
  1215. $warn = "BUT $h1_fold does NOT exist on host1!" ;
  1216. }
  1217. myprintf( "%-40s -> %-40s %s\n",
  1218. jux_utf8( $h1_fold ), jux_utf8( $h2_fold ), $warn ) ;
  1219. }
  1220. myprint( "\n" ) ;
  1221. }
  1222. exit_clean( $sync, $EX_OK ) if ( $sync->{justfolderlists} ) ;
  1223. exit_clean( $sync, $EX_OK ) if ( $sync->{justautomap} ) ;
  1224. debugsleep( $sync ) ;
  1225. if ( $foldersizes ) {
  1226. foldersizes_on_h1h2( ) ;
  1227. }
  1228. exit_clean( $sync, $EX_OK ) if ( $justfoldersizes ) ;
  1229. $sync->{stats} = 1 ;
  1230. if ( $sync->{'delete1emptyfolders'} ) {
  1231. delete1emptyfolders( $sync ) ;
  1232. }
  1233. delete_folders_in_2_not_in_1( ) if $delete2folders ;
  1234. # folder loop
  1235. $h1_folders_wanted_nb = scalar @h1_folders_wanted ;
  1236. myprint( "++++ Looping on each one of $h1_folders_wanted_nb folders to sync\n" ) ;
  1237. $sync->{begin_transfer_time} = time ;
  1238. my %uid_candidate_for_deletion ;
  1239. my %uid_candidate_no_deletion ;
  1240. my %h2_folders_of_md5 = ( ) ;
  1241. FOLDER: foreach my $h1_fold ( @h1_folders_wanted ) {
  1242. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1243. my $h2_fold = imap2_folder_name( $h1_fold ) ;
  1244. $h1_folders_wanted_ct++ ;
  1245. myprintf( "Folder %7s %-35s -> %-35s\n", "$h1_folders_wanted_ct/$h1_folders_wanted_nb",
  1246. jux_utf8( $h1_fold ), jux_utf8( $h2_fold ) ) ;
  1247. if ( $sync->{debugmemory} ) {
  1248. myprintf("FL: Memory consumption: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI) ;
  1249. }
  1250. # host1 can not be fetched read only, select is needed because of expunge.
  1251. select_folder( $imap1, $h1_fold, 'Host1' ) or next FOLDER ;
  1252. debugsleep( $sync ) ;
  1253. my $h1_fold_nb_messages = count_from_select( $imap1->History ) ;
  1254. myprint( "Host1 folder [$h1_fold] has $h1_fold_nb_messages messages in total (mentioned by SELECT)\n" ) ;
  1255. if ( $skipemptyfolders and 0 == $h1_fold_nb_messages ) {
  1256. myprint( "Skipping empty host1 folder [$h1_fold]\n" ) ;
  1257. next FOLDER ;
  1258. }
  1259. if ( ! exists $h2_folders_all{ $h2_fold } ) {
  1260. create_folder( $imap2, $h2_fold, $h1_fold ) or next FOLDER ;
  1261. }
  1262. acls_sync( $h1_fold, $h2_fold ) ;
  1263. # Sometimes the folder on host2 is listed (it exists) but is
  1264. # not selectable but becomes selectable by a create (Gmail)
  1265. select_folder( $imap2, $h2_fold, 'Host2' )
  1266. or ( create_folder( $imap2, $h2_fold, $h1_fold )
  1267. and select_folder( $imap2, $h2_fold, 'Host2' ) )
  1268. or next FOLDER ;
  1269. my @select_results = $imap2->Results( ) ;
  1270. my $h2_fold_nb_messages = count_from_select( @select_results ) ;
  1271. myprint( "Host2 folder [$h2_fold] has $h2_fold_nb_messages messages in total (mentioned by SELECT)\n" ) ;
  1272. my $permanentflags2 = permanentflags( @select_results ) ;
  1273. myprint( "Host2 folder [$h2_fold] permanentflags: $permanentflags2\n" ) ;
  1274. if ( $expunge1 ){
  1275. myprint( "Host1: Expunging $h1_fold $sync->{dry_message}\n" ) ;
  1276. if ( ! $sync->{dry} ) { $imap1->expunge( ) } ;
  1277. }
  1278. if ( ( ( $subscribe and exists $h1_subscribed_folder{ $h1_fold } ) or $subscribeall )
  1279. and not exists $h2_subscribed_folder{ $h2_fold } ) {
  1280. myprint( "Host2: Subscribing to folder $h2_fold\n" ) ;
  1281. if ( ! $sync->{dry} ) { $imap2->subscribe( $h2_fold ) } ;
  1282. }
  1283. next FOLDER if ( $justfolders ) ;
  1284. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1285. my $h1_msgs_all_hash_ref = { } ;
  1286. my @h1_msgs = select_msgs( $imap1, $h1_msgs_all_hash_ref, $search1, $sync->{abletosearch1}, $h1_fold );
  1287. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1288. my $h1_msgs_nb = scalar @h1_msgs ;
  1289. $h1{ $h1_fold }{ 'messages_nb' } = $h1_msgs_nb ;
  1290. myprint( "Host1 folder [$h1_fold] considering $h1_msgs_nb messages\n" ) ;
  1291. ( $debug or $debuglist ) and myprint( "Host1 folder [$h1_fold] considering $h1_msgs_nb messages, LIST gives: @h1_msgs\n" ) ;
  1292. $debug and myprint( "Host1 selecting messages of folder [$h1_fold] took ", timenext(), " s\n" ) ;
  1293. my $h2_msgs_all_hash_ref = { } ;
  1294. my @h2_msgs = select_msgs( $imap2, $h2_msgs_all_hash_ref, $search2, $sync->{abletosearch2}, $h2_fold ) ;
  1295. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1296. my $h2_msgs_nb = scalar @h2_msgs ;
  1297. $h2{ $h2_fold }{ 'messages_nb' } = $h2_msgs_nb ;
  1298. myprint( "Host2 folder [$h2_fold] considering $h2_msgs_nb messages\n" ) ;
  1299. ( $debug or $debuglist ) and myprint( "Host2 folder [$h2_fold] considering $h2_msgs_nb messages, LIST gives: @h2_msgs\n" ) ;
  1300. $debug and myprint( "Host2 selecting messages of folder [$h2_fold] took ", timenext(), " s\n" ) ;
  1301. my $cache_base = "$tmpdir/imapsync_cache/" ;
  1302. my $cache_dir = cache_folder( $cache_base, "$sync->{host1}/$sync->{user1}/$sync->{host2}/$sync->{user2}", $h1_fold, $h2_fold ) ;
  1303. my ( $cache_1_2_ref, $cache_2_1_ref ) = ( {}, {} ) ;
  1304. my $h1_uidvalidity = $imap1->uidvalidity( ) || q{} ;
  1305. my $h2_uidvalidity = $imap2->uidvalidity( ) || q{} ;
  1306. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1307. if ( $usecache ) {
  1308. myprint( "cache directory: $cache_dir\n" ) ;
  1309. mkpath( "$cache_dir" ) ;
  1310. ( $cache_1_2_ref, $cache_2_1_ref )
  1311. = get_cache( $cache_dir, \@h1_msgs, \@h2_msgs, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) ;
  1312. myprint( 'CACHE h1 h2: ', scalar keys %{ $cache_1_2_ref } , " files\n" ) ;
  1313. $debug and myprint( '[',
  1314. map ( { "$_->$cache_1_2_ref->{$_} " } keys %{ $cache_1_2_ref } ), " ]\n" ) ;
  1315. }
  1316. my %h1_hash = () ;
  1317. my %h2_hash = () ;
  1318. my ( %h1_msgs, %h2_msgs ) ;
  1319. @h1_msgs{ @h1_msgs } = ();
  1320. @h2_msgs{ @h2_msgs } = ();
  1321. my @h1_msgs_in_cache = sort { $a <=> $b } keys %{ $cache_1_2_ref } ;
  1322. my @h2_msgs_in_cache = keys %{ $cache_2_1_ref } ;
  1323. my ( %h1_msgs_not_in_cache, %h2_msgs_not_in_cache ) ;
  1324. %h1_msgs_not_in_cache = %h1_msgs ;
  1325. %h2_msgs_not_in_cache = %h2_msgs ;
  1326. delete @h1_msgs_not_in_cache{ @h1_msgs_in_cache } ;
  1327. delete @h2_msgs_not_in_cache{ @h2_msgs_in_cache } ;
  1328. my @h1_msgs_not_in_cache = keys %h1_msgs_not_in_cache ;
  1329. #myprint( "h1_msgs_not_in_cache: [@h1_msgs_not_in_cache]\n" ) ;
  1330. my @h2_msgs_not_in_cache = keys %h2_msgs_not_in_cache ;
  1331. my @h2_msgs_delete2_not_in_cache = () ;
  1332. %h1_msgs_copy_by_uid = ( ) ;
  1333. if ( $useuid ) {
  1334. # use uid so we have to avoid getting header
  1335. @h1_msgs_copy_by_uid{ @h1_msgs_not_in_cache } = ( ) ;
  1336. @h2_msgs_delete2_not_in_cache = @h2_msgs_not_in_cache if $usecache ;
  1337. @h1_msgs_not_in_cache = ( ) ;
  1338. @h2_msgs_not_in_cache = ( ) ;
  1339. #myprint( "delete2: @h2_msgs_delete2_not_in_cache\n" ) ;
  1340. }
  1341. $debug and myprint( "Host1 parsing headers of folder [$h1_fold]\n" ) ;
  1342. my ($h1_heads_ref, $h1_fir_ref) = ({}, {});
  1343. $h1_heads_ref = $imap1->parse_headers([@h1_msgs_not_in_cache], @useheader) if (@h1_msgs_not_in_cache);
  1344. $debug and myprint( "Host1 parsing headers of folder [$h1_fold] took ", timenext(), " s\n" ) ;
  1345. @{ $h1_fir_ref }{@h1_msgs} = ( undef ) ;
  1346. $debug and myprint( "Host1 getting flags idate and sizes of folder [$h1_fold]\n" ) ;
  1347. if ( $sync->{abletosearch1} ) {
  1348. $h1_fir_ref = $imap1->fetch_hash( \@h1_msgs, 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE', $h1_fir_ref )
  1349. if ( @h1_msgs ) ;
  1350. }else{
  1351. my $uidnext = $imap1->uidnext( $h1_fold ) || $uidnext_default ;
  1352. my $fetch_hash_uids = $fetch_hash_set || "1:$uidnext" ;
  1353. $h1_fir_ref = $imap1->fetch_hash( $fetch_hash_uids, 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE', $h1_fir_ref )
  1354. if ( @h1_msgs ) ;
  1355. }
  1356. $debug and myprint( "Host1 getting flags idate and sizes of folder [$h1_fold] took ", timenext(), " s\n" ) ;
  1357. if ( ! $h1_fir_ref ) {
  1358. my $error = join( q{}, "Host1 folder $h1_fold: Could not fetch_hash ",
  1359. scalar @h1_msgs, ' msgs: ', $imap1->LastError || q{}, "\n" ) ;
  1360. errors_incr( $sync, $error ) ;
  1361. next FOLDER ;
  1362. }
  1363. my @h1_msgs_duplicate;
  1364. foreach my $m (@h1_msgs_not_in_cache) {
  1365. my $rc = parse_header_msg($imap1, $m, $h1_heads_ref, $h1_fir_ref, 'Host1', \%h1_hash);
  1366. if ( ! defined $rc ) {
  1367. my $h1_size = $h1_fir_ref->{$m}->{'RFC822.SIZE'} || 0;
  1368. myprint( "Host1 $h1_fold/$m size $h1_size ignored (no wanted headers so we ignore this message. To solve this: use --addheader)\n" ) ;
  1369. $total_bytes_skipped += $h1_size;
  1370. $nb_msg_skipped += 1;
  1371. $h1_nb_msg_noheader +=1;
  1372. $h1_nb_msg_processed +=1 ;
  1373. } elsif(0 == $rc) {
  1374. # duplicate
  1375. push @h1_msgs_duplicate, $m;
  1376. # duplicate, same id same size?
  1377. my $h1_size = $h1_fir_ref->{$m}->{'RFC822.SIZE'} || 0;
  1378. $nb_msg_skipped += 1;
  1379. $h1_total_bytes_duplicate += $h1_size;
  1380. $h1_nb_msg_duplicate += 1;
  1381. $h1_nb_msg_processed +=1 ;
  1382. }
  1383. }
  1384. my $h1_msgs_duplicate_nb = scalar @h1_msgs_duplicate ;
  1385. $h1{ $h1_fold }{ 'duplicates_nb' } = $h1_msgs_duplicate_nb ;
  1386. $debug and myprint( "Host1 selected: $h1_msgs_nb duplicates: $h1_msgs_duplicate_nb\n" ) ;
  1387. $debug and myprint( 'Host1 whole time parsing headers took ', timenext(), " s\n" ) ;
  1388. # Getting headers and metada can be so long that host2 might be disconnected here
  1389. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1390. $debug and myprint( "Host2 parsing headers of folder [$h2_fold]\n" ) ;
  1391. my ($h2_heads_ref, $h2_fir_ref) = ( {}, {} );
  1392. $h2_heads_ref = $imap2->parse_headers([@h2_msgs_not_in_cache], @useheader) if (@h2_msgs_not_in_cache);
  1393. $debug and myprint( "Host2 parsing headers of folder [$h2_fold] took ", timenext(), " s\n" ) ;
  1394. $debug and myprint( "Host2 getting flags idate and sizes of folder [$h2_fold]\n" ) ;
  1395. @{ $h2_fir_ref }{@h2_msgs} = ( ); # fetch_hash can select by uid with last arg as ref
  1396. if ( $sync->{abletosearch2} and scalar( @h2_msgs ) ) {
  1397. $h2_fir_ref = $imap2->fetch_hash( \@h2_msgs, 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE', $h2_fir_ref) ;
  1398. }else{
  1399. my $uidnext = $imap2->uidnext( $h2_fold ) || $uidnext_default ;
  1400. my $fetch_hash_uids = $fetch_hash_set || "1:$uidnext" ;
  1401. $h2_fir_ref = $imap2->fetch_hash( $fetch_hash_uids, 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE', $h2_fir_ref )
  1402. if ( @h2_msgs ) ;
  1403. }
  1404. $debug and myprint( "Host2 getting flags idate and sizes of folder [$h2_fold] took ", timenext(), " s\n" ) ;
  1405. my @h2_msgs_duplicate;
  1406. foreach my $m (@h2_msgs_not_in_cache) {
  1407. my $rc = parse_header_msg($imap2, $m, $h2_heads_ref, $h2_fir_ref, 'Host2', \%h2_hash) ;
  1408. my $h2_size = $h2_fir_ref->{$m}->{'RFC822.SIZE'} || 0 ;
  1409. if (! defined $rc ) {
  1410. myprint( "Host2 $h2_fold/$m size $h2_size ignored (no wanted headers so we ignore this message)\n" ) ;
  1411. $h2_nb_msg_noheader += 1 ;
  1412. } elsif( 0 == $rc ) {
  1413. # duplicate
  1414. $h2_nb_msg_duplicate += 1 ;
  1415. $h2_total_bytes_duplicate += $h2_size ;
  1416. push @h2_msgs_duplicate, $m ;
  1417. }
  1418. }
  1419. # %h2_folders_of_md5
  1420. foreach my $md5 ( keys %h2_hash ) {
  1421. $h2_folders_of_md5{ $md5 }->{ $h2_fold } ++ ;
  1422. }
  1423. my $h2_msgs_duplicate_nb = scalar @h2_msgs_duplicate ;
  1424. $h2{ $h2_fold }{ 'duplicates_nb' } = $h2_msgs_duplicate_nb ;
  1425. myprint( "Host2 folder $h2_fold selected: $h2_msgs_nb messages, duplicates: $h2_msgs_duplicate_nb\n" )
  1426. if ( $debug or $delete2duplicates or $h2_msgs_duplicate_nb ) ;
  1427. $debug and myprint( 'Host2 whole time parsing headers took ', timenext( ), " s\n" ) ;
  1428. $debug and myprint( "++++ Verifying [$h1_fold] -> [$h2_fold]\n" ) ;
  1429. # messages in host1 that are not in host2
  1430. my @h1_hash_keys_sorted_by_uid
  1431. = sort {$h1_hash{$a}{'m'} <=> $h1_hash{$b}{'m'}} keys %h1_hash;
  1432. #myprint( map { $h1_hash{$_}{'m'} . q{ }} @h1_hash_keys_sorted_by_uid ) ;
  1433. my @h2_hash_keys_sorted_by_uid
  1434. = sort {$h2_hash{$a}{'m'} <=> $h2_hash{$b}{'m'}} keys %h2_hash;
  1435. if( $delete2duplicates and not exists $h2_folders_from_1_several{ $h2_fold } ) {
  1436. my @h2_expunge ;
  1437. foreach my $h2_msg ( @h2_msgs_duplicate ) {
  1438. myprint( "msg $h2_fold/$h2_msg marked \\Deleted [duplicate] on host2 $sync->{dry_message}\n" ) ;
  1439. push @h2_expunge, $h2_msg if $uidexpunge2 ;
  1440. if ( ! $sync->{dry} ) {
  1441. $imap2->delete_message( $h2_msg ) ;
  1442. $h2_nb_msg_deleted += 1 ;
  1443. }
  1444. }
  1445. my $cnt = scalar @h2_expunge ;
  1446. if( @h2_expunge and not $expunge2 ) {
  1447. myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n" ) ;
  1448. $imap2->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ;
  1449. }
  1450. if ( $expunge2 ){
  1451. myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n" ) ;
  1452. $imap2->expunge( ) if ! $sync->{dry} ;
  1453. }
  1454. }
  1455. if( $delete2 and not exists $h2_folders_from_1_several{ $h2_fold } ) {
  1456. # No host1 folders f1a f1b ... going all to same f2 (via --regextrans2)
  1457. my @h2_expunge;
  1458. foreach my $m_id (@h2_hash_keys_sorted_by_uid) {
  1459. #myprint( "$m_id " ) ;
  1460. if ( ! exists $h1_hash{$m_id} ) {
  1461. my $h2_msg = $h2_hash{$m_id}{'m'};
  1462. my $h2_flags = $h2_hash{$m_id}{'F'} || q{};
  1463. my $isdel = $h2_flags =~ /\B\\Deleted\b/x ? 1 : 0;
  1464. myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted on host2 [$m_id] $sync->{dry_message}\n" )
  1465. if ! $isdel;
  1466. push @h2_expunge, $h2_msg if $uidexpunge2;
  1467. if ( ! ( $sync->{dry} or $isdel ) ) {
  1468. $imap2->delete_message($h2_msg);
  1469. $h2_nb_msg_deleted += 1;
  1470. }
  1471. }
  1472. }
  1473. foreach my $h2_msg ( @h2_msgs_delete2_not_in_cache ) {
  1474. myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted [not in cache] on host2 $sync->{dry_message}\n" ) ;
  1475. push @h2_expunge, $h2_msg if $uidexpunge2;
  1476. if ( ! $sync->{dry} ) {
  1477. $imap2->delete_message($h2_msg);
  1478. $h2_nb_msg_deleted += 1;
  1479. }
  1480. }
  1481. my $cnt = scalar @h2_expunge ;
  1482. if( @h2_expunge and not $expunge2 ) {
  1483. myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n" ) ;
  1484. $imap2->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ;
  1485. }
  1486. if ( $expunge2 ) {
  1487. myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n" ) ;
  1488. $imap2->expunge( ) if ! $sync->{dry} ;
  1489. }
  1490. }
  1491. if( $delete2 and exists $h2_folders_from_1_several{ $h2_fold } ) {
  1492. myprint( "Host2 folder $h2_fold $h2_folders_from_1_several{ $h2_fold } folders left to sync there\n" ) ;
  1493. my @h2_expunge;
  1494. foreach my $m_id ( @h2_hash_keys_sorted_by_uid ) {
  1495. my $h2_msg = $h2_hash{ $m_id }{ 'm' } ;
  1496. if ( ! exists $h1_hash{ $m_id } ) {
  1497. my $h2_flags = $h2_hash{ $m_id }{ 'F' } || q{} ;
  1498. my $isdel = $h2_flags =~ /\B\\Deleted\b/x ? 1 : 0 ;
  1499. if ( ! $isdel ) {
  1500. $debug and myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion [$m_id]\n" ) ;
  1501. $uid_candidate_for_deletion{ $h2_fold }{ $h2_msg }++ ;
  1502. }
  1503. }else{
  1504. $debug and myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [$m_id]\n" ) ;
  1505. $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
  1506. }
  1507. }
  1508. foreach my $h2_msg ( @h2_msgs_delete2_not_in_cache ) {
  1509. myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion [not in cache]\n" ) ;
  1510. $uid_candidate_for_deletion{ $h2_fold }{ $h2_msg }++ ;
  1511. }
  1512. foreach my $h2_msg ( @h2_msgs_in_cache ) {
  1513. myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [in cache]\n" ) ;
  1514. $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
  1515. }
  1516. if ( 0 == $h2_folders_from_1_several{ $h2_fold } ) {
  1517. # last host1 folder going to $h2_fold
  1518. myprint( "Last host1 folder going to $h2_fold\n" ) ;
  1519. foreach my $h2_msg ( keys %{ $uid_candidate_for_deletion{ $h2_fold } } ) {
  1520. $debug and myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion\n" ) ;
  1521. if ( exists $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg } ) {
  1522. $debug and myprint( "Host2: msg $h2_fold/$h2_msg canceled deletion\n" ) ;
  1523. }else{
  1524. myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted $sync->{dry_message}\n" ) ;
  1525. push @h2_expunge, $h2_msg if $uidexpunge2 ;
  1526. if ( ! $sync->{dry} ) {
  1527. $imap2->delete_message( $h2_msg ) ;
  1528. $h2_nb_msg_deleted += 1 ;
  1529. }
  1530. }
  1531. }
  1532. }
  1533. my $cnt = scalar @h2_expunge ;
  1534. if( @h2_expunge and not $expunge2 ) {
  1535. myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n" ) ;
  1536. $imap2->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ;
  1537. }
  1538. if ( $expunge2 ) {
  1539. myprint( "Host2: Expunging host2 folder $h2_fold $sync->{dry_message}\n" ) ;
  1540. $imap2->expunge( ) if ! $sync->{dry} ;
  1541. }
  1542. $h2_folders_from_1_several{ $h2_fold }-- ;
  1543. }
  1544. my $h2_uidnext = $imap2->uidnext( $h2_fold ) ;
  1545. $debug and myprint( "Host2 uidnext: $h2_uidnext\n" ) ;
  1546. $h2_uidguess = $h2_uidnext ;
  1547. # Getting host2 headers, metada and delete2 stuff can be so long that host1 might be disconnected here
  1548. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1549. MESS: foreach my $m_id (@h1_hash_keys_sorted_by_uid) {
  1550. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1551. #myprint( "h1_nb_msg_processed: $h1_nb_msg_processed\n" ) ;
  1552. my $h1_size = $h1_hash{$m_id}{'s'};
  1553. my $h1_msg = $h1_hash{$m_id}{'m'};
  1554. my $h1_idate = $h1_hash{$m_id}{'D'};
  1555. if ( ( not exists $h2_hash{ $m_id } )
  1556. and ( not ( exists $h2_folders_of_md5{ $m_id } )
  1557. or not $skipcrossduplicates ) ) {
  1558. # copy
  1559. my $h2_msg = copy_message( $sync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) ;
  1560. $h2_folders_of_md5{ $m_id }->{ $h2_fold } ++ ;
  1561. if( $delete2 and ( exists $h2_folders_from_1_several{ $h2_fold } ) and $h2_msg ) {
  1562. myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [fresh copy] on host2\n" ) ;
  1563. $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
  1564. }
  1565. last FOLDER if total_bytes_max_reached( ) ;
  1566. next MESS;
  1567. }
  1568. else{
  1569. # already on host2
  1570. if ( exists $h2_hash{ $m_id } ) {
  1571. my $h2_msg = $h2_hash{$m_id}{'m'} ;
  1572. $debug and myprint( "Host1 found msg $h1_fold/$h1_msg equals Host2 $h2_fold/$h2_msg\n" ) ;
  1573. if ( $usecache ) {
  1574. $debugcache and myprint( "touch $cache_dir/${h1_msg}_$h2_msg\n" ) ;
  1575. touch( "$cache_dir/${h1_msg}_$h2_msg" )
  1576. or croak( "Couldn't touch $cache_dir/${h1_msg}_$h2_msg" ) ;
  1577. }
  1578. }elsif( exists $h2_folders_of_md5{ $m_id } ) {
  1579. my @folders_dup = keys %{ $h2_folders_of_md5{ $m_id } } ;
  1580. ( $debug or $debugcrossduplicates ) and myprint( "Host1 found msg $h1_fold/$h1_msg is also in Host2 folders @folders_dup\n" ) ;
  1581. }
  1582. $total_bytes_skipped += $h1_size ;
  1583. $nb_msg_skipped += 1 ;
  1584. $h1_nb_msg_processed +=1 ;
  1585. }
  1586. if ( exists $h2_hash{ $m_id } ) {
  1587. #$debug and myprint( "MESSAGE $m_id\n" ) ;
  1588. my $h2_msg = $h2_hash{$m_id}{'m'};
  1589. sync_flags_fir( $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) ;
  1590. # Good
  1591. my $h2_size = $h2_hash{$m_id}{'s'};
  1592. $debug and myprint(
  1593. "Host1 size msg $h1_fold/$h1_msg = $h1_size <> $h2_size = Host2 $h2_fold/$h2_msg\n" ) ;
  1594. }
  1595. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1596. if ( $delete1 ) {
  1597. delete_message_on_host1( $h1_msg, $h1_fold ) ;
  1598. }
  1599. }
  1600. # END MESS: loop
  1601. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1602. MESS_IN_CACHE: foreach my $h1_msg ( @h1_msgs_in_cache ) {
  1603. my $h2_msg = $cache_1_2_ref->{ $h1_msg } ;
  1604. $debugcache and myprint( "cache messages update flags $h1_msg->$h2_msg\n" ) ;
  1605. sync_flags_fir( $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) ;
  1606. my $h1_size = $h1_fir_ref->{ $h1_msg }->{ 'RFC822.SIZE' } || 0 ;
  1607. $total_bytes_skipped += $h1_size;
  1608. $nb_msg_skipped += 1;
  1609. $h1_nb_msg_processed +=1 ;
  1610. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1611. }
  1612. #myprint( "Messages by uid: ", map { "$_ " } keys %h1_msgs_copy_by_uid, "\n" ) ;
  1613. MESS_BY_UID: foreach my $h1_msg ( sort { $a <=> $b } keys %h1_msgs_copy_by_uid ) {
  1614. #
  1615. $debug and myprint( "Copy by uid $h1_fold/$h1_msg\n" ) ;
  1616. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1617. my $h2_msg = copy_message( $sync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) ;
  1618. if( $delete2 and exists $h2_folders_from_1_several{ $h2_fold } and $h2_msg ) {
  1619. myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [fresh copy] on host2\n" ) ;
  1620. $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
  1621. }
  1622. last FOLDER if total_bytes_max_reached( ) ;
  1623. }
  1624. if ( $expunge1 ){
  1625. myprint( "Host1: Expunging folder $h1_fold $sync->{dry_message}\n" ) ;
  1626. if ( ! $sync->{dry} ) { $imap1->expunge( ) } ;
  1627. }
  1628. if ( $expunge2 ){
  1629. myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n" ) ;
  1630. if ( ! $sync->{dry} ) { $imap2->expunge( ) } ;
  1631. }
  1632. $debug and myprint( 'Time: ', timenext( ), " s\n" ) ;
  1633. }
  1634. myprint( "++++ End looping on each folder\n" ) ;
  1635. if ( $delete1 and $sync->{'delete1emptyfolders'} ) {
  1636. delete1emptyfolders( $sync ) ;
  1637. }
  1638. ( $debug or $sync->{debugfolders} ) and myprint( 'Time: ', timenext( ), " s\n" ) ;
  1639. if ( $foldersizesatend ) {
  1640. myprint( << 'END_SIZE' ) ;
  1641. Folders sizes after the synchronization.
  1642. You can remove this foldersizes listing by using "--nofoldersizesatend"
  1643. END_SIZE
  1644. foldersizesatend( ) ;
  1645. }
  1646. if ( ! lost_connection( $imap1, "for host1 [$sync->{host1}]" ) ) { $imap1->logout( ) ; }
  1647. if ( ! lost_connection( $imap2, "for host2 [$sync->{host2}]" ) ) { $imap2->logout( ) ; }
  1648. stats( $sync ) ;
  1649. myprint( errorsdump( $sync->{nb_errors}, errors_log( $sync ) ) ) if ( $sync->{errorsdump} ) ;
  1650. tests_live_result( $sync->{nb_errors} ) if ( $sync->{testslive} or $sync->{testslive6} ) ;
  1651. exit_clean( $sync, $EXIT_WITH_ERRORS ) if ( $sync->{nb_errors} ) ;
  1652. exit_clean( $sync, $EX_OK ) ;
  1653. # END of main program
  1654. # subroutines
  1655. sub myprint { return print @ARG ; }
  1656. sub myprintf { return printf @ARG ; }
  1657. sub mysprintf {
  1658. my( $format, @list ) = @ARG ;
  1659. return sprintf $format, @list ;
  1660. }
  1661. sub output_start {
  1662. my $mysync = shift @ARG ;
  1663. if ( not $mysync ) { return ; }
  1664. my @output = @ARG ;
  1665. $mysync->{ output } = join( q{}, @output ) . ( $mysync->{ output } || q{} ) ;
  1666. return $mysync->{ output } ;
  1667. }
  1668. sub tests_output_start {
  1669. note( 'Entering tests_output_start()' ) ;
  1670. my $mysync = { } ;
  1671. is( undef, output_start( ), 'output_start: no args => undef' ) ;
  1672. is( q{}, output_start( $mysync ), 'output_start: one arg => ""' ) ;
  1673. is( 'rrrr', output_start( $mysync, 'rrrr' ), 'output_start: rrrr => rrrr' ) ;
  1674. is( 'aaaarrrr', output_start( $mysync, 'aaaa' ), 'output_start: aaaa => aaaarrrr' ) ;
  1675. is( "\naaaarrrr", output_start( $mysync, "\n" ), 'output_start: \n => \naaaarrrr' ) ;
  1676. is( "ABC\naaaarrrr", output_start( $mysync, 'A', 'B', 'C' ), 'output_start: A B C => ABC\naaaarrrr' ) ;
  1677. note( 'Leaving tests_output_start()' ) ;
  1678. return ;
  1679. }
  1680. sub tests_output {
  1681. note( 'Entering tests_output()' ) ;
  1682. my $mysync = { } ;
  1683. is( undef, output( ), 'output: no args => undef' ) ;
  1684. is( q{}, output( $mysync ), 'output: one arg => ""' ) ;
  1685. is( 'rrrr', output( $mysync, 'rrrr' ), 'output: rrrr => rrrr' ) ;
  1686. is( 'rrrraaaa', output( $mysync, 'aaaa' ), 'output: aaaa => rrrraaaa' ) ;
  1687. is( "rrrraaaa\n", output( $mysync, "\n" ), 'output: \n => rrrraaaa\n' ) ;
  1688. is( "rrrraaaa\nABC", output( $mysync, 'A', 'B', 'C' ), 'output: A B C => rrrraaaaABC\n' ) ;
  1689. note( 'Leaving tests_output()' ) ;
  1690. return ;
  1691. }
  1692. sub output {
  1693. my $mysync = shift @ARG ;
  1694. if ( not $mysync ) { return ; }
  1695. my @output = @ARG ;
  1696. $mysync->{ output } .= join( q{}, @output ) ;
  1697. return $mysync->{ output } ;
  1698. }
  1699. sub tests_output_reset_with {
  1700. note( 'Entering tests_output_reset_with()' ) ;
  1701. my $mysync = { } ;
  1702. is( undef, output_reset_with( ), 'output_reset_with: no args => undef' ) ;
  1703. is( q{}, output_reset_with( $mysync ), 'output_reset_with: one arg => ""' ) ;
  1704. is( 'rrrr', output_reset_with( $mysync, 'rrrr' ), 'output_reset_with: rrrr => rrrr' ) ;
  1705. is( 'aaaa', output_reset_with( $mysync, 'aaaa' ), 'output_reset_with: aaaa => aaaa' ) ;
  1706. is( "\n", output_reset_with( $mysync, "\n" ), 'output_reset_with: \n => \n' ) ;
  1707. note( 'Leaving tests_output_reset_with()' ) ;
  1708. return ;
  1709. }
  1710. sub output_reset_with {
  1711. my $mysync = shift @ARG ;
  1712. if ( not $mysync ) { return ; }
  1713. my @output = @ARG ;
  1714. $mysync->{ output } = join( q{}, @output ) ;
  1715. return $mysync->{ output } ;
  1716. }
  1717. sub abort {
  1718. my $mysync = shift @ARG ;
  1719. if ( ! -r $sync->{pidfile} ) {
  1720. myprint( "Can not read pidfile $sync->{pidfile}. Exiting.\n" ) ;
  1721. exit $EX_OK ;
  1722. }
  1723. my $pidtokill = firstline( $sync->{pidfile} ) ;
  1724. if ( ! $pidtokill ) {
  1725. myprint( "No process to abort. Exiting.\n" ) ;
  1726. exit $EX_OK ;
  1727. }
  1728. # First ask for suicide
  1729. if ( kill 'ZERO', $pidtokill ) {
  1730. myprint( "Sending signal QUIT to PID $pidtokill \n" ) ;
  1731. kill 'QUIT', $pidtokill ;
  1732. sleep 1 ;
  1733. }else{
  1734. myprint( "Can not send signal to PID $pidtokill. Exiting.\n" ) ;
  1735. exit $EX_OK ;
  1736. }
  1737. # Then murder
  1738. if ( kill 'ZERO', $pidtokill ) {
  1739. myprint( "Sending signal KILL to PID $pidtokill \n" ) ;
  1740. kill 'KILL', $pidtokill ;
  1741. sleep 1 ;
  1742. }else{
  1743. myprint( "Process PID $pidtokill ended. Exiting.\n" ) ;
  1744. exit $EX_OK ;
  1745. }
  1746. # Well ...
  1747. if ( kill 'ZERO', $pidtokill ) {
  1748. myprint( "Process PID $pidtokill still there. Can not do much. Exiting.\n" ) ;
  1749. exit $EX_OK ;
  1750. }
  1751. return ;
  1752. }
  1753. sub docker_context {
  1754. my $mysync = shift ;
  1755. -e '/.dockerenv' || return ;
  1756. myprint( "Docker context detected with /.dockerenv\n" ) ;
  1757. # No pidfile
  1758. $mysync->{pidfile} = q{} ;
  1759. # No log
  1760. $mysync->{log} = 0 ;
  1761. # In case
  1762. myprint( "Changing current directory to /var/tmp/\n" ) ;
  1763. chdir '/var/tmp/' ;
  1764. return ;
  1765. }
  1766. sub cgibegin {
  1767. if ( ! under_cgi_context( ) ) { return ; }
  1768. my $mysync = shift ;
  1769. require CGI ;
  1770. CGI->import( qw( -no_debug ) ) ;
  1771. require CGI::Carp ;
  1772. CGI::Carp->import( qw( fatalsToBrowser ) ) ;
  1773. $mysync->{cgi} = CGI->new( ) ;
  1774. return ;
  1775. }
  1776. sub tests_under_cgi_context {
  1777. note( 'Entering tests_under_cgi_context()' ) ;
  1778. # $ENV{SERVER_SOFTWARE} = 'under imapsync' ;
  1779. do {
  1780. # Not in cgi context
  1781. delete local $ENV{SERVER_SOFTWARE} ;
  1782. is( undef, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE unset => not in cgi context' ) ;
  1783. } ;
  1784. do {
  1785. # In cgi context
  1786. local $ENV{SERVER_SOFTWARE} = 'under imapsync' ;
  1787. is( 1, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE set => in cgi context' ) ;
  1788. } ;
  1789. do {
  1790. # Not in cgi context
  1791. delete local $ENV{SERVER_SOFTWARE} ;
  1792. is( undef, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE unset => not in cgi context' ) ;
  1793. } ;
  1794. do {
  1795. # In cgi context
  1796. local $ENV{SERVER_SOFTWARE} = 'under imapsync' ;
  1797. is( 1, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE set => in cgi context' ) ;
  1798. } ;
  1799. note( 'Leaving tests_under_cgi_context()' ) ;
  1800. return ;
  1801. }
  1802. sub under_cgi_context {
  1803. # Under cgi context
  1804. if ( $ENV{SERVER_SOFTWARE} ) {
  1805. return 1 ;
  1806. }
  1807. # Not in cgi context
  1808. return ;
  1809. }
  1810. sub cgibuildheader {
  1811. if ( ! under_cgi_context( ) ) { return ; }
  1812. my $mysync = shift ;
  1813. my $imapsync_runs = $mysync->{cgi}->cookie( 'imapsync_runs' ) || 0 ;
  1814. my $cookie = $mysync->{cgi}->cookie(
  1815. -name => 'imapsync_runs',
  1816. -value => 1 + $imapsync_runs,
  1817. -expires => '+20y',
  1818. -path => '/cgi-bin/imapsync',
  1819. ) ;
  1820. my $httpheader ;
  1821. if ( $mysync->{ abort } ) {
  1822. $httpheader = $mysync->{cgi}->header(
  1823. -type => 'text/plain',
  1824. -status => '200 OK to stop playing IMAP mailboxes' . ". Load is $mysync->{ loadavg }",
  1825. ) ;
  1826. }elsif( $mysync->{ loaddelay } ) {
  1827. # https://tools.ietf.org/html/rfc2616#section-10.5.4
  1828. # 503 Service Unavailable
  1829. # The server is currently unable to handle the request due to a temporary overloading or maintenance of the server.
  1830. $httpheader = $mysync->{cgi}->header(
  1831. -type => 'text/plain',
  1832. -status => '503 Service Unavailable' . ". Be back in $mysync->{ loaddelay } min. Load is $mysync->{ loadavg }",
  1833. ) ;
  1834. }else{
  1835. $httpheader = $mysync->{cgi}->header(
  1836. -type => 'text/plain',
  1837. -status => '200 OK to play IMAP mailboxes' . ". Load is $mysync->{ loadavg }",
  1838. -cookie => $cookie,
  1839. ) ;
  1840. }
  1841. output_start( $mysync, $httpheader ) ;
  1842. return ;
  1843. }
  1844. sub cgiload {
  1845. if ( ! under_cgi_context( ) ) { return ; }
  1846. my $mysync = shift ;
  1847. if ( $mysync->{ abort } ) { return ; } # keep going to abort
  1848. if ( $mysync->{ loaddelay } ) {
  1849. myprint( "Server is on heavy load. Be back in $mysync->{ loaddelay } min. Load is $mysync->{ loadavg }\n") ;
  1850. exit_clean( $mysync, $EX_UNAVAILABLE ) ;
  1851. }
  1852. return ;
  1853. }
  1854. sub tests_set_umask {
  1855. note( 'Entering tests_set_umask()' ) ;
  1856. my $save_umask = umask ;
  1857. my $mysync = {} ;
  1858. if ( 'MSWin32' eq $OSNAME ) {
  1859. is( undef, set_umask( $mysync ), "set_umask: set failure to $UMASK_PARANO on MSWin32" ) ;
  1860. }else{
  1861. is( 1, set_umask( $mysync ), "set_umask: set to $UMASK_PARANO" ) ;
  1862. }
  1863. umask $save_umask ;
  1864. note( 'Leaving tests_set_umask()' ) ;
  1865. return ;
  1866. }
  1867. sub set_umask {
  1868. my $mysync = shift ;
  1869. my $previous_umask = umask_str( ) ;
  1870. my $new_umask = umask_str( $UMASK_PARANO ) ;
  1871. output( $mysync, "Umask set with $new_umask (was $previous_umask)\n" ) ;
  1872. if ( $new_umask eq $UMASK_PARANO ) {
  1873. return 1 ;
  1874. }
  1875. return ;
  1876. }
  1877. sub tests_umask_str {
  1878. note( 'Entering tests_umask_str()' ) ;
  1879. my $save_umask = umask ;
  1880. is( umask_str( ), umask_str( ), 'umask_str: no parameters => idopotent' ) ;
  1881. is( my $save_umask_str = umask_str( ), umask_str( ), 'umask_str: no parameters => idopotent + save' ) ;
  1882. is( '0000', umask_str( q{ } ), 'umask_str: q{ } => 0000' ) ;
  1883. is( '0000', umask_str( q{} ), 'umask_str: q{} => 0000' ) ;
  1884. is( '0000', umask_str( '0000' ), 'umask_str: 0000 => 0000' ) ;
  1885. is( '0000', umask_str( '0' ), 'umask_str: 0 => 0000' ) ;
  1886. is( '0200', umask_str( '0200' ), 'umask_str: 0200 => 0200' ) ;
  1887. is( '0400', umask_str( '0400' ), 'umask_str: 0400 => 0400' ) ;
  1888. is( '0600', umask_str( '0600' ), 'umask_str: 0600 => 0600' ) ;
  1889. SKIP: {
  1890. if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests success only for Unix', 6 ) ; }
  1891. is( '0100', umask_str( '0100' ), 'umask_str: 0100 => 0100' ) ;
  1892. is( '0001', umask_str( '0001' ), 'umask_str: 0001 => 0001' ) ;
  1893. is( '0777', umask_str( '0777' ), 'umask_str: 0777 => 0777' ) ;
  1894. is( '0777', umask_str( '00777' ), 'umask_str: 00777 => 0777' ) ;
  1895. is( '0777', umask_str( ' 777 ' ), 'umask_str: 777 => 0777' ) ;
  1896. is( "$UMASK_PARANO", umask_str( $UMASK_PARANO ), "umask_str: UMASK_PARANO $UMASK_PARANO => $UMASK_PARANO" ) ;
  1897. }
  1898. is( $save_umask_str, umask_str( $save_umask_str ), 'umask_str: restore with str' ) ;
  1899. is( $save_umask, umask, 'umask_str: umask is restored, controlled by direct umask' ) ;
  1900. is( $save_umask, umask $save_umask, 'umask_str: umask is restored by direct umask' ) ;
  1901. is( $save_umask, umask, 'umask_str: umask initial controlled by direct umask' ) ;
  1902. note( 'Leaving tests_umask_str()' ) ;
  1903. return ;
  1904. }
  1905. sub umask_str {
  1906. my $value = shift ;
  1907. if ( defined $value ) {
  1908. umask oct( $value ) ;
  1909. }
  1910. my $current = umask ;
  1911. return( sprintf( '%#04o', $current ) ) ;
  1912. }
  1913. sub tests_umask {
  1914. note( 'Entering tests_umask()' ) ;
  1915. my $save_umask ;
  1916. is( umask, umask, 'umask: umask is umask' ) ;
  1917. is( $save_umask = umask, umask, "umask: umask is umask again + save it: $save_umask" ) ;
  1918. is( $save_umask, umask oct(0000), 'umask: umask 0000' ) ;
  1919. is( oct(0000), umask, 'umask: umask is now 0000' ) ;
  1920. is( oct(0000), umask oct(777), 'umask: umask 0777 call, previous 0000' ) ;
  1921. SKIP: {
  1922. if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests success only for Unix', 2 ) ; }
  1923. is( oct(777), umask, 'umask: umask is now 0777' ) ;
  1924. is( oct(777), umask $save_umask, "umask: umask $save_umask restore inital value, previous 0777" ) ;
  1925. }
  1926. ok( defined umask $save_umask, "umask: umask $save_umask restore inital value, previous defined" ) ;
  1927. is( $save_umask, umask, 'umask: umask is umask restored' ) ;
  1928. note( 'Leaving tests_umask()' ) ;
  1929. return ;
  1930. }
  1931. sub cgisetcontext {
  1932. if ( ! under_cgi_context( ) ) { return ; }
  1933. my $mysync = shift @ARG ;
  1934. output( $mysync, "Under cgi context\n" ) ;
  1935. set_umask( $mysync ) ;
  1936. # Remove all content in unsafe evaled options
  1937. @regextrans2 = ( ) ;
  1938. @regexflag = ( ) ;
  1939. @regexmess = ( ) ;
  1940. @skipmess = ( ) ;
  1941. @pipemess = ( ) ;
  1942. $delete2foldersonly = undef ;
  1943. $delete2foldersbutnot = undef ;
  1944. $maxlinelengthcmd = undef ;
  1945. # Set safe default values (I hope...)
  1946. $mysync->{pidfile} = 'imapsync.pid' ;
  1947. $mysync->{pidfilelocking} = 1 ;
  1948. $mysync->{errorsmax} = $ERRORS_MAX_CGI ;
  1949. $modulesversion = 0 ;
  1950. $releasecheck = 1 ;
  1951. $usecache = 0 ;
  1952. $mysync->{showpasswords} = 0 ;
  1953. $debugimap1 = $debugimap2 = $debugimap = 0 ;
  1954. $reconnectretry1 = $reconnectretry2 = $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ;
  1955. $pipemesscheck = 0 ;
  1956. $mysync->{hashfile} = $CGI_HASHFILE ;
  1957. my $hashsynclocal = hashsynclocal( $mysync ) || die "Can not get hashsynclocal. Exiting\n" ;
  1958. $cgidir = $CGI_TMPDIR_TOP . '/' . $hashsynclocal ;
  1959. -d $cgidir or mkpath $cgidir or die "Can not create $cgidir: $OS_ERROR\n" ;
  1960. chdir $cgidir or die "Can not cd to $cgidir: $OS_ERROR\n" ;
  1961. $tmpdir = $cgidir ;
  1962. cgioutputenvcontext( $mysync ) ;
  1963. $debug and output( $mysync, 'Current directory is ' . getcwd( ) . "\n" ) ;
  1964. $debug and output( $mysync, 'Real user id is ' . getpwuid_any_os( $REAL_USER_ID ) . " (uid $REAL_USER_ID)\n" ) ;
  1965. $debug and output( $mysync, 'Effective user id is ' . getpwuid_any_os( $EFFECTIVE_USER_ID ). " (euid $EFFECTIVE_USER_ID)\n" ) ;
  1966. return ;
  1967. }
  1968. sub cgioutputenvcontext {
  1969. my $mysync = shift @ARG ;
  1970. for my $envvar ( qw( REMOTE_ADDR REMOTE_HOST HTTP_REFERER HTTP_USER_AGENT HTTP_COOKIE ) ) {
  1971. my $envval = $ENV{ $envvar } || q{} ;
  1972. if ( $envval ) { output( $mysync, "$envvar is $envval\n" ) } ;
  1973. }
  1974. return ;
  1975. }
  1976. sub debugsleep {
  1977. my $mysync = shift @ARG ;
  1978. if ( defined $mysync->{debugsleep} ) {
  1979. myprint( "Info: sleeping $mysync->{debugsleep}s\n" ) ;
  1980. sleep $mysync->{debugsleep} ;
  1981. }
  1982. return ;
  1983. }
  1984. sub foldersizes_on_h1h2 {
  1985. myprint( << 'END_SIZE' ) ;
  1986. Folders sizes before the synchronization.
  1987. You can remove foldersizes listings by using "--nofoldersizes" and "--nofoldersizesatend"
  1988. but then you will also loose the ETA (Estimation Time of Arrival) given after each message copy.
  1989. END_SIZE
  1990. ( $h1_nb_msg_start, $h1_bytes_start ) = foldersizes( 'Host1', $imap1, $search1, $sync->{abletosearch1}, @h1_folders_wanted ) ;
  1991. ( $h2_nb_msg_start, $h2_bytes_start ) = foldersizes( 'Host2', $imap2, $search2, $sync->{abletosearch2}, @h2_folders_from_1_wanted ) ;
  1992. if ( not all_defined( $h1_nb_msg_start, $h1_bytes_start, $h2_nb_msg_start, $h2_bytes_start ) ) {
  1993. my $error = "Failure getting foldersizes, ETA and final diff will not be displayed\n" ;
  1994. errors_incr( $sync, $error ) ;
  1995. $foldersizes = 0 ;
  1996. $foldersizesatend = 0 ;
  1997. return ;
  1998. }
  1999. my $h2_bytes_limit = $sync->{h2}->{quota_limit_bytes} || 0 ;
  2000. if ( $h2_bytes_limit and ( $h2_bytes_limit < $h1_bytes_start ) ) {
  2001. my $quota_percent = mysprintf( '%.0f', $NUMBER_100 * $h1_bytes_start / $h2_bytes_limit ) ;
  2002. my $error = "Host2: Quota limit will be exceeded! Over $quota_percent % ( $h1_bytes_start bytes / $h2_bytes_limit bytes )\n" ;
  2003. errors_incr( $sync, $error ) ;
  2004. }
  2005. return ;
  2006. }
  2007. sub total_bytes_max_reached {
  2008. return( 0 ) if not $exitwhenover ;
  2009. if ( $sync->{total_bytes_transferred} >= $exitwhenover ) {
  2010. myprint( "Maximum bytes transferred reached, $sync->{total_bytes_transferred} >= $exitwhenover, ending sync\n" ) ;
  2011. return( 1 ) ;
  2012. }
  2013. }
  2014. sub all_defined {
  2015. if ( not @ARG ) {
  2016. return 0 ;
  2017. }
  2018. foreach my $elem ( @ARG ) {
  2019. if ( not defined $elem ) {
  2020. return 0 ;
  2021. }
  2022. }
  2023. return 1 ;
  2024. }
  2025. sub tests_all_defined {
  2026. note( 'Entering tests_all_defined()' ) ;
  2027. is( 0, all_defined( ), 'all_defined: no param => 0' ) ;
  2028. is( 0, all_defined( () ), 'all_defined: void list => 0' ) ;
  2029. is( 0, all_defined( undef ), 'all_defined: undef => 0' ) ;
  2030. is( 0, all_defined( undef, undef ), 'all_defined: undef => 0' ) ;
  2031. is( 0, all_defined( 1, undef ), 'all_defined: 1 undef => 0' ) ;
  2032. is( 0, all_defined( undef, 1 ), 'all_defined: undef 1 => 0' ) ;
  2033. is( 1, all_defined( 1, 1 ), 'all_defined: 1 1 => 1' ) ;
  2034. is( 1, all_defined( (1, 1) ), 'all_defined: (1 1) => 1' ) ;
  2035. note( 'Leaving tests_all_defined()' ) ;
  2036. return ;
  2037. }
  2038. sub tests_hashsynclocal {
  2039. note( 'Entering tests_hashsynclocal()' ) ;
  2040. my $mysync = {
  2041. host1 => '',
  2042. user1 => '',
  2043. password1 => '',
  2044. host2 => '',
  2045. user2 => '',
  2046. password2 => '',
  2047. } ;
  2048. is( undef, hashsynclocal( $mysync ), 'hashsynclocal: no hashfile name' ) ;
  2049. $mysync->{ hashfile } = '' ;
  2050. is( undef, hashsynclocal( $mysync ), 'hashsynclocal: empty hashfile name' ) ;
  2051. $mysync->{ hashfile } = './noexist/rrr' ;
  2052. is( undef, hashsynclocal( $mysync ), 'hashsynclocal: no exists hashfile dir' ) ;
  2053. SKIP: {
  2054. if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests only for Unix', 1 ) ; }
  2055. $mysync->{ hashfile } = '/rrr' ;
  2056. is( undef, hashsynclocal( $mysync ), 'hashsynclocal: permission denied' ) ;
  2057. }
  2058. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'hashsynclocal: mkpath W/tmp/tests/' ) ;
  2059. $mysync->{ hashfile } = 'W/tmp/tests/imapsync_hash' ;
  2060. ok( ! -e 'W/tmp/tests/imapsync_hash' || unlink 'W/tmp/tests/imapsync_hash', 'hashsynclocal: unlink W/tmp/tests/imapsync_hash' ) ;
  2061. ok( ! -e 'W/tmp/tests/imapsync_hash', 'hashsynclocal: verify there is no W/tmp/tests/imapsync_hash' ) ;
  2062. is( 'ecdeb4ede672794d173da4e08c52b8ee19b7d252', hashsynclocal( $mysync, 'mukksyhpmbixkxkpjlqivmlqsulpictj' ), 'hashsynclocal: creating/reading W/tmp/tests/imapsync_hash' ) ;
  2063. # A second time now
  2064. is( 'ecdeb4ede672794d173da4e08c52b8ee19b7d252', hashsynclocal( $mysync ), 'hashsynclocal: reading W/tmp/tests/imapsync_hash second time => same' ) ;
  2065. note( 'Leaving tests_hashsynclocal()' ) ;
  2066. return ;
  2067. }
  2068. sub hashsynclocal {
  2069. my $mysync = shift ;
  2070. my $hashkey = shift ; # Optional, only there for tests
  2071. my $hashfile = $mysync->{ hashfile } ;
  2072. $hashfile = createhashfileifneeded( $hashfile, $hashkey ) ;
  2073. if ( ! $hashfile ) {
  2074. return ;
  2075. }
  2076. $hashkey = firstline( $hashfile ) ;
  2077. if ( ! $hashkey ) {
  2078. myprint( "No hashkey!\n" ) ;
  2079. return ;
  2080. }
  2081. my $hashsynclocal = hashsync( $mysync, $hashkey ) ;
  2082. return( $hashsynclocal ) ;
  2083. }
  2084. sub tests_hashsync {
  2085. note( 'Entering tests_hashsync()' ) ;
  2086. is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hashsync( {}, q{} ), 'hashsync: empty args' ) ;
  2087. my $mysync ;
  2088. $mysync->{ host1 } = 'zzz' ;
  2089. is( 'e86a28a3611c1e7bbaf8057cd00ae122781a11fe', hashsync( $mysync, q{} ), 'hashsync: host1 zzz => ' ) ;
  2090. is( 'e86a28a3611c1e7bbaf8057cd00ae122781a11fe', hashsync( $mysync, q{} ), 'hashsync: host1 zzz => ' ) ;
  2091. $mysync->{ host2 } = 'zzz' ;
  2092. is( '15959573e4a86763253a7aedb1a2b0c60d133dc2', hashsync( $mysync, q{} ), 'hashsync: + host2 zzz => ' ) ;
  2093. is( 'b8d4ab541b209c75928528020ca28ee43488bd8f', hashsync( $mysync, 'A' ), 'hashsync: + hashkey A => ' ) ;
  2094. note( 'Leaving tests_hashsync()' ) ;
  2095. return ;
  2096. }
  2097. sub hashsync {
  2098. my $mysync = shift ;
  2099. my $hashkey = shift ;
  2100. my $mystring = join( q{},
  2101. $mysync->{ host1 } || q{},
  2102. $mysync->{ user1 } || q{},
  2103. $mysync->{ password1 } || q{},
  2104. $mysync->{ host2 } || q{},
  2105. $mysync->{ user2 } || q{},
  2106. $mysync->{ password2 } || q{},
  2107. ) ;
  2108. my $hashsync = hmac_sha1_hex( $mystring, $hashkey ) ;
  2109. #myprint( "$hashsync\n" ) ;
  2110. return( $hashsync ) ;
  2111. }
  2112. sub tests_createhashfileifneeded {
  2113. note( 'Entering tests_createhashfileifneeded()' ) ;
  2114. is( undef, createhashfileifneeded( ), 'createhashfileifneeded: no parameters => undef' ) ;
  2115. note( 'Leaving tests_createhashfileifneeded()' ) ;
  2116. return ;
  2117. }
  2118. sub createhashfileifneeded {
  2119. my $hashfile = shift ;
  2120. my $hashkey = shift || rand32( ) ;
  2121. # no name
  2122. if ( ! $hashfile ) {
  2123. return ;
  2124. }
  2125. # already there
  2126. if ( -e -r $hashfile ) {
  2127. return $hashfile ;
  2128. }
  2129. # not creatable
  2130. if ( ! -w dirname( $hashfile ) ) {
  2131. return ;
  2132. }
  2133. # creatable
  2134. open my $FILE_HANDLE, '>', $hashfile
  2135. or do {
  2136. myprint( "Could not open $hashfile for writing. Check permissions or disk space." ) ;
  2137. return ;
  2138. } ;
  2139. myprint( "Writing random hashkey in $hashfile, once for all times\n" ) ;
  2140. print $FILE_HANDLE $hashkey ;
  2141. close $FILE_HANDLE ;
  2142. # Should be there now
  2143. if ( -e -r $hashfile ) {
  2144. return $hashfile ;
  2145. }
  2146. # unknown failure
  2147. return ;
  2148. }
  2149. sub tests_rand32 {
  2150. note( 'Entering tests_rand32()' ) ;
  2151. my $string = rand32( ) ;
  2152. print "$string\n" ;
  2153. is( 32, length( $string ), 'rand32: 32 characters long' ) ;
  2154. is( 32, length( rand32( ) ), 'rand32: 32 characters long, another one' ) ;
  2155. note( 'Leaving tests_rand32()' ) ;
  2156. return ;
  2157. }
  2158. sub rand32 {
  2159. my @chars = ( "a".."z" ) ;
  2160. my $string;
  2161. $string .= $chars[rand @chars] for 1..32 ;
  2162. return $string ;
  2163. }
  2164. sub imap_id_stuff {
  2165. my $mysync = shift ;
  2166. if ( not $mysync->{id} ) { return ; } ;
  2167. $mysync->{h1_imap_id} = imap_id( $mysync, $mysync->{imap1}, 'Host1' ) ;
  2168. #myprint( 'Host1: ' . $mysync->{h1_imap_id} ) ;
  2169. $mysync->{h2_imap_id} = imap_id( $mysync, $mysync->{imap2}, 'Host2' ) ;
  2170. #myprint( 'Host2: ' . $mysync->{h2_imap_id} ) ;
  2171. return ;
  2172. }
  2173. sub imap_id {
  2174. my ( $mysync, $imap, $Side ) = @_ ;
  2175. $Side ||= q{} ;
  2176. my $imap_id_response = q{} ;
  2177. if ( not $imap->has_capability( 'ID' ) ) {
  2178. $imap_id_response = 'No ID capability' ;
  2179. myprint( "$Side: No ID capability\n" ) ;
  2180. }else{
  2181. my $id_inp = imapsync_id( $mysync, { side => lc $Side } ) ;
  2182. myprint( "\n$Side: found ID capability. Sending/receiving ID, presented in raw IMAP for now.\n"
  2183. . "In order to avoid sending/receiving ID, use option --noid\n" ) ;
  2184. my $debug_before = $imap->Debug( ) ;
  2185. $imap->Debug( 1 ) ;
  2186. my $id_out = $imap->tag_and_run( 'ID ' . $id_inp ) ;
  2187. #my $id_out = $imap->tag_and_run( 'ID NIL' ) ;
  2188. myprint( "\n" ) ;
  2189. $imap->Debug( $debug_before ) ;
  2190. #$imap_id_response = Data::Dumper->Dump( [ $id_out ], [ 'IMAP_ID' ] ) ;
  2191. }
  2192. return( $imap_id_response ) ;
  2193. }
  2194. sub imapsync_id {
  2195. my $mysync = shift ;
  2196. my $overhashref = shift ;
  2197. # See http://tools.ietf.org/html/rfc2971.html
  2198. my $imapsync_id = { } ;
  2199. my $imapsync_id_lamiral = {
  2200. name => 'imapsync',
  2201. version => imapsync_version( $mysync ),
  2202. os => $OSNAME,
  2203. vendor => 'Gilles LAMIRAL',
  2204. 'support-url' => 'http://imapsync.lamiral.info/',
  2205. # Example of date-time: 19-Sep-2015 08:56:07
  2206. date => date_from_rcs( q{$Date: 2017/09/05 16:14:53 $ } ),
  2207. } ;
  2208. my $imapsync_id_github = {
  2209. name => 'imapsync',
  2210. version => imapsync_version( $mysync ),
  2211. os => $OSNAME,
  2212. vendor => 'github',
  2213. 'support-url' => 'https://github.com/imapsync/imapsync',
  2214. date => date_from_rcs( q{$Date: 2017/09/05 16:14:53 $ } ),
  2215. } ;
  2216. $imapsync_id = $imapsync_id_lamiral ;
  2217. #$imapsync_id = $imapsync_id_github ;
  2218. my %mix = ( %{ $imapsync_id }, %{ $overhashref } ) ;
  2219. my $imapsync_id_str = format_for_imap_arg( \%mix ) ;
  2220. #myprint( "$imapsync_id_str\n" ) ;
  2221. return( $imapsync_id_str ) ;
  2222. }
  2223. sub tests_imapsync_id {
  2224. note( 'Entering tests_imapsync_id()' ) ;
  2225. my $mysync ;
  2226. ok( '("name" "imapsync" "version" "111" "os" "beurk" "vendor" "Gilles LAMIRAL" "support-url" "http://imapsync.lamiral.info/" "date" "22-12-1968" "side" "host1")'
  2227. eq imapsync_id( $mysync,
  2228. {
  2229. version => 111,
  2230. os => 'beurk',
  2231. date => '22-12-1968',
  2232. side => 'host1'
  2233. }
  2234. ),
  2235. 'tests_imapsync_id override'
  2236. ) ;
  2237. note( 'Leaving tests_imapsync_id()' ) ;
  2238. return ;
  2239. }
  2240. sub format_for_imap_arg {
  2241. my $ref = shift ;
  2242. my $string = q{} ;
  2243. my %terms = %{ $ref } ;
  2244. my @terms = ( ) ;
  2245. if ( not ( %terms ) ) { return( 'NIL' ) } ;
  2246. # sort like in RFC then add extra key/values
  2247. foreach my $key ( qw( name version os os-version vendor support-url address date command arguments environment) ) {
  2248. if ( $terms{ $key } ) {
  2249. push @terms, $key, $terms{ $key } ;
  2250. delete $terms{ $key } ;
  2251. }
  2252. }
  2253. push @terms, %terms ;
  2254. $string = '(' . ( join q{ }, map { '"' . $_ . '"' } @terms ) . ')' ;
  2255. return( $string ) ;
  2256. }
  2257. sub tests_format_for_imap_arg {
  2258. note( 'Entering tests_format_for_imap_arg()' ) ;
  2259. ok( 'NIL' eq format_for_imap_arg( { } ), 'format_for_imap_arg empty hash ref' ) ;
  2260. ok( '("name" "toto")' eq format_for_imap_arg( { name => 'toto' } ), 'format_for_imap_arg { name => toto }' ) ;
  2261. ok( '("name" "toto" "key" "val")' eq format_for_imap_arg( { name => 'toto', key => 'val' } ), 'format_for_imap_arg 2 x key val' ) ;
  2262. note( 'Leaving tests_format_for_imap_arg()' ) ;
  2263. return ;
  2264. }
  2265. sub quota {
  2266. my ( $imap, $side, $mysync ) = @_ ;
  2267. my %side = (
  2268. h1 => 'Host1',
  2269. h2 => 'Host2',
  2270. ) ;
  2271. my $Side = $side{ $side } ;
  2272. my $debug_before = $imap->Debug( ) ;
  2273. $imap->Debug( 1 ) ;
  2274. if ( not $imap->has_capability( 'QUOTA' ) ) {
  2275. $imap->Debug( $debug_before ) ;
  2276. return ;
  2277. } ;
  2278. myprint( "\n$Side: found quota, presented in raw IMAP\n" ) ;
  2279. my $getquotaroot = $imap->getquotaroot( 'INBOX' ) ;
  2280. # Gmail INBOX quotaroot is "" but with it Mail::IMAPClient does a literal GETQUOTA {2} \n ""
  2281. #$imap->quota( 'ROOT' ) ;
  2282. #$imap->quota( '""' ) ;
  2283. myprint( "\n" ) ;
  2284. $imap->Debug( $debug_before ) ;
  2285. my $quota_limit_bytes = quota_extract_storage_limit_in_bytes( $getquotaroot ) ;
  2286. my $quota_current_bytes = quota_extract_storage_current_in_bytes( $getquotaroot ) ;
  2287. $mysync->{$side}->{quota_limit_bytes} = $quota_limit_bytes ;
  2288. $mysync->{$side}->{quota_current_bytes} = $quota_current_bytes ;
  2289. my $quota_percent ;
  2290. if ( $quota_limit_bytes > 0 ) {
  2291. $quota_percent = mysprintf( '%.2f', $NUMBER_100 * $quota_current_bytes / $quota_limit_bytes ) ;
  2292. }else{
  2293. $quota_percent = 0 ;
  2294. }
  2295. myprint( "$Side: Quota current storage is $quota_current_bytes bytes. Limit is $quota_limit_bytes bytes. So $quota_percent % full\n" ) ;
  2296. if ( $QUOTA_PERCENT_LIMIT < $quota_percent ) {
  2297. my $error = "$Side: $quota_percent % full: it is time to find a bigger place! ( $quota_current_bytes bytes / $quota_limit_bytes bytes )\n" ;
  2298. errors_incr( $mysync, $error ) ;
  2299. }
  2300. return ;
  2301. }
  2302. sub tests_quota_extract_storage_limit_in_bytes {
  2303. note( 'Entering tests_quota_extract_storage_limit_in_bytes()' ) ;
  2304. my $imap_output = [
  2305. '* QUOTAROOT "INBOX" "Storage quota" "Messages quota"',
  2306. '* QUOTA "Storage quota" (STORAGE 1 104857600)',
  2307. '* QUOTA "Messages quota" (MESSAGE 2 100000)',
  2308. '5 OK Getquotaroot completed.'
  2309. ] ;
  2310. ok( $NUMBER_104_857_600 * $KIBI == quota_extract_storage_limit_in_bytes( $imap_output ), 'quota_extract_storage_limit_in_bytes ') ;
  2311. note( 'Leaving tests_quota_extract_storage_limit_in_bytes()' ) ;
  2312. return ;
  2313. }
  2314. sub quota_extract_storage_limit_in_bytes {
  2315. my $imap_output = shift ;
  2316. my $limit_kb ;
  2317. $limit_kb = ( map { /.*\(\s*STORAGE\s+\d+\s+(\d+)\s*\)/x ? $1 : () } @{ $imap_output } )[0] ;
  2318. $limit_kb ||= 0 ;
  2319. $debug and myprint( "storage_limit_kb = $limit_kb\n" ) ;
  2320. return( $KIBI * $limit_kb ) ;
  2321. }
  2322. sub tests_quota_extract_storage_current_in_bytes {
  2323. note( 'Entering tests_quota_extract_storage_current_in_bytes()' ) ;
  2324. my $imap_output = [
  2325. '* QUOTAROOT "INBOX" "Storage quota" "Messages quota"',
  2326. '* QUOTA "Storage quota" (STORAGE 1 104857600)',
  2327. '* QUOTA "Messages quota" (MESSAGE 2 100000)',
  2328. '5 OK Getquotaroot completed.'
  2329. ] ;
  2330. ok( 1*$KIBI == quota_extract_storage_current_in_bytes( $imap_output ), 'quota_extract_storage_current_in_bytes: 1 => 1024 ') ;
  2331. note( 'Leaving tests_quota_extract_storage_current_in_bytes()' ) ;
  2332. return ;
  2333. }
  2334. sub quota_extract_storage_current_in_bytes {
  2335. my $imap_output = shift ;
  2336. my $current_kb ;
  2337. $current_kb = ( map { /.*\(\s*STORAGE\s+(\d+)\s+\d+\s*\)/x ? $1 : () } @{ $imap_output } )[0] ;
  2338. $current_kb ||= 0 ;
  2339. $debug and myprint( "storage_current_kb = $current_kb\n" ) ;
  2340. return( $KIBI * $current_kb ) ;
  2341. }
  2342. sub automap {
  2343. my ( $mysync ) = @_ ;
  2344. if ( $mysync->{automap} ) {
  2345. myprint( "Turned on automapping folders ( use --noautomap to turn off automapping )\n" ) ;
  2346. }else{
  2347. myprint( "Turned off automapping folders ( use --automap to turn on automapping )\n" ) ;
  2348. return ;
  2349. }
  2350. $mysync->{h1_special} = special_from_folders_hash( $mysync->{imap1}, 'Host1' ) ;
  2351. $mysync->{h2_special} = special_from_folders_hash( $mysync->{imap2}, 'Host2' ) ;
  2352. build_possible_special( $mysync ) ;
  2353. build_guess_special( $mysync ) ;
  2354. build_automap( $mysync ) ;
  2355. return ;
  2356. }
  2357. sub build_guess_special {
  2358. my ( $mysync ) = shift ;
  2359. foreach my $h1_fold ( sort keys %{ $mysync->{h1_folders_all} } ) {
  2360. my $special = guess_special( $h1_fold, $mysync->{possible_special}, $mysync->{h1_prefix} ) ;
  2361. if ( $special ) {
  2362. $mysync->{h1_special_guessed}{$h1_fold} = $special ;
  2363. my $already_guessed = $mysync->{h1_special_guessed}{$special} ;
  2364. if ( $already_guessed ) {
  2365. myprint( "Host1: $h1_fold not $special because set to $already_guessed\n" ) ;
  2366. }else{
  2367. $mysync->{h1_special_guessed}{$special} = $h1_fold ;
  2368. }
  2369. }
  2370. }
  2371. foreach my $h2_fold ( sort keys %{ $mysync->{h2_folders_all} } ) {
  2372. my $special = guess_special( $h2_fold, $mysync->{possible_special}, $mysync->{h2_prefix} ) ;
  2373. if ( $special ) {
  2374. $mysync->{h2_special_guessed}{$h2_fold} = $special ;
  2375. my $already_guessed = $mysync->{h2_special_guessed}{$special} ;
  2376. if ( $already_guessed ) {
  2377. myprint( "Host2: $h2_fold not $special because set to $already_guessed\n" ) ;
  2378. }else{
  2379. $mysync->{h2_special_guessed}{$special} = $h2_fold ;
  2380. }
  2381. }
  2382. }
  2383. return ;
  2384. }
  2385. sub guess_special {
  2386. my( $folder, $possible_special_ref, $prefix ) = @_ ;
  2387. my $folder_no_prefix = $folder ;
  2388. $folder_no_prefix =~ s/\Q${prefix}\E//xms ;
  2389. #$debug and myprint( "folder_no_prefix: $folder_no_prefix\n" ) ;
  2390. my $guess_special = $possible_special_ref->{ $folder }
  2391. || $possible_special_ref->{ $folder_no_prefix }
  2392. || q{} ;
  2393. return( $guess_special ) ;
  2394. }
  2395. sub tests_guess_special {
  2396. note( 'Entering tests_guess_special()' ) ;
  2397. my $possible_special_ref = build_possible_special( my $mysync ) ;
  2398. ok( '\Sent' eq guess_special( 'Sent', $possible_special_ref, q{} ) ,'guess_special: Sent => \Sent' ) ;
  2399. ok( q{} eq guess_special( 'Blabla', $possible_special_ref, q{} ) ,'guess_special: Blabla => q{}' ) ;
  2400. ok( '\Sent' eq guess_special( 'INBOX.Sent', $possible_special_ref, 'INBOX.' ) ,'guess_special: INBOX.Sent => \Sent' ) ;
  2401. ok( '\Sent' eq guess_special( 'IN BOX.Sent', $possible_special_ref, 'IN BOX.' ) ,'guess_special: IN BOX.Sent => \Sent' ) ;
  2402. note( 'Leaving tests_guess_special()' ) ;
  2403. return ;
  2404. }
  2405. sub build_automap {
  2406. my $mysync = shift ;
  2407. $debug and myprint( "Entering build_automap\n" ) ;
  2408. foreach my $h1_fold ( @{ $mysync->{h1_folders_wanted} } ) {
  2409. my $h2_fold ;
  2410. my $h1_special = $mysync->{h1_special}{$h1_fold} ;
  2411. my $h1_special_guessed = $mysync->{h1_special_guessed}{$h1_fold} ;
  2412. # Case 1: special on both sides.
  2413. if ( $h1_special
  2414. and exists $mysync->{h2_special}{$h1_special} ) {
  2415. $h2_fold = $mysync->{h2_special}{$h1_special} ;
  2416. $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ;
  2417. next ;
  2418. }
  2419. # Case 2: special on host1, not on host2
  2420. if ( $h1_special
  2421. and ( not exists $mysync->{h2_special}{$h1_special} )
  2422. and ( exists $mysync->{h2_special_guessed}{$h1_special} )
  2423. ) {
  2424. # special_guessed on host2
  2425. $h2_fold = $mysync->{h2_special_guessed}{$h1_special} ;
  2426. $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ;
  2427. next ;
  2428. }
  2429. # Case 3: no special on host1, special on host2
  2430. if ( ( not $h1_special )
  2431. and ( $h1_special_guessed )
  2432. and ( exists $mysync->{h2_special}{$h1_special_guessed} )
  2433. ) {
  2434. $h2_fold = $mysync->{h2_special}{$h1_special_guessed} ;
  2435. $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ;
  2436. next ;
  2437. }
  2438. # Case 4: no special on both sides.
  2439. if ( ( not $h1_special )
  2440. and ( $h1_special_guessed )
  2441. and ( not exists $mysync->{h2_special}{$h1_special_guessed} )
  2442. and ( exists $mysync->{h2_special_guessed}{$h1_special_guessed} )
  2443. ) {
  2444. $h2_fold = $mysync->{h2_special_guessed}{$h1_special_guessed} ;
  2445. $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ;
  2446. next ;
  2447. }
  2448. }
  2449. return( $mysync->{f1f2auto} ) ;
  2450. }
  2451. # I willll probably add what there is at:
  2452. # http://stackoverflow.com/questions/2185391/localized-gmail-imap-folders/2185548#2185548
  2453. sub build_possible_special {
  2454. my $mysync = shift ;
  2455. my $possible_special = { } ;
  2456. # All|Archive|Drafts|Flagged|Junk|Sent|Trash
  2457. $possible_special->{'\All'} = [ 'All', 'All Messages', '&BBIEQQQ1-' ] ;
  2458. $possible_special->{'\Archive'} = [ 'Archive', 'Archives', '&BBAEQARFBDgEMg-' ] ;
  2459. $possible_special->{'\Drafts'} = [ 'Drafts', '&BCcENQRABD0EPgQyBDgEOgQ4-' ] ;
  2460. $possible_special->{'\Flagged'} = [ 'Flagged', 'Starred', '&BB8EPgQ8BDUERwQ1BD0EPQRLBDU-' ] ;
  2461. $possible_special->{'\Junk'} = [ 'Junk', 'Spam', '&BCEEPwQwBDw-' ] ;
  2462. $possible_special->{'\Sent'} = [ 'Sent', 'Sent Messages', 'Sent Items',
  2463. 'Gesendete Elemente', 'Gesendete Objekte',
  2464. '&AMk-l&AOk-ments envoy&AOk-s', 'Envoy&AOk-', 'Objets envoy&AOk-s',
  2465. 'Elementos enviados',
  2466. '&kAFP4W4IMH8wojCkMMYw4A-',
  2467. '&BB4EQgQ,BEAEMAQyBDsENQQ9BD0ESwQ1-'] ;
  2468. $possible_special->{'\Trash'} = [ 'Trash', '&BCMENAQwBDsENQQ9BD0ESwQ1-', '&BBoEPgRABDcEOAQ9BDA-' ] ;
  2469. foreach my $special ( qw( \All \Archive \Drafts \Flagged \Junk \Sent \Trash ) ){
  2470. foreach my $possible_folder ( @{ $possible_special->{$special} } ) {
  2471. $possible_special->{ $possible_folder } = $special ;
  2472. } ;
  2473. }
  2474. $mysync->{possible_special} = $possible_special ;
  2475. $debug and myprint( Data::Dumper->Dump( [ $possible_special ], [ 'possible_special' ] ) ) ;
  2476. return( $possible_special ) ;
  2477. }
  2478. sub special_from_folders_hash {
  2479. my ( $imap, $side ) = @_ ;
  2480. my %special = ( ) ;
  2481. if ( ! defined $imap ) { return ; }
  2482. $side = defined $side ? $side : 'Host?' ;
  2483. if ( ! $imap->can( 'folders_hash' ) ) {
  2484. my $error = "$side: To have automagic rfc6154 folder mapping, upgrade Mail::IMAPClient >= 3.34\n" ;
  2485. errors_incr( $sync, $error ) ;
  2486. return( \%special ) ; # empty hash ref
  2487. }
  2488. my $folders_hash = $imap->folders_hash( ) ;
  2489. foreach my $fhash (@{ $folders_hash } ) {
  2490. my @special = grep { /\\(?:All|Archive|Drafts|Flagged|Junk|Sent|Trash)/x } @{ $fhash->{attrs} } ;
  2491. if ( @special ) {
  2492. my $special = $special[0] ; # keep first one. Could be not very good.
  2493. if ( exists $special{ $special } ) {
  2494. myprintf( "%s: special %-20s = %s already assigned to %s\n",
  2495. $side, $fhash->{name}, join( q{ }, @special ), $special{ $special } ) ;
  2496. }else{
  2497. myprintf( "%s: special %-20s = %s\n",
  2498. $side, $fhash->{name}, join( q{ }, @special ) ) ;
  2499. $special{ $special } = $fhash->{name} ;
  2500. $special{ $fhash->{name} } = $special ; # double entry value => key
  2501. }
  2502. }
  2503. }
  2504. myprint( "\n" ) if ( %special ) ;
  2505. return( \%special ) ;
  2506. }
  2507. sub tests_special_from_folders_hash {
  2508. note( 'Entering tests_special_from_folders_hash()' ) ;
  2509. require Test::MockObject ;
  2510. my $imapT = Test::MockObject->new( ) ;
  2511. is( undef, special_from_folders_hash( ), 'special_from_folders_hash: no args' ) ;
  2512. is_deeply( {}, special_from_folders_hash( $imapT ), 'special_from_folders_hash: $imap void' ) ;
  2513. $imapT->mock( 'folders_hash', sub { return( [ { name => 'Sent', attrs => [ '\Sent' ] } ] ) } ) ;
  2514. is_deeply( { Sent => '\Sent', '\Sent' => 'Sent' }, special_from_folders_hash( $imapT ), 'special_from_folders_hash: $imap \Sent' ) ;
  2515. note( 'Leaving tests_special_from_folders_hash()' ) ;
  2516. return( ) ;
  2517. }
  2518. sub errors_incr {
  2519. my ( $mysync, @error ) = @ARG ;
  2520. $sync->{nb_errors}++ ;
  2521. if ( @error ) {
  2522. errors_log( $mysync, @error ) ;
  2523. myprint( @error ) ;
  2524. }
  2525. $mysync->{errorsmax} ||= $ERRORS_MAX ;
  2526. if ( $sync->{nb_errors} >= $mysync->{errorsmax} ) {
  2527. myprint( "Maximum number of errors $mysync->{errorsmax} reached ( you can change $mysync->{errorsmax} to 100 with --errorsmax 100 ). Exiting.\n" ) ;
  2528. if ( $mysync->{errorsdump} ) {
  2529. myprint( errorsdump( $sync->{nb_errors}, errors_log( $mysync ) ) ) ;
  2530. # again since errorsdump( ) can be very verbose and masq previous warning
  2531. myprint( "Maximum number of errors $mysync->{errorsmax} reached ( you can change $mysync->{errorsmax} to 100 with --errorsmax 100 ). Exiting.\n" ) ;
  2532. }
  2533. exit_clean( $mysync, $EXIT_WITH_ERRORS_MAX ) ;
  2534. }
  2535. return ;
  2536. }
  2537. sub errors_log {
  2538. my ( $mysync, @error ) = @ARG ;
  2539. if ( ! $mysync->{errors_log} ) {
  2540. $mysync->{errors_log} = [] ;
  2541. }
  2542. if ( @error ) {
  2543. push @{ $mysync->{errors_log} }, join( q{}, @error ) ;
  2544. }
  2545. if ( @{ $mysync->{errors_log} } ) {
  2546. return @{ $mysync->{errors_log} } ;
  2547. }
  2548. else {
  2549. return ;
  2550. }
  2551. }
  2552. sub tests_errors_log {
  2553. note( 'Entering tests_errors_log()' ) ;
  2554. note( 'Leaving tests_errors_log()' ) ;
  2555. return ;
  2556. }
  2557. sub errorsdump {
  2558. my( $nb_errors, @errors_log ) = @ARG ;
  2559. my $error_num = 0 ;
  2560. my $errors_list = q{} ;
  2561. if ( @errors_log ) {
  2562. $errors_list = "++++ Listing $nb_errors errors encountered during the sync ( avoid this listing with --noerrorsdump ).\n" ;
  2563. foreach my $error ( @errors_log ) {
  2564. $error_num++ ;
  2565. $errors_list .= "Err $error_num/$nb_errors: $error" ;
  2566. }
  2567. }
  2568. return( $errors_list ) ;
  2569. }
  2570. sub tests_live_result {
  2571. note( 'Entering tests_live_result()' ) ;
  2572. my $nb_errors = shift ;
  2573. if ( $nb_errors ) {
  2574. myprint( "Live tests failed with $nb_errors errors\n" ) ;
  2575. } else {
  2576. myprint( "Live tests ended successfully\n" ) ;
  2577. }
  2578. note( 'Leaving tests_live_result()' ) ;
  2579. return ;
  2580. }
  2581. sub foldersizesatend {
  2582. timenext( ) ;
  2583. return if ( $imap1->IsUnconnected( ) ) ;
  2584. return if ( $imap2->IsUnconnected( ) ) ;
  2585. # Get all folders on host2 again since new were created
  2586. @h2_folders_all = sort $imap2->folders();
  2587. for ( @h2_folders_all ) {
  2588. $h2_folders_all{ $_ } = 1 ;
  2589. $h2_folders_all_UPPER{ uc $_ } = 1 ;
  2590. } ;
  2591. ( $h1_nb_msg_end, $h1_bytes_end ) = foldersizes( 'Host1', $imap1, $search1, $sync->{abletosearch1}, @h1_folders_wanted ) ;
  2592. ( $h2_nb_msg_end, $h2_bytes_end ) = foldersizes( 'Host2', $imap2, $search2, $sync->{abletosearch2}, @h2_folders_from_1_wanted ) ;
  2593. if ( not all_defined( $h1_nb_msg_end, $h1_bytes_end, $h2_nb_msg_end, $h2_bytes_end ) ) {
  2594. my $error = "Failure getting foldersizes, final differences will not be calculated\n" ;
  2595. errors_incr( $sync, $error ) ;
  2596. }
  2597. return ;
  2598. }
  2599. sub size_filtered_flag {
  2600. my $h1_size = shift ;
  2601. if (defined $maxsize and $h1_size >= $maxsize) {
  2602. return( 1 ) ;
  2603. }
  2604. if (defined $minsize and $h1_size <= $minsize) {
  2605. return( 1 ) ;
  2606. }
  2607. return( 0 ) ;
  2608. }
  2609. sub sync_flags_fir {
  2610. my ( $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) = @_ ;
  2611. if ( not defined $h1_msg ) { return } ;
  2612. if ( not defined $h2_msg ) { return } ;
  2613. my $h1_size = $h1_fir_ref->{$h1_msg}->{'RFC822.SIZE'} ;
  2614. return if size_filtered_flag( $h1_size ) ;
  2615. # used cached flag values for efficiency
  2616. my $h1_flags = $h1_fir_ref->{ $h1_msg }->{ 'FLAGS' } || q{} ;
  2617. my $h2_flags = $h2_fir_ref->{ $h2_msg }->{ 'FLAGS' } || q{} ;
  2618. sync_flags( $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) ;
  2619. return ;
  2620. }
  2621. sub sync_flags_after_copy {
  2622. my( $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $permanentflags2 ) = @_ ;
  2623. my @h2_flags = $imap2->flags( $h2_msg ) ;
  2624. my $h2_flags = "@h2_flags" ;
  2625. ( $debug or $debugflags ) and myprint( "Host2 flags before resync by STORE on msg $h2_msg: $h2_flags\n" ) ;
  2626. sync_flags( $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) ;
  2627. return ;
  2628. }
  2629. sub sync_flags {
  2630. my( $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) = @_ ;
  2631. ( $debug or $debugflags ) and
  2632. myprint( "Host1: flags init msg $h1_fold/$h1_msg flags( $h1_flags ) Host2 $h2_fold/$h2_msg flags( $h2_flags )\n" ) ;
  2633. $h1_flags = flags_for_host2( $h1_flags, $permanentflags2 ) ;
  2634. $h2_flags = flagscase( $h2_flags ) ;
  2635. ( $debug or $debugflags ) and
  2636. myprint( "Host1 flags filt msg $h1_fold/$h1_msg flags( $h1_flags ) Host2 $h2_fold/$h2_msg flags( $h2_flags )\n" ) ;
  2637. # compare flags - set flags if there a difference
  2638. my @h1_flags = sort split(q{ }, $h1_flags );
  2639. my @h2_flags = sort split(q{ }, $h2_flags );
  2640. my $diff = compare_lists( \@h1_flags, \@h2_flags );
  2641. $diff and ( $debug or $debugflags )
  2642. and myprint( "Host2 flags msg $h2_fold/$h2_msg replacing h2 flags( $h2_flags ) with h1 flags( $h1_flags )\n" ) ;
  2643. # This sets flags so flags can be removed with this
  2644. # When you remove a \Seen flag on host1 you want to it
  2645. # to be removed on host2. Just add flags is not what
  2646. # we need most of the time.
  2647. if ( not $sync->{dry} and $diff and not $imap2->store( $h2_msg, "FLAGS.SILENT (@h1_flags)" ) ) {
  2648. my $error_msg = join q{}, "Host2 flags msg $h2_fold/$h2_msg could not add flags [@h1_flags]: ",
  2649. $imap2->LastError || q{}, "\n" ;
  2650. errors_incr( $sync, $error_msg ) ;
  2651. }
  2652. return ;
  2653. }
  2654. sub _filter {
  2655. my $str = shift or return q{} ;
  2656. my $sz = $SIZE_MAX_STR ;
  2657. my $len = length $str ;
  2658. if ( not $debug and $len > $sz*2 ) {
  2659. my $beg = substr $str, 0, $sz ;
  2660. my $end = substr $str, -$sz, $sz ;
  2661. $str = $beg . '...' . $end ;
  2662. }
  2663. $str =~ s/\012?\015$//x ;
  2664. return "(len=$len) " . $str ;
  2665. }
  2666. sub lost_connection {
  2667. my( $imap, $error_message ) = @_;
  2668. if ( $imap->IsUnconnected( ) ) {
  2669. $sync->{nb_errors}++ ;
  2670. my $lcomm = $imap->LastIMAPCommand || q{} ;
  2671. my $einfo = $imap->LastError || @{$imap->History}[$LAST] || q{} ;
  2672. # if string is long try reduce to a more reasonable size
  2673. $lcomm = _filter( $lcomm ) ;
  2674. $einfo = _filter( $einfo ) ;
  2675. myprint( "Failure: last command: $lcomm\n") if ($debug && $lcomm) ;
  2676. myprint( "Failure: lost connection $error_message: ", $einfo, "\n") ;
  2677. return( 1 ) ;
  2678. }
  2679. else{
  2680. return( 0 ) ;
  2681. }
  2682. }
  2683. sub max {
  2684. my @list = @_ ;
  2685. return( undef ) if ( 0 == scalar @list ) ;
  2686. no warnings 'numeric' ;
  2687. no warnings 'uninitialized' ;
  2688. my @sorted = sort { $a <=> $b || $a cmp $b } @list ;
  2689. return( pop @sorted ) ;
  2690. }
  2691. sub tests_max {
  2692. note( 'Entering tests_max()' ) ;
  2693. is( 0, max( 0 ), 'max 0 => 0' ) ;
  2694. is( 1, max( 1 ), 'max 1 => 1' ) ;
  2695. is( $MINUS_ONE, max( $MINUS_ONE ), 'max -1 => -1') ;
  2696. is( undef, max( ), 'max no arg => undef' ) ;
  2697. is( $NUMBER_100, max( 1, $NUMBER_100 ), 'max 1 100 => 100' ) ;
  2698. is( $NUMBER_100, max( $NUMBER_100, 1 ), 'max 100 1 => 100' ) ;
  2699. is( $NUMBER_100, max( $NUMBER_100, $NUMBER_42, 1 ), 'max 100 42 1 => 100' ) ;
  2700. is( $NUMBER_100, max( $NUMBER_100, '42', 1 ), 'max 100 42 1 => 100' ) ;
  2701. is( $NUMBER_100, max( '100', '42', 1 ), 'max 100 42 1 => 100' ) ;
  2702. is( $NUMBER_100, max( $NUMBER_100, 'haha', 1 ), 'max 100 haha 1 => 100') ;
  2703. is( 1, max( $MINUS_ONE, 1 ), 'max -1 1 => 1') ;
  2704. is( 1, max( undef, 1 ), 'max undef 1 => 1' ) ;
  2705. is( 0, max( undef, 0 ), 'max undef 0 => 0' ) ;
  2706. is( 'haha', max( 'haha' ), 'max haha => haha') ;
  2707. is( 'bb', max( 'aa', 'bb' ), 'max aa bb => bb') ;
  2708. is( 'bb', max( 'bb', 'aa' ), 'max bb aa bb => bb') ;
  2709. is( 'bb', max( 'bb', 'aa', 'bb' ), 'max bb aa bb => bb') ;
  2710. note( 'Leaving tests_max()' ) ;
  2711. return ;
  2712. }
  2713. sub min {
  2714. my @list = @_ ;
  2715. return( undef ) if ( 0 == scalar @list ) ;
  2716. no warnings 'numeric' ;
  2717. no warnings 'uninitialized' ;
  2718. my @sorted = sort { $a <=> $b || $a cmp $b } @list ;
  2719. return( shift @sorted ) ;
  2720. }
  2721. sub tests_min {
  2722. note( 'Entering tests_min()' ) ;
  2723. is( 0, min( 0 ), 'min 0 => 0' ) ;
  2724. is( 1, min( 1 ), 'min 1 => 1' ) ;
  2725. is( $MINUS_ONE, min( $MINUS_ONE ), 'min -1 => -1' ) ;
  2726. is( undef, min( ), 'min no arg => undef' ) ;
  2727. is( 1, min( 1, $NUMBER_100 ), 'min 1 100 => 1' ) ;
  2728. is( 1, min( $NUMBER_100, 1 ), 'min 100 1 => 1' ) ;
  2729. is( 1, min( $NUMBER_100, $NUMBER_42, 1 ), 'min 100 42 1 => 1' ) ;
  2730. is( 1, min( $NUMBER_100, '42', 1 ), 'min 100 42 1 => 1' ) ;
  2731. is( 1, min( '100', '42', 1 ), 'min 100 42 1 => 1' ) ;
  2732. is( 'haha', min( 100, 'haha', 1 ), 'min 100 haha 1 => haha') ;
  2733. is( $MINUS_ONE, min( $MINUS_ONE, 1 ), 'min -1 1 => -1') ;
  2734. is( undef, min( undef, 1 ), 'min undef 1 => undef' ) ;
  2735. is( undef, min( undef, 0 ), 'min undef 0 => undef' ) ;
  2736. is( 'haha', min( 'haha' ), 'min haha => haha') ;
  2737. is( 'aa', min( 'aa', 'bb' ), 'min aa bb => aa') ;
  2738. is( 'aa', min( 'bb', 'aa' ), 'min bb aa bb => aa') ;
  2739. is( 'aa', min( 'bb', 'aa', 'bb' ), 'min bb aa bb => aa') ;
  2740. note( 'Leaving tests_min()' ) ;
  2741. return ;
  2742. }
  2743. sub check_lib_version {
  2744. $debug and myprint( "IMAPClient $Mail::IMAPClient::VERSION\n" ) ;
  2745. if ( '2.2.9' eq $Mail::IMAPClient::VERSION ) {
  2746. myprint( "imapsync no longer supports Mail::IMAPClient 2.2.9, upgrade it\n" ) ;
  2747. return 0 ;
  2748. }
  2749. else{
  2750. # 3.x.x is no longer buggy with imapsync.
  2751. # 3.30 or currently superior is imposed in the Perl "use Mail::IMAPClient line".
  2752. return 1 ;
  2753. }
  2754. return ;
  2755. }
  2756. sub module_version_str {
  2757. my( $module_name, $module_version ) = @_ ;
  2758. my $str = mysprintf( "%-20s %s\n", $module_name, $module_version ) ;
  2759. return( $str ) ;
  2760. }
  2761. sub modulesversion {
  2762. my @list_version;
  2763. my %modulesversion = (
  2764. 'Authen::NTLM' => sub { $Authen::NTLM::VERSION },
  2765. 'Compress::Zlib' => sub { $Compress::Zlib::VERSION },
  2766. 'Crypt::OpenSSL::RSA' => sub { $Crypt::OpenSSL::RSA::VERSION },
  2767. 'Data::Uniqid' => sub { $Data::Uniqid::VERSION },
  2768. 'Digest::HMAC_MD5' => sub { $Digest::HMAC_MD5::VERSION },
  2769. 'Digest::HMAC_SHA1' => sub { $Digest::HMAC_SHA1::VERSION },
  2770. 'Digest::MD5' => sub { $Digest::MD5::VERSION },
  2771. 'File::Copy::Recursive' => sub { $File::Copy::Recursive::VERSION },
  2772. 'File::Spec' => sub { $File::Spec::VERSION },
  2773. 'Getopt::Long' => sub { $Getopt::Long::VERSION },
  2774. 'HTML::Entities' => sub { $HTML::Entities::VERSION },
  2775. 'IO::Socket::INET6' => sub { $IO::Socket::INET6::VERSION },
  2776. 'IO::Socket::INET' => sub { $IO::Socket::INET::VERSION },
  2777. 'IO::Socket::SSL' => sub { $IO::Socket::SSL::VERSION },
  2778. 'IO::Socket' => sub { $IO::Socket::VERSION },
  2779. 'IO::Tee' => sub { $IO::Tee::VERSION },
  2780. 'JSON' => sub { $JSON::VERSION },
  2781. 'JSON::WebToken' => sub { $JSON::WebToken::VERSION },
  2782. 'LWP' => sub { $LWP::VERSION },
  2783. 'Mail::IMAPClient' => sub { $Mail::IMAPClient::VERSION },
  2784. 'Net::Ping' => sub { $Net::Ping::VERSION },
  2785. 'Net::SSLeay' => sub { $Net::SSLeay::VERSION },
  2786. 'Term::ReadKey' => sub { $Term::ReadKey::VERSION },
  2787. 'Test::MockObject' => sub { $Test::MockObject::VERSION },
  2788. 'Time::HiRes' => sub { $Time::HiRes::VERSION },
  2789. 'Unicode::String' => sub { $Unicode::String::VERSION },
  2790. 'URI::Escape' => sub { $URI::Escape::VERSION },
  2791. #'Lalala' => sub { $Lalala::VERSION },
  2792. ) ;
  2793. foreach my $module_name ( sort keys %modulesversion ) {
  2794. # trick from http://www.perlmonks.org/?node_id=152122
  2795. my $file_name = $module_name . '.pm' ;
  2796. $file_name =~s,::,/,xmgs; # Foo::Bar::Baz => Foo/Bar/Baz.pm
  2797. my $v ;
  2798. eval {
  2799. require $file_name ;
  2800. $v = defined $modulesversion{ $module_name } ? $modulesversion{ $module_name }->() : q{?} ;
  2801. } or $v = q{Not installed} ;
  2802. push @list_version, module_version_str( $module_name, $v ) ;
  2803. }
  2804. return( @list_version ) ;
  2805. }
  2806. # Construct a command line copy with passwords replaced by MASKED.
  2807. sub command_line_nopassword {
  2808. my @argv = @_ ;
  2809. my @argv_nopassword ;
  2810. return( "@argv" ) if $sync->{showpasswords} ;
  2811. while ( @argv ) {
  2812. my $arg = shift @argv ; # option name or value
  2813. if ( $arg =~ m/-password[12]/x ) {
  2814. shift @argv ; # password value
  2815. push @argv_nopassword, $arg, 'MASKED' ; # option name and fake value
  2816. }else{
  2817. push @argv_nopassword, $arg ; # same option or value
  2818. }
  2819. }
  2820. return("@argv_nopassword") ;
  2821. }
  2822. sub tests_command_line_nopassword {
  2823. note( 'Entering tests_command_line_nopassword()' ) ;
  2824. ok(q{} eq command_line_nopassword(), 'command_line_nopassword void');
  2825. ok('--blabla' eq command_line_nopassword('--blabla'), 'command_line_nopassword --blabla');
  2826. #myprint( command_line_nopassword((qw{ --password1 secret1 })), "\n" ) ;
  2827. ok('--password1 MASKED' eq command_line_nopassword(qw{ --password1 secret1}), 'command_line_nopassword --password1');
  2828. ok('--blabla --password1 MASKED --blibli'
  2829. eq command_line_nopassword(qw{ --blabla --password1 secret1 --blibli }), 'command_line_nopassword --password1 --blibli');
  2830. $sync->{showpasswords} = 1 ;
  2831. ok(q{} eq command_line_nopassword(), 'command_line_nopassword void');
  2832. ok('--blabla' eq command_line_nopassword('--blabla'), 'command_line_nopassword --blabla');
  2833. #myprint( command_line_nopassword((qw{ --password1 secret1 })), "\n" ) ;
  2834. ok('--password1 secret1' eq command_line_nopassword(qw{ --password1 secret1}), 'command_line_nopassword --password1');
  2835. ok('--blabla --password1 secret1 --blibli'
  2836. eq command_line_nopassword(qw{ --blabla --password1 secret1 --blibli }), 'command_line_nopassword --password1 --blibli');
  2837. note( 'Leaving tests_command_line_nopassword()' ) ;
  2838. return ;
  2839. }
  2840. sub ask_for_password {
  2841. my ( $user, $host ) = @ARG ;
  2842. myprint( "What's the password for $user" . '@' . "$host? (not visible while you type, then enter RETURN) " ) ;
  2843. Term::ReadKey::ReadMode( 2 ) ;
  2844. my $password = <STDIN> ;
  2845. chomp $password ;
  2846. myprint( "\nGot it\n" ) ;
  2847. Term::ReadKey::ReadMode( 0 ) ;
  2848. return $password ;
  2849. }
  2850. # Have to refactor get_password1() get_password2()
  2851. # to have only get_password() and two calls
  2852. sub get_password1 {
  2853. my $mysync = shift ;
  2854. $mysync->{password1}
  2855. || $passfile1
  2856. || 'PREAUTH' eq $authmech1
  2857. || 'EXTERNAL' eq $authmech1
  2858. || $ENV{IMAPSYNC_PASSWORD1}
  2859. || do {
  2860. myprint( << 'FIN_PASSFILE' ) ;
  2861. If you are afraid of giving password on the command line arguments, you can put the
  2862. password of user1 in a file named file1 and use "--passfile1 file1" instead of typing it.
  2863. Then give this file restrictive permissions with the command "chmod 600 file1".
  2864. An other solution is to set the environment variable IMAPSYNC_PASSWORD1
  2865. FIN_PASSFILE
  2866. $mysync->{password1} = ask_for_password( $authuser1 || $mysync->{user1}, $mysync->{host1} ) ;
  2867. } ;
  2868. if ( defined $passfile1 ) {
  2869. if ( ! -e -r $passfile1 ) {
  2870. myprint( "Failure: file from parameter --passfile1 $passfile1 does not exist or is not readable\n" ) ;
  2871. exit_clean( $mysync, $EX_NOINPUT ) ;
  2872. }
  2873. # passfile1 readable
  2874. $mysync->{password1} = firstline ( $passfile1 ) ;
  2875. return ;
  2876. }
  2877. if ( $ENV{IMAPSYNC_PASSWORD1} ) {
  2878. $mysync->{password1} = $ENV{IMAPSYNC_PASSWORD1} ;
  2879. return ;
  2880. }
  2881. return ;
  2882. }
  2883. sub get_password2 {
  2884. my $mysync = shift ;
  2885. $mysync->{password2}
  2886. || $passfile2
  2887. || 'PREAUTH' eq $authmech2
  2888. || 'EXTERNAL' eq $authmech2
  2889. || $ENV{IMAPSYNC_PASSWORD2}
  2890. || do {
  2891. myprint( << 'FIN_PASSFILE' ) ;
  2892. If you are afraid of giving password on the command line arguments, you can put the
  2893. password of user2 in a file named file2 and use "--passfile2 file2" instead of typing it.
  2894. Then give this file restrictive permissions with the command "chmod 600 file2".
  2895. An other solution is to set the environment variable IMAPSYNC_PASSWORD2
  2896. FIN_PASSFILE
  2897. $mysync->{password2} = ask_for_password( $authuser2 || $mysync->{user2}, $mysync->{host2} ) ;
  2898. } ;
  2899. if ( defined $passfile2 ) {
  2900. if ( ! -e -r $passfile2 ) {
  2901. myprint( "Failure: file from parameter --passfile2 $passfile2 does not exist or is not readable\n" ) ;
  2902. exit_clean( $mysync, $EX_NOINPUT ) ;
  2903. }
  2904. # passfile2 readable
  2905. $mysync->{password2} = firstline ( $passfile2 ) ;
  2906. return ;
  2907. }
  2908. if ( $ENV{IMAPSYNC_PASSWORD2} ) {
  2909. $mysync->{password2} = $ENV{IMAPSYNC_PASSWORD2} ;
  2910. return ;
  2911. }
  2912. return ;
  2913. }
  2914. sub catch_exit {
  2915. my $mysync = shift ;
  2916. my $signame = shift ;
  2917. if ( $signame ) {
  2918. myprint( "\nGot a signal $signame\n" ) ;
  2919. }
  2920. stats( $mysync ) ;
  2921. myprint( "Ended by a signal\n" ) ;
  2922. exit_clean( $mysync, $EXIT_BY_SIGNAL ) ;
  2923. return ;
  2924. }
  2925. sub catch_reconnect {
  2926. my $mysync = shift ;
  2927. my $signame = shift ;
  2928. myprint( "\nGot a signal $signame\n",
  2929. "Hit 2 ctr-c within 2 seconds to exit the program\n",
  2930. "Hit only 1 ctr-c to reconnect to both imap servers\n",
  2931. ) ;
  2932. if ( here_twice( $mysync ) ) {
  2933. myprint( "Got two signals $signame within $INTERVAL_TO_EXIT seconds. Exiting...\n" ) ;
  2934. catch_exit( $mysync ) ;
  2935. }
  2936. else{
  2937. myprint( "For now only one signal $signame within $INTERVAL_TO_EXIT seconds.\n" ) ;
  2938. }
  2939. if ( ! defined $mysync->{imap1} ) { return ; }
  2940. if ( ! defined $mysync->{imap2} ) { return ; }
  2941. myprint( "Info: reconnecting to host1 imap server\n" ) ;
  2942. $mysync->{imap1}->State( Mail::IMAPClient::Unconnected ) ;
  2943. $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT} += 1 ;
  2944. $mysync->{imap1}->reconnect( ) ;
  2945. myprint( "Info: reconnecting to host2 imap server\n" ) ;
  2946. $mysync->{imap2}->State( Mail::IMAPClient::Unconnected ) ;
  2947. $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT} += 1 ;
  2948. $mysync->{imap2}->reconnect( ) ;
  2949. myprint( "Info: reconnected to both imap servers\n" ) ;
  2950. return ;
  2951. }
  2952. sub tests_reconnect_12_if_needed {
  2953. note( 'Entering tests_reconnect_12_if_needed()' ) ;
  2954. my $mysync ;
  2955. $mysync->{imap1} = Mail::IMAPClient->new( ) ;
  2956. $mysync->{imap2} = Mail::IMAPClient->new( ) ;
  2957. $mysync->{imap1}->Server( 'test1.lamiral.info' ) ;
  2958. $mysync->{imap2}->Server( 'test2.lamiral.info' ) ;
  2959. is( 2, reconnect_12_if_needed( $mysync ), 'reconnect_12_if_needed: test1&test2 .lamiral.info => 1' ) ;
  2960. is( 1, $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_12_if_needed: test1.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ;
  2961. is( 1, $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_12_if_needed: test2.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ;
  2962. note( 'Leaving tests_reconnect_12_if_needed()' ) ;
  2963. return ;
  2964. }
  2965. sub reconnect_12_if_needed {
  2966. my $mysync = shift ;
  2967. #return 2 ;
  2968. if ( ! reconnect_if_needed( $mysync->{imap1} ) ) {
  2969. return ;
  2970. }
  2971. if ( ! reconnect_if_needed( $mysync->{imap2} ) ) {
  2972. return ;
  2973. }
  2974. # both were good
  2975. return 2 ;
  2976. }
  2977. sub tests_reconnect_if_needed {
  2978. note( 'Entering tests_reconnect_if_needed()' ) ;
  2979. my $myimap ;
  2980. is( undef, reconnect_if_needed( ), 'reconnect_if_needed: no args => undef' ) ;
  2981. is( undef, reconnect_if_needed( $myimap ), 'reconnect_if_needed: undef arg => undef' ) ;
  2982. $myimap = Mail::IMAPClient->new( ) ;
  2983. $myimap->Debug( 1 ) ;
  2984. is( undef, reconnect_if_needed( $myimap ), 'reconnect_if_needed: empty new Mail::IMAPClient => undef' ) ;
  2985. $myimap->Server( 'test.lamiral.info' ) ;
  2986. is( 1, reconnect_if_needed( $myimap ), 'reconnect_if_needed: test.lamiral.info => 1' ) ;
  2987. is( 1, $myimap->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_if_needed: test.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ;
  2988. note( 'Leaving tests_reconnect_if_needed()' ) ;
  2989. return ;
  2990. }
  2991. sub reconnect_if_needed {
  2992. # return undef upon failure.
  2993. # return 1 upon connection success, with or without reconnection.
  2994. my $imap = shift ;
  2995. if ( ! defined $imap ) { return ; }
  2996. if ( ! $imap->Server( ) ) { return ; }
  2997. if ( $imap->IsUnconnected( ) ) {
  2998. $imap->{IMAPSYNC_RECONNECT_COUNT} += 1 ;
  2999. if ( $imap->reconnect( ) ) {
  3000. return 1 ;
  3001. }
  3002. }else{
  3003. return 1 ;
  3004. }
  3005. # A last forced one
  3006. $imap->State( Mail::IMAPClient::Unconnected ) ;
  3007. $imap->reconnect( ) ;
  3008. $imap->{IMAPSYNC_RECONNECT_COUNT} += 1 ;
  3009. if ( $imap->noop ) {
  3010. # NOOP is ok
  3011. return 1 ;
  3012. }
  3013. return ;
  3014. }
  3015. sub here_twice {
  3016. my $mysync = shift ;
  3017. my $now = time ;
  3018. my $previous = $mysync->{lastcatch} || 0 ;
  3019. $mysync->{lastcatch} = $now ;
  3020. if ( $INTERVAL_TO_EXIT >= $now - $previous ) {
  3021. return $TRUE ;
  3022. }else{
  3023. return $FALSE ;
  3024. }
  3025. }
  3026. sub justconnect {
  3027. $imap1 = connect_imap( $sync->{host1}, $sync->{port1}, $debugimap1, $sync->{ssl1}, $sync->{tls1}, 'Host1', $sync->{h1}->{timeout}, $sync->{h1} ) ;
  3028. $imap2 = connect_imap( $sync->{host2}, $sync->{port2}, $debugimap2, $sync->{ssl2}, $sync->{tls2}, 'Host2', $sync->{h2}->{timeout}, $sync->{h2} ) ;
  3029. $imap1->logout( ) ;
  3030. $imap2->logout( ) ;
  3031. return ;
  3032. }
  3033. sub tests_mailimapclient_connect {
  3034. note( 'Entering tests_mailimapclient_connect()' ) ;
  3035. my $imap ;
  3036. # ipv4
  3037. ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect ipv4: new' ) ;
  3038. is( 'Mail::IMAPClient', ref( $imap ), 'mailimapclient_connect ipv4: ref is Mail::IMAPClient' ) ;
  3039. SKIP: {
  3040. if ( 'macosx' eq hostname() ) { skip( 'Tests avoided on macosx get stuck', 1 ) ; }
  3041. is( undef, $imap->connect( ), 'mailimapclient_connect ipv4: connect with no server => failure' ) ;
  3042. }
  3043. is( 'test.lamiral.info', $imap->Server( 'test.lamiral.info' ), 'mailimapclient_connect ipv4: setting Server(test.lamiral.info)' ) ;
  3044. is( 1, $imap->Debug( 1 ), 'mailimapclient_connect ipv4: setting Debug( 1 )' ) ;
  3045. is( 143, $imap->Port( 143 ), 'mailimapclient_connect ipv4: setting Port( 143 )' ) ;
  3046. is( 3, $imap->Timeout( 3 ), 'mailimapclient_connect ipv4: setting Timout( 30 )' ) ;
  3047. like( ref( $imap->connect( ) ), qr/IO::Socket::INET/, 'mailimapclient_connect ipv4: connect to test.lamiral.info' ) ;
  3048. like( $imap->logout( ), qr/Mail::IMAPClient/, 'mailimapclient_connect ipv4: logout' ) ;
  3049. is( undef, undef $imap, 'mailimapclient_connect ipv4: free variable' ) ;
  3050. # ipv4 + ssl
  3051. ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect ipv4 + ssl: new' ) ;
  3052. is( 'test.lamiral.info', $imap->Server( 'test.lamiral.info' ), 'mailimapclient_connect ipv4 + ssl: setting Server(test.lamiral.info)' ) ;
  3053. is( 1, $imap->Debug( 1 ), 'mailimapclient_connect ipv4 + ssl: setting Debug( 1 )' ) ;
  3054. ok( $imap->Ssl( [ SSL_verify_mode => SSL_VERIFY_NONE ] ), 'mailimapclient_connect ipv4 + ssl: setting Ssl( SSL_VERIFY_NONE )' ) ;
  3055. is( 993, $imap->Port( 993 ), 'mailimapclient_connect ipv4 + ssl: setting Port( 993 )' ) ;
  3056. like( ref( $imap->connect( ) ), qr/IO::Socket::SSL/, 'mailimapclient_connect ipv4 + ssl: connect to test.lamiral.info' ) ;
  3057. is( $imap->logout( ), undef, 'mailimapclient_connect ipv4 + ssl: logout in ssl causes failure' ) ;
  3058. is( undef, undef $imap, 'mailimapclient_connect ipv4 + ssl: free variable' ) ;
  3059. # ipv6 + ssl
  3060. ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect ipv6 + ssl: new' ) ;
  3061. is( 'ks2ipv6.lamiral.info', $imap->Server( 'ks2ipv6.lamiral.info' ), 'mailimapclient_connect ipv6 + ssl: setting Server(ks2ipv6.lamiral.info)' ) ;
  3062. ok( $imap->Ssl( [ SSL_verify_mode => SSL_VERIFY_NONE ] ), 'mailimapclient_connect ipv6 + ssl: setting Ssl( SSL_VERIFY_NONE )' ) ;
  3063. is( 993, $imap->Port( 993 ), 'mailimapclient_connect ipv6 + ssl: setting Port( 993 )' ) ;
  3064. SKIP: {
  3065. if ( 'CUILLERE' eq hostname() ) { skip( 'Tests avoided on CUILLERE can not do ipv6', 2 ) ; }
  3066. like( ref( $imap->connect( ) ), qr/IO::Socket::SSL/, 'mailimapclient_connect ipv6 + ssl: connect to ks2ipv6.lamiral.info' ) ;
  3067. is( $imap->logout( ), undef, 'mailimapclient_connect ipv6 + ssl: logout in ssl causes failure' ) ;
  3068. }
  3069. is( undef, undef $imap, 'mailimapclient_connect ipv6 + ssl: free variable' ) ;
  3070. note( 'Leaving tests_mailimapclient_connect()' ) ;
  3071. return ;
  3072. }
  3073. sub tests_mailimapclient_connect_bug {
  3074. note( 'Entering tests_mailimapclient_connect_bug()' ) ;
  3075. my $imap ;
  3076. # ipv6
  3077. ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect ipv6: new' ) ;
  3078. is( 'ks2ipv6.lamiral.info', $imap->Server( 'ks2ipv6.lamiral.info' ), 'mailimapclient_connect ipv6: setting Server(ks2ipv6.lamiral.info)' ) ;
  3079. is( 143, $imap->Port( 143 ), 'mailimapclient_connect ipv6: setting Port( 993 )' ) ;
  3080. SKIP: {
  3081. if ( 'CUILLERE' eq hostname() ) { skip( 'Tests avoided on CUILLERE can not do ipv6', 1 ) ; }
  3082. like( ref( $imap->connect( ) ), qr/IO::Socket::INET/, 'mailimapclient_connect ipv6: connect to ks2ipv6.lamiral.info' )
  3083. or diag( 'mailimapclient_connect ipv6: ', $imap->LastError( ), $!, ) ;
  3084. }
  3085. #is( $imap->logout( ), undef, 'mailimapclient_connect ipv6: logout in ssl causes failure' ) ;
  3086. is( undef, undef $imap, 'mailimapclient_connect ipv6: free variable' ) ;
  3087. note( 'Leaving tests_mailimapclient_connect_bug()' ) ;
  3088. return ;
  3089. }
  3090. sub mailimapclient_connect {
  3091. return ;
  3092. }
  3093. sub tests_connect_socket {
  3094. note( 'Entering tests_connect_socket()' ) ;
  3095. is( undef, connect_socket( ), 'connect_socket: no args' ) ;
  3096. my $socket ;
  3097. my $imap ;
  3098. SKIP: {
  3099. if ( 'CUILLERE' eq hostname() ) { skip( 'Tests avoided on CUILLERE cannot do ipv6', 2 ) ; }
  3100. $socket = IO::Socket::INET6->new(
  3101. PeerAddr => 'ks2ipv6.lamiral.info',
  3102. PeerPort => 143,
  3103. ) ;
  3104. ok( $imap = connect_socket( $socket ), 'connect_socket: ks2ipv6.lamiral.info port 143 IO::Socket::INET6' ) ;
  3105. #$imap->Debug( 1 ) ;
  3106. #print $imap->capability( ) ;
  3107. if ( $imap ) {
  3108. $imap->logout( ) ;
  3109. }
  3110. #$IO::Socket::SSL::DEBUG = 4 ;
  3111. $socket = IO::Socket::SSL->new(
  3112. PeerHost => 'ks2ipv6.lamiral.info',
  3113. PeerPort => 993,
  3114. SSL_verify_mode => SSL_VERIFY_NONE,
  3115. ) ;
  3116. #print $socket ;
  3117. ok( $imap = connect_socket( $socket ), 'connect_socket: ks2ipv6.lamiral.info port 993 IO::Socket::SSL' ) ;
  3118. #$imap->Debug( 1 ) ;
  3119. #print $imap->capability( ) ;
  3120. $socket->close( ) ;
  3121. if ( $imap ) {
  3122. $socket->close( ) ;
  3123. }
  3124. #$socket->close(SSL_no_shutdown => 1) ;
  3125. #$imap->logout( ) ;
  3126. #print "\n" ;
  3127. #$imap->logout( ) ;
  3128. }
  3129. note( 'Leaving tests_connect_socket()' ) ;
  3130. return ;
  3131. }
  3132. sub connect_socket {
  3133. my( $socket ) = @ARG ;
  3134. if ( ! defined $socket ) { return ; }
  3135. my $host = $socket->peerhost( ) ;
  3136. my $port = $socket->peerport( ) ;
  3137. #print "socket->peerhost: ", $socket->peerhost( ), "\n" ;
  3138. #print "socket->peerport: ", $socket->peerport( ), "\n" ;
  3139. my $imap = Mail::IMAPClient->new( ) ;
  3140. $imap->Socket( $socket ) ;
  3141. my $banner = $imap->Results()->[0] ;
  3142. #myprint( "banner: $banner" ) ;
  3143. return $imap ;
  3144. }
  3145. sub tests_probe_imapssl {
  3146. note( 'Entering tests_probe_imapssl()' ) ;
  3147. is( undef, probe_imapssl( ), 'probe_imapssl: no args => undef' ) ;
  3148. is( undef, probe_imapssl( 'unknown' ), 'probe_imapssl: unknown => undef' ) ;
  3149. SKIP: {
  3150. if ( 'CUILLERE' eq hostname() ) { skip( 'Tests avoided on CUILLERE cannot do ipv6', 1 ) ; }
  3151. like( probe_imapssl( 'ks2ipv6.lamiral.info' ), qr/^\* OK/, 'probe_imapssl: ks2ipv6.lamiral.info matches "* OK"' ) ;
  3152. } ;
  3153. like( probe_imapssl( 'test1.lamiral.info' ), qr/^\* OK/, 'probe_imapssl: test1.lamiral.info matches "* OK"' ) ;
  3154. like( probe_imapssl( 'imap.gmail.com' ), qr/^\* OK/, 'probe_imapssl: imap.gmail.com matches "* OK"' ) ;
  3155. note( 'Leaving tests_probe_imapssl()' ) ;
  3156. return ;
  3157. }
  3158. sub probe_imapssl {
  3159. my $host = shift ;
  3160. if ( ! $host ) { return ; }
  3161. my $socket = IO::Socket::SSL->new(
  3162. PeerHost => $host,
  3163. PeerPort => $IMAP_SSL_PORT,
  3164. SSL_verify_mode => SSL_VERIFY_NONE,
  3165. ) ;
  3166. #print "$socket\n" ;
  3167. if ( ! $socket ) { return ; }
  3168. my $banner ;
  3169. $socket->sysread( $banner, 65_536 ) ;
  3170. #print "$banner" ;
  3171. $socket->close( ) ;
  3172. return $banner ;
  3173. }
  3174. sub connect_imap {
  3175. my( $host, $port, $mydebugimap, $ssl, $tls, $Side, $mytimeout, $h ) = @_ ;
  3176. my $imap = Mail::IMAPClient->new( ) ;
  3177. if ( $ssl ) { set_ssl( $imap, $h ) }
  3178. $imap->Server( $host ) ;
  3179. $imap->Port( $port ) ;
  3180. $imap->Debug( $mydebugimap ) ;
  3181. $imap->Timeout( $mytimeout ) ;
  3182. my $side = lc $Side ;
  3183. myprint( "$Side: connecting on $side [$host] port [$port]\n" ) ;
  3184. $imap->connect( )
  3185. or die_clean( "$Side: Can not open imap connection on [$host]: " . $imap->LastError . " $OS_ERROR\n" ) ;
  3186. myprint( "$Side IP address: ", $imap->Socket->peerhost(), "\n" ) ;
  3187. my $banner = $imap->Results()->[0] ;
  3188. myprint( "$Side banner: $banner" ) ;
  3189. myprint( "$Side capability: ", join(q{ }, @{ $imap->capability() || [] }), "\n" ) ;
  3190. if ( $tls ) {
  3191. set_tls( $imap, $h ) ;
  3192. $imap->starttls( )
  3193. or die_clean("$Side: Can not go to tls encryption on $side [$host]:", $imap->LastError, "\n" ) ;
  3194. myprint( "$Side: Socket successfuly converted to SSL\n" ) ;
  3195. }
  3196. return( $imap ) ;
  3197. }
  3198. sub login_imap {
  3199. my @allargs = @_ ;
  3200. my(
  3201. $host, $port, $user, $domain, $password,
  3202. $mydebugimap, $mytimeout, $fastio,
  3203. $ssl, $tls, $authmech, $authuser, $reconnectretry,
  3204. $proxyauth, $uid, $split, $Side, $h, $mysync ) = @allargs ;
  3205. my $side = lc $Side ;
  3206. myprint( "$Side: connecting and login on $side [$host] port [$port] with user [$user]\n" ) ;
  3207. my $imap = init_imap( @allargs ) ;
  3208. $imap->connect()
  3209. or die_clean("$Side failure: can not open imap connection on $side [$host] with user [$user]: " . $imap->LastError . " $OS_ERROR\n" ) ;
  3210. myprint( "$Side IP address: ", $imap->Socket->peerhost(), "\n" ) ;
  3211. my $banner = $imap->Results()->[0] ;
  3212. myprint( "$Side banner: $banner" ) ;
  3213. myprint( "$Side capability before authentication: ", join(q{ }, @{ $imap->capability() || [] }), "\n" ) ;
  3214. if ( (! $ssl) and (! defined $tls ) and $imap->has_capability( 'STARTTLS' ) ) {
  3215. myprint( "$Side: going to ssl because STARTTLS is in CAPABILITY. Use --notls1 or --notls2 to avoid that behavior\n" ) ;
  3216. $tls = 1 ;
  3217. }
  3218. if ( $authmech eq 'PREAUTH' ) {
  3219. if ( $imap->IsAuthenticated( ) ) {
  3220. $imap->Socket ;
  3221. myprintf("%s: Assuming PREAUTH for %s\n", $Side, $imap->Server ) ;
  3222. }else{
  3223. die_clean( "$Side failure: error login on $side [$host] with user [$user] auth [PREAUTH]" ) ;
  3224. }
  3225. }
  3226. if ( $tls ) {
  3227. set_tls( $imap, $h ) ;
  3228. $imap->starttls( )
  3229. or die_clean("$Side failure: Can not go to tls encryption on $side [$host]:", $imap->LastError, "\n" ) ;
  3230. myprint( "$Side: Socket successfuly converted to SSL\n" ) ;
  3231. }
  3232. authenticate_imap( $imap, @allargs ) ;
  3233. myprint( "$Side: success login on [$host] with user [$user] auth [$authmech]\n" ) ;
  3234. return( $imap ) ;
  3235. }
  3236. sub authenticate_imap {
  3237. my($imap,
  3238. $host, $port, $user, $domain, $password,
  3239. $mydebugimap, $mytimeout, $fastio,
  3240. $ssl, $tls, $authmech, $authuser, $reconnectretry,
  3241. $proxyauth, $uid, $split, $Side, $h, $mysync ) = @_ ;
  3242. check_capability( $imap, $authmech, $Side ) ;
  3243. if ( $proxyauth ) {
  3244. $imap->Authmechanism(q{}) ;
  3245. $imap->User($authuser) ;
  3246. } else {
  3247. $imap->Authmechanism( $authmech ) unless ( $authmech eq 'LOGIN' or $authmech eq 'PREAUTH' ) ;
  3248. $imap->User($user) ;
  3249. }
  3250. $imap->Authcallback(\&xoauth) if ( 'XOAUTH' eq $authmech ) ;
  3251. $imap->Authcallback(\&xoauth2) if ( 'XOAUTH2' eq $authmech ) ;
  3252. $imap->Authcallback(\&plainauth) if ( ( 'PLAIN' eq $authmech ) or ( 'EXTERNAL' eq $authmech ) ) ;
  3253. $imap->Domain($domain) if (defined $domain) ;
  3254. $imap->Authuser($authuser) ;
  3255. $imap->Password($password) ;
  3256. unless ( $authmech eq 'PREAUTH' or $imap->login( ) ) {
  3257. my $info = "$Side failure: Error login on [$host] with user [$user] auth" ;
  3258. my $einfo = $imap->LastError || @{$imap->History}[$LAST] ;
  3259. chomp $einfo ;
  3260. my $error = "$info [$authmech]: $einfo\n" ;
  3261. if ( $authmech eq 'LOGIN' or $imap->IsUnconnected( ) or $authuser ) {
  3262. die_clean( $error ) ;
  3263. }else{
  3264. myprint( $error ) ;
  3265. }
  3266. myprint( "$Side info: trying LOGIN Auth mechanism on [$host] with user [$user]\n" ) ;
  3267. $imap->Authmechanism(q{}) ;
  3268. $imap->login() or
  3269. die_clean("$info [LOGIN]: ", $imap->LastError, "\n") ;
  3270. }
  3271. if ( $proxyauth ) {
  3272. if ( ! $imap->proxyauth( $user ) ) {
  3273. my $info = "$Side failure: Error doing proxyauth as user [$user] on [$host] using proxy-login as [$authuser]" ;
  3274. my $einfo = $imap->LastError || @{$imap->History}[$LAST] ;
  3275. chomp $einfo ;
  3276. die_clean( "$info: $einfo\n" ) ;
  3277. }
  3278. }
  3279. return ;
  3280. }
  3281. sub check_capability {
  3282. my( $imap, $authmech, $Side ) = @_ ;
  3283. if ($imap->has_capability( "AUTH=$authmech" )
  3284. or $imap->has_capability( $authmech ) ) {
  3285. myprintf("%s: %s says it has CAPABILITY for AUTHENTICATE %s\n",
  3286. $Side, $imap->Server, $authmech) ;
  3287. return ;
  3288. }
  3289. if ( $authmech eq 'LOGIN' ) {
  3290. # Well, the warning is so common and useless that I prefer to remove it
  3291. # No more "... says it has NO CAPABILITY for AUTHENTICATE LOGIN"
  3292. return ;
  3293. }
  3294. myprintf( "%s: %s says it has NO CAPABILITY for AUTHENTICATE %s\n",
  3295. $Side, $imap->Server, $authmech ) ;
  3296. if ($authmech eq 'PLAIN') {
  3297. myprint( "$Side: frequently PLAIN is only supported with SSL, try --ssl or --tls options\n" ) ;
  3298. }
  3299. return ;
  3300. }
  3301. sub set_ssl {
  3302. my ( $imap, $h ) = @_ ;
  3303. # SSL_version can be
  3304. # SSLv3 SSLv2 SSLv23 SSLv23:!SSLv2 (last one is the default in IO-Socket-SSL-1.953)
  3305. #
  3306. my $sslargs_hash = $h->{sslargs} ;
  3307. my $sslargs_default = {
  3308. SSL_verify_mode => $DEFAULT_SSL_VERIFY,
  3309. SSL_verifycn_scheme => 'imap',
  3310. SSL_cipher_list => 'DEFAULT:!DH',
  3311. } ;
  3312. # initiate with default values
  3313. my %sslargs_mix = %{ $sslargs_default } ;
  3314. # now override with passed values
  3315. @sslargs_mix{ keys %{ $sslargs_hash } } = values %{ $sslargs_hash } ;
  3316. # remove keys with undef values
  3317. foreach my $key ( keys %sslargs_mix ) {
  3318. delete $sslargs_mix{ $key } if ( not defined $sslargs_mix{ $key } ) ;
  3319. }
  3320. # back to an ARRAY
  3321. my @sslargs_mix = %sslargs_mix ;
  3322. #myprint( Data::Dumper->Dump( [ $sslargs_hash, $sslargs_default, \%sslargs_mix, \@sslargs_mix ] ) ) ;
  3323. $imap->Ssl( \@sslargs_mix ) ;
  3324. return ;
  3325. }
  3326. sub set_tls {
  3327. my ( $imap, $h ) = @_ ;
  3328. my $sslargs_hash = $h->{sslargs} ;
  3329. my $sslargs_default = {
  3330. SSL_verify_mode => $DEFAULT_SSL_VERIFY,
  3331. SSL_cipher_list => 'DEFAULT:!DH',
  3332. } ;
  3333. # initiate with default values
  3334. my %sslargs_mix = %{ $sslargs_default } ;
  3335. # now override with passed values
  3336. @sslargs_mix{ keys %{ $sslargs_hash } } = values %{ $sslargs_hash } ;
  3337. # remove keys with undef values
  3338. foreach my $key ( keys %sslargs_mix ) {
  3339. delete $sslargs_mix{ $key } if ( not defined $sslargs_mix{ $key } ) ;
  3340. }
  3341. # back to an ARRAY
  3342. my @sslargs_mix = %sslargs_mix ;
  3343. $imap->Starttls( \@sslargs_mix ) ;
  3344. return ;
  3345. }
  3346. sub init_imap {
  3347. my(
  3348. $host, $port, $user, $domain, $password,
  3349. $mydebugimap, $mytimeout, $fastio,
  3350. $ssl, $tls, $authmech, $authuser, $reconnectretry,
  3351. $proxyauth, $uid, $split, $Side, $h, $mysync ) = @_ ;
  3352. my ( $imap ) ;
  3353. $imap = Mail::IMAPClient->new() ;
  3354. if ( $ssl ) { set_ssl( $imap, $h ) }
  3355. if ( $tls ) { } # can not do set_tls() here because connect() will directly do a STARTTLS
  3356. $imap->Clear(1);
  3357. $imap->Server($host);
  3358. $imap->Port($port);
  3359. $imap->Fast_io($fastio);
  3360. $imap->Buffer($buffersize || $DEFAULT_BUFFER_SIZE);
  3361. $imap->Uid($uid);
  3362. $imap->Peek(1);
  3363. $imap->Debug($mydebugimap);
  3364. if ( $mysync->{ showpasswords } ) {
  3365. $imap->Showcredentials( 1 ) ;
  3366. }
  3367. defined $mytimeout and $imap->Timeout( $mytimeout ) ;
  3368. $imap->Reconnectretry( $reconnectretry ) if ( $reconnectretry ) ;
  3369. $imap->{IMAPSYNC_RECONNECT_COUNT} = 0 ;
  3370. $imap->Ignoresizeerrors( $allowsizemismatch ) ;
  3371. $split and $imap->Maxcommandlength( $SPLIT_FACTOR * $split ) ;
  3372. return( $imap ) ;
  3373. }
  3374. sub plainauth {
  3375. my $code = shift;
  3376. my $imap = shift;
  3377. my $string = mysprintf("%s\x00%s\x00%s", $imap->User,
  3378. $imap->Authuser, $imap->Password);
  3379. return encode_base64("$string", q{});
  3380. }
  3381. # Copy from https://github.com/imapsync/imapsync/pull/25/files
  3382. # Changes "use" pragmas to "require".
  3383. # The openssl system call shall be replaced by pure Perl and
  3384. # https://metacpan.org/pod/Crypt::OpenSSL::PKCS12
  3385. # Now the Joaquin Lopez code:
  3386. #
  3387. # Used this as an example: https://gist.github.com/gsainio/6322375
  3388. #
  3389. # And this as a reference: https://developers.google.com/accounts/docs/OAuth2ServiceAccount
  3390. # (note there is an http/rest tab, where the real info is hidden away... went on a witch hunt
  3391. # until I noticed that...)
  3392. #
  3393. # This is targeted at gmail to maintain compatibility after google's oauth1 service is deactivated
  3394. # on May 5th, 2015: https://developers.google.com/gmail/oauth_protocol
  3395. # If there are other oauth2 implementations out there, this would need to be modified to be
  3396. # compatible
  3397. #
  3398. # This is a good guide on setting up the google api/apps side of the equation:
  3399. # http://www.limilabs.com/blog/oauth2-gmail-imap-service-account
  3400. #
  3401. # 2016/05/27: Updated to support oauth/key data in the .json files Google now defaults to
  3402. # when creating gmail service accounts. They're easier to work with since they neither
  3403. # requiring decrypting nor specifying the oauth2 client id separately.
  3404. #
  3405. # If the password arg ends in .json, it will assume this new json method, otherwise it
  3406. # will fallback to the "oauth client id;.p12" format it was previously using.
  3407. sub xoauth2 {
  3408. require JSON::WebToken ;
  3409. require LWP::UserAgent ;
  3410. require HTML::Entities ;
  3411. require JSON ;
  3412. require JSON::WebToken::Crypt::RSA ;
  3413. require Crypt::OpenSSL::RSA ;
  3414. require Encode::Byte ;
  3415. require IO::Socket::SSL ;
  3416. my $code = shift;
  3417. my $imap = shift;
  3418. my ($iss,$key);
  3419. if( $imap->Password =~ /^(.*\.json)$/x ) {
  3420. my $json = JSON->new( ) ;
  3421. my $filename = $1;
  3422. $debug and myprint( "XOAUTH2 json file: $filename\n" ) ;
  3423. open( my $FILE, '<', $filename ) or die_clean( "error [$filename]: $OS_ERROR " ) ;
  3424. my $jsonfile = $json->decode( join q{}, <$FILE> ) ;
  3425. close $FILE ;
  3426. $iss = $jsonfile->{client_id};
  3427. $key = $jsonfile->{private_key};
  3428. $debug and myprint( "Service account: $iss\n");
  3429. $debug and myprint( "Private key:\n$key\n");
  3430. }
  3431. else {
  3432. # Get iss (service account address), keyfile name, and keypassword if necessary
  3433. ( $iss, my $keyfile, my $keypass ) = $imap->Password =~ /([\-\d\w\@\.]+);([a-zA-Z0-9 \_\-\.\/]+);?(.*)?/x ;
  3434. # Assume key password is google default if not provided
  3435. $keypass = 'notasecret' if not $keypass;
  3436. $debug and myprint( "Service account: $iss\nKey file: $keyfile\nKey password: $keypass\n");
  3437. # Get private key from p12 file (would be better in perl...)
  3438. $key = `openssl pkcs12 -in "$keyfile" -nodes -nocerts -passin pass:$keypass -nomacver`;
  3439. $debug and myprint( "Private key:\n$key\n");
  3440. }
  3441. # Create jwt of oauth2 request
  3442. my $time = time ;
  3443. my $jwt = JSON::WebToken->encode( {
  3444. 'iss' => $iss, # service account
  3445. 'scope' => 'https://mail.google.com/',
  3446. 'aud' => 'https://www.googleapis.com/oauth2/v3/token',
  3447. 'exp' => $time + $DEFAULT_EXPIRATION_TIME_OAUTH2_PK12,
  3448. 'iat' => $time,
  3449. 'prn' => $imap->User # user to auth as
  3450. },
  3451. $key, 'RS256', {'typ' => 'JWT'} ); # Crypt::OpenSSL::RSA needed here.
  3452. # Post oauth2 request
  3453. my $ua = LWP::UserAgent->new( ) ;
  3454. $ua->env_proxy( ) ;
  3455. my $response = $ua->post('https://www.googleapis.com/oauth2/v3/token',
  3456. { grant_type => HTML::Entities::encode_entities('urn:ietf:params:oauth:grant-type:jwt-bearer'),
  3457. assertion => $jwt } ) ;
  3458. unless( $response->is_success( ) ) {
  3459. die_clean( $response->code, "\n", $response->content, "\n" ) ;
  3460. }else{
  3461. $debug and myprint( $response->content ) ;
  3462. }
  3463. # access_token in response is what we need
  3464. my $data = JSON::decode_json( $response->content ) ;
  3465. # format as oauth2 auth data
  3466. my $xoauth2_string = encode_base64( 'user=' . $imap->User . "\1auth=Bearer " . $data->{access_token} . "\1\1", q{} ) ;
  3467. $debug and myprint( "XOAUTH2 String: $xoauth2_string\n");
  3468. return($xoauth2_string);
  3469. }
  3470. # xoauth() thanks to Eduardo Bortoluzzi Junior
  3471. sub xoauth {
  3472. require URI::Escape ;
  3473. require Data::Uniqid ;
  3474. my $code = shift;
  3475. my $imap = shift;
  3476. # The base information needed to construct the OAUTH authentication
  3477. my $method = 'GET' ;
  3478. my $url = mysprintf( 'https://mail.google.com/mail/b/%s/imap/', $imap->User ) ;
  3479. my $urlparm = mysprintf( 'xoauth_requestor_id=%s', URI::Escape::uri_escape( $imap->User ) ) ;
  3480. # For Google Apps, the consumer key is the primary domain
  3481. # TODO: create a command line argument to define the consumer key
  3482. my @user_parts = split /@/x, $imap->User ;
  3483. $debug and myprint( "XOAUTH: consumer key: $user_parts[1]\n" ) ;
  3484. # All the parameters needed to be signed on the XOAUTH
  3485. my %hash = ();
  3486. $hash { 'xoauth_requestor_id' } = URI::Escape::uri_escape($imap->User);
  3487. $hash { 'oauth_consumer_key' } = $user_parts[1];
  3488. $hash { 'oauth_nonce' } = md5_hex(Data::Uniqid::uniqid(rand(), 1==1));
  3489. $hash { 'oauth_signature_method' } = 'HMAC-SHA1';
  3490. $hash { 'oauth_timestamp' } = time ;
  3491. $hash { 'oauth_version' } = '1.0';
  3492. # Base will hold the string to be signed
  3493. my $base = "$method&" . URI::Escape::uri_escape( $url ) . q{&} ;
  3494. # The parameters must be in dictionary order before signing
  3495. my $baseparms = q{} ;
  3496. foreach my $key ( sort keys %hash ) {
  3497. if ( length( $baseparms ) > 0 ) {
  3498. $baseparms .= q{&} ;
  3499. }
  3500. $baseparms .= "$key=$hash{$key}" ;
  3501. }
  3502. $base .= URI::Escape::uri_escape($baseparms);
  3503. $debug and myprint( "XOAUTH: base request to sign: $base\n" ) ;
  3504. # Sign it with the consumer secret, informed on the command line (password)
  3505. my $digest = hmac_sha1( $base, URI::Escape::uri_escape( $imap->Password ) . q{&} ) ;
  3506. # The parameters signed become a parameter and...
  3507. $hash { 'oauth_signature' } = URI::Escape::uri_escape( substr encode_base64( $digest ), 0, $MINUS_ONE ) ;
  3508. # ... we don't need the requestor_id anymore.
  3509. delete $hash{'xoauth_requestor_id'} ;
  3510. # Create the final authentication string
  3511. my $string = $method . q{ } . $url . q{?} . $urlparm .q{ } ;
  3512. # All the parameters must be sorted
  3513. $baseparms = q{};
  3514. foreach my $key (sort keys %hash) {
  3515. if(length($baseparms)>0) {
  3516. $baseparms .= q{,} ;
  3517. }
  3518. $baseparms .= "$key=\"$hash{$key}\"";
  3519. }
  3520. $string .= $baseparms;
  3521. $debug and myprint( "XOAUTH: authentication string: $string\n" ) ;
  3522. # It must be base64 encoded
  3523. return encode_base64("$string", q{});
  3524. }
  3525. sub banner_imapsync {
  3526. my @argv = @_ ;
  3527. my $banner_imapsync = join q{},
  3528. q{$RCSfile: imapsync,v $ },
  3529. q{$Revision: 1.836 $ },
  3530. q{$Date: 2017/09/05 16:14:53 $ },
  3531. "\n", localhost_info(), "\n",
  3532. "Command line used:\n",
  3533. "$PROGRAM_NAME ", command_line_nopassword( @argv ), "\n" ;
  3534. return( $banner_imapsync ) ;
  3535. }
  3536. sub do_valid_directory {
  3537. my $dir = shift;
  3538. # all good => return ok.
  3539. return( 1 ) if ( -d $dir and -r _ and -w _ ) ;
  3540. # exist but bad
  3541. if ( -e $dir and not -d _ ) {
  3542. myprint( "Error: $dir exists but is not a directory\n" ) ;
  3543. return( 0 ) ;
  3544. }
  3545. if ( -e $dir and not -w _ ) {
  3546. my $sb = stat $dir ;
  3547. myprintf( "Error: directory %s is not writable for user %s, permissions are %04o and owner is %s ( uid %s )\n",
  3548. $dir, getpwuid_any_os( $EFFECTIVE_USER_ID ), ($sb->mode & oct($PERMISSION_FILTER) ), getpwuid_any_os( $sb->uid ), $sb->uid( ) ) ;
  3549. return( 0 ) ;
  3550. }
  3551. # Trying to create it
  3552. myprint( "Creating directory $dir\n" ) ;
  3553. if ( ! eval { mkpath( $dir ) } ) {
  3554. myprint( "$EVAL_ERROR" ) if ( $EVAL_ERROR ) ;
  3555. }
  3556. return( 1 ) if ( -d $dir and -r _ and -w _ ) ;
  3557. return( 0 ) ;
  3558. }
  3559. sub tests_do_valid_directory {
  3560. note( 'Entering tests_do_valid_directory()' ) ;
  3561. Readonly my $NB_UNIX_tests_do_valid_directory => 4 ;
  3562. SKIP: {
  3563. skip( 'Tests only for Unix', $NB_UNIX_tests_do_valid_directory ) if ( 'MSWin32' eq $OSNAME ) ;
  3564. ok( 1 == do_valid_directory( '.'), 'do_valid_directory: . good' ) ;
  3565. ok( 1 == do_valid_directory( './W/tmp/tests/valid/sub'), 'do_valid_directory: ./W/tmp/tests/valid/sub good' ) ;
  3566. diag( 'Error / not writable is on purpose' ) ;
  3567. ok( 0 == do_valid_directory( '/'), 'do_valid_directory: / bad' ) ;
  3568. diag( 'Error permission denied on /noway is on purpose' ) ;
  3569. ok( 0 == do_valid_directory( '/noway'), 'do_valid_directory: /noway bad' ) ;
  3570. }
  3571. note( 'Leaving tests_do_valid_directory()' ) ;
  3572. return ;
  3573. }
  3574. sub write_pidfile {
  3575. my $pid_filename = shift ;
  3576. my $lock = shift ;
  3577. myprint( "PID file is $pid_filename ( to change it use --pidfile filepath ; to avoid it use --pidfile \"\" )\n" ) ;
  3578. if ( -e $pid_filename and $lock ) {
  3579. myprint( "$pid_filename already exists, another imapsync may be curently running. Aborting imapsync.\n" ) ;
  3580. exit $EXIT_PID_FILE_ALREADY_EXIST ;
  3581. }
  3582. if ( -e $pid_filename ) {
  3583. myprint( "$pid_filename already exists, overwriting it ( use --pidfilelocking to avoid concurrent runs )\n" ) ;
  3584. }
  3585. open my $FILE_HANDLE, '>', $pid_filename
  3586. or do {
  3587. myprint( "Could not open $pid_filename for writing. Check permissions or disk space." ) ;
  3588. return ;
  3589. } ;
  3590. myprint( "Writing my PID $PROCESS_ID in $pid_filename\n" ) ;
  3591. print $FILE_HANDLE $PROCESS_ID ;
  3592. close $FILE_HANDLE ;
  3593. return( $PROCESS_ID ) ;
  3594. }
  3595. sub remove_tmp_files {
  3596. my $mysync = shift or return ;
  3597. $mysync->{pidfile} or return ;
  3598. if ( -e $mysync->{pidfile} ) {
  3599. unlink $mysync->{pidfile} ;
  3600. }
  3601. return ;
  3602. }
  3603. sub exit_clean {
  3604. my $mysync = shift ;
  3605. my $status = shift ;
  3606. $status = defined $status ? $status : $EXIT_UNKNOWN ;
  3607. remove_tmp_files( $mysync ) ;
  3608. myprint( "Exiting with return value $status\n" ) ;
  3609. if ( $mysync->{log} ) {
  3610. myprint( "Log file is $mysync->{logfile} ( to change it, use --logfile filepath ; or use --nolog to turn off logging )\n" ) ;
  3611. close $mysync->{logfile_handle} ;
  3612. }
  3613. exit $status ;
  3614. }
  3615. sub die_clean {
  3616. my @messages = @_ ;
  3617. remove_tmp_files( $sync ) ;
  3618. myprint( @messages ) ;
  3619. exit 255 ;
  3620. }
  3621. sub missing_option {
  3622. my ( $option ) = @_ ;
  3623. die_clean( "$option option is mandatory, for help run $PROGRAM_NAME --help\n" ) ;
  3624. return ;
  3625. }
  3626. sub fix_Inbox_INBOX_mapping {
  3627. my( $h1_all, $h2_all ) = @_ ;
  3628. my $regex = q{} ;
  3629. SWITCH: {
  3630. if ( exists $h1_all->{INBOX} and exists $h2_all->{INBOX} ) { $regex = q{} ; last SWITCH ; } ;
  3631. if ( exists $h1_all->{Inbox} and exists $h2_all->{Inbox} ) { $regex = q{} ; last SWITCH ; } ;
  3632. if ( exists $h1_all->{INBOX} and exists $h2_all->{Inbox} ) { $regex = q{s/^INBOX$/Inbox/x} ; last SWITCH ; } ;
  3633. if ( exists $h1_all->{Inbox} and exists $h2_all->{INBOX} ) { $regex = q{s/^Inbox$/INBOX/x} ; last SWITCH ; } ;
  3634. } ;
  3635. return( $regex ) ;
  3636. }
  3637. sub tests_fix_Inbox_INBOX_mapping {
  3638. note( 'Entering tests_fix_Inbox_INBOX_mapping()' ) ;
  3639. my( $h1_all, $h2_all ) ;
  3640. $h1_all = { 'INBOX' => q{} } ;
  3641. $h2_all = { 'INBOX' => q{} } ;
  3642. ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX INBOX' ) ;
  3643. $h1_all = { 'Inbox' => q{} } ;
  3644. $h2_all = { 'Inbox' => q{} } ;
  3645. ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: Inbox Inbox' ) ;
  3646. $h1_all = { 'INBOX' => q{} } ;
  3647. $h2_all = { 'Inbox' => q{} } ;
  3648. ok( q{s/^INBOX$/Inbox/x} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX Inbox' ) ;
  3649. $h1_all = { 'Inbox' => q{} } ;
  3650. $h2_all = { 'INBOX' => q{} } ;
  3651. ok( q{s/^Inbox$/INBOX/x} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: Inbox INBOX' ) ;
  3652. $h1_all = { 'INBOX' => q{} } ;
  3653. $h2_all = { 'rrrrr' => q{} } ;
  3654. ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX rrrrrr' ) ;
  3655. $h1_all = { 'rrrrr' => q{} } ;
  3656. $h2_all = { 'Inbox' => q{} } ;
  3657. ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: rrrrr Inbox' ) ;
  3658. note( 'Leaving tests_fix_Inbox_INBOX_mapping()' ) ;
  3659. return ;
  3660. }
  3661. sub jux_utf8_list {
  3662. my @s_inp = @_ ;
  3663. my $s_out = q{} ;
  3664. foreach my $s ( @s_inp ) {
  3665. $s_out .= jux_utf8( $s ) . "\n" ;
  3666. }
  3667. return( $s_out ) ;
  3668. }
  3669. sub tests_jux_utf8_list {
  3670. note( 'Entering tests_jux_utf8_list()' ) ;
  3671. ok( q{} eq jux_utf8_list( ), 'jux_utf8_list: void' ) ;
  3672. ok( "[]\n" eq jux_utf8_list( q{} ), 'jux_utf8_list: empty string' ) ;
  3673. ok( "[INBOX]\n" eq jux_utf8_list( 'INBOX' ), 'jux_utf8_list: INBOX' ) ;
  3674. ok( "[&ANY-] = [Ö]\n" eq jux_utf8_list( '&ANY-' ), 'jux_utf8_list: &ANY-' ) ;
  3675. note( 'Leaving tests_jux_utf8_list()' ) ;
  3676. return( 0 ) ;
  3677. }
  3678. sub jux_utf8 {
  3679. # juxtapose utf8 at the right if different
  3680. my ( $s_utf7 ) = shift ;
  3681. my ( $s_utf8 ) = imap_utf7_decode( $s_utf7 ) ;
  3682. if ( $s_utf7 eq $s_utf8 ) {
  3683. #myprint( "[$s_utf7]\n" ) ;
  3684. return( "[$s_utf7]" ) ;
  3685. }else{
  3686. #myprint( "[$s_utf7] = [$s_utf8]\n" ) ;
  3687. return( "[$s_utf7] = [$s_utf8]" ) ;
  3688. }
  3689. }
  3690. # editing utf8 can be tricky without an utf8 editor
  3691. sub tests_jux_utf8 {
  3692. note( 'Entering tests_jux_utf8()' ) ;
  3693. ok( '[INBOX]' eq jux_utf8( 'INBOX'), 'jux_utf8: INBOX => [INBOX]' ) ;
  3694. ok( '[&ZTZO9nux-] = [收件箱]' eq jux_utf8( '&ZTZO9nux-'), 'jux_utf8: => [&ZTZO9nux-] = [收件箱]' ) ;
  3695. ok( '[&ANY-] = [Ö]' eq jux_utf8( '&ANY-'), 'jux_utf8: &ANY- => [&ANY-] = [Ö]' ) ;
  3696. ok( '[]' eq jux_utf8( q{} ), 'jux_utf8: void => []' ) ;
  3697. ok( '[+BD8EQAQ1BDQEOwQ+BDM-] = [предлог]' eq jux_utf8( '+BD8EQAQ1BDQEOwQ+BDM-' ), 'jux_utf8: => [+BD8EQAQ1BDQEOwQ+BDM-] = [предлог]' ) ;
  3698. ok( '[&BB8EQAQ+BDUEOgRC-] = [Проект]' eq jux_utf8( '&BB8EQAQ+BDUEOgRC-' ), 'jux_utf8: => [&BB8EQAQ+BDUEOgRC-] = [Проект]' ) ;
  3699. note( 'Leaving tests_jux_utf8()' ) ;
  3700. return ;
  3701. }
  3702. # Copied from http://cpansearch.perl.org/src/FABPOT/Unicode-IMAPUtf7-2.01/lib/Unicode/IMAPUtf7.pm
  3703. # and then fixed with
  3704. # https://rt.cpan.org/Public/Bug/Display.html?id=11172
  3705. sub imap_utf7_decode {
  3706. my ( $s ) = shift ;
  3707. # Algorithm
  3708. # On remplace , par / dans les BASE 64 (, entre & et -)
  3709. # On remplace les &, non suivi d'un - par +
  3710. # On remplace les &- par &
  3711. $s =~ s/&([^,&\-]*),([^,\-&]*)\-/&$1\/$2\-/xg ;
  3712. $s =~ s/&(?!\-)/\+/xg ;
  3713. $s =~ s/&\-/&/xg ;
  3714. return( Unicode::String::utf7( $s )->utf8 ) ;
  3715. }
  3716. sub imap_utf7_encode {
  3717. my ( $s ) = @_ ;
  3718. $s = Unicode::String::utf8( $s )->utf7 ;
  3719. $s =~ s/\+([^\/&\-]*)\/([^\/\-&]*)\-/\+$1,$2\-/xg ;
  3720. $s =~ s/&/&\-/xg ;
  3721. $s =~ s/\+([^+\-]+)?\-/&$1\-/xg ;
  3722. return( $s ) ;
  3723. }
  3724. sub select_folder {
  3725. my ( $imap, $folder, $hostside ) = @_ ;
  3726. if ( ! $imap->select( $folder ) ) {
  3727. my $error = join q{},
  3728. "$hostside folder $folder: Could not select: ",
  3729. $imap->LastError, "\n" ;
  3730. errors_incr( $sync, $error ) ;
  3731. return( 0 ) ;
  3732. }else{
  3733. # ok select succeeded
  3734. return( 1 ) ;
  3735. }
  3736. }
  3737. sub examine_folder {
  3738. my ( $imap, $folder, $hostside ) = @_ ;
  3739. if ( ! $imap->examine( $folder ) ) {
  3740. my $error = join q{},
  3741. "$hostside folder $folder: Could not examine: ",
  3742. $imap->LastError, "\n" ;
  3743. errors_incr( $sync, $error ) ;
  3744. return( 0 ) ;
  3745. }else{
  3746. # ok select succeeded
  3747. return( 1 ) ;
  3748. }
  3749. }
  3750. sub count_from_select {
  3751. my @lines = @_ ;
  3752. my $count ;
  3753. foreach my $line ( @lines ) {
  3754. #myprint( "line = [$line]\n" ) ;
  3755. if ( $line =~ m/^\*\s+(\d+)\s+EXISTS/x ) {
  3756. $count = $1 ;
  3757. return( $count ) ;
  3758. }
  3759. }
  3760. return( undef ) ;
  3761. }
  3762. sub create_folder_old {
  3763. my( $imap, $h2_fold, $h1_fold ) = @_ ;
  3764. myprint( "Creating (old way) folder [$h2_fold] on host2\n" ) ;
  3765. if ( ( 'INBOX' eq uc $h2_fold )
  3766. and ( $imap->exists( $h2_fold ) ) ) {
  3767. myprint( "Folder [$h2_fold] already exists\n" ) ;
  3768. return( 1 ) ;
  3769. }
  3770. if ( ! $sync->{dry} ){
  3771. if ( ! $imap->create( $h2_fold ) ) {
  3772. my $error = join q{},
  3773. "Could not create folder [$h2_fold] from [$h1_fold]: ",
  3774. $imap->LastError( ), "\n" ;
  3775. errors_incr( $sync, $error ) ;
  3776. # success if folder exists ("already exists" error)
  3777. return( 1 ) if $imap->exists( $h2_fold ) ;
  3778. # failure since create failed
  3779. return( 0 ) ;
  3780. }else{
  3781. #create succeeded
  3782. myprint( "Created ( the old way ) folder [$h2_fold] on host2\n" ) ;
  3783. return( 1 ) ;
  3784. }
  3785. }else{
  3786. # dry mode, no folder so many imap will fail, assuming failure
  3787. myprint( "Created ( the old way ) folder [$h2_fold] on host2 $sync->{dry_message}\n" ) ;
  3788. return( 0 ) ;
  3789. }
  3790. }
  3791. sub create_folder {
  3792. my( $imap2 , $h2_fold , $h1_fold ) = @_ ;
  3793. my( @parts , $parent ) ;
  3794. if ( $imap2->IsUnconnected( ) ) {
  3795. myprint( "Host2: Unconnected state\n" ) ;
  3796. return( 0 ) ;
  3797. }
  3798. if ( $create_folder_old ) {
  3799. return( create_folder_old( $imap2 , $h2_fold , $h1_fold ) ) ;
  3800. }
  3801. myprint( "Creating folder [$h2_fold] on host2\n" ) ;
  3802. if ( ( 'INBOX' eq uc $h2_fold )
  3803. and ( $imap2->exists( $h2_fold ) ) ) {
  3804. myprint( "Folder [$h2_fold] already exists\n" ) ;
  3805. return( 1 ) ;
  3806. }
  3807. if ( $mixfolders and $imap2->exists( $h2_fold ) ) {
  3808. myprint( "Folder [$h2_fold] already exists (--nomixfolders is not set)\n" ) ;
  3809. return( 1 ) ;
  3810. }
  3811. if ( ( not $mixfolders ) and ( $imap2->exists( $h2_fold ) ) ) {
  3812. myprint( "Folder [$h2_fold] already exists and --nomixfolders is set\n" ) ;
  3813. return( 0 ) ;
  3814. }
  3815. @parts = split /\Q$h2_sep\E/x, $h2_fold ;
  3816. pop @parts ;
  3817. $parent = join $h2_sep, @parts ;
  3818. $parent =~ s/^\s+|\s+$//xg ;
  3819. if ( ( $parent ne q{} ) and ( ! $imap2->exists( $parent ) ) ) {
  3820. create_folder( $imap2 , $parent , $h1_fold ) ;
  3821. }
  3822. if ( ! $sync->{dry} ) {
  3823. if ( ! $imap2->create( $h2_fold ) ) {
  3824. my $error = join q{},
  3825. "Could not create folder [$h2_fold] from [$h1_fold]: " ,
  3826. $imap2->LastError( ), "\n" ;
  3827. errors_incr( $sync, $error ) ;
  3828. # success if folder exists ("already exists" error)
  3829. return( 1 ) if $imap2->exists( $h2_fold ) ;
  3830. # failure since create failed
  3831. return( 0 ) ;
  3832. }else{
  3833. #create succeeded
  3834. myprint( "Created folder [$h2_fold] on host2\n" ) ;
  3835. return( 1 ) ;
  3836. }
  3837. }else{
  3838. # dry mode, no folder so many imap will fail, assuming failure
  3839. myprint( "Created folder [$h2_fold] on host2 $sync->{dry_message}\n" ) ;
  3840. if ( ! $justfolders ) {
  3841. myprint( "Since --dry mode is on and folder [$h2_fold] on host2 does not exist yet, syncing messages will not be simulated.\n"
  3842. . "To simulate message syncing, use --justfolders without --dry to first create the missing folders then rerun the --dry sync.\n" ) ;
  3843. }
  3844. return( 0 ) ;
  3845. }
  3846. }
  3847. sub tests_folder_routines {
  3848. note( 'Entering tests_folder_routines()' ) ;
  3849. ok( !is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 1' );
  3850. ok( add_to_requested_folders('folder_foo'), 'add_to_requested_folders folder_foo' );
  3851. ok( is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 2' );
  3852. ok( !is_requested_folder('folder_NO_EXIST'), 'is_requested_folder folder_NO_EXIST' );
  3853. ok( !remove_from_requested_folders('folder_foo'), 'removed folder_foo' );
  3854. ok( !is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 3' );
  3855. my @f ;
  3856. ok( @f = add_to_requested_folders('folder_bar', 'folder_toto'), "add result: @f" );
  3857. ok( is_requested_folder('folder_bar'), 'is_requested_folder 4' );
  3858. ok( is_requested_folder('folder_toto'), 'is_requested_folder 5' );
  3859. ok( remove_from_requested_folders('folder_toto'), 'remove_from_requested_folders: ' );
  3860. ok( !is_requested_folder('folder_toto'), 'is_requested_folder 6' );
  3861. ok( !remove_from_requested_folders('folder_bar'), 'remove_from_requested_folders: empty' ) ;
  3862. ok( 0 == compare_lists( [ sort_requested_folders( ) ], [] ), 'sort_requested_folders: all empty' ) ;
  3863. ok( add_to_requested_folders('M_55'), 'add_to_requested_folders M_55' );
  3864. ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'M_55' ] ), 'sort_requested_folders: middle' ) ;
  3865. @folderfirst = ( 'Z_11' ) ;
  3866. ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'M_55' ] ), 'sort_requested_folders: first+middle' ) ;
  3867. @folderlast = ( 'A_99' ) ;
  3868. ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'M_55', 'A_99' ] ), 'sort_requested_folders: first+middle+last 1' ) ;
  3869. ok( add_to_requested_folders('M_55', 'M_44',), 'add_to_requested_folders M_55 M_44' );
  3870. ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'M_44', 'M_55', 'A_99' ] ), 'sort_requested_folders: first+middle+last 2' ) ;
  3871. @folderfirst = qw( Z_22 Z_11 ) ;
  3872. @folderlast = qw( A_99 A_88 ) ;
  3873. 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' ) ;
  3874. note( 'Leaving tests_folder_routines()' ) ;
  3875. return ;
  3876. }
  3877. sub sort_requested_folders {
  3878. my @requested_folders_sorted = () ;
  3879. foreach my $folder ( @folderfirst ) {
  3880. remove_from_requested_folders( $folder ) ;
  3881. }
  3882. foreach my $folder ( @folderlast ) {
  3883. remove_from_requested_folders( $folder ) ;
  3884. }
  3885. my @middle = sort keys %requested_folder ;
  3886. @requested_folders_sorted = ( @folderfirst, @middle, @folderlast ) ;
  3887. return( @requested_folders_sorted ) ;
  3888. }
  3889. sub is_requested_folder {
  3890. my ( $folder ) = @_;
  3891. return( defined $requested_folder{ $folder } ) ;
  3892. }
  3893. sub add_to_requested_folders {
  3894. my @wanted_folders = @_ ;
  3895. foreach my $folder ( @wanted_folders ) {
  3896. ++$requested_folder{ $folder } ;
  3897. }
  3898. return( keys %requested_folder ) ;
  3899. }
  3900. sub remove_from_requested_folders {
  3901. my @wanted_folders = @_ ;
  3902. foreach my $folder ( @wanted_folders ) {
  3903. delete $requested_folder{ $folder } ;
  3904. }
  3905. return( keys %requested_folder ) ;
  3906. }
  3907. sub compare_lists {
  3908. my ($list_1_ref, $list_2_ref) = @_;
  3909. return($MINUS_ONE) if ((not defined $list_1_ref) and defined $list_2_ref);
  3910. return(0) if ((not defined $list_1_ref) and not defined $list_2_ref); # end if no list
  3911. return(1) if (not defined $list_2_ref); # end if only one list
  3912. if (not ref $list_1_ref ) {$list_1_ref = [$list_1_ref]};
  3913. if (not ref $list_2_ref ) {$list_2_ref = [$list_2_ref]};
  3914. my $last_used_indice = $MINUS_ONE;
  3915. ELEMENT:
  3916. foreach my $indice ( 0 .. $#{ $list_1_ref } ) {
  3917. $last_used_indice = $indice ;
  3918. # End of list_2
  3919. return 1 if ($indice > $#{ $list_2_ref } ) ;
  3920. my $element_list_1 = $list_1_ref->[$indice] ;
  3921. my $element_list_2 = $list_2_ref->[$indice] ;
  3922. my $balance = $element_list_1 cmp $element_list_2 ;
  3923. next ELEMENT if ($balance == 0) ;
  3924. return $balance ;
  3925. }
  3926. # each element equal until last indice of list_1
  3927. return $MINUS_ONE if ($last_used_indice < $#{ $list_2_ref } ) ;
  3928. # same size, each element equal
  3929. return 0 ;
  3930. }
  3931. sub tests_compare_lists {
  3932. note( 'Entering tests_compare_lists()' ) ;
  3933. my $empty_list_ref = [];
  3934. ok( 0 == compare_lists() , 'compare_lists, no args');
  3935. ok( 0 == compare_lists(undef) , 'compare_lists, undef = nothing');
  3936. ok( 0 == compare_lists(undef, undef) , 'compare_lists, undef = undef');
  3937. ok($MINUS_ONE == compare_lists(undef , []) , 'compare_lists, undef < []');
  3938. ok($MINUS_ONE == compare_lists(undef , [1]) , 'compare_lists, undef < [1]');
  3939. ok($MINUS_ONE == compare_lists(undef , [0]) , 'compare_lists, undef < [0]');
  3940. ok(+1 == compare_lists([]) , 'compare_lists, [] > nothing');
  3941. ok(+1 == compare_lists([], undef) , 'compare_lists, [] > undef');
  3942. ok( 0 == compare_lists([] , []) , 'compare_lists, [] = []');
  3943. ok($MINUS_ONE == compare_lists([] , [1]) , 'compare_lists, [] < [1]');
  3944. ok(+1 == compare_lists([1] , []) , 'compare_lists, [1] > []');
  3945. ok( 0 == compare_lists([1], 1 ) , 'compare_lists, [1] = 1 ') ;
  3946. ok( 0 == compare_lists( 1 , [1]) , 'compare_lists, 1 = [1]') ;
  3947. ok( 0 == compare_lists( 1 , 1 ) , 'compare_lists, 1 = 1 ') ;
  3948. ok($MINUS_ONE == compare_lists( 0 , 1 ) , 'compare_lists, 0 < 1 ') ;
  3949. ok($MINUS_ONE == compare_lists($MINUS_ONE , 0 ) , 'compare_lists, -1 < 0 ') ;
  3950. ok($MINUS_ONE == compare_lists( 1 , 2 ) , 'compare_lists, 1 < 2 ') ;
  3951. ok(+1 == compare_lists( 2 , 1 ) , 'compare_lists, 2 > 1 ') ;
  3952. ok( 0 == compare_lists([1,2], [1,2]) , 'compare_lists, [1,2] = [1,2]' ) ;
  3953. ok($MINUS_ONE == compare_lists([1], [1,2]) , 'compare_lists, [1] < [1,2]' ) ;
  3954. ok(+1 == compare_lists([2], [1,2]) , 'compare_lists, [2] > [1,2]' ) ;
  3955. ok($MINUS_ONE == compare_lists([1], [1,1]) , 'compare_lists, [1] < [1,1]' ) ;
  3956. ok(+1 == compare_lists([1, 1], [1]) , 'compare_lists, [1, 1] > [1]' ) ;
  3957. ok( 0 == compare_lists([1 .. $NUMBER_20_000] , [1 .. $NUMBER_20_000])
  3958. , 'compare_lists, [1..20_000] = [1..20_000]' ) ;
  3959. ok($MINUS_ONE == compare_lists([1], [2]) , 'compare_lists, [1] < [2]') ;
  3960. ok( 0 == compare_lists([2], [2]) , 'compare_lists, [0] = [2]') ;
  3961. ok(+1 == compare_lists([2], [1]) , 'compare_lists, [2] > [1]') ;
  3962. ok($MINUS_ONE == compare_lists(['a'], ['b']) , 'compare_lists, ["a"] < ["b"]') ;
  3963. ok( 0 == compare_lists(['a'], ['a']) , 'compare_lists, ["a"] = ["a"]') ;
  3964. ok( 0 == compare_lists(['ab'], ['ab']) , 'compare_lists, ["ab"] = ["ab"]') ;
  3965. ok(+1 == compare_lists(['b'], ['a']) , 'compare_lists, ["b"] > ["a"]') ;
  3966. ok($MINUS_ONE == compare_lists(['a'], ['aa']) , 'compare_lists, ["a"] < ["aa"]') ;
  3967. ok($MINUS_ONE == compare_lists(['a'], ['a', 'a']), 'compare_lists, ["a"] < ["a", "a"]') ;
  3968. ok( 0 == compare_lists([split q{ }, 'a b' ], ['a', 'b']), 'compare_lists, split') ;
  3969. ok( 0 == compare_lists([sort split q{ }, 'b a' ], ['a', 'b']), 'compare_lists, sort split') ;
  3970. note( 'Leaving tests_compare_lists()' ) ;
  3971. return ;
  3972. }
  3973. sub guess_prefix {
  3974. my @foldernames = @_ ;
  3975. my $prefix_guessed = q{} ;
  3976. foreach my $folder ( @foldernames ) {
  3977. next if ( $folder =~ m{^INBOX$}xi ) ; # no guessing from INBOX
  3978. if ( $folder !~ m{^INBOX}xi ) {
  3979. $prefix_guessed = q{} ; # prefix empty guessed
  3980. last ;
  3981. }
  3982. if ( $folder =~ m{^(INBOX(?:\.|\/))}xi ) {
  3983. $prefix_guessed = $1 ; # prefix Inbox/ or INBOX. guessed
  3984. }
  3985. }
  3986. return( $prefix_guessed ) ;
  3987. }
  3988. sub tests_guess_prefix {
  3989. note( 'Entering tests_guess_prefix()' ) ;
  3990. is( guess_prefix( ), q{}, 'guess_prefix: no args => empty string' ) ;
  3991. is( q{} , guess_prefix( 'INBOX' ), 'guess_prefix: INBOX alone' ) ;
  3992. is( q{} , guess_prefix( 'Inbox' ), 'guess_prefix: Inbox alone' ) ;
  3993. is( q{} , guess_prefix( 'INBOX' ), 'guess_prefix: INBOX alone' ) ;
  3994. is( 'INBOX/' , guess_prefix( 'INBOX', 'INBOX/Junk' ), 'guess_prefix: INBOX INBOX/Junk' ) ;
  3995. is( 'INBOX.' , guess_prefix( 'INBOX', 'INBOX.Junk' ), 'guess_prefix: INBOX INBOX.Junk' ) ;
  3996. is( 'Inbox/' , guess_prefix( 'Inbox', 'Inbox/Junk' ), 'guess_prefix: Inbox Inbox/Junk' ) ;
  3997. is( 'Inbox.' , guess_prefix( 'Inbox', 'Inbox.Junk' ), 'guess_prefix: Inbox Inbox.Junk' ) ;
  3998. is( 'INBOX/' , guess_prefix( 'INBOX', 'INBOX/Junk', 'INBOX/rrr' ), 'guess_prefix: INBOX INBOX/Junk INBOX/rrr' ) ;
  3999. is( q{} , guess_prefix( 'INBOX', 'INBOX/Junk', 'INBOX/rrr', 'zzz' ), 'guess_prefix: INBOX INBOX/Junk INBOX/rrr zzz' ) ;
  4000. is( q{} , guess_prefix( 'INBOX', 'Junk' ), 'guess_prefix: INBOX Junk' ) ;
  4001. is( q{} , guess_prefix( 'INBOX', 'Junk' ), 'guess_prefix: INBOX Junk' ) ;
  4002. note( 'Leaving tests_guess_prefix()' ) ;
  4003. return ;
  4004. }
  4005. sub get_prefix {
  4006. my( $imap, $prefix_in, $prefix_opt, $Side, $folders_ref ) = @_ ;
  4007. my( $prefix_out, $prefix_guessed ) ;
  4008. ( $debug or $sync->{debugfolders} ) and myprint( "$Side: Getting prefix\n" ) ;
  4009. $prefix_guessed = guess_prefix( @{ $folders_ref } ) ;
  4010. myprint( "$Side: guessing prefix from folder listing: [$prefix_guessed]\n" ) ;
  4011. ( $debug or $sync->{debugfolders} ) and myprint( "$Side: Calling namespace capability\n" ) ;
  4012. if ( $imap->has_capability( 'namespace' ) ) {
  4013. my $r_namespace = $imap->namespace( ) ;
  4014. $prefix_out = $r_namespace->[0][0][0] ;
  4015. myprint( "$Side: prefix given by NAMESPACE: [$prefix_out]\n" ) ;
  4016. if ( defined $prefix_in ) {
  4017. myprint( "$Side: but using [$prefix_in] given by $prefix_opt\n" ) ;
  4018. $prefix_out = $prefix_in ;
  4019. return( $prefix_out ) ;
  4020. }else{
  4021. # all good
  4022. return( $prefix_out ) ;
  4023. }
  4024. }
  4025. else{
  4026. if ( defined $prefix_in ) {
  4027. myprint( "$Side: using [$prefix_in] given by $prefix_opt\n" ) ;
  4028. $prefix_out = $prefix_in ;
  4029. return( $prefix_out ) ;
  4030. }else{
  4031. myprint(
  4032. "$Side: No NAMESPACE capability so using guessed prefix [$prefix_guessed]\n",
  4033. help_to_guess_prefix( $imap, $prefix_opt ) ) ;
  4034. return( $prefix_guessed ) ;
  4035. }
  4036. }
  4037. return ;
  4038. }
  4039. sub guess_separator {
  4040. my @foldernames = @_ ;
  4041. #return( undef ) unless ( @foldernames ) ;
  4042. my $sep_guessed ;
  4043. my %counter ;
  4044. foreach my $folder ( @foldernames ) {
  4045. $counter{'/'}++ while ( $folder =~ m{/}xg ) ; # count /
  4046. $counter{'.'}++ while ( $folder =~ m{\.}xg ) ; # count .
  4047. $counter{'\\\\'}++ while ( $folder =~ m{(\\){2}}xg ) ; # count \\
  4048. $counter{'\\'}++ while ( $folder =~ m{[^\\](\\){1}(?=[^\\])}xg ) ; # count \
  4049. }
  4050. my @race_sorted = sort { $counter{ $b } <=> $counter{ $a } } keys %counter ;
  4051. $debug and myprint( "@foldernames\n@race_sorted\n", %counter, "\n" ) ;
  4052. $sep_guessed = shift @race_sorted || $LAST_RESSORT_SEPARATOR ; # / when nothing found.
  4053. return( $sep_guessed ) ;
  4054. }
  4055. sub tests_guess_separator {
  4056. note( 'Entering tests_guess_separator()' ) ;
  4057. ok( '/' eq guess_separator( ), 'guess_separator: no args' ) ;
  4058. ok( '/' eq guess_separator( 'abcd' ), 'guess_separator: abcd' ) ;
  4059. ok( '/' eq guess_separator( 'a/b/c.d' ), 'guess_separator: a/b/c.d' ) ;
  4060. ok( '.' eq guess_separator( 'a.b/c.d' ), 'guess_separator: a.b/c.d' ) ;
  4061. ok( '\\\\' eq guess_separator( 'a\\\\b\\\\c.c\\\\d/e/f' ), 'guess_separator: a\\\\b\\\\c.c\\\\d/e/f' ) ;
  4062. ok( '\\' eq guess_separator( 'a\\b\\c.c\\d/e/f' ), 'guess_separator: a\\b\\c.c\\d/e/f' ) ;
  4063. ok( '\\' eq guess_separator( 'a\\b' ), 'guess_separator: a\\b' ) ;
  4064. ok( '\\' eq guess_separator( 'a\\b\\c' ), 'guess_separator: a\\b\\c' ) ;
  4065. note( 'Leaving tests_guess_separator()' ) ;
  4066. return ;
  4067. }
  4068. sub get_separator {
  4069. my( $imap, $sep_in, $sep_opt, $Side, $folders_ref ) = @_ ;
  4070. my( $sep_out, $sep_guessed ) ;
  4071. ( $debug or $sync->{debugfolders} ) and myprint( "$Side: Getting separator\n" ) ;
  4072. $sep_guessed = guess_separator( @{ $folders_ref } ) ;
  4073. myprint( "$Side: guessing separator from folder listing: [$sep_guessed]\n" ) ;
  4074. ( $debug or $sync->{debugfolders} ) and myprint( "$Side: calling namespace capability\n" ) ;
  4075. if ( $imap->has_capability( 'namespace' ) ) {
  4076. $sep_out = $imap->separator( ) ;
  4077. if ( defined $sep_out ) {
  4078. myprint( "$Side: separator given by NAMESPACE: [$sep_out]\n" ) ;
  4079. if ( defined $sep_in ) {
  4080. myprint( "$Side: but using [$sep_in] given by $sep_opt\n" ) ;
  4081. $sep_out = $sep_in ;
  4082. return( $sep_out ) ;
  4083. }else{
  4084. return( $sep_out ) ;
  4085. }
  4086. }else{
  4087. if ( defined $sep_in ) {
  4088. myprint( "$Side: NAMESPACE request failed but using [$sep_in] given by $sep_opt\n" ) ;
  4089. $sep_out = $sep_in ;
  4090. return( $sep_out ) ;
  4091. }else{
  4092. myprint(
  4093. "$Side: NAMESPACE request failed so using guessed separator [$sep_guessed]\n",
  4094. help_to_guess_sep( $imap, $sep_opt ) ) ;
  4095. return( $sep_guessed ) ;
  4096. }
  4097. }
  4098. }
  4099. else{
  4100. if ( defined $sep_in ) {
  4101. myprint( "$Side: No NAMESPACE capability but using [$sep_in] given by $sep_opt\n" ) ;
  4102. $sep_out = $sep_in ;
  4103. return( $sep_out ) ;
  4104. }else{
  4105. myprint(
  4106. "$Side: No NAMESPACE capability, so using guessed separator [$sep_guessed]\n",
  4107. help_to_guess_sep( $imap, $sep_opt ) ) ;
  4108. return( $sep_guessed ) ;
  4109. }
  4110. }
  4111. return ;
  4112. }
  4113. sub help_to_guess_sep {
  4114. my( $imap, $sep_opt ) = @_ ;
  4115. my $help_to_guess_sep = "You can set the separator character with the $sep_opt option,\n"
  4116. . "the complete listing of folders may help you to find it\n"
  4117. . folders_list_to_help( $imap ) ;
  4118. return( $help_to_guess_sep ) ;
  4119. }
  4120. sub help_to_guess_prefix {
  4121. my( $imap, $prefix_opt ) = @_ ;
  4122. my $help_to_guess_prefix = "You can set the prefix namespace with the $prefix_opt option,\n"
  4123. . "the folowing listing of folders may help you to find it:\n"
  4124. . folders_list_to_help( $imap ) ;
  4125. return( $help_to_guess_prefix ) ;
  4126. }
  4127. sub folders_list_to_help {
  4128. my($imap) = @_ ;
  4129. my @folders = $imap->folders ;
  4130. my $listing = join q{}, map { "[$_]\n" } @folders ;
  4131. return( $listing ) ;
  4132. }
  4133. sub tests_separator_invert {
  4134. note( 'Entering tests_separator_invert()' ) ;
  4135. $fixslash2 = 0 ;
  4136. ok( not( defined separator_invert( ) ), 'separator_invert: no args' ) ;
  4137. ok( not( defined separator_invert( q{} ) ), 'separator_invert: not enough args' ) ;
  4138. ok( not( defined separator_invert( q{}, q{} ) ), 'separator_invert: not enough args' ) ;
  4139. ok( q{} eq separator_invert( q{}, q{}, q{} ), 'separator_invert: 3 empty strings' ) ;
  4140. ok( 'lalala' eq separator_invert( 'lalala', q{}, q{} ), 'separator_invert: empty separator' ) ;
  4141. ok( 'lalala' eq separator_invert( 'lalala', '/', '/' ), 'separator_invert: same separator /' ) ;
  4142. ok( 'lal/ala' eq separator_invert( 'lal/ala', '/', '/' ), 'separator_invert: same separator / 2' ) ;
  4143. ok( 'lal.ala' eq separator_invert( 'lal/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
  4144. ok( 'lal/ala' eq separator_invert( 'lal.ala', '.', '/' ), 'separator_invert: separators ./' ) ;
  4145. ok( 'la.l/ala' eq separator_invert( 'la/l.ala', '.', '/' ), 'separator_invert: separators ./' ) ;
  4146. ok( 'l/al.ala' eq separator_invert( 'l.al/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
  4147. $fixslash2 = 1 ;
  4148. ok( 'l_al.ala' eq separator_invert( 'l.al/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
  4149. note( 'Leaving tests_separator_invert()' ) ;
  4150. return ;
  4151. }
  4152. sub separator_invert {
  4153. my( $h1_fold, $h1_separator, $h2_separator ) = @_ ;
  4154. return( undef ) if ( not defined $h1_fold or not defined $h1_separator or not defined $h2_separator ) ;
  4155. # The separator we hope we'll never encounter: 00000000 == 0x00
  4156. my $o_sep = "\000" ;
  4157. my $h2_fold = $h1_fold ;
  4158. $h2_fold =~ s,\Q$h2_separator,$o_sep,xg ;
  4159. $h2_fold =~ s,\Q$h1_separator,$h2_separator,xg ;
  4160. $h2_fold =~ s,\Q$o_sep,$h1_separator,xg ;
  4161. $h2_fold =~ s,/,_,xg if( $fixslash2 and '/' ne $h2_separator and '/' eq $h1_separator ) ;
  4162. return( $h2_fold ) ;
  4163. }
  4164. sub tests_imap2_folder_name {
  4165. note( 'Entering tests_imap2_folder_name()' ) ;
  4166. $h1_prefix = $h2_prefix = q{};
  4167. $h1_sep = '/';
  4168. $h2_sep = '.';
  4169. $debug and myprint( <<"EOS"
  4170. prefix1: [$h1_prefix]
  4171. prefix2: [$h2_prefix]
  4172. sep1:[$h1_sep]
  4173. sep2:[$h2_sep]
  4174. EOS
  4175. ) ;
  4176. $fixslash2 = 0 ;
  4177. ok(q{} eq imap2_folder_name(q{}), 'imap2_folder_name: empty string');
  4178. ok('blabla' eq imap2_folder_name('blabla'), 'imap2_folder_name: blabla');
  4179. ok('spam.spam' eq imap2_folder_name('spam/spam'), 'imap2_folder_name: spam/spam');
  4180. ok('spam/spam' eq imap2_folder_name('spam.spam'), 'imap2_folder_name: spam.spam');
  4181. ok('spam.spam/spam' eq imap2_folder_name('spam/spam.spam'), 'imap2_folder_name: spam/spam.spam');
  4182. ok('s pam.spam/sp am' eq imap2_folder_name('s pam/spam.sp am'), 'imap2_folder_name: s pam/spam.sp am');
  4183. $sync->{f1f2}{ 'auto' } = 'moto' ;
  4184. ok( 'moto' eq imap2_folder_name( 'auto' ), 'imap2_folder_name: auto' ) ;
  4185. $sync->{f1f2}{ 'auto/auto' } = 'moto x 2' ;
  4186. ok( 'moto x 2' eq imap2_folder_name( 'auto/auto' ), 'imap2_folder_name: auto/auto' ) ;
  4187. @regextrans2 = ('s,/,X,g');
  4188. ok(q{} eq imap2_folder_name(q{}), 'imap2_folder_name: empty string [s,/,X,g]');
  4189. ok('blabla' eq imap2_folder_name('blabla'), 'imap2_folder_name: blabla [s,/,X,g]');
  4190. ok('spam.spam' eq imap2_folder_name('spam/spam'), 'imap2_folder_name: spam/spam [s,/,X,g]');
  4191. ok('spamXspam' eq imap2_folder_name('spam.spam'), 'imap2_folder_name: spam.spam [s,/,X,g]');
  4192. ok('spam.spamXspam' eq imap2_folder_name('spam/spam.spam'), 'imap2_folder_name: spam/spam.spam [s,/,X,g]');
  4193. @regextrans2 = ( 's, ,_,g' ) ;
  4194. ok('blabla' eq imap2_folder_name('blabla'), 'imap2_folder_name: blabla [s, ,_,g]');
  4195. ok('bla_bla' eq imap2_folder_name('bla bla'), 'imap2_folder_name: blabla [s, ,_,g]');
  4196. @regextrans2 = ( q{s,(.*),\U$1,} ) ;
  4197. ok( 'BLABLA' eq imap2_folder_name( 'blabla' ), q{imap2_folder_name: blabla [s,\U(.*)\E,$1,]} ) ;
  4198. $fixslash2 = 1 ;
  4199. @regextrans2 = ( ) ;
  4200. ok(q{} eq imap2_folder_name(q{}), 'imap2_folder_name: empty string');
  4201. ok('blabla' eq imap2_folder_name('blabla'), 'imap2_folder_name: blabla');
  4202. ok('spam.spam' eq imap2_folder_name('spam/spam'), 'imap2_folder_name: spam/spam -> spam.spam');
  4203. ok('spam_spam' eq imap2_folder_name('spam.spam'), 'imap2_folder_name: spam.spam -> spam_spam');
  4204. ok('spam.spam_spam' eq imap2_folder_name('spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam_spam');
  4205. ok('s pam.spam_spa m' eq imap2_folder_name('s pam/spam.spa m'), 'imap2_folder_name: s pam/spam.spa m -> s pam.spam_spa m');
  4206. $h1_sep = '.';
  4207. $h2_sep = '/';
  4208. ok(q{} eq imap2_folder_name(q{}), 'imap2_folder_name: empty string');
  4209. ok('blabla' eq imap2_folder_name('blabla'), 'imap2_folder_name: blabla');
  4210. ok('spam.spam' eq imap2_folder_name('spam/spam'), 'imap2_folder_name: spam/spam -> spam.spam');
  4211. ok('spam/spam' eq imap2_folder_name('spam.spam'), 'imap2_folder_name: spam.spam -> spam/spam');
  4212. ok('spam.spam/spam' eq imap2_folder_name('spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam');
  4213. $fixslash2 = 0 ;
  4214. $h1_prefix = q{ };
  4215. ok('spam.spam/spam' eq imap2_folder_name('spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam');
  4216. ok('spam.spam/spam' eq imap2_folder_name(' spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam');
  4217. $h1_sep = '.' ;
  4218. $h2_sep = '/' ;
  4219. $h1_prefix = 'INBOX.' ;
  4220. $h2_prefix = q{} ;
  4221. @regextrans2 = ( q{s,(.*),\U$1,} ) ;
  4222. ok( 'BLABLA' eq imap2_folder_name( 'blabla' ), 'imap2_folder_name: blabla' ) ;
  4223. ok( 'TEST/TEST/TEST/TEST' eq imap2_folder_name( 'INBOX.TEST.test.Test.tesT' ), 'imap2_folder_name: INBOX.TEST.test.Test.tesT' ) ;
  4224. @regextrans2 = ( q{s,(.*),\L$1,} ) ;
  4225. ok( 'test/test/test/test' eq imap2_folder_name( 'INBOX.TEST.test.Test.tesT' ), 'imap2_folder_name: INBOX.TEST.test.Test.tesT' ) ;
  4226. note( 'Leaving tests_imap2_folder_name()' ) ;
  4227. return ;
  4228. }
  4229. sub imap2_folder_name {
  4230. my ( $h1_fold ) = @_ ;
  4231. my ( $h2_fold ) ;
  4232. if ( $sync->{f1f2}{ $h1_fold } ) {
  4233. $h2_fold = $sync->{f1f2}{ $h1_fold } ;
  4234. ( $debug or $sync->{debugfolders} ) and myprint( "f1f2 [$h1_fold] -> [$h2_fold]\n" ) ;
  4235. return( $h2_fold ) ;
  4236. }
  4237. if ( $sync->{f1f2auto}{ $h1_fold } ) {
  4238. $h2_fold = $sync->{f1f2auto}{ $h1_fold } ;
  4239. ( $debug or $sync->{debugfolders} ) and myprint( "automap [$h1_fold] -> [$h2_fold]\n" ) ;
  4240. return( $h2_fold ) ;
  4241. }
  4242. $h2_fold = prefix_seperator_invertion( $h1_fold ) ;
  4243. $h2_fold = regextrans2( $h2_fold ) ;
  4244. return( $h2_fold ) ;
  4245. }
  4246. sub prefix_seperator_invertion {
  4247. my ( $h1_fold ) = @_ ;
  4248. my ( $h2_fold ) ;
  4249. # first we remove the prefix
  4250. $h1_fold =~ s/^\Q$h1_prefix\E//x ;
  4251. ( $debug or $sync->{debugfolders} ) and myprint( "removed host1 prefix: [$h1_fold]\n" ) ;
  4252. $h2_fold = separator_invert( $h1_fold, $h1_sep, $h2_sep ) ;
  4253. ( $debug or $sync->{debugfolders} ) and myprint( "inverted separators: [$h2_fold]\n" ) ;
  4254. # Adding the prefix supplied by namespace or the --prefix2 option
  4255. $h2_fold = $h2_prefix . $h2_fold
  4256. unless( ( $h2_prefix eq 'INBOX' . $h2_sep ) and ( $h2_fold =~ m/^INBOX$/xi ) ) ;
  4257. ( $debug or $sync->{debugfolders} ) and myprint( "added host2 prefix: [$h2_fold]\n" ) ;
  4258. return( $h2_fold ) ;
  4259. }
  4260. sub regextrans2 {
  4261. my( $h2_fold ) = @_ ;
  4262. # Transforming the folder name by the --regextrans2 option(s)
  4263. foreach my $regextrans2 ( @regextrans2 ) {
  4264. my $h2_fold_before = $h2_fold ;
  4265. my $ret = eval "\$h2_fold =~ $regextrans2 ; 1 " ;
  4266. ( $debug or $sync->{debugfolders} ) and myprint( "[$h2_fold_before] -> [$h2_fold] using regextrans2 [$regextrans2]\n" ) ;
  4267. if ( not ( defined $ret ) or $EVAL_ERROR ) {
  4268. die_clean( "error: eval regextrans2 '$regextrans2': $EVAL_ERROR\n" ) ;
  4269. }
  4270. }
  4271. return( $h2_fold ) ;
  4272. }
  4273. sub tests_decompose_regex {
  4274. note( 'Entering tests_decompose_regex()' ) ;
  4275. ok( 1, 'decompose_regex 1' ) ;
  4276. ok( 0 == compare_lists( [ q{}, q{} ], [ decompose_regex( q{} ) ] ), 'decompose_regex empty string' ) ;
  4277. ok( 0 == compare_lists( [ '.*', 'lala' ], [ decompose_regex( 's/.*/lala/' ) ] ), 'decompose_regex s/.*/lala/' ) ;
  4278. note( 'Leaving tests_decompose_regex()' ) ;
  4279. return ;
  4280. }
  4281. sub decompose_regex {
  4282. my $regex = shift ;
  4283. my( $left_part, $right_part ) ;
  4284. ( $left_part, $right_part ) = $regex =~ m{^s/((?:[^/]|\\/)+)/((?:[^/]|\\/)+)/}x;
  4285. return( q{}, q{} ) if not $left_part ;
  4286. return( $left_part, $right_part ) ;
  4287. }
  4288. sub foldersizes {
  4289. my ( $side, $imap, $search_cmd, $abletosearch, @folders ) = @_ ;
  4290. my $total_size = 0 ;
  4291. my $total_nb = 0 ;
  4292. my $biggest_in_all = 0 ;
  4293. my $nb_folders = scalar @folders ;
  4294. my $ct_folders = 0 ; # folder counter.
  4295. myprint( "++++ Calculating sizes of $nb_folders folders on $side\n" ) ;
  4296. foreach my $folder ( @folders ) {
  4297. my $stot = 0 ;
  4298. my $nb_msgs = 0 ;
  4299. $ct_folders++ ;
  4300. myprintf( "$side folder %7s %-35s", "$ct_folders/$nb_folders", jux_utf8( $folder ) ) ;
  4301. if ( 'Host2' eq $side and not exists $h2_folders_all_UPPER{ uc $folder } ) {
  4302. myprint( " does not exist yet\n") ;
  4303. next ;
  4304. }
  4305. if ( 'Host1' eq $side and not exists $h1_folders_all{ $folder } ) {
  4306. myprint( " does not exist\n" ) ;
  4307. next ;
  4308. }
  4309. last if $imap->IsUnconnected( ) ;
  4310. # FTGate is RFC buggy with EXAMINE it does not act as SELECT
  4311. #unless ( $imap->examine( $folder ) ) {
  4312. unless ( $imap->select( $folder ) ) {
  4313. my $error = join q{},
  4314. "$side Folder $folder: Could not select: ",
  4315. $imap->LastError, "\n" ;
  4316. errors_incr( $sync, $error ) ;
  4317. next ;
  4318. }
  4319. last if $imap->IsUnconnected( ) ;
  4320. my $hash_ref = { } ;
  4321. my @msgs = select_msgs( $imap, undef, $search_cmd, $abletosearch, $folder ) ;
  4322. $nb_msgs = scalar @msgs ;
  4323. my $biggest_in_folder = 0 ;
  4324. @{ $hash_ref }{ @msgs } = ( undef ) if @msgs ;
  4325. last if $imap->IsUnconnected( ) ;
  4326. if ( $nb_msgs > 0 and @msgs ) {
  4327. if ( $abletosearch ) {
  4328. if ( ! $imap->fetch_hash( \@msgs, 'RFC822.SIZE', $hash_ref) ) {
  4329. my $error = "$side failure with fetch_hash: $EVAL_ERROR" ;
  4330. errors_incr( $sync, $error ) ;
  4331. return ;
  4332. }
  4333. }else{
  4334. my $uidnext = $imap->uidnext( $folder ) || $uidnext_default ;
  4335. my $fetch_hash_uids = $fetch_hash_set || "1:$uidnext" ;
  4336. if ( ! $imap->fetch_hash( $fetch_hash_uids, 'RFC822.SIZE', $hash_ref ) ) {
  4337. my $error = "$side failure with fetch_hash: $EVAL_ERROR" ;
  4338. errors_incr( $sync, $error ) ;
  4339. return ;
  4340. }
  4341. }
  4342. for ( keys %{ $hash_ref } ) {
  4343. my $size = $hash_ref->{ $_ }->{ 'RFC822.SIZE' } ;
  4344. $stot += $size ;
  4345. $biggest_in_folder = max( $biggest_in_folder, $size ) ;
  4346. }
  4347. }
  4348. myprintf( ' Size: %9s', $stot ) ;
  4349. myprintf( ' Messages: %5s', $nb_msgs ) ;
  4350. myprintf( " Biggest: %9s\n", $biggest_in_folder ) ;
  4351. $total_size += $stot ;
  4352. $total_nb += $nb_msgs ;
  4353. $biggest_in_all = max( $biggest_in_all, $biggest_in_folder ) ;
  4354. }
  4355. myprintf( "%s Nb folders: %11s folders\n", $side, $nb_folders ) ;
  4356. myprintf( "%s Nb messages: %11s messages\n", $side, $total_nb ) ;
  4357. myprintf( "%s Total size: %11s bytes (%s)\n", $side, $total_size, bytes_display_string( $total_size ) ) ;
  4358. myprintf( "%s Biggest message: %11s bytes (%s)\n", $side, $biggest_in_all, bytes_display_string( $biggest_in_all ) ) ;
  4359. myprintf( "%s Time spent: %11.1f seconds\n", $side, timenext( ) ) ;
  4360. return( $total_nb, $total_size ) ;
  4361. }
  4362. sub timenext {
  4363. my ( $timenow, $timediff ) ;
  4364. # $timebefore is global, beurk !
  4365. $timenow = time ;
  4366. $timediff = $timenow - $timebefore ;
  4367. $timebefore = $timenow ;
  4368. return( $timediff ) ;
  4369. }
  4370. sub timesince {
  4371. my $timeinit = shift || 0 ;
  4372. my ( $timenow, $timediff ) ;
  4373. $timenow = time ;
  4374. $timediff = $timenow - $timeinit ;
  4375. # Often used in a division so no 0
  4376. return( max( 1, $timediff) ) ;
  4377. }
  4378. sub tests_flags_regex {
  4379. note( 'Entering tests_flags_regex()' ) ;
  4380. ok( q{} eq flags_regex(q{} ), 'flags_regex, null string q{}' ) ;
  4381. ok( q{\Seen NonJunk $Spam} eq flags_regex( q{\Seen NonJunk $Spam} ), q{flags_regex, nothing to do} ) ;
  4382. @regexflag = ('I am BAD' ) ;
  4383. ok( not ( defined flags_regex( q{} ) ), 'flags_regex, bad regex' ) ;
  4384. @regexflag = ( 's/NonJunk//g' ) ;
  4385. ok( q{\Seen $Spam} eq flags_regex( q{\Seen NonJunk $Spam} ), q{flags_regex, remove NonJunk: 's/NonJunk//g'} ) ;
  4386. @regexflag = ( q's/\$Spam//g' ) ;
  4387. ok( q{\Seen NonJunk } eq flags_regex( q{\Seen NonJunk $Spam} ), q{flags_regex, remove $Spam: 's/\$Spam//g'} ) ;
  4388. @regexflag = ( 's/\\\\Seen//g' ) ;
  4389. ok( q{ NonJunk $Spam} eq flags_regex( q{\Seen NonJunk $Spam} ), q{flags_regex, remove \Seen: 's/\\\\\\\\Seen//g'} ) ;
  4390. @regexflag = ( 's/(\s|^)[^\\\\]\w+//g' ) ;
  4391. ok( q{\Seen \Middle \End} eq flags_regex( q{\Seen NonJunk \Middle $Spam \End} ), q{flags_regex: only \word among \Seen NonJunk \Middle $Spam \End} ) ;
  4392. ok( q{ \Seen \Middle \End1} eq flags_regex( q{Begin \Seen NonJunk \Middle $Spam \End1 End} ),
  4393. q{flags_regex: only \word among Begin \Seen NonJunk \Middle $Spam \End1 End} ) ;
  4394. @regexflag = ( q{s/.*?(Keep1|Keep2|Keep3)/$1 /g} ) ;
  4395. ok( 'Keep1 Keep2 ReB' eq flags_regex('ReA Keep1 REM Keep2 ReB'), 'Keep only regex' ) ;
  4396. ok( 'Keep1 Keep2 ' eq flags_regex( 'REM REM Keep1 Keep2'), 'Keep only regex' ) ;
  4397. ok( 'Keep1 Keep2 ' eq flags_regex( 'Keep1 REM REM Keep2'), 'Keep only regex' ) ;
  4398. ok( 'Keep1 Keep2 ' eq flags_regex( 'REM Keep1 REM REM Keep2'), 'Keep only regex' ) ;
  4399. ok( 'Keep1 Keep2 ' eq flags_regex( 'Keep1 Keep2'), 'Keep only regex' ) ;
  4400. ok( 'Keep1 ' eq flags_regex( 'REM Keep1'), 'Keep only regex' ) ;
  4401. @regexflag = ( q{s/(Keep1|Keep2|Keep3) (?!(Keep1|Keep2|Keep3)).*/$1 /g} ) ;
  4402. ok( 'Keep1 Keep2 ' eq flags_regex( 'Keep1 Keep2 ReB'), 'Keep only regex' ) ;
  4403. ok( 'Keep1 Keep2 ' eq flags_regex( 'Keep1 Keep2 REM REM REM'), 'Keep only regex' ) ;
  4404. ok( 'Keep2 ' eq flags_regex('Keep2 REM REM REM'), 'Keep only regex' ) ;
  4405. @regexflag = ( q{s/.*?(Keep1|Keep2|Keep3)/$1 /g},
  4406. 's/(Keep1|Keep2|Keep3) (?!(Keep1|Keep2|Keep3)).*/$1 /g' ) ;
  4407. ok( 'Keep1 Keep2 ' eq flags_regex('REM Keep1 REM Keep2 REM'), 'Keep only regex' ) ;
  4408. ok( 'Keep1 Keep2 ' eq flags_regex('Keep1 REM Keep2 REM'), 'Keep only regex' ) ;
  4409. ok( 'Keep1 Keep2 ' eq flags_regex('REM Keep1 Keep2 REM'), 'Keep only regex' ) ;
  4410. ok( 'Keep1 Keep2 ' eq flags_regex('REM Keep1 REM Keep2'), 'Keep only regex' ) ;
  4411. ok( 'Keep1 Keep2 Keep3 ' eq flags_regex('REM Keep1 REM Keep2 REM REM Keep3 REM'), 'Keep only regex' ) ;
  4412. ok( 'Keep1 ' eq flags_regex('REM REM Keep1 REM REM REM '), 'Keep only regex' ) ;
  4413. ok( 'Keep1 Keep3 ' eq flags_regex('RE1 Keep1 RE2 Keep3 RE3 RE4 RE5 '), 'Keep only regex' ) ;
  4414. @regexflag = ( 's/(.*)/$1 jrdH8u/' ) ;
  4415. ok('REM REM REM REM REM jrdH8u' eq flags_regex('REM REM REM REM REM'), q{Keep only regex 's/(.*)/\$1 jrdH8u/'} ) ;
  4416. @regexflag = ('s/jrdH8u *//');
  4417. ok('REM REM REM REM REM ' eq flags_regex('REM REM REM REM REM jrdH8u'), q{Keep only regex s/jrdH8u *//} ) ;
  4418. @regexflag = (
  4419. 's/(.*)/$1 jrdH8u/',
  4420. 's/.*?(Keep1|Keep2|Keep3|jrdH8u)/$1 /g',
  4421. 's/(Keep1|Keep2|Keep3|jrdH8u) (?!(Keep1|Keep2|Keep3|jrdH8u)).*/$1 /g',
  4422. 's/jrdH8u *//'
  4423. );
  4424. ok('Keep1 Keep2 ' eq flags_regex('REM Keep1 REM Keep2 REM'), q{Keep only regex 'REM Keep1 REM Keep2 REM'} ) ;
  4425. ok('Keep1 Keep2 ' eq flags_regex('Keep1 REM Keep2 REM'), 'Keep only regex');
  4426. ok('Keep1 Keep2 ' eq flags_regex('REM Keep1 Keep2 REM'), 'Keep only regex');
  4427. ok('Keep1 Keep2 ' eq flags_regex('REM Keep1 REM Keep2'), 'Keep only regex');
  4428. ok('Keep1 Keep2 Keep3 ' eq flags_regex('REM Keep1 REM Keep2 REM REM Keep3 REM'), 'Keep only regex');
  4429. ok('Keep1 ' eq flags_regex('REM REM Keep1 REM REM REM '), 'Keep only regex');
  4430. ok('Keep1 Keep3 ' eq flags_regex('RE1 Keep1 RE2 Keep3 RE3 RE4 RE5 '), 'Keep only regex');
  4431. ok(q{} eq flags_regex('REM REM REM REM REM'), 'Keep only regex');
  4432. @regexflag = (
  4433. 's/(.*)/$1 jrdH8u/',
  4434. 's/.*?(\\\\Seen|\\\\Answered|\\\\Flagged|\\\\Deleted|\\\\Draft|jrdH8u)/$1 /g',
  4435. 's/(\\\\Seen|\\\\Answered|\\\\Flagged|\\\\Deleted|\\\\Draft|jrdH8u) (?!(\\\\Seen|\\\\Answered|\\\\Flagged|\\\\Deleted|\\\\Draft|jrdH8u)).*/$1 /g',
  4436. 's/jrdH8u *//'
  4437. );
  4438. ok('\\Deleted \\Answered '
  4439. eq flags_regex('Blabla $Junk \\Deleted machin \\Answered truc'), 'Keep only regex: Exchange case' ) ;
  4440. ok( q{} eq flags_regex( q{} ), 'Keep only regex: Exchange case, null string' ) ;
  4441. ok( q{}
  4442. eq flags_regex('Blabla $Junk machin truc'), 'Keep only regex: Exchange case, no accepted flags' ) ;
  4443. ok( '\\Deleted \\Answered \\Draft \\Flagged '
  4444. eq flags_regex('\\Deleted \\Answered \\Draft \\Flagged '), 'Keep only regex: Exchange case' ) ;
  4445. @regexflag = (
  4446. 's/.*?(?:(\\\\(?:Answered|Flagged|Deleted|Seen|Draft)\s?)|$)/defined($1)?$1:q()/eg'
  4447. );
  4448. ok( '\\Deleted \\Answered '
  4449. eq flags_regex('Blabla \$Junk \\Deleted machin \\Answered truc'),
  4450. 'Keep only regex: Exchange case (Phil)' ) ;
  4451. ok( q{} eq flags_regex( q{} ), 'Keep only regex: Exchange case, null string (Phil)' ) ;
  4452. ok( q{}
  4453. eq flags_regex('Blabla $Junk machin truc'),
  4454. 'Keep only regex: Exchange case, no accepted flags (Phil)' ) ;
  4455. ok('\\Deleted \\Answered \\Draft \\Flagged '
  4456. eq flags_regex('\\Deleted \\Answered \\Draft \\Flagged '),
  4457. 'Keep only regex: Exchange case (Phil)' ) ;
  4458. note( 'Leaving tests_flags_regex()' ) ;
  4459. return ;
  4460. }
  4461. sub flags_regex {
  4462. my ( $h1_flags ) = @_ ;
  4463. foreach my $regexflag ( @regexflag ) {
  4464. my $h1_flags_orig = $h1_flags ;
  4465. $debugflags and myprint( "eval \$h1_flags =~ $regexflag\n" ) ;
  4466. my $ret = eval "\$h1_flags =~ $regexflag ; 1 " ;
  4467. $debugflags and myprint( "regexflag $regexflag [$h1_flags_orig] -> [$h1_flags]\n" ) ;
  4468. if( not ( defined $ret ) or $EVAL_ERROR ) {
  4469. myprint( "Error: eval regexflag '$regexflag': $EVAL_ERROR\n" ) ;
  4470. return( undef ) ;
  4471. }
  4472. }
  4473. return( $h1_flags ) ;
  4474. }
  4475. sub acls_sync {
  4476. my($h1_fold, $h2_fold) = @_ ;
  4477. if ( $syncacls ) {
  4478. my $h1_hash = $imap1->getacl($h1_fold)
  4479. or myprint( "Could not getacl for $h1_fold: $EVAL_ERROR\n" ) ;
  4480. my $h2_hash = $imap2->getacl($h2_fold)
  4481. or myprint( "Could not getacl for $h2_fold: $EVAL_ERROR\n" ) ;
  4482. my %users = map { ($_, 1) } ( keys %{ $h1_hash} , keys %{ $h2_hash } ) ;
  4483. foreach my $user (sort keys %users ) {
  4484. my $acl = $h1_hash->{$user} || 'none' ;
  4485. myprint( "acl $user: [$acl]\n" ) ;
  4486. next if ($h1_hash->{$user} && $h2_hash->{$user} &&
  4487. $h1_hash->{$user} eq $h2_hash->{$user});
  4488. unless ($sync->{dry}) {
  4489. myprint( "setting acl $h2_fold $user $acl\n" ) ;
  4490. $imap2->setacl($h2_fold, $user, $acl)
  4491. or myprint( "Could not set acl: $EVAL_ERROR\n" ) ;
  4492. }
  4493. }
  4494. }
  4495. return ;
  4496. }
  4497. sub tests_permanentflags {
  4498. note( 'Entering tests_permanentflags()' ) ;
  4499. my $string;
  4500. ok(q{} eq permanentflags(' * OK [PERMANENTFLAGS (\* \Draft \Answered)] Limited'),
  4501. 'permanentflags \*');
  4502. ok('\Draft \Answered' eq permanentflags(' * OK [PERMANENTFLAGS (\Draft \Answered)] Limited'),
  4503. 'permanentflags \Draft \Answered');
  4504. ok('\Draft \Answered'
  4505. eq permanentflags('Blabla',
  4506. ' * OK [PERMANENTFLAGS (\Draft \Answered)] Limited',
  4507. 'Blabla'),
  4508. 'permanentflags \Draft \Answered'
  4509. );
  4510. ok(q{} eq permanentflags('Blabla'), 'permanentflags nothing');
  4511. note( 'Leaving tests_permanentflags()' ) ;
  4512. return ;
  4513. }
  4514. sub permanentflags {
  4515. my @lines = @_ ;
  4516. foreach my $line (@lines) {
  4517. if ( $line =~ m{\[PERMANENTFLAGS\s\(([^)]+?)\)\]}x ) {
  4518. ( $debugflags or $debug ) and myprint( "permanentflags: $line" ) ;
  4519. my $permanentflags = $1 ;
  4520. if ( $permanentflags =~ m{\\\*}x ) {
  4521. $permanentflags = q{} ;
  4522. }
  4523. return($permanentflags) ;
  4524. } ;
  4525. }
  4526. return( q{} ) ;
  4527. }
  4528. sub tests_flags_filter {
  4529. note( 'Entering tests_flags_filter()' ) ;
  4530. ok( '\Seen' eq flags_filter('\Seen', '\Draft \Seen \Answered'), 'flags_filter ' );
  4531. ok( q{} eq flags_filter('\Seen', '\Draft \Answered'), 'flags_filter ' );
  4532. ok( '\Seen' eq flags_filter('\Seen', '\Seen'), 'flags_filter ' );
  4533. ok( '\Seen' eq flags_filter('\Seen', ' \Seen '), 'flags_filter ' );
  4534. ok( '\Seen \Draft'
  4535. eq flags_filter('\Seen \Draft', '\Draft \Seen \Answered'), 'flags_filter ' );
  4536. ok( '\Seen \Draft'
  4537. eq flags_filter('\Seen \Draft', ' \Draft \Seen \Answered '), 'flags_filter ' );
  4538. note( 'Leaving tests_flags_filter()' ) ;
  4539. return ;
  4540. }
  4541. sub flags_filter {
  4542. my( $flags, $allowed_flags ) = @_ ;
  4543. my @flags = split /\s+/x, $flags ;
  4544. my %allowed_flags = map { $_ => 1 } split q{ }, $allowed_flags ;
  4545. my @flags_out = map { exists $allowed_flags{$_} ? $_ : () } @flags ;
  4546. my $flags_out = join q{ }, @flags_out ;
  4547. return( $flags_out ) ;
  4548. }
  4549. sub flagscase {
  4550. my $flags = shift ;
  4551. my @flags = split /\s+/x, $flags ;
  4552. my %rfc_flags = map { $_ => 1 } split q{ }, '\Answered \Flagged \Deleted \Seen \Draft' ;
  4553. my @flags_out = map { exists $rfc_flags{ ucsecond( lc $_ ) } ? ucsecond( lc $_ ) : $_ } @flags ;
  4554. my $flags_out = join q{ }, @flags_out ;
  4555. return( $flags_out ) ;
  4556. }
  4557. sub tests_flagscase {
  4558. note( 'Entering tests_flagscase()' ) ;
  4559. ok( '\Seen' eq flagscase( '\Seen' ), 'flagscase: \Seen -> \Seen' ) ;
  4560. ok( '\Seen' eq flagscase( '\SEEN' ), 'flagscase: \SEEN -> \Seen' ) ;
  4561. ok( '\Seen \Draft' eq flagscase( '\SEEN \DRAFT' ), 'flagscase: \SEEN \DRAFT -> \Seen \Draft' ) ;
  4562. ok( '\Draft \Seen' eq flagscase( '\DRAFT \SEEN' ), 'flagscase: \DRAFT \SEEN -> \Draft \Seen' ) ;
  4563. ok( '\Draft LALA \Seen' eq flagscase( '\DRAFT LALA \SEEN' ), 'flagscase: \DRAFT LALA \SEEN -> \Draft LALA \Seen' ) ;
  4564. ok( '\Draft lala \Seen' eq flagscase( '\DRAFT lala \SEEN' ), 'flagscase: \DRAFT lala \SEEN -> \Draft lala \Seen' ) ;
  4565. note( 'Leaving tests_flagscase()' ) ;
  4566. return ;
  4567. }
  4568. sub ucsecond {
  4569. my $string = shift ;
  4570. my $output ;
  4571. return( $string ) if ( 1 >= length $string ) ;
  4572. $output = ( substr( $string, 0, 1) ) . ( uc substr $string, 1, 1 ) . ( substr $string, 2 ) ;
  4573. #myprint( "UUU $string -> $output\n" ) ;
  4574. return( $output ) ;
  4575. }
  4576. sub tests_ucsecond {
  4577. note( 'Entering tests_ucsecond()' ) ;
  4578. ok( 'aBcde' eq ucsecond( 'abcde' ), 'ucsecond: abcde -> aBcde' ) ;
  4579. ok( 'ABCDE' eq ucsecond( 'ABCDE' ), 'ucsecond: ABCDE -> ABCDE' ) ;
  4580. ok( 'ABCDE' eq ucsecond( 'AbCDE' ), 'ucsecond: AbCDE -> ABCDE' ) ;
  4581. ok( 'ABCde' eq ucsecond( 'AbCde' ), 'ucsecond: AbCde -> ABCde' ) ;
  4582. ok( 'A' eq ucsecond( 'A' ), 'ucsecond: A -> A' ) ;
  4583. ok( 'AB' eq ucsecond( 'Ab' ), 'ucsecond: Ab -> AB' ) ;
  4584. ok( '\B' eq ucsecond( '\b' ), 'ucsecond: \b -> \B' ) ;
  4585. ok( '\Bcde' eq ucsecond( '\bcde' ), 'ucsecond: \bcde -> \Bcde' ) ;
  4586. note( 'Leaving tests_ucsecond()' ) ;
  4587. return ;
  4588. }
  4589. sub select_msgs {
  4590. my ( $imap, $msgs_all_hash_ref, $search_cmd, $abletosearch, $folder ) = @_ ;
  4591. my ( @msgs ) ;
  4592. if ( $abletosearch ) {
  4593. @msgs = select_msgs_by_search( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) ;
  4594. }else{
  4595. @msgs = select_msgs_by_fetch( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) ;
  4596. }
  4597. return( @msgs ) ;
  4598. }
  4599. sub select_msgs_by_search {
  4600. my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ;
  4601. my ( @msgs, @msgs_all ) ;
  4602. # Need to have the whole list in msgs_all_hash_ref
  4603. # without calling messages() several times.
  4604. # Need all messages list to avoid deleting useful cache part
  4605. # in case of --search or --minage or --maxage
  4606. if ( ( defined $msgs_all_hash_ref and $usecache )
  4607. or ( not defined $maxage and not defined $minage and not defined $search_cmd )
  4608. ) {
  4609. $debugdev and myprint( "Calling messages()\n" ) ;
  4610. @msgs_all = $imap->messages( ) ;
  4611. return if ( $#msgs_all == 0 && !defined $msgs_all[0] ) ;
  4612. if ( defined $msgs_all_hash_ref ) {
  4613. @{ $msgs_all_hash_ref }{ @msgs_all } = () ;
  4614. }
  4615. # return all messages
  4616. if ( not defined $maxage and not defined $minage and not defined $search_cmd ) {
  4617. return( @msgs_all ) ;
  4618. }
  4619. }
  4620. if ( defined $search_cmd ) {
  4621. @msgs = $imap->search( $search_cmd ) ;
  4622. return( @msgs ) ;
  4623. }
  4624. # we are here only if $maxage or $minage is defined
  4625. @msgs = select_msgs_by_age( $imap ) ;
  4626. return( @msgs );
  4627. }
  4628. sub select_msgs_by_fetch {
  4629. my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ;
  4630. my ( @msgs, @msgs_all, %fetch ) ;
  4631. # Need to have the whole list in msgs_all_hash_ref
  4632. # without calling messages() several times.
  4633. # Need all messages list to avoid deleting useful cache part
  4634. # in case of --search or --minage or --maxage
  4635. $debugdev and myprint( "Calling fetch_hash()\n" ) ;
  4636. my $uidnext = $imap->uidnext( $folder ) || $uidnext_default ;
  4637. my $fetch_hash_uids = $fetch_hash_set || "1:$uidnext" ;
  4638. %fetch = %{$imap->fetch_hash( $fetch_hash_uids, 'INTERNALDATE' ) } ;
  4639. @msgs_all = sort { $a <=> $b } keys %fetch ;
  4640. $debugdev and myprint( "Done fetch_hash()\n" ) ;
  4641. return if ( $#msgs_all == 0 && !defined $msgs_all[0] ) ;
  4642. if ( defined $msgs_all_hash_ref ) {
  4643. @{ $msgs_all_hash_ref }{ @msgs_all } = () ;
  4644. }
  4645. # return all messages
  4646. if ( not defined $maxage and not defined $minage and not defined $search_cmd ) {
  4647. return( @msgs_all ) ;
  4648. }
  4649. if ( defined $search_cmd ) {
  4650. myprint( "Warning: strange to see --search with --noabletosearch, an error can happen\n" ) ;
  4651. @msgs = $imap->search( $search_cmd ) ;
  4652. return( @msgs ) ;
  4653. }
  4654. # we are here only if $maxage or $minage is defined
  4655. my( @max, @min, $maxage_epoch, $minage_epoch ) ;
  4656. if ( defined $maxage ) { $maxage_epoch = $timestart_int - $NB_SECONDS_IN_A_DAY * $maxage ; }
  4657. if ( defined $minage ) { $minage_epoch = $timestart_int - $NB_SECONDS_IN_A_DAY * $minage ; }
  4658. foreach my $msg ( @msgs_all ) {
  4659. my $idate = $fetch{ $msg }->{'INTERNALDATE'} ;
  4660. #myprint( "$idate\n" ) ;
  4661. if ( defined $maxage and ( epoch( $idate ) >= $maxage_epoch ) ) {
  4662. push @max, $msg ;
  4663. }
  4664. if ( defined $minage and ( epoch( $idate ) <= $minage_epoch ) ) {
  4665. push @min, $msg ;
  4666. }
  4667. }
  4668. @msgs = msgs_from_maxmin( \@max, \@min ) ;
  4669. return( @msgs ) ;
  4670. }
  4671. sub select_msgs_by_age {
  4672. my( $imap ) = @_ ;
  4673. my( @max, @min, @msgs, @inter, @union ) ;
  4674. if ( defined $maxage ) {
  4675. @max = $imap->sentsince( $timestart_int - $NB_SECONDS_IN_A_DAY * $maxage ) ;
  4676. }
  4677. if ( defined $minage ) {
  4678. @min = $imap->sentbefore( $timestart_int - $NB_SECONDS_IN_A_DAY * $minage ) ;
  4679. }
  4680. @msgs = msgs_from_maxmin( \@max, \@min ) ;
  4681. return( @msgs ) ;
  4682. }
  4683. sub msgs_from_maxmin {
  4684. my( $max_ref, $min_ref ) = @_ ;
  4685. my( @max, @min, @msgs, @inter, @union ) ;
  4686. @max = @{ $max_ref } ;
  4687. @min = @{ $min_ref } ;
  4688. SWITCH: {
  4689. unless( defined $minage ) { @msgs = @max ; last SWITCH } ;
  4690. unless( defined $maxage ) { @msgs = @min ; last SWITCH } ;
  4691. my ( %union, %inter ) ;
  4692. foreach my $m ( @min, @max ) { $union{ $m }++ && $inter{ $m }++ }
  4693. @inter = sort { $a <=> $b } keys %inter ;
  4694. @union = sort { $a <=> $b } keys %union ;
  4695. # normal case
  4696. if ( $minage <= $maxage ) { @msgs = @inter ; last SWITCH } ;
  4697. # just exclude messages between
  4698. if ( $minage > $maxage ) { @msgs = @union ; last SWITCH } ;
  4699. }
  4700. return( @msgs ) ;
  4701. }
  4702. sub tests_msgs_from_maxmin {
  4703. note( 'Entering tests_msgs_from_maxmin()' ) ;
  4704. my @msgs ;
  4705. $maxage = $NUMBER_200 ;
  4706. @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
  4707. ok( 0 == compare_lists( [ '1', '2' ], \@msgs ), 'msgs_from_maxmin: maxage++' ) ;
  4708. $minage = $NUMBER_100 ;
  4709. @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
  4710. ok( 0 == compare_lists( [ '2' ], \@msgs ), 'msgs_from_maxmin: -maxage++minage-' ) ;
  4711. $minage = $NUMBER_300 ;
  4712. @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
  4713. ok( 0 == compare_lists( [ '1', '2', '3' ], \@msgs ), 'msgs_from_maxmin: ++maxage-minage++' ) ;
  4714. $maxage = undef ;
  4715. @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
  4716. ok( 0 == compare_lists( [ '2', '3' ], \@msgs ), 'msgs_from_maxmin: ++minage-' ) ;
  4717. note( 'Leaving tests_msgs_from_maxmin()' ) ;
  4718. return ;
  4719. }
  4720. sub lastuid {
  4721. my $imap = shift ;
  4722. my $folder = shift ;
  4723. my $lastuid_guess = shift ;
  4724. my $lastuid ;
  4725. # rfc3501: The only reliable way to identify recent messages is to
  4726. # look at message flags to see which have the \Recent flag
  4727. # set, or to do a SEARCH RECENT.
  4728. # SEARCH RECENT doesn't work this way on courrier.
  4729. my @recent_messages ;
  4730. # SEARCH RECENT for each transfer can be expensive with a big folder
  4731. # Call commented for now
  4732. #@recent_messages = $imap->recent( ) ;
  4733. #myprint( "Recent: @recent_messages\n" ) ;
  4734. my $max_recent ;
  4735. $max_recent = max( @recent_messages ) ;
  4736. if ( defined $max_recent and ($lastuid_guess <= $max_recent ) ) {
  4737. $lastuid = $max_recent ;
  4738. }else{
  4739. $lastuid = $lastuid_guess
  4740. }
  4741. return( $lastuid ) ;
  4742. }
  4743. sub size_filtered {
  4744. my( $h1_size, $h1_msg, $h1_fold, $h2_fold ) = @_ ;
  4745. $h1_size = 0 if ( ! $h1_size ) ; # null if empty or undef
  4746. if (defined $maxsize and $h1_size > $maxsize) {
  4747. myprint( "msg $h1_fold/$h1_msg skipped ($h1_size exceeds maxsize limit $maxsize bytes)\n" ) ;
  4748. $total_bytes_skipped += $h1_size;
  4749. $nb_msg_skipped += 1;
  4750. return( 1 ) ;
  4751. }
  4752. if (defined $minsize and $h1_size <= $minsize) {
  4753. myprint( "msg $h1_fold/$h1_msg skipped ($h1_size smaller than minsize $minsize bytes)\n" ) ;
  4754. $total_bytes_skipped += $h1_size;
  4755. $nb_msg_skipped += 1;
  4756. return( 1 ) ;
  4757. }
  4758. return( 0 ) ;
  4759. }
  4760. sub message_exists {
  4761. my( $imap, $msg ) = @_ ;
  4762. return( 1 ) if not $imap->Uid( ) ;
  4763. my $search_uid ;
  4764. ( $search_uid ) = $imap->search( "UID $msg" ) ;
  4765. #myprint( "$search ? $msg\n" ) ;
  4766. return( 1 ) if ( $search_uid eq $msg ) ;
  4767. return( 0 ) ;
  4768. }
  4769. sub copy_message {
  4770. # copy
  4771. my ( $mysync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) = @_ ;
  4772. ( $debug or $mysync->{dry}) and myprint( "msg $h1_fold/$h1_msg copying to $h2_fold $mysync->{dry_message}\n" ) ;
  4773. my $h1_size = $h1_fir_ref->{$h1_msg}->{'RFC822.SIZE'} || 0 ;
  4774. my $h1_flags = $h1_fir_ref->{$h1_msg}->{'FLAGS'} || q{} ;
  4775. my $h1_idate = $h1_fir_ref->{$h1_msg}->{'INTERNALDATE'} || q{} ;
  4776. if ( size_filtered( $h1_size, $h1_msg, $h1_fold, $h2_fold ) ) {
  4777. $h1_nb_msg_processed +=1 ;
  4778. return ;
  4779. }
  4780. debugsleep( $mysync ) ;
  4781. myprint( "- msg $h1_fold/$h1_msg S[$h1_size] F[$h1_flags] I[$h1_idate] has RFC822.SIZE null!\n" ) if ( ! $h1_size ) ;
  4782. if ( $checkmessageexists and not message_exists( $imap1, $h1_msg ) ) {
  4783. $total_bytes_skipped += $h1_size;
  4784. $nb_msg_skipped += 1;
  4785. $h1_nb_msg_processed +=1 ;
  4786. return ;
  4787. }
  4788. if ( $mysync->{debugmemory} ) {
  4789. myprintf("C1: Memory consumption: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI) ;
  4790. }
  4791. my ( $string, $string_len ) ;
  4792. ( $string_len ) = message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, \$string ) ;
  4793. if ( $mysync->{debugmemory} ) {
  4794. myprintf("C2: Memory consumption: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI) ;
  4795. }
  4796. # not defined or empty $string
  4797. if ( ( not $string ) or ( not $string_len ) ) {
  4798. myprint( "- msg $h1_fold/$h1_msg skipped.\n" ) ;
  4799. $total_bytes_skipped += $h1_size;
  4800. $nb_msg_skipped += 1;
  4801. $h1_nb_msg_processed += 1 ;
  4802. return ;
  4803. }
  4804. # Lines too long (or not enough) => do no copy or fix
  4805. if ( ( defined $maxlinelength ) or ( defined $minmaxlinelength ) ) {
  4806. $string = linelengthstuff( $string, $h1_fold, $h1_msg, $string_len, $h1_size, $h1_flags, $h1_idate ) ;
  4807. if ( not defined $string ) {
  4808. $h1_nb_msg_processed +=1 ;
  4809. $total_bytes_skipped += $h1_size ;
  4810. $nb_msg_skipped += 1 ;
  4811. return ;
  4812. }
  4813. }
  4814. my $h1_date = date_for_host2( $h1_msg, $h1_idate ) ;
  4815. ( $debug or $debugflags ) and
  4816. myprint( "Host1 flags init msg $h1_fold/$h1_msg date [$h1_date] flags [$h1_flags] size [$h1_size]\n" ) ;
  4817. $h1_flags = flags_for_host2( $h1_flags, $permanentflags2 ) ;
  4818. ( $debug or $debugflags ) and
  4819. myprint( "Host1 flags filt msg $h1_fold/$h1_msg date [$h1_date] flags [$h1_flags] size [$h1_size]\n" ) ;
  4820. $h1_date = undef if ($h1_date eq q{});
  4821. my $new_id = append_message_on_host2( \$string, $h1_fold, $h1_msg, $string_len, $h2_fold, $h1_size, $h1_flags, $h1_date, $cache_dir ) ;
  4822. if ( $new_id and $syncflagsaftercopy ) {
  4823. sync_flags_after_copy( $h1_fold, $h1_msg, $h1_flags, $h2_fold, $new_id, $permanentflags2 ) ;
  4824. }
  4825. if ( $mysync->{debugmemory} ) {
  4826. myprintf("C3: Memory consumption: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI) ;
  4827. }
  4828. return $new_id ;
  4829. }
  4830. sub linelengthstuff {
  4831. my( $string, $h1_fold, $h1_msg, $string_len, $h1_size, $h1_flags, $h1_idate ) = @_ ;
  4832. my $maxlinelength_string = max_line_length( $string ) ;
  4833. $debugmaxlinelength and myprint( "msg $h1_fold/$h1_msg maxlinelength: $maxlinelength_string\n" ) ;
  4834. if ( ( defined $minmaxlinelength ) and ( $maxlinelength_string <= $minmaxlinelength ) ) {
  4835. my $subject = subject( $string ) ;
  4836. $debugdev and myprint( "- msg $h1_fold/$h1_msg skipped S[$h1_size] F[$h1_flags] I[$h1_idate] "
  4837. . "(Subject:[$subject]) (max line length under minmaxlinelength $minmaxlinelength bytes)\n" ) ;
  4838. return ;
  4839. }
  4840. if ( ( defined $maxlinelength ) and ( $maxlinelength_string > $maxlinelength ) ) {
  4841. my $subject = subject( $string ) ;
  4842. if ( $maxlinelengthcmd ) {
  4843. $string = pipemess( $string, $maxlinelengthcmd ) ;
  4844. # string undef means something was bad.
  4845. if ( not ( defined $string ) ) {
  4846. myprint( "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate] "
  4847. . "(Subject:[$subject]) could not be successfully transformed by --maxlinelengthcmd option\n" ) ;
  4848. return ;
  4849. }else{
  4850. return $string ;
  4851. }
  4852. }
  4853. myprint( "- msg $h1_fold/$h1_msg skipped S[$h1_size] F[$h1_flags] I[$h1_idate] "
  4854. . "(Subject:[$subject]) (line length exceeds maxlinelength $maxlinelength bytes)\n" ) ;
  4855. return ;
  4856. }
  4857. return $string ;
  4858. }
  4859. sub message_for_host2 {
  4860. # global variable list:
  4861. # @skipmess
  4862. # @regexmess
  4863. # @pipemess
  4864. # $addheader
  4865. # $debugcontent
  4866. # $debug
  4867. #
  4868. # API current
  4869. #
  4870. # at failure:
  4871. # * return nothing ( will then be undef or () )
  4872. # * $string_ref content is undef or empty
  4873. # at success:
  4874. # * return string length ($string_ref content length)
  4875. # * $string_ref content filled with message
  4876. # API future
  4877. #
  4878. #
  4879. my ( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ) = @_ ;
  4880. # abort when missing a parameter
  4881. if ( (!$sync) or (!$h1_msg) or (!$h1_fold) or (!$h1_size) or (!defined $h1_flags) or (!defined $h1_idate) or (!$h1_fir_ref) or (!$string_ref) ) {
  4882. return ;
  4883. }
  4884. if ( $mysync->{debugmemory} ) {
  4885. myprintf("M1: Memory consumption: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI) ;
  4886. }
  4887. my $imap1 = $mysync->{imap1} ;
  4888. my $string_ok = $imap1->message_to_file( $string_ref, $h1_msg ) ;
  4889. if ( $mysync->{debugmemory} ) {
  4890. myprintf("M2: Memory consumption: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI) ;
  4891. }
  4892. my $string_len = length_ref( $string_ref ) ;
  4893. unless ( defined $string_ok and $string_len ) {
  4894. # undef or 0 length
  4895. my $error = join q{},
  4896. "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate] could not be fetched: ",
  4897. $imap1->LastError || q{}, "\n" ;
  4898. errors_incr( $mysync, $error ) ;
  4899. $total_bytes_error += $h1_size if ( $h1_size ) ;
  4900. $h1_nb_msg_processed +=1 ;
  4901. return ;
  4902. }
  4903. if ( @skipmess ) {
  4904. my $match = skipmess( ${ $string_ref } ) ;
  4905. # string undef means the eval regex was bad.
  4906. if ( not ( defined $match ) ) {
  4907. myprint(
  4908. "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
  4909. . " could not be skipped by --skipmess option, bad regex\n" ) ;
  4910. return ;
  4911. }
  4912. if ( $match ) {
  4913. my $subject = subject( ${ $string_ref } ) ;
  4914. myprint( "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
  4915. . " (Subject:[$subject]) skipped by --skipmess\n" ) ;
  4916. return ;
  4917. }
  4918. }
  4919. if ( @regexmess ) {
  4920. ${ $string_ref } = regexmess( ${ $string_ref } ) ;
  4921. # string undef means the eval regex was bad.
  4922. if ( not ( defined ${ $string_ref } ) ) {
  4923. myprint(
  4924. "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
  4925. . " could not be transformed by --regexmess\n" ) ;
  4926. return ;
  4927. }
  4928. }
  4929. if ( @pipemess ) {
  4930. ${ $string_ref } = pipemess( ${ $string_ref }, @pipemess ) ;
  4931. # string undef means something was bad.
  4932. if ( not ( defined ${ $string_ref } ) ) {
  4933. myprint(
  4934. "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
  4935. . " could not be successfully transformed by --pipemess option\n" ) ;
  4936. return ;
  4937. }
  4938. }
  4939. if ( $addheader and defined $h1_fir_ref->{$h1_msg}->{'NO_HEADER'} ) {
  4940. my $header = add_header( $h1_msg ) ;
  4941. $debug and myprint( "msg $h1_fold/$h1_msg adding custom header [$header]\n" ) ;
  4942. ${ $string_ref } = $header . "\r\n" . ${ $string_ref } ;
  4943. }
  4944. $string_len = length_ref( $string_ref ) ;
  4945. $debugcontent and myprint(
  4946. q{=} x $STD_CHAR_PER_LINE, "\n",
  4947. "F message content begin next line ($string_len characters long)\n",
  4948. ${ $string_ref },
  4949. "F message content ended on previous line\n", q{=} x $STD_CHAR_PER_LINE, "\n" ) ;
  4950. if ( $mysync->{debugmemory} ) {
  4951. myprintf("M3: Memory consumption: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI) ;
  4952. }
  4953. return $string_len ;
  4954. }
  4955. sub tests_message_for_host2 {
  4956. note( 'Entering tests_message_for_host2()' ) ;
  4957. my ( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ) ;
  4958. is( undef, message_for_host2( ), q{message_for_host2: no args} ) ;
  4959. 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} ) ;
  4960. require Test::MockObject ;
  4961. my $imapT = Test::MockObject->new( ) ;
  4962. $mysync->{imap1} = $imapT ;
  4963. my $string ;
  4964. $h1_msg = 1 ;
  4965. $h1_fold = 'FoldFoo';
  4966. $h1_size = 9 ;
  4967. $h1_flags = '' ;
  4968. $h1_idate = '10-Jul-2015 09:00:00 +0200' ;
  4969. $h1_fir_ref = {} ;
  4970. $string_ref = \$string ;
  4971. $imapT->mock( 'message_to_file',
  4972. sub {
  4973. my ( $imap, $mystring_ref, $msg ) = @_ ;
  4974. ${$mystring_ref} = 'blablabla' ;
  4975. return length ${$mystring_ref} ;
  4976. }
  4977. ) ;
  4978. is( 9, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
  4979. q{message_for_host2: msg 1 == "blablabla", length} ) ;
  4980. is( 'blablabla', $string, q{message_for_host2: msg 1 == "blablabla", value} ) ;
  4981. # so far so good
  4982. # now the --pipemess stuff
  4983. SKIP: {
  4984. Readonly my $NB_WIN_tests_message_for_host2 => 0 ;
  4985. skip( 'Not on MSWin32', $NB_WIN_tests_message_for_host2 ) if ('MSWin32' ne $OSNAME) ;
  4986. # Windows
  4987. # "type" command does not accept redirection of STDIN with <
  4988. # "sort" does
  4989. } ;
  4990. SKIP: {
  4991. Readonly my $NB_UNX_tests_message_for_host2 => 6 ;
  4992. skip( 'Not on Unix', $NB_UNX_tests_message_for_host2 ) if ('MSWin32' eq $OSNAME) ;
  4993. # Unix
  4994. # no change by cat
  4995. @pipemess = ( 'cat' ) ;
  4996. is( 9, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
  4997. q{message_for_host2: --pipemess 'cat', length} ) ;
  4998. is( 'blablabla', $string, q{message_for_host2: --pipemess 'cat', value} ) ;
  4999. # failure by false
  5000. @pipemess = ( 'false' ) ;
  5001. is( undef, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
  5002. q{message_for_host2: --pipemess 'false', length} ) ;
  5003. is( undef, $string, q{message_for_host2: --pipemess 'false', value} ) ;
  5004. # failure by true since no output
  5005. @pipemess = ( 'true' ) ;
  5006. is( undef, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
  5007. q{message_for_host2: --pipemess 'true', length} ) ;
  5008. is( undef, $string, q{message_for_host2: --pipemess 'true', value} ) ;
  5009. }
  5010. note( 'Leaving tests_message_for_host2()' ) ;
  5011. return ;
  5012. }
  5013. sub length_ref {
  5014. my $string_ref = shift ;
  5015. my $string_len = defined ${ $string_ref } ? length( ${ $string_ref } ) : q{} ; # length or empty string
  5016. return $string_len ;
  5017. }
  5018. sub tests_length_ref {
  5019. note( 'Entering tests_length_ref()' ) ;
  5020. my $notdefined ;
  5021. is( q{}, length_ref( \$notdefined ), q{length_ref: value not defined} ) ;
  5022. my $notref ;
  5023. is( q{}, length_ref( $notref ), q{length_ref: param not a ref} ) ;
  5024. my $lala = 'lala' ;
  5025. is( 4, length_ref( \$lala ), q{length_ref: lala length == 4} ) ;
  5026. is( 4, length_ref( \'lili' ), q{length_ref: lili length == 4} ) ;
  5027. note( 'Leaving tests_length_ref()' ) ;
  5028. return ;
  5029. }
  5030. sub date_for_host2 {
  5031. my( $h1_msg, $h1_idate ) = @_ ;
  5032. my $h1_date = q{} ;
  5033. if ( $syncinternaldates ) {
  5034. $h1_date = $h1_idate ;
  5035. $debug and myprint( "internal date from host1: [$h1_date]\n" ) ;
  5036. $h1_date = good_date( $h1_date ) ;
  5037. $debug and myprint( "internal date from host1: [$h1_date] (fixed)\n" ) ;
  5038. }
  5039. if ( $idatefromheader ) {
  5040. $h1_date = $imap1->get_header( $h1_msg, 'Date' ) ;
  5041. $debug and myprint( "header date from host1: [$h1_date]\n" ) ;
  5042. $h1_date = good_date( $h1_date ) ;
  5043. $debug and myprint( "header date from host1: [$h1_date] (fixed)\n" ) ;
  5044. }
  5045. return( $h1_date ) ;
  5046. }
  5047. sub flags_for_host2 {
  5048. my( $h1_flags, $permanentflags2 ) = @_ ;
  5049. # RFC 2060: This flag can not be altered by any client
  5050. $h1_flags =~ s@\\Recent\s?@@xgi ;
  5051. my $h1_flags_re ;
  5052. if ( @regexflag and defined( $h1_flags_re = flags_regex( $h1_flags ) ) ) {
  5053. $h1_flags = $h1_flags_re ;
  5054. }
  5055. $h1_flags = flagscase( $h1_flags ) if $flagscase ;
  5056. $h1_flags = flags_filter( $h1_flags, $permanentflags2) if ( $permanentflags2 and $filterflags ) ;
  5057. return( $h1_flags ) ;
  5058. }
  5059. sub subject {
  5060. my $string = shift ;
  5061. my $subject = q{} ;
  5062. my $header = extract_header( $string ) ;
  5063. if( $header =~ m/^Subject:\s*([^\n\r]*)\r?$/msx ) {
  5064. #myprint( "MMM[$1]\n" ) ;
  5065. $subject = $1 ;
  5066. }
  5067. return( $subject ) ;
  5068. }
  5069. sub tests_subject {
  5070. note( 'Entering tests_subject()' ) ;
  5071. ok( q{} eq subject( q{} ), 'subject: null') ;
  5072. ok( 'toto le hero' eq subject( 'Subject: toto le hero' ), 'subject: toto le hero') ;
  5073. ok( 'toto le hero' eq subject( 'Subject:toto le hero' ), 'subject: toto le hero blank') ;
  5074. ok( 'toto le hero' eq subject( "Subject:toto le hero\r\n" ), 'subject: toto le hero\r\n') ;
  5075. my $MESS ;
  5076. $MESS = <<'EOF';
  5077. From: lalala
  5078. Subject: toto le hero
  5079. Date: zzzzzz
  5080. Boogie boogie
  5081. EOF
  5082. ok( 'toto le hero' eq subject( $MESS ), 'subject: toto le hero 2') ;
  5083. $MESS = <<'EOF';
  5084. Subject: toto le hero
  5085. From: lalala
  5086. Date: zzzzzz
  5087. Boogie boogie
  5088. EOF
  5089. ok( 'toto le hero' eq subject( $MESS ), 'subject: toto le hero 3') ;
  5090. $MESS = <<'EOF';
  5091. From: lalala
  5092. Subject: cuicui
  5093. Date: zzzzzz
  5094. Subject: toto le hero
  5095. EOF
  5096. ok( 'cuicui' eq subject( $MESS ), 'subject: cuicui') ;
  5097. $MESS = <<'EOF';
  5098. From: lalala
  5099. Date: zzzzzz
  5100. Subject: toto le hero
  5101. EOF
  5102. ok( q{} eq subject( $MESS ), 'subject: null but body could') ;
  5103. note( 'Leaving tests_subject()' ) ;
  5104. return ;
  5105. }
  5106. # GlobVar
  5107. # $sync
  5108. # $max_msg_size_in_bytes
  5109. # $imap2
  5110. # $imap1
  5111. # $total_bytes_error
  5112. # $h1_nb_msg_processed
  5113. # $h2_uidguess
  5114. # ...
  5115. #
  5116. #
  5117. sub append_message_on_host2 {
  5118. my( $string_ref, $h1_fold, $h1_msg, $string_len, $h2_fold, $h1_size, $h1_flags, $h1_date, $cache_dir ) = @_ ;
  5119. if ( $sync->{debugmemory} ) {
  5120. myprintf("A1: Memory consumption: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI) ;
  5121. }
  5122. my $new_id ;
  5123. if ( ! $sync->{dry} ) {
  5124. $max_msg_size_in_bytes = max( $h1_size, $max_msg_size_in_bytes ) ;
  5125. $new_id = $imap2->append_string( $h2_fold, ${ $string_ref }, $h1_flags, $h1_date ) ;
  5126. if ( $sync->{debugmemory} ) {
  5127. myprintf("A2: Memory consumption: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI) ;
  5128. }
  5129. if ( ! $new_id){
  5130. my $subject = subject( ${ $string_ref } ) ;
  5131. my $error_imap = $imap2->LastError || q{} ;
  5132. my $error = "- msg $h1_fold/$h1_msg {$string_len} could not append ( Subject:[$subject], Date:[$h1_date], Size:[$h1_size] ) to folder $h2_fold: $error_imap\n" ;
  5133. errors_incr( $sync, $error ) ;
  5134. $total_bytes_error += $h1_size;
  5135. $h1_nb_msg_processed +=1 ;
  5136. return ;
  5137. }
  5138. else{
  5139. # good
  5140. # $new_id is an id if the IMAP server has the
  5141. # UIDPLUS capability else just a ref
  5142. if ( $new_id !~ m{^\d+$}x ) {
  5143. $new_id = lastuid( $imap2, $h2_fold, $h2_uidguess ) ;
  5144. }
  5145. $h2_uidguess += 1 ;
  5146. $sync->{total_bytes_transferred} += $h1_size ;
  5147. $sync->{nb_msg_transferred} += 1 ;
  5148. $h1_nb_msg_processed +=1 ;
  5149. my $time_spent = timesince( $sync->{begin_transfer_time} ) ;
  5150. my $rate = bytes_display_string( $sync->{total_bytes_transferred} / $time_spent ) ;
  5151. my $eta = eta( $time_spent,
  5152. $h1_nb_msg_processed, $h1_nb_msg_start, $sync->{nb_msg_transferred} ) ;
  5153. my $amount_transferred = bytes_display_string( $sync->{total_bytes_transferred} ) ;
  5154. myprintf( "msg %s/%-19s copied to %s/%-10s %.2f msgs/s %s/s %s copied %s\n",
  5155. $h1_fold, "$h1_msg {$string_len}", $h2_fold, $new_id, $sync->{nb_msg_transferred}/$time_spent, $rate,
  5156. $amount_transferred,
  5157. $eta );
  5158. sleep_if_needed( $sync ) ;
  5159. if ( $usecache and $cacheaftercopy and $new_id =~ m{^\d+$}x ) {
  5160. $debugcache and myprint( "touch $cache_dir/${h1_msg}_$new_id\n" ) ;
  5161. touch( "$cache_dir/${h1_msg}_$new_id" )
  5162. or croak( "Couldn't touch $cache_dir/${h1_msg}_$new_id" ) ;
  5163. }
  5164. if ( $delete1 ) {
  5165. delete_message_on_host1( $h1_msg, $h1_fold ) ;
  5166. }
  5167. #myprint( "PRESS ENTER" ) and my $a = <> ;
  5168. return( $new_id ) ;
  5169. }
  5170. }
  5171. else{
  5172. $nb_msg_skipped_dry_mode += 1 ;
  5173. $h1_nb_msg_processed +=1 ;
  5174. }
  5175. return ;
  5176. }
  5177. sub tests_sleep_if_needed {
  5178. note( 'Entering tests_sleep_if_needed()' ) ;
  5179. is( undef, sleep_if_needed( ), 'sleep_if_needed: no args => undef' ) ;
  5180. my $mysync ;
  5181. is( undef, sleep_if_needed( $mysync ), 'sleep_if_needed: arg undef => undef' ) ;
  5182. $mysync->{maxbytespersecond} = 1000 ;
  5183. is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: maxbytespersecond only => no sleep => 0' ) ;
  5184. $mysync->{begin_transfer_time} = time ; # now
  5185. is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: begin_transfer_time now => no sleep => 0' ) ;
  5186. $mysync->{begin_transfer_time} = time - 2 ; # 2 s before
  5187. is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 0 => no sleep => 0' ) ;
  5188. $mysync->{total_bytes_transferred} = 2200 ;
  5189. $mysync->{begin_transfer_time} = time - 2 ; # 2 s before
  5190. is( '0.20', sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 2200 since 2s => sleep 0.2s' ) ;
  5191. is( '0', sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 2200 since 2+2 == 4s => no sleep' ) ;
  5192. $mysync->{maxsleep} = 0.1 ;
  5193. $mysync->{begin_transfer_time} = time - 2 ; # 2 s before again
  5194. is( '0.10', sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 4000 since 2s but maxsleep 0.1s => sleep 0.1s' ) ;
  5195. $mysync->{maxbytesafter} = 4000 ;
  5196. $mysync->{begin_transfer_time} = time - 2 ; # 2 s before again
  5197. is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: maxbytesafter == total_bytes_transferred => no sleep => 0' ) ;
  5198. note( 'Leaving tests_sleep_if_needed()' ) ;
  5199. return ;
  5200. }
  5201. sub sleep_if_needed {
  5202. my( $mysync ) = shift ;
  5203. if ( ! $mysync ) {
  5204. return ;
  5205. }
  5206. # No need to go further if there is no limit set
  5207. if ( not ( $mysync->{maxmessagespersecond}
  5208. or $mysync->{maxbytespersecond} )
  5209. ) {
  5210. return ;
  5211. }
  5212. $mysync->{maxsleep} = defined $mysync->{maxsleep} ? $mysync->{maxsleep} : $MAX_SLEEP ;
  5213. my $time_spent = timesince( $mysync->{begin_transfer_time} ) ;
  5214. my $sleep_max_messages = sleep_max_messages( $mysync->{nb_msg_transferred}, $time_spent, $mysync->{maxmessagespersecond} ) ;
  5215. my $maxbytesafter = $mysync->{maxbytesafter} || 0 ;
  5216. my $total_bytes_transferred = $mysync->{total_bytes_transferred} || 0 ;
  5217. my $total_bytes_to_consider = $total_bytes_transferred - $maxbytesafter ;
  5218. #myprint( "maxbytesafter:$maxbytesafter\n" ) ;
  5219. #myprint( "total_bytes_to_consider:$total_bytes_to_consider\n" ) ;
  5220. my $sleep_max_bytes = sleep_max_bytes( $total_bytes_to_consider, $time_spent, $mysync->{maxbytespersecond} ) ;
  5221. my $sleep_max = min( $mysync->{maxsleep}, max( $sleep_max_messages, $sleep_max_bytes ) ) ;
  5222. $sleep_max = mysprintf( "%.2f", $sleep_max ) ; # round with 2 decimals.
  5223. if ( $sleep_max > 0 ) {
  5224. myprint( "sleeping $sleep_max s\n" ) ;
  5225. sleep $sleep_max ;
  5226. # Slept
  5227. return $sleep_max ;
  5228. }
  5229. # No sleep
  5230. return 0 ;
  5231. }
  5232. sub sleep_max_messages {
  5233. # how long we have to sleep to go under max_messages_per_second
  5234. my( $nb_msg_transferred, $time_spent, $maxmessagespersecond ) = @_ ;
  5235. if ( ( not defined $maxmessagespersecond ) or $maxmessagespersecond <= 0 ) { return( 0 ) } ;
  5236. my $sleep = ( $nb_msg_transferred / $maxmessagespersecond ) - $time_spent ;
  5237. # the sleep must be positive
  5238. return( max( 0, $sleep ) ) ;
  5239. }
  5240. sub tests_sleep_max_messages {
  5241. note( 'Entering tests_sleep_max_messages()' ) ;
  5242. ok( 0 == sleep_max_messages( 4, 2, undef ), 'sleep_max_messages: maxmessagespersecond = undef') ;
  5243. ok( 0 == sleep_max_messages( 4, 2, 0 ), 'sleep_max_messages: maxmessagespersecond = 0') ;
  5244. ok( 0 == sleep_max_messages( 4, 2, $MINUS_ONE ), 'sleep_max_messages: maxmessagespersecond = -1') ;
  5245. ok( 0 == sleep_max_messages( 4, 2, 2 ), 'sleep_max_messages: maxmessagespersecond = 2 max reached') ;
  5246. ok( 2 == sleep_max_messages( 8, 2, 2 ), 'sleep_max_messages: maxmessagespersecond = 2 max over') ;
  5247. ok( 0 == sleep_max_messages( 2, 2, 2 ), 'sleep_max_messages: maxmessagespersecond = 2 max not reached') ;
  5248. note( 'Leaving tests_sleep_max_messages()' ) ;
  5249. return ;
  5250. }
  5251. sub sleep_max_bytes {
  5252. # how long we have to sleep to go under max_bytes_per_second
  5253. my( $total_bytes_to_consider, $time_spent, $maxbytespersecond ) = @_ ;
  5254. $total_bytes_to_consider ||= 0 ;
  5255. $time_spent ||= 0 ;
  5256. if ( ( not defined $maxbytespersecond ) or $maxbytespersecond <= 0 ) { return( 0 ) } ;
  5257. #myprint( "total_bytes_to_consider:$total_bytes_to_consider\n" ) ;
  5258. my $sleep = ( $total_bytes_to_consider / $maxbytespersecond ) - $time_spent ;
  5259. # the sleep must be positive
  5260. return( max( 0, $sleep ) ) ;
  5261. }
  5262. sub tests_sleep_max_bytes {
  5263. note( 'Entering tests_sleep_max_bytes()' ) ;
  5264. ok( 0 == sleep_max_bytes( 4000, 2, undef ), 'sleep_max_bytes: maxbytespersecond == undef => sleep 0' ) ;
  5265. ok( 0 == sleep_max_bytes( 4000, 2, 0 ), 'sleep_max_bytes: maxbytespersecond = 0 => sleep 0') ;
  5266. ok( 0 == sleep_max_bytes( 4000, 2, $MINUS_ONE ), 'sleep_max_bytes: maxbytespersecond = -1 => sleep 0') ;
  5267. ok( 0 == sleep_max_bytes( 4000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max reached sharp => sleep 0') ;
  5268. ok( 2 == sleep_max_bytes( 8000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max over => sleep a little') ;
  5269. ok( 0 == sleep_max_bytes( -8000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max not reached => sleep 0') ;
  5270. ok( 0 == sleep_max_bytes( 2000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max not reached => sleep 0') ;
  5271. ok( 0 == sleep_max_bytes( -2000, 2, 1000 ), 'sleep_max_bytes: maxbytespersecond = 1k max not reached => sleep 0') ;
  5272. note( 'Leaving tests_sleep_max_bytes()' ) ;
  5273. return ;
  5274. }
  5275. # 6 GlobVar: $sync $imap1 $h1_nb_msg_deleted $expunge1
  5276. sub delete_message_on_host1 {
  5277. my( $h1_msg, $h1_fold ) = @_ ;
  5278. my $expunge_message = q{} ;
  5279. $expunge_message = 'and expunged' if ( $expungeaftereach and $expunge1 ) ;
  5280. myprint( "Host1 msg $h1_fold/$h1_msg marked deleted $expunge_message $sync->{dry_message}\n" ) ;
  5281. if ( ! $sync->{dry} ) {
  5282. $imap1->delete_message( $h1_msg ) ;
  5283. $h1_nb_msg_deleted += 1 ;
  5284. $imap1->expunge( ) if ( $expungeaftereach and $expunge1 ) ;
  5285. }
  5286. return ;
  5287. }
  5288. sub eta {
  5289. my( $my_time_spent, $h1_nb_processed, $my_h1_nb_msg_start, $nb_transferred ) = @_ ;
  5290. return( q{} ) if not $foldersizes ;
  5291. my $time_remaining = time_remaining( $my_time_spent, $h1_nb_processed, $my_h1_nb_msg_start, $nb_transferred ) ;
  5292. my $nb_msg_remaining = $my_h1_nb_msg_start - $h1_nb_processed ;
  5293. my $eta_date = localtime( time + $time_remaining ) ;
  5294. return( mysprintf( 'ETA: %s %1.0f s %s/%s msgs left', $eta_date, $time_remaining, $nb_msg_remaining, $my_h1_nb_msg_start ) ) ;
  5295. }
  5296. sub time_remaining {
  5297. my( $my_time_spent, $h1_nb_processed, $my_h1_nb_msg_start, $nb_transferred ) = @_ ;
  5298. my $time_remaining = ( $my_time_spent / $nb_transferred ) * ( $my_h1_nb_msg_start - $h1_nb_processed ) ;
  5299. return( $time_remaining ) ;
  5300. }
  5301. sub tests_time_remaining {
  5302. note( 'Entering tests_time_remaining()' ) ;
  5303. ok( 1 == time_remaining( 1, 1, 2, 1 ), 'time_remaining: 1, 1, 2, 1 -> 1' ) ;
  5304. ok( 1 == time_remaining( 9, 9, 10, 9 ), 'time_remaining: 9, 9, 10, 9 -> 1' ) ;
  5305. ok( 9 == time_remaining( 1, 1, 10, 1 ), 'time_remaining: 1, 1, 10, 1 -> 1' ) ;
  5306. note( 'Leaving tests_time_remaining()' ) ;
  5307. return ;
  5308. }
  5309. sub cache_map {
  5310. my ( $cache_files_ref, $h1_msgs_ref, $h2_msgs_ref ) = @_;
  5311. my ( %map1_2, %map2_1, %done2 ) ;
  5312. my $h1_msgs_hash_ref = { } ;
  5313. my $h2_msgs_hash_ref = { } ;
  5314. @{ $h1_msgs_hash_ref }{ @{ $h1_msgs_ref } } = ( ) ;
  5315. @{ $h2_msgs_hash_ref }{ @{ $h2_msgs_ref } } = ( ) ;
  5316. foreach my $file ( sort @{ $cache_files_ref } ) {
  5317. $debugcache and myprint( "C12: $file\n" ) ;
  5318. ( $uid1, $uid2 ) = match_a_cache_file( $file ) ;
  5319. if ( exists( $h1_msgs_hash_ref->{ defined $uid1 ? $uid1 : q{} } )
  5320. and exists( $h2_msgs_hash_ref->{ defined $uid2 ? $uid2 : q{} } ) ) {
  5321. # keep only the greatest uid2
  5322. # 130_2301 and
  5323. # 130_231 => keep only 130 -> 2301
  5324. # keep only the greatest uid1
  5325. # 1601_260 and
  5326. # 161_260 => keep only 1601 -> 260
  5327. my $max_uid2 = max( $uid2, $map1_2{ $uid1 } || $MINUS_ONE ) ;
  5328. if ( exists $done2{ $max_uid2 } ) {
  5329. if ( $done2{ $max_uid2 } < $uid1 ) {
  5330. $map1_2{ $uid1 } = $max_uid2 ;
  5331. delete $map1_2{ $done2{ $max_uid2 } } ;
  5332. $done2{ $max_uid2 } = $uid1 ;
  5333. }
  5334. }else{
  5335. $map1_2{ $uid1 } = $max_uid2 ;
  5336. $done2{ $max_uid2 } = $uid1 ;
  5337. }
  5338. };
  5339. }
  5340. %map2_1 = reverse %map1_2 ;
  5341. return( \%map1_2, \%map2_1) ;
  5342. }
  5343. sub tests_cache_map {
  5344. note( 'Entering tests_cache_map()' ) ;
  5345. #$debugcache = 1 ;
  5346. my @cache_files = qw (
  5347. 100_200
  5348. 101_201
  5349. 120_220
  5350. 142_242
  5351. 143_243
  5352. 177_277
  5353. 177_278
  5354. 177_279
  5355. 155_255
  5356. 180_280
  5357. 181_280
  5358. 182_280
  5359. 130_231
  5360. 130_2301
  5361. 161_260
  5362. 1601_260
  5363. ) ;
  5364. my $msgs_1 = [120, 142, 143, 144, 161, 1601, 177, 182, 130 ];
  5365. my $msgs_2 = [ 242, 243, 260, 299, 377, 279, 255, 280, 231, 2301 ];
  5366. my( $c12, $c21 ) ;
  5367. ok( ( $c12, $c21 ) = cache_map( \@cache_files, $msgs_1, $msgs_2 ), 'cache_map: 02' );
  5368. my $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
  5369. my $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
  5370. ok( 0 == compare_lists( [ 130, 142, 143, 177, 182, 1601 ], $a1 ), 'cache_map: 03' );
  5371. ok( 0 == compare_lists( [ 242, 243, 260, 279, 280, 2301 ], $a2 ), 'cache_map: 04' );
  5372. ok( ! $c12->{161}, 'cache_map: ! 161 -> 260' );
  5373. ok( 260 == $c12->{1601}, 'cache_map: 1601 -> 260' );
  5374. ok( 2301 == $c12->{130}, 'cache_map: 130 -> 2301' );
  5375. #myprint( $c12->{1601}, "\n" ) ;
  5376. note( 'Leaving tests_cache_map()' ) ;
  5377. return ;
  5378. }
  5379. sub cache_dir_fix {
  5380. my $cache_dir = shift ;
  5381. $cache_dir =~ s/([;<>\*\|`&\$!#\(\)\[\]\{\}:'"\\])/\\$1/xg ;
  5382. #myprint( "cache_dir_fix: $cache_dir\n" ) ;
  5383. return( $cache_dir ) ;
  5384. }
  5385. sub tests_cache_dir_fix {
  5386. note( 'Entering tests_cache_dir_fix()' ) ;
  5387. ok( 'lalala' eq cache_dir_fix('lalala'), 'cache_dir_fix: lalala -> lalala' );
  5388. ok( 'ii\\\\ii' eq cache_dir_fix('ii\ii'), 'cache_dir_fix: ii\ii -> ii\\\\ii' );
  5389. ok( 'ii@ii' eq cache_dir_fix('ii@ii'), 'cache_dir_fix: ii@ii -> ii@ii' );
  5390. ok( 'ii@ii\\:ii' eq cache_dir_fix('ii@ii:ii'), 'cache_dir_fix: ii@ii:ii -> ii@ii\\:ii' );
  5391. ok( 'i\\\\i\\\\ii' eq cache_dir_fix('i\i\ii'), 'cache_dir_fix: i\i\ii -> i\\\\i\\\\ii' );
  5392. ok( 'i\\\\ii' eq cache_dir_fix('i\\ii'), 'cache_dir_fix: i\\ii -> i\\\\\\\\ii' );
  5393. ok( '\\\\ ' eq cache_dir_fix('\\ '), 'cache_dir_fix: \\ -> \\\\\ ' );
  5394. ok( '\\\\ ' eq cache_dir_fix('\ '), 'cache_dir_fix: \ -> \\\\\ ' );
  5395. ok( '\[bracket\]' eq cache_dir_fix('[bracket]'), 'cache_dir_fix: [bracket] -> \[bracket\]' );
  5396. note( 'Leaving tests_cache_dir_fix()' ) ;
  5397. return ;
  5398. }
  5399. sub cache_dir_fix_win {
  5400. my $cache_dir = shift ;
  5401. $cache_dir =~ s/(\[|\])/[$1]/xg ;
  5402. #myprint( "cache_dir_fix_win: $cache_dir\n" ) ;
  5403. return( $cache_dir ) ;
  5404. }
  5405. sub tests_cache_dir_fix_win {
  5406. note( 'Entering tests_cache_dir_fix_win()' ) ;
  5407. ok( 'lalala' eq cache_dir_fix_win('lalala'), 'cache_dir_fix_win: lalala -> lalala' );
  5408. ok( '[[]bracket[]]' eq cache_dir_fix_win('[bracket]'), 'cache_dir_fix_win: [bracket] -> [[]bracket[]]' );
  5409. note( 'Leaving tests_cache_dir_fix_win()' ) ;
  5410. return ;
  5411. }
  5412. sub get_cache {
  5413. my ( $cache_dir, $h1_msgs_ref, $h2_msgs_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) = @_;
  5414. $debugcache and myprint( "Entering get_cache\n" ) ;
  5415. -d $cache_dir or return( undef ); # exit if cache directory doesn't exist
  5416. $debugcache and myprint( "cache_dir : $cache_dir\n" ) ;
  5417. if ( 'MSWin32' ne $OSNAME ) {
  5418. $cache_dir = cache_dir_fix( $cache_dir ) ;
  5419. }else{
  5420. $cache_dir = cache_dir_fix_win( $cache_dir ) ;
  5421. }
  5422. $debugcache and myprint( "cache_dir_fix: $cache_dir\n" ) ;
  5423. my @cache_files = bsd_glob( "$cache_dir/*" ) ;
  5424. #$debugcache and myprint( "cache_files: [@cache_files]\n" ) ;
  5425. $debugcache and myprint( 'cache_files: ', scalar @cache_files , " files found\n" ) ;
  5426. my( $cache_1_2_ref, $cache_2_1_ref )
  5427. = cache_map( \@cache_files, $h1_msgs_ref, $h2_msgs_ref ) ;
  5428. clean_cache( \@cache_files, $cache_1_2_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) ;
  5429. $debugcache and myprint( "Exiting get_cache\n" ) ;
  5430. return( $cache_1_2_ref, $cache_2_1_ref ) ;
  5431. }
  5432. sub tests_get_cache {
  5433. note( 'Entering tests_get_cache()' ) ;
  5434. ok( not( get_cache('/cache_no_exist') ), 'get_cache: /cache_no_exist' );
  5435. ok( ( not -d 'W/tmp/cache/F1/F2' or rmtree( 'W/tmp/cache/F1/F2' ) ), 'get_cache: rmtree W/tmp/cache/F1/F2' ) ;
  5436. ok( mkpath( 'W/tmp/cache/F1/F2' ), 'get_cache: mkpath W/tmp/cache/F1/F2' ) ;
  5437. my @test_files_cache = ( qw(
  5438. W/tmp/cache/F1/F2/100_200
  5439. W/tmp/cache/F1/F2/101_201
  5440. W/tmp/cache/F1/F2/120_220
  5441. W/tmp/cache/F1/F2/142_242
  5442. W/tmp/cache/F1/F2/143_243
  5443. W/tmp/cache/F1/F2/177_277
  5444. W/tmp/cache/F1/F2/177_377
  5445. W/tmp/cache/F1/F2/177_777
  5446. W/tmp/cache/F1/F2/155_255
  5447. ) ) ;
  5448. ok( touch( @test_files_cache ), 'get_cache: touch W/tmp/cache/F1/F2/...' ) ;
  5449. # on cache: 100_200 101_201 142_242 143_243 177_277 177_377 177_777 155_255
  5450. # on live:
  5451. my $msgs_1 = [120, 142, 143, 144, 177 ];
  5452. my $msgs_2 = [ 242, 243, 299, 377, 777, 255 ];
  5453. my $msgs_all_1 = { 120 => 0, 142 => 0, 143 => 0, 144 => 0, 177 => 0 } ;
  5454. my $msgs_all_2 = { 242 => 0, 243 => 0, 299 => 0, 377 => 0, 777 => 0, 255 => 0 } ;
  5455. my( $c12, $c21 ) ;
  5456. ok( ( $c12, $c21 ) = get_cache( 'W/tmp/cache/F1/F2', $msgs_1, $msgs_2, $msgs_all_1, $msgs_all_2 ), 'get_cache: 02' );
  5457. my $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
  5458. my $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
  5459. ok( 0 == compare_lists( [ 142, 143, 177 ], $a1 ), 'get_cache: 03' );
  5460. ok( 0 == compare_lists( [ 242, 243, 777 ], $a2 ), 'get_cache: 04' );
  5461. ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 142_242');
  5462. ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 143_243');
  5463. ok( ! -f 'W/tmp/cache/F1/F2/100_200', 'get_cache: file removed 100_200');
  5464. ok( ! -f 'W/tmp/cache/F1/F2/101_201', 'get_cache: file removed 101_201');
  5465. # test clean_cache executed
  5466. $maxage = 2 ;
  5467. ok( touch(@test_files_cache), 'get_cache: touch W/tmp/cache/F1/F2/...' ) ;
  5468. ok( ( $c12, $c21 ) = get_cache('W/tmp/cache/F1/F2', $msgs_1, $msgs_2, $msgs_all_1, $msgs_all_2 ), 'get_cache: 02' );
  5469. ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 142_242');
  5470. ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 143_243');
  5471. ok( ! -f 'W/tmp/cache/F1/F2/100_200', 'get_cache: file NOT removed 100_200');
  5472. ok( ! -f 'W/tmp/cache/F1/F2/101_201', 'get_cache: file NOT removed 101_201');
  5473. # strange files
  5474. #$debugcache = 1 ;
  5475. $maxage = undef ;
  5476. ok( ( not -d 'W/tmp/cache/rr\uee' or rmtree( 'W/tmp/cache/rr\uee' )), 'get_cache: rmtree W/tmp/cache/rr\uee' ) ;
  5477. ok( mkpath( 'W/tmp/cache/rr\uee' ), 'get_cache: mkpath W/tmp/cache/rr\uee' ) ;
  5478. @test_files_cache = ( qw(
  5479. W/tmp/cache/rr\uee/100_200
  5480. W/tmp/cache/rr\uee/101_201
  5481. W/tmp/cache/rr\uee/120_220
  5482. W/tmp/cache/rr\uee/142_242
  5483. W/tmp/cache/rr\uee/143_243
  5484. W/tmp/cache/rr\uee/177_277
  5485. W/tmp/cache/rr\uee/177_377
  5486. W/tmp/cache/rr\uee/177_777
  5487. W/tmp/cache/rr\uee/155_255
  5488. ) ) ;
  5489. ok( touch(@test_files_cache), 'get_cache: touch strange W/tmp/cache/...' ) ;
  5490. # on cache: 100_200 101_201 142_242 143_243 177_277 177_377 177_777 155_255
  5491. # on live:
  5492. $msgs_1 = [120, 142, 143, 144, 177 ] ;
  5493. $msgs_2 = [ 242, 243, 299, 377, 777, 255 ] ;
  5494. $msgs_all_1 = { 120 => q{}, 142 => q{}, 143 => q{}, 144 => q{}, 177 => q{} } ;
  5495. $msgs_all_2 = { 242 => q{}, 243 => q{}, 299 => q{}, 377 => q{}, 777 => q{}, 255 => q{} } ;
  5496. 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' );
  5497. $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
  5498. $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
  5499. ok( 0 == compare_lists( [ 142, 143, 177 ], $a1 ), 'get_cache: strange path 03' );
  5500. ok( 0 == compare_lists( [ 242, 243, 777 ], $a2 ), 'get_cache: strange path 04' );
  5501. ok( -f 'W/tmp/cache/rr\uee/142_242', 'get_cache: strange path file kept 142_242');
  5502. ok( -f 'W/tmp/cache/rr\uee/142_242', 'get_cache: strange path file kept 143_243');
  5503. ok( ! -f 'W/tmp/cache/rr\uee/100_200', 'get_cache: strange path file removed 100_200');
  5504. ok( ! -f 'W/tmp/cache/rr\uee/101_201', 'get_cache: strange path file removed 101_201');
  5505. note( 'Leaving tests_get_cache()' ) ;
  5506. return ;
  5507. }
  5508. sub match_a_cache_file {
  5509. my $file = shift ;
  5510. my ( $cache_uid1, $cache_uid2 ) ;
  5511. return( ( undef, undef ) ) if ( ! $file ) ;
  5512. if ( $file =~ m{(?:^|/)(\d+)_(\d+)$}x ) {
  5513. $cache_uid1 = $1 ;
  5514. $cache_uid2 = $2 ;
  5515. }
  5516. return( $cache_uid1, $cache_uid2 ) ;
  5517. }
  5518. sub tests_match_a_cache_file {
  5519. note( 'Entering tests_match_a_cache_file()' ) ;
  5520. my ( $tuid1, $tuid2 ) ;
  5521. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( ), 'match_a_cache_file: no arg' ) ;
  5522. ok( ! defined $tuid1 , 'match_a_cache_file: no arg 1' ) ;
  5523. ok( ! defined $tuid2 , 'match_a_cache_file: no arg 2' ) ;
  5524. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( q{} ), 'match_a_cache_file: empty arg' ) ;
  5525. ok( ! defined $tuid1 , 'match_a_cache_file: empty arg 1' ) ;
  5526. ok( ! defined $tuid2 , 'match_a_cache_file: empty arg 2' ) ;
  5527. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '000_000' ), 'match_a_cache_file: 000_000' ) ;
  5528. ok( '000' eq $tuid1, 'match_a_cache_file: 000_000 1' ) ;
  5529. ok( '000' eq $tuid2, 'match_a_cache_file: 000_000 2' ) ;
  5530. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '123_456' ), 'match_a_cache_file: 123_456' ) ;
  5531. ok( '123' eq $tuid1, 'match_a_cache_file: 123_456 1' ) ;
  5532. ok( '456' eq $tuid2, 'match_a_cache_file: 123_456 2' ) ;
  5533. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '/tmp/truc/123_456' ), 'match_a_cache_file: /tmp/truc/123_456' ) ;
  5534. ok( '123' eq $tuid1, 'match_a_cache_file: /tmp/truc/123_456 1' ) ;
  5535. ok( '456' eq $tuid2, 'match_a_cache_file: /tmp/truc/123_456 2' ) ;
  5536. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '/lala123_456' ), 'match_a_cache_file: NO /lala123_456' ) ;
  5537. ok( ! $tuid1, 'match_a_cache_file: /lala123_456 1' ) ;
  5538. ok( ! $tuid2, 'match_a_cache_file: /lala123_456 2' ) ;
  5539. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( 'la123_456' ), 'match_a_cache_file: NO la123_456' ) ;
  5540. ok( ! $tuid1, 'match_a_cache_file: la123_456 1' ) ;
  5541. ok( ! $tuid2, 'match_a_cache_file: la123_456 2' ) ;
  5542. note( 'Leaving tests_match_a_cache_file()' ) ;
  5543. return ;
  5544. }
  5545. sub clean_cache {
  5546. my ( $cache_files_ref, $cache_1_2_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) = @_ ;
  5547. $debugcache and myprint( "Entering clean_cache\n" ) ;
  5548. $debugcache and myprint( map { "$_ -> " . $cache_1_2_ref->{ $_ } . "\n" } keys %{ $cache_1_2_ref } ) ;
  5549. foreach my $file ( @{ $cache_files_ref } ) {
  5550. $debugcache and myprint( "$file\n" ) ;
  5551. my ( $cache_uid1, $cache_uid2 ) = match_a_cache_file( $file ) ;
  5552. $debugcache and myprint( "u1: $cache_uid1 u2: $cache_uid2 c12: ", $cache_1_2_ref->{ $cache_uid1 } || q{}, "\n") ;
  5553. # or ( ! exists( $cache_1_2_ref->{ $cache_uid1 } ) )
  5554. # or ( ! ( $cache_uid2 == $cache_1_2_ref->{ $cache_uid1 } ) )
  5555. if ( ( not defined $cache_uid1 )
  5556. or ( not defined $cache_uid2 )
  5557. or ( not exists $h1_msgs_all_hash_ref->{ $cache_uid1 } )
  5558. or ( not exists $h2_msgs_all_hash_ref->{ $cache_uid2 } )
  5559. ) {
  5560. $debugcache and myprint( "remove $file\n" ) ;
  5561. unlink $file or myprint( "$OS_ERROR" ) ;
  5562. }
  5563. }
  5564. $debugcache and myprint( "Exiting clean_cache\n" ) ;
  5565. return( 1 ) ;
  5566. }
  5567. sub tests_clean_cache {
  5568. note( 'Entering tests_clean_cache()' ) ;
  5569. ok( ( not -d 'W/tmp/cache/G1/G2' or rmtree( 'W/tmp/cache/G1/G2' )), 'clean_cache: rmtree W/tmp/cache/G1/G2' ) ;
  5570. ok( mkpath( 'W/tmp/cache/G1/G2' ), 'clean_cache: mkpath W/tmp/cache/G1/G2' ) ;
  5571. my @test_files_cache = ( qw(
  5572. W/tmp/cache/G1/G2/100_200
  5573. W/tmp/cache/G1/G2/101_201
  5574. W/tmp/cache/G1/G2/120_220
  5575. W/tmp/cache/G1/G2/142_242
  5576. W/tmp/cache/G1/G2/143_243
  5577. W/tmp/cache/G1/G2/177_277
  5578. W/tmp/cache/G1/G2/177_377
  5579. W/tmp/cache/G1/G2/177_777
  5580. W/tmp/cache/G1/G2/155_255
  5581. ) ) ;
  5582. ok( touch(@test_files_cache), 'clean_cache: touch W/tmp/cache/G1/G2/...' ) ;
  5583. ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache: 100_200 before' );
  5584. ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache: 142_242 before' );
  5585. ok( -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache: 177_277 before' );
  5586. ok( -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache: 177_377 before' );
  5587. ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache: 177_777 before' );
  5588. ok( -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache: 155_255 before' );
  5589. my $cache = {
  5590. 142 => 242,
  5591. 177 => 777,
  5592. } ;
  5593. my $all_1 = {
  5594. 142 => q{},
  5595. 177 => q{},
  5596. } ;
  5597. my $all_2 = {
  5598. 200 => q{},
  5599. 242 => q{},
  5600. 777 => q{},
  5601. } ;
  5602. ok( clean_cache( \@test_files_cache, $cache, $all_1, $all_2 ), 'clean_cache: ' ) ;
  5603. ok( ! -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache: 100_200 after' );
  5604. ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache: 142_242 after' );
  5605. ok( ! -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache: 177_277 after' );
  5606. ok( ! -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache: 177_377 after' );
  5607. ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache: 177_777 after' );
  5608. ok( ! -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache: 155_255 after' );
  5609. note( 'Leaving tests_clean_cache()' ) ;
  5610. return ;
  5611. }
  5612. sub tests_clean_cache_2 {
  5613. note( 'Entering tests_clean_cache_2()' ) ;
  5614. ok( ( not -d 'W/tmp/cache/G1/G2' or rmtree( 'W/tmp/cache/G1/G2' )), 'clean_cache_2: rmtree W/tmp/cache/G1/G2' ) ;
  5615. ok( mkpath( 'W/tmp/cache/G1/G2' ), 'clean_cache_2: mkpath W/tmp/cache/G1/G2' ) ;
  5616. my @test_files_cache = ( qw(
  5617. W/tmp/cache/G1/G2/100_200
  5618. W/tmp/cache/G1/G2/101_201
  5619. W/tmp/cache/G1/G2/120_220
  5620. W/tmp/cache/G1/G2/142_242
  5621. W/tmp/cache/G1/G2/143_243
  5622. W/tmp/cache/G1/G2/177_277
  5623. W/tmp/cache/G1/G2/177_377
  5624. W/tmp/cache/G1/G2/177_777
  5625. W/tmp/cache/G1/G2/155_255
  5626. ) ) ;
  5627. ok( touch(@test_files_cache), 'clean_cache_2: touch W/tmp/cache/G1/G2/...' ) ;
  5628. ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache_2: 100_200 before' );
  5629. ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache_2: 142_242 before' );
  5630. ok( -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache_2: 177_277 before' );
  5631. ok( -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache_2: 177_377 before' );
  5632. ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache_2: 177_777 before' );
  5633. ok( -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache_2: 155_255 before' );
  5634. my $cache = {
  5635. 142 => 242,
  5636. 177 => 777,
  5637. } ;
  5638. my $all_1 = {
  5639. $NUMBER_100 => q{},
  5640. 142 => q{},
  5641. 177 => q{},
  5642. } ;
  5643. my $all_2 = {
  5644. 200 => q{},
  5645. 242 => q{},
  5646. 777 => q{},
  5647. } ;
  5648. ok( clean_cache( \@test_files_cache, $cache, $all_1, $all_2 ), 'clean_cache_2: ' ) ;
  5649. ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache_2: 100_200 after' );
  5650. ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache_2: 142_242 after' );
  5651. ok( ! -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache_2: 177_277 after' );
  5652. ok( ! -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache_2: 177_377 after' );
  5653. ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache_2: 177_777 after' );
  5654. ok( ! -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache_2: 155_255 after' );
  5655. note( 'Leaving tests_clean_cache_2()' ) ;
  5656. return ;
  5657. }
  5658. sub tests_mkpath {
  5659. note( 'Entering tests_mkpath()' ) ;
  5660. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' )), 'mkpath: mkpath W/tmp/tests/' ) ;
  5661. SKIP: {
  5662. skip( 'Tests only for Unix', 10 ) if ( 'MSWin32' eq $OSNAME ) ;
  5663. my $long_path_unix = '123456789/' x 30 ;
  5664. ok( ( -d "W/tmp/tests/long/$long_path_unix" or mkpath( "W/tmp/tests/long/$long_path_unix" ) ), 'mkpath: mkpath 300 char' ) ;
  5665. ok( -d "W/tmp/tests/long/$long_path_unix", 'mkpath: mkpath > 300 char verified' ) ;
  5666. ok( ( -d "W/tmp/tests/long/$long_path_unix" and rmtree( 'W/tmp/tests/long/' ) ), 'mkpath: rmtree 300 char' ) ;
  5667. ok( ! -d "W/tmp/tests/long/$long_path_unix", 'mkpath: rmtree 300 char verified' ) ;
  5668. ok( ( -d 'W/tmp/tests/trailing_dots...' or mkpath( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: mkpath trailing_dots...' ) ;
  5669. ok( -d 'W/tmp/tests/trailing_dots...', 'mkpath: mkpath trailing_dots... verified' ) ;
  5670. ok( ( -d 'W/tmp/tests/trailing_dots...' and rmtree( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: rmtree trailing_dots...' ) ;
  5671. ok( ! -d 'W/tmp/tests/trailing_dots...', 'mkpath: rmtree trailing_dots... verified' ) ;
  5672. eval { ok( 1 / 0, 'mkpath: divide by 0' ) ; } or ok( 1, 'mkpath: can not divide by 0' ) ;
  5673. ok( 1, 'mkpath: still alive' ) ;
  5674. } ;
  5675. SKIP: {
  5676. skip( 'Tests only for MSWin32', 13 ) if ( 'MSWin32' ne $OSNAME ) ;
  5677. my $long_path_2_prefix = "$tmpdir\\imapsync_tests" || '\\\?\\E:\\TEMP\\imapsync_tests' ;
  5678. myprint( "long_path_2_prefix: $long_path_2_prefix\n" ) ;
  5679. my $long_path_100 = $long_path_2_prefix . '\\' . '123456789\\' x 10 . 'END' ;
  5680. my $long_path_300 = $long_path_2_prefix . '\\' . '123456789\\' x 30 . 'END' ;
  5681. #myprint( "$long_path_100\n" ) ;
  5682. ok( ( -d $long_path_2_prefix or mkpath( $long_path_2_prefix ) ), 'mkpath: -d mkpath small path' ) ;
  5683. ok( ( -d $long_path_2_prefix ), 'mkpath: -d mkpath small path done' ) ;
  5684. ok( ( -d $long_path_100 or mkpath( $long_path_100 ) ), 'mkpath: mkpath > 100 char' ) ;
  5685. ok( ( -d $long_path_100 ), 'mkpath: -d mkpath > 200 char done' ) ;
  5686. ok( ( -d $long_path_2_prefix and rmtree( $long_path_2_prefix ) ), 'mkpath: rmtree > 100 char' ) ;
  5687. ok( (! -d $long_path_2_prefix ), 'mkpath: ! -d rmtree done' ) ;
  5688. # Without the eval the following mkpath 300 just kill the whole process without a whisper
  5689. #myprint( "$long_path_300\n" ) ;
  5690. eval { ok( ( -d $long_path_300 or mkpath( $long_path_300 ) ), 'mkpath: create a path with 300 characters' ) ; }
  5691. or ok( 1, 'mkpath: can not create a path with 300 characters' ) ;
  5692. ok( ( ( ! -d $long_path_300 ) or -d $long_path_300 and rmtree( $long_path_300 ) ), 'mkpath: rmtree the 300 character path' ) ;
  5693. ok( 1, 'mkpath: still alive' ) ;
  5694. ok( ( -d 'W/tmp/tests/trailing_dots...' or mkpath( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: mkpath trailing_dots...' ) ;
  5695. ok( -d 'W/tmp/tests/trailing_dots...', 'mkpath: mkpath trailing_dots... verified' ) ;
  5696. ok( ( -d 'W/tmp/tests/trailing_dots...' and rmtree( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: rmtree trailing_dots...' ) ;
  5697. ok( ! -d 'W/tmp/tests/trailing_dots...', 'mkpath: rmtree trailing_dots... verified' ) ;
  5698. } ;
  5699. note( 'Leaving tests_mkpath()' ) ;
  5700. # Keep this because of the eval used by the caller (failed badly?)
  5701. return 1 ;
  5702. }
  5703. sub tests_touch {
  5704. note( 'Entering tests_touch()' ) ;
  5705. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' )), 'touch: mkpath W/tmp/tests/' ) ;
  5706. ok( 1 == touch( 'W/tmp/tests/lala'), 'touch: W/tmp/tests/lala') ;
  5707. ok( 1 == touch( 'W/tmp/tests/\y'), 'touch: W/tmp/tests/\y') ;
  5708. ok( 0 == touch( '/no/no/no/aaa'), 'touch: not /aaa') ;
  5709. ok( 1 == touch( 'W/tmp/tests/lili', 'W/tmp/tests/lolo'), 'touch: 2 files') ;
  5710. ok( 0 == touch( 'W/tmp/tests/\y', '/no/no/aaa'), 'touch: 2 files, 1 fails' ) ;
  5711. note( 'Leaving tests_touch()' ) ;
  5712. return ;
  5713. }
  5714. sub touch {
  5715. my @files = @_ ;
  5716. my $failures = 0 ;
  5717. foreach my $file ( @files ) {
  5718. my $fh = IO::File->new ;
  5719. if ( $fh->open(">> $file" ) ) {
  5720. $fh->close ;
  5721. }else{
  5722. myprint( "Could not open file $file in write/append mode\n" ) ;
  5723. $failures++ ;
  5724. }
  5725. }
  5726. return( ! $failures );
  5727. }
  5728. sub tests_tmpdir_has_colon_bug {
  5729. note( 'Entering tests_tmpdir_has_colon_bug()' ) ;
  5730. ok( 0 == tmpdir_has_colon_bug( q{} ), 'tmpdir_has_colon_bug: ' ) ;
  5731. ok( 0 == tmpdir_has_colon_bug( '/tmp' ), 'tmpdir_has_colon_bug: /tmp' ) ;
  5732. ok( 1 == tmpdir_has_colon_bug( 'C:' ), 'tmpdir_has_colon_bug: C:' ) ;
  5733. ok( 1 == tmpdir_has_colon_bug( 'C:\temp' ), 'tmpdir_has_colon_bug: C:\temp' ) ;
  5734. note( 'Leaving tests_tmpdir_has_colon_bug()' ) ;
  5735. return ;
  5736. }
  5737. sub tmpdir_has_colon_bug {
  5738. my $path = shift ;
  5739. my $path_filtered = filter_forbidden_characters( $path ) ;
  5740. if ( $path_filtered ne $path ) {
  5741. ( -d $path_filtered ) and myprint( "Path $path was previously mistakely changed to $path_filtered\n" ) ;
  5742. return( 1 ) ;
  5743. }
  5744. return( 0 ) ;
  5745. }
  5746. sub tmpdir_fix_colon_bug {
  5747. my $err = 0 ;
  5748. if ( not (-d $tmpdir and -r _ and -w _) ) {
  5749. myprint( "tmpdir $tmpdir is not valid\n" ) ;
  5750. return( 0 ) ;
  5751. }
  5752. my $cachedir_new = "$tmpdir/imapsync_cache" ;
  5753. if ( not tmpdir_has_colon_bug( $cachedir_new ) ) { return( 0 ) } ;
  5754. # check if old cache directory already exists
  5755. my $cachedir_old = filter_forbidden_characters( $cachedir_new ) ;
  5756. if ( not ( -d $cachedir_old ) ) {
  5757. myprint( "Old cache directory $cachedir_new no exists, nothing to do\n" ) ;
  5758. return( 1 ) ;
  5759. }
  5760. # check if new cache directory already exists
  5761. if ( -d $cachedir_new ) {
  5762. myprint( "New fixed cache directory $cachedir_new already exists, not moving the old one $cachedir_old. Fix this manually.\n" ) ;
  5763. return( 0 ) ;
  5764. }else{
  5765. # move the old one to the new place
  5766. myprint( "Moving $cachedir_old to $cachedir_new Do not interrupt this task.\n" ) ;
  5767. File::Copy::Recursive::rmove( $cachedir_old, $cachedir_new )
  5768. or do {
  5769. myprint( "Could not move $cachedir_old to $cachedir_new\n" ) ;
  5770. $err++ ;
  5771. } ;
  5772. # check it succeeded
  5773. if ( -d $cachedir_new and -r _ and -w _ ) {
  5774. myprint( "New fixed cache directory $cachedir_new ok\n" ) ;
  5775. }else{
  5776. myprint( "New fixed cache directory $cachedir_new does not exist\n" ) ;
  5777. $err++ ;
  5778. }
  5779. if ( -d $cachedir_old ) {
  5780. myprint( "Old cache directory $cachedir_old still exists\n" ) ;
  5781. $err++ ;
  5782. }else{
  5783. myprint( "Old cache directory $cachedir_old successfuly moved\n" ) ;
  5784. }
  5785. }
  5786. return( not $err ) ;
  5787. }
  5788. sub tests_cache_folder {
  5789. note( 'Entering tests_cache_folder()' ) ;
  5790. ok( '/path/fold1/fold2' eq cache_folder( q{}, '/path', 'fold1', 'fold2'), 'cache_folder: /path, fold1, fold2 -> /path/fold1/fold2' ) ;
  5791. ok( '/pa_th/fold1/fold2' eq cache_folder( q{}, '/pa*th', 'fold1', 'fold2'), 'cache_folder: /pa*th, fold1, fold2 -> /path/fold1/fold2' ) ;
  5792. 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' ) ;
  5793. ok( 'D:/path/fold1/fold2' eq cache_folder( 'D:', '/path', 'fold1', 'fold2'), 'cache_folder: /path, fold1, fold2 -> /path/fold1/fold2' ) ;
  5794. ok( 'D:/pa_th/fold1/fold2' eq cache_folder( 'D:', '/pa*th', 'fold1', 'fold2'), 'cache_folder: /pa*th, fold1, fold2 -> /path/fold1/fold2' ) ;
  5795. 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' ) ;
  5796. ok( '//' eq cache_folder( q{}, q{}, q{}, q{}), 'cache_folder: -> //' ) ;
  5797. ok( '//_______' eq cache_folder( q{}, q{}, q{}, '*|?:"<>'), 'cache_folder: *|?:"<> -> //_______' ) ;
  5798. note( 'Leaving tests_cache_folder()' ) ;
  5799. return ;
  5800. }
  5801. sub cache_folder {
  5802. my( $cache_base, $cache_dir, $h1_fold, $h2_fold ) = @_ ;
  5803. my $sep_1 = $h1_sep || '/';
  5804. my $sep_2 = $h2_sep || '/';
  5805. #myprint( "$cache_dir h1_fold $h1_fold sep1 $sep_1 h2_fold $h2_fold sep2 $sep_2\n" ) ;
  5806. $h1_fold = convert_sep_to_slash( $h1_fold, $sep_1 ) ;
  5807. $h2_fold = convert_sep_to_slash( $h2_fold, $sep_2 ) ;
  5808. my $cache_folder = "$cache_base" . filter_forbidden_characters( "$cache_dir/$h1_fold/$h2_fold" ) ;
  5809. #myprint( "cache_folder [$cache_folder]\n" ) ;
  5810. return( $cache_folder ) ;
  5811. }
  5812. sub filter_forbidden_characters {
  5813. my $string = shift ;
  5814. if ( ! defined $string ) { return ; }
  5815. if ( 'MSWin32' eq $OSNAME ) {
  5816. # Move trailing whitespace to _ " a b /c d " -> " a b_/c d_"
  5817. $string =~ s{\ (/|$)}{_$1}xg ;
  5818. }
  5819. $string =~ s{[\Q*|?:"<>\E]}{_}xg ;
  5820. #myprint( "[$string]\n" ) ;
  5821. return( $string ) ;
  5822. }
  5823. sub tests_filter_forbidden_characters {
  5824. note( 'Entering tests_filter_forbidden_characters()' ) ;
  5825. ok( 'a_b' eq filter_forbidden_characters( 'a_b' ), 'filter_forbidden_characters: a_b -> a_b' ) ;
  5826. ok( 'a_b' eq filter_forbidden_characters( 'a*b' ), 'filter_forbidden_characters: a*b -> a_b' ) ;
  5827. ok( 'a_b' eq filter_forbidden_characters( 'a|b' ), 'filter_forbidden_characters: a|b -> a_b' ) ;
  5828. ok( 'a_b' eq filter_forbidden_characters( 'a?b' ), 'filter_forbidden_characters: a?b -> a_b' ) ;
  5829. ok( 'a_______b' eq filter_forbidden_characters( 'a*|?:"<>b' ), 'filter_forbidden_characters: a*|?:"<>b -> a_______b' ) ;
  5830. SKIP: {
  5831. skip( 'Not on MSWin32', 1 ) if ( 'MSWin32' eq $OSNAME ) ;
  5832. ok( ( 'a b ' eq filter_forbidden_characters( 'a b ' ) ), 'filter_forbidden_characters: "a b " -> "a b "' ) ;
  5833. } ;
  5834. SKIP: {
  5835. skip( 'Only on MSWin32', 2 ) if ( 'MSWin32' ne $OSNAME ) ;
  5836. ok( ( ' a b_' eq filter_forbidden_characters( ' a b ' ) ), 'filter_forbidden_characters: "a b " -> "a b_"' ) ;
  5837. ok( ( ' a b_/ c d_' eq filter_forbidden_characters( ' a b / c d ' ) ), 'filter_forbidden_characters: " a b / c d " -> "a b_/ c d_"' ) ;
  5838. } ;
  5839. note( 'Leaving tests_filter_forbidden_characters()' ) ;
  5840. return ;
  5841. }
  5842. sub convert_sep_to_slash {
  5843. my ( $folder, $sep ) = @_ ;
  5844. $folder =~ s{\Q$sep\E}{/}xg ;
  5845. return( $folder ) ;
  5846. }
  5847. sub tests_convert_sep_to_slash {
  5848. note( 'Entering tests_convert_sep_to_slash()' ) ;
  5849. ok(q{} eq convert_sep_to_slash(q{}, '/'), 'convert_sep_to_slash: no folder');
  5850. ok('INBOX' eq convert_sep_to_slash('INBOX', '/'), 'convert_sep_to_slash: INBOX');
  5851. ok('INBOX/foo' eq convert_sep_to_slash('INBOX/foo', '/'), 'convert_sep_to_slash: INBOX/foo');
  5852. ok('INBOX/foo' eq convert_sep_to_slash('INBOX_foo', '_'), 'convert_sep_to_slash: INBOX_foo');
  5853. ok('INBOX/foo/zob' eq convert_sep_to_slash('INBOX_foo_zob', '_'), 'convert_sep_to_slash: INBOX_foo_zob');
  5854. ok('INBOX/foo' eq convert_sep_to_slash('INBOX.foo', '.'), 'convert_sep_to_slash: INBOX.foo');
  5855. ok('INBOX/foo/hi' eq convert_sep_to_slash('INBOX.foo.hi', '.'), 'convert_sep_to_slash: INBOX.foo.hi');
  5856. note( 'Leaving tests_convert_sep_to_slash()' ) ;
  5857. return ;
  5858. }
  5859. sub tests_regexmess {
  5860. note( 'Entering tests_regexmess()' ) ;
  5861. ok( 'blabla' eq regexmess( 'blabla' ), 'regexmess, no regexmess, nothing to do' ) ;
  5862. @regexmess = ( 'lalala' ) ;
  5863. ok( not( defined regexmess( 'popopo' ) ), 'regexmess, bad regex lalala' ) ;
  5864. @regexmess = ( 's/p/Z/g' ) ;
  5865. ok( 'ZoZoZo' eq regexmess( 'popopo' ), 'regexmess, s/p/Z/g' ) ;
  5866. @regexmess = ( 's{c}{C}gxms' ) ;
  5867. ok("H1: abC\nH2: Cde\n\nBody abC"
  5868. eq regexmess( "H1: abc\nH2: cde\n\nBody abc"),
  5869. 'regexmess, c->C');
  5870. @regexmess = ( 's{\AFrom\ }{From:}gxms' ) ;
  5871. ok( q{}
  5872. eq regexmess(q{}),
  5873. 'From mbox 1 add colon blank');
  5874. ok( 'From:<tartanpion@machin.truc>'
  5875. eq regexmess('From <tartanpion@machin.truc>'),
  5876. 'From mbox 2 add colo');
  5877. ok( "\n" . 'From <tartanpion@machin.truc>'
  5878. eq regexmess("\n" . 'From <tartanpion@machin.truc>'),
  5879. 'From mbox 3 add colo') ;
  5880. ok( "From: zzz\n" . 'From <tartanpion@machin.truc>'
  5881. eq regexmess("From zzz\n" . 'From <tartanpion@machin.truc>'),
  5882. 'From mbox 4 add colo') ;
  5883. @regexmess = ( 's{\AFrom\ [^\n]*(\n)?}{}gxms' ) ;
  5884. ok( q{}
  5885. eq regexmess(q{}),
  5886. 'From mbox 1 remove, blank');
  5887. ok( q{}
  5888. eq regexmess('From <tartanpion@machin.truc>'),
  5889. 'From mbox 2 remove');
  5890. ok( "\n" . 'From <tartanpion@machin.truc>'
  5891. eq regexmess("\n" . 'From <tartanpion@machin.truc>'),
  5892. 'From mbox 3 remove');
  5893. #myprint( "[", regexmess("From zzz\n" . 'From <tartanpion@machin.truc>'), "]" ) ;
  5894. ok( q{} . 'From <tartanpion@machin.truc>'
  5895. eq regexmess("From zzz\n" . 'From <tartanpion@machin.truc>'),
  5896. 'From mbox 4 remove');
  5897. ok(
  5898. <<'EOM'
  5899. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5900. From:<tartanpion@machin.truc>
  5901. Hello,
  5902. Bye.
  5903. EOM
  5904. eq regexmess(
  5905. <<'EOM'
  5906. From zzz
  5907. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5908. From:<tartanpion@machin.truc>
  5909. Hello,
  5910. Bye.
  5911. EOM
  5912. ), 'From mbox 5 remove');
  5913. @regexmess = ( 's{\A((?:[^\n]+\n)+|)^Disposition-Notification-To:[^\n]*\n(\r?\n|.*\n\r?\n)}{$1$2}xms' ) ; # SUPER SUPER BEST!
  5914. ok(
  5915. <<'EOM'
  5916. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5917. From:<tartanpion@machin.truc>
  5918. Hello,
  5919. Bye.
  5920. EOM
  5921. eq regexmess(
  5922. <<'EOM'
  5923. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5924. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  5925. From:<tartanpion@machin.truc>
  5926. Hello,
  5927. Bye.
  5928. EOM
  5929. ),
  5930. 'regexmess: 1 Delete header Disposition-Notification-To:');
  5931. ok(
  5932. <<'EOM'
  5933. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5934. From:<tartanpion@machin.truc>
  5935. Hello,
  5936. Bye.
  5937. EOM
  5938. eq regexmess(
  5939. <<'EOM'
  5940. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5941. From:<tartanpion@machin.truc>
  5942. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  5943. Hello,
  5944. Bye.
  5945. EOM
  5946. ),
  5947. 'regexmess: 2 Delete header Disposition-Notification-To:');
  5948. ok(
  5949. <<'EOM'
  5950. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5951. From:<tartanpion@machin.truc>
  5952. Hello,
  5953. Bye.
  5954. EOM
  5955. eq regexmess(
  5956. <<'EOM'
  5957. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  5958. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5959. From:<tartanpion@machin.truc>
  5960. Hello,
  5961. Bye.
  5962. EOM
  5963. ),
  5964. 'regexmess: 3 Delete header Disposition-Notification-To:');
  5965. ok(
  5966. <<'EOM'
  5967. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5968. From:<tartanpion@machin.truc>
  5969. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  5970. Bye.
  5971. EOM
  5972. eq regexmess(
  5973. <<'EOM'
  5974. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  5975. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5976. From:<tartanpion@machin.truc>
  5977. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  5978. Bye.
  5979. EOM
  5980. ),
  5981. 'regexmess: 4 Delete header Disposition-Notification-To:');
  5982. ok(
  5983. <<'EOM'
  5984. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5985. From:<tartanpion@machin.truc>
  5986. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  5987. Bye.
  5988. EOM
  5989. eq regexmess(
  5990. <<'EOM'
  5991. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5992. From:<tartanpion@machin.truc>
  5993. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  5994. Bye.
  5995. EOM
  5996. ),
  5997. 'regexmess: 5 Delete header Disposition-Notification-To:');
  5998. ok(
  5999. <<'EOM'
  6000. Date: Sat, 10 Jul 2010 05:34:45 -0700
  6001. From:<tartanpion@machin.truc>
  6002. Hello,
  6003. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  6004. Bye.
  6005. EOM
  6006. eq regexmess(
  6007. <<'EOM'
  6008. Date: Sat, 10 Jul 2010 05:34:45 -0700
  6009. From:<tartanpion@machin.truc>
  6010. Hello,
  6011. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  6012. Bye.
  6013. EOM
  6014. ),
  6015. 'regexmess: 6 Delete header Disposition-Notification-To:');
  6016. ok(
  6017. <<'EOM'
  6018. Date: Sat, 10 Jul 2010 05:34:45 -0700
  6019. From:<tartanpion@machin.truc>
  6020. Hello,
  6021. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  6022. Bye.
  6023. EOM
  6024. eq regexmess(
  6025. <<'EOM'
  6026. Date: Sat, 10 Jul 2010 05:34:45 -0700
  6027. From:<tartanpion@machin.truc>
  6028. Hello,
  6029. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  6030. Bye.
  6031. EOM
  6032. ),
  6033. 'regexmess: 7 Delete header Disposition-Notification-To:');
  6034. ok(
  6035. <<'EOM'
  6036. Date: Sat, 10 Jul 2010 05:34:45 -0700
  6037. From:<tartanpion@machin.truc>
  6038. Hello,
  6039. Bye.
  6040. EOM
  6041. eq regexmess(
  6042. <<'EOM'
  6043. Date: Sat, 10 Jul 2010 05:34:45 -0700
  6044. From:<tartanpion@machin.truc>
  6045. Hello,
  6046. Bye.
  6047. EOM
  6048. ),
  6049. 'regexmess: 8 Delete header Disposition-Notification-To:');
  6050. ok(
  6051. <<'EOM'
  6052. Date: Sat, 10 Jul 2010 05:34:45 -0700
  6053. From:<tartanpion@machin.truc>
  6054. Hello,
  6055. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  6056. Bye.
  6057. EOM
  6058. eq regexmess(
  6059. <<'EOM'
  6060. Date: Sat, 10 Jul 2010 05:34:45 -0700
  6061. From:<tartanpion@machin.truc>
  6062. Hello,
  6063. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  6064. Bye.
  6065. EOM
  6066. ),
  6067. 'regexmess: 9 Delete header Disposition-Notification-To:');
  6068. ok(
  6069. <<'EOM'
  6070. Date: Sat, 10 Jul 2010 05:34:45 -0700
  6071. From:<tartanpion@machin.truc>
  6072. Hello,
  6073. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  6074. Bye.
  6075. EOM
  6076. eq regexmess(
  6077. <<'EOM'
  6078. Date: Sat, 10 Jul 2010 05:34:45 -0700
  6079. From:<tartanpion@machin.truc>
  6080. Hello,
  6081. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  6082. Bye.
  6083. EOM
  6084. ),
  6085. 'regexmess: 10 Delete header Disposition-Notification-To:');
  6086. ok(
  6087. <<'EOM'
  6088. Date: Sat, 10 Jul 2010 05:34:45 -0700
  6089. From:<tartanpion@machin.truc>
  6090. Hello,
  6091. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  6092. Bye.
  6093. EOM
  6094. eq regexmess(
  6095. <<'EOM'
  6096. Date: Sat, 10 Jul 2010 05:34:45 -0700
  6097. From:<tartanpion@machin.truc>
  6098. Hello,
  6099. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  6100. Bye.
  6101. EOM
  6102. ),
  6103. 'regexmess: 11 Delete header Disposition-Notification-To:');
  6104. ok(
  6105. <<'EOM'
  6106. Date: Sat, 10 Jul 2010 05:34:45 -0700
  6107. From:<tartanpion@machin.truc>
  6108. Hello,
  6109. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  6110. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  6111. Bye.
  6112. EOM
  6113. eq regexmess(
  6114. <<'EOM'
  6115. Date: Sat, 10 Jul 2010 05:34:45 -0700
  6116. From:<tartanpion@machin.truc>
  6117. Hello,
  6118. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  6119. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  6120. Bye.
  6121. EOM
  6122. ),
  6123. 'regexmess: 12 Delete header Disposition-Notification-To:');
  6124. @regexmess = ( 's{\A(.*?(?! ^$))^Disposition-Notification-To:(.*?)$}{$1X-Disposition-Notification-To:$2}igxms' ) ; # BAD!
  6125. @regexmess = ( 's{\A((?:[^\n]+\n)+|)(^Disposition-Notification-To:[^\n]*\n)(\r?\n|.*\n\r?\n)}{$1X-$2$3}ims' ) ;
  6126. ok(
  6127. <<'EOM'
  6128. Date: Sat, 10 Jul 2010 05:34:45 -0700
  6129. From:<tartanpion@machin.truc>
  6130. Hello,
  6131. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  6132. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  6133. Bye.
  6134. EOM
  6135. eq regexmess(
  6136. <<'EOM'
  6137. Date: Sat, 10 Jul 2010 05:34:45 -0700
  6138. From:<tartanpion@machin.truc>
  6139. Hello,
  6140. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  6141. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  6142. Bye.
  6143. EOM
  6144. ),
  6145. 'regexmess: 13 Delete header Disposition-Notification-To:');
  6146. ok(
  6147. <<'EOM'
  6148. Date: Sat, 10 Jul 2010 05:34:45 -0700
  6149. X-Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  6150. From:<tartanpion@machin.truc>
  6151. Hello,
  6152. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  6153. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  6154. Bye.
  6155. EOM
  6156. eq regexmess(
  6157. <<'EOM'
  6158. Date: Sat, 10 Jul 2010 05:34:45 -0700
  6159. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  6160. From:<tartanpion@machin.truc>
  6161. Hello,
  6162. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  6163. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  6164. Bye.
  6165. EOM
  6166. ),
  6167. 'regexmess: 14 Delete header Disposition-Notification-To:');
  6168. ok(
  6169. <<'EOM'
  6170. Date: Sat, 10 Jul 2010 05:34:45 -0700
  6171. X-Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  6172. From:<tartanpion@machin.truc>
  6173. Hello,
  6174. Bye.
  6175. EOM
  6176. eq regexmess(
  6177. <<'EOM'
  6178. Date: Sat, 10 Jul 2010 05:34:45 -0700
  6179. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  6180. From:<tartanpion@machin.truc>
  6181. Hello,
  6182. Bye.
  6183. EOM
  6184. ),
  6185. 'regexmess: 15 Delete header Disposition-Notification-To:');
  6186. ok(
  6187. <<'EOM'
  6188. Date: Sat, 10 Jul 2010 05:34:45 -0700
  6189. From:<tartanpion@machin.truc>
  6190. X-Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  6191. Hello,
  6192. Bye.
  6193. EOM
  6194. eq regexmess(
  6195. <<'EOM'
  6196. Date: Sat, 10 Jul 2010 05:34:45 -0700
  6197. From:<tartanpion@machin.truc>
  6198. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  6199. Hello,
  6200. Bye.
  6201. EOM
  6202. ),
  6203. 'regexmess: 16 Delete header Disposition-Notification-To:');
  6204. ok(
  6205. <<'EOM'
  6206. X-Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  6207. Date: Sat, 10 Jul 2010 05:34:45 -0700
  6208. From:<tartanpion@machin.truc>
  6209. Hello,
  6210. Bye.
  6211. EOM
  6212. eq regexmess(
  6213. <<'EOM'
  6214. Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
  6215. Date: Sat, 10 Jul 2010 05:34:45 -0700
  6216. From:<tartanpion@machin.truc>
  6217. Hello,
  6218. Bye.
  6219. EOM
  6220. ),
  6221. 'regexmess: 17 Delete header Disposition-Notification-To:');
  6222. # regex to play with Date: from the FAQ
  6223. #@regexmess = 's{\A(.*?(?! ^$))^Date:(.*?)$}{$1Date:$2\nX-Date:$2}gxms'
  6224. note( 'Leaving tests_regexmess()' ) ;
  6225. return ;
  6226. }
  6227. sub regexmess {
  6228. my ( $string ) = @_ ;
  6229. foreach my $regexmess ( @regexmess ) {
  6230. $debug and myprint( "eval \$string =~ $regexmess\n" ) ;
  6231. my $ret = eval "\$string =~ $regexmess ; 1" ;
  6232. #myprint( "eval [$ret]\n" ) ;
  6233. if ( ( not $ret ) or $EVAL_ERROR ) {
  6234. myprint( "Error: eval regexmess '$regexmess': $EVAL_ERROR" ) ;
  6235. return( undef ) ;
  6236. }
  6237. }
  6238. $debug and myprint( "$string\n" ) ;
  6239. return( $string ) ;
  6240. }
  6241. sub tests_skipmess {
  6242. note( 'Entering tests_skipmess()' ) ;
  6243. ok( not( defined skipmess( 'blabla' ) ), 'skipmess, no skipmess, no skip' ) ;
  6244. @skipmess = ('[') ;
  6245. ok( not( defined skipmess( 'popopo' ) ), 'skipmess, bad regex [' ) ;
  6246. @skipmess = ('lalala') ;
  6247. ok( not( defined skipmess( 'popopo' ) ), 'skipmess, bad regex lalala' ) ;
  6248. @skipmess = ('/popopo/') ;
  6249. ok( 1 == skipmess( 'popopo' ), 'skipmess, popopo match regex /popopo/' ) ;
  6250. @skipmess = ('/popopo/') ;
  6251. ok( 0 == skipmess( 'rrrrrr' ), 'skipmess, rrrrrr does not match regex /popopo/' ) ;
  6252. @skipmess = ('m{^$}') ;
  6253. ok( 1 == skipmess( q{} ), 'skipmess: empty string yes' ) ;
  6254. ok( 0 == skipmess( 'Hi!' ), 'skipmess: empty string no' ) ;
  6255. @skipmess = ('m{i}') ;
  6256. ok( 1 == skipmess( 'Hi!' ), 'skipmess: i string yes' ) ;
  6257. ok( 0 == skipmess( 'Bye!' ), 'skipmess: i string no' ) ;
  6258. @skipmess = ('m{[\x80-\xff]}') ;
  6259. ok( 0 == skipmess( 'Hi!' ), 'skipmess: i 8bit no' ) ;
  6260. ok( 1 == skipmess( "\xff" ), 'skipmess: \xff 8bit yes' ) ;
  6261. @skipmess = ('m{A}', 'm{B}') ;
  6262. ok( 0 == skipmess( 'Hi!' ), 'skipmess: A or B no' ) ;
  6263. ok( 0 == skipmess( 'lala' ), 'skipmess: A or B no' ) ;
  6264. ok( 0 == skipmess( "\xff" ), 'skipmess: A or B no' ) ;
  6265. ok( 1 == skipmess( 'AB' ), 'skipmess: A or B yes' ) ;
  6266. ok( 1 == skipmess( 'BA' ), 'skipmess: A or B yes' ) ;
  6267. ok( 1 == skipmess( 'AA' ), 'skipmess: A or B yes' ) ;
  6268. ok( 1 == skipmess( 'Ok Bye' ), 'skipmess: A or B yes' ) ;
  6269. @skipmess = ( 'm#\A((?:[^\n]+\n)+|)^Content-Type: Message/Partial;[^\n]*\n(?:\n|.*\n\n)#ism' ) ; # SUPER BEST!
  6270. ok( 1 == skipmess(
  6271. <<'EOM'
  6272. Date: Sat, 10 Jul 2010 05:34:45 -0700
  6273. Content-Type: Message/Partial; blabla
  6274. From:<tartanpion@machin.truc>
  6275. Hello!
  6276. Bye.
  6277. EOM
  6278. ),
  6279. 'skipmess: 1 match Content-Type: Message/Partial' ) ;
  6280. ok( 0 == skipmess(
  6281. <<'EOM'
  6282. Date: Sat, 10 Jul 2010 05:34:45 -0700
  6283. From:<tartanpion@machin.truc>
  6284. Hello!
  6285. Bye.
  6286. EOM
  6287. ),
  6288. 'skipmess: 2 not match Content-Type: Message/Partial' ) ;
  6289. ok( 1 == skipmess(
  6290. <<'EOM'
  6291. Date: Sat, 10 Jul 2010 05:34:45 -0700
  6292. From:<tartanpion@machin.truc>
  6293. Content-Type: Message/Partial; blabla
  6294. Hello!
  6295. Bye.
  6296. EOM
  6297. ),
  6298. 'skipmess: 3 match Content-Type: Message/Partial' ) ;
  6299. ok( 0 == skipmess(
  6300. <<'EOM'
  6301. Date: Sat, 10 Jul 2010 05:34:45 -0700
  6302. From:<tartanpion@machin.truc>
  6303. Hello!
  6304. Content-Type: Message/Partial; blabla
  6305. Bye.
  6306. EOM
  6307. ),
  6308. 'skipmess: 4 not match Content-Type: Message/Partial' ) ;
  6309. ok( 0 == skipmess(
  6310. <<'EOM'
  6311. Date: Sat, 10 Jul 2010 05:34:45 -0700
  6312. From:<tartanpion@machin.truc>
  6313. Hello!
  6314. Content-Type: Message/Partial; blabla
  6315. Bye.
  6316. EOM
  6317. ),
  6318. 'skipmess: 5 not match Content-Type: Message/Partial' ) ;
  6319. ok( 1 == skipmess(
  6320. <<'EOM'
  6321. Date: Sat, 10 Jul 2010 05:34:45 -0700
  6322. Content-Type: Message/Partial; blabla
  6323. From:<tartanpion@machin.truc>
  6324. Hello!
  6325. Content-Type: Message/Partial; blabla
  6326. Bye.
  6327. EOM
  6328. ),
  6329. 'skipmess: 6 match Content-Type: Message/Partial' ) ;
  6330. ok( 1 == skipmess(
  6331. <<'EOM'
  6332. Date: Sat, 10 Jul 2010 05:34:45 -0700
  6333. Content-Type: Message/Partial;
  6334. From:<tartanpion@machin.truc>
  6335. Hello!
  6336. Bye.
  6337. EOM
  6338. ),
  6339. 'skipmess: 7 match Content-Type: Message/Partial' ) ;
  6340. ok( 1 == skipmess(
  6341. <<'EOM'
  6342. Date: Wed, 2 Jul 2014 02:26:40 +0000
  6343. MIME-Version: 1.0
  6344. Content-Type: message/partial;
  6345. id="TAN_U_P<1404267997.00007489ed17>";
  6346. number=3;
  6347. total=3
  6348. 6HQ6Hh3CdXj77qEGixerQ6zHx0OnQ/Cf5On4W0Y6vtU2crABZQtD46Hx1EOh8dDz4+OnTr1G
  6349. Hello!
  6350. Bye.
  6351. EOM
  6352. ),
  6353. 'skipmess: 8 match Content-Type: Message/Partial' ) ;
  6354. ok( 1 == skipmess(
  6355. <<'EOM'
  6356. Return-Path: <gilles@lamiral.info>
  6357. Received: by lamiral.info (Postfix, from userid 1000)
  6358. id 21EB12443BF; Mon, 2 Mar 2015 15:38:35 +0100 (CET)
  6359. Subject: test: aethaecohngiexao
  6360. To: <tata@petite.lamiral.info>
  6361. X-Mailer: mail (GNU Mailutils 2.2)
  6362. Message-Id: <20150302143835.21EB12443BF@lamiral.info>
  6363. Content-Type: message/partial;
  6364. id="TAN_U_P<1404267997.00007489ed17>";
  6365. number=3;
  6366. total=3
  6367. Date: Mon, 2 Mar 2015 15:38:34 +0100 (CET)
  6368. From: gilles@lamiral.info (Gilles LAMIRAL)
  6369. test: aethaecohngiexao
  6370. EOM
  6371. ),
  6372. 'skipmess: 9 match Content-Type: Message/Partial' ) ;
  6373. ok( 1 == skipmess(
  6374. <<'EOM'
  6375. Date: Mon, 2 Mar 2015 15:38:34 +0100 (CET)
  6376. From: gilles@lamiral.info (Gilles LAMIRAL)
  6377. Content-Type: message/partial;
  6378. id="TAN_U_P<1404267997.00007489ed17>";
  6379. number=3;
  6380. total=3
  6381. test: aethaecohngiexao
  6382. EOM
  6383. . "lalala\n" x 3_000_000
  6384. ),
  6385. 'skipmess: 10 match Content-Type: Message/Partial' ) ;
  6386. ok( 0 == skipmess(
  6387. <<'EOM'
  6388. Date: Mon, 2 Mar 2015 15:38:34 +0100 (CET)
  6389. From: gilles@lamiral.info (Gilles LAMIRAL)
  6390. test: aethaecohngiexao
  6391. EOM
  6392. . "lalala\n" x 3_000_000
  6393. ),
  6394. 'skipmess: 11 match Content-Type: Message/Partial' ) ;
  6395. ok( 0 == skipmess(
  6396. <<"EOM"
  6397. From: fff\r
  6398. To: fff\r
  6399. Subject: Testing imapsync --skipmess\r
  6400. Date: Mon, 22 Aug 2011 08:40:20 +0800\r
  6401. Mime-Version: 1.0\r
  6402. Content-Type: text/plain; charset=iso-8859-1\r
  6403. Content-Transfer-Encoding: 7bit\r
  6404. \r
  6405. EOM
  6406. . qq{!#"d%&'()*+,-./0123456789:;<=>?\@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefg\r\n } x 32_730
  6407. ),
  6408. 'skipmess: 12 not match Content-Type: Message/Partial' ) ;
  6409. # Complex regular subexpression recursion limit (32766) exceeded with more lines
  6410. # exit;
  6411. note( 'Leaving tests_skipmess()' ) ;
  6412. return ;
  6413. }
  6414. sub skipmess {
  6415. my ( $string ) = @_ ;
  6416. my $match ;
  6417. #myprint( "$string\n" ) ;
  6418. foreach my $skipmess ( @skipmess ) {
  6419. $debug and myprint( "eval \$match = \$string =~ $skipmess\n" ) ;
  6420. my $ret = eval "\$match = \$string =~ $skipmess ; 1" ;
  6421. #myprint( "eval [$ret]\n" ) ;
  6422. $debug and myprint( "match [$match]\n" ) ;
  6423. if ( ( not $ret ) or $EVAL_ERROR ) {
  6424. myprint( "Error: eval skipmess '$skipmess': $EVAL_ERROR" ) ;
  6425. return( undef ) ;
  6426. }
  6427. return( $match ) if ( $match ) ;
  6428. }
  6429. return( $match ) ;
  6430. }
  6431. sub tests_bytes_display_string {
  6432. note( 'Entering tests_bytes_display_string()' ) ;
  6433. is( 'NA', bytes_display_string( ), 'bytes_display_string: no args => NA' ) ;
  6434. is( 'NA', bytes_display_string( undef ), 'bytes_display_string: undef => NA' ) ;
  6435. is( 'NA', bytes_display_string( 'blabla' ), 'bytes_display_string: blabla => NA' ) ;
  6436. ok( '0.000 KiB' eq bytes_display_string( 0 ), 'bytes_display_string: 0' ) ;
  6437. ok( '0.001 KiB' eq bytes_display_string( 1 ), 'bytes_display_string: 1' ) ;
  6438. ok( '0.010 KiB' eq bytes_display_string( 10 ), 'bytes_display_string: 10' ) ;
  6439. ok( '1.000 MiB' eq bytes_display_string( 1_048_575 ), 'bytes_display_string: 1_048_575' ) ;
  6440. ok( '1.000 MiB' eq bytes_display_string( 1_048_576 ), 'bytes_display_string: 1_048_576' ) ;
  6441. ok( '1.000 GiB' eq bytes_display_string( 1_073_741_823 ), 'bytes_display_string: 1_073_741_823 ' ) ;
  6442. ok( '1.000 GiB' eq bytes_display_string( 1_073_741_824 ), 'bytes_display_string: 1_073_741_824 ' ) ;
  6443. ok( '1.000 TiB' eq bytes_display_string( 1_099_511_627_775 ), 'bytes_display_string: 1_099_511_627_775' ) ;
  6444. ok( '1.000 TiB' eq bytes_display_string( 1_099_511_627_776 ), 'bytes_display_string: 1_099_511_627_776' ) ;
  6445. ok( '1.000 PiB' eq bytes_display_string( 1_125_899_906_842_623 ), 'bytes_display_string: 1_125_899_906_842_623' ) ;
  6446. ok( '1.000 PiB' eq bytes_display_string( 1_125_899_906_842_624 ), 'bytes_display_string: 1_125_899_906_842_624' ) ;
  6447. ok( '1024.000 PiB' eq bytes_display_string( 1_152_921_504_606_846_975 ), 'bytes_display_string: 1_152_921_504_606_846_975' ) ;
  6448. ok( '1024.000 PiB' eq bytes_display_string( 1_152_921_504_606_846_976 ), 'bytes_display_string: 1_152_921_504_606_846_976' ) ;
  6449. ok( '1048576.000 PiB' eq bytes_display_string( 1_180_591_620_717_411_303_424 ), 'bytes_display_string: 1_180_591_620_717_411_303_424' ) ;
  6450. #myprint( bytes_display_string( 1_180_591_620_717_411_303_424 ), "\n" ) ;
  6451. note( 'Leaving tests_bytes_display_string()' ) ;
  6452. return ;
  6453. }
  6454. sub bytes_display_string {
  6455. my ( $bytes ) = @_ ;
  6456. my $readable_value = q{} ;
  6457. if ( ! defined( $bytes ) ) {
  6458. return( 'NA' ) ;
  6459. }
  6460. if ( not match_number( $bytes ) ) {
  6461. return( 'NA' ) ;
  6462. }
  6463. SWITCH: {
  6464. if ( abs( $bytes ) < ( 1000 * $KIBI ) ) {
  6465. $readable_value = mysprintf( '%.3f KiB', $bytes / $KIBI) ;
  6466. last SWITCH ;
  6467. }
  6468. if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI ) ) {
  6469. $readable_value = mysprintf( '%.3f MiB', $bytes / ($KIBI * $KIBI) ) ;
  6470. last SWITCH ;
  6471. }
  6472. if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI * $KIBI) ) {
  6473. $readable_value = mysprintf( '%.3f GiB', $bytes / ($KIBI * $KIBI * $KIBI) ) ;
  6474. last SWITCH ;
  6475. }
  6476. if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI * $KIBI * $KIBI) ) {
  6477. $readable_value = mysprintf( '%.3f TiB', $bytes / ($KIBI * $KIBI * $KIBI * $KIBI) ) ;
  6478. last SWITCH ;
  6479. } else {
  6480. $readable_value = mysprintf( '%.3f PiB', $bytes / ($KIBI * $KIBI * $KIBI * $KIBI * $KIBI) ) ;
  6481. }
  6482. # if you have exabytes (EiB) of email to transfer, you have too much email!
  6483. }
  6484. #myprint( "$bytes = $readable_value\n" ) ;
  6485. return( $readable_value ) ;
  6486. }
  6487. sub stats {
  6488. my $mysync = shift ;
  6489. if ( ! $mysync->{stats} ) {
  6490. return ;
  6491. }
  6492. my $timeend = time ;
  6493. my $timediff = $timeend - $mysync->{timestart} ;
  6494. my $timeend_str = localtime $timeend ;
  6495. my $memory_consumption = 0 ;
  6496. $memory_consumption = memory_consumption( ) || 0 ;
  6497. my $memory_ratio = ($max_msg_size_in_bytes) ?
  6498. mysprintf('%.1f', $memory_consumption / $max_msg_size_in_bytes) : 'NA' ;
  6499. myprint( "++++ Statistics\n" ) ;
  6500. myprint( "Transfer started on : $timestart_str\n" ) ;
  6501. myprint( "Transfer ended on : $timeend_str\n" ) ;
  6502. myprintf( "Transfer time : %.1f sec\n", $timediff ) ;
  6503. myprint( "Folders synced : $h1_folders_wanted_ct/$h1_folders_wanted_nb synced\n" ) ;
  6504. myprint( "Messages transferred : $mysync->{nb_msg_transferred} " ) ;
  6505. myprint( "(could be $nb_msg_skipped_dry_mode without dry mode)" ) if ( $mysync->{dry} ) ;
  6506. myprint( "\n" ) ;
  6507. myprint( "Messages skipped : $nb_msg_skipped\n" ) ;
  6508. myprint( "Messages found duplicate on host1 : $h1_nb_msg_duplicate\n" ) ;
  6509. myprint( "Messages found duplicate on host2 : $h2_nb_msg_duplicate\n" ) ;
  6510. myprint( "Messages void (noheader) on host1 : $h1_nb_msg_noheader\n" ) ;
  6511. myprint( "Messages void (noheader) on host2 : $h2_nb_msg_noheader\n" ) ;
  6512. myprint( "Messages deleted on host1 : $h1_nb_msg_deleted\n" ) ;
  6513. myprint( "Messages deleted on host2 : $h2_nb_msg_deleted\n" ) ;
  6514. myprintf( "Total bytes transferred : %s (%s)\n",
  6515. $mysync->{total_bytes_transferred},
  6516. bytes_display_string( $mysync->{total_bytes_transferred} ) ) ;
  6517. myprintf( "Total bytes duplicate host1 : %s (%s)\n",
  6518. $h1_total_bytes_duplicate,
  6519. bytes_display_string( $h1_total_bytes_duplicate) ) ;
  6520. myprintf( "Total bytes duplicate host2 : %s (%s)\n",
  6521. $h2_total_bytes_duplicate,
  6522. bytes_display_string( $h2_total_bytes_duplicate) ) ;
  6523. myprintf( "Total bytes skipped : %s (%s)\n",
  6524. $total_bytes_skipped,
  6525. bytes_display_string( $total_bytes_skipped ) ) ;
  6526. myprintf( "Total bytes error : %s (%s)\n",
  6527. $total_bytes_error,
  6528. bytes_display_string( $total_bytes_error ) ) ;
  6529. $timediff ||= 1 ; # No division per 0
  6530. myprintf("Message rate : %.1f messages/s\n", $mysync->{nb_msg_transferred} / $timediff ) ;
  6531. myprintf("Average bandwidth rate : %.1f KiB/s\n", $mysync->{total_bytes_transferred} / $KIBI / $timediff ) ;
  6532. myprint( "Reconnections to host1 : $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT}\n" ) ;
  6533. myprint( "Reconnections to host2 : $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT}\n" ) ;
  6534. myprintf("Memory consumption : %.1f MiB\n", $memory_consumption / $KIBI / $KIBI ) ;
  6535. myprintf("Biggest message : %s bytes (%s)\n",
  6536. $max_msg_size_in_bytes,
  6537. bytes_display_string( $max_msg_size_in_bytes) ) ;
  6538. myprint( "Memory/biggest message ratio : $memory_ratio\n" ) ;
  6539. if ( $foldersizesatend and $foldersizes ) {
  6540. my $nb_msg_start_diff = diff_or_NA( $h2_nb_msg_start, $h1_nb_msg_start ) ;
  6541. my $bytes_start_diff = diff_or_NA( $h2_bytes_start, $h1_bytes_start ) ;
  6542. myprintf("Start difference host2 - host1 : %s messages, %s bytes (%s)\n", $nb_msg_start_diff,
  6543. $bytes_start_diff,
  6544. bytes_display_string( $bytes_start_diff ) ) ;
  6545. my $nb_msg_end_diff = diff_or_NA( $h2_nb_msg_end, $h1_nb_msg_end ) ;
  6546. my $bytes_end_diff = diff_or_NA( $h2_bytes_end, $h1_bytes_end ) ;
  6547. myprintf("Final difference host2 - host1 : %s messages, %s bytes (%s)\n", $nb_msg_end_diff,
  6548. $bytes_end_diff,
  6549. bytes_display_string( $bytes_end_diff ) ) ;
  6550. }
  6551. myprint( "Detected $mysync->{nb_errors} errors\n\n" ) ;
  6552. myprint( $warn_release, "\n" ) ;
  6553. myprint( homepage( ), "\n" ) ;
  6554. return ;
  6555. }
  6556. sub diff_or_NA {
  6557. my( $n1, $n2 ) = @ARG ;
  6558. if ( not defined $n1 or not defined $n2 ) {
  6559. return 'NA' ;
  6560. }
  6561. if ( not match_number( $n1 )
  6562. or not match_number( $n2 ) ) {
  6563. return 'NA' ;
  6564. }
  6565. return( $n1 - $n2 ) ;
  6566. }
  6567. sub match_number {
  6568. my $n = shift @ARG ;
  6569. if ( not defined $n ) {
  6570. return 0 ;
  6571. }
  6572. if ( $n =~ /[0-9]+\.?[0-9]?/x ) {
  6573. return 1 ;
  6574. }
  6575. else {
  6576. return 0 ;
  6577. }
  6578. }
  6579. sub tests_match_number {
  6580. note( 'Entering tests_match_number()' ) ;
  6581. is( 0, match_number( ), 'match_number: no parameters => 0' ) ;
  6582. is( 0, match_number( undef ), 'match_number: undef => 0' ) ;
  6583. is( 0, match_number( 'blabla' ), 'match_number: blabla => 0' ) ;
  6584. is( 1, match_number( 0 ), 'match_number: 0 => 1' ) ;
  6585. is( 1, match_number( 1 ), 'match_number: 1 => 1' ) ;
  6586. is( 1, match_number( 1.0 ), 'match_number: 1.0 => 1' ) ;
  6587. is( 1, match_number( 0.0 ), 'match_number: 0.0 => 1' ) ;
  6588. note( 'Leaving tests_match_number()' ) ;
  6589. return ;
  6590. }
  6591. sub tests_diff_or_NA {
  6592. note( 'Entering tests_diff_or_NA()' ) ;
  6593. is( 'NA', diff_or_NA( ), 'diff_or_NA: no parameters => NA' ) ;
  6594. is( 'NA', diff_or_NA( undef ), 'diff_or_NA: undef => NA' ) ;
  6595. is( 'NA', diff_or_NA( undef, undef ), 'diff_or_NA: undef undef => NA' ) ;
  6596. is( 'NA', diff_or_NA( undef, 1 ), 'diff_or_NA: undef 1 => NA' ) ;
  6597. is( 'NA', diff_or_NA( 1, undef ), 'diff_or_NA: 1 undef => NA' ) ;
  6598. is( 'NA', diff_or_NA( 'blabla', 1 ), 'diff_or_NA: blabla 1 => NA' ) ;
  6599. is( 'NA', diff_or_NA( 1, 'blabla' ), 'diff_or_NA: 1 blabla => NA' ) ;
  6600. is( 0, diff_or_NA( 1, 1 ), 'diff_or_NA: 1 1 => 0' ) ;
  6601. is( 1, diff_or_NA( 1, 0 ), 'diff_or_NA: 1 0 => 1' ) ;
  6602. is( -1, diff_or_NA( 0, 1 ), 'diff_or_NA: 0 1 => -1' ) ;
  6603. is( 0, diff_or_NA( 1.0, 1 ), 'diff_or_NA: 1.0 1 => 0' ) ;
  6604. is( 1, diff_or_NA( 1.0, 0 ), 'diff_or_NA: 1.0 0 => 1' ) ;
  6605. is( -1, diff_or_NA( 0, 1.0 ), 'diff_or_NA: 0 1.0 => -1' ) ;
  6606. note( 'Leaving tests_diff_or_NA()' ) ;
  6607. return ;
  6608. }
  6609. sub homepage {
  6610. return( 'Homepage: http://imapsync.lamiral.info/' ) ;
  6611. }
  6612. sub load_modules {
  6613. if ( $sync->{ssl1}
  6614. or $sync->{ssl2}
  6615. or $sync->{tls1}
  6616. or $sync->{tls2}) {
  6617. if ( $sync->{inet4} ) {
  6618. IO::Socket::SSL->import( 'inet4' ) ;
  6619. }
  6620. if ( $sync->{inet6} ) {
  6621. IO::Socket::SSL->import( 'inet6' ) ;
  6622. }
  6623. }
  6624. return ;
  6625. }
  6626. sub parse_header_msg {
  6627. my ( $imap, $m_uid, $s_heads, $s_fir, $side, $s_hash ) = @_ ;
  6628. my $head = $s_heads->{$m_uid} ;
  6629. my $headnum = scalar keys %{ $head } ;
  6630. $debug and myprint( "$side uid $m_uid head nb pass one: ", $headnum, "\n" ) ;
  6631. if ( ( ! $headnum ) and ( $wholeheaderifneeded ) ){
  6632. myprint( "$side uid $m_uid no header by parse_headers so taking whole header with BODY.PEEK[HEADER]\n" ) ;
  6633. $imap->fetch($m_uid, 'BODY.PEEK[HEADER]' ) ;
  6634. my $whole_header = $imap->_transaction_literals ;
  6635. #myprint( $whole_header ) ;
  6636. $head = decompose_header( $whole_header ) ;
  6637. $headnum = scalar keys %{ $head } ;
  6638. $debug and myprint( "$side uid $m_uid head nb pass two: ", $headnum, "\n" ) ;
  6639. }
  6640. #myprint( Data::Dumper->Dump( [ $head, \%useheader ] ) ) ;
  6641. my $headstr ;
  6642. $headstr = header_construct( $head, $side, $m_uid ) ;
  6643. if ( ( ! $headstr) and ( $addheader ) and ( $side eq 'Host1' ) ) {
  6644. my $header = add_header( $m_uid ) ;
  6645. myprint( "Host1 uid $m_uid no header found so adding our own [$header]\n" ) ;
  6646. $headstr .= uc $header ;
  6647. $s_fir->{$m_uid}->{NO_HEADER} = 1;
  6648. }
  6649. return if ( ! $headstr ) ;
  6650. my $size = $s_fir->{$m_uid}->{'RFC822.SIZE'} ;
  6651. my $flags = $s_fir->{$m_uid}->{'FLAGS'} ;
  6652. my $idate = $s_fir->{$m_uid}->{'INTERNALDATE'} ;
  6653. $size = length $headstr unless ( $size ) ;
  6654. my $m_md5 = md5_base64( $headstr ) ;
  6655. $debug and myprint( "$side uid $m_uid sig $m_md5 size $size idate $idate\n" ) ;
  6656. my $key ;
  6657. if ($skipsize) {
  6658. $key = "$m_md5";
  6659. }
  6660. else {
  6661. $key = "$m_md5:$size";
  6662. }
  6663. # 0 return code is used to identify duplicate message hash
  6664. return 0 if exists $s_hash->{"$key"};
  6665. $s_hash->{"$key"}{'5'} = $m_md5;
  6666. $s_hash->{"$key"}{'s'} = $size;
  6667. $s_hash->{"$key"}{'D'} = $idate;
  6668. $s_hash->{"$key"}{'F'} = $flags;
  6669. $s_hash->{"$key"}{'m'} = $m_uid;
  6670. return( 1 ) ;
  6671. }
  6672. sub header_construct {
  6673. my( $head, $side, $m_uid ) = @_ ;
  6674. my $headstr ;
  6675. foreach my $h ( sort keys %{ $head } ) {
  6676. next if ( not ( exists $useheader{ uc $h } )
  6677. and ( not exists $useheader{ 'ALL' } )
  6678. ) ;
  6679. foreach my $val ( sort @{$head->{$h}} ) {
  6680. my $H = header_line_normalize( $h, $val ) ;
  6681. # show stuff in debug mode
  6682. $debug and myprint( "$side uid $m_uid header [$H]", "\n" ) ;
  6683. if ($skipheader and $H =~ m/$skipheader/xi) {
  6684. $debug and myprint( "$side uid $m_uid skipping header [$H]\n" ) ;
  6685. next ;
  6686. }
  6687. $headstr .= "$H" ;
  6688. }
  6689. }
  6690. return( $headstr ) ;
  6691. }
  6692. sub header_line_normalize {
  6693. my( $header_key, $header_val ) = @_ ;
  6694. # no 8-bit data in headers !
  6695. $header_val =~ s/[\x80-\xff]/X/xog;
  6696. # change tabulations to space (Gmail bug on with "Received:" on multilines)
  6697. $header_val =~ s/\t/\ /xgo ;
  6698. # remove the first blanks ( dbmail bug? )
  6699. $header_val =~ s/^\s*//xo;
  6700. # remove the last blanks ( Gmail bug )
  6701. $header_val =~ s/\s*$//xo;
  6702. # remove successive blanks ( Mailenable does it )
  6703. $header_val =~ s/\s+/ /xgo;
  6704. # remove Message-Id value domain part ( Mailenable changes it )
  6705. if ( ( $messageidnodomain ) and ( 'MESSAGE-ID' eq uc $header_key ) ) { $header_val =~ s/^([^@]+).*$/$1/xo ; }
  6706. # and uppercase header line
  6707. # (dbmail and dovecot)
  6708. my $header_line = uc "$header_key: $header_val" ;
  6709. return( $header_line ) ;
  6710. }
  6711. sub tests_header_line_normalize {
  6712. note( 'Entering tests_header_line_normalize()' ) ;
  6713. ok( ': ' eq header_line_normalize( q{}, q{} ), 'header_line_normalize: empty args' ) ;
  6714. ok( 'HHH: VVV' eq header_line_normalize( 'hhh', 'vvv' ), 'header_line_normalize: hhh vvv ' ) ;
  6715. ok( 'HHH: VVV' eq header_line_normalize( 'hhh', ' vvv' ), 'header_line_normalize: remove first blancs' ) ;
  6716. ok( 'HHH: AA BB CCC D' eq header_line_normalize( 'hhh', 'aa bb ccc d' ), 'header_line_normalize: remove succesive blanks' ) ;
  6717. ok( 'HHH: AA BB CCC' eq header_line_normalize( 'hhh', 'aa bb ccc ' ), 'header_line_normalize: remove last blanks' ) ;
  6718. ok( 'HHH: VVV XX YY' eq header_line_normalize( 'hhh', "vvv\t\txx\tyy" ), 'header_line_normalize: tabs' ) ;
  6719. ok( 'HHH: XABX' eq header_line_normalize( 'hhh', "\x80AB\xff" ), 'header_line_normalize: 8bit' ) ;
  6720. note( 'Leaving tests_header_line_normalize()' ) ;
  6721. return ;
  6722. }
  6723. sub firstline {
  6724. # extract the first line of a file (without \n)
  6725. my( $file ) = @_ ;
  6726. my $line = q{} ;
  6727. if ( ! -e $file ) {
  6728. myprint( "Cannot open file $file since it does not exist\n" ) ;
  6729. return ;
  6730. }
  6731. open my $FILE, '<', $file or do {
  6732. myprint( "Error opening file $file : $OS_ERROR\n" ) ;
  6733. return ;
  6734. } ;
  6735. $line = <$FILE> || q{} ;
  6736. close $FILE ;
  6737. chomp $line ;
  6738. return $line ;
  6739. }
  6740. sub tests_firstline {
  6741. note( 'Entering tests_firstline()' ) ;
  6742. is( undef , firstline( 'W/tmp/tests/noexist.txt' ), 'tests_firstline: not getting blabla from W/tmp/tests/noexist.txt' ) ;
  6743. is( "blabla\n" , string_to_file( "blabla\n", 'W/tmp/tests/firstline.txt' ), 'tests_firstline: put blabla in W/tmp/tests/firstline.txt' ) ;
  6744. is( 'blabla' , firstline( 'W/tmp/tests/firstline.txt' ), 'tests_firstline: get blabla from W/tmp/tests/firstline.txt' ) ;
  6745. is( q{} , string_to_file( q{}, 'W/tmp/tests/firstline2.txt' ), 'tests_firstline: put empty string in W/tmp/tests/firstline2.txt' ) ;
  6746. is( q{} , firstline( 'W/tmp/tests/firstline2.txt' ), 'tests_firstline: get empty string from W/tmp/tests/firstline2.txt' ) ;
  6747. is( "\n" , string_to_file( "\n", 'W/tmp/tests/firstline3.txt' ), 'tests_firstline: put CR in W/tmp/tests/firstline3.txt' ) ;
  6748. is( q{} , firstline( 'W/tmp/tests/firstline3.txt' ), 'tests_firstline: get empty string from W/tmp/tests/firstline3.txt' ) ;
  6749. note( 'Leaving tests_firstline()' ) ;
  6750. return ;
  6751. }
  6752. # Should be unit tested and then be used by file_to_string, refactoring file_to_string
  6753. sub file_to_array {
  6754. my( $file ) = shift ;
  6755. my @string ;
  6756. open my $FILE, '<', $file or do {
  6757. myprint( "Error reading file $file : $OS_ERROR" ) ;
  6758. return ;
  6759. } ;
  6760. @string = <$FILE> ;
  6761. close $FILE ;
  6762. return( @string ) ;
  6763. }
  6764. sub tests_file_to_string {
  6765. note( 'Entering tests_file_to_string()' ) ;
  6766. is( undef, file_to_string( ), 'file_to_string: no args => undef' ) ;
  6767. is( undef, file_to_string( '/noexist' ), 'file_to_string: /noexist => undef' ) ;
  6768. is( undef, file_to_string( '/' ), 'file_to_string: reading a directory => undef' ) ;
  6769. ok( file_to_string( $PROGRAM_NAME ), 'file_to_string: reading myself' ) ;
  6770. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'file_to_string: mkpath W/tmp/tests/' ) ;
  6771. is( 'lilili', string_to_file( 'lilili', 'W/tmp/tests/canbewritten' ), 'file_to_string: string_to_file filling W/tmp/tests/canbewritten with lilili' ) ;
  6772. is( 'lilili', file_to_string( 'W/tmp/tests/canbewritten' ), 'file_to_string: reading W/tmp/tests/canbewritten is lilili' ) ;
  6773. is( q{}, string_to_file( q{}, 'W/tmp/tests/empty' ), 'file_to_string: string_to_file filling W/tmp/tests/empty with empty string' ) ;
  6774. is( q{}, file_to_string( 'W/tmp/tests/empty' ), 'file_to_string: reading W/tmp/tests/empty is empty' ) ;
  6775. note( 'Leaving tests_file_to_string()' ) ;
  6776. return ;
  6777. }
  6778. sub file_to_string {
  6779. my $file = shift ;
  6780. if ( ! $file ) { return ; }
  6781. if ( ! -e $file ) { return ; }
  6782. if ( ! -f $file ) { return ; }
  6783. if ( ! -r $file ) { return ; }
  6784. my @string ;
  6785. if ( open my $FILE, '<', $file ) {
  6786. @string = <$FILE> ;
  6787. close $FILE ;
  6788. return( join q{}, @string ) ;
  6789. }else{
  6790. myprint( "Error reading file $file : $OS_ERROR\n" ) ;
  6791. return ;
  6792. }
  6793. }
  6794. sub tests_string_to_file {
  6795. note( 'Entering tests_string_to_file()' ) ;
  6796. is( undef, string_to_file( ), 'string_to_file: no args => undef' ) ;
  6797. is( undef, string_to_file( 'lalala' ), 'string_to_file: one arg => undef' ) ;
  6798. is( undef, string_to_file( 'lalala', '.' ), 'string_to_file: writing a directory => undef' ) ;
  6799. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'string_to_file: mkpath W/tmp/tests/' ) ;
  6800. is( 'lalala', string_to_file( 'lalala', 'W/tmp/tests/canbewritten' ), 'string_to_file: W/tmp/tests/canbewritten with lalala' ) ;
  6801. is( q{}, string_to_file( q{}, 'W/tmp/tests/empty' ), 'string_to_file: W/tmp/tests/empty with empty string' ) ;
  6802. SKIP: {
  6803. Readonly my $NB_UNX_tests_string_to_file => 1 ;
  6804. skip( 'Not on Unix', $NB_UNX_tests_string_to_file ) if ('MSWin32' eq $OSNAME) ;
  6805. is( undef, string_to_file( 'lalala', '/cantouch' ), 'string_to_file: /cantouch denied => undef' ) ;
  6806. }
  6807. note( 'Leaving tests_string_to_file()' ) ;
  6808. return ;
  6809. }
  6810. sub string_to_file {
  6811. my( $string, $file ) = @_ ;
  6812. if( ! defined $string ) { return ; }
  6813. if( ! defined $file ) { return ; }
  6814. if ( ! -e $file && ! -w dirname( $file ) ) {
  6815. myprint( "string_to_file: directory of $file is not writable\n" ) ;
  6816. return ;
  6817. }
  6818. if ( ! sysopen( FILE, $file, O_WRONLY|O_TRUNC|O_CREAT, 0600) ) {
  6819. myprint( "string_to_file: failure writing to $file with error: $OS_ERROR\n" ) ;
  6820. return ;
  6821. }
  6822. print FILE $string ;
  6823. close FILE ;
  6824. return $string ;
  6825. }
  6826. q^
  6827. This is a multiline comment.
  6828. Based on David Carter discussion, to do:
  6829. * Call parameters stay the same.
  6830. * Now always "return( $string, $error )". Descriptions below.
  6831. OK * Still capture STDOUT via "1> $output_tmpfile" to finish in $string and "return( $string, $error )"
  6832. OK * Now also capture STDERR via "2> $error_tmpfile" to finish in $error and "return( $string, $error )"
  6833. OK * in case of CHILD_ERROR, return( undef, $error )
  6834. and print $error, with folder/UID/maybeSubject context,
  6835. on console and at the end with the final error listing. Count this as a sync error.
  6836. * in case of good command, take final $string as is, unless void. In case $error with value then print it.
  6837. * in case of good command and final $string empty, consider it like CHILD_ERROR =>
  6838. return( undef, $error ) and print $error, with folder/UID/maybeSubject context,
  6839. on console and at the end with the final error listing. Count this as a sync error.
  6840. ^ if 0 ; # End of multiline comment.
  6841. sub pipemess {
  6842. my ( $string, @commands ) = @_ ;
  6843. my $error = q{} ;
  6844. foreach my $command ( @commands ) {
  6845. my $input_tmpfile = "$tmpdir/imapsync_tmp_file.$PROCESS_ID.inp.txt" ;
  6846. my $output_tmpfile = "$tmpdir/imapsync_tmp_file.$PROCESS_ID.out.txt" ;
  6847. my $error_tmpfile = "$tmpdir/imapsync_tmp_file.$PROCESS_ID.err.txt" ;
  6848. string_to_file( $string, $input_tmpfile ) ;
  6849. ` $command < $input_tmpfile 1> $output_tmpfile 2> $error_tmpfile ` ;
  6850. my $is_command_ko = $CHILD_ERROR ;
  6851. my $error_cmd = file_to_string( $error_tmpfile ) ;
  6852. chomp( $error_cmd ) ;
  6853. $string = file_to_string( $output_tmpfile ) ;
  6854. my $string_len = length( $string ) ;
  6855. unlink $input_tmpfile, $output_tmpfile, $error_tmpfile ;
  6856. if ( $is_command_ko or ( ! $string_len ) ) {
  6857. my $cmd_exit_value = $CHILD_ERROR >> 8 ;
  6858. my $cmd_end_signal = $CHILD_ERROR & 127 ;
  6859. my $signal_log = ( $cmd_end_signal ) ? " signal $cmd_end_signal and" : q{} ;
  6860. 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} ;
  6861. myprint( $error_log ) ;
  6862. if ( wantarray ) {
  6863. return @{ [ undef, $error_log ] }
  6864. }else{
  6865. return ;
  6866. }
  6867. }
  6868. if ( $error_cmd ) {
  6869. $error .= qq{STDERR of --pipemess "$command": $error_cmd\n} ;
  6870. myprint( qq{STDERR of --pipemess "$command": $error_cmd\n} ) ;
  6871. }
  6872. }
  6873. #myprint( "[$string]\n" ) ;
  6874. if ( wantarray ) {
  6875. return ( $string, $error ) ;
  6876. }else{
  6877. return $string ;
  6878. }
  6879. }
  6880. sub tests_pipemess {
  6881. note( 'Entering tests_pipemess()' ) ;
  6882. SKIP: {
  6883. Readonly my $NB_WIN_tests_pipemess => 3 ;
  6884. skip( 'Not on MSWin32', $NB_WIN_tests_pipemess ) if ('MSWin32' ne $OSNAME) ;
  6885. # Windows
  6886. # "type" command does not accept redirection of STDIN with <
  6887. # "sort" does
  6888. ok( "nochange\n" eq pipemess( 'nochange', 'sort' ), 'pipemess: nearly no change by sort' ) ;
  6889. ok( "nochange2\n" eq pipemess( 'nochange2', qw( sort sort ) ), 'pipemess: nearly no change by sort,sort' ) ;
  6890. # command not found
  6891. #diag( 'Warning and failure about cacaprout are on purpose' ) ;
  6892. ok( ! defined( pipemess( q{}, 'cacaprout' ) ), 'pipemess: command not found' ) ;
  6893. } ;
  6894. my ( $stringT, $errorT ) ;
  6895. SKIP: {
  6896. Readonly my $NB_UNX_tests_pipemess => 25 ;
  6897. skip( 'Not on Unix', $NB_UNX_tests_pipemess ) if ('MSWin32' eq $OSNAME) ;
  6898. # Unix
  6899. ok( 'nochange' eq pipemess( 'nochange', 'cat' ), 'pipemess: no change by cat' ) ;
  6900. ok( 'nochange2' eq pipemess( 'nochange2', 'cat', 'cat' ), 'pipemess: no change by cat,cat' ) ;
  6901. ok( " 1\tnumberize\n" eq pipemess( "numberize\n", 'cat -n' ), 'pipemess: numberize by cat -n' ) ;
  6902. ok( " 1\tnumberize\n 2\tnumberize\n" eq pipemess( "numberize\nnumberize\n", 'cat -n' ), 'pipemess: numberize by cat -n' ) ;
  6903. ok( "A\nB\nC\n" eq pipemess( "A\nC\nB\n", 'sort' ), 'pipemess: sort' ) ;
  6904. # command not found
  6905. #diag( 'Warning and failure about cacaprout are on purpose' ) ;
  6906. is( undef, pipemess( q{}, 'cacaprout' ), 'pipemess: command not found' ) ;
  6907. # success with true but no output at all
  6908. is( undef, pipemess( q{blabla}, 'true' ), 'pipemess: true but no output' ) ;
  6909. # failure with false and no output at all
  6910. is( undef, pipemess( q{blabla}, 'false' ), 'pipemess: false and no output' ) ;
  6911. # Failure since pipemess is not a real pipe, so first cat wait for standard input
  6912. is( q{blabla}, pipemess( q{blabla}, '( cat|cat ) ' ), 'pipemess: ok by ( cat|cat )' ) ;
  6913. ( $stringT, $errorT ) = pipemess( 'nochange', 'cat' ) ;
  6914. is( $stringT, 'nochange', 'pipemess: list context, no change by cat, string' ) ;
  6915. is( $errorT, q{}, 'pipemess: list context, no change by cat, no error' ) ;
  6916. ( $stringT, $errorT ) = pipemess( 'dontcare', 'true' ) ;
  6917. is( $stringT, undef, 'pipemess: list context, true but no output, string' ) ;
  6918. 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' ) ;
  6919. ( $stringT, $errorT ) = pipemess( 'dontcare', 'false' ) ;
  6920. is( $stringT, undef, 'pipemess: list context, false and no output, string' ) ;
  6921. like( $errorT, qr{\QFailure: --pipemess command "false" ended with "0" characters exit value "1" and STDERR ""\E}xm,
  6922. 'pipemess: list context, false and no output, error' ) ;
  6923. ( $stringT, $errorT ) = pipemess( 'dontcare', '/bin/echo -n blablabla' ) ;
  6924. is( $stringT, q{blablabla}, 'pipemess: list context, "echo -n blablabla", string' ) ;
  6925. is( $errorT, q{}, 'pipemess: list context, "echo blablabla", error' ) ;
  6926. ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo -n blablabla 3>&1 1>&2 2>&3 )' ) ;
  6927. is( $stringT, undef, 'pipemess: list context, "no output STDERR blablabla", string' ) ;
  6928. like( $errorT, qr{blablabla"}xm, 'pipemess: list context, "no output STDERR blablabla", error' ) ;
  6929. ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo -n blablabla 3>&1 1>&2 2>&3 )', 'false' ) ;
  6930. is( $stringT, undef, 'pipemess: list context, "no output STDERR blablabla then false", string' ) ;
  6931. like( $errorT, qr{blablabla"}xm, 'pipemess: list context, "no output STDERR blablabla then false", error' ) ;
  6932. ( $stringT, $errorT ) = pipemess( 'dontcare', 'false', '( echo -n blablabla 3>&1 1>&2 2>&3 )' ) ;
  6933. is( $stringT, undef, 'pipemess: list context, "false then STDERR blablabla", string' ) ;
  6934. like( $errorT, qr{\QFailure: --pipemess command "false" ended with "0" characters exit value "1" and STDERR ""\E}xm,
  6935. 'pipemess: list context, "false then STDERR blablabla", error' ) ;
  6936. ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo rrrrr ; echo -n error_blablabla 3>&1 1>&2 2>&3 )' ) ;
  6937. like( $stringT, qr{rrrrr}xm, 'pipemess: list context, "STDOUT rrrrr STDERR error_blablabla", string' ) ;
  6938. like( $errorT, qr{STDERR.*error_blablabla}xm, 'pipemess: list context, "STDOUT rrrrr STDERR error_blablabla", error' ) ;
  6939. }
  6940. ( $stringT, $errorT ) = pipemess( 'dontcare', 'cacaprout' ) ;
  6941. is( $stringT, undef, 'pipemess: list context, cacaprout not found, string' ) ;
  6942. like( $errorT, qr{\QFailure: --pipemess command "cacaprout" ended with "0" characters exit value\E}xm,
  6943. 'pipemess: list context, cacaprout not found, error' ) ;
  6944. note( 'Leaving tests_pipemess()' ) ;
  6945. return ;
  6946. }
  6947. sub tests_is_a_release_number {
  6948. note( 'Entering tests_is_a_release_number()' ) ;
  6949. ok(is_a_release_number($RELEASE_NUMBER_EXAMPLE_1), 'is_a_release_number 1.351') ;
  6950. ok(is_a_release_number($RELEASE_NUMBER_EXAMPLE_2), 'is_a_release_number 42.4242') ;
  6951. ok(is_a_release_number( imapsync_version( $sync ) ), 'is_a_release_number imapsync_version( )') ;
  6952. ok(! is_a_release_number('blabla' ), '! is_a_release_number blabla') ;
  6953. note( 'Leaving tests_is_a_release_number()' ) ;
  6954. return ;
  6955. }
  6956. sub is_a_release_number {
  6957. my $number = shift;
  6958. return( $number =~ m{^\d+\.\d+$}xo ) ;
  6959. }
  6960. sub imapsync_version_public {
  6961. my $local_version = imapsync_version( $sync ) ;
  6962. my $imapsync_basename = imapsync_basename( ) ;
  6963. my $agent_info = "$OSNAME system, perl "
  6964. . mysprintf( '%vd', $PERL_VERSION)
  6965. . ", Mail::IMAPClient $Mail::IMAPClient::VERSION"
  6966. . " $imapsync_basename" ;
  6967. my $sock = IO::Socket::INET->new(
  6968. PeerAddr => 'imapsync.lamiral.info',
  6969. PeerPort => 80,
  6970. Proto => 'tcp',
  6971. ) ;
  6972. return( 'unknown' ) if not $sock ;
  6973. print $sock
  6974. "GET /prj/imapsync/VERSION HTTP/1.0\r\n",
  6975. "User-Agent: imapsync/$local_version ($agent_info)\r\n",
  6976. "Host: ks.lamiral.info\r\n\r\n" ;
  6977. my @line = <$sock> ;
  6978. close $sock ;
  6979. my $last_release = $line[$LAST] ;
  6980. chomp $last_release ;
  6981. return( $last_release ) ;
  6982. }
  6983. sub not_long_imapsync_version_public {
  6984. #myprint( "Entering not_long_imapsync_version_public\n" ) ;
  6985. my $fake = shift ;
  6986. if ( $fake ) { return $fake }
  6987. my $val ;
  6988. # Doesn't work with gethostbyname (see perlipc)
  6989. #local $SIG{ALRM} = sub { die "alarm\n" } ;
  6990. if ('MSWin32' eq $OSNAME) {
  6991. local $SIG{ALRM} = sub { die "alarm\n" } ;
  6992. }else{
  6993. POSIX::sigaction(SIGALRM,
  6994. POSIX::SigAction->new(sub { croak 'alarm' } ) )
  6995. or myprint( "Error setting SIGALRM handler: $OS_ERROR\n" ) ;
  6996. }
  6997. my $ret = eval {
  6998. alarm 3 ;
  6999. {
  7000. $val = imapsync_version_public( ) ;
  7001. #sleep 4 ;
  7002. #myprint( "End of imapsync_version_public\n" ) ;
  7003. }
  7004. alarm 0 ;
  7005. 1 ;
  7006. } ;
  7007. #myprint( "eval [$ret]\n" ) ;
  7008. if ( ( not $ret ) or $EVAL_ERROR ) {
  7009. #myprint( "$EVAL_ERROR" ) ;
  7010. if ($EVAL_ERROR =~ /alarm/) {
  7011. # timed out
  7012. return('timeout') ;
  7013. }else{
  7014. alarm 0 ;
  7015. return( 'unknown' ) ; # propagate unexpected errors
  7016. }
  7017. }else {
  7018. # Good!
  7019. return( $val ) ;
  7020. }
  7021. }
  7022. sub tests_not_long_imapsync_version_public {
  7023. note( 'Entering tests_not_long_imapsync_version_public()' ) ;
  7024. is( 1, is_a_release_number( not_long_imapsync_version_public( ) ),
  7025. 'not_long_imapsync_version_public: public release is a number' ) ;
  7026. note( 'Leaving tests_not_long_imapsync_version_public()' ) ;
  7027. return ;
  7028. }
  7029. sub check_last_release {
  7030. my $fake = shift ;
  7031. my $public_release = not_long_imapsync_version_public( $fake ) ;
  7032. $debug and myprint( "check_last_release: [$public_release]\n" ) ;
  7033. return( 'Imapsync public release is unknown' ) if ( $public_release eq 'unknown' ) ;
  7034. return( 'Imapsync public release is unknown (timeout)' ) if ( $public_release eq 'timeout' ) ;
  7035. return( "Imapsync public release is unknown ($public_release)" ) if ( ! is_a_release_number( $public_release ) ) ;
  7036. my $imapsync_here = imapsync_version( $sync ) ;
  7037. if ( $public_release > $imapsync_here ) {
  7038. return(
  7039. "New imapsync release $public_release available to replace this $imapsync_here\n"
  7040. . "Get it at https://imapsync.lamiral.info/dist/") ;
  7041. }else{
  7042. return( 'This imapsync is up to date. ' . "( local $imapsync_here >= official $public_release )") ;
  7043. }
  7044. return('really unknown') ; # Should never arrive here
  7045. }
  7046. sub tests_check_last_release {
  7047. note( 'Entering tests_check_last_release()' ) ;
  7048. diag( check_last_release( 1.1 ) ) ;
  7049. like( check_last_release( 1.1 ), qr/\Qup to date\E/mxs, 'check_last_release: up to date' ) ;
  7050. like( check_last_release( 1.1 ), qr/1\.1/mxs, 'check_last_release: up to date, include number' ) ;
  7051. diag( check_last_release( 999.999 ) ) ;
  7052. like( check_last_release( 999.999 ), qr/available/mxs, 'check_last_release: update available' ) ;
  7053. like( check_last_release( 999.999 ), qr/999\.999/mxs, 'check_last_release: update available, include number' ) ;
  7054. diag( check_last_release( ) ) ;
  7055. is( 'Imapsync public release is unknown', check_last_release( 'unknown' ), 'check_last_release: unknown' ) ;
  7056. is( 'Imapsync public release is unknown (timeout)', check_last_release( 'timeout' ), 'check_last_release: timeout' ) ;
  7057. is( 'Imapsync public release is unknown (lalala)', check_last_release( 'lalala' ), 'check_last_release: lalala' ) ;
  7058. note( 'Leaving tests_check_last_release()' ) ;
  7059. return ;
  7060. }
  7061. sub imapsync_version {
  7062. my $mysync = shift ;
  7063. my $rcs = $mysync->{rcs} ;
  7064. my $imapsync_version ;
  7065. $imapsync_version = version_from_rcs( $rcs ) ;
  7066. return( $imapsync_version ) ;
  7067. }
  7068. sub tests_version_from_rcs {
  7069. note( 'Entering tests_version_from_rcs()' ) ;
  7070. is( undef, version_from_rcs( ), 'version_from_rcs: no args => UNKNOWN' ) ;
  7071. 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' ) ;
  7072. is( 'UNKNOWN', version_from_rcs( 1.831 ), 'version_from_rcs: 1.831 => UNKNOWN' ) ;
  7073. note( 'Leaving tests_version_from_rcs()' ) ;
  7074. return ;
  7075. }
  7076. sub version_from_rcs {
  7077. my $rcs = shift ;
  7078. if ( ! $rcs ) { return ; }
  7079. my $version = 'UNKNOWN' ;
  7080. if ( $rcs =~ m{,v\s+(\d+\.\d+)}mxso ) {
  7081. $version = $1
  7082. }
  7083. return( $version ) ;
  7084. }
  7085. sub tests_imapsync_basename {
  7086. note( 'Entering tests_imapsync_basename()' ) ;
  7087. ok( imapsync_basename() =~ m/imapsync/, 'imapsync_basename: match imapsync');
  7088. ok( 'blabla' ne imapsync_basename(), 'imapsync_basename: do not equal blabla');
  7089. note( 'Leaving tests_imapsync_basename()' ) ;
  7090. return ;
  7091. }
  7092. sub imapsync_basename {
  7093. return basename( $PROGRAM_NAME ) ;
  7094. }
  7095. sub localhost_info {
  7096. my( $infos ) = join q{},
  7097. "Here is " . hostname() . ", a " . memory_available( ) . " [$OSNAME] system (",
  7098. join(q{ },
  7099. uname(),
  7100. ),
  7101. ")\n",
  7102. 'with Perl ',
  7103. mysprintf( '%vd ', $PERL_VERSION),
  7104. "and Mail::IMAPClient $Mail::IMAPClient::VERSION",
  7105. ;
  7106. return( $infos ) ;
  7107. }
  7108. sub tests_cpu_number {
  7109. note( 'Entering tests_cpu_number()' ) ;
  7110. ok( 1 <= cpu_number( ), "cpu_number: 1 or more" ) ;
  7111. note( 'Leaving tests_cpu_number()' ) ;
  7112. return ;
  7113. }
  7114. sub cpu_number {
  7115. my @cpuinfo ;
  7116. # Well, here 1 is better than 0 or undef
  7117. my $cpu_number = 1 ; # Default value, erased if better found
  7118. if ( $ENV{"NUMBER_OF_PROCESSORS"} ) {
  7119. # might be under a Windows system
  7120. $cpu_number = $ENV{"NUMBER_OF_PROCESSORS"} ;
  7121. $debug and myprint( "Number of processors found by envvar NUMBER_OF_PROCESSORS: $cpu_number\n" ) ;
  7122. return $cpu_number ;
  7123. }
  7124. if ( 'darwin' eq $OSNAME ) {
  7125. $cpu_number = `sysctl -n hw.ncpu` ;
  7126. chomp( $cpu_number ) ;
  7127. return $cpu_number ;
  7128. }
  7129. if ( ! -e '/proc/cpuinfo' ) {
  7130. $debug and myprint( "Number of processors not found so use: $cpu_number\n" ) ;
  7131. return $cpu_number ;
  7132. }
  7133. @cpuinfo = file_to_array( '/proc/cpuinfo' ) ;
  7134. if ( @cpuinfo ) {
  7135. $cpu_number = grep { /^processor/mxs } @cpuinfo ;
  7136. }
  7137. $debug and myprint( "Number of processors found via /proc/cpuinfo: $cpu_number\n" ) ;
  7138. return $cpu_number ;
  7139. }
  7140. sub tests_loadavg {
  7141. note( 'Entering tests_loadavg()' ) ;
  7142. SKIP: {
  7143. skip( 'Tests for darwin', 2 ) if ('darwin' ne $OSNAME) ;
  7144. is( undef, loadavg( '/noexist' ), 'loadavg: /noexist => undef' ) ;
  7145. is_deeply( [ '0.11', '0.22', '0.33' ],
  7146. [ loadavg( 'W/t/loadavg.out' ) ],
  7147. 'loadavg W/t/loadavg.out => 0.11 0.22 0.33' ) ;
  7148. } ;
  7149. SKIP: {
  7150. skip( 'Tests for linux', 3 ) if ('linux' ne $OSNAME) ;
  7151. is( undef, loadavg( '/noexist' ), 'loadavg: /noexist => undef' ) ;
  7152. ok( loadavg( ), 'loadavg: no args' ) ;
  7153. is_deeply( [ '0.39', '0.30', '0.37', '1/602' ],
  7154. [ loadavg( '0.39 0.30 0.37 1/602 6073' ) ],
  7155. 'loadavg 0.39 0.30 0.37 1/602 6073 => [0.39, 0.30, 0.37, 1/602]' ) ;
  7156. } ;
  7157. SKIP: {
  7158. skip( 'Tests for Windows', 1 ) if ('MSWin32' ne $OSNAME) ;
  7159. is_deeply( [ 0 ],
  7160. [ loadavg( ) ],
  7161. 'loadavg on MSWin32 => 0' ) ;
  7162. } ;
  7163. note( 'Leaving tests_loadavg()' ) ;
  7164. return ;
  7165. }
  7166. sub loadavg {
  7167. if ( 'linux' eq $OSNAME ) {
  7168. return ( loadavg_linux( @ARG ) ) ;
  7169. }
  7170. if ( 'darwin' eq $OSNAME ) {
  7171. return ( loadavg_darwin( @ARG ) ) ;
  7172. }
  7173. if ( 'MSWin32' eq $OSNAME ) {
  7174. return ( loadavg_windows( @ARG ) ) ;
  7175. }
  7176. return( 'unknown' ) ;
  7177. }
  7178. sub loadavg_linux {
  7179. my $line = shift ;
  7180. if ( ! $line ) {
  7181. $line = firstline( '/proc/loadavg' ) or return ;
  7182. }
  7183. my ( $avg_1_min, $avg_5_min, $avg_15_min, $current_runs ) = split /\s/mxs, $line ;
  7184. if ( all_defined( $avg_1_min, $avg_5_min, $avg_15_min ) ) {
  7185. $debug and print "System load: $avg_1_min $avg_5_min $avg_15_min $current_runs\n" ;
  7186. return ( $avg_1_min, $avg_5_min, $avg_15_min, $current_runs ) ;
  7187. }
  7188. return ;
  7189. }
  7190. sub loadavg_darwin {
  7191. my $file = shift ;
  7192. # Example of output of command "sysctl vm.loadavg":
  7193. # vm.loadavg: { 0.15 0.08 0.08 }
  7194. my $loadavg ;
  7195. if ( ! defined $file ) {
  7196. eval {
  7197. $loadavg = `/usr/sbin/sysctl vm.loadavg` ;
  7198. #myprint( "LOADAVG DARWIN: $loadavg\n" ) ;
  7199. } ;
  7200. if ( $EVAL_ERROR ) { myprint( "[$EVAL_ERROR]\n" ) ; return ; }
  7201. }else{
  7202. $loadavg = firstline( $file ) or return ;
  7203. }
  7204. my ( $avg_1_min, $avg_5_min, $avg_15_min )
  7205. = $loadavg =~ /vm\.loadavg\s*[:=]\s*\{?\s*(\d+\.?\d*)\s+(\d+\.?\d*)\s+(\d+\.?\d*)/mxs ;
  7206. $debug and print "System load: $avg_1_min $avg_5_min $avg_15_min\n" ;
  7207. return ( $avg_1_min, $avg_5_min, $avg_15_min ) ;
  7208. }
  7209. sub loadavg_windows {
  7210. my $file = shift ;
  7211. # Example of output of command "wmic cpu get loadpercentage":
  7212. # LoadPercentage
  7213. # 12
  7214. my $loadavg ;
  7215. if ( ! defined $file ) {
  7216. eval {
  7217. #$loadavg = `CMD wmic cpu get loadpercentage` ;
  7218. $loadavg = "LoadPercentage\n0\n" ;
  7219. #myprint( "LOADAVG WIN: $loadavg\n" ) ;
  7220. } ;
  7221. if ( $EVAL_ERROR ) { myprint( "[$EVAL_ERROR]\n" ) ; return ; }
  7222. }else{
  7223. $loadavg = file_to_string( $file ) or return ;
  7224. #myprint( "$loadavg" ) ;
  7225. }
  7226. $loadavg =~ /LoadPercentage\n(\d+)/xms ;
  7227. my $num = $1 ;
  7228. $num /= 100 ;
  7229. $debug and myprint( "System load: $num\n" ) ;
  7230. return ( $num ) ;
  7231. }
  7232. sub tests_load_and_delay {
  7233. note( 'Entering tests_load_and_delay()' ) ;
  7234. is( undef, load_and_delay( ), 'load_and_delay: no args => undef ' ) ;
  7235. is( undef, load_and_delay( 1 ), 'load_and_delay: not 4 args => undef ' ) ;
  7236. is( undef, load_and_delay( 0, 1, 1, 1 ), 'load_and_delay: division per 0 => undef ' ) ;
  7237. is( 0, load_and_delay( 1, 1, 1, 1 ), 'load_and_delay: one core, loads are all 1 => ok ' ) ;
  7238. is( 0, load_and_delay( 2, 2, 2, 2 ), 'load_and_delay: two core, loads are all 2 => ok ' ) ;
  7239. is( 0, load_and_delay( 2, 2, 4, 5 ), 'load_and_delay: two core, load1m is 2 => ok ' ) ;
  7240. is( 0, load_and_delay( 1, 0, 0, 0 ), 'load_and_delay: one core, load1m=0 load5m=0 load15m=0 => 0 ' ) ;
  7241. is( 0, load_and_delay( 1, 0, 0, 2 ), 'load_and_delay: one core, load1m=0 load5m=0 load15m=2 => 0 ' ) ;
  7242. is( 0, load_and_delay( 1, 0, 2, 0 ), 'load_and_delay: one core, load1m=0 load5m=2 load15m=0 => 0 ' ) ;
  7243. is( 0, load_and_delay( 1, 0, 2, 2 ), 'load_and_delay: one core, load1m=0 load5m=2 load15m=2 => 0 ' ) ;
  7244. is( 1, load_and_delay( 1, 2, 0, 0 ), 'load_and_delay: one core, load1m=2 load5m=0 load15m=0 => 1 ' ) ;
  7245. is( 1, load_and_delay( 1, 2, 0, 2 ), 'load_and_delay: one core, load1m=2 load5m=0 load15m=2 => 1 ' ) ;
  7246. is( 5, load_and_delay( 1, 2, 2, 0 ), 'load_and_delay: one core, load1m=2 load5m=2 load15m=0 => 5 ' ) ;
  7247. is( 15, load_and_delay( 1, 2, 2, 2 ), 'load_and_delay: one core, load1m=2 load5m=2 load15m=2 => 15 ' ) ;
  7248. is( 0, load_and_delay( 4, 0, 2, 2 ), 'load_and_delay: four core, load1m=0 load5m=2 load15m=2 => 0 ' ) ;
  7249. is( 1, load_and_delay( 4, 8, 0, 0 ), 'load_and_delay: four core, load1m=2 load5m=0 load15m=0 => 1 ' ) ;
  7250. is( 1, load_and_delay( 4, 8, 0, 2 ), 'load_and_delay: four core, load1m=2 load5m=0 load15m=2 => 1 ' ) ;
  7251. is( 5, load_and_delay( 4, 8, 8, 0 ), 'load_and_delay: four core, load1m=2 load5m=2 load15m=0 => 5 ' ) ;
  7252. is( 15, load_and_delay( 4, 8, 8, 8 ), 'load_and_delay: four core, load1m=2 load5m=2 load15m=2 => 15 ' ) ;
  7253. is( 15, load_and_delay( 4, 8, 8, 8, 'lalala' ), 'load_and_delay: five arguments is ok' ) ;
  7254. note( 'Leaving tests_load_and_delay()' ) ;
  7255. return ;
  7256. }
  7257. sub load_and_delay {
  7258. # Basically return 0 if load is not heavy, ie <= 1 per processor
  7259. if ( 4 > scalar @ARG ) { return ; }
  7260. my ( $cpu_num, $avg_1_min, $avg_5_min, $avg_15_min ) = @ARG ;
  7261. if ( 0 == $cpu_num ) { return ; }
  7262. # Let divide by number of cores
  7263. ( $avg_1_min, $avg_5_min, $avg_15_min ) = map { $_ / $cpu_num } ( $avg_1_min, $avg_5_min, $avg_15_min ) ;
  7264. # One of avg ok => ok, for now it is a OR
  7265. if ( $avg_1_min <= 1 ) { return 0 ; }
  7266. if ( $avg_5_min <= 1 ) { return 1 ; } # Retry in 1 minute
  7267. if ( $avg_15_min <= 1 ) { return 5 ; } # Retry in 5 minutes
  7268. return 15 ; # Retry in 15 minutes
  7269. }
  7270. sub memory_available {
  7271. # / ( 1000 ** 3 )
  7272. return(
  7273. sprintf( "%.1f GiB", Sys::MemInfo::get("totalmem") / ( 1024 ** 3 ) )
  7274. ) ;
  7275. }
  7276. sub memory_consumption {
  7277. # memory consumed by imapsync until now in bytes
  7278. return( ( memory_consumption_of_pids( ) )[0] );
  7279. }
  7280. sub tests_memory_consumption {
  7281. note( 'Entering tests_memory_consumption()' ) ;
  7282. like( memory_consumption( ), qr{\d+}xms,'memory_consumption no args') ;
  7283. like( memory_consumption( 1 ), qr{\d+}xms,'memory_consumption 1') ;
  7284. like( memory_consumption( $PROCESS_ID ), qr{\d+}xms,"memory_consumption_of_pids $PROCESS_ID") ;
  7285. like( memory_consumption_ratio(), qr{\d+}xms, 'memory_consumption_ratio' ) ;
  7286. like( memory_consumption_ratio(1), qr{\d+}xms, 'memory_consumption_ratio 1' ) ;
  7287. like( memory_consumption_ratio(10), qr{\d+}xms, 'memory_consumption_ratio 10' ) ;
  7288. like( memory_consumption(), qr{\d+}xms, "memory_consumption\n" ) ;
  7289. note( 'Leaving tests_memory_consumption()' ) ;
  7290. return ;
  7291. }
  7292. sub memory_consumption_of_pids {
  7293. my @pid = @_;
  7294. @pid = (@pid) ? @pid : ($PROCESS_ID) ;
  7295. #myprint( "PIDs: @pid\n" ) ;
  7296. my @val;
  7297. if ('MSWin32' eq $OSNAME) {
  7298. @val = memory_consumption_of_pids_win32(@pid);
  7299. }else{
  7300. # Unix
  7301. my @ps = qx{ ps -o vsz -p @pid } ;
  7302. #myprint( @ps ) ;
  7303. #my @ps = backtick( "ps -o vsz -p @pid" ) ;
  7304. shift @ps; # First line is column name "VSZ"
  7305. chomp @ps;
  7306. # convert to octets
  7307. @val = map { $_ * $KIBI } @ps;
  7308. }
  7309. return( @val ) ;
  7310. }
  7311. sub memory_consumption_of_pids_win32 {
  7312. # Windows
  7313. my @PID = @_;
  7314. my %PID;
  7315. # hash of pids as key values
  7316. map { $PID{$_}++ } @PID;
  7317. # Does not work but should reading the tasklist documentation
  7318. #@ps = qx{ tasklist /FI "PID eq @PID" };
  7319. my @ps = qx{ tasklist /NH /FO CSV } ;
  7320. #my @ps = backtick( 'tasklist /NH /FO CSV' ) ;
  7321. #myprint( "-" x $STD_CHAR_PER_LINE, "\n", @ps, "-" x $STD_CHAR_PER_LINE, "\n" ) ;
  7322. my @val;
  7323. foreach my $line (@ps) {
  7324. my($name, $pid, $mem) = (split ',', $line )[0,1,4];
  7325. next if (! $pid);
  7326. #myprint( "[$name][$pid][$mem]" ) ;
  7327. if ($PID{remove_qq($pid)}) {
  7328. #myprint( "MATCH !\n" ) ;
  7329. chomp $mem ;
  7330. $mem = remove_qq($mem);
  7331. $mem = remove_Ko($mem);
  7332. $mem = remove_not_num($mem);
  7333. #myprint( "[$mem]\n" ) ;
  7334. push @val, $mem * $KIBI;
  7335. }
  7336. }
  7337. return(@val);
  7338. }
  7339. sub backtick {
  7340. my $command = shift ;
  7341. if ( ! $command ) { return ; }
  7342. my ( $writer, $reader, $err ) ;
  7343. my @output ;
  7344. my $pid ;
  7345. eval {
  7346. $pid = open3( $writer, $reader, $err, $command ) ;
  7347. } ;
  7348. if ( ! $pid ) { return ; }
  7349. waitpid( $pid, 0 ) ;
  7350. @output = <$reader>; # Output here
  7351. #
  7352. #my @errors = <$err>; #Errors here, instead of the console
  7353. if ( not @output ) { return ; }
  7354. $debugdev and myprint( @output ) ;
  7355. if ( $output[0] =~ /\Qopen3: exec of $command failed\E/mxs ) { return ; }
  7356. if ( wantarray ) {
  7357. return( @output ) ;
  7358. } else {
  7359. return( join( q{}, @output) ) ;
  7360. }
  7361. }
  7362. sub tests_backtick {
  7363. note( 'Entering tests_backtick()' ) ;
  7364. is( undef, backtick( ), 'backtick: no args' ) ;
  7365. is( undef, backtick( q{} ), 'backtick: empty command' ) ;
  7366. SKIP: {
  7367. skip( 'test for MSWin32', 5 ) if ('MSWin32' ne $OSNAME) ;
  7368. my @output ;
  7369. @output = backtick( 'echo Hello World!' ) ;
  7370. # Add \r on Windows.
  7371. ok( "Hello World!\r\n" eq $output[0], 'backtick: echo Hello World!' ) ;
  7372. $debug and myprint( "[@output]" ) ;
  7373. @output = backtick( 'echo Hello & echo World!' ) ;
  7374. ok( "Hello \r\n" eq $output[0], 'backtick: echo Hello & echo World! line 1' ) ;
  7375. ok( "World!\r\n" eq $output[1], 'backtick: echo Hello & echo World! line 2' ) ;
  7376. $debug and myprint( "[@output][$output[0]][$output[1]]" ) ;
  7377. # Scalar context
  7378. ok( "Hello World!\r\n" eq backtick( 'echo Hello World!' ),
  7379. 'backtick: echo Hello World! scalar' ) ;
  7380. ok( "Hello \r\nWorld!\r\n" eq backtick( 'echo Hello & echo World!' ),
  7381. 'backtick: echo Hello & echo World! scalar 2 lines' ) ;
  7382. } ;
  7383. SKIP: {
  7384. skip( 'test for Unix', 7 ) if ('MSWin32' eq $OSNAME) ;
  7385. is( undef, backtick( 'aaaarrrg' ), 'backtick: aaaarrrg command not found' ) ;
  7386. # Array context
  7387. my @output ;
  7388. @output = backtick( 'echo Hello World!' ) ;
  7389. ok( "Hello World!\n" eq $output[0], 'backtick: echo Hello World!' ) ;
  7390. $debug and myprint( "[@output]" ) ;
  7391. @output = backtick( "echo Hello\necho World!" ) ;
  7392. ok( "Hello\n" eq $output[0], 'backtick: echo Hello; echo World! line 1' ) ;
  7393. ok( "World!\n" eq $output[1], 'backtick: echo Hello; echo World! line 2' ) ;
  7394. $debug and myprint( "[@output]" ) ;
  7395. # Scalar context
  7396. ok( "Hello World!\n" eq backtick( 'echo Hello World!' ),
  7397. 'backtick: echo Hello World! scalar' ) ;
  7398. ok( "Hello\nWorld!\n" eq backtick( "echo Hello\necho World!" ),
  7399. 'backtick: echo Hello; echo World! scalar 2 lines' ) ;
  7400. # Return error positive value, that's ok
  7401. is( undef, backtick( 'false' ), 'backtick: false returns no output' ) ;
  7402. }
  7403. note( 'Leaving tests_backtick()' ) ;
  7404. return ;
  7405. }
  7406. sub remove_not_num {
  7407. my $string = shift ;
  7408. $string =~ tr/0-9//cd ;
  7409. #myprint( "tr [$string]\n" ) ;
  7410. return( $string ) ;
  7411. }
  7412. sub tests_remove_not_num {
  7413. note( 'Entering tests_remove_not_num()' ) ;
  7414. ok( '123' eq remove_not_num( 123 ), 'remove_not_num( 123 )' ) ;
  7415. ok( '123' eq remove_not_num( '123' ), q{remove_not_num( '123' )} ) ;
  7416. ok( '123' eq remove_not_num( '12 3' ), q{remove_not_num( '12 3' )} ) ;
  7417. ok( '123' eq remove_not_num( 'a 12 3 Ko' ), q{remove_not_num( 'a 12 3 Ko' )} ) ;
  7418. note( 'Leaving tests_remove_not_num()' ) ;
  7419. return ;
  7420. }
  7421. sub remove_Ko {
  7422. my $string = shift;
  7423. if ($string =~ /^(.*)\sKo$/xo) {
  7424. return($1);
  7425. }else{
  7426. return($string);
  7427. }
  7428. }
  7429. sub remove_qq {
  7430. my $string = shift;
  7431. if ($string =~ /^"(.*)"$/xo) {
  7432. return($1);
  7433. }else{
  7434. return($string);
  7435. }
  7436. }
  7437. sub memory_consumption_ratio {
  7438. my ($base) = @_;
  7439. $base ||= 1;
  7440. my $consu = memory_consumption();
  7441. return($consu / $base);
  7442. }
  7443. sub date_from_rcs {
  7444. my $d = shift ;
  7445. 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 ) ;
  7446. if ($d =~ m{(\d{4})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) {
  7447. # Handles the following format
  7448. # 2015/07/10 11:05:59 -- Generated by RCS Date tag.
  7449. #myprint( "$d\n" ) ;
  7450. #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
  7451. my ($year, $month, $day, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6) ;
  7452. $month = $num2mon{$month} ;
  7453. $d = "$day-$month-$year $hour:$min:$sec +0000" ;
  7454. #myprint( "$d\n" ) ;
  7455. }
  7456. return( $d ) ;
  7457. }
  7458. sub tests_date_from_rcs {
  7459. note( 'Entering tests_date_from_rcs()' ) ;
  7460. ok('19-Sep-2015 16:11:07 +0000'
  7461. eq date_from_rcs('Date: 2015/09/19 16:11:07 '), 'date_from_rcs from RCS date' ) ;
  7462. note( 'Leaving tests_date_from_rcs()' ) ;
  7463. return ;
  7464. }
  7465. sub good_date {
  7466. # two incoming formats:
  7467. # header Tue, 24 Aug 2010 16:00:00 +0200
  7468. # internal 24-Aug-2010 16:00:00 +0200
  7469. # outgoing format: internal date format
  7470. # 24-Aug-2010 16:00:00 +0200
  7471. my $d = shift ;
  7472. return(q{}) if not defined $d;
  7473. SWITCH: {
  7474. if ( $d =~ m{(\d?)(\d-...-\d{4})(\s\d{2}:\d{2}:\d{2})(\s(?:\+|-)\d{4})?}xo ) {
  7475. #myprint( "internal: [$1][$2][$3][$4]\n" ) ;
  7476. my ($day_1, $date_rest, $hour, $zone) = ($1,$2,$3,$4) ;
  7477. $day_1 = '0' if ($day_1 eq q{}) ;
  7478. $zone = ' +0000' if not defined $zone ;
  7479. $d = $day_1 . $date_rest . $hour . $zone ;
  7480. last SWITCH ;
  7481. }
  7482. 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 ) {
  7483. # Handles any combination of following formats
  7484. # Tue, 24 Aug 2010 16:00:00 +0200 -- Standard
  7485. # 24 Aug 2010 16:00:00 +0200 -- Missing Day of Week
  7486. # Tue, 24 Aug 97 16:00:00 +0200 -- Two digit year
  7487. # Tue, 24 Aug 1997 16.00.00 +0200 -- Periods instead of colons
  7488. # Tue, 24 Aug 1997 16:00:00 +0200 -- Extra whitespace between year and hour
  7489. # Tue, 24 Aug 1997 6:5:2 +0200 -- Single digit hour, min, or second
  7490. # Tue, 24, Aug 1997 16:00:00 +0200 -- Extra comma
  7491. #myprint( "header: [$1][$2][$3][$4][$5][$6][$7][$8]\n" ) ;
  7492. my ($day, $month, $year, $hour, $min, $sec, $zone) = ($1,$2,$3,$4,$5,$6,$7,$8);
  7493. $year = '19' . $year if length($year) == 2 && $year =~ m/^[789]/xo;
  7494. $year = '20' . $year if length($year) == 2;
  7495. $month = substr $month, 0, 3 if length($month) > 4;
  7496. $day = mysprintf( '%02d', $day);
  7497. $hour = mysprintf( '%02d', $hour);
  7498. $min = mysprintf( '%02d', $min);
  7499. $sec = '00' if not defined $sec ;
  7500. $sec = mysprintf( '%02d', $sec ) ;
  7501. $zone = '+0000' if not defined $zone ;
  7502. $d = "$day-$month-$year $hour:$min:$sec $zone" ;
  7503. last SWITCH ;
  7504. }
  7505. 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 ) {
  7506. # Handles any combination of following formats
  7507. # Sun Aug 20 11:55:09 2006
  7508. # Wed Jan 24 11:58:38 MST 2007
  7509. # Wed Jan 2 08:40:57 2008
  7510. #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
  7511. my ($month, $day, $hour, $min, $sec, $year) = ($1,$2,$3,$4,$5,$6);
  7512. $day = mysprintf( '%02d', $day ) ;
  7513. $hour = mysprintf( '%02d', $hour ) ;
  7514. $min = mysprintf( '%02d', $min ) ;
  7515. $sec = mysprintf( '%02d', $sec ) ;
  7516. $d = "$day-$month-$year $hour:$min:$sec +0000" ;
  7517. last SWITCH ;
  7518. }
  7519. 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 ) ;
  7520. if ($d =~ m{(\d{4})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) {
  7521. # Handles the following format
  7522. # 2015/07/10 11:05:59 -- Generated by RCS Date tag.
  7523. #myprint( "$d\n" ) ;
  7524. #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
  7525. my ($year, $month, $day, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6) ;
  7526. $month = $num2mon{$month} ;
  7527. $d = "$day-$month-$year $hour:$min:$sec +0000" ;
  7528. #myprint( "$d\n" ) ;
  7529. last SWITCH ;
  7530. }
  7531. if ($d =~ m{(\d{2})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) {
  7532. # Handles the following format
  7533. # 02/06/09 22:18:08 -- Generated by AVTECH TemPageR devices
  7534. #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
  7535. my ($month, $day, $year, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6);
  7536. $year = '20' . $year;
  7537. $month = $num2mon{$month};
  7538. $d = "$day-$month-$year $hour:$min:$sec +0000";
  7539. last SWITCH ;
  7540. }
  7541. 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 ) {
  7542. # Handles the following format
  7543. # Saturday, December 14, 2002 05:00 PM - KBtoys.com order confirmations
  7544. my ($month, $day, $year, $hour, $min, $apm) = ($1,$2,$3,$4,$5,$6);
  7545. $hour += 12 if $apm eq 'PM' ;
  7546. $day = mysprintf( '%02d', $day ) ;
  7547. $d = "$day-$month-$year $hour:$min:00 +0000" ;
  7548. last SWITCH ;
  7549. }
  7550. if ($d =~ m{(\w{3})\s(\d{1,2})\s(\d{4})\s(\d{2}):(\d{2}):(\d{2})\s((?:\+|-)\d{4})}xo ) {
  7551. # Handles the following format
  7552. # Saturday, December 14, 2002 05:00 PM - jr.com order confirmations
  7553. my ($month, $day, $year, $hour, $min, $sec, $zone) = ($1,$2,$3,$4,$5,$6,$7);
  7554. $day = mysprintf( '%02d', $day ) ;
  7555. $d = "$day-$month-$year $hour:$min:$sec $zone";
  7556. last SWITCH ;
  7557. }
  7558. if ($d =~ m{(\d{1,2})-(\w{3})-(\d{4})}xo ) {
  7559. # Handles the following format
  7560. # 21-Jun-2001 - register.com domain transfer email circa 2001
  7561. my ($day, $month, $year) = ($1,$2,$3);
  7562. $day = mysprintf( '%02d', $day);
  7563. $d = "$day-$month-$year 11:11:11 +0000";
  7564. last SWITCH ;
  7565. }
  7566. # unknown or unmatch => return same string
  7567. return($d);
  7568. }
  7569. $d = qq("$d") ;
  7570. return( $d ) ;
  7571. }
  7572. sub tests_good_date {
  7573. note( 'Entering tests_good_date()' ) ;
  7574. ok(q{} eq good_date(), 'good_date no arg');
  7575. ok('"24-Aug-2010 16:00:00 +0200"' eq good_date('24-Aug-2010 16:00:00 +0200'), 'good_date internal 2digit zone');
  7576. ok('"24-Aug-2010 16:00:00 +0000"' eq good_date('24-Aug-2010 16:00:00'), 'good_date internal 2digit no zone');
  7577. ok('"01-Sep-2010 16:00:00 +0200"' eq good_date( '1-Sep-2010 16:00:00 +0200'), 'good_date internal SP 1digit');
  7578. ok('"24-Aug-2010 16:00:00 +0200"' eq good_date('Tue, 24 Aug 2010 16:00:00 +0200'), 'good_date header 2digit zone');
  7579. ok('"01-Sep-2010 16:00:00 +0000"' eq good_date('Wed, 1 Sep 2010 16:00:00'), 'good_date header SP 1digit zone');
  7580. 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');
  7581. 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');
  7582. ok('"06-Feb-2009 22:18:08 +0000"' eq good_date('02/06/09 22:18:08'), 'good_date header TemPageR');
  7583. 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');
  7584. 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');
  7585. 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');
  7586. 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');
  7587. ok('"24-Aug-2067 16:00:00 +0200"' eq good_date('Tue, 24 Aug 67 16:00:00 +0200'), 'good_date header 2digit year');
  7588. ok('"24-Aug-1977 16:00:00 +0200"' eq good_date('Tue, 24 Aug 77 16:00:00 +0200'), 'good_date header 2digit year');
  7589. ok('"24-Aug-1987 16:00:00 +0200"' eq good_date('Tue, 24 Aug 87 16:00:00 +0200'), 'good_date header 2digit year');
  7590. ok('"24-Aug-1997 16:00:00 +0200"' eq good_date('Tue, 24 Aug 97 16:00:00 +0200'), 'good_date header 2digit year');
  7591. ok('"24-Aug-2004 16:00:00 +0200"' eq good_date('Tue, 24 Aug 04 16:00:00 +0200'), 'good_date header 2digit year');
  7592. 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');
  7593. 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');
  7594. 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');
  7595. ok('"24-Aug-1997 05:06:02 +0200"' eq good_date('Tue, 24, Aug 1997 05:06:02 +0200'), 'good_date header extra commas');
  7596. ok('"01-Oct-2003 12:45:24 +0000"' eq good_date('Wednesday, 01 October 2003 12:45:24 CDT'), 'good_date header no abbrev');
  7597. ok('"11-Jan-2005 17:58:27 -0500"' eq good_date('Tue, 11 Jan 2005 17:58:27 -0500'), 'good_date extra white space');
  7598. ok('"18-Dec-2002 15:07:00 +0000"' eq good_date('Wednesday, December 18, 2002 03:07 PM'), 'good_date kbtoys.com orders');
  7599. ok('"16-Dec-2004 02:01:49 -0500"' eq good_date('Dec 16 2004 02:01:49 -0500'), 'good_date jr.com orders');
  7600. ok('"21-Jun-2001 11:11:11 +0000"' eq good_date('21-Jun-2001'), 'good_date register.com domain transfer');
  7601. 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)');
  7602. ok('"19-Sep-2015 16:11:07 +0000"' eq good_date('Date: 2015/09/19 16:11:07 '), 'good_date from RCS date' ) ;
  7603. note( 'Leaving tests_good_date()' ) ;
  7604. return ;
  7605. }
  7606. sub tests_list_keys_in_2_not_in_1 {
  7607. note( 'Entering tests_list_keys_in_2_not_in_1()' ) ;
  7608. my @list;
  7609. ok( ! list_keys_in_2_not_in_1( {}, {}), 'list_keys_in_2_not_in_1: {} {}');
  7610. ok( 0 == compare_lists( [], [ list_keys_in_2_not_in_1( {}, {} ) ] ), 'list_keys_in_2_not_in_1: {} {}');
  7611. 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}');
  7612. 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}');
  7613. 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}');
  7614. 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}');
  7615. 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}');
  7616. note( 'Leaving tests_list_keys_in_2_not_in_1()' ) ;
  7617. return ;
  7618. }
  7619. sub list_keys_in_2_not_in_1 {
  7620. my $folders1_ref = shift;
  7621. my $folders2_ref = shift;
  7622. my @list;
  7623. foreach my $folder ( sort keys %{ $folders2_ref } ) {
  7624. next if exists $folders1_ref->{$folder};
  7625. push @list, $folder;
  7626. }
  7627. return(@list);
  7628. }
  7629. sub list_folders_in_2_not_in_1 {
  7630. my (@h2_folders_not_in_h1, %h2_folders_not_in_h1) ;
  7631. @h2_folders_not_in_h1 = list_keys_in_2_not_in_1( \%h1_folders_all, \%h2_folders_all) ;
  7632. map { $h2_folders_not_in_h1{$_} = 1} @h2_folders_not_in_h1 ;
  7633. @h2_folders_not_in_h1 = list_keys_in_2_not_in_1( \%h2_folders_from_1_all, \%h2_folders_not_in_h1) ;
  7634. return( reverse @h2_folders_not_in_h1 );
  7635. }
  7636. sub tests_match {
  7637. note( 'Entering tests_match()' ) ;
  7638. # undef serie
  7639. is( undef, match( ), 'match: no args => undef' ) ;
  7640. is( undef, match( 'lalala' ), 'match: one args => undef' ) ;
  7641. # This one gives 0 under a binary made by pp
  7642. # but 1 under "normal" Perl interpreter. So a PAR bug?
  7643. #is( 1, match( q{}, q{} ), 'match: q{} =~ q{} => 1' ) ;
  7644. is( 1, match( 'lalala', 'lalala' ), 'match: lalala =~ lalala => 1' ) ;
  7645. is( 1, match( 'lalala', '^lalala' ), 'match: lalala =~ ^lalala => 1' ) ;
  7646. is( 1, match( 'lalala', 'lalala$' ), 'match: lalala =~ lalala$ => 1' ) ;
  7647. is( 1, match( 'lalala', '^lalala$' ), 'match: lalala =~ ^lalala$ => 1' ) ;
  7648. is( 1, match( '_lalala_', 'lalala' ), 'match: _lalala_ =~ lalala => 1' ) ;
  7649. is( 1, match( 'lalala', '.*' ), 'match: lalala =~ .* => 1' ) ;
  7650. is( 1, match( 'lalala', '.' ), 'match: lalala =~ . => 1' ) ;
  7651. is( 1, match( '/lalala/', '/lalala/' ), 'match: /lalala/ =~ /lalala/ => 1' ) ;
  7652. is( 0, match( 'lalala', 'ooo' ), 'match: lalala =~ ooo => 0' ) ;
  7653. is( 0, match( 'lalala', 'lal_ala' ), 'match: lalala =~ lal_ala => 0' ) ;
  7654. is( 0, match( 'lalala', '\.' ), 'match: lalala =~ \. => 0' ) ;
  7655. is( 0, match( 'lalalaX', '^lalala$' ), 'match: lalalaX =~ ^lalala$ => 0' ) ;
  7656. is( 0, match( 'lalala', '/lalala/' ), 'match: lalala =~ /lalala/ => 1' ) ;
  7657. is( 1, match( 'LALALA', '(?i:lalala)' ), 'match: LALALA =~ (?i:lalala) => 1' ) ;
  7658. is( undef, match( 'LALALA', '(?{`ls /`})' ), 'match: LALALA =~ (?{`ls /`}) => undef' ) ;
  7659. is( undef, match( 'LALALA', '(?{print "CACA"})' ), 'match: LALALA =~ (?{print "CACA"}) => undef' ) ;
  7660. is( undef, match( 'CACA', '(??{print "CACA"})' ), 'match: CACA =~ (??{print "CACA"}) => undef' ) ;
  7661. note( 'Leaving tests_match()' ) ;
  7662. return ;
  7663. }
  7664. sub match {
  7665. my( $var, $regex ) = @ARG ;
  7666. # undef cases
  7667. if ( ( ! defined $var ) or ( ! defined $regex ) ) { return ; }
  7668. # normal cases
  7669. if ( eval { $var =~ $regex } ) {
  7670. return 1 ;
  7671. }elsif ( $EVAL_ERROR ) {
  7672. print "Fatal regex $regex\n" ;
  7673. return ;
  7674. } else {
  7675. return 0 ;
  7676. }
  7677. return ;
  7678. }
  7679. sub tests_notmatch {
  7680. note( 'Entering tests_notmatch()' ) ;
  7681. # undef serie
  7682. is( undef, notmatch( ), 'notmatch: no args => undef' ) ;
  7683. is( undef, notmatch( 'lalala' ), 'notmatch: one args => undef' ) ;
  7684. is( 1, notmatch( 'lalala', '/lalala/' ), 'notmatch: lalala !~ /lalala/ => 1' ) ;
  7685. is( 0, notmatch( '/lalala/', '/lalala/' ), 'notmatch: /lalala/ !~ /lalala/ => 0' ) ;
  7686. is( 1, notmatch( 'lalala', '/ooo/' ), 'notmatch: lalala !~ /ooo/ => 1' ) ;
  7687. # This one gives 1 under a binary made by pp
  7688. # but 0 under "normal" Perl interpreter. So a PAR bug, same in tests_match .
  7689. #is( 0, notmatch( q{}, q{} ), 'notmatch: q{} !~ q{} => 0' ) ;
  7690. is( 0, notmatch( 'lalala', 'lalala' ), 'notmatch: lalala !~ lalala => 0' ) ;
  7691. is( 0, notmatch( 'lalala', '^lalala' ), 'notmatch: lalala !~ ^lalala => 0' ) ;
  7692. is( 0, notmatch( 'lalala', 'lalala$' ), 'notmatch: lalala !~ lalala$ => 0' ) ;
  7693. is( 0, notmatch( 'lalala', '^lalala$' ), 'notmatch: lalala !~ ^lalala$ => 0' ) ;
  7694. is( 0, notmatch( '_lalala_', 'lalala' ), 'notmatch: _lalala_ !~ lalala => 0' ) ;
  7695. is( 0, notmatch( 'lalala', '.*' ), 'notmatch: lalala !~ .* => 0' ) ;
  7696. is( 0, notmatch( 'lalala', '.' ), 'notmatch: lalala !~ . => 0' ) ;
  7697. is( 1, notmatch( 'lalala', 'ooo' ), 'notmatch: does not match regex => 1' ) ;
  7698. is( 1, notmatch( 'lalala', 'lal_ala' ), 'notmatch: does not match regex => 1' ) ;
  7699. is( 1, notmatch( 'lalala', '\.' ), 'notmatch: matches regex => 0' ) ;
  7700. is( 1, notmatch( 'lalalaX', '^lalala$' ), 'notmatch: does not match regex => 1' ) ;
  7701. note( 'Leaving tests_notmatch()' ) ;
  7702. return ;
  7703. }
  7704. sub notmatch {
  7705. my( $var, $regex ) = @ARG ;
  7706. # undef cases
  7707. if ( ( ! defined $var ) or ( ! defined $regex ) ) { return ; }
  7708. # normal cases
  7709. if ( eval { $var !~ $regex } ) {
  7710. return 1 ;
  7711. }elsif ( $EVAL_ERROR ) {
  7712. print "Fatal regex $regex\n" ;
  7713. return ;
  7714. }else{
  7715. return 0 ;
  7716. }
  7717. return ;
  7718. }
  7719. sub delete_folders_in_2_not_in_1 {
  7720. foreach my $folder (@h2_folders_not_in_1) {
  7721. if ( defined $delete2foldersonly and eval "\$folder !~ $delete2foldersonly" ) {
  7722. myprint( "Not deleting $folder because of --delete2foldersonly $delete2foldersonly\n" ) ;
  7723. next ;
  7724. }
  7725. if ( defined $delete2foldersbutnot and eval "\$folder =~ $delete2foldersbutnot" ) {
  7726. myprint( "Not deleting $folder because of --delete2foldersbutnot $delete2foldersbutnot\n" ) ;
  7727. next ;
  7728. }
  7729. my $res = $sync->{dry} ; # always success in dry mode!
  7730. $imap2->unsubscribe( $folder ) if ( ! $sync->{dry} ) ;
  7731. $res = $imap2->delete( $folder ) if ( ! $sync->{dry} ) ;
  7732. if ( $res ) {
  7733. myprint( "Deleted $folder", "$sync->{dry_message}", "\n" ) ;
  7734. }else{
  7735. myprint( "Deleting $folder failed", "\n" ) ;
  7736. }
  7737. }
  7738. return ;
  7739. }
  7740. sub delete_folder {
  7741. my ( $mysync, $imap, $folder, $Side ) = @_ ;
  7742. if ( ! $mysync ) { return ; }
  7743. if ( ! $imap ) { return ; }
  7744. if ( ! $folder ) { return ; }
  7745. $Side ||= 'HostX' ;
  7746. my $res = $mysync->{dry} ; # always success in dry mode!
  7747. if ( ! $mysync->{dry} ) {
  7748. $imap->unsubscribe( $folder ) ;
  7749. $res = $imap->delete( $folder ) ;
  7750. }
  7751. if ( $res ) {
  7752. myprint( "$Side deleted $folder", $mysync->{dry_message}, "\n" ) ;
  7753. return 1 ;
  7754. }else{
  7755. myprint( "$Side deleting $folder failed", "\n" ) ;
  7756. return ;
  7757. }
  7758. }
  7759. sub delete1emptyfolders {
  7760. my $mysync = shift ;
  7761. if ( ! $mysync ) { return ; } # abort if no parameter
  7762. if ( ! $mysync->{delete1emptyfolders} ) { return ; } # abort if --delete1emptyfolders off
  7763. my $imap = $mysync->{imap1} ;
  7764. if ( ! $imap ) { return ; } # abort if no imap
  7765. if ( $imap->IsUnconnected( ) ) { return ; } # abort if diesconnected
  7766. my %folders_kept ;
  7767. myprint( qq{Host1 deleting empty folders\n} ) ;
  7768. foreach my $folder ( reverse sort @{ $mysync->{h1_folders_wanted} } ) {
  7769. my $parenthood = $imap->is_parent( $folder ) ;
  7770. if ( defined $parenthood and $parenthood ) {
  7771. myprint( "Host1 folder $folder has subfolders\n" ) ;
  7772. $folders_kept{ $folder }++ ;
  7773. next ;
  7774. }
  7775. my $nb_messages_select = examine_folder_and_count( $imap, $folder, 'Host1' ) ;
  7776. if ( ! defined $nb_messages_select ) { next ; } # Select failed => Neither continue nor keep this folder }
  7777. my $nb_messages_search = scalar( @{ $imap->messages( ) } ) ;
  7778. if ( 0 != $nb_messages_select and 0 != $nb_messages_search ) {
  7779. myprint( "Host1 folder $folder has messages: $nb_messages_search (search) $nb_messages_select (select)\n" ) ;
  7780. $folders_kept{ $folder }++ ;
  7781. next ;
  7782. }
  7783. if ( 0 != $nb_messages_select + $nb_messages_search ) {
  7784. myprint( "Host1 folder $folder odd messages count: $nb_messages_search (search) $nb_messages_select (select)\n" ) ;
  7785. $folders_kept{ $folder }++ ;
  7786. next ;
  7787. }
  7788. # Here we must have 0 messages by messages() aka "SEARCH ALL" and also "EXAMINE"
  7789. if ( uc $folder eq 'INBOX' ) {
  7790. myprint( "Host1 Not deleting $folder\n" ) ;
  7791. $folders_kept{ $folder }++ ;
  7792. next ;
  7793. }
  7794. myprint( "Host1 deleting empty folder $folder\n" ) ;
  7795. # can not delete a SELECTed or EXAMINEd folder so closing it
  7796. # could changed be SELECT INBOX
  7797. $imap->close( ) ; # close after examine does not expunge; anyway expunging an empty folder...
  7798. if ( delete_folder( $mysync, $imap, $folder, 'Host1' ) ) {
  7799. next ; # Deleted, good!
  7800. }else{
  7801. $folders_kept{ $folder }++ ;
  7802. next ; # Not deleted, bad!
  7803. }
  7804. }
  7805. remove_deleted_folders_from_wanted_list( $mysync, %folders_kept ) ;
  7806. myprint( qq{Host1 ended deleting empty folders\n} ) ;
  7807. return ;
  7808. }
  7809. sub remove_deleted_folders_from_wanted_list {
  7810. my ( $mysync, %folders_kept ) = @ARG ;
  7811. my @h1_folders_wanted_init = @{ $mysync->{h1_folders_wanted} } ;
  7812. my @h1_folders_wanted_last ;
  7813. foreach my $folder ( @h1_folders_wanted_init ) {
  7814. if ( $folders_kept{ $folder } ) {
  7815. push @h1_folders_wanted_last, $folder ;
  7816. }
  7817. }
  7818. @{ $mysync->{h1_folders_wanted} } = @h1_folders_wanted_last ;
  7819. return ;
  7820. }
  7821. sub examine_folder_and_count {
  7822. my ( $imap, $folder, $Side ) = @_ ;
  7823. $Side ||= 'HostX' ;
  7824. if ( ! examine_folder( $imap, $folder, $Side ) ) {
  7825. return ;
  7826. }
  7827. my $nb_messages_select = count_from_select( $imap->History ) ;
  7828. return $nb_messages_select ;
  7829. }
  7830. sub tests_delete1emptyfolders {
  7831. note( 'Entering tests_delete1emptyfolders()' ) ;
  7832. is( undef, delete1emptyfolders( ), q{delete1emptyfolders: undef} ) ;
  7833. my $syncT ;
  7834. is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: undef 2} ) ;
  7835. my $imapT ;
  7836. $syncT->{imap1} = $imapT ;
  7837. is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: undef imap} ) ;
  7838. require Test::MockObject ;
  7839. $imapT = Test::MockObject->new( ) ;
  7840. $syncT->{imap1} = $imapT ;
  7841. $imapT->set_true( 'IsUnconnected' ) ;
  7842. is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: Unconnected imap} ) ;
  7843. # Now connected tests
  7844. $imapT->set_false( 'IsUnconnected' ) ;
  7845. $imapT->mock( 'LastError', sub { q{LastError mocked} } ) ;
  7846. $syncT->{delete1emptyfolders} = 0 ;
  7847. tests_delete1emptyfolders_unit(
  7848. $syncT,
  7849. [ qw{ INBOX DELME1 DELME2 } ],
  7850. [ qw{ INBOX DELME1 DELME2 } ],
  7851. q{tests_delete1emptyfolders: --delete1emptyfolders OFF}
  7852. ) ;
  7853. # All are parents => no deletion at all
  7854. $imapT->set_true( 'is_parent' ) ;
  7855. $syncT->{delete1emptyfolders} = 1 ;
  7856. tests_delete1emptyfolders_unit(
  7857. $syncT,
  7858. [ qw{ INBOX DELME1 DELME2 } ],
  7859. [ qw{ INBOX DELME1 DELME2 } ],
  7860. q{tests_delete1emptyfolders: --delete1emptyfolders ON}
  7861. ) ;
  7862. # No parents but examine false for all => skip all
  7863. $imapT->set_false( 'is_parent', 'examine' ) ;
  7864. tests_delete1emptyfolders_unit(
  7865. $syncT,
  7866. [ qw{ INBOX DELME1 DELME2 } ],
  7867. [ ],
  7868. q{tests_delete1emptyfolders: EXAMINE fails}
  7869. ) ;
  7870. # examine ok for all but History bad => skip all
  7871. $imapT->set_true( 'examine' ) ;
  7872. $imapT->mock( 'History', sub { ( q{History badly mocked} ) } ) ;
  7873. tests_delete1emptyfolders_unit(
  7874. $syncT,
  7875. [ qw{ INBOX DELME1 DELME2 } ],
  7876. [ ],
  7877. q{tests_delete1emptyfolders: examine ok but History badly mocked so count messages fails}
  7878. ) ;
  7879. # History good but some messages EXISTS == messages() => no deletion
  7880. $imapT->mock( 'History', sub { ( q{* 2 EXISTS} ) } ) ;
  7881. $imapT->mock( 'messages', sub { [ qw{ UID_1 UID_2 } ] } ) ;
  7882. tests_delete1emptyfolders_unit(
  7883. $syncT,
  7884. [ qw{ INBOX DELME1 DELME2 } ],
  7885. [ qw{ INBOX DELME1 DELME2 } ],
  7886. q{tests_delete1emptyfolders: History EXAMINE ok, several messages}
  7887. ) ;
  7888. # 0 EXISTS but != messages() => no deletion
  7889. $imapT->mock( 'History', sub { ( q{* 0 EXISTS} ) } ) ;
  7890. $imapT->mock( 'messages', sub { [ qw{ UID_1 UID_2 } ] } ) ;
  7891. tests_delete1emptyfolders_unit(
  7892. $syncT,
  7893. [ qw{ INBOX DELME1 DELME2 } ],
  7894. [ qw{ INBOX DELME1 DELME2 } ],
  7895. q{tests_delete1emptyfolders: 0 EXISTS but 2 by messages()}
  7896. ) ;
  7897. # 1 EXISTS but != 0 == messages() => no deletion
  7898. $imapT->mock( 'History', sub { ( q{* 1 EXISTS} ) } ) ;
  7899. $imapT->mock( 'messages', sub { [ ] } ) ;
  7900. tests_delete1emptyfolders_unit(
  7901. $syncT,
  7902. [ qw{ INBOX DELME1 DELME2 } ],
  7903. [ qw{ INBOX DELME1 DELME2 } ],
  7904. q{tests_delete1emptyfolders: 1 EXISTS but 0 by messages()}
  7905. ) ;
  7906. # 0 EXISTS and 0 == messages() => deletion except INBOX
  7907. $imapT->mock( 'History', sub { ( q{* 0 EXISTS} ) } ) ;
  7908. $imapT->mock( 'messages', sub { [ ] } ) ;
  7909. $imapT->set_true( qw{ delete close unsubscribe } ) ;
  7910. $syncT->{dry_message} = q{ (not really since in a mocked test)} ;
  7911. tests_delete1emptyfolders_unit(
  7912. $syncT,
  7913. [ qw{ INBOX DELME1 DELME2 } ],
  7914. [ qw{ INBOX } ],
  7915. q{tests_delete1emptyfolders: 0 EXISTS 0 by messages() delete folders, keep INBOX}
  7916. ) ;
  7917. note( 'Leaving tests_delete1emptyfolders()' ) ;
  7918. return ;
  7919. }
  7920. sub tests_delete1emptyfolders_unit {
  7921. note( 'Entering tests_delete1emptyfolders_unit()' ) ;
  7922. my $syncT = shift ;
  7923. my $folders1wanted_init_ref = shift ;
  7924. my $folders1wanted_after_ref = shift ;
  7925. my $comment = shift || q{delete1emptyfolders:} ;
  7926. my @folders1wanted_init = @{ $folders1wanted_init_ref } ;
  7927. my @folders1wanted_after = @{ $folders1wanted_after_ref } ;
  7928. @{ $syncT->{h1_folders_wanted} } = @folders1wanted_init ;
  7929. is_deeply( $syncT->{h1_folders_wanted}, \@folders1wanted_init, qq{$comment, init check} ) ;
  7930. delete1emptyfolders( $syncT ) ;
  7931. is_deeply( $syncT->{h1_folders_wanted}, \@folders1wanted_after, qq{$comment, after check} ) ;
  7932. note( 'Leaving tests_delete1emptyfolders_unit()' ) ;
  7933. return ;
  7934. }
  7935. sub extract_header {
  7936. my $string = shift ;
  7937. my ( $header ) = split /\n\n/x, $string ;
  7938. if ( ! $header ) { return( q{} ) ; }
  7939. #myprint( "[$header]\n" ) ;
  7940. return( $header ) ;
  7941. }
  7942. sub tests_extract_header {
  7943. note( 'Entering tests_extract_header()' ) ;
  7944. my $h = <<'EOM';
  7945. Message-Id: <20100428101817.A66CB162474E@plume.est.belle>
  7946. Date: Wed, 28 Apr 2010 12:18:17 +0200 (CEST)
  7947. From: gilles@louloutte.dyndns.org (Gilles LAMIRAL)
  7948. EOM
  7949. chomp $h ;
  7950. ok( $h eq extract_header(
  7951. <<'EOM'
  7952. Message-Id: <20100428101817.A66CB162474E@plume.est.belle>
  7953. Date: Wed, 28 Apr 2010 12:18:17 +0200 (CEST)
  7954. From: gilles@louloutte.dyndns.org (Gilles LAMIRAL)
  7955. body
  7956. lalala
  7957. EOM
  7958. ), 'extract_header: 1') ;
  7959. note( 'Leaving tests_extract_header()' ) ;
  7960. return ;
  7961. }
  7962. sub decompose_header{
  7963. my $string = shift ;
  7964. # a hash, for a keyword header KEY value are list of strings [VAL1, VAL1_other, etc]
  7965. # Think of multiple "Received:" header lines.
  7966. my $header = { } ;
  7967. my ($key, $val ) ;
  7968. my @line = split /\n|\r\n/x, $string ;
  7969. foreach my $line ( @line ) {
  7970. #myprint( "DDD $line\n" ) ;
  7971. # End of header
  7972. last if ( $line =~ m{^$}xo ) ;
  7973. # Key: value
  7974. if ( $line =~ m/(^[^:]+):\s(.*)/xo ) {
  7975. $key = $1 ;
  7976. $val = $2 ;
  7977. $debugdev and myprint( "DDD KV [$key] [$val]\n" ) ;
  7978. push @{ $header->{ $key } }, $val ;
  7979. # blanc and value => value from previous line continues
  7980. }elsif( $line =~ m/^(\s+)(.*)/xo ) {
  7981. $val = $2 ;
  7982. $debugdev and myprint( "DDD V [$val]\n" ) ;
  7983. @{ $header->{ $key } }[ $LAST ] .= " $val" if $key ;
  7984. # dirty line?
  7985. }else{
  7986. next ;
  7987. }
  7988. }
  7989. #myprint( Data::Dumper->Dump( [ $header ] ) ) ;
  7990. return( $header ) ;
  7991. }
  7992. sub tests_decompose_header{
  7993. note( 'Entering tests_decompose_header()' ) ;
  7994. my $header_dec ;
  7995. $header_dec = decompose_header(
  7996. <<'EOH'
  7997. KEY_1: VAL_1
  7998. KEY_2: VAL_2
  7999. VAL_2_+
  8000. VAL_2_++
  8001. KEY_3: VAL_3
  8002. KEY_1: VAL_1_other
  8003. KEY_4: VAL_4
  8004. VAL_4_+
  8005. KEY_5 BLANC: VAL_5
  8006. KEY_6_BAD_BODY: VAL_6
  8007. EOH
  8008. ) ;
  8009. ok( 'VAL_3'
  8010. eq $header_dec->{ 'KEY_3' }[0], 'decompose_header: VAL_3' ) ;
  8011. ok( 'VAL_1'
  8012. eq $header_dec->{ 'KEY_1' }[0], 'decompose_header: VAL_1' ) ;
  8013. ok( 'VAL_1_other'
  8014. eq $header_dec->{ 'KEY_1' }[1], 'decompose_header: VAL_1_other' ) ;
  8015. ok( 'VAL_2 VAL_2_+ VAL_2_++'
  8016. eq $header_dec->{ 'KEY_2' }[0], 'decompose_header: VAL_2 VAL_2_+ VAL_2_++' ) ;
  8017. ok( 'VAL_4 VAL_4_+'
  8018. eq $header_dec->{ 'KEY_4' }[0], 'decompose_header: VAL_4 VAL_4_+' ) ;
  8019. ok( ' VAL_5'
  8020. eq $header_dec->{ 'KEY_5 BLANC' }[0], 'decompose_header: KEY_5 BLANC' ) ;
  8021. ok( not( defined $header_dec->{ 'KEY_6_BAD_BODY' }[0] ), 'decompose_header: KEY_6_BAD_BODY' ) ;
  8022. $header_dec = decompose_header(
  8023. <<'EOH'
  8024. Message-Id: <20100428101817.A66CB162474E@plume.est.belle>
  8025. Date: Wed, 28 Apr 2010 12:18:17 +0200 (CEST)
  8026. From: gilles@louloutte.dyndns.org (Gilles LAMIRAL)
  8027. EOH
  8028. ) ;
  8029. ok( '<20100428101817.A66CB162474E@plume.est.belle>'
  8030. eq $header_dec->{ 'Message-Id' }[0], 'decompose_header: 1' ) ;
  8031. $header_dec = decompose_header(
  8032. <<'EOH'
  8033. Return-Path: <gilles@louloutte.dyndns.org>
  8034. Received: by plume.est.belle (Postfix, from userid 1000)
  8035. id 120A71624742; Wed, 28 Apr 2010 01:46:40 +0200 (CEST)
  8036. Subject: test:eekahceishukohpe
  8037. EOH
  8038. ) ;
  8039. ok(
  8040. 'by plume.est.belle (Postfix, from userid 1000) id 120A71624742; Wed, 28 Apr 2010 01:46:40 +0200 (CEST)'
  8041. eq $header_dec->{ 'Received' }[0], 'decompose_header: 2' ) ;
  8042. $header_dec = decompose_header(
  8043. <<'EOH'
  8044. Received: from plume (localhost [127.0.0.1])
  8045. by plume.est.belle (Postfix) with ESMTP id C6EB73F6C9
  8046. for <gilles@localhost>; Mon, 26 Nov 2007 10:39:06 +0100 (CET)
  8047. Received: from plume [192.168.68.7]
  8048. by plume with POP3 (fetchmail-6.3.6)
  8049. for <gilles@localhost> (single-drop); Mon, 26 Nov 2007 10:39:06 +0100 (CET)
  8050. EOH
  8051. ) ;
  8052. ok(
  8053. '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)'
  8054. eq $header_dec->{ 'Received' }[0], 'decompose_header: 3' ) ;
  8055. ok(
  8056. '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)'
  8057. eq $header_dec->{ 'Received' }[1], 'decompose_header: 3' ) ;
  8058. # Bad header beginning with a blank character
  8059. $header_dec = decompose_header(
  8060. <<'EOH'
  8061. KEY_1: VAL_1
  8062. KEY_2: VAL_2
  8063. VAL_2_+
  8064. VAL_2_++
  8065. KEY_3: VAL_3
  8066. KEY_1: VAL_1_other
  8067. EOH
  8068. ) ;
  8069. ok( 'VAL_3'
  8070. eq $header_dec->{ 'KEY_3' }[0], 'decompose_header: Bad header VAL_3' ) ;
  8071. ok( 'VAL_1_other'
  8072. eq $header_dec->{ 'KEY_1' }[0], 'decompose_header: Bad header VAL_1_other' ) ;
  8073. ok( 'VAL_2 VAL_2_+ VAL_2_++'
  8074. eq $header_dec->{ 'KEY_2' }[0], 'decompose_header: Bad header VAL_2 VAL_2_+ VAL_2_++' ) ;
  8075. note( 'Leaving tests_decompose_header()' ) ;
  8076. return ;
  8077. }
  8078. sub tests_epoch {
  8079. note( 'Entering tests_epoch()' ) ;
  8080. ok( '1282658400' eq epoch( '24-Aug-2010 16:00:00 +0200' ), 'epoch 24-Aug-2010 16:00:00 +0200 -> 1282658400' ) ;
  8081. ok( '1282658400' eq epoch( '24-Aug-2010 14:00:00 +0000' ), 'epoch 24-Aug-2010 14:00:00 +0000 -> 1282658400' ) ;
  8082. ok( '1282658400' eq epoch( '24-Aug-2010 12:00:00 -0200' ), 'epoch 24-Aug-2010 12:00:00 -0200 -> 1282658400' ) ;
  8083. ok( '1282658400' eq epoch( '24-Aug-2010 16:01:00 +0201' ), 'epoch 24-Aug-2010 16:01:00 +0201 -> 1282658400' ) ;
  8084. ok( '1282658400' eq epoch( '24-Aug-2010 14:01:00 +0001' ), 'epoch 24-Aug-2010 14:01:00 +0001 -> 1282658400' ) ;
  8085. ok( '1280671200' eq epoch( '1-Aug-2010 16:00:00 +0200' ), 'epoch 1-Aug-2010 16:00:00 +0200 -> 1280671200' ) ;
  8086. ok( '1280671200' eq epoch( '1-Aug-2010 14:00:00 +0000' ), 'epoch 1-Aug-2010 14:00:00 +0000 -> 1280671200' ) ;
  8087. ok( '1280671200' eq epoch( '1-Aug-2010 12:00:00 -0200' ), 'epoch 1-Aug-2010 12:00:00 -0200 -> 1280671200' ) ;
  8088. ok( '1280671200' eq epoch( '1-Aug-2010 16:01:00 +0201' ), 'epoch 1-Aug-2010 16:01:00 +0201 -> 1280671200' ) ;
  8089. ok( '1280671200' eq epoch( '1-Aug-2010 14:01:00 +0001' ), 'epoch 1-Aug-2010 14:01:00 +0001 -> 1280671200' ) ;
  8090. note( 'Leaving tests_epoch()' ) ;
  8091. return ;
  8092. }
  8093. sub epoch {
  8094. # incoming format:
  8095. # internal date 24-Aug-2010 16:00:00 +0200
  8096. # outgoing format: epoch
  8097. my $d = shift ;
  8098. return(q{}) if not defined $d;
  8099. my ( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m ) ;
  8100. my $time ;
  8101. 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 ) {
  8102. #myprint( "internal: [$1][$2][$3][$4][$5][$6][$7][$8][$9]\n" ) ;
  8103. ( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m )
  8104. = ( $1, $2, $3, $4, $5, $6, $7, $8, $9 ) ;
  8105. #myprint( "( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m )\n" ) ;
  8106. $sign = +1 if ( '+' eq $sign ) ;
  8107. $sign = $MINUS_ONE if ( '-' eq $sign ) ;
  8108. $time = timegm( $sec, $min, $hour, $mday, $month_abrev{$month}, $year )
  8109. - $sign * ( 3600 * $zone_h + 60 * $zone_m ) ;
  8110. #myprint( "$time ", scalar localtime($time), "\n");
  8111. }
  8112. return( $time ) ;
  8113. }
  8114. sub tests_add_header {
  8115. note( 'Entering tests_add_header()' ) ;
  8116. ok( 'Message-Id: <mistake@imapsync>' eq add_header(), 'add_header no arg' ) ;
  8117. ok( 'Message-Id: <123456789@imapsync>' eq add_header( '123456789' ), 'add_header 123456789' ) ;
  8118. note( 'Leaving tests_add_header()' ) ;
  8119. return ;
  8120. }
  8121. sub add_header {
  8122. my $header_uid = shift || 'mistake' ;
  8123. my $header_Message_Id = 'Message-Id: <' . $header_uid . '@imapsync>' ;
  8124. return( $header_Message_Id ) ;
  8125. }
  8126. sub tests_max_line_length {
  8127. note( 'Entering tests_max_line_length()' ) ;
  8128. ok( 0 == max_line_length( q{} ), 'max_line_length: 0 == null string' ) ;
  8129. ok( 1 == max_line_length( "\n" ), 'max_line_length: 1 == \n' ) ;
  8130. ok( 1 == max_line_length( "\n\n" ), 'max_line_length: 1 == \n\n' ) ;
  8131. ok( 1 == max_line_length( "\n" x 500 ), 'max_line_length: 1 == 500 \n' ) ;
  8132. ok( 1 == max_line_length( 'a' ), 'max_line_length: 1 == a' ) ;
  8133. ok( 2 == max_line_length( "a\na" ), 'max_line_length: 2 == a\na' ) ;
  8134. ok( 2 == max_line_length( "a\na\n" ), 'max_line_length: 2 == a\na\n' ) ;
  8135. ok( 3 == max_line_length( "a\nab\n" ), 'max_line_length: 3 == a\nab\n' ) ;
  8136. ok( 3 == max_line_length( "a\nab\n" x 10_000 ), 'max_line_length: 3 == 10_000 a\nab\n' ) ;
  8137. ok( 3 == max_line_length( "a\nab\nabc" ), 'max_line_length: 3 == a\nab\nabc' ) ;
  8138. ok( 4 == max_line_length( "a\nab\nabc\n" ), 'max_line_length: 4 == a\nab\nabc\n' ) ;
  8139. ok( 5 == max_line_length( "a\nabcd\nabc\n" ), 'max_line_length: 5 == a\nabcd\nabc\n' ) ;
  8140. 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' ) ;
  8141. note( 'Leaving tests_max_line_length()' ) ;
  8142. return ;
  8143. }
  8144. sub max_line_length {
  8145. my $string = shift ;
  8146. my $max = 0 ;
  8147. while ( $string =~ m/([^\n]*\n?)/msxg ) {
  8148. $max = max( $max, length $1 ) ;
  8149. }
  8150. return( $max ) ;
  8151. }
  8152. sub tests_setlogfile {
  8153. note( 'Entering tests_setlogfile()' ) ;
  8154. my $mysync = {} ;
  8155. $mysync->{logdir} = 'vallogdir' ;
  8156. $mysync->{logfile} = 'vallogfile.txt' ;
  8157. is( 'vallogdir/vallogfile.txt', setlogfile( $mysync ),
  8158. 'setlogfile: logdir vallogdir, logfile vallogfile.txt, vallogdir/vallogfile.txt' ) ;
  8159. SKIP: {
  8160. skip( 'Too hard to have a well known timezone on Windows', 6 ) if ( 'MSWin32' eq $OSNAME ) ;
  8161. local $ENV{TZ} = 'GMT' ;
  8162. $mysync = {
  8163. timestart => 2,
  8164. } ;
  8165. is( 'LOG_imapsync/1970_01_01_00_00_02_000__.txt', setlogfile( $mysync ),
  8166. 'setlogfile: default is like LOG_imapsync/1970_01_01_00_00_02_000__.txt' ) ;
  8167. $mysync = {
  8168. timestart => 2,
  8169. user1 => 'user1',
  8170. user2 => 'user2',
  8171. } ;
  8172. is( 'LOG_imapsync/1970_01_01_00_00_02_000_user1_user2.txt', setlogfile( $mysync ),
  8173. 'setlogfile: default is like LOG_imapsync/1970_01_01_00_00_02_000_user1_user2.txt' ) ;
  8174. $mysync->{logdir} = undef ;
  8175. $mysync->{logfile} = undef ;
  8176. is( 'LOG_imapsync/1970_01_01_00_00_02_000_user1_user2.txt', setlogfile( $mysync ),
  8177. 'setlogfile: logdir undef, LOG_imapsync/1970_01_01_00_00_02_000_user1_user2.txt' ) ;
  8178. $mysync->{logdir} = q{} ;
  8179. $mysync->{logfile} = undef ;
  8180. is( '1970_01_01_00_00_02_000_user1_user2.txt', setlogfile( $mysync ),
  8181. 'setlogfile: logdir empty, 1970_01_01_00_00_02_000_user1_user2.txt' ) ;
  8182. $mysync->{logdir} = 'vallogdir' ;
  8183. $mysync->{logfile} = undef ;
  8184. is( 'vallogdir/1970_01_01_00_00_02_000_user1_user2.txt', setlogfile( $mysync ),
  8185. 'setlogfile: logdir vallogdir, vallogdir/1970_01_01_00_00_02_000_user1_user2.txt' ) ;
  8186. $mysync = {
  8187. user1 => 'us/er1a*|?:"<>b',
  8188. user2 => 'u/ser2a*|?:"<>b',
  8189. } ;
  8190. is( 'LOG_imapsync/1970_01_01_00_00_00_000_us_er1a_______b_u_ser2a_______b.txt', setlogfile( $mysync ),
  8191. 'setlogfile: logdir undef, LOG_imapsync/1970_01_01_00_00_00_000_us_er1a_______b_u_ser2a_______b.txt' ) ;
  8192. } ;
  8193. note( 'Leaving tests_setlogfile()' ) ;
  8194. return ;
  8195. }
  8196. sub tests_move_slash {
  8197. note( 'Entering tests_move_slash()' ) ;
  8198. is( undef, move_slash( ), 'move_slash: no parameters => undef' ) ;
  8199. is( '_', move_slash( '/' ), 'move_slash: / => _' ) ;
  8200. is( '_abc_def_', move_slash( '/abc/def/' ), 'move_slash: /abc/def/ => _abc_def_' ) ;
  8201. note( 'Leaving tests_move_slash()' ) ;
  8202. return ;
  8203. }
  8204. sub move_slash {
  8205. my $string = shift ;
  8206. if ( ! defined $string ) { return ; }
  8207. $string =~ tr{/}{_} ;
  8208. return( $string ) ;
  8209. }
  8210. sub setlogfile {
  8211. my( $mysync ) = shift ;
  8212. my $suffix = ( filter_forbidden_characters( move_slash( $mysync->{user1} ) ) || q{} )
  8213. . '_' .
  8214. ( filter_forbidden_characters( move_slash( $mysync->{user2} ) ) || q{} ) ;
  8215. $mysync->{logdir} = defined $mysync->{logdir} ? $mysync->{logdir} : 'LOG_imapsync' ;
  8216. $mysync->{logfile} = defined $mysync->{logfile} ? "$mysync->{logdir}/$mysync->{logfile}" :
  8217. logfile( $mysync->{timestart}, $suffix, $mysync->{logdir} ) ;
  8218. #myprint( "logdir = $mysync->{logdir}\n" ) ;
  8219. #myprint( "logfile = $mysync->{logfile}\n" ) ;
  8220. return( $mysync->{logfile} ) ;
  8221. }
  8222. sub tests_logfile {
  8223. note( 'Entering tests_logfile()' ) ;
  8224. SKIP: {
  8225. # Too hard to have a well known timezone on Windows
  8226. skip( 'Too hard to have a well known timezone on Windows', 8 ) if ( 'MSWin32' eq $OSNAME ) ;
  8227. local $ENV{TZ} = 'GMT' ;
  8228. { POSIX::tzset unless ('MSWin32' eq $OSNAME) ;
  8229. is( '1970_01_01_00_00_00_000.txt', logfile( ), 'logfile: no args => 1970_01_01_00_00_00.txt' ) ;
  8230. is( '1970_01_01_00_00_00_000.txt', logfile( 0 ), 'logfile: 0 => 1970_01_01_00_00_00.txt' ) ;
  8231. is( '1970_01_01_00_01_01_000.txt', logfile( 61 ), 'logfile: 0 => 1970_01_01_00_01_01.txt' ) ;
  8232. is( '1970_01_01_00_01_01_234.txt', logfile( 61.234 ), 'logfile: 0 => 1970_01_01_00_01_01.txt' ) ;
  8233. 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' ) ;
  8234. 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' ) ;
  8235. 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' ) ;
  8236. 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' ) ;
  8237. }
  8238. POSIX::tzset unless ('MSWin32' eq $OSNAME) ;
  8239. } ;
  8240. note( 'Leaving tests_logfile()' ) ;
  8241. return ;
  8242. }
  8243. sub logfile {
  8244. my ( $time, $suffix, $dir ) = @_ ;
  8245. $time ||= 0 ;
  8246. $suffix ||= q{} ;
  8247. $suffix =~ tr/ //ds ;
  8248. my $sep_suffix = ( $suffix ) ? '_' : q{} ;
  8249. $dir ||= q{} ;
  8250. my $sep_dir = ( $dir ) ? '/' : q{} ;
  8251. my $date_str = POSIX::strftime( '%Y_%m_%d_%H_%M_%S', localtime $time ) ;
  8252. # Because of ab tests or web access, more than one sync withing one second is possible
  8253. # so we add millisecons
  8254. $date_str .= sprintf "_%03d", ($time - int( $time ) ) * 1000 ; # without rounding
  8255. my $logfile = "${dir}${sep_dir}${date_str}${sep_suffix}${suffix}.txt" ;
  8256. $debug and myprint( "date_str: $date_str\n" ) ;
  8257. $debug and myprint( "logfile : $logfile\n" ) ;
  8258. return( $logfile ) ;
  8259. }
  8260. sub tests_million_folders_baby_2 {
  8261. note( 'Entering tests_million_folders_baby_2()' ) ;
  8262. my %long ;
  8263. @long{ 1 .. 900_000 } = (1) x 900_000 ;
  8264. #myprint( %long, "\n" ) ;
  8265. my $pasglop = 0 ;
  8266. foreach my $elem ( 1 .. 900_000 ) {
  8267. #$debug and myprint( "$elem " ) ;
  8268. if ( not exists $long{ $elem } ) {
  8269. $pasglop++ ;
  8270. }
  8271. }
  8272. ok( 0 == $pasglop, 'tests_million_folders_baby_2: search among 900_000' ) ;
  8273. # myprint( "$pasglop\n" ) ;
  8274. note( 'Leaving tests_million_folders_baby_2()' ) ;
  8275. return ;
  8276. }
  8277. sub tests_always_fail {
  8278. note( 'Entering tests_always_fail()' ) ;
  8279. is( 0, 1, 'always_fail: 0 is 1' ) ;
  8280. note( 'Leaving tests_always_fail()' ) ;
  8281. return ;
  8282. }
  8283. sub logfileprepa {
  8284. my $logfile = shift ;
  8285. my $dirname = dirname( $logfile ) ;
  8286. do_valid_directory( $dirname ) || return( 0 ) ;
  8287. return( 1 ) ;
  8288. }
  8289. sub teelaunch {
  8290. my $mysync = shift ;
  8291. my $logfile = $mysync->{logfile} ;
  8292. logfileprepa( $logfile ) || croak "Error no valid directory to write log file $logfile : $OS_ERROR" ;
  8293. open my $logfile_handle, '>', $logfile
  8294. or croak( "Can not open $logfile for write: $OS_ERROR" ) ;
  8295. my $tee = IO::Tee->new( $logfile_handle, \*STDOUT ) ;
  8296. *STDERR = *$tee{IO} ;
  8297. select $tee ;
  8298. $tee->autoflush( 1 ) ;
  8299. $mysync->{logfile_handle} = $logfile_handle ;
  8300. $mysync->{tee} = $tee ;
  8301. return $logfile_handle ;
  8302. }
  8303. sub getpwuid_any_os {
  8304. my $uid = shift ;
  8305. return( scalar getlogin ) if ( 'MSWin32' eq $OSNAME ) ; # Windows system
  8306. return( scalar getpwuid $uid ) ; # Unix system
  8307. }
  8308. sub simulong {
  8309. my $max_seconds = shift ;
  8310. my $division = 5 ;
  8311. my $last = $division * $max_seconds ;
  8312. foreach my $i ( 1 .. ( $last ) ) {
  8313. myprint( "Are you still here $i/$last\n" ) ;
  8314. #myprint( "Are you still here $i/$last\n" . ( "Ah" x 40 . "\n") x 4000 ) ;
  8315. sleep( 1 / $division ) ;
  8316. }
  8317. return ;
  8318. }
  8319. sub printenv {
  8320. myprint( "Environment variables listing:\n",
  8321. ( map { "$_ => $ENV{$_}\n" } sort keys %ENV),
  8322. "Environment variables listing end\n" ) ;
  8323. return ;
  8324. }
  8325. sub testsexit {
  8326. my $mysync = shift ;
  8327. if ( ! ( $mysync->{ tests } or $mysync->{ testsdebug } or $mysync->{ testsunit } ) ) {
  8328. return ;
  8329. }
  8330. my $test_builder = Test::More->builder ;
  8331. tests( $mysync ) ;
  8332. testsdebug( $mysync ) ;
  8333. testunitsession( $mysync ) ;
  8334. my @summary = $test_builder->summary() ;
  8335. my @details = $test_builder->details() ;
  8336. my $nb_tests_run = scalar( @summary ) ;
  8337. my $nb_tests_expected = $test_builder->expected_tests() ;
  8338. my $nb_tests_failed = count_0s( @summary ) ;
  8339. my $tests_failed = report_failures( @details ) ;
  8340. if ( $nb_tests_failed or ( $nb_tests_run != $nb_tests_expected ) ) {
  8341. #$test_builder->reset( ) ;
  8342. myprint( "Summary of tests: failed $nb_tests_failed tests, run $nb_tests_run tests, expected to run $nb_tests_expected tests.\n",
  8343. "List of failed tests:\n", $tests_failed ) ;
  8344. exit $EXIT_TESTS_FAILED ;
  8345. }
  8346. exit ;
  8347. #return ;
  8348. }
  8349. sub after_get_options {
  8350. my $numopt = shift ;
  8351. # exit with --help option or no option at all
  8352. $debug and myprint( "numopt:$numopt\n" ) ;
  8353. myprint( usage( $sync ) ) and exit if ( $help or not $numopt ) ;
  8354. return ;
  8355. }
  8356. sub easyany {
  8357. my $mysync = shift ;
  8358. # Gmail
  8359. if ( $mysync->{gmail1} and $mysync->{gmail2} ) {
  8360. $debug and myprint( "gmail1 gmail2\n") ;
  8361. gmail12( $mysync ) ;
  8362. return ;
  8363. }
  8364. if ( $mysync->{gmail1} ) {
  8365. $debug and myprint( "gmail1\n" ) ;
  8366. gmail1( $mysync ) ;
  8367. }
  8368. if ( $mysync->{gmail2} ) {
  8369. $debug and myprint( "gmail2\n" ) ;
  8370. gmail2( $mysync ) ;
  8371. }
  8372. # Office 365
  8373. if ( $mysync->{office1} ) {
  8374. office1( $mysync ) ;
  8375. }
  8376. if ( $mysync->{office2} ) {
  8377. office2( $mysync ) ;
  8378. }
  8379. # Domino
  8380. if ( $mysync->{domino1} ) {
  8381. domino1( $mysync ) ;
  8382. }
  8383. if ( $mysync->{domino2} ) {
  8384. domino2( $mysync ) ;
  8385. }
  8386. return ;
  8387. }
  8388. # From https://imapsync.lamiral.info/FAQ.d/FAQ.Gmail.txt
  8389. sub gmail12 {
  8390. my $mysync = shift ;
  8391. # Gmail at host1 and host2
  8392. $mysync->{host1} ||= 'imap.gmail.com' ;
  8393. $mysync->{ssl1} = ( defined $mysync->{ssl1} ) ? $mysync->{ssl1} : 1 ;
  8394. $mysync->{host2} ||= 'imap.gmail.com' ;
  8395. $mysync->{ssl2} = ( defined $mysync->{ssl2} ) ? $mysync->{ssl2} : 1 ;
  8396. $mysync->{maxbytespersecond} ||= 20_000 ; # should be 10_000 computed from by Gmail documentation
  8397. $mysync->{maxbytesafter} ||= 1_000_000_000 ;
  8398. $mysync->{automap} = ( defined $mysync->{automap} ) ? $mysync->{automap} : 1 ;
  8399. $mysync->{maxsleep} = $MAX_SLEEP ;
  8400. push @exclude, '\[Gmail\]$' ;
  8401. return ;
  8402. }
  8403. sub gmail1 {
  8404. my $mysync = shift ;
  8405. # Gmail at host2
  8406. $mysync->{host1} ||= 'imap.gmail.com' ;
  8407. $mysync->{ssl1} = ( defined $mysync->{ssl1} ) ? $mysync->{ssl1} : 1 ;
  8408. $mysync->{maxbytespersecond} ||= 40_000 ; # should be 20_000 computed from by Gmail documentation
  8409. $mysync->{maxbytesafter} ||= 2_500_000_000 ;
  8410. $mysync->{automap} = ( defined $mysync->{automap} ) ? $mysync->{automap} : 1 ;
  8411. $skipcrossduplicates = ( defined $skipcrossduplicates ) ? $skipcrossduplicates : 1 ;
  8412. $mysync->{maxsleep} = $MAX_SLEEP ;
  8413. push @useheader, 'X-Gmail-Received', 'Message-Id' ;
  8414. push @regextrans2, 's,\[Gmail\].,,' ;
  8415. push @folderlast, '[Gmail]/All Mail' ;
  8416. return ;
  8417. }
  8418. sub gmail2 {
  8419. my $mysync = shift ;
  8420. # Gmail at host2
  8421. $mysync->{host2} ||= 'imap.gmail.com' ;
  8422. $mysync->{ssl2} = ( defined $mysync->{ssl2} ) ? $mysync->{ssl2} : 1 ;
  8423. $mysync->{maxbytespersecond} ||= 20_000 ; # should be 10_000 computed from by Gmail documentation
  8424. $mysync->{maxbytesafter} ||= 1_000_000_000 ; # In fact it is documented as half: 500_000_000
  8425. $maxsize ||= 25_000_000 ;
  8426. $mysync->{automap} = ( defined $mysync->{automap} ) ? $mysync->{automap} : 1 ;
  8427. $skipcrossduplicates = ( defined $skipcrossduplicates ) ? $skipcrossduplicates : 1 ;
  8428. $expunge1 = ( defined $expunge1 ) ? $expunge1 : 1 ;
  8429. $addheader = ( defined $addheader ) ? $addheader : 1 ;
  8430. $mysync->{maxsleep} = $MAX_SLEEP ;
  8431. push @exclude, '\[Gmail\]$' ;
  8432. push @useheader, 'X-Gmail-Received', 'Message-Id' ;
  8433. push @regextrans2, 's,\[Gmail\].,,' ;
  8434. push @regextrans2, 's/[ ]+/_/g' ;
  8435. push @regextrans2, q{s/['\\^"]/_/g} ; # Verified this
  8436. push @folderlast, "[Gmail]/All Mail" ;
  8437. return ;
  8438. }
  8439. # From https://imapsync.lamiral.info/FAQ.d/FAQ.Exchange.txt
  8440. sub office1 {
  8441. # Office 365 at host1
  8442. my $mysync = shift ;
  8443. $debug and myprint( "office1 configuration\n" ) ;
  8444. $mysync->{host1} ||= 'outlook.office365.com' ;
  8445. $mysync->{ssl1} = ( defined $mysync->{ssl1} ) ? $mysync->{ssl1} : 1 ;
  8446. return ;
  8447. }
  8448. sub office2 {
  8449. # Office 365 at host1
  8450. my $mysync = shift ;
  8451. $mysync->{host2} ||= 'outlook.office365.com' ;
  8452. $mysync->{ssl2} = ( defined $mysync->{ssl2} ) ? $mysync->{ssl2} : 1 ;
  8453. $maxsize ||= 45_000_000 ;
  8454. $mysync->{maxmessagespersecond} ||= 4 ;
  8455. push @regexflag, 's/\\Flagged//g' ;
  8456. $disarmreadreceipts = ( defined $disarmreadreceipts ) ? $disarmreadreceipts : 1 ;
  8457. push @regexmess, 's,(.{10500}),$1\r\n,g' ;
  8458. return ;
  8459. }
  8460. sub exchange1 {
  8461. # Exchange 2010/2013 at host1
  8462. # Well nothing to do so far
  8463. return ;
  8464. }
  8465. sub exchange2 {
  8466. # Exchange 2010/2013 at host2
  8467. my $mysync = shift ;
  8468. $maxsize ||= 10_000_000 ;
  8469. $mysync->{maxmessagespersecond} ||= 4 ;
  8470. push @regexflag, 's/\\Flagged//g' ;
  8471. $disarmreadreceipts = ( defined $disarmreadreceipts ) ? $disarmreadreceipts : 1 ;
  8472. push @regexmess, 's,(.{10500}),$1\r\n,g' ;
  8473. return ;
  8474. }
  8475. sub domino1 {
  8476. # Domino at host1
  8477. my $mysync = shift ;
  8478. $sep1 = q{\\} ;
  8479. $prefix1 = q{} ;
  8480. $messageidnodomain = ( defined $messageidnodomain ) ? $messageidnodomain : 1 ;
  8481. return ;
  8482. }
  8483. sub domino2 {
  8484. # Domino at host1
  8485. my $mysync = shift ;
  8486. $sep2 = q{\\} ;
  8487. $prefix2 = q{} ;
  8488. $messageidnodomain = ( defined $messageidnodomain ) ? $messageidnodomain : 1 ;
  8489. push @regextrans2, 's,^Inbox\\\\(.*),$1,i' ;
  8490. return ;
  8491. }
  8492. sub tests_resolv {
  8493. note( 'Entering tests_resolv()' ) ;
  8494. # is( , resolv( ), 'resolv: => ' ) ;
  8495. is( undef, resolv( ), 'resolv: no args => undef' ) ;
  8496. is( undef, resolv( '' ), 'resolv: empty string => undef' ) ;
  8497. is( undef, resolv( 'hostnotexist' ), 'resolv: hostnotexist => undef' ) ;
  8498. is( '127.0.0.1', resolv( '127.0.0.1' ), 'resolv: 127.0.0.1 => 127.0.0.1' ) ;
  8499. is( '127.0.0.1', resolv( 'localhost' ), 'resolv: localhost => 127.0.0.1' ) ;
  8500. is( '5.135.158.182', resolv( 'imapsync.lamiral.info' ), 'resolv: imapsync.lamiral.info => 5.135.158.182' ) ;
  8501. # ip6-localhost ( in /etc/hosts )
  8502. is( '::1', resolv( 'ip6-localhost' ), 'resolv: ip6-localhost => ::1' ) ;
  8503. is( '::1', resolv( '::1' ), 'resolv: ::1 => ::1' ) ;
  8504. # ks2
  8505. is( '2001:41d0:8:d8b6::1', resolv( '2001:41d0:8:d8b6::1' ), 'resolv: 2001:41d0:8:d8b6::1 => 2001:41d0:8:d8b6::1' ) ;
  8506. is( '2001:41d0:8:d8b6::1', resolv( 'ks2ipv6.lamiral.info' ), 'resolv: ks2ipv6.lamiral.info => 2001:41d0:8:d8b6::1' ) ;
  8507. # ks3
  8508. is( '2001:41d0:8:bebd::1', resolv( '2001:41d0:8:bebd::1' ), 'resolv: 2001:41d0:8:bebd::1 => 2001:41d0:8:bebd::1' ) ;
  8509. is( '2001:41d0:8:bebd::1', resolv( 'ks3ipv6.lamiral.info' ), 'resolv: ks3ipv6.lamiral.info => 2001:41d0:8:bebd::1' ) ;
  8510. note( 'Leaving tests_resolv()' ) ;
  8511. return ;
  8512. }
  8513. sub resolv {
  8514. my $host = shift @ARG ;
  8515. if ( ! $host ) { return ; }
  8516. my $addr ;
  8517. if ( defined &Socket::getaddrinfo ) {
  8518. $addr = resolv_with_getaddrinfo( $host ) ;
  8519. return( $addr ) ;
  8520. }
  8521. my $iaddr = inet_aton( $host ) ;
  8522. if ( ! $iaddr ) { return ; }
  8523. $addr = inet_ntoa( $iaddr ) ;
  8524. return $addr ;
  8525. }
  8526. sub resolv_with_getaddrinfo {
  8527. my $host = shift @ARG ;
  8528. if ( ! $host ) { return ; }
  8529. my ( $err, @res ) = Socket::getaddrinfo( $host, "", { socktype => Socket::SOCK_RAW } ) ;
  8530. if ( $err ) {
  8531. myprint( "Cannot getaddrinfo of $host: $err\n" ) ;
  8532. return ;
  8533. }
  8534. my @addr ;
  8535. while( my $ai = shift @res ) {
  8536. my ( $err, $ipaddr ) = Socket::getnameinfo( $ai->{addr}, Socket::NI_NUMERICHOST(), Socket::NIx_NOSERV() ) ;
  8537. if ( $err ) {
  8538. myprint( "Cannot getnameinfo of $host: $err\n" ) ;
  8539. return ;
  8540. }
  8541. $debug and myprint "$host => $ipaddr\n" ;
  8542. push @addr, $ipaddr ;
  8543. my ( $err_r, $reverse ) = Socket::getnameinfo( $ai->{addr}, 0, Socket::NIx_NOSERV() ) ;
  8544. $debug and myprint "$host => $ipaddr => $reverse\n" ;
  8545. }
  8546. return $addr[0] ;
  8547. }
  8548. sub tests_resolvrev {
  8549. note( 'Entering tests_resolvrev()' ) ;
  8550. # is( , resolvrev( ), 'resolvrev: => ' ) ;
  8551. is( undef, resolvrev( ), 'resolvrev: no args => undef' ) ;
  8552. is( undef, resolvrev( '' ), 'resolvrev: empty string => undef' ) ;
  8553. is( undef, resolvrev( 'hostnotexist' ), 'resolvrev: hostnotexist => undef' ) ;
  8554. is( 'localhost', resolvrev( '127.0.0.1' ), 'resolvrev: 127.0.0.1 => localhost' ) ;
  8555. is( 'localhost', resolvrev( 'localhost' ), 'resolvrev: localhost => localhost' ) ;
  8556. is( 'ks.lamiral.info', resolvrev( 'imapsync.lamiral.info' ), 'resolvrev: imapsync.lamiral.info => ks.lamiral.info' ) ;
  8557. # ip6-localhost ( in /etc/hosts )
  8558. is( 'ip6-localhost', resolvrev( 'ip6-localhost' ), 'resolvrev: ip6-localhost => ip6-localhost' ) ;
  8559. is( 'ip6-localhost', resolvrev( '::1' ), 'resolvrev: ::1 => ip6-localhost' ) ;
  8560. # ks2
  8561. is( 'ks2ipv6.lamiral.info', resolvrev( '2001:41d0:8:d8b6::1' ), 'resolvrev: 2001:41d0:8:d8b6::1 => ks2ipv6.lamiral.info' ) ;
  8562. is( 'ks2ipv6.lamiral.info', resolvrev( 'ks2ipv6.lamiral.info' ), 'resolvrev: ks2ipv6.lamiral.info => ks2ipv6.lamiral.info' ) ;
  8563. # ks3
  8564. is( 'ks3ipv6.lamiral.info', resolvrev( '2001:41d0:8:bebd::1' ), 'resolvrev: 2001:41d0:8:bebd::1 => ks3ipv6.lamiral.info' ) ;
  8565. is( 'ks3ipv6.lamiral.info', resolvrev( 'ks3ipv6.lamiral.info' ), 'resolvrev: ks3ipv6.lamiral.info => ks3ipv6.lamiral.info' ) ;
  8566. note( 'Leaving tests_resolvrev()' ) ;
  8567. return ;
  8568. }
  8569. sub resolvrev {
  8570. my $host = shift @ARG ;
  8571. if ( ! $host ) { return ; }
  8572. if ( defined &Socket::getaddrinfo ) {
  8573. my $name = resolvrev_with_getaddrinfo( $host ) ;
  8574. return( $name ) ;
  8575. }
  8576. return ;
  8577. }
  8578. sub resolvrev_with_getaddrinfo {
  8579. my $host = shift @ARG ;
  8580. if ( ! $host ) { return ; }
  8581. my ( $err, @res ) = Socket::getaddrinfo( $host, "", { socktype => Socket::SOCK_RAW } ) ;
  8582. if ( $err ) {
  8583. myprint( "Cannot getaddrinfo of $host: $err\n" ) ;
  8584. return ;
  8585. }
  8586. my @name ;
  8587. while( my $ai = shift @res ) {
  8588. my ( $err, $reverse ) = Socket::getnameinfo( $ai->{addr}, 0, Socket::NIx_NOSERV() ) ;
  8589. if ( $err ) {
  8590. myprint( "Cannot getnameinfo of $host: $err\n" ) ;
  8591. return ;
  8592. }
  8593. $debug and myprint "$host => $reverse\n" ;
  8594. push @name, $reverse ;
  8595. }
  8596. return $name[0] ;
  8597. }
  8598. sub tests_imapsping {
  8599. note( 'Entering tests_imapsping()' ) ;
  8600. is( undef, imapsping( ), 'imapsping: no args => undef' ) ;
  8601. is( undef, imapsping( 'hostnotexist' ), 'imapsping: hostnotexist => undef' ) ;
  8602. is( 1, imapsping( 'imapsync.lamiral.info' ), 'imapsping: imapsync.lamiral.info => 1' ) ;
  8603. is( 1, imapsping( 'ks2ipv6.lamiral.info' ), 'imapsping: ks2ipv6.lamiral.info => 1' ) ;
  8604. note( 'Leaving tests_imapsping()' ) ;
  8605. return ;
  8606. }
  8607. sub imapsping {
  8608. my $host = shift ;
  8609. return tcpping( $host, $IMAP_SSL_PORT ) ;
  8610. }
  8611. sub tests_tcpping {
  8612. note( 'Entering tests_tcpping()' ) ;
  8613. is( undef, tcpping( ), 'tcpping: no args => undef' ) ;
  8614. is( undef, tcpping( 'hostnotexist' ), 'tcpping: one arg => undef' ) ;
  8615. is( undef, tcpping( undef, 888 ), 'tcpping: arg undef, port => undef' ) ;
  8616. is( undef, tcpping( 'hostnotexist', 993 ), 'tcpping: hostnotexist 993 => undef' ) ;
  8617. is( undef, tcpping( 'hostnotexist', 888 ), 'tcpping: hostnotexist 888 => undef' ) ;
  8618. is( 1, tcpping( 'imapsync.lamiral.info', 993 ), 'tcpping: imapsync.lamiral.info 993 => 1' ) ;
  8619. is( 0, tcpping( 'imapsync.lamiral.info', 888 ), 'tcpping: imapsync.lamiral.info 888 => 0' ) ;
  8620. is( 1, tcpping( '5.135.158.182', 993 ), 'tcpping: 5.135.158.182 993 => 1' ) ;
  8621. is( 0, tcpping( '5.135.158.182', 888 ), 'tcpping: 5.135.158.182 888 => 0' ) ;
  8622. # Net::Ping supports ipv6 only after release 1.50
  8623. # http://cpansearch.perl.org/src/RURBAN/Net-Ping-2.59/Changes
  8624. # Anyway I plan to avoid Net-Ping for that too long standing feature
  8625. # Net-Ping is integrated in Perl itself, who knows ipv6 for a long time
  8626. is( 1, tcpping( '2001:41d0:8:d8b6::1', 993 ), 'tcpping: 2001:41d0:8:d8b6::1 993 => 1' ) ;
  8627. is( 0, tcpping( '2001:41d0:8:d8b6::1', 888 ), 'tcpping: 2001:41d0:8:d8b6::1 888 => 0' ) ;
  8628. note( 'Leaving tests_tcpping()' ) ;
  8629. return ;
  8630. }
  8631. sub tcpping {
  8632. if ( 2 != scalar( @ARG ) ) {
  8633. return ;
  8634. }
  8635. my ( $host, $port ) = @ARG ;
  8636. if ( ! $host ) { return ; }
  8637. if ( ! $port ) { return ; }
  8638. my $mytimeout = $TCP_PING_TIMEOUT ;
  8639. require Net::Ping ;
  8640. #my $p = Net::Ping->new( 'tcp' ) ;
  8641. my $p = Net::Ping->new( ) ;
  8642. $p->{port_num} = $port ;
  8643. $p->service_check( 1 ) ;
  8644. $p->hires( 1 ) ;
  8645. my ($ping_ok, $rtt, $ip ) = $p->ping( $host, $mytimeout ) ;
  8646. if ( ! defined $ping_ok ) { return ; }
  8647. my $rtt_approx = sprintf( "%.3f", $rtt ) ;
  8648. $debug and myprint( "Host $host timeout $mytimeout port $port ok $ping_ok ip $ip acked in $rtt_approx s\n" ) ;
  8649. $p->close( ) ;
  8650. if( $ping_ok ) {
  8651. return 1 ;
  8652. }else{
  8653. return 0 ;
  8654. }
  8655. }
  8656. sub tests_sslcheck {
  8657. note( 'Entering tests_sslcheck()' ) ;
  8658. my $mysync ;
  8659. is( undef, sslcheck( $mysync ), 'sslcheck: no sslcheck => undef' ) ;
  8660. $mysync = {
  8661. sslcheck => 1,
  8662. } ;
  8663. is( 0, sslcheck( $mysync ), 'sslcheck: no host => 0' ) ;
  8664. $mysync = {
  8665. sslcheck => 1,
  8666. host1 => 'imapsync.lamiral.info',
  8667. tls1 => 1,
  8668. } ;
  8669. is( 0, sslcheck( $mysync ), 'sslcheck: tls1 => 0' ) ;
  8670. $mysync = {
  8671. sslcheck => 1,
  8672. host1 => 'imapsync.lamiral.info',
  8673. } ;
  8674. is( 1, sslcheck( $mysync ), 'sslcheck: imapsync.lamiral.info => 1' ) ;
  8675. is( 1, $mysync->{ssl1}, 'sslcheck: imapsync.lamiral.info => ssl1 1' ) ;
  8676. $mysync->{sslcheck} = 0 ;
  8677. is( undef, sslcheck( $mysync ), 'sslcheck: sslcheck off => undef' ) ;
  8678. note( 'Leaving tests_sslcheck()' ) ;
  8679. return ;
  8680. }
  8681. sub sslcheck {
  8682. my $mysync = shift ;
  8683. if ( ! $mysync->{sslcheck} ) {
  8684. return ;
  8685. }
  8686. my $nb_on = 0 ;
  8687. $debug and myprint( "sslcheck\n" ) ;
  8688. if (
  8689. ( ! defined $mysync->{port1} )
  8690. and
  8691. ( ! defined $mysync->{tls1} )
  8692. and
  8693. ( ! defined $mysync->{ssl1} )
  8694. and
  8695. ( defined $mysync->{host1} )
  8696. and
  8697. ( probe_imapssl( $mysync->{host1} ) )
  8698. ) {
  8699. $mysync->{ssl1} = 1 ;
  8700. 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" ) ;
  8701. $nb_on++ ;
  8702. }
  8703. if (
  8704. ( ! defined $mysync->{port2} )
  8705. and
  8706. ( ! defined $mysync->{tls2} )
  8707. and
  8708. ( ! defined $mysync->{ssl2} )
  8709. and
  8710. ( defined $mysync->{host2} )
  8711. and
  8712. ( probe_imapssl( $mysync->{host2} ) )
  8713. ) {
  8714. $mysync->{ssl2} = 1 ;
  8715. 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" ) ;
  8716. $nb_on++ ;
  8717. }
  8718. return $nb_on ;
  8719. }
  8720. sub testslive {
  8721. my $mysync = shift ;
  8722. $mysync->{host1} = 'test1.lamiral.info' ;
  8723. $mysync->{user1} = 'test1' ;
  8724. $mysync->{password1} = 'secret1' ;
  8725. $mysync->{host2} = 'test2.lamiral.info' ;
  8726. $mysync->{user2} = 'test2' ;
  8727. $mysync->{password2} ='secret2' ;
  8728. return ;
  8729. }
  8730. sub testslive6 {
  8731. my $mysync = shift ;
  8732. $mysync->{host1} = 'ks2ipv6.lamiral.info' ;
  8733. $mysync->{user1} = 'test1' ;
  8734. $mysync->{password1} = 'secret1' ;
  8735. $mysync->{host2} = 'ks2ipv6.lamiral.info' ;
  8736. $mysync->{user2} = 'test2' ;
  8737. $mysync->{password2} ='secret2' ;
  8738. return ;
  8739. }
  8740. sub tests_backslash_caret {
  8741. note( 'Entering tests_backslash_caret()' ) ;
  8742. is( "lalala", backslash_caret( "lalala" ), 'backslash_caret: lalala => lalala' ) ;
  8743. is( "lalala\n", backslash_caret( "lalala\n" ), 'backslash_caret: lalala => lalala 2nd' ) ;
  8744. is( '^', backslash_caret( '\\' ), 'backslash_caret: \\ => ^' ) ;
  8745. is( "^\n", backslash_caret( "\\\n" ), 'backslash_caret: \\ => ^' ) ;
  8746. is( "\\lalala", backslash_caret( "\\lalala" ), 'backslash_caret: \\lalala => \\lalala' ) ;
  8747. is( "\\lal\\ala", backslash_caret( "\\lal\\ala" ), 'backslash_caret: \\lal\\ala => \\lal\\ala' ) ;
  8748. is( "\\lalala\n", backslash_caret( "\\lalala\n" ), 'backslash_caret: \\lalala => \\lalala 2nd' ) ;
  8749. is( "lalala^\n", backslash_caret( "lalala\\\n" ), 'backslash_caret: lalala\\\n => lalala^\n' ) ;
  8750. is( "lalala^\nlalala^\n", backslash_caret( "lalala\\\nlalala\\\n" ), 'backslash_caret: lalala\\\nlalala\\\n => lalala^\nlalala^\n' ) ;
  8751. is( "lal\\ala^\nlalala^\n", backslash_caret( "lal\\ala\\\nlalala\\\n" ), 'backslash_caret: lal\\ala\\\nlalala\\\n => lal\\ala^\nlalala^\n' ) ;
  8752. note( 'Leaving tests_backslash_caret()' ) ;
  8753. return ;
  8754. }
  8755. sub backslash_caret {
  8756. my $string = shift ;
  8757. $string =~ s{\\ $ }{^}gxms ;
  8758. return $string ;
  8759. }
  8760. sub usage {
  8761. my $mysync = shift ;
  8762. my $usage = q{} ;
  8763. my $usage_from_pod ;
  8764. my $usage_footer = usage_footer( $mysync ) ;
  8765. # pod2usage writes on a filehandle only and I want a variable
  8766. open my $fh_pod2usage, ">", \$usage_from_pod or do {
  8767. warn $OS_ERROR ;
  8768. return ;
  8769. } ;
  8770. pod2usage(
  8771. -exitval => 'NOEXIT',
  8772. -noperldoc => 1,
  8773. -verbose => 99,
  8774. -sections => [ qw(NAME VERSION USAGE OPTIONS) ],
  8775. -indent => 1,
  8776. -loose => 1,
  8777. -output => $fh_pod2usage,
  8778. ) ;
  8779. close $fh_pod2usage ;
  8780. if ( 'MSWin32' eq $OSNAME ) {
  8781. $usage_from_pod = backslash_caret( $usage_from_pod ) ;
  8782. }
  8783. $usage = join( q{}, $usage_from_pod, $usage_footer ) ;
  8784. return( $usage ) ;
  8785. }
  8786. sub tests_usage {
  8787. my $usage ;
  8788. like( $usage = usage( $sync ), qr/Name:/, 'usage2: contains Name:' ) ;
  8789. myprint( $usage ) ;
  8790. like( $usage, qr/Version:/, 'usage2: contains Version:' ) ;
  8791. like( $usage, qr/Usage:/, 'usage2: contains Usage:' ) ;
  8792. like( $usage, qr/imapsync/, 'usage2: contains imapsync' ) ;
  8793. return ;
  8794. }
  8795. sub usage_footer {
  8796. my $mysync = shift ;
  8797. my $footer = q{} ;
  8798. my $localhost_info = localhost_info( ) ;
  8799. my $rcs = $mysync->{rcs} ;
  8800. my $homepage = homepage( ) ;
  8801. my $imapsync_release = q{} ;
  8802. $imapsync_release = check_last_release( ) if ( not defined $releasecheck ) ;
  8803. $footer =
  8804. qq{$localhost_info
  8805. $rcs
  8806. $imapsync_release
  8807. $homepage
  8808. } ;
  8809. return( $footer ) ;
  8810. }
  8811. sub usage_complete {
  8812. # Unused, I guess this function could be deleted
  8813. my $usage = <<'EOF' ;
  8814. --skipheader reg : Don't take into account header keyword
  8815. matching reg ex: --skipheader 'X.*'
  8816. --skipsize : Don't take message size into account to compare
  8817. messages on both sides. On by default.
  8818. Use --no-skipsize for using size comparaison.
  8819. --allowsizemismatch : allow RFC822.SIZE != fetched msg size
  8820. consider also --skipsize to avoid duplicate messages
  8821. when running syncs more than one time per mailbox
  8822. --reconnectretry1 int : reconnect to host1 if connection is lost up to
  8823. int times per imap command (default is 3)
  8824. --reconnectretry2 int : same as --reconnectretry1 but for host2
  8825. --split1 int : split the requests in several parts on host1.
  8826. int is the number of messages handled per request.
  8827. default is like --split1 500.
  8828. --split2 int : same thing on host2.
  8829. --nofixInboxINBOX : Don't fix Inbox INBOX mapping.
  8830. EOF
  8831. return( $usage ) ;
  8832. }
  8833. sub myGetOptions {
  8834. # Started as a copy of Luke Ross Getopt::Long::CGI
  8835. # https://metacpan.org/release/Getopt-Long-CGI
  8836. # So this sub function is under the same license as Getopt-Long-CGI Luke Ross wants it,
  8837. # which was Perl 5.6 or later licenses at the date of the copy.
  8838. my $mycgi = shift @ARG ;
  8839. my $arguments_ref = shift @ARG ;
  8840. my %options = @ARG ;
  8841. if ( not under_cgi_context( ) ) {
  8842. # Not CGI - pass upstream for normal command line handling
  8843. return Getopt::Long::GetOptionsFromArray( $arguments_ref, %options ) ;
  8844. }
  8845. my $b_ref = $options{'debugbasket=s'} ;
  8846. my $badthings = 0 ;
  8847. foreach my $key (sort keys %options) {
  8848. my $val = $options{$key};
  8849. #push( @{$b_ref}, "opt:[$key] val:[$val]" . ( ('SCALAR' eq ref($val) and defined $$val ) ? " [$$val]" : q{} ) . "\n" ) ;
  8850. if ( $key !~ m/^([\w\d\|]+)([=:][isf])?([\+!\@\%])?$/mxs ) {
  8851. push @{$b_ref}, "Unknown option type: [$key]\n" ;
  8852. $badthings++ ;
  8853. next ; # Unknown item
  8854. }
  8855. my $name = [split '|', $1, 1 ]->[0];
  8856. if (($3 || q{}) eq '+') {
  8857. ${ $val } = $mycgi->param($name); # "Incremental" integer
  8858. } elsif ($2) {
  8859. my @values = $mycgi->multi_param($name);
  8860. my $type = $2;
  8861. #myprint( "[$type][@values][", $3 || q{}, "][$val][", ref($val), "]\n" ) ;
  8862. if (($3 || q{}) eq '%' or ref($val) eq 'HASH') {
  8863. my %values = map { split /=/mxs, $_ } @values;
  8864. if ($type =~ m/i$/mxs) {
  8865. foreach my $k (keys %values) {
  8866. $values{$k} = int $values{$k} ;
  8867. }
  8868. } elsif ($type =~ m/f$/mxs) {
  8869. foreach my $k (keys %values) {
  8870. $values{$k} = 0 + $values{$k}
  8871. }
  8872. }
  8873. if ( 'REF' eq ref $val ) {
  8874. #push( @{$b_ref}, "refref($$val): " . ref($$val) . " %values= ", %values, "\n\n" ) ;
  8875. %{ ${ $val } } = %values;
  8876. } else {
  8877. #push( @{$b_ref}, "ref($val): " . ref($val) . " %values= ", %values, "\n\n" ) ;
  8878. %{ $val } = %values;
  8879. }
  8880. } else {
  8881. if ($type =~ m/i$/mxs) {
  8882. @values = map { int $_ } @values;
  8883. } elsif ($type =~ m/f$/mxs) {
  8884. @values = map { 0 + $_ } @values;
  8885. }
  8886. if (($3 || q{}) eq '@' or ref($val) eq 'ARRAY') {
  8887. @{ $val } = @values ;
  8888. } else {
  8889. ${ $val } = $values[0] ;
  8890. }
  8891. }
  8892. } else {
  8893. # Checkbox
  8894. # Considers only --name
  8895. # Should consider also --no-name and --noname
  8896. ${ $val } = $mycgi->param($name) ? 1 : undef ;
  8897. #push( @{$b_ref}, "param($name) ref($val): " . ref($val) . " val=[$$val]\n\n" ) ;
  8898. #myprint( "param($name) ref($val): " . ref($val) . " val=[$$val]\n\n" ) ;
  8899. #myprint( "param($name) ref($val): " . ref($val) . " \n\n" ) ;
  8900. }
  8901. }
  8902. if ( $badthings ) {
  8903. return ; # undef or ()
  8904. } else {
  8905. return( 1 ) ;
  8906. }
  8907. }
  8908. sub tests_get_options {
  8909. note( 'Entering tests_get_options()' ) ;
  8910. # CAVEAT: still setting global variables, be carefull
  8911. # with tests, the context increases! $debug stays on for example.
  8912. # API:
  8913. # * input arguments: two ways, command line or CGI
  8914. # * the program arguments
  8915. # * QUERY_STRING env variable
  8916. # * return
  8917. # * undef if bad things happened like
  8918. # * options not known
  8919. # * --delete 2 input
  8920. # * number of arguments or QUERY_STRING length
  8921. my $mysync ;
  8922. is( undef, get_options( $mysync, qw( --noexist ) ), 'get_options: --noexist => undef' ) ;
  8923. is( undef, get_options( $mysync, qw( --lalala --noexist --version ) ), 'get_options: --lalala --noexist --version => undef' ) ;
  8924. is( undef, get_options( $mysync, qw( --delete 2 ) ), 'get_options: --delete 2 => undef' ) ;
  8925. is( 1, get_options( $mysync, "--version" ), 'get_options: --version => 1' ) ;
  8926. is( 1, get_options( $mysync, "--help" ), 'get_options: --help => 1' ) ;
  8927. is( undef, get_options( $mysync, qw( --debug --noexist --version ) ), 'get_options: --debug --noexist --version => undef' ) ;
  8928. note( 'Leaving tests_get_options()' ) ;
  8929. return ;
  8930. }
  8931. sub tests_get_options_cgi {
  8932. note( 'Entering tests_get_options_cgi()' ) ;
  8933. # Temporary, have to think harder about testing CGI context in command line --tests
  8934. # API:
  8935. # * input arguments: two ways, command line or CGI
  8936. # * the program arguments
  8937. # * QUERY_STRING env variable
  8938. # * return
  8939. # * QUERY_STRING length
  8940. # CGI context
  8941. local $ENV{SERVER_SOFTWARE} = 'Votre serviteur' ;
  8942. # Real full test
  8943. # = 'host1=test1.lamiral.info&user1=test1&password1=secret1&host2=test2.lamiral.info&user2=test2&password2=secret2&debugenv=on'
  8944. my $mysync ;
  8945. require CGI ;
  8946. CGI->import( qw( -no_debug ) ) ;
  8947. # Testing boolean
  8948. $mysync->{cgi} = CGI->new( 'version=on&debugenv=on' ) ;
  8949. local $ENV{'QUERY_STRING'} = 'version=on&debugenv=on' ;
  8950. is( 22, get_options_cgi( $mysync ), 'get_options: QUERY_STRING => 22' ) ;
  8951. is( 1, $version, 'get_options: $version => 1' ) ;
  8952. # debugenv is not allowed in cgi context
  8953. is( undef, $mysync->{debugenv}, 'get_options: $mysync->{debugenv} => undef' ) ;
  8954. # QUERY_STRING in this test is only for return value of get_options_cgi
  8955. # Have to think harder, GET/POST context, is this return value a good thing?
  8956. local $ENV{'QUERY_STRING'} = 'host1=test1.lamiral.info&user1=test1' ;
  8957. $mysync->{cgi} = CGI->new( 'host1=test1.lamiral.info&user1=test1' ) ;
  8958. is( 36, get_options_cgi( $mysync, ), 'get_options: QUERY_STRING => 36' ) ;
  8959. is( 'test1', $mysync->{user1}, 'get_options: $mysync->{user1} => test1' ) ;
  8960. # Testing @
  8961. $mysync->{cgi} = CGI->new( 'folder=fd1' ) ;
  8962. get_options_cgi( $mysync ) ;
  8963. is_deeply( [ 'fd1' ], [ @folder ], 'get_options: @folder => fd1' ) ;
  8964. $mysync->{cgi} = CGI->new( 'folder=fd1&folder=fd2' ) ;
  8965. get_options_cgi( $mysync ) ;
  8966. is_deeply( [ 'fd1', 'fd2' ], [ @folder ], 'get_options: @folder => fd1' ) ;
  8967. # Testing %
  8968. $mysync->{cgi} = CGI->new( 'f1f2=s1=d1&f1f2=s2=d2&f1f2=s3=d3' ) ;
  8969. get_options_cgi( $mysync ) ;
  8970. #$mysync->{f1f2} = { 's1' => 'd1', 's2' => 'd2' } ;
  8971. is_deeply( { 's1' => 'd1', 's2' => 'd2', 's3' => 'd3' },
  8972. $mysync->{f1f2}, 'get_options: f1f2 => s1=d1 s2=d2 s3=d3' ) ;
  8973. # Testing boolean ! with --noxxx, doesnot work
  8974. $mysync->{cgi} = CGI->new( 'nodry=on' ) ;
  8975. is( undef, $mysync->{dry}, 'get_options: --nodry => $mysync->{dry} => 0' ) ;
  8976. note( 'Leaving tests_get_options_cgi()' ) ;
  8977. return ;
  8978. }
  8979. sub get_options_cgi {
  8980. # In CGI context arguments are not in @ARGV but in QUERY_STRING variable (with GET).
  8981. my $mysync = shift @ARG ;
  8982. my $mycgi = $mysync->{cgi} || return ;
  8983. my @arguments = @ARG ;
  8984. # final 0 is used to print usage when no option is given
  8985. my $numopt = length $ENV{'QUERY_STRING'} || 1 ;
  8986. $mysync->{f1f2} = {} ;
  8987. my $opt_ret = myGetOptions(
  8988. $mycgi,
  8989. \@arguments,
  8990. 'abort' => \$mysync->{abort},
  8991. 'host1=s' => \$mysync->{host1},
  8992. 'host2=s' => \$mysync->{host2},
  8993. 'user1=s' => \$mysync->{user1},
  8994. 'user2=s' => \$mysync->{user2},
  8995. 'password1=s' => \$mysync->{password1},
  8996. 'password2=s' => \$mysync->{password2},
  8997. 'dry!' => \$mysync->{dry},
  8998. 'version' => \$version,
  8999. 'ssl1!' => \$mysync->{ssl1},
  9000. 'ssl2!' => \$mysync->{ssl2},
  9001. 'tls1!' => \$mysync->{tls1},
  9002. 'tls2!' => \$mysync->{tls2},
  9003. 'justlogin!' => \$justlogin,
  9004. 'addheader!' => \$addheader,
  9005. 'automap!' => \$mysync->{automap},
  9006. 'justautomap!' => \$mysync->{justautomap},
  9007. 'gmail1' => \$mysync->{gmail1},
  9008. 'gmail2' => \$mysync->{gmail2},
  9009. 'office1' => \$mysync->{office1},
  9010. 'office2' => \$mysync->{office2},
  9011. 'exchange1' => \$mysync->{exchange1},
  9012. 'exchange2' => \$mysync->{exchange2},
  9013. 'domino1' => \$mysync->{domino1},
  9014. 'domino2' => \$mysync->{domino2},
  9015. 'f1f2=s%' => \$mysync->{f1f2},
  9016. 'folder=s' => \@folder,
  9017. 'testslive!' => \$mysync->{testslive},
  9018. 'testslive6!' => \$mysync->{testslive6},
  9019. ) ;
  9020. $debug and output( $mysync, "get options: [$opt_ret][$numopt]\n" ) ;
  9021. if ( ! $opt_ret ) {
  9022. return ;
  9023. }
  9024. return $numopt ;
  9025. }
  9026. sub get_options_cmd {
  9027. my $mysync = shift @ARG ;
  9028. my @arguments = @ARG ;
  9029. my $mycgi = $mysync->{cgi} ;
  9030. # final 0 is used to print usage when no option is given on command line
  9031. my $numopt = scalar @arguments || 0 ;
  9032. my $argv = join "\x00", @arguments ;
  9033. if ( $argv =~ m/-delete\x002/x ) {
  9034. output( $mysync, "May be you mean --delete2 instead of --delete 2\n" ) ;
  9035. return ;
  9036. }
  9037. $mysync->{f1f2} = {} ;
  9038. my $opt_ret = myGetOptions(
  9039. $mycgi,
  9040. \@arguments,
  9041. 'debug!' => \$debug,
  9042. 'debuglist!' => \$debuglist,
  9043. 'debugcontent!' => \$debugcontent,
  9044. 'debugsleep=f' => \$mysync->{debugsleep},
  9045. 'debugflags!' => \$debugflags,
  9046. 'debugimap!' => \$debugimap,
  9047. 'debugimap1!' => \$debugimap1,
  9048. 'debugimap2!' => \$debugimap2,
  9049. 'debugdev!' => \$debugdev,
  9050. 'debugmemory!' => \$mysync->{debugmemory},
  9051. 'debugfolders!' => \$mysync->{debugfolders},
  9052. 'debugssl=i' => \$mysync->{debugssl},
  9053. 'debugbasket=s' => \@debugbasket,
  9054. 'debugcgi!' => \$debugcgi,
  9055. 'debugenv' => \$mysync->{debugenv},
  9056. 'simulong=i' => \$mysync->{simulong},
  9057. 'abort' => \$mysync->{abort},
  9058. 'host1=s' => \$mysync->{host1},
  9059. 'host2=s' => \$mysync->{host2},
  9060. 'port1=i' => \$mysync->{port1},
  9061. 'port2=i' => \$mysync->{port2},
  9062. 'inet4|ipv4' => \$mysync->{inet4},
  9063. 'inet6|ipv6' => \$mysync->{inet6},
  9064. 'user1=s' => \$mysync->{user1},
  9065. 'user2=s' => \$mysync->{user2},
  9066. 'gmail1' => \$mysync->{gmail1},
  9067. 'gmail2' => \$mysync->{gmail2},
  9068. 'office1' => \$mysync->{office1},
  9069. 'office2' => \$mysync->{office2},
  9070. 'exchange1' => \$mysync->{exchange1},
  9071. 'exchange2' => \$mysync->{exchange2},
  9072. 'domino1' => \$mysync->{domino1},
  9073. 'domino2' => \$mysync->{domino2},
  9074. 'domain1=s' => \$domain1,
  9075. 'domain2=s' => \$domain2,
  9076. 'password1=s' => \$mysync->{password1},
  9077. 'password2=s' => \$mysync->{password2},
  9078. 'passfile1=s' => \$passfile1,
  9079. 'passfile2=s' => \$passfile2,
  9080. 'authmd5!' => \$authmd5,
  9081. 'authmd51!' => \$authmd51,
  9082. 'authmd52!' => \$authmd52,
  9083. 'sep1=s' => \$sep1,
  9084. 'sep2=s' => \$sep2,
  9085. 'folder=s' => \@folder,
  9086. 'folderrec=s' => \@folderrec,
  9087. 'include=s' => \@include,
  9088. 'exclude=s' => \@exclude,
  9089. 'folderfirst=s' => \@folderfirst,
  9090. 'folderlast=s' => \@folderlast,
  9091. 'prefix1=s' => \$prefix1,
  9092. 'prefix2=s' => \$prefix2,
  9093. 'subfolder2=s' => \$subfolder2,
  9094. 'fixslash2!' => \$fixslash2,
  9095. 'fixInboxINBOX!' => \$fixInboxINBOX,
  9096. 'regextrans2=s' => \@regextrans2,
  9097. 'mixfolders!' => \$mixfolders,
  9098. 'skipemptyfolders!' => \$skipemptyfolders,
  9099. 'regexmess=s' => \@regexmess,
  9100. 'skipmess=s' => \@skipmess,
  9101. 'pipemess=s' => \@pipemess,
  9102. 'pipemesscheck!' => \$pipemesscheck,
  9103. 'disarmreadreceipts!' => \$disarmreadreceipts,
  9104. 'regexflag=s' => \@regexflag,
  9105. 'filterflags!' => \$filterflags,
  9106. 'flagscase!' => \$flagscase,
  9107. 'syncflagsaftercopy!' => \$syncflagsaftercopy,
  9108. 'delete|delete1!' => \$delete1,
  9109. 'delete2!' => \$delete2,
  9110. 'delete2duplicates!' => \$delete2duplicates,
  9111. 'delete2folders!' => \$delete2folders,
  9112. 'delete2foldersonly=s' => \$delete2foldersonly,
  9113. 'delete2foldersbutnot=s' => \$delete2foldersbutnot,
  9114. 'syncinternaldates!' => \$syncinternaldates,
  9115. 'idatefromheader!' => \$idatefromheader,
  9116. 'syncacls!' => \$syncacls,
  9117. 'maxsize=i' => \$maxsize,
  9118. 'minsize=i' => \$minsize,
  9119. 'maxage=i' => \$maxage,
  9120. 'minage=i' => \$minage,
  9121. 'search=s' => \$search,
  9122. 'search1=s' => \$search1,
  9123. 'search2=s' => \$search2,
  9124. 'foldersizes!' => \$foldersizes,
  9125. 'foldersizesatend!' => \$foldersizesatend,
  9126. 'dry!' => \$mysync->{dry},
  9127. 'expunge1|expunge!' => \$expunge1,
  9128. 'expunge2!' => \$expunge2,
  9129. 'uidexpunge2!' => \$uidexpunge2,
  9130. 'subscribed!' => \$subscribed,
  9131. 'subscribe!' => \$subscribe,
  9132. 'subscribeall|subscribe_all!' => \$subscribeall,
  9133. 'justbanner!' => \$justbanner,
  9134. 'justconnect!'=> \$justconnect,
  9135. 'justfolders!'=> \$justfolders,
  9136. 'justfoldersizes!' => \$justfoldersizes,
  9137. 'fast!' => \$fast,
  9138. 'version' => \$version,
  9139. 'help' => \$help,
  9140. 'timeout=i' => \$timeout,
  9141. 'timeout1=i' => \$mysync->{h1}->{timeout},
  9142. 'timeout2=i' => \$mysync->{h2}->{timeout},
  9143. 'skipheader=s' => \$skipheader,
  9144. 'useheader=s' => \@useheader,
  9145. 'wholeheaderifneeded!' => \$wholeheaderifneeded,
  9146. 'messageidnodomain!' => \$messageidnodomain,
  9147. 'skipsize!' => \$skipsize,
  9148. 'allowsizemismatch!' => \$allowsizemismatch,
  9149. 'fastio1!' => \$fastio1,
  9150. 'fastio2!' => \$fastio2,
  9151. 'sslcheck!' => \$mysync->{sslcheck},
  9152. 'ssl1!' => \$mysync->{ssl1},
  9153. 'ssl2!' => \$mysync->{ssl2},
  9154. 'ssl1_ssl_version=s' => \$mysync->{h1}->{sslargs}->{SSL_version},
  9155. 'ssl2_ssl_version=s' => \$mysync->{h2}->{sslargs}->{SSL_version},
  9156. 'sslargs1=s%' => \$mysync->{h1}->{sslargs},
  9157. 'sslargs2=s%' => \$mysync->{h2}->{sslargs},
  9158. 'tls1!' => \$mysync->{tls1},
  9159. 'tls2!' => \$mysync->{tls2},
  9160. 'uid1!' => \$uid1,
  9161. 'uid2!' => \$uid2,
  9162. 'authmech1=s' => \$authmech1,
  9163. 'authmech2=s' => \$authmech2,
  9164. 'authuser1=s' => \$authuser1,
  9165. 'authuser2=s' => \$authuser2,
  9166. 'proxyauth1' => \$proxyauth1,
  9167. 'proxyauth2' => \$proxyauth2,
  9168. 'split1=i' => \$split1,
  9169. 'split2=i' => \$split2,
  9170. 'buffersize=i' => \$buffersize,
  9171. 'reconnectretry1=i' => \$reconnectretry1,
  9172. 'reconnectretry2=i' => \$reconnectretry2,
  9173. 'tests!' => \$mysync->{ tests },
  9174. 'testsdebug|tests_debug!' => \$mysync->{ testsdebug },
  9175. 'testsunit=s@' => \$mysync->{testsunit},
  9176. 'testslive!' => \$mysync->{testslive},
  9177. 'testslive6!' => \$mysync->{testslive6},
  9178. 'justlogin!' => \$justlogin,
  9179. 'tmpdir=s' => \$tmpdir,
  9180. 'pidfile=s' => \$mysync->{pidfile},
  9181. 'pidfilelocking!' => \$mysync->{pidfilelocking},
  9182. 'releasecheck!' => \$releasecheck,
  9183. 'modulesversion|modules_version!' => \$modulesversion,
  9184. 'usecache!' => \$usecache,
  9185. 'cacheaftercopy!' => \$cacheaftercopy,
  9186. 'debugcache!' => \$debugcache,
  9187. 'useuid!' => \$useuid,
  9188. 'addheader!' => \$addheader,
  9189. 'exitwhenover=i' => \$exitwhenover,
  9190. 'checkselectable!' => \$checkselectable,
  9191. 'checkmessageexists!' => \$checkmessageexists,
  9192. 'expungeaftereach!' => \$expungeaftereach,
  9193. 'abletosearch!' => \$mysync->{abletosearch},
  9194. 'abletosearch1!' => \$mysync->{abletosearch1},
  9195. 'abletosearch2!' => \$mysync->{abletosearch2},
  9196. 'showpasswords!' => \$mysync->{showpasswords},
  9197. 'maxlinelength=i' => \$maxlinelength,
  9198. 'maxlinelengthcmd=s' => \$maxlinelengthcmd,
  9199. 'minmaxlinelength=i' => \$minmaxlinelength,
  9200. 'debugmaxlinelength!' => \$debugmaxlinelength,
  9201. 'fixcolonbug!' => \$fixcolonbug,
  9202. 'create_folder_old!' => \$create_folder_old,
  9203. 'maxmessagespersecond=f' => \$mysync->{maxmessagespersecond},
  9204. 'maxbytespersecond=i' => \$mysync->{maxbytespersecond},
  9205. 'maxbytesafter=i' => \$mysync->{maxbytesafter},
  9206. 'maxsleep=f' => \$mysync->{maxsleep},
  9207. 'skipcrossduplicates!' => \$skipcrossduplicates,
  9208. 'debugcrossduplicates!' => \$debugcrossduplicates,
  9209. 'log!' => \$mysync->{log},
  9210. 'logfile=s' => \$mysync->{logfile},
  9211. 'logdir=s' => \$mysync->{logdir},
  9212. 'errorsmax=i' => \$mysync->{errorsmax},
  9213. 'errorsdump!' => \$mysync->{errorsdump},
  9214. 'fetch_hash_set=s' => \$fetch_hash_set,
  9215. 'automap!' => \$mysync->{automap},
  9216. 'justautomap!' => \$mysync->{justautomap},
  9217. 'id!' => \$mysync->{id},
  9218. 'f1f2=s%' => \$mysync->{f1f2},
  9219. 'justfolderlists!' => \$mysync->{justfolderlists},
  9220. 'delete1emptyfolders' => \$mysync->{delete1emptyfolders},
  9221. ) ;
  9222. $debug and output( $mysync, "get options: [$opt_ret][$numopt]\n" ) ;
  9223. if ( ! $opt_ret ) {
  9224. return ;
  9225. }
  9226. return $numopt ;
  9227. }
  9228. sub get_options {
  9229. my $mysync = shift @ARG ;
  9230. my @arguments = @ARG ;
  9231. my $mycgi = $mysync->{cgi} ;
  9232. if ( under_cgi_context( ) ) {
  9233. # CGI context
  9234. return get_options_cgi( $mysync, @arguments ) ;
  9235. }else{
  9236. # Command line context ;
  9237. return get_options_cmd( $mysync, @arguments ) ;
  9238. }
  9239. return ;
  9240. }
  9241. sub testunitsession {
  9242. my $mysync = shift ;
  9243. if ( ! $mysync ) { return ; }
  9244. if ( ! $mysync->{ testsunit } ) { return ; }
  9245. my @functions = @{ $mysync->{ testsunit } } ;
  9246. if ( ! @functions ) { return ; }
  9247. SKIP: {
  9248. if ( ! @functions ) { skip 'No test in normal run' ; }
  9249. testsunit( @functions ) ;
  9250. done_testing( ) ;
  9251. }
  9252. return ;
  9253. }
  9254. sub tests_count_0s {
  9255. note( 'Entering tests_count_zeros()' ) ;
  9256. is( 0, count_0s( ), 'count_0s: no parameters => undef' ) ;
  9257. is( 1, count_0s( 0 ), 'count_0s: 0 => 1' ) ;
  9258. is( 0, count_0s( 1 ), 'count_0s: 1 => 0' ) ;
  9259. is( 1, count_0s( 1, 0, 1 ), 'count_0s: 1, 0, 1 => 1' ) ;
  9260. is( 2, count_0s( 1, 0, 1, 0 ), 'count_0s: 1, 0, 1, 0 => 2' ) ;
  9261. note( 'Leaving tests_count_zeros()' ) ;
  9262. return ;
  9263. }
  9264. sub count_0s {
  9265. my @array = @ARG ;
  9266. if ( ! @array ) { return 0 ; }
  9267. my $nb_zeros = 0 ;
  9268. map { $_ == 0 and $nb_zeros += 1 } @array ;
  9269. return $nb_zeros ;
  9270. }
  9271. sub tests_report_failures {
  9272. note( 'Entering tests_report_failures()' ) ;
  9273. is( undef, report_failures( ), 'report_failures: no parameters => undef' ) ;
  9274. is( "n° 1 - first\n", report_failures( ({'ok' => 0, name => 'first'}) ), 'report_failures: "first" failed => n° 1 - first' ) ;
  9275. is( q{}, report_failures( ( {'ok' => 1, name => 'first'} ) ), 'report_failures: "first" success =>' ) ;
  9276. is( "n° 2 - second\n", report_failures( ( {'ok' => 1, name => 'second'}, {'ok' => 0, name => 'second'} ) ), 'report_failures: "second" failed => n° 2 - second' ) ;
  9277. is( "n° 1 - first\nn° 2 - second\n", report_failures( ( {'ok' => 0, name => 'first'}, {'ok' => 0, name => 'second'} ) ), 'report_failures: both failed => n° 1 - first n° 2 - second' ) ;
  9278. note( 'Leaving tests_report_failures()' ) ;
  9279. return ;
  9280. }
  9281. sub report_failures {
  9282. my @details = @ARG ;
  9283. if ( ! @details ) { return ; }
  9284. my $counter = 1 ;
  9285. my $report = q{} ;
  9286. foreach my $details ( @details ) {
  9287. if ( ! $details->{ 'ok' } ) {
  9288. my $name = $details->{ 'name' } || 'NONAME' ;
  9289. $report .= "n° $counter - $name\n" ;
  9290. }
  9291. $counter += 1 ;
  9292. }
  9293. return $report ;
  9294. }
  9295. sub tests_true {
  9296. note( 'Entering tests_true()' ) ;
  9297. is( 1, 1, 'true: 1 is 1' ) ;
  9298. note( 'Leaving tests_true()' ) ;
  9299. return ;
  9300. }
  9301. sub tests_testsunit {
  9302. note( 'Entering tests_testunit()' ) ;
  9303. is( undef, testsunit( ), 'testsunit: no parameters => undef' ) ;
  9304. is( undef, testsunit( undef ), 'testsunit: an undef parameter => undef' ) ;
  9305. is( undef, testsunit( q{} ), 'testsunit: an empty parameter => undef' ) ;
  9306. is( undef, testsunit( 'idonotexist' ), 'testsunit: a do not exist function as parameter => undef' ) ;
  9307. is( undef, testsunit( 'tests_true' ), 'testsunit: tests_true => undef' ) ;
  9308. note( 'Leaving tests_testunit()' ) ;
  9309. return ;
  9310. }
  9311. sub testsunit {
  9312. my @functions = @ARG ;
  9313. if ( ! @functions ) { #
  9314. myprint( "testsunit warning: no argument given\n" ) ;
  9315. return ;
  9316. }
  9317. foreach my $function ( @functions ) {
  9318. if ( ! $function ) {
  9319. myprint( "testsunit warning: argument is empty\n" ) ;
  9320. next ;
  9321. }
  9322. if ( ! exists &$function ) {
  9323. myprint( "testsunit warning: function $function does not exist\n" ) ;
  9324. next ;
  9325. }
  9326. if ( ! defined &$function ) {
  9327. myprint( "testsunit warning: function $function is not defined\n" ) ;
  9328. next ;
  9329. }
  9330. my $function_ref = \&{ $function } ;
  9331. &$function_ref() ;
  9332. }
  9333. return ;
  9334. }
  9335. sub testsdebug {
  9336. my $mysync = shift ;
  9337. if ( ! $mysync->{ testsdebug } ) { return ; }
  9338. SKIP: {
  9339. if ( ! $mysync->{ testsdebug } ) {
  9340. skip 'No test in normal run' ;
  9341. }
  9342. note( 'Entering testsdebug()' ) ;
  9343. ok( ( ( not -d 'W/tmp/tests' ) or rmtree( 'W/tmp/tests/' ) ), 'testsdebug: rmtree W/tmp/tests' ) ;
  9344. #tests_bytes_display_string( ) ;
  9345. #tests_ucsecond( ) ;
  9346. #tests_mkpath( ) ;
  9347. #tests_format_for_imap_arg( ) ;
  9348. #tests_is_a_release_number( ) ;
  9349. #tests_delete1emptyfolders( ) ;
  9350. #tests_memory_consumption( ) ;
  9351. #tests_imap2_folder_name() ;
  9352. #tests_length_ref( ) ;
  9353. #tests_diff_or_NA( ) ;
  9354. #tests_match_number( ) ;
  9355. #tests_all_defined( ) ;
  9356. #tests_guess_separator( ) ;
  9357. #tests_message_for_host2( ) ;
  9358. #tests_special_from_folders_hash( ) ;
  9359. #tests_do_valid_directory( ) ;
  9360. #tests_notmatch( ) ;
  9361. #tests_match( ) ;
  9362. #tests_get_options( ) ;
  9363. #tests_rand32( ) ;
  9364. #tests_string_to_file( ) ;
  9365. #tests_hashsynclocal( ) ;
  9366. #tests_output( ) ;
  9367. #tests_output_reset_with( ) ;
  9368. #tests_output_start( ) ;
  9369. #tests_hashsync( ) ;
  9370. #tests_check_last_release( ) ;
  9371. #tests_cpu_number( ) ;
  9372. #tests_load_and_delay( ) ;
  9373. #tests_loadavg( ) ;
  9374. #tests_backtick( ) ;
  9375. #tests_firstline( ) ;
  9376. #tests_pipemess( ) ;
  9377. #tests_not_long_imapsync_version_public( ) ;
  9378. #tests_get_options_cgi( ) ;
  9379. #tests_guess_special( ) ;
  9380. ####tests_reconnect_if_needed( ) ;
  9381. #tests_reconnect_12_if_needed( ) ;
  9382. #tests_sleep_max_bytes( ) ;
  9383. #tests_file_to_string( ) ;
  9384. #tests_under_cgi_context( ) ;
  9385. #tests_umask( ) ;
  9386. #tests_umask_str( ) ;
  9387. #tests_set_umask( ) ;
  9388. #tests_createhashfileifneeded( ) ;
  9389. #tests_filter_forbidden_characters( ) ;
  9390. #tests_logfile( ) ;
  9391. #tests_setlogfile( ) ;
  9392. #tests_move_slash( ) ;
  9393. #tests_testsunit( ) ;
  9394. #tests_always_fail( ) ;
  9395. #tests_count_0s( ) ;
  9396. #tests_report_failures( ) ;
  9397. #tests_max( ) ;
  9398. #tests_min( ) ;
  9399. #tests_sleep_if_needed( ) ;
  9400. #tests_imapsping( ) ;
  9401. #tests_tcpping( ) ;
  9402. #tests_sslcheck( ) ;
  9403. #tests_resolv( ) ;
  9404. #tests_resolvrev( ) ;
  9405. #tests_connect_socket( ) ;
  9406. #tests_probe_imapssl( ) ;
  9407. #tests_mailimapclient_connect( ) ;
  9408. #tests_guess_prefix( ) ;
  9409. #tests_usage( ) ;
  9410. #tests_version_from_rcs( ) ;
  9411. #tests_mailimapclient_connect_bug( ) ; # it fails with Mail-IMAPClient <= 3.39
  9412. tests_backslash_caret( ) ;
  9413. note( 'Leaving testsdebug()' ) ;
  9414. done_testing( ) ;
  9415. }
  9416. return ;
  9417. }
  9418. sub tests {
  9419. my $mysync = shift ;
  9420. if ( ! $mysync->{ tests } ) { return ; }
  9421. SKIP: {
  9422. skip 'No test in normal run' if ( ! $mysync->{ tests } ) ;
  9423. note( 'Entering tests()' ) ;
  9424. tests_folder_routines( ) ;
  9425. tests_compare_lists( ) ;
  9426. tests_regexmess( ) ;
  9427. tests_skipmess( ) ;
  9428. tests_flags_regex();
  9429. tests_ucsecond( ) ;
  9430. tests_permanentflags();
  9431. tests_flags_filter( ) ;
  9432. tests_separator_invert( ) ;
  9433. tests_imap2_folder_name( ) ;
  9434. tests_command_line_nopassword( ) ;
  9435. tests_good_date( ) ;
  9436. tests_max( ) ;
  9437. tests_remove_not_num();
  9438. tests_memory_consumption( ) ;
  9439. tests_is_a_release_number();
  9440. tests_imapsync_basename();
  9441. tests_list_keys_in_2_not_in_1();
  9442. tests_convert_sep_to_slash( ) ;
  9443. tests_match_a_cache_file( ) ;
  9444. tests_cache_map( ) ;
  9445. tests_get_cache( ) ;
  9446. tests_clean_cache( ) ;
  9447. tests_clean_cache_2( ) ;
  9448. tests_touch( ) ;
  9449. tests_flagscase( ) ;
  9450. tests_mkpath( ) ;
  9451. tests_extract_header( ) ;
  9452. tests_decompose_header( ) ;
  9453. tests_epoch( ) ;
  9454. tests_add_header( ) ;
  9455. tests_cache_dir_fix( ) ;
  9456. tests_cache_dir_fix_win( ) ;
  9457. tests_filter_forbidden_characters( ) ;
  9458. tests_cache_folder( ) ;
  9459. tests_time_remaining( ) ;
  9460. tests_decompose_regex( ) ;
  9461. tests_backtick( ) ;
  9462. tests_bytes_display_string( ) ;
  9463. tests_header_line_normalize( ) ;
  9464. tests_fix_Inbox_INBOX_mapping( ) ;
  9465. tests_max_line_length( ) ;
  9466. tests_subject( ) ;
  9467. tests_msgs_from_maxmin( ) ;
  9468. tests_tmpdir_has_colon_bug( ) ;
  9469. tests_sleep_max_messages( ) ;
  9470. tests_sleep_max_bytes( ) ;
  9471. tests_logfile( ) ;
  9472. tests_setlogfile( ) ;
  9473. tests_jux_utf8( ) ;
  9474. tests_pipemess( ) ;
  9475. tests_jux_utf8_list( ) ;
  9476. tests_guess_prefix( ) ;
  9477. tests_guess_separator( ) ;
  9478. tests_format_for_imap_arg( ) ;
  9479. tests_imapsync_id( ) ;
  9480. tests_date_from_rcs( ) ;
  9481. tests_quota_extract_storage_limit_in_bytes( ) ;
  9482. tests_quota_extract_storage_current_in_bytes( ) ;
  9483. tests_guess_special( ) ;
  9484. tests_do_valid_directory( ) ;
  9485. tests_delete1emptyfolders( ) ;
  9486. tests_message_for_host2( ) ;
  9487. tests_length_ref( ) ;
  9488. tests_firstline( ) ;
  9489. tests_diff_or_NA( ) ;
  9490. tests_match_number( ) ;
  9491. tests_all_defined( ) ;
  9492. tests_special_from_folders_hash( ) ;
  9493. tests_notmatch( ) ;
  9494. tests_match( ) ;
  9495. tests_get_options( ) ;
  9496. tests_get_options_cgi( ) ;
  9497. tests_rand32( ) ;
  9498. tests_hashsynclocal( ) ;
  9499. tests_hashsync( ) ;
  9500. tests_output( ) ;
  9501. tests_output_reset_with( ) ;
  9502. tests_output_start( ) ;
  9503. tests_check_last_release( ) ;
  9504. tests_loadavg( ) ;
  9505. tests_cpu_number( ) ;
  9506. tests_load_and_delay( ) ;
  9507. #tests_imapsping( ) ;
  9508. #tests_tcpping( ) ;
  9509. tests_sslcheck( ) ;
  9510. tests_not_long_imapsync_version_public( ) ;
  9511. tests_reconnect_if_needed( ) ;
  9512. tests_reconnect_12_if_needed( ) ;
  9513. tests_sleep_if_needed( ) ;
  9514. tests_string_to_file( ) ;
  9515. tests_file_to_string( ) ;
  9516. tests_under_cgi_context( ) ;
  9517. tests_umask( ) ;
  9518. tests_umask_str( ) ;
  9519. tests_set_umask( ) ;
  9520. tests_createhashfileifneeded( ) ;
  9521. tests_move_slash( ) ;
  9522. tests_testsunit( ) ;
  9523. tests_count_0s( ) ;
  9524. tests_report_failures( ) ;
  9525. tests_min( ) ;
  9526. #tests_resolv( ) ;
  9527. #tests_resolvrev( ) ;
  9528. tests_connect_socket( ) ;
  9529. tests_probe_imapssl( ) ;
  9530. tests_mailimapclient_connect( ) ;
  9531. tests_usage( ) ;
  9532. tests_version_from_rcs( ) ;
  9533. tests_backslash_caret( ) ;
  9534. #tests_mailimapclient_connect_bug( ) ; # it fails with Mail-IMAPClient <= 3.39
  9535. #tests_always_fail( ) ;
  9536. done_testing( 1012 ) ;
  9537. note( 'Leaving tests()' ) ;
  9538. }
  9539. return ;
  9540. }