imapsync 581 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693126941269512696126971269812699127001270112702127031270412705127061270712708127091271012711127121271312714127151271612717127181271912720127211272212723127241272512726127271272812729127301273112732127331273412735127361273712738127391274012741127421274312744127451274612747127481274912750127511275212753127541275512756127571275812759127601276112762127631276412765127661276712768127691277012771127721277312774127751277612777127781277912780127811278212783127841278512786127871278812789127901279112792127931279412795127961279712798127991280012801128021280312804128051280612807128081280912810128111281212813128141281512816128171281812819128201282112822128231282412825128261282712828128291283012831128321283312834128351283612837128381283912840128411284212843128441284512846128471284812849128501285112852128531285412855128561285712858128591286012861128621286312864128651286612867128681286912870128711287212873128741287512876128771287812879128801288112882128831288412885128861288712888128891289012891128921289312894128951289612897128981289912900129011290212903129041290512906129071290812909129101291112912129131291412915129161291712918129191292012921129221292312924129251292612927129281292912930129311293212933129341293512936129371293812939129401294112942129431294412945129461294712948129491295012951129521295312954129551295612957129581295912960129611296212963129641296512966129671296812969129701297112972129731297412975129761297712978129791298012981129821298312984129851298612987129881298912990129911299212993129941299512996129971299812999130001300113002130031300413005130061300713008130091301013011130121301313014130151301613017130181301913020130211302213023130241302513026130271302813029130301303113032130331303413035130361303713038130391304013041130421304313044130451304613047130481304913050130511305213053130541305513056130571305813059130601306113062130631306413065130661306713068130691307013071130721307313074130751307613077130781307913080130811308213083130841308513086130871308813089130901309113092130931309413095130961309713098130991310013101131021310313104131051310613107131081310913110131111311213113131141311513116131171311813119131201312113122131231312413125131261312713128131291313013131131321313313134131351313613137131381313913140131411314213143131441314513146131471314813149131501315113152131531315413155131561315713158131591316013161131621316313164131651316613167131681316913170131711317213173131741317513176131771317813179131801318113182131831318413185131861318713188131891319013191131921319313194131951319613197131981319913200132011320213203132041320513206132071320813209132101321113212132131321413215132161321713218132191322013221132221322313224132251322613227132281322913230132311323213233132341323513236132371323813239132401324113242132431324413245132461324713248132491325013251132521325313254132551325613257132581325913260132611326213263132641326513266132671326813269132701327113272132731327413275132761327713278132791328013281132821328313284132851328613287132881328913290132911329213293132941329513296132971329813299133001330113302133031330413305133061330713308133091331013311133121331313314133151331613317133181331913320133211332213323133241332513326133271332813329133301333113332133331333413335133361333713338133391334013341133421334313344133451334613347133481334913350133511335213353133541335513356133571335813359133601336113362133631336413365133661336713368133691337013371133721337313374133751337613377133781337913380133811338213383133841338513386133871338813389133901339113392133931339413395133961339713398133991340013401134021340313404134051340613407134081340913410134111341213413134141341513416134171341813419134201342113422134231342413425134261342713428134291343013431134321343313434134351343613437134381343913440134411344213443134441344513446134471344813449134501345113452134531345413455134561345713458134591346013461134621346313464134651346613467134681346913470134711347213473134741347513476134771347813479134801348113482134831348413485134861348713488134891349013491134921349313494134951349613497134981349913500135011350213503135041350513506135071350813509135101351113512135131351413515135161351713518135191352013521135221352313524135251352613527135281352913530135311353213533135341353513536135371353813539135401354113542135431354413545135461354713548135491355013551135521355313554135551355613557135581355913560135611356213563135641356513566135671356813569135701357113572135731357413575135761357713578135791358013581135821358313584135851358613587135881358913590135911359213593135941359513596135971359813599136001360113602136031360413605136061360713608136091361013611136121361313614136151361613617136181361913620136211362213623136241362513626136271362813629136301363113632136331363413635136361363713638136391364013641136421364313644136451364613647136481364913650136511365213653136541365513656136571365813659136601366113662136631366413665136661366713668136691367013671136721367313674136751367613677136781367913680136811368213683136841368513686136871368813689136901369113692136931369413695136961369713698136991370013701137021370313704137051370613707137081370913710137111371213713137141371513716137171371813719137201372113722137231372413725137261372713728137291373013731137321373313734137351373613737137381373913740137411374213743137441374513746137471374813749137501375113752137531375413755137561375713758137591376013761137621376313764137651376613767137681376913770137711377213773137741377513776137771377813779137801378113782137831378413785137861378713788137891379013791137921379313794137951379613797137981379913800138011380213803138041380513806138071380813809138101381113812138131381413815138161381713818138191382013821138221382313824138251382613827138281382913830138311383213833138341383513836138371383813839138401384113842138431384413845138461384713848138491385013851138521385313854138551385613857138581385913860138611386213863138641386513866138671386813869138701387113872138731387413875138761387713878138791388013881138821388313884138851388613887138881388913890138911389213893138941389513896138971389813899139001390113902139031390413905139061390713908139091391013911139121391313914139151391613917139181391913920139211392213923139241392513926139271392813929139301393113932139331393413935139361393713938139391394013941139421394313944139451394613947139481394913950139511395213953139541395513956139571395813959139601396113962139631396413965139661396713968139691397013971139721397313974139751397613977139781397913980139811398213983139841398513986139871398813989139901399113992139931399413995139961399713998139991400014001140021400314004140051400614007140081400914010140111401214013140141401514016140171401814019140201402114022140231402414025140261402714028140291403014031140321403314034140351403614037140381403914040140411404214043140441404514046140471404814049140501405114052140531405414055140561405714058140591406014061140621406314064140651406614067140681406914070140711407214073140741407514076140771407814079140801408114082140831408414085140861408714088140891409014091140921409314094140951409614097140981409914100141011410214103141041410514106141071410814109141101411114112141131411414115141161411714118141191412014121141221412314124141251412614127141281412914130141311413214133141341413514136141371413814139141401414114142141431414414145141461414714148141491415014151141521415314154141551415614157141581415914160141611416214163141641416514166141671416814169141701417114172141731417414175141761417714178141791418014181141821418314184141851418614187141881418914190141911419214193141941419514196141971419814199142001420114202142031420414205142061420714208142091421014211142121421314214142151421614217142181421914220142211422214223142241422514226142271422814229142301423114232142331423414235142361423714238142391424014241142421424314244142451424614247142481424914250142511425214253142541425514256142571425814259142601426114262142631426414265142661426714268142691427014271142721427314274142751427614277142781427914280142811428214283142841428514286142871428814289142901429114292142931429414295142961429714298142991430014301143021430314304143051430614307143081430914310143111431214313143141431514316143171431814319143201432114322143231432414325143261432714328143291433014331143321433314334143351433614337143381433914340143411434214343143441434514346143471434814349143501435114352143531435414355143561435714358143591436014361143621436314364143651436614367143681436914370143711437214373143741437514376143771437814379143801438114382143831438414385143861438714388143891439014391143921439314394143951439614397143981439914400144011440214403144041440514406144071440814409144101441114412144131441414415144161441714418144191442014421144221442314424144251442614427144281442914430144311443214433144341443514436144371443814439144401444114442144431444414445144461444714448144491445014451144521445314454144551445614457144581445914460144611446214463144641446514466144671446814469144701447114472144731447414475144761447714478144791448014481144821448314484144851448614487144881448914490144911449214493144941449514496144971449814499145001450114502145031450414505145061450714508145091451014511145121451314514145151451614517145181451914520145211452214523145241452514526145271452814529145301453114532145331453414535145361453714538145391454014541145421454314544145451454614547145481454914550145511455214553145541455514556145571455814559145601456114562145631456414565145661456714568145691457014571145721457314574145751457614577145781457914580145811458214583145841458514586145871458814589145901459114592145931459414595145961459714598145991460014601146021460314604146051460614607146081460914610146111461214613146141461514616146171461814619146201462114622146231462414625146261462714628146291463014631146321463314634146351463614637146381463914640146411464214643146441464514646146471464814649146501465114652146531465414655146561465714658146591466014661146621466314664146651466614667146681466914670146711467214673146741467514676146771467814679146801468114682146831468414685146861468714688146891469014691146921469314694146951469614697146981469914700147011470214703147041470514706147071470814709147101471114712147131471414715147161471714718147191472014721147221472314724147251472614727147281472914730147311473214733147341473514736147371473814739147401474114742147431474414745147461474714748147491475014751147521475314754147551475614757147581475914760147611476214763147641476514766147671476814769147701477114772147731477414775147761477714778147791478014781147821478314784147851478614787147881478914790147911479214793147941479514796147971479814799148001480114802148031480414805148061480714808148091481014811148121481314814148151481614817148181481914820148211482214823148241482514826148271482814829148301483114832148331483414835148361483714838148391484014841148421484314844148451484614847148481484914850148511485214853148541485514856148571485814859148601486114862148631486414865148661486714868148691487014871148721487314874148751487614877148781487914880148811488214883148841488514886148871488814889148901489114892148931489414895148961489714898148991490014901149021490314904149051490614907149081490914910149111491214913149141491514916149171491814919149201492114922149231492414925149261492714928149291493014931149321493314934149351493614937149381493914940149411494214943149441494514946149471494814949149501495114952149531495414955149561495714958149591496014961149621496314964149651496614967149681496914970149711497214973149741497514976149771497814979149801498114982149831498414985149861498714988149891499014991149921499314994149951499614997149981499915000150011500215003150041500515006150071500815009150101501115012150131501415015150161501715018150191502015021150221502315024150251502615027150281502915030150311503215033150341503515036150371503815039150401504115042150431504415045150461504715048150491505015051150521505315054150551505615057150581505915060150611506215063150641506515066150671506815069150701507115072150731507415075150761507715078150791508015081150821508315084150851508615087150881508915090150911509215093150941509515096150971509815099151001510115102151031510415105151061510715108151091511015111151121511315114151151511615117151181511915120151211512215123151241512515126151271512815129151301513115132151331513415135151361513715138151391514015141151421514315144151451514615147151481514915150151511515215153151541515515156151571515815159151601516115162151631516415165151661516715168151691517015171151721517315174151751517615177151781517915180151811518215183151841518515186151871518815189151901519115192151931519415195151961519715198151991520015201152021520315204152051520615207152081520915210152111521215213152141521515216152171521815219152201522115222152231522415225152261522715228152291523015231152321523315234152351523615237152381523915240152411524215243152441524515246152471524815249152501525115252152531525415255152561525715258152591526015261152621526315264152651526615267152681526915270152711527215273152741527515276152771527815279152801528115282152831528415285152861528715288152891529015291152921529315294152951529615297152981529915300153011530215303153041530515306153071530815309153101531115312153131531415315153161531715318153191532015321153221532315324153251532615327153281532915330153311533215333153341533515336153371533815339153401534115342153431534415345153461534715348153491535015351153521535315354153551535615357153581535915360153611536215363153641536515366153671536815369153701537115372153731537415375153761537715378153791538015381153821538315384153851538615387153881538915390153911539215393153941539515396153971539815399154001540115402154031540415405154061540715408154091541015411154121541315414154151541615417154181541915420154211542215423154241542515426154271542815429154301543115432154331543415435154361543715438154391544015441154421544315444154451544615447154481544915450154511545215453154541545515456154571545815459154601546115462154631546415465154661546715468154691547015471154721547315474154751547615477154781547915480154811548215483154841548515486154871548815489154901549115492154931549415495154961549715498154991550015501155021550315504155051550615507155081550915510155111551215513155141551515516155171551815519155201552115522155231552415525155261552715528155291553015531155321553315534155351553615537155381553915540155411554215543155441554515546155471554815549155501555115552155531555415555155561555715558155591556015561155621556315564155651556615567155681556915570155711557215573155741557515576155771557815579155801558115582155831558415585155861558715588155891559015591155921559315594155951559615597155981559915600156011560215603156041560515606156071560815609156101561115612156131561415615156161561715618156191562015621156221562315624156251562615627156281562915630156311563215633156341563515636156371563815639156401564115642156431564415645156461564715648156491565015651156521565315654156551565615657156581565915660156611566215663156641566515666156671566815669156701567115672156731567415675156761567715678156791568015681156821568315684156851568615687156881568915690156911569215693156941569515696156971569815699157001570115702157031570415705157061570715708157091571015711157121571315714157151571615717157181571915720157211572215723157241572515726157271572815729157301573115732157331573415735157361573715738157391574015741157421574315744157451574615747157481574915750157511575215753157541575515756157571575815759157601576115762157631576415765157661576715768157691577015771
  1. #!/usr/bin/env perl
  2. # $Id: imapsync,v 1.937 2019/05/01 22:14:00 gilles Exp gilles $
  3. # structure
  4. # pod documentation
  5. # use pragmas
  6. # main program
  7. # global variables initialization
  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.937 $
  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. one 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 of data transferred by not transferring
  40. a given message if it resides already on both sides.
  41. Same specific headers and the transfer is done only once.
  42. By default, the identification headers are
  43. "Message-Id:" and "Received:" lines
  44. but this choice can be changed with the --useheader option.
  45. All flags are preserved, unread messages will stay unread,
  46. read ones will stay read, deleted will stay deleted.
  47. You can stop the transfer at any time and restart it later,
  48. imapsync works well with bad connections and interruptions,
  49. by design.
  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. You can also decide to remove empty folders once all of their
  57. messages have been transferred. Add --delete1emptyfolders to
  58. obtain this behavior.
  59. A different scenario is synchronizing a mailbox B from another mailbox A
  60. in case you just want to keep a "live" copy of A in B.
  61. For this, option --delete2 has to be used, it deletes messages in host2
  62. folder B that are not in host1 folder A. If you also need to destroy
  63. host2 folders that are not in host1 then use --delete2folders. See also
  64. --delete2foldersonly and --delete2foldersbutnot.
  65. Imapsync is not adequate for maintaining two active imap accounts
  66. in synchronization when the user plays independently on both sides.
  67. Use offlineimap (written by John Goerzen) or mbsync (written by
  68. Michael R. Elkins) for a 2 ways synchronization.
  69. =head1 OPTIONS
  70. usage: imapsync [options]
  71. Mandatory options are the six values, three on each sides,
  72. needed to log in into the IMAP servers, ie,
  73. a host, a username, and a password, two times.
  74. Conventions used:
  75. str means string
  76. int means integer
  77. reg means regular expression
  78. cmd means command
  79. --dry : Makes imapsync doing nothing for real, just print what
  80. would be done without --dry.
  81. =head2 OPTIONS/credentials
  82. --host1 str : Source or "from" imap server. Mandatory.
  83. --port1 int : Port to connect on host1.
  84. Optional since default port is 143 or 993 if --ssl1
  85. --user1 str : User to login on host1. Mandatory.
  86. --password1 str : Password for the user1.
  87. --host2 str : "destination" imap server. Mandatory.
  88. --port2 int : Port to connect on host2.
  89. Optional since default port is 143 or 993 if --ssl2
  90. --user2 str : User to login on host2. Mandatory.
  91. --password2 str : Password for the user2.
  92. --showpasswords : Shows passwords on output instead of "MASKED".
  93. Useful to restart a complete run by just reading the log,
  94. or to debug passwords. It's not a secure practice.
  95. --passfile1 str : Password file for the user1. It must contain the
  96. password on the first line. This option avoids to show
  97. the password on the command line like --password1 does.
  98. --passfile2 str : Password file for the user2. Contains the password.
  99. You can also pass the passwords in the environment variables
  100. IMAPSYNC_PASSWORD1 and IMAPSYNC_PASSWORD2
  101. =head2 OPTIONS/encryption
  102. --nossl1 : Do not use a SSL connection on host1.
  103. --ssl1 : Use a SSL connection on host1. On by default if possible.
  104. --nossl2 : Do not use a SSL connection on host2.
  105. --ssl2 : Use a SSL connection on host2. On by default if possible.
  106. --notls1 : Do not use a TLS connection on host1.
  107. --tls1 : Use a TLS connection on host1. On by default if possible.
  108. --notls2 : Do not use a TLS connection on host2.
  109. --tls2 : Use a TLS connection on host2. On by default if possible.
  110. --debugssl int : SSL debug mode from 0 to 4.
  111. --sslargs1 str : Pass any ssl parameter for host1 ssl or tls connection. Example:
  112. --sslargs1 SSL_verify_mode=1 --sslargs1 SSL_version=SSLv3
  113. See all possibilities in the new() method of IO::Socket::SSL
  114. http://search.cpan.org/perldoc?IO::Socket::SSL#Description_Of_Methods
  115. --sslargs2 str : Pass any ssl parameter for host2 ssl or tls connection.
  116. See --sslargs1
  117. --timeout1 int : Connection timeout in seconds for host1.
  118. Default is 120 and 0 means no timeout at all.
  119. --timeout2 int : Connection timeout in seconds for host2.
  120. Default is 120 and 0 means no timeout at all.
  121. =head2 OPTIONS/authentication
  122. --authmech1 str : Auth mechanism to use with host1:
  123. PLAIN, LOGIN, CRAM-MD5 etc. Use UPPERCASE.
  124. --authmech2 str : Auth mechanism to use with host2. See --authmech1
  125. --authuser1 str : User to auth with on host1 (admin user).
  126. Avoid using --authmech1 SOMETHING with --authuser1.
  127. --authuser2 str : User to auth with on host2 (admin user).
  128. --proxyauth1 : Use proxyauth on host1. Requires --authuser1.
  129. Required by Sun/iPlanet/Netscape IMAP servers to
  130. be able to use an administrative user.
  131. --proxyauth2 : Use proxyauth on host2. Requires --authuser2.
  132. --authmd51 : Use MD5 authentication for host1.
  133. --authmd52 : Use MD5 authentication for host2.
  134. --domain1 str : Domain on host1 (NTLM authentication).
  135. --domain2 str : Domain on host2 (NTLM authentication).
  136. =head2 OPTIONS/folders
  137. --folder str : Sync this folder.
  138. --folder str : and this one, etc.
  139. --folderrec str : Sync this folder recursively.
  140. --folderrec str : and this one, etc.
  141. --folderfirst str : Sync this folder first. --folderfirst "Work"
  142. --folderfirst str : then this one, etc.
  143. --folderlast str : Sync this folder last. --folderlast "[Gmail]/All Mail"
  144. --folderlast str : then this one, etc.
  145. --nomixfolders : Do not merge folders when host1 is case-sensitive
  146. while host2 is not (like Exchange). Only the first
  147. similar folder is synced (ex: with Sent SENT sent
  148. on host1 only Sent will be synced to host2).
  149. --skipemptyfolders : Empty host1 folders are not created on host2.
  150. --include reg : Sync folders matching this regular expression
  151. --include reg : or this one, etc.
  152. If both --include --exclude options are used, then
  153. include is done before.
  154. --exclude reg : Skips folders matching this regular expression
  155. Several folders to avoid:
  156. --exclude 'fold1|fold2|f3' skips fold1, fold2 and f3.
  157. --exclude reg : or this one, etc.
  158. --automap : guesses folders mapping, for folders well known as
  159. "Sent", "Junk", "Drafts", "All", "Archive", "Flagged".
  160. --f1f2 str1=str2 : Force folder str1 to be synced to str2,
  161. --f1f2 overrides --automap and --regextrans2.
  162. --subfolder2 str : Syncs the whole host1 folders hierarchy under the
  163. host2 folder named str.
  164. It does it internally by adding three
  165. --regextrans2 options before all others.
  166. Add --debug to see what's really going on.
  167. --subfolder1 str : Syncs the host1 folders hierarchy under str
  168. to the root hierarchy of host2.
  169. It's the couterpart of a sync done by --subfolder2
  170. in the reverse order. Use --subfolder2 str
  171. for a backup under str and --subfolder1 str
  172. for the restore from str.
  173. --subscribed : Transfers subscribed folders.
  174. --subscribe : Subscribe to the folders transferred on the
  175. host2 that are subscribed on host1. On by default.
  176. --subscribeall : Subscribe to the folders transferred on the
  177. host2 even if they are not subscribed on host1.
  178. --prefix1 str : Remove prefix str to all destination folders,
  179. usually INBOX. or INBOX/ or an empty string "".
  180. imapsync guesses the prefix if host1 imap server
  181. does not have NAMESPACE capability. This option
  182. should not be used, most of the time.
  183. --prefix2 str : Add prefix to all host2 folders. See --prefix1
  184. --sep1 str : Host1 separator in case NAMESPACE is not supported.
  185. --sep2 str : Host2 separator in case NAMESPACE is not supported.
  186. --regextrans2 reg : Apply the whole regex to each destination folders.
  187. --regextrans2 reg : and this one. etc.
  188. When you play with the --regextrans2 option, first
  189. add also the safe options --dry --justfolders
  190. Then, when happy, remove --dry, remove --justfolders.
  191. Have in mind that --regextrans2 is applied after prefix
  192. and separator inversion. For examples see
  193. https://imapsync.lamiral.info/FAQ.d/FAQ.Folders_Mapping.txt
  194. =head2 OPTIONS/folders sizes
  195. --nofoldersizes : Do not calculate the size of each folder at the
  196. beginning of the sync. Default is to calculate them.
  197. --nofoldersizesatend: Do not calculate the size of each folder at the
  198. end of the sync. Default is to calculate them.
  199. --justfoldersizes : Exit after having printed the initial folder sizes.
  200. =head2 OPTIONS/tmp
  201. --tmpdir str : Where to store temporary files and subdirectories.
  202. Will be created if it doesn't exist.
  203. Default is system specific, Unix is /tmp but
  204. /tmp is often too small and deleted at reboot.
  205. --tmpdir /var/tmp should be better.
  206. --pidfile str : The file where imapsync pid is written,
  207. it can be dirname/filename.
  208. Default name is imapsync.pid in tmpdir.
  209. --pidfilelocking : Abort if pidfile already exists. Useful to avoid
  210. concurrent transfers on the same mailbox.
  211. =head2 OPTIONS/log
  212. --nolog : Turn off logging on file
  213. --logfile str : Change the default log filename (can be dirname/filename).
  214. --logdir str : Change the default log directory. Default is LOG_imapsync/
  215. =head2 OPTIONS/messages
  216. --skipmess reg : Skips messages matching the regex.
  217. Example: 'm/[\x80-ff]/' # to avoid 8bits messages.
  218. --skipmess is applied before --regexmess
  219. --skipmess reg : or this one, etc.
  220. --pipemess cmd : Apply this cmd command to each message content
  221. before the copy.
  222. --pipemess cmd : and this one, etc.
  223. --disarmreadreceipts : Disarms read receipts (host2 Exchange issue)
  224. --regexmess reg : Apply the whole regex to each message before transfer.
  225. Example: 's/\000/ /g' # to replace null by space.
  226. --regexmess reg : and this one, etc.
  227. =head2 OPTIONS/flags
  228. --regexflag reg : Apply the whole regex to each flags list.
  229. Example: 's/"Junk"//g' # to remove "Junk" flag.
  230. --regexflag reg : then this one, etc.
  231. --resyncflags : Resync flags for already transferred messages.
  232. On by default.
  233. --noresyncflags : Do not resync flags for already transferred messages.
  234. May be useful when a user has already started to play
  235. with its host2 account.
  236. =head2 OPTIONS/deletions
  237. --delete1 : Deletes messages on host1 server after a successful
  238. transfer. Option --delete1 has the following behavior:
  239. it marks messages as deleted with the IMAP flag
  240. \Deleted, then messages are really deleted with an
  241. EXPUNGE IMAP command. If expunging after each message
  242. slows down too much the sync then use
  243. --noexpungeaftereach to speed up.
  244. --expunge1 : Expunge messages on host1 just before syncing a folder.
  245. Expunge is done per folder.
  246. Expunge aims is to really delete messages marked deleted.
  247. An expunge is also done after each message copied
  248. if option --delete1 is set.
  249. --noexpunge1 : Do not expunge messages on host1.
  250. --delete1emptyfolders : Deletes empty folders on host1, INBOX excepted.
  251. Useful with --delete1 since what remains on host1
  252. is only what failed to be synced.
  253. --delete2 : Delete messages in host2 that are not in
  254. host1 server. Useful for backup or pre-sync.
  255. --delete2duplicates : Delete messages in host2 that are duplicates.
  256. Works only without --useuid since duplicates are
  257. detected with an header part of each message.
  258. --delete2folders : Delete folders in host2 that are not in host1 server.
  259. For safety, first try it like this (it is safe):
  260. --delete2folders --dry --justfolders --nofoldersizes
  261. --delete2foldersonly reg : Deleted only folders matching regex.
  262. Example: --delete2foldersonly "/^Junk$|^INBOX.Junk$/"
  263. --delete2foldersbutnot reg : Do not delete folders matching regex.
  264. Example: --delete2foldersbutnot "/Tasks$|Contacts$|Foo$/"
  265. --expunge2 : Expunge messages on host2 after messages transfer.
  266. --uidexpunge2 : uidexpunge messages on the host2 account
  267. that are not on the host1 account, requires --delete2
  268. =head2 OPTIONS/dates
  269. --syncinternaldates : Sets the internal dates on host2 same as host1.
  270. Turned on by default. Internal date is the date
  271. a message arrived on a host (mtime).
  272. --idatefromheader : Sets the internal dates on host2 same as the
  273. "Date:" headers.
  274. If you encounter problems with dates see also
  275. https://imapsync.lamiral.info/FAQ.d/FAQ.Dates.txt
  276. =head2 OPTIONS/message selection
  277. --maxsize int : Skip messages larger (or equal) than int bytes
  278. --minsize int : Skip messages smaller (or equal) than int bytes
  279. --maxage int : Skip messages older than int days.
  280. final stats (skipped) don't count older messages
  281. see also --minage
  282. --minage int : Skip messages newer than int days.
  283. final stats (skipped) don't count newer messages
  284. You can do (+ are the messages selected):
  285. past|----maxage+++++++++++++++>now
  286. past|+++++++++++++++minage---->now
  287. past|----maxage+++++minage---->now (intersection)
  288. past|++++minage-----maxage++++>now (union)
  289. --search str : Selects only messages returned by this IMAP SEARCH
  290. command. Applied on both sides.
  291. For a complete of what can be search see
  292. https://imapsync.lamiral.info/FAQ.d/FAQ.Messages_Selection.txt
  293. --search1 str : Same as --search but for selecting host1 messages only.
  294. --search2 str : Same as --search but for selecting host2 messages only.
  295. --search CRIT equals --search1 CRIT --search2 CRIT
  296. --maxlinelength int : skip messages with a line length longer than int bytes.
  297. RFC 2822 says it must be no more than 1000 bytes.
  298. --useheader str : Use this header to compare messages on both sides.
  299. Ex: Message-ID or Subject or Date.
  300. --useheader str and this one, etc.
  301. --usecache : Use cache to speed up the sync.
  302. --nousecache : Do not use cache. Caveat: --useuid --nousecache creates
  303. duplicates on multiple runs.
  304. --useuid : Use UIDs instead of headers as a criterium to recognize
  305. messages. Option --usecache is then implied unless
  306. --nousecache is used.
  307. =head2 OPTIONS/miscellaneous
  308. --syncacls : Synchronizes acls (Access Control Lists).
  309. --nosyncacls : Does not synchronize acls. This is the default.
  310. Acls in IMAP are not standardized, be careful.
  311. --addheader : When a message has no headers to be identified,
  312. --addheader adds a "Message-Id" header,
  313. like "Message-Id: 12345@imapsync", where 12345
  314. is the imap UID of the message on the host1 folder.
  315. =head2 OPTIONS/debugging
  316. --debug : Debug mode.
  317. --debugfolders : Debug mode for the folders part only.
  318. --debugcontent : Debug content of the messages transferred. Huge output.
  319. --debugflags : Debug mode for flags.
  320. --debugimap1 : IMAP debug mode for host1. Very verbose.
  321. --debugimap2 : IMAP debug mode for host2. Very verbose.
  322. --debugimap : IMAP debug mode for host1 and host2. Twice very verbose.
  323. --debugmemory : Debug mode showing memory consumption after each copy.
  324. --errorsmax int : Exit when int number of errors is reached. Default is 50.
  325. --tests : Run local non-regression tests. Exit code 0 means all ok.
  326. --testslive : Run a live test with test1.lamiral.info imap server.
  327. Useful to check the basics. Needs internet connection.
  328. --testslive6 : Run a live test with ks2ipv6.lamiral.info imap server.
  329. Useful to check the ipv6 connectivity. Needs internet.
  330. =head2 OPTIONS/specific
  331. --gmail1 : sets --host1 to Gmail and options from FAQ.Gmail.txt
  332. --gmail2 : sets --host2 to Gmail and options from FAQ.Gmail.txt
  333. --office1 : sets --host1 to Office365 options from FAQ.Exchange.txt
  334. --office2 : sets --host2 to Office365 options from FAQ.Exchange.txt
  335. --exchange1 : sets options from FAQ.Exchange.txt, account1 part
  336. --exchange2 : sets options from FAQ.Exchange.txt, account2 part
  337. --domino1 : sets options from FAQ.Domino.txt, account1 part
  338. --domino2 : sets options from FAQ.Domino.txt, account2 part
  339. =head2 OPTIONS/behavior
  340. --maxmessagespersecond int : limits the number of messages transferred per second.
  341. --maxbytespersecond int : limits the average transfer rate per second.
  342. --maxbytesafter int : starts --maxbytespersecond limitation only after
  343. --maxbytesafter amount of data transferred.
  344. --maxsleep int : do not sleep more than int seconds.
  345. On by default, 2 seconds max, like --maxsleep 2
  346. --abort : terminates a previous call still running.
  347. It uses the pidfile to know what process to abort.
  348. --exitwhenover int : Stop syncing when total bytes transferred reached.
  349. --version : Print only software version.
  350. --noreleasecheck : Do not check for new imapsync release (a http request).
  351. --releasecheck : Check for new imapsync release (a http request).
  352. --noid : Do not send/receive ID command to imap servers.
  353. --justconnect : Just connect to both servers and print useful
  354. information. Need only --host1 and --host2 options.
  355. --justlogin : Just login to both host1 and host2 with users
  356. credentials, then exit.
  357. --justfolders : Do only things about folders (ignore messages).
  358. --help : print this help.
  359. Example: to synchronize imap account "test1" on "test1.lamiral.info"
  360. to imap account "test2" on "test2.lamiral.info"
  361. with test1 password "secret1"
  362. and test2 password "secret2"
  363. imapsync \
  364. --host1 test1.lamiral.info --user1 test1 --password1 secret1 \
  365. --host2 test2.lamiral.info --user2 test2 --password2 secret2
  366. =cut
  367. # comment
  368. =pod
  369. =head1 SECURITY
  370. You can use --passfile1 instead of --password1 to give the
  371. password since it is safer. With --password1 option, any user
  372. on your host can see the password by using the 'ps auxwwww'
  373. command. Using a variable (like $PASSWORD1) is also
  374. dangerous because of the 'ps auxwwwwe' command. So, saving
  375. the password in a well protected file (600 or rw-------) is
  376. the best solution.
  377. Imapsync activates ssl or tls encryption by default, if possible.
  378. What detailed behavior is under this "if possible"?
  379. Imapsync activates ssl if the well known port imaps port (993) is open
  380. on the imap servers. If the imaps port is closed then it open a
  381. normal (clear) connection on port 143 but it looks for TLS support
  382. in the CAPABILITY list of the servers. If TLS is supported
  383. then imapsync goes to encryption.
  384. If the automatic ssl/tls detection fails then imapsync will
  385. not protect against sniffing activities on the
  386. network, especially for passwords.
  387. If you want to force ssl or tls just use --ssl1 --ssl2 or --tls1 --tls2
  388. See also the document FAQ.Security.txt in the FAQ.d/ directory
  389. or at https://imapsync.lamiral.info/FAQ.d/FAQ.Security.txt
  390. =head1 EXIT STATUS
  391. Imapsync will exit with a 0 status (return code) if everything went good.
  392. Otherwise, it exits with a non-zero status.
  393. Here is the list of the exit code values (an integer between 0 and 255),
  394. the names reflects their meaning:
  395. =for comment
  396. egrep '^Readonly my.*\$EX' imapsync | egrep -o 'EX.*' | sed 's_^_ _'
  397. EX_OK => 0 ; #/* successful termination */
  398. EX_USAGE => 64 ; #/* command line usage error */
  399. EX_NOINPUT => 66 ; #/* cannot open input */
  400. EX_UNAVAILABLE => 69 ; #/* service unavailable */
  401. EX_SOFTWARE => 70 ; #/* internal software error */
  402. EXIT_CATCH_ALL => 1 ; # Any other error
  403. EXIT_BY_SIGNAL => 6 ; # Should be 128+n where n is the sig_num
  404. EXIT_PID_FILE_ERROR => 8 ;
  405. EXIT_CONNECTION_FAILURE => 10 ;
  406. EXIT_TLS_FAILURE => 12 ;
  407. EXIT_AUTHENTICATION_FAILURE => 16 ;
  408. EXIT_SUBFOLDER1_NO_EXISTS => 21 ;
  409. EXIT_WITH_ERRORS => 111 ;
  410. EXIT_WITH_ERRORS_MAX => 112 ;
  411. EXIT_TESTS_FAILED => 254 ; # Like Test::More API
  412. =head1 LICENSE AND COPYRIGHT
  413. Imapsync is free, open, public but not always gratis software
  414. cover by the NOLIMIT Public License.
  415. See the LICENSE file included in the distribution or just read this
  416. simple sentence as it IS the licence text:
  417. "No limits to do anything with this work and this license."
  418. In case it is not long enough, I repeat:
  419. "No limits to do anything with this work and this license."
  420. https://imapsync.lamiral.info/LICENSE
  421. =head1 AUTHOR
  422. Gilles LAMIRAL <gilles@lamiral.info>
  423. Feedback good or bad is very often welcome.
  424. Gilles LAMIRAL earns his living by writing, installing,
  425. configuring and teaching free, open and often gratis
  426. software. Imapsync used to be "always gratis" but now it is
  427. only "often gratis" because imapsync is sold by its author,
  428. a good way to maintain and support free open public
  429. software over decades.
  430. =head1 BUGS AND LIMITATIONS
  431. See https://imapsync.lamiral.info/FAQ.d/FAQ.Reporting_Bugs.txt
  432. =head1 IMAP SERVERS supported
  433. See https://imapsync.lamiral.info/S/imapservers.shtml
  434. =head1 HUGE MIGRATION
  435. Pay special attention to options
  436. --subscribed
  437. --subscribe
  438. --delete1
  439. --delete1emptyfolders
  440. --delete2
  441. --delete2folders
  442. --maxage
  443. --minage
  444. --maxsize
  445. --useuid
  446. --usecache
  447. If you have many mailboxes to migrate think about a little
  448. shell program. Write a file called file.txt (for example)
  449. containing users and passwords.
  450. The separator used in this example is ';'
  451. The file.txt file contains:
  452. user001_1;password001_1;user001_2;password001_2
  453. user002_1;password002_1;user002_2;password002_2
  454. user003_1;password003_1;user003_2;password003_2
  455. user004_1;password004_1;user004_2;password004_2
  456. user005_1;password005_1;user005_2;password005_2
  457. ...
  458. On Unix the shell program can be:
  459. { while IFS=';' read u1 p1 u2 p2; do
  460. imapsync --host1 imap.side1.org --user1 "$u1" --password1 "$p1" \
  461. --host2 imap.side2.org --user2 "$u2" --password2 "$p2" ...
  462. done ; } < file.txt
  463. On Windows the batch program can be:
  464. FOR /F "tokens=1,2,3,4 delims=; eol=#" %%G IN (file.txt) DO imapsync ^
  465. --host1 imap.side1.org --user1 %%G --password1 %%H ^
  466. --host2 imap.side2.org --user2 %%I --password2 %%J ...
  467. The ... have to be replaced by nothing or any imapsync option.
  468. Welcome in shell or batch programming !
  469. You will find already written scripts at
  470. https://imapsync.lamiral.info/examples/
  471. =head1 INSTALL
  472. Imapsync works under any Unix with perl.
  473. Imapsync works under most Windows (2000, XP, Vista, Seven, Eight, Ten
  474. and all Server releases 2000, 2003, 2008 and R2, 2012 and R2)
  475. as a standalone binary software called imapsync.exe,
  476. usually launched from a batch file in order to avoid always typing
  477. the options.
  478. Imapsync works under OS X as a standalone binary
  479. software called imapsync_bin_Darwin
  480. Purchase latest imapsync at
  481. https://imapsync.lamiral.info/
  482. You'll receive a link to a compressed tarball called imapsync-x.xx.tgz
  483. where x.xx is the version number. Untar the tarball where
  484. you want (on Unix):
  485. tar xzvf imapsync-x.xx.tgz
  486. Go into the directory imapsync-x.xx and read the INSTALL file.
  487. As mentioned at https://imapsync.lamiral.info/#install
  488. the INSTALL file can also be found at
  489. https://imapsync.lamiral.info/INSTALL.d/INSTALL.ANY.txt
  490. It is now split in several files for each system
  491. https://imapsync.lamiral.info/INSTALL.d/
  492. =head1 CONFIGURATION
  493. There is no specific configuration file for imapsync,
  494. everything is specified by the command line parameters
  495. and the default behavior.
  496. =head1 HACKING
  497. Feel free to hack imapsync as the NOLIMIT license permits it.
  498. =head1 SIMILAR SOFTWARE
  499. See also https://imapsync.lamiral.info/S/external.shtml
  500. for a better up to date list.
  501. Last updated and verified on Thu Apr 11, 2019.
  502. imapsync : https://github.com/imapsync/imapsync
  503. (this is an imapsync copy, sometimes delayed,
  504. with --noreleasecheck by default since release 1.592, 2014/05/22)
  505. imap_tools : https://web.archive.org/web/20161228145952/http://www.athensfbc.com/imap_tools/
  506. The imap_tools code is now at
  507. https://github.com/andrewnimmo/rick-sanders-imap-tools
  508. imaputils : https://github.com/mtsatsenko/imaputils (very old imap_tools fork)
  509. Doveadm-Sync : https://wiki2.dovecot.org/Tools/Doveadm/Sync ( Dovecot sync tool )
  510. davmail : http://davmail.sourceforge.net/
  511. offlineimap : http://offlineimap.org/
  512. mbsync : http://isync.sourceforge.net/
  513. mailsync : http://mailsync.sourceforge.net/
  514. mailutil : http://www.washington.edu/imap/ part of the UW IMAP tookit.
  515. imaprepl : https://bl0rg.net/software/ http://freecode.com/projects/imap-repl/
  516. imapcopy (Pascal): http://www.ardiehl.de/imapcopy/
  517. imapcopy (Java) : https://code.google.com/archive/p/imapcopy/
  518. imapsize : http://www.broobles.com/imapsize/
  519. migrationtool : http://sourceforge.net/projects/migrationtool/
  520. imapmigrate : http://sourceforge.net/projects/cyrus-utils/
  521. larch : https://github.com/rgrove/larch (derived from wonko_imapsync, good at Gmail)
  522. wonko_imapsync : http://wonko.com/article/554 (superseded by larch)
  523. pop2imap : http://www.linux-france.org/prj/pop2imap/ (I wrote that too)
  524. exchange-away : http://exchange-away.sourceforge.net/
  525. SyncBackPro : http://www.2brightsparks.com/syncback/sbpro.html
  526. ImapSyncClient : https://github.com/ridaamirini/ImapSyncClient
  527. MailStore : https://www.mailstore.com/en/products/mailstore-home/
  528. mnIMAPSync : https://github.com/manusa/mnIMAPSync
  529. imap-upload : http://imap-upload.sourceforge.net/
  530. (a tool for uploading a local mbox file to IMAP4 server)
  531. =head1 HISTORY
  532. I wrote imapsync because an enterprise (basystemes) paid me to install
  533. a new imap server without losing huge old mailboxes located in a far
  534. away remote imap server, accessible by an often broken low-bandwidth ISDN link.
  535. I had to verify every mailbox was well transferred, all folders, all messages,
  536. without wasting bandwidth or creating duplicates upon resyncs. The design was
  537. made with the rsync command in mind.
  538. Imapsync started its life as a patch of the copy_folder.pl
  539. script. The script copy_folder.pl comes from the Mail-IMAPClient-2.1.3 perl
  540. module tarball source (more precisely in the examples/ directory of the
  541. Mail-IMAPClient tarball).
  542. So many happened since then that I wonder
  543. if it remains any lines of the original copy_folder.pl in imapsync source code.
  544. =cut
  545. # use pragmas
  546. use strict ;
  547. use warnings ;
  548. use Carp ;
  549. use Data::Dumper ;
  550. use Digest::HMAC_SHA1 qw( hmac_sha1 hmac_sha1_hex ) ;
  551. use Digest::MD5 qw( md5 md5_hex md5_base64 ) ;
  552. use English qw( -no_match_vars ) ;
  553. use Errno qw(EAGAIN EPIPE ECONNRESET) ;
  554. use Fcntl ;
  555. use File::Basename ;
  556. use File::Copy::Recursive ;
  557. use File::Glob qw( :glob ) ;
  558. use File::Path qw( mkpath rmtree ) ;
  559. use File::Spec ;
  560. use File::stat ;
  561. use Getopt::Long ( ) ;
  562. use IO::File ;
  563. use IO::Socket qw( :crlf SOL_SOCKET SO_KEEPALIVE ) ;
  564. use IO::Socket::INET6 ;
  565. use IO::Socket::SSL ;
  566. use IO::Tee ;
  567. use IPC::Open3 'open3' ;
  568. use Mail::IMAPClient 3.30 ;
  569. use MIME::Base64 ;
  570. use Pod::Usage qw(pod2usage) ;
  571. use POSIX qw(uname SIGALRM) ;
  572. use Sys::Hostname ;
  573. use Term::ReadKey ;
  574. use Test::More ;
  575. use Time::HiRes qw( time sleep ) ;
  576. use Time::Local ;
  577. use Unicode::String ;
  578. use Cwd ;
  579. use Readonly ;
  580. use Sys::MemInfo ;
  581. use Regexp::Common ;
  582. use Text::ParseWords;
  583. use File::Tail ;
  584. local $OUTPUT_AUTOFLUSH = 1 ;
  585. # constants
  586. # Let us do like sysexits.h
  587. # /usr/include/sysexits.h
  588. # and https://www.tldp.org/LDP/abs/html/exitcodes.html
  589. # Should avoid 2 126 127 128..128+64=192 255
  590. # Should use 0 1 3..125 193..254
  591. Readonly my $EX_OK => 0 ; #/* successful termination */
  592. Readonly my $EX_USAGE => 64 ; #/* command line usage error */
  593. #Readonly my $EX_DATAERR => 65 ; #/* data format error */
  594. Readonly my $EX_NOINPUT => 66 ; #/* cannot open input */
  595. #Readonly my $EX_NOUSER => 67 ; #/* addressee unknown */
  596. #Readonly my $EX_NOHOST => 68 ; #/* host name unknown */
  597. Readonly my $EX_UNAVAILABLE => 69 ; #/* service unavailable */
  598. Readonly my $EX_SOFTWARE => 70 ; #/* internal software error */
  599. #Readonly my $EX_OSERR => 71 ; #/* system error (e.g., can't fork) */
  600. #Readonly my $EX_OSFILE => 72 ; #/* critical OS file missing */
  601. #Readonly my $EX_CANTCREAT => 73 ; #/* can't create (user) output file */
  602. #Readonly my $EX_IOERR => 74 ; #/* input/output error */
  603. #Readonly my $EX_TEMPFAIL => 75 ; #/* temp failure; user is invited to retry */
  604. #Readonly my $EX_PROTOCOL => 76 ; #/* remote error in protocol */
  605. #Readonly my $EX_NOPERM => 77 ; #/* permission denied */
  606. #Readonly my $EX_CONFIG => 78 ; #/* configuration error */
  607. # Mine
  608. Readonly my $EXIT_CATCH_ALL => 1 ; # Any other error
  609. Readonly my $EXIT_BY_SIGNAL => 6 ; # Should be 128+n where n is the sig_num
  610. Readonly my $EXIT_PID_FILE_ERROR => 8 ;
  611. Readonly my $EXIT_CONNECTION_FAILURE => 10 ;
  612. Readonly my $EXIT_TLS_FAILURE => 12 ;
  613. Readonly my $EXIT_AUTHENTICATION_FAILURE => 16 ;
  614. Readonly my $EXIT_SUBFOLDER1_NO_EXISTS => 21 ;
  615. Readonly my $EXIT_WITH_ERRORS => 111 ;
  616. Readonly my $EXIT_WITH_ERRORS_MAX => 112 ;
  617. Readonly my $EXIT_TESTS_FAILED => 254 ; # Like Test::More API
  618. Readonly my $DEFAULT_LOGDIR => 'LOG_imapsync' ;
  619. Readonly my $ERRORS_MAX => 50 ; # exit after 50 errors.
  620. Readonly my $ERRORS_MAX_CGI => 20 ; # exit after 20 errors in CGI context.
  621. Readonly my $INTERVAL_TO_EXIT => 2 ; # interval max to exit instead of reconnect
  622. Readonly my $SPLIT => 100 ; # By default, 100 at a time, not more.
  623. Readonly my $SPLIT_FACTOR => 10 ; # init_imap() calls Maxcommandlength( $SPLIT_FACTOR * $split )
  624. # which means default Maxcommandlength is 10*100 = 1000 characters ;
  625. Readonly my $IMAP_PORT => 143 ; # Well know port for IMAP
  626. Readonly my $IMAP_SSL_PORT => 993 ; # Well know port for IMAP over SSL
  627. Readonly my $LAST => -1 ;
  628. Readonly my $MINUS_ONE => -1 ;
  629. Readonly my $MINUS_TWO => -2 ;
  630. Readonly my $RELEASE_NUMBER_EXAMPLE_1 => '1.351' ;
  631. Readonly my $RELEASE_NUMBER_EXAMPLE_2 => 42.4242 ;
  632. Readonly my $TCP_PING_TIMEOUT => 5 ;
  633. Readonly my $DEFAULT_TIMEOUT => 120 ;
  634. Readonly my $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND => 3 ;
  635. Readonly my $DEFAULT_UIDNEXT => 999_999 ;
  636. Readonly my $DEFAULT_BUFFER_SIZE => 4096 ;
  637. Readonly my $MAX_SLEEP => 2 ; # 2 seconds max for limiting too long sleeps from --maxbytespersecond and --maxmessagespersecond
  638. Readonly my $DEFAULT_EXPIRATION_TIME_OAUTH2_PK12 => 3600 ;
  639. Readonly my $PERMISSION_FILTER => 7777 ;
  640. Readonly my $KIBI => 1024 ;
  641. Readonly my $NUMBER_10 => 10 ;
  642. Readonly my $NUMBER_42 => 42 ;
  643. Readonly my $NUMBER_100 => 100 ;
  644. Readonly my $NUMBER_200 => 200 ;
  645. Readonly my $NUMBER_300 => 300 ;
  646. Readonly my $NUMBER_123456 => 123456 ;
  647. Readonly my $NUMBER_654321 => 654321 ;
  648. Readonly my $NUMBER_20_000 => 20_000 ;
  649. Readonly my $QUOTA_PERCENT_LIMIT => 90 ;
  650. Readonly my $NUMBER_104_857_600 => 104_857_600 ;
  651. Readonly my $SIZE_MAX_STR => 64 ;
  652. Readonly my $NB_SECONDS_IN_A_DAY => 86_400 ;
  653. Readonly my $STD_CHAR_PER_LINE => 80 ;
  654. Readonly my $TRUE => 1 ;
  655. Readonly my $FALSE => 0 ;
  656. Readonly my $LAST_RESSORT_SEPARATOR => q{/} ;
  657. Readonly my $CGI_TMPDIR_TOP => '/var/tmp/imapsync_cgi' ;
  658. Readonly my $CGI_HASHFILE => '/var/tmp/imapsync_hash' ;
  659. Readonly my $UMASK_PARANO => '0077' ;
  660. Readonly my $STR_use_releasecheck => q{Check if a new imapsync release is available by adding --releasecheck} ;
  661. Readonly my $GMAIL_MAXSIZE => 35_651_584 ;
  662. # if ( 'MSWin32' eq $OSNAME )
  663. # if ( 'darwin' eq $OSNAME )
  664. # if ( 'linux' eq $OSNAME )
  665. # global variables
  666. # Currently working to finish with only $sync
  667. # Not finished yet...
  668. my(
  669. $sync,
  670. $debugimap, $debugimap1, $debugimap2, $debugcontent, $debugflags,
  671. $debuglist, $debugdev, $debugmaxlinelength, $debugcgi,
  672. $domain1, $domain2,
  673. @include, @exclude, @folderrec,
  674. @folderfirst, @folderlast,
  675. $prefix1, $prefix2,
  676. @regexmess, @regexflag, @skipmess, @pipemess, $pipemesscheck,
  677. $flagscase, $filterflags, $syncflagsaftercopy,
  678. $syncinternaldates,
  679. $idatefromheader,
  680. $syncacls,
  681. $fastio1, $fastio2,
  682. $minsize, $maxage, $minage,
  683. $search, $search1, $search2,
  684. $skipheader, @useheader,
  685. $skipsize, $allowsizemismatch, $foldersizes, $foldersizesatend, $buffersize,
  686. $authmd5, $authmd51, $authmd52,
  687. $subscribed, $subscribe, $subscribeall,
  688. $help,
  689. $justbanner,
  690. $fast,
  691. $nb_msg_skipped_dry_mode,
  692. $h1_nb_msg_duplicate,
  693. $h2_nb_msg_duplicate,
  694. $h2_nb_msg_noheader,
  695. $h2_nb_msg_deleted,
  696. $h1_bytes_processed,
  697. $h1_nb_msg_start, $h1_bytes_start,
  698. $h2_nb_msg_start, $h2_bytes_start,
  699. $h1_nb_msg_end, $h1_bytes_end,
  700. $h2_nb_msg_end, $h2_bytes_end,
  701. $timeout,
  702. $timestart_int,
  703. $timebefore,
  704. $uid1, $uid2,
  705. $authuser1, $authuser2,
  706. $proxyauth1, $proxyauth2,
  707. $authmech1, $authmech2,
  708. $split1, $split2,
  709. $reconnectretry1, $reconnectretry2,
  710. $max_msg_size_in_bytes,
  711. $modulesversion,
  712. $delete2folders, $delete2foldersonly, $delete2foldersbutnot,
  713. $usecache, $debugcache, $cacheaftercopy,
  714. $wholeheaderifneeded, %h1_msgs_copy_by_uid, $useuid, $h2_uidguess,
  715. $checkmessageexists,
  716. $messageidnodomain,
  717. $fixInboxINBOX,
  718. $maxlinelength, $maxlinelengthcmd,
  719. $minmaxlinelength,
  720. $uidnext_default,
  721. $fixcolonbug,
  722. $create_folder_old,
  723. $skipcrossduplicates, $debugcrossduplicates,
  724. $disarmreadreceipts,
  725. $mixfolders, $skipemptyfolders,
  726. $fetch_hash_set,
  727. ) ;
  728. # main program
  729. # global variables initialization
  730. # I'm currently removing all global variables except $sync
  731. # passing each of them under $sync->{variable_name}
  732. $sync->{timestart} = time ; # Is a float because of use Time::HiRres
  733. $sync->{rcs} = q{$Id: imapsync,v 1.937 2019/05/01 22:14:00 gilles Exp gilles $} ;
  734. $sync->{ memory_consumption_at_start } = memory_consumption( ) || 0 ;
  735. my @loadavg = loadavg( ) ;
  736. $sync->{cpu_number} = cpu_number( ) ;
  737. $sync->{loaddelay} = load_and_delay( $sync->{cpu_number}, @loadavg ) ;
  738. $sync->{loadavg} = join( q{ }, $loadavg[ 0 ] )
  739. . " on $sync->{cpu_number} cores and "
  740. . ram_memory_info( ) ;
  741. $sync->{ total_bytes_transferred } = 0 ;
  742. $sync->{ total_bytes_skipped } = 0;
  743. $sync->{ nb_msg_transferred } = 0;
  744. $sync->{ nb_msg_skipped } = $nb_msg_skipped_dry_mode = 0;
  745. $sync->{ h1_nb_msg_deleted } = $h2_nb_msg_deleted = 0;
  746. $h1_nb_msg_duplicate = $h2_nb_msg_duplicate = 0;
  747. $sync->{ h1_nb_msg_noheader } = 0 ;
  748. $h2_nb_msg_noheader = 0 ;
  749. $h1_nb_msg_start = $h1_bytes_start = 0 ;
  750. $h2_nb_msg_start = $h2_bytes_start = 0 ;
  751. $sync->{ h1_nb_msg_processed } = $h1_bytes_processed = 0 ;
  752. $sync->{ h2_nb_msg_crossdup } = 0 ;
  753. #$h1_nb_msg_end = $h1_bytes_end = 0 ;
  754. #$h2_nb_msg_end = $h2_bytes_end = 0 ;
  755. $sync->{nb_errors} = 0;
  756. $max_msg_size_in_bytes = 0;
  757. my %month_abrev = (
  758. Jan => '00',
  759. Feb => '01',
  760. Mar => '02',
  761. Apr => '03',
  762. May => '04',
  763. Jun => '05',
  764. Jul => '06',
  765. Aug => '07',
  766. Sep => '08',
  767. Oct => '09',
  768. Nov => '10',
  769. Dec => '11',
  770. );
  771. my $cgidir ;
  772. # Just create a CGI object if under cgi context only.
  773. # Needed for the get_options() call
  774. cgibegin( $sync ) ;
  775. # In cgi context, printing must start by the header so we delay other prints by using output() storage
  776. my $options_good = get_options( $sync, @ARGV ) ;
  777. # Is it The first myprint?
  778. docker_context( $sync ) ;
  779. cgibuildheader( $sync ) ;
  780. myprint( output( $sync ) ) ;
  781. output_reset_with( $sync ) ;
  782. # Old place for cgiload( $sync ) ;
  783. # don't go on if options are not all known.
  784. if ( ! defined $options_good ) { exit $EX_USAGE ; }
  785. # If you want releasecheck not to be done by default (like the github maintainer),
  786. # then just uncomment the first "$sync->{releasecheck} =" line, the line ending with "0 ;",
  787. # the second line (ending with "1 ;") can then stay active or be commented,
  788. # the result will be the same: no releasecheck by default (because 0 is then the defined value).
  789. #$sync->{releasecheck} = defined $sync->{releasecheck} ? $sync->{releasecheck} : 0 ;
  790. $sync->{releasecheck} = defined $sync->{releasecheck} ? $sync->{releasecheck} : 1 ;
  791. # just the version
  792. if ( $sync->{ version } ) {
  793. myprint( imapsync_version( $sync ), "\n" ) ;
  794. exit 0 ;
  795. }
  796. $sync->{debugenv} and printenv( $sync ) ; # if option --debugenv
  797. load_modules( ) ;
  798. # after_get_options call usage and exit if --help or options were not well got
  799. after_get_options( $sync, $options_good ) ;
  800. # Under CGI environment, fix caveat emptor potential issues
  801. cgisetcontext( $sync ) ;
  802. # --gmail --gmail --exchange --office etc.
  803. easyany( $sync ) ;
  804. $sync->{ sanitize } = defined $sync->{ sanitize } ? $sync->{ sanitize } : 1 ;
  805. sanitize( $sync ) ;
  806. $sync->{ tmpdir } ||= File::Spec->tmpdir( ) ;
  807. # Unit tests
  808. testsexit( $sync ) ;
  809. # init live varaiables
  810. testslive( $sync ) if ( $sync->{testslive} ) ;
  811. testslive6( $sync ) if ( $sync->{testslive6} ) ;
  812. #
  813. $sync->{pidfile} = defined $sync->{pidfile} ? $sync->{pidfile} : $sync->{ tmpdir } . '/imapsync.pid' ;
  814. $sync->{pidfilelocking} = defined $sync->{pidfilelocking} ? $sync->{pidfilelocking} : 0 ;
  815. # old abort place
  816. # Unix signals
  817. @{ $sync->{ sigexit } } = ( defined( $sync->{ sigexit } ) ) ? @{ $sync->{ sigexit } } : ( 'QUIT', 'TERM' ) ;
  818. @{ $sync->{ sigreconnect } } = ( defined( $sync->{ sigreconnect } ) ) ? @{ $sync->{ sigreconnect } } : ( 'INT' ) ;
  819. @{ $sync->{ sigprint } } = ( defined( $sync->{ sigprint } ) ) ? @{ $sync->{ sigprint } } : ( 'HUP' ) ;
  820. @{ $sync->{ sigignore } } = ( defined( $sync->{ sigignore } ) ) ? @{ $sync->{ sigignore } } : ( ) ;
  821. local %SIG = %SIG ;
  822. sig_install( $sync, \&catch_exit, @{ $sync->{ sigexit } } ) ;
  823. sig_install( $sync, \&catch_reconnect, @{ $sync->{ sigreconnect } } ) ;
  824. sig_install( $sync, \&catch_print, @{ $sync->{ sigprint } } ) ;
  825. # --sigignore can override sigexit, sigreconnect and sigprint (for the same signals only)
  826. sig_install( $sync, \&catch_ignore, @{ $sync->{ sigignore } } ) ;
  827. sig_install_toggle_sleep( $sync ) ;
  828. $sync->{log} = defined $sync->{log} ? $sync->{log} : 1 ;
  829. $sync->{errorsdump} = defined $sync->{errorsdump} ? $sync->{errorsdump} : 1 ;
  830. $sync->{errorsmax} = defined $sync->{errorsmax} ? $sync->{errorsmax} : $ERRORS_MAX ;
  831. # log and output
  832. if ( $sync->{log} ) {
  833. setlogfile( $sync ) ;
  834. teelaunch( $sync ) ;
  835. # now $sync->{tee} is a filehandle to STDOUT and the logfile
  836. }
  837. # STDERR goes to the same place: LOG and STDOUT (if logging is on)
  838. $sync->{tee} and local *STDERR = *${$sync->{tee}}{IO} ;
  839. $timestart_int = int( $sync->{timestart} ) ;
  840. $timebefore = $sync->{timestart} ;
  841. my $timestart_str = localtime( $sync->{timestart} ) ;
  842. # The prints in the log starts here
  843. myprint( localhost_info( $sync ), "\n" ) ;
  844. myprint( "Transfer started at $timestart_str\n" ) ;
  845. myprint( "PID is $PROCESS_ID my PPID is ", mygetppid( ), "\n" ) ;
  846. myprint( "Log file is $sync->{logfile} ( to change it, use --logfile path ; or use --nolog to turn off logging )\n" ) if ( $sync->{log} ) ;
  847. myprint( "Load is " . ( join( q{ }, loadavg( ) ) || 'unknown' ), " on $sync->{cpu_number} cores\n" ) ;
  848. #myprintf( "Memory consumption so far: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI ) ;
  849. myprint( 'Current directory is ' . getcwd( ) . "\n" ) ;
  850. myprint( 'Real user id is ' . getpwuid_any_os( $REAL_USER_ID ) . " (uid $REAL_USER_ID)\n" ) ;
  851. myprint( 'Effective user id is ' . getpwuid_any_os( $EFFECTIVE_USER_ID ). " (euid $EFFECTIVE_USER_ID)\n" ) ;
  852. $modulesversion = defined $modulesversion ? $modulesversion : 1 ;
  853. my $warn_release = ( $sync->{releasecheck} ) ? check_last_release( ) : $STR_use_releasecheck ;
  854. $wholeheaderifneeded = defined $wholeheaderifneeded ? $wholeheaderifneeded : 1;
  855. # Activate --usecache if --useuid is set and no --nousecache
  856. $usecache = 1 if ( $useuid and ( ! defined $usecache ) ) ;
  857. $cacheaftercopy = 1 if ( $usecache and ( ! defined $cacheaftercopy ) ) ;
  858. $sync->{ checkselectable } = defined $sync->{ checkselectable } ? $sync->{ checkselectable } : 1 ;
  859. $sync->{ checkfoldersexist } = defined $sync->{ checkfoldersexist } ? $sync->{ checkfoldersexist } : 1 ;
  860. $checkmessageexists = defined $checkmessageexists ? $checkmessageexists : 0 ;
  861. $sync->{ expungeaftereach } = defined $sync->{ expungeaftereach } ? $sync->{ expungeaftereach } : 1 ;
  862. # abletosearch is on by default
  863. $sync->{abletosearch} = defined $sync->{abletosearch} ? $sync->{abletosearch} : 1 ;
  864. $sync->{abletosearch1} = defined $sync->{abletosearch1} ? $sync->{abletosearch1} : $sync->{abletosearch} ;
  865. $sync->{abletosearch2} = defined $sync->{abletosearch2} ? $sync->{abletosearch2} : $sync->{abletosearch} ;
  866. $checkmessageexists = 0 if ( not $sync->{abletosearch1} ) ;
  867. $sync->{showpasswords} = defined $sync->{showpasswords} ? $sync->{showpasswords} : 0 ;
  868. $sync->{ fixslash2 } = defined $sync->{ fixslash2 } ? $sync->{ fixslash2 } : 1 ;
  869. $fixInboxINBOX = defined $fixInboxINBOX ? $fixInboxINBOX : 1 ;
  870. $create_folder_old = defined $create_folder_old ? $create_folder_old : 0 ;
  871. $mixfolders = defined $mixfolders ? $mixfolders : 1 ;
  872. $sync->{automap} = defined $sync->{automap} ? $sync->{automap} : 0 ;
  873. $sync->{ delete2duplicates } = 1 if ( $sync->{ delete2 } and ( ! defined $sync->{ delete2duplicates } ) ) ;
  874. $sync->{maxmessagespersecond} = defined $sync->{maxmessagespersecond} ? $sync->{maxmessagespersecond} : 0 ;
  875. $sync->{maxbytespersecond} = defined $sync->{maxbytespersecond} ? $sync->{maxbytespersecond} : 0 ;
  876. $sync->{sslcheck} = defined $sync->{sslcheck} ? $sync->{sslcheck} : 1 ;
  877. myprint( banner_imapsync( $sync, @ARGV ) ) ;
  878. myprint( "Temp directory is $sync->{ tmpdir } ( to change it use --tmpdir dirpath )\n" ) ;
  879. myprint( output( $sync ) ) ;
  880. output_reset_with( $sync ) ;
  881. do_valid_directory( $sync->{ tmpdir } ) || croak "Error creating tmpdir $sync->{ tmpdir } : $OS_ERROR" ;
  882. remove_pidfile_not_running( $sync->{pidfile} ) ;
  883. # if another imapsync is running then tail -f its logfile and exit
  884. # useful in cgi context
  885. if ( $sync->{tail} and tail( $sync ) )
  886. {
  887. myprint( "Tail -f finished. Now finishing myself\n" ) ;
  888. exit_clean( $sync, $EX_OK ) ;
  889. exit $EX_OK ;
  890. }
  891. if ( ! write_pidfile( $sync ) ) {
  892. myprint( "Exiting with return value $EXIT_PID_FILE_ERROR\n" ) ;
  893. exit $EXIT_PID_FILE_ERROR ;
  894. }
  895. # New place for abort
  896. # abort before simulong in order to be able to abort a simulong sync
  897. if ( $sync->{ abort } )
  898. {
  899. abort( $sync ) ;
  900. }
  901. # simulong is just a loop printing some lines for xx seconds with option "--simulong xx".
  902. if ( $sync->{ simulong } )
  903. {
  904. simulong( $sync->{ simulong } ) ;
  905. }
  906. # New place for cgiload 2019_03_03
  907. # because I want to log it
  908. # Can break here if load is too heavy
  909. cgiload( $sync ) ;
  910. $fixcolonbug = defined $fixcolonbug ? $fixcolonbug : 1 ;
  911. if ( $usecache and $fixcolonbug ) { tmpdir_fix_colon_bug( $sync ) } ;
  912. $modulesversion and myprint( "Modules version list:\n", modulesversion(), "( use --no-modulesversion to turn off printing this Perl modules list )\n" ) ;
  913. check_lib_version( $sync ) or
  914. croak "imapsync needs perl lib Mail::IMAPClient release 3.30 or superior.\n";
  915. exit_clean( $sync, $EX_OK ) if ( $justbanner ) ;
  916. # turn on RFC standard flags correction like \SEEN -> \Seen
  917. $flagscase = defined $flagscase ? $flagscase : 1 ;
  918. # Use PERMANENTFLAGS if available
  919. $filterflags = defined $filterflags ? $filterflags : 1 ;
  920. # sync flags just after an APPEND, some servers ignore the flags given in the APPEND
  921. # like MailEnable IMAP server.
  922. # Off by default since it takes time.
  923. $syncflagsaftercopy = defined $syncflagsaftercopy ? $syncflagsaftercopy : 0 ;
  924. # update flags on host2 for already transferred messages
  925. $sync->{resyncflags} = defined $sync->{resyncflags} ? $sync->{resyncflags} : 1 ;
  926. if ( $sync->{resyncflags} ) {
  927. myprint( "Info: will resync flags for already transferred messages. Use --noresyncflags to not resync flags.\n" ) ;
  928. }else{
  929. myprint( "Info: will not resync flags for already transferred messages. Use --resyncflags to resync flags.\n" ) ;
  930. }
  931. sslcheck( $sync ) ;
  932. #print Data::Dumper->Dump( [ \$sync ] ) ;
  933. $split1 ||= $SPLIT ;
  934. $split2 ||= $SPLIT ;
  935. $sync->{host1} || missing_option( $sync, '--host1' ) ;
  936. $sync->{port1} ||= ( $sync->{ssl1} ) ? $IMAP_SSL_PORT : $IMAP_PORT ;
  937. $sync->{host2} || missing_option( $sync, '--host2' ) ;
  938. $sync->{port2} ||= ( $sync->{ssl2} ) ? $IMAP_SSL_PORT : $IMAP_PORT ;
  939. $debugimap1 = $debugimap2 = 1 if ( $debugimap ) ;
  940. $sync->{ debug } = 1 if ( $debugimap1 or $debugimap2 ) ;
  941. # By default, don't take size to compare
  942. $skipsize = (defined $skipsize) ? $skipsize : 1;
  943. $uid1 = defined $uid1 ? $uid1 : 1;
  944. $uid2 = defined $uid2 ? $uid2 : 1;
  945. $subscribe = defined $subscribe ? $subscribe : 1;
  946. # Allow size mismatch by default
  947. $allowsizemismatch = defined $allowsizemismatch ? $allowsizemismatch : 1;
  948. if ( defined $delete2foldersbutnot or defined $delete2foldersonly ) {
  949. $delete2folders = 1 ;
  950. }
  951. my $SSL_VERIFY_POLICY ;
  952. my %SSL_VERIFY_STR ;
  953. Readonly $SSL_VERIFY_POLICY => IO::Socket::SSL::SSL_VERIFY_NONE( ) ;
  954. Readonly %SSL_VERIFY_STR => (
  955. IO::Socket::SSL::SSL_VERIFY_NONE( ) => 'SSL_VERIFY_NONE, ie, do not check the certificate server.' ,
  956. IO::Socket::SSL::SSL_VERIFY_PEER( ) => 'SSL_VERIFY_PEER, ie, check the certificate server' ,
  957. ) ;
  958. $IO::Socket::SSL::DEBUG = defined( $sync->{debugssl} ) ? $sync->{debugssl} : 1 ;
  959. if ( $sync->{ssl1} or $sync->{ssl2} or $sync->{tls1} or $sync->{tls2}) {
  960. 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" ) ;
  961. }
  962. if ( $sync->{ssl1} ) {
  963. myprint( qq{Host1: SSL default mode is like --sslargs1 "SSL_verify_mode=$SSL_VERIFY_POLICY", meaning for host1 $SSL_VERIFY_STR{$SSL_VERIFY_POLICY}\n} ) ;
  964. myprint( 'Host1: Use --sslargs1 SSL_verify_mode=' . IO::Socket::SSL::SSL_VERIFY_PEER( ) . " to have $SSL_VERIFY_STR{IO::Socket::SSL::SSL_VERIFY_PEER( )} of host1\n" ) ;
  965. }
  966. if ( $sync->{ssl2} ) {
  967. myprint( qq{Host2: SSL default mode is like --sslargs2 "SSL_verify_mode=$SSL_VERIFY_POLICY", meaning for host2 $SSL_VERIFY_STR{$SSL_VERIFY_POLICY}\n} ) ;
  968. myprint( 'Host2: Use --sslargs2 SSL_verify_mode=' . IO::Socket::SSL::SSL_VERIFY_PEER( ) . " to have $SSL_VERIFY_STR{IO::Socket::SSL::SSL_VERIFY_PEER( )} of host2\n" ) ;
  969. }
  970. if ( $sync->{justconnect} ) {
  971. justconnect( $sync ) ;
  972. myprint( debugmemory( $sync, " after justconnect() call" ) ) ;
  973. exit_clean( $sync, $EX_OK ) ;
  974. }
  975. $sync->{user1} || missing_option( $sync, '--user1' ) ;
  976. $sync->{user2} || missing_option( $sync, '--user2' ) ;
  977. $syncinternaldates = defined $syncinternaldates ? $syncinternaldates : 1;
  978. # Turn on expunge if there is not explicit option --noexpunge1 and option
  979. # --delete1 is given.
  980. # Done because --delete1 --noexpunge1 is very dangerous on the second run:
  981. # the Deleted flag is then synced to all previously transferred messages.
  982. # So --delete1 implies --expunge1 is a better usability default behavior.
  983. if ( $sync->{ delete1 } ) {
  984. if ( ! defined $sync->{ expunge1 } ) {
  985. myprint( "Info: turning on --expunge1 because --delete1 --noexpunge1 is very dangerous on the second run.\n" ) ;
  986. $sync->{ expunge1 } = 1 ;
  987. }
  988. myprint( "Info: if expunging after each message slows down too much the sync then use --noexpungeaftereach to speed up\n" ) ;
  989. }
  990. if ( $sync->{ uidexpunge2 } and not Mail::IMAPClient->can( 'uidexpunge' ) ) {
  991. myprint( "Failure: uidexpunge not supported (IMAPClient release < 3.17), use nothing or --expunge2 instead\n" ) ;
  992. exit_clean( $sync, $EX_SOFTWARE ) ;
  993. }
  994. if ( ( $sync->{ delete2 } or $sync->{ delete2duplicates } ) and not defined $sync->{ uidexpunge2 } ) {
  995. if ( Mail::IMAPClient->can( 'uidexpunge' ) ) {
  996. myprint( "Info: will act as --uidexpunge2\n" ) ;
  997. $sync->{ uidexpunge2 } = 1 ;
  998. }elsif ( not defined $sync->{ expunge2 } ) {
  999. myprint( "Info: will act as --expunge2 (no uidexpunge support)\n" ) ;
  1000. $sync->{ expunge2 } = 1 ;
  1001. }
  1002. }
  1003. if ( $sync->{ delete1 } and $sync->{ delete2 } ) {
  1004. myprint( "Warning: using --delete1 and --delete2 together is almost always a bad idea, exiting imapsync\n" ) ;
  1005. exit_clean( $sync, $EX_USAGE ) ;
  1006. }
  1007. if ( $idatefromheader ) {
  1008. myprint( 'Turned ON idatefromheader, ',
  1009. "will set the internal dates on host2 from the 'Date:' header line.\n" ) ;
  1010. $syncinternaldates = 0 ;
  1011. }
  1012. if ( $syncinternaldates ) {
  1013. myprint( 'Info: turned ON syncinternaldates, ',
  1014. "will set the internal dates (arrival dates) on host2 same as host1.\n" ) ;
  1015. }else{
  1016. myprint( "Info: turned OFF syncinternaldates\n" ) ;
  1017. }
  1018. if ( defined $authmd5 and $authmd5 ) {
  1019. $authmd51 = 1 ;
  1020. $authmd52 = 1 ;
  1021. }
  1022. if ( defined $authmd51 and $authmd51 ) {
  1023. $authmech1 ||= 'CRAM-MD5';
  1024. }
  1025. else{
  1026. $authmech1 ||= $authuser1 ? 'PLAIN' : 'LOGIN';
  1027. }
  1028. if ( defined $authmd52 and $authmd52 ) {
  1029. $authmech2 ||= 'CRAM-MD5';
  1030. }
  1031. else{
  1032. $authmech2 ||= $authuser2 ? 'PLAIN' : 'LOGIN';
  1033. }
  1034. $authmech1 = uc $authmech1;
  1035. $authmech2 = uc $authmech2;
  1036. if (defined $proxyauth1 && !$authuser1) {
  1037. missing_option( $sync, 'With --proxyauth1, --authuser1' ) ;
  1038. }
  1039. if (defined $proxyauth2 && !$authuser2) {
  1040. missing_option( $sync, 'With --proxyauth2, --authuser2' ) ;
  1041. }
  1042. $authuser1 ||= $sync->{user1};
  1043. $authuser2 ||= $sync->{user2};
  1044. myprint( "Host1: will try to use $authmech1 authentication on host1\n") ;
  1045. myprint( "Host2: will try to use $authmech2 authentication on host2\n") ;
  1046. $timeout = defined $timeout ? $timeout : $DEFAULT_TIMEOUT ;
  1047. $sync->{h1}->{timeout} = defined $sync->{h1}->{timeout} ? $sync->{h1}->{timeout} : $timeout ;
  1048. myprint( "Host1: imap connection timeout is $sync->{h1}->{timeout} seconds\n") ;
  1049. $sync->{h2}->{timeout} = defined $sync->{h2}->{timeout} ? $sync->{h2}->{timeout} : $timeout ;
  1050. myprint( "Host2: imap connection timeout is $sync->{h2}->{timeout} seconds\n" ) ;
  1051. $syncacls = defined $syncacls ? $syncacls : 0 ;
  1052. # No folders sizes if --justfolders, unless really wanted.
  1053. if ( $sync->{ justfolders } and not defined $foldersizes ) { $foldersizes = 0 ; }
  1054. $foldersizes = ( defined $foldersizes ) ? $foldersizes : 1 ;
  1055. $foldersizesatend = ( defined $foldersizesatend ) ? $foldersizesatend : $foldersizes ;
  1056. $fastio1 = defined $fastio1 ? $fastio1 : 0 ;
  1057. $fastio2 = defined $fastio2 ? $fastio2 : 0 ;
  1058. $reconnectretry1 = defined $reconnectretry1 ? $reconnectretry1 : $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ;
  1059. $reconnectretry2 = defined $reconnectretry2 ? $reconnectretry2 : $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ;
  1060. # Since select_msgs() returns no messages when uidnext does not return something
  1061. # then $uidnext_default is never used. So I have to remove it.
  1062. $uidnext_default = $DEFAULT_UIDNEXT ;
  1063. if ( ! @useheader ) { @useheader = qw( Message-Id Received ) ; }
  1064. my %useheader ;
  1065. # Make a hash %useheader of each --useheader 'key' in uppercase
  1066. for ( @useheader ) { $useheader{ uc $_ } = undef } ;
  1067. #myprint( Data::Dumper->Dump( [ \%useheader ] ) ) ;
  1068. #exit ;
  1069. myprint( "Host1: IMAP server [$sync->{host1}] port [$sync->{port1}] user [$sync->{user1}]\n" ) ;
  1070. myprint( "Host2: IMAP server [$sync->{host2}] port [$sync->{port2}] user [$sync->{user2}]\n" ) ;
  1071. get_password1( $sync ) ;
  1072. get_password2( $sync ) ;
  1073. $sync->{dry_message} = q{} ;
  1074. if( $sync->{dry} ) {
  1075. $sync->{dry_message} = "\t(not really since --dry mode)" ;
  1076. }
  1077. $search1 ||= $search if ( $search ) ;
  1078. $search2 ||= $search if ( $search ) ;
  1079. if ( $disarmreadreceipts ) {
  1080. push @regexmess, q{s{\A((?:[^\n]+\r\n)+|)(^Disposition-Notification-To:[^\n]*\n)(\r?\n|.*\n\r?\n)}{$1X-$2$3}ims} ;
  1081. }
  1082. $pipemesscheck = ( defined $pipemesscheck ) ? $pipemesscheck : 1 ;
  1083. if ( @pipemess and $pipemesscheck ) {
  1084. myprint( 'Checking each --pipemess command, '
  1085. . join( q{, }, @pipemess )
  1086. . ", with an space string. ( Can avoid this check with --nopipemesscheck )\n" ) ;
  1087. my $string = pipemess( q{ }, @pipemess ) ;
  1088. # string undef means something was bad.
  1089. if ( not ( defined $string ) ) {
  1090. exit_clean( $sync, $EX_USAGE, "Error: one of --pipemess command is bad, check it\n" ) ;
  1091. }
  1092. myprint( "Ok with each --pipemess @pipemess\n" ) ;
  1093. }
  1094. if ( $maxlinelengthcmd ) {
  1095. myprint( "Checking --maxlinelengthcmd command, $maxlinelengthcmd, with an space string.\n" ) ;
  1096. my $string = pipemess( q{ }, $maxlinelengthcmd ) ;
  1097. # string undef means something was bad.
  1098. if ( not ( defined $string ) ) {
  1099. exit_clean( $sync, $EX_USAGE, "Error: --maxlinelengthcmd command is bad, check it\n" ) ;
  1100. }
  1101. myprint( "Ok with --maxlinelengthcmd $maxlinelengthcmd\n" ) ;
  1102. }
  1103. if ( @regexmess ) {
  1104. my $string = regexmess( q{ } ) ;
  1105. myprint( "Checking each --regexmess command with an space string.\n" ) ;
  1106. # string undef means one of the eval regex was bad.
  1107. if ( not ( defined $string ) ) {
  1108. exit_clean( $sync, $EX_USAGE, 'Error: one of --regexmess option is bad, check it' ) ;
  1109. }
  1110. myprint( "Ok with each --regexmess\n" ) ;
  1111. }
  1112. if ( @skipmess ) {
  1113. myprint( "Checking each --skipmess command with an space string.\n" ) ;
  1114. my $match = skipmess( q{ } ) ;
  1115. # match undef means one of the eval regex was bad.
  1116. if ( not ( defined $match ) ) {
  1117. exit_clean( $sync, $EX_USAGE, 'Error: one of --skipmess option is bad, check it' ) ;
  1118. }
  1119. myprint( "Ok with each --skipmess\n" ) ;
  1120. }
  1121. if ( @regexflag ) {
  1122. myprint( "Checking each --regexflag command with an space string.\n" ) ;
  1123. my $string = flags_regex( q{ } ) ;
  1124. # string undef means one of the eval regex was bad.
  1125. if ( not ( defined $string ) ) {
  1126. exit_clean( $sync, $EX_USAGE, 'Error: one of --regexflag option is bad, check it' ) ;
  1127. }
  1128. myprint( "Ok with each --regexflag\n" ) ;
  1129. }
  1130. $sync->{imap1} = login_imap( $sync->{host1}, $sync->{port1}, $sync->{user1}, $domain1, $sync->{password1},
  1131. $debugimap1, $sync->{h1}->{timeout}, $fastio1, $sync->{ssl1}, $sync->{tls1},
  1132. $authmech1, $authuser1, $reconnectretry1,
  1133. $proxyauth1, $uid1, $split1, 'Host1', $sync->{h1}, $sync ) ;
  1134. $sync->{imap2} = login_imap( $sync->{host2}, $sync->{port2}, $sync->{user2}, $domain2, $sync->{password2},
  1135. $debugimap2, $sync->{h2}->{timeout}, $fastio2, $sync->{ssl2}, $sync->{tls2},
  1136. $authmech2, $authuser2, $reconnectretry2,
  1137. $proxyauth2, $uid2, $split2, 'Host2', $sync->{h2}, $sync ) ;
  1138. $sync->{ debug } and myprint( 'Host1 Buffer I/O: ', $sync->{imap1}->Buffer(), "\n" ) ;
  1139. $sync->{ debug } and myprint( 'Host2 Buffer I/O: ', $sync->{imap2}->Buffer(), "\n" ) ;
  1140. if ( ! $sync->{imap1}->IsAuthenticated( ) ) { exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE, 'Not authenticated on host1' ) ; }
  1141. myprint( "Host1: state Authenticated\n" ) ;
  1142. if ( ! $sync->{imap2}->IsAuthenticated( ) ) { exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE, 'Not authenticated on host2' ) ; }
  1143. myprint( "Host2: state Authenticated\n" ) ;
  1144. myprint( 'Host1 capability once authenticated: ', join(q{ }, @{ $sync->{imap1}->capability() || [] }), "\n" ) ;
  1145. #myprint( Data::Dumper->Dump( [ $sync->{imap1} ] ) ) ;
  1146. #myprint( "imap4rev1: " . $sync->{imap1}->imap4rev1() . "\n" ) ;
  1147. myprint( 'Host2 capability once authenticated: ', join(q{ }, @{ $sync->{imap2}->capability() || [] }), "\n" ) ;
  1148. # ID on by default since 1.832
  1149. $sync->{id} = defined $sync->{id} ? $sync->{id} : 1 ;
  1150. imap_id_stuff( $sync ) ;
  1151. #quota( $sync, $sync->{imap1}, 'h1' ) ; # quota on host1 is useless and pollute host2 output.
  1152. quota( $sync, $sync->{imap2}, 'h2' ) ;
  1153. maxsize_setting( $sync ) ;
  1154. if ( $sync->{ justlogin } ) {
  1155. $sync->{imap1}->logout( ) ;
  1156. $sync->{imap2}->logout( ) ;
  1157. myprint( "Exiting because of --justlogin\n" ) ;
  1158. exit_clean( $sync, $EX_OK ) ;
  1159. }
  1160. #
  1161. # Folder stuff
  1162. #
  1163. my (
  1164. @h1_folders_all , %h1_folders_all , @h1_folders_wanted , %requested_folder ,
  1165. %h1_subscribed_folder , %h2_subscribed_folder ,
  1166. @h2_folders_all , %h2_folders_all , %h2_folders_all_UPPER ,
  1167. @h2_folders_from_1_wanted , %h2_folders_from_1_wanted ,
  1168. %h2_folders_from_1_several ,
  1169. %h2_folders_from_1_all ,
  1170. ) ;
  1171. my $h1_folders_wanted_nb = 0 ;
  1172. my $h1_folders_wanted_ct = 0 ; # counter of folders done.
  1173. # All folders on host1 and host2
  1174. @h1_folders_all = sort $sync->{imap1}->folders( ) ;
  1175. @h2_folders_all = sort $sync->{imap2}->folders( ) ;
  1176. myprint( 'Host1: found ', scalar @h1_folders_all , " folders.\n" ) ;
  1177. myprint( 'Host2: found ', scalar @h2_folders_all , " folders.\n" ) ;
  1178. foreach my $f ( @h1_folders_all ) {
  1179. $h1_folders_all{ $f } = 1
  1180. }
  1181. foreach my $f ( @h2_folders_all ) {
  1182. $h2_folders_all{ $f } = 1 ;
  1183. $h2_folders_all_UPPER{ uc $f } = 1 ;
  1184. }
  1185. $sync->{h1_folders_all} = \%h1_folders_all ;
  1186. $sync->{h2_folders_all} = \%h2_folders_all ;
  1187. $sync->{h2_folders_all_UPPER} = \%h2_folders_all_UPPER ;
  1188. private_folders_separators_and_prefixes( ) ;
  1189. # Make a hash of subscribed folders in both servers.
  1190. for ( $sync->{imap1}->subscribed( ) ) { $h1_subscribed_folder{ $_ } = 1 } ;
  1191. for ( $sync->{imap2}->subscribed( ) ) { $h2_subscribed_folder{ $_ } = 1 } ;
  1192. if ( defined $sync->{ subfolder1 } ) {
  1193. subfolder1( $sync ) ;
  1194. }
  1195. if ( defined $sync->{ subfolder2 } ) {
  1196. subfolder2( $sync ) ;
  1197. }
  1198. if ( $fixInboxINBOX and ( my $reg = fix_Inbox_INBOX_mapping( \%h1_folders_all, \%h2_folders_all ) ) ) {
  1199. push @{ $sync->{ regextrans2 } }, $reg ;
  1200. }
  1201. if ( ( $sync->{ folder } and scalar @{ $sync->{ folder } } )
  1202. or $subscribed
  1203. or scalar @folderrec )
  1204. {
  1205. # folders given by option --folder
  1206. if ( $sync->{ folder } and scalar @{ $sync->{ folder } } ) {
  1207. add_to_requested_folders( @{ $sync->{ folder } } ) ;
  1208. }
  1209. # option --subscribed
  1210. if ( $subscribed ) {
  1211. add_to_requested_folders( keys %h1_subscribed_folder ) ;
  1212. }
  1213. # option --folderrec
  1214. if ( scalar @folderrec ) {
  1215. foreach my $folderrec ( @folderrec ) {
  1216. add_to_requested_folders( $sync->{imap1}->folders( $folderrec ) ) ;
  1217. }
  1218. }
  1219. }
  1220. else
  1221. {
  1222. # no include, no folder/subscribed/folderrec options => all folders
  1223. if ( not scalar @include ) {
  1224. 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" ) ;
  1225. add_to_requested_folders( @h1_folders_all ) ;
  1226. }
  1227. }
  1228. # consider (optional) includes and excludes
  1229. if ( scalar @include ) {
  1230. foreach my $include ( @include ) {
  1231. my @included_folders = grep { /$include/ } @h1_folders_all ;
  1232. add_to_requested_folders( @included_folders ) ;
  1233. myprint( "Including folders matching pattern $include\n" . jux_utf8_list( @included_folders ) . "\n" ) ;
  1234. }
  1235. }
  1236. if ( scalar @exclude ) {
  1237. foreach my $exclude ( @exclude ) {
  1238. my @requested_folder = sort keys %requested_folder ;
  1239. my @excluded_folders = grep { /$exclude/ } @requested_folder ;
  1240. remove_from_requested_folders( @excluded_folders ) ;
  1241. myprint( "Excluding folders matching pattern $exclude\n" . jux_utf8_list( @excluded_folders ) . "\n" ) ;
  1242. }
  1243. }
  1244. # sort before is not very powerful
  1245. # it adds --folderfirst and --folderlast even if they don't exist on host1
  1246. @h1_folders_wanted = sort_requested_folders( ) ;
  1247. # Remove no selectable folders
  1248. if ( $sync->{ checkfoldersexist } ) {
  1249. my @h1_folders_wanted_exist ;
  1250. myprint( "Host1: Checking wanted folders exist. Use --nocheckfoldersexist to avoid this check (shared of public namespace targeted).\n" ) ;
  1251. foreach my $folder ( @h1_folders_wanted ) {
  1252. ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "Checking $folder exists on host1\n" ) ;
  1253. if ( ! exists $h1_folders_all{ $folder } ) {
  1254. myprint( "Host1: warning! ignoring folder $folder because it is not in host1 whole folders list.\n" ) ;
  1255. next ;
  1256. }else{
  1257. push @h1_folders_wanted_exist, $folder ;
  1258. }
  1259. }
  1260. @h1_folders_wanted = @h1_folders_wanted_exist ;
  1261. }else{
  1262. myprint( "Host1: Not checking that wanted folders exist. Remove --nocheckfoldersexist to get this check.\n" ) ;
  1263. }
  1264. if ( $sync->{ checkselectable } ) {
  1265. my @h1_folders_wanted_selectable ;
  1266. myprint( "Host1: Checking wanted folders are selectable. Use --nocheckselectable to avoid this check.\n" ) ;
  1267. foreach my $folder ( @h1_folders_wanted ) {
  1268. ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "Checking $folder is selectable on host1\n" ) ;
  1269. # It does an imap command LIST "" $folder and then search for no \Noselect
  1270. if ( ! $sync->{imap1}->selectable( $folder ) ) {
  1271. myprint( "Host1: warning! ignoring folder $folder because it is not selectable\n" ) ;
  1272. }else{
  1273. push @h1_folders_wanted_selectable, $folder ;
  1274. }
  1275. }
  1276. @h1_folders_wanted = @h1_folders_wanted_selectable ;
  1277. ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( 'Host1: checking folders took ', timenext( ), " s\n" ) ;
  1278. }else{
  1279. myprint( "Host1: Not checking that wanted folders are selectable. Remove --nocheckselectable to get this check.\n" ) ;
  1280. }
  1281. $sync->{h1_folders_wanted} = \@h1_folders_wanted ;
  1282. # Old place of private_folders_separators_and_prefixes( ) call.
  1283. #private_folders_separators_and_prefixes( ) ;
  1284. # this hack is because LWP post does not pass well a hash in the $form parameter
  1285. # but it does pass well an array
  1286. %{ $sync->{f1f2h} } = split_around_equal( @{ $sync->{f1f2} } ) ;
  1287. automap( $sync ) ;
  1288. foreach my $h1_fold ( @h1_folders_wanted ) {
  1289. my $h2_fold ;
  1290. $h2_fold = imap2_folder_name( $sync, $h1_fold ) ;
  1291. $h2_folders_from_1_wanted{ $h2_fold }++ ;
  1292. if ( 1 < $h2_folders_from_1_wanted{ $h2_fold } ) {
  1293. $h2_folders_from_1_several{ $h2_fold }++ ;
  1294. }
  1295. }
  1296. @h2_folders_from_1_wanted = sort keys %h2_folders_from_1_wanted;
  1297. foreach my $h1_fold ( @h1_folders_all ) {
  1298. my $h2_fold ;
  1299. $h2_fold = imap2_folder_name( $sync, $h1_fold ) ;
  1300. $h2_folders_from_1_all{ $h2_fold }++ ;
  1301. }
  1302. myprint( << 'END_LISTING' ) ;
  1303. ++++ Listing folders
  1304. All foldernames are presented between brackets like [X] where X is the foldername.
  1305. When a foldername contains non-ASCII characters it is presented in the form
  1306. [X] = [Y] where
  1307. X is the imap foldername you have to use in command line options and
  1308. Y is the utf8 output just printed for convenience, to recognize it.
  1309. END_LISTING
  1310. myprint(
  1311. "Host1: folders list (first the raw imap format then the [X] = [Y]):\n",
  1312. $sync->{imap1}->list( ),
  1313. "\n",
  1314. jux_utf8_list( @h1_folders_all ),
  1315. "\n",
  1316. "Host2: folders list (first the raw imap format then the [X] = [Y]):\n",
  1317. $sync->{imap2}->list( ),
  1318. "\n",
  1319. jux_utf8_list( @h2_folders_all ),
  1320. "\n",
  1321. q{}
  1322. ) ;
  1323. if ( $subscribed ) {
  1324. myprint(
  1325. 'Host1 subscribed folders list: ',
  1326. jux_utf8_list( sort keys %h1_subscribed_folder ), "\n",
  1327. ) ;
  1328. }
  1329. my @h2_folders_not_in_1;
  1330. @h2_folders_not_in_1 = list_folders_in_2_not_in_1( ) ;
  1331. if ( @h2_folders_not_in_1 ) {
  1332. myprint( "Folders in host2 not in host1:\n",
  1333. jux_utf8_list( @h2_folders_not_in_1 ), "\n" ) ;
  1334. }
  1335. if ( keys %{ $sync->{f1f2auto} } ) {
  1336. myprint( "Folders mapping from --automap feature (use --f1f2 to override any mapping):\n" ) ;
  1337. foreach my $h1_fold ( keys %{ $sync->{f1f2auto} } ) {
  1338. my $h2_fold = $sync->{f1f2auto}{$h1_fold} ;
  1339. myprintf( "%-40s -> %-40s\n",
  1340. jux_utf8( $h1_fold ), jux_utf8( $h2_fold ) ) ;
  1341. }
  1342. myprint( "\n" ) ;
  1343. }
  1344. if ( keys %{ $sync->{f1f2h} } ) {
  1345. myprint( "Folders mapping from --f1f2 options, it overrides --automap:\n" ) ;
  1346. foreach my $h1_fold ( keys %{ $sync->{f1f2h} } ) {
  1347. my $h2_fold = $sync->{f1f2h}{$h1_fold} ;
  1348. my $warn = q{} ;
  1349. if ( not exists $h1_folders_all{ $h1_fold } ) {
  1350. $warn = "BUT $h1_fold does NOT exist on host1!" ;
  1351. }
  1352. myprintf( "%-40s -> %-40s %s\n",
  1353. jux_utf8( $h1_fold ), jux_utf8( $h2_fold ), $warn ) ;
  1354. }
  1355. myprint( "\n" ) ;
  1356. }
  1357. exit_clean( $sync, $EX_OK ) if ( $sync->{justfolderlists} ) ;
  1358. exit_clean( $sync, $EX_OK ) if ( $sync->{justautomap} ) ;
  1359. debugsleep( $sync ) ;
  1360. if ( $foldersizes ) {
  1361. foldersizes_on_h1h2( $sync ) ;
  1362. }
  1363. if ( $sync->{ justfoldersizes } )
  1364. {
  1365. myprint( "Exiting because of --justfoldersizes\n" ) ;
  1366. exit_clean( $sync, $EX_OK ) ;
  1367. }
  1368. $sync->{stats} = 1 ;
  1369. if ( $sync->{ delete1emptyfolders } ) {
  1370. delete1emptyfolders( $sync ) ;
  1371. }
  1372. delete_folders_in_2_not_in_1( ) if $delete2folders ;
  1373. # folder loop
  1374. $h1_folders_wanted_nb = scalar @h1_folders_wanted ;
  1375. myprint( "++++ Looping on each one of $h1_folders_wanted_nb folders to sync\n" ) ;
  1376. $sync->{begin_transfer_time} = time ;
  1377. my %uid_candidate_for_deletion ;
  1378. my %uid_candidate_no_deletion ;
  1379. $sync->{ h2_folders_of_md5 } = { } ;
  1380. FOLDER: foreach my $h1_fold ( @h1_folders_wanted ) {
  1381. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1382. my $h2_fold = imap2_folder_name( $sync, $h1_fold ) ;
  1383. $h1_folders_wanted_ct++ ;
  1384. myprintf( "Folder %7s %-35s -> %-35s\n", "$h1_folders_wanted_ct/$h1_folders_wanted_nb",
  1385. jux_utf8( $h1_fold ), jux_utf8( $h2_fold ) ) ;
  1386. myprint( debugmemory( $sync, " at folder loop" ) ) ;
  1387. # host1 can not be fetched read only, select is needed because of expunge.
  1388. select_folder( $sync, $sync->{imap1}, $h1_fold, 'Host1' ) or next FOLDER ;
  1389. debugsleep( $sync ) ;
  1390. my $h1_fold_nb_messages = count_from_select( $sync->{imap1}->History ) ;
  1391. myprint( "Host1: folder [$h1_fold] has $h1_fold_nb_messages messages in total (mentioned by SELECT)\n" ) ;
  1392. if ( $skipemptyfolders and 0 == $h1_fold_nb_messages ) {
  1393. myprint( "Host1: skipping empty host1 folder [$h1_fold]\n" ) ;
  1394. next FOLDER ;
  1395. }
  1396. # Code added from https://github.com/imapsync/imapsync/issues/95
  1397. # Thanks jh1995
  1398. if ( $skipemptyfolders ) {
  1399. my $h1_msgs_all_hash_ref_tmp = { } ;
  1400. my @h1_msgs_tmp = select_msgs( $sync->{imap1}, $h1_msgs_all_hash_ref_tmp, $search1, $h1_fold ) ;
  1401. my $h1_fold_nb_messages_tmp = scalar( @h1_msgs_tmp ) ;
  1402. if ( 0 == $h1_fold_nb_messages_tmp ) {
  1403. myprint( "Host1: skipping empty host1 folder [$h1_fold] (0 message found by SEARCH)\n" ) ;
  1404. next FOLDER ;
  1405. }
  1406. }
  1407. if ( ! exists $h2_folders_all{ $h2_fold } ) {
  1408. create_folder( $sync, $sync->{imap2}, $h2_fold, $h1_fold ) or next FOLDER ;
  1409. }
  1410. acls_sync( $h1_fold, $h2_fold ) ;
  1411. # Sometimes the folder on host2 is listed (it exists) but is
  1412. # not selectable but becomes selectable by a create (Gmail)
  1413. select_folder( $sync, $sync->{imap2}, $h2_fold, 'Host2' )
  1414. or ( create_folder( $sync, $sync->{imap2}, $h2_fold, $h1_fold )
  1415. and select_folder( $sync, $sync->{imap2}, $h2_fold, 'Host2' ) )
  1416. or next FOLDER ;
  1417. my @select_results = $sync->{imap2}->Results( ) ;
  1418. my $h2_fold_nb_messages = count_from_select( @select_results ) ;
  1419. myprint( "Host2: folder [$h2_fold] has $h2_fold_nb_messages messages in total (mentioned by SELECT)\n" ) ;
  1420. my $permanentflags2 = permanentflags( @select_results ) ;
  1421. myprint( "Host2: folder [$h2_fold] permanentflags: $permanentflags2\n" ) ;
  1422. if ( $sync->{ expunge1 } )
  1423. {
  1424. myprint( "Host1: Expunging $h1_fold $sync->{dry_message}\n" ) ;
  1425. if ( ! $sync->{dry} ) { $sync->{imap1}->expunge( ) } ;
  1426. }
  1427. if ( ( ( $subscribe and exists $h1_subscribed_folder{ $h1_fold } ) or $subscribeall )
  1428. and not exists $h2_subscribed_folder{ $h2_fold } )
  1429. {
  1430. myprint( "Host2: Subscribing to folder $h2_fold\n" ) ;
  1431. if ( ! $sync->{dry} ) { $sync->{imap2}->subscribe( $h2_fold ) } ;
  1432. }
  1433. next FOLDER if ( $sync->{ justfolders } ) ;
  1434. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1435. my $h1_msgs_all_hash_ref = { } ;
  1436. my @h1_msgs = select_msgs( $sync->{imap1}, $h1_msgs_all_hash_ref, $search1, $sync->{abletosearch1}, $h1_fold );
  1437. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1438. my $h1_msgs_nb = scalar @h1_msgs ;
  1439. myprint( "Host1: folder [$h1_fold] considering $h1_msgs_nb messages\n" ) ;
  1440. ( $sync->{ debug } or $debuglist ) and myprint( "Host1: folder [$h1_fold] considering $h1_msgs_nb messages, LIST gives: @h1_msgs\n" ) ;
  1441. $sync->{ debug } and myprint( "Host1: selecting messages of folder [$h1_fold] took ", timenext(), " s\n" ) ;
  1442. my $h2_msgs_all_hash_ref = { } ;
  1443. my @h2_msgs = select_msgs( $sync->{imap2}, $h2_msgs_all_hash_ref, $search2, $sync->{abletosearch2}, $h2_fold ) ;
  1444. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1445. my $h2_msgs_nb = scalar @h2_msgs ;
  1446. myprint( "Host2: folder [$h2_fold] considering $h2_msgs_nb messages\n" ) ;
  1447. ( $sync->{ debug } or $debuglist ) and myprint( "Host2: folder [$h2_fold] considering $h2_msgs_nb messages, LIST gives: @h2_msgs\n" ) ;
  1448. $sync->{ debug } and myprint( "Host2: selecting messages of folder [$h2_fold] took ", timenext(), " s\n" ) ;
  1449. my $cache_base = "$sync->{ tmpdir }/imapsync_cache/" ;
  1450. my $cache_dir = cache_folder( $cache_base, "$sync->{host1}/$sync->{user1}/$sync->{host2}/$sync->{user2}", $h1_fold, $h2_fold ) ;
  1451. my ( $cache_1_2_ref, $cache_2_1_ref ) = ( {}, {} ) ;
  1452. my $h1_uidvalidity = $sync->{imap1}->uidvalidity( ) || q{} ;
  1453. my $h2_uidvalidity = $sync->{imap2}->uidvalidity( ) || q{} ;
  1454. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1455. if ( $usecache ) {
  1456. myprint( "Local cache directory: $cache_dir ( " . length( $cache_dir ) . " characters long )\n" ) ;
  1457. mkpath( "$cache_dir" ) ;
  1458. ( $cache_1_2_ref, $cache_2_1_ref )
  1459. = get_cache( $cache_dir, \@h1_msgs, \@h2_msgs, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) ;
  1460. myprint( 'CACHE h1 h2: ', scalar keys %{ $cache_1_2_ref } , " files\n" ) ;
  1461. $sync->{ debug } and myprint( '[',
  1462. map ( { "$_->$cache_1_2_ref->{$_} " } keys %{ $cache_1_2_ref } ), " ]\n" ) ;
  1463. }
  1464. my %h1_hash = ( ) ;
  1465. my %h2_hash = ( ) ;
  1466. my ( %h1_msgs, %h2_msgs ) ;
  1467. @h1_msgs{ @h1_msgs } = ( ) ;
  1468. @h2_msgs{ @h2_msgs } = ( ) ;
  1469. my @h1_msgs_in_cache = sort { $a <=> $b } keys %{ $cache_1_2_ref } ;
  1470. my @h2_msgs_in_cache = keys %{ $cache_2_1_ref } ;
  1471. my ( %h1_msgs_not_in_cache, %h2_msgs_not_in_cache ) ;
  1472. %h1_msgs_not_in_cache = %h1_msgs ;
  1473. %h2_msgs_not_in_cache = %h2_msgs ;
  1474. delete @h1_msgs_not_in_cache{ @h1_msgs_in_cache } ;
  1475. delete @h2_msgs_not_in_cache{ @h2_msgs_in_cache } ;
  1476. my @h1_msgs_not_in_cache = keys %h1_msgs_not_in_cache ;
  1477. #myprint( "h1_msgs_not_in_cache: [@h1_msgs_not_in_cache]\n" ) ;
  1478. my @h2_msgs_not_in_cache = keys %h2_msgs_not_in_cache ;
  1479. my @h2_msgs_delete2_not_in_cache = () ;
  1480. %h1_msgs_copy_by_uid = ( ) ;
  1481. if ( $useuid ) {
  1482. # use uid so we have to avoid getting header
  1483. @h1_msgs_copy_by_uid{ @h1_msgs_not_in_cache } = ( ) ;
  1484. @h2_msgs_delete2_not_in_cache = @h2_msgs_not_in_cache if $usecache ;
  1485. @h1_msgs_not_in_cache = ( ) ;
  1486. @h2_msgs_not_in_cache = ( ) ;
  1487. #myprint( "delete2: @h2_msgs_delete2_not_in_cache\n" ) ;
  1488. }
  1489. $sync->{ debug } and myprint( "Host1: parsing headers of folder [$h1_fold]\n" ) ;
  1490. my ($h1_heads_ref, $h1_fir_ref) = ({}, {});
  1491. $h1_heads_ref = $sync->{imap1}->parse_headers([@h1_msgs_not_in_cache], @useheader) if (@h1_msgs_not_in_cache);
  1492. $sync->{ debug } and myprint( "Host1: parsing headers of folder [$h1_fold] took ", timenext(), " s\n" ) ;
  1493. @{ $h1_fir_ref }{@h1_msgs} = ( undef ) ;
  1494. $sync->{ debug } and myprint( "Host1: getting flags idate and sizes of folder [$h1_fold]\n" ) ;
  1495. my @h1_common_fetch_param = ( 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE' ) ;
  1496. if ( $sync->{ synclabels } or $sync->{ resynclabels } ) { push @h1_common_fetch_param, 'X-GM-LABELS' ; }
  1497. if ( $sync->{abletosearch1} )
  1498. {
  1499. $h1_fir_ref = $sync->{imap1}->fetch_hash( \@h1_msgs, @h1_common_fetch_param, $h1_fir_ref )
  1500. if ( @h1_msgs ) ;
  1501. }
  1502. else
  1503. {
  1504. my $uidnext = $sync->{imap1}->uidnext( $h1_fold ) || $uidnext_default ;
  1505. my $fetch_hash_uids = $fetch_hash_set || "1:$uidnext" ;
  1506. $h1_fir_ref = $sync->{imap1}->fetch_hash( $fetch_hash_uids, @h1_common_fetch_param, $h1_fir_ref )
  1507. if ( @h1_msgs ) ;
  1508. }
  1509. $sync->{ debug } and myprint( "Host1: getting flags idate and sizes of folder [$h1_fold] took ", timenext(), " s\n" ) ;
  1510. if ( ! $h1_fir_ref )
  1511. {
  1512. my $error = join( q{}, "Host1: folder $h1_fold : Could not fetch_hash ",
  1513. scalar @h1_msgs, ' msgs: ', $sync->{imap1}->LastError || q{}, "\n" ) ;
  1514. errors_incr( $sync, $error ) ;
  1515. next FOLDER ;
  1516. }
  1517. my @h1_msgs_duplicate;
  1518. foreach my $m ( @h1_msgs_not_in_cache )
  1519. {
  1520. my $rc = parse_header_msg( $sync, $sync->{imap1}, $m, $h1_heads_ref, $h1_fir_ref, 'Host1', \%h1_hash ) ;
  1521. if ( ! defined $rc )
  1522. {
  1523. my $h1_size = $h1_fir_ref->{$m}->{'RFC822.SIZE'} || 0;
  1524. myprint( "Host1: $h1_fold/$m size $h1_size ignored (no wanted headers so we ignore this message. To solve this: use --addheader)\n" ) ;
  1525. $sync->{ total_bytes_skipped } += $h1_size ;
  1526. $sync->{ nb_msg_skipped } += 1 ;
  1527. $sync->{ h1_nb_msg_noheader } +=1 ;
  1528. $sync->{ h1_nb_msg_processed } +=1 ;
  1529. } elsif(0 == $rc)
  1530. {
  1531. # duplicate
  1532. push @h1_msgs_duplicate, $m;
  1533. # duplicate, same id same size?
  1534. my $h1_size = $h1_fir_ref->{$m}->{'RFC822.SIZE'} || 0;
  1535. $sync->{ nb_msg_skipped } += 1;
  1536. $h1_nb_msg_duplicate += 1;
  1537. $sync->{ h1_nb_msg_processed } +=1 ;
  1538. }
  1539. }
  1540. my $h1_msgs_duplicate_nb = scalar @h1_msgs_duplicate ;
  1541. myprint( "Host1: folder [$h1_fold] selected $h1_msgs_nb messages, duplicates $h1_msgs_duplicate_nb\n" ) ;
  1542. $sync->{ debug } and myprint( 'Host1: whole time parsing headers took ', timenext(), " s\n" ) ;
  1543. # Getting headers and metada can be so long that host2 might be disconnected here
  1544. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1545. $sync->{ debug } and myprint( "Host2: parsing headers of folder [$h2_fold]\n" ) ;
  1546. my ($h2_heads_ref, $h2_fir_ref) = ( {}, {} );
  1547. $h2_heads_ref = $sync->{imap2}->parse_headers([@h2_msgs_not_in_cache], @useheader) if (@h2_msgs_not_in_cache);
  1548. $sync->{ debug } and myprint( "Host2: parsing headers of folder [$h2_fold] took ", timenext(), " s\n" ) ;
  1549. $sync->{ debug } and myprint( "Host2: getting flags idate and sizes of folder [$h2_fold]\n" ) ;
  1550. @{ $h2_fir_ref }{@h2_msgs} = ( ); # fetch_hash can select by uid with last arg as ref
  1551. my @h2_common_fetch_param = ( 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE' ) ;
  1552. if ( $sync->{ synclabels } or $sync->{ resynclabels } ) { push @h2_common_fetch_param, 'X-GM-LABELS' ; }
  1553. if ( $sync->{abletosearch2} and scalar( @h2_msgs ) ) {
  1554. $h2_fir_ref = $sync->{imap2}->fetch_hash( \@h2_msgs, @h2_common_fetch_param, $h2_fir_ref) ;
  1555. }else{
  1556. my $uidnext = $sync->{imap2}->uidnext( $h2_fold ) || $uidnext_default ;
  1557. my $fetch_hash_uids = $fetch_hash_set || "1:$uidnext" ;
  1558. $h2_fir_ref = $sync->{imap2}->fetch_hash( $fetch_hash_uids, @h2_common_fetch_param, $h2_fir_ref )
  1559. if ( @h2_msgs ) ;
  1560. }
  1561. $sync->{ debug } and myprint( "Host2: getting flags idate and sizes of folder [$h2_fold] took ", timenext(), " s\n" ) ;
  1562. my @h2_msgs_duplicate;
  1563. foreach my $m (@h2_msgs_not_in_cache) {
  1564. my $rc = parse_header_msg( $sync, $sync->{imap2}, $m, $h2_heads_ref, $h2_fir_ref, 'Host2', \%h2_hash ) ;
  1565. my $h2_size = $h2_fir_ref->{$m}->{'RFC822.SIZE'} || 0 ;
  1566. if (! defined $rc ) {
  1567. myprint( "Host2: $h2_fold/$m size $h2_size ignored (no wanted headers so we ignore this message)\n" ) ;
  1568. $h2_nb_msg_noheader += 1 ;
  1569. } elsif( 0 == $rc ) {
  1570. # duplicate
  1571. $h2_nb_msg_duplicate += 1 ;
  1572. push @h2_msgs_duplicate, $m ;
  1573. }
  1574. }
  1575. # %h2_folders_of_md5
  1576. foreach my $md5 ( keys %h2_hash ) {
  1577. $sync->{ h2_folders_of_md5 }->{ $md5 }->{ $h2_fold } ++ ;
  1578. }
  1579. # %h1_folders_of_md5
  1580. foreach my $md5 ( keys %h1_hash ) {
  1581. $sync->{ h1_folders_of_md5 }->{ $md5 }->{ $h2_fold } ++ ;
  1582. }
  1583. my $h2_msgs_duplicate_nb = scalar @h2_msgs_duplicate ;
  1584. myprint( "Host2: folder [$h2_fold] selected $h2_msgs_nb messages, duplicates $h2_msgs_duplicate_nb\n" ) ;
  1585. $sync->{ debug } and myprint( 'Host2 whole time parsing headers took ', timenext( ), " s\n" ) ;
  1586. $sync->{ debug } and myprint( "++++ Verifying [$h1_fold] -> [$h2_fold]\n" ) ;
  1587. # messages in host1 that are not in host2
  1588. my @h1_hash_keys_sorted_by_uid
  1589. = sort {$h1_hash{$a}{'m'} <=> $h1_hash{$b}{'m'}} keys %h1_hash;
  1590. #myprint( map { $h1_hash{$_}{'m'} . q{ }} @h1_hash_keys_sorted_by_uid ) ;
  1591. my @h2_hash_keys_sorted_by_uid
  1592. = sort {$h2_hash{$a}{'m'} <=> $h2_hash{$b}{'m'}} keys %h2_hash;
  1593. # Deletions on account2.
  1594. if( $sync->{ delete2duplicates } and not exists $h2_folders_from_1_several{ $h2_fold } ) {
  1595. my @h2_expunge ;
  1596. foreach my $h2_msg ( @h2_msgs_duplicate ) {
  1597. myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted [duplicate] on host2 $sync->{dry_message}\n" ) ;
  1598. push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 } ;
  1599. if ( ! $sync->{dry} ) {
  1600. $sync->{imap2}->delete_message( $h2_msg ) ;
  1601. $h2_nb_msg_deleted += 1 ;
  1602. }
  1603. }
  1604. my $cnt = scalar @h2_expunge ;
  1605. if( @h2_expunge and not $sync->{ expunge2 } ) {
  1606. myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n" ) ;
  1607. $sync->{imap2}->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ;
  1608. }
  1609. if ( $sync->{ expunge2 } ){
  1610. myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n" ) ;
  1611. $sync->{imap2}->expunge( ) if ! $sync->{dry} ;
  1612. }
  1613. }
  1614. if( $sync->{ delete2 } and not exists $h2_folders_from_1_several{ $h2_fold } ) {
  1615. # No host1 folders f1a f1b ... going all to same f2 (via --regextrans2)
  1616. my @h2_expunge;
  1617. foreach my $m_id (@h2_hash_keys_sorted_by_uid) {
  1618. #myprint( "$m_id " ) ;
  1619. if ( ! exists $h1_hash{$m_id} ) {
  1620. my $h2_msg = $h2_hash{$m_id}{'m'};
  1621. my $h2_flags = $h2_hash{$m_id}{'F'} || q{};
  1622. my $isdel = $h2_flags =~ /\B\\Deleted\b/x ? 1 : 0;
  1623. myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted on host2 [$m_id] $sync->{dry_message}\n" )
  1624. if ! $isdel;
  1625. push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 };
  1626. if ( ! ( $sync->{dry} or $isdel ) ) {
  1627. $sync->{imap2}->delete_message($h2_msg);
  1628. $h2_nb_msg_deleted += 1;
  1629. }
  1630. }
  1631. }
  1632. foreach my $h2_msg ( @h2_msgs_delete2_not_in_cache ) {
  1633. myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted [not in cache] on host2 $sync->{dry_message}\n" ) ;
  1634. push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 };
  1635. if ( ! $sync->{dry} ) {
  1636. $sync->{imap2}->delete_message($h2_msg);
  1637. $h2_nb_msg_deleted += 1;
  1638. }
  1639. }
  1640. my $cnt = scalar @h2_expunge ;
  1641. if( @h2_expunge and not $sync->{ expunge2 } ) {
  1642. myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n" ) ;
  1643. $sync->{imap2}->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ;
  1644. }
  1645. if ( $sync->{ expunge2 } ) {
  1646. myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n" ) ;
  1647. $sync->{imap2}->expunge( ) if ! $sync->{dry} ;
  1648. }
  1649. }
  1650. if( $sync->{ delete2 } and exists $h2_folders_from_1_several{ $h2_fold } ) {
  1651. myprint( "Host2: folder $h2_fold $h2_folders_from_1_several{ $h2_fold } folders left to sync there\n" ) ;
  1652. my @h2_expunge;
  1653. foreach my $m_id ( @h2_hash_keys_sorted_by_uid ) {
  1654. my $h2_msg = $h2_hash{ $m_id }{ 'm' } ;
  1655. if ( ! exists $h1_hash{ $m_id } ) {
  1656. my $h2_flags = $h2_hash{ $m_id }{ 'F' } || q{} ;
  1657. my $isdel = $h2_flags =~ /\B\\Deleted\b/x ? 1 : 0 ;
  1658. if ( ! $isdel ) {
  1659. $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion [$m_id]\n" ) ;
  1660. $uid_candidate_for_deletion{ $h2_fold }{ $h2_msg }++ ;
  1661. }
  1662. }else{
  1663. $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [$m_id]\n" ) ;
  1664. $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
  1665. }
  1666. }
  1667. foreach my $h2_msg ( @h2_msgs_delete2_not_in_cache ) {
  1668. myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion [not in cache]\n" ) ;
  1669. $uid_candidate_for_deletion{ $h2_fold }{ $h2_msg }++ ;
  1670. }
  1671. foreach my $h2_msg ( @h2_msgs_in_cache ) {
  1672. myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [in cache]\n" ) ;
  1673. $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
  1674. }
  1675. if ( 0 == $h2_folders_from_1_several{ $h2_fold } ) {
  1676. # last host1 folder going to $h2_fold
  1677. myprint( "Last host1 folder going to $h2_fold\n" ) ;
  1678. foreach my $h2_msg ( keys %{ $uid_candidate_for_deletion{ $h2_fold } } ) {
  1679. $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion\n" ) ;
  1680. if ( exists $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg } ) {
  1681. $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg canceled deletion\n" ) ;
  1682. }else{
  1683. myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted $sync->{dry_message}\n" ) ;
  1684. push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 } ;
  1685. if ( ! $sync->{dry} ) {
  1686. $sync->{imap2}->delete_message( $h2_msg ) ;
  1687. $h2_nb_msg_deleted += 1 ;
  1688. }
  1689. }
  1690. }
  1691. }
  1692. my $cnt = scalar @h2_expunge ;
  1693. if( @h2_expunge and not $sync->{ expunge2 } ) {
  1694. myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n" ) ;
  1695. $sync->{imap2}->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ;
  1696. }
  1697. if ( $sync->{ expunge2 } ) {
  1698. myprint( "Host2: Expunging host2 folder $h2_fold $sync->{dry_message}\n" ) ;
  1699. $sync->{imap2}->expunge( ) if ! $sync->{dry} ;
  1700. }
  1701. $h2_folders_from_1_several{ $h2_fold }-- ;
  1702. }
  1703. my $h2_uidnext = $sync->{imap2}->uidnext( $h2_fold ) ;
  1704. $sync->{ debug } and myprint( "Host2: uidnext is $h2_uidnext\n" ) ;
  1705. $h2_uidguess = $h2_uidnext ;
  1706. # Getting host2 headers, metada and delete2 stuff can be so long that host1 might be disconnected here
  1707. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1708. my @h1_msgs_to_delete ;
  1709. MESS: foreach my $m_id (@h1_hash_keys_sorted_by_uid) {
  1710. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1711. #myprint( "h1_nb_msg_processed: $sync->{ h1_nb_msg_processed }\n" ) ;
  1712. my $h1_size = $h1_hash{$m_id}{'s'};
  1713. my $h1_msg = $h1_hash{$m_id}{'m'};
  1714. my $h1_idate = $h1_hash{$m_id}{'D'};
  1715. #my $labels = labels( $sync->{imap1}, $h1_msg ) ;
  1716. #print "LABELS: $labels\n" ;
  1717. if ( ( not exists $h2_hash{ $m_id } )
  1718. and ( not ( exists $sync->{ h2_folders_of_md5 }->{ $m_id } )
  1719. or not $skipcrossduplicates ) )
  1720. {
  1721. # copy
  1722. my $h2_msg = copy_message( $sync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) ;
  1723. if ( $h2_msg and $sync->{ delete1 } and not $sync->{ expungeaftereach } ) {
  1724. # not expunged
  1725. push @h1_msgs_to_delete, $h1_msg ;
  1726. }
  1727. # A bug here with imapsync 1.920, fixed in 1.921
  1728. # Added $h2_msg in the condition. Errors of APPEND were not counted as missing messages on host2!
  1729. if ( $h2_msg and not $sync->{ dry } )
  1730. {
  1731. $sync->{ h2_folders_of_md5 }->{ $m_id }->{ $h2_fold } ++ ;
  1732. }
  1733. #
  1734. if( $sync->{ delete2 } and ( exists $h2_folders_from_1_several{ $h2_fold } ) and $h2_msg ) {
  1735. myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [fresh copy] on host2\n" ) ;
  1736. $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
  1737. }
  1738. if ( total_bytes_max_reached( $sync ) ) {
  1739. # a bug when using --delete1 --noexpungeaftereach
  1740. # same thing below on all total_bytes_max_reached!
  1741. last FOLDER ;
  1742. }
  1743. next MESS;
  1744. }
  1745. else
  1746. {
  1747. # already on host2
  1748. if ( exists $h2_hash{ $m_id } )
  1749. {
  1750. my $h2_msg = $h2_hash{$m_id}{'m'} ;
  1751. $sync->{ debug } and myprint( "Host1: found that msg $h1_fold/$h1_msg equals Host2 $h2_fold/$h2_msg\n" ) ;
  1752. if ( $usecache )
  1753. {
  1754. $debugcache and myprint( "touch $cache_dir/${h1_msg}_$h2_msg\n" ) ;
  1755. touch( "$cache_dir/${h1_msg}_$h2_msg" )
  1756. or croak( "Couldn't touch $cache_dir/${h1_msg}_$h2_msg" ) ;
  1757. }
  1758. }
  1759. elsif( exists $sync->{ h2_folders_of_md5 }->{ $m_id } )
  1760. {
  1761. my @folders_dup = keys %{ $sync->{ h2_folders_of_md5 }->{ $m_id } } ;
  1762. ( $sync->{ debug } or $debugcrossduplicates ) and myprint( "Host1: found that msg $h1_fold/$h1_msg is also in Host2 folders @folders_dup\n" ) ;
  1763. $sync->{ h2_nb_msg_crossdup } +=1 ;
  1764. }
  1765. $sync->{ total_bytes_skipped } += $h1_size ;
  1766. $sync->{ nb_msg_skipped } += 1 ;
  1767. $sync->{ h1_nb_msg_processed } +=1 ;
  1768. }
  1769. if ( exists $h2_hash{ $m_id } ) {
  1770. #$debug and myprint( "MESSAGE $m_id\n" ) ;
  1771. my $h2_msg = $h2_hash{$m_id}{'m'};
  1772. if ( $sync->{resyncflags} ) {
  1773. sync_flags_fir( $sync, $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) ;
  1774. }
  1775. # Good
  1776. my $h2_size = $h2_hash{$m_id}{'s'};
  1777. $sync->{ debug } and myprint(
  1778. "Host1: size msg $h1_fold/$h1_msg = $h1_size <> $h2_size = Host2 $h2_fold/$h2_msg\n" ) ;
  1779. if ( $sync->{ resynclabels } )
  1780. {
  1781. resynclabels( $sync, $h1_msg, $h2_msg, $h1_fir_ref, $h2_fir_ref, $h1_fold )
  1782. }
  1783. }
  1784. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1785. if ( $sync->{ delete1 } ) {
  1786. push @h1_msgs_to_delete, $h1_msg ;
  1787. }
  1788. }
  1789. # END MESS: loop
  1790. delete_message_on_host1( $sync, $h1_fold, $sync->{ expunge1 }, @h1_msgs_to_delete, @h1_msgs_in_cache ) ;
  1791. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1792. # MESS_IN_CACHE:
  1793. if ( ! $sync->{ delete1 } )
  1794. {
  1795. foreach my $h1_msg ( @h1_msgs_in_cache )
  1796. {
  1797. my $h2_msg = $cache_1_2_ref->{ $h1_msg } ;
  1798. $debugcache and myprint( "cache messages update flags $h1_msg->$h2_msg\n" ) ;
  1799. if ( $sync->{resyncflags} )
  1800. {
  1801. sync_flags_fir( $sync, $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) ;
  1802. }
  1803. my $h1_size = $h1_fir_ref->{ $h1_msg }->{ 'RFC822.SIZE' } || 0 ;
  1804. $sync->{ total_bytes_skipped } += $h1_size;
  1805. $sync->{ nb_msg_skipped } += 1;
  1806. $sync->{ h1_nb_msg_processed } +=1 ;
  1807. }
  1808. }
  1809. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1810. @h1_msgs_to_delete = ( ) ;
  1811. #myprint( "Messages by uid: ", map { "$_ " } keys %h1_msgs_copy_by_uid, "\n" ) ;
  1812. # MESS_BY_UID:
  1813. foreach my $h1_msg ( sort { $a <=> $b } keys %h1_msgs_copy_by_uid )
  1814. {
  1815. $sync->{ debug } and myprint( "Copy by uid $h1_fold/$h1_msg\n" ) ;
  1816. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1817. my $h2_msg = copy_message( $sync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) ;
  1818. if( $sync->{ delete2 } and exists $h2_folders_from_1_several{ $h2_fold } and $h2_msg ) {
  1819. myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [fresh copy] on host2\n" ) ;
  1820. $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
  1821. }
  1822. last FOLDER if total_bytes_max_reached( $sync ) ;
  1823. }
  1824. if ( $sync->{ expunge1 } ){
  1825. myprint( "Host1: Expunging folder $h1_fold $sync->{dry_message}\n" ) ;
  1826. if ( ! $sync->{dry} ) { $sync->{imap1}->expunge( ) } ;
  1827. }
  1828. if ( $sync->{ expunge2 } ){
  1829. myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n" ) ;
  1830. if ( ! $sync->{dry} ) { $sync->{imap2}->expunge( ) } ;
  1831. }
  1832. $sync->{ debug } and myprint( 'Time: ', timenext( ), " s\n" ) ;
  1833. }
  1834. myprint( "++++ End looping on each folder\n" ) ;
  1835. if ( $sync->{ delete1 } and $sync->{ delete1emptyfolders } ) {
  1836. delete1emptyfolders( $sync ) ;
  1837. }
  1838. ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( 'Time: ', timenext( ), " s\n" ) ;
  1839. if ( $foldersizesatend ) {
  1840. myprint( << 'END_SIZE' ) ;
  1841. Folders sizes after the synchronization.
  1842. You can remove this foldersizes listing by using "--nofoldersizesatend"
  1843. END_SIZE
  1844. foldersizesatend( $sync ) ;
  1845. }
  1846. if ( ! lost_connection( $sync, $sync->{imap1}, "for host1 [$sync->{host1}]" ) ) { $sync->{imap1}->logout( ) ; }
  1847. if ( ! lost_connection( $sync, $sync->{imap2}, "for host2 [$sync->{host2}]" ) ) { $sync->{imap2}->logout( ) ; }
  1848. stats( $sync ) ;
  1849. myprint( errorsdump( $sync->{nb_errors}, errors_log( $sync ) ) ) if ( $sync->{errorsdump} ) ;
  1850. tests_live_result( $sync->{nb_errors} ) if ( $sync->{testslive} or $sync->{testslive6} ) ;
  1851. exit_clean( $sync, $EXIT_WITH_ERRORS ) if ( $sync->{nb_errors} ) ;
  1852. exit_clean( $sync, $EX_OK ) ;
  1853. # END of main program
  1854. # subroutines
  1855. sub myprint
  1856. {
  1857. #print @ARG ;
  1858. print { $sync->{ tee } || \*STDOUT } @ARG ;
  1859. return ;
  1860. }
  1861. sub myprintf
  1862. {
  1863. printf { $sync->{ tee } || \*STDOUT } @ARG ;
  1864. return ;
  1865. }
  1866. sub mysprintf
  1867. {
  1868. my( $format, @list ) = @ARG ;
  1869. return sprintf $format, @list ;
  1870. }
  1871. sub output_start
  1872. {
  1873. my $mysync = shift @ARG ;
  1874. if ( not $mysync ) { return ; }
  1875. my @output = @ARG ;
  1876. $mysync->{ output } = join( q{}, @output ) . ( $mysync->{ output } || q{} ) ;
  1877. return $mysync->{ output } ;
  1878. }
  1879. sub tests_output_start
  1880. {
  1881. note( 'Entering tests_output_start()' ) ;
  1882. my $mysync = { } ;
  1883. is( undef, output_start( ), 'output_start: no args => undef' ) ;
  1884. is( q{}, output_start( $mysync ), 'output_start: one arg => ""' ) ;
  1885. is( 'rrrr', output_start( $mysync, 'rrrr' ), 'output_start: rrrr => rrrr' ) ;
  1886. is( 'aaaarrrr', output_start( $mysync, 'aaaa' ), 'output_start: aaaa => aaaarrrr' ) ;
  1887. is( "\naaaarrrr", output_start( $mysync, "\n" ), 'output_start: \n => \naaaarrrr' ) ;
  1888. is( "ABC\naaaarrrr", output_start( $mysync, 'A', 'B', 'C' ), 'output_start: A B C => ABC\naaaarrrr' ) ;
  1889. note( 'Leaving tests_output_start()' ) ;
  1890. return ;
  1891. }
  1892. sub tests_output
  1893. {
  1894. note( 'Entering tests_output()' ) ;
  1895. my $mysync = { } ;
  1896. is( undef, output( ), 'output: no args => undef' ) ;
  1897. is( q{}, output( $mysync ), 'output: one arg => ""' ) ;
  1898. is( 'rrrr', output( $mysync, 'rrrr' ), 'output: rrrr => rrrr' ) ;
  1899. is( 'rrrraaaa', output( $mysync, 'aaaa' ), 'output: aaaa => rrrraaaa' ) ;
  1900. is( "rrrraaaa\n", output( $mysync, "\n" ), 'output: \n => rrrraaaa\n' ) ;
  1901. is( "rrrraaaa\nABC", output( $mysync, 'A', 'B', 'C' ), 'output: A B C => rrrraaaaABC\n' ) ;
  1902. note( 'Leaving tests_output()' ) ;
  1903. return ;
  1904. }
  1905. sub output
  1906. {
  1907. my $mysync = shift @ARG ;
  1908. if ( not $mysync ) { return ; }
  1909. my @output = @ARG ;
  1910. $mysync->{ output } .= join( q{}, @output ) ;
  1911. return $mysync->{ output } ;
  1912. }
  1913. sub tests_output_reset_with
  1914. {
  1915. note( 'Entering tests_output_reset_with()' ) ;
  1916. my $mysync = { } ;
  1917. is( undef, output_reset_with( ), 'output_reset_with: no args => undef' ) ;
  1918. is( q{}, output_reset_with( $mysync ), 'output_reset_with: one arg => ""' ) ;
  1919. is( 'rrrr', output_reset_with( $mysync, 'rrrr' ), 'output_reset_with: rrrr => rrrr' ) ;
  1920. is( 'aaaa', output_reset_with( $mysync, 'aaaa' ), 'output_reset_with: aaaa => aaaa' ) ;
  1921. is( "\n", output_reset_with( $mysync, "\n" ), 'output_reset_with: \n => \n' ) ;
  1922. note( 'Leaving tests_output_reset_with()' ) ;
  1923. return ;
  1924. }
  1925. sub output_reset_with
  1926. {
  1927. my $mysync = shift @ARG ;
  1928. if ( not $mysync ) { return ; }
  1929. my @output = @ARG ;
  1930. $mysync->{ output } = join( q{}, @output ) ;
  1931. return $mysync->{ output } ;
  1932. }
  1933. sub abort
  1934. {
  1935. my $mysync = shift @ARG ;
  1936. if ( ! -r $mysync->{pidfile} ) {
  1937. myprint( "Can not read pidfile $mysync->{pidfile}. Exiting.\n" ) ;
  1938. exit $EX_OK ;
  1939. }
  1940. my $pidtokill = firstline( $mysync->{pidfile} ) ;
  1941. if ( ! $pidtokill ) {
  1942. myprint( "No process to abort. Exiting.\n" ) ;
  1943. exit $EX_OK ;
  1944. }
  1945. # First ask for suicide
  1946. if ( kill 'ZERO', $pidtokill ) {
  1947. myprint( "Sending signal QUIT to PID $pidtokill \n" ) ;
  1948. kill 'QUIT', $pidtokill ;
  1949. sleep 1 ;
  1950. }else{
  1951. myprint( "Can not send signal to PID $pidtokill. Exiting.\n" ) ;
  1952. exit $EX_OK ;
  1953. }
  1954. # Then murder
  1955. if ( kill 'ZERO', $pidtokill ) {
  1956. myprint( "Sending signal KILL to PID $pidtokill \n" ) ;
  1957. kill 'KILL', $pidtokill ;
  1958. sleep 1 ;
  1959. }else{
  1960. myprint( "Process PID $pidtokill ended. Exiting.\n" ) ;
  1961. exit $EX_OK ;
  1962. }
  1963. # Well ...
  1964. if ( kill 'ZERO', $pidtokill ) {
  1965. myprint( "Process PID $pidtokill still there. Can not do much. Exiting.\n" ) ;
  1966. exit $EX_OK ;
  1967. }else{
  1968. myprint( "Process PID $pidtokill ended. Exiting.\n" ) ;
  1969. exit $EX_OK ;
  1970. }
  1971. # well abort job done anyway
  1972. exit $EX_OK ;
  1973. }
  1974. sub docker_context
  1975. {
  1976. my $mysync = shift ;
  1977. -e '/.dockerenv' || return ;
  1978. myprint( "Docker context detected with /.dockerenv\n" ) ;
  1979. # No pidfile
  1980. $mysync->{pidfile} = q{} ;
  1981. # No log
  1982. $mysync->{log} = 0 ;
  1983. # In case
  1984. myprint( "Changing current directory to /var/tmp/\n" ) ;
  1985. chdir '/var/tmp/' ;
  1986. return ;
  1987. }
  1988. sub cgibegin
  1989. {
  1990. my $mysync = shift ;
  1991. if ( ! under_cgi_context( $mysync ) ) { return ; }
  1992. require CGI ;
  1993. CGI->import( qw( -no_debug ) ) ;
  1994. require CGI::Carp ;
  1995. CGI::Carp->import( qw( fatalsToBrowser ) ) ;
  1996. $mysync->{cgi} = CGI->new( ) ;
  1997. return ;
  1998. }
  1999. sub tests_under_cgi_context
  2000. {
  2001. note( 'Entering tests_under_cgi_context()' ) ;
  2002. # $ENV{SERVER_SOFTWARE} = 'under imapsync' ;
  2003. do {
  2004. # Not in cgi context
  2005. delete local $ENV{SERVER_SOFTWARE} ;
  2006. is( undef, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE unset => not in cgi context' ) ;
  2007. } ;
  2008. do {
  2009. # In cgi context
  2010. local $ENV{SERVER_SOFTWARE} = 'under imapsync' ;
  2011. is( 1, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE set => in cgi context' ) ;
  2012. } ;
  2013. do {
  2014. # Not in cgi context
  2015. delete local $ENV{SERVER_SOFTWARE} ;
  2016. is( undef, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE unset => not in cgi context' ) ;
  2017. } ;
  2018. do {
  2019. # In cgi context
  2020. local $ENV{SERVER_SOFTWARE} = 'under imapsync' ;
  2021. is( 1, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE set => in cgi context' ) ;
  2022. } ;
  2023. note( 'Leaving tests_under_cgi_context()' ) ;
  2024. return ;
  2025. }
  2026. sub under_cgi_context
  2027. {
  2028. my $mysync = shift ;
  2029. # Under cgi context
  2030. if ( $ENV{SERVER_SOFTWARE} ) {
  2031. return 1 ;
  2032. }
  2033. # Not in cgi context
  2034. return ;
  2035. }
  2036. sub cgibuildheader
  2037. {
  2038. my $mysync = shift ;
  2039. if ( ! under_cgi_context( $mysync ) ) { return ; }
  2040. my $imapsync_runs = $mysync->{cgi}->cookie( 'imapsync_runs' ) || 0 ;
  2041. my $cookie = $mysync->{cgi}->cookie(
  2042. -name => 'imapsync_runs',
  2043. -value => 1 + $imapsync_runs,
  2044. -expires => '+20y',
  2045. -path => '/cgi-bin/imapsync',
  2046. ) ;
  2047. my $httpheader ;
  2048. if ( $mysync->{ abort } ) {
  2049. $httpheader = $mysync->{cgi}->header(
  2050. -type => 'text/plain',
  2051. -status => '200 OK to abort syncing IMAP boxes' . ". Here is " . hostname(),
  2052. ) ;
  2053. }elsif( $mysync->{ loaddelay } ) {
  2054. # https://tools.ietf.org/html/rfc2616#section-10.5.4
  2055. # 503 Service Unavailable
  2056. # The server is currently unable to handle the request due to a temporary overloading or maintenance of the server.
  2057. $httpheader = $mysync->{cgi}->header(
  2058. -type => 'text/plain',
  2059. -status => '503 Service Unavailable' . ". Be back in $mysync->{ loaddelay } min. Load on " . hostname() . " is $mysync->{ loadavg }",
  2060. ) ;
  2061. }else{
  2062. $httpheader = $mysync->{cgi}->header(
  2063. -type => 'text/plain',
  2064. -status => '200 OK to sync IMAP boxes' . ". Load on " . hostname() . " is $mysync->{ loadavg }",
  2065. -cookie => $cookie,
  2066. ) ;
  2067. }
  2068. output_start( $mysync, $httpheader ) ;
  2069. return ;
  2070. }
  2071. sub cgiload
  2072. {
  2073. # Exit on heavy load in CGI context
  2074. my $mysync = shift ;
  2075. if ( ! under_cgi_context( $mysync ) ) { return ; }
  2076. if ( $mysync->{ abort } ) { return ; } # keep going to abort since some ressources will be free soon
  2077. if ( $mysync->{ loaddelay } )
  2078. {
  2079. myprint( "Server is on heavy load. Be back in $mysync->{ loaddelay } min. Load is $mysync->{ loadavg }\n") ;
  2080. exit_clean( $mysync, $EX_UNAVAILABLE ) ;
  2081. }
  2082. return ;
  2083. }
  2084. sub tests_set_umask
  2085. {
  2086. note( 'Entering tests_set_umask()' ) ;
  2087. my $save_umask = umask ;
  2088. my $mysync = {} ;
  2089. if ( 'MSWin32' eq $OSNAME ) {
  2090. is( undef, set_umask( $mysync ), "set_umask: set failure to $UMASK_PARANO on MSWin32" ) ;
  2091. }else{
  2092. is( 1, set_umask( $mysync ), "set_umask: set to $UMASK_PARANO" ) ;
  2093. }
  2094. umask $save_umask ;
  2095. note( 'Leaving tests_set_umask()' ) ;
  2096. return ;
  2097. }
  2098. sub set_umask
  2099. {
  2100. my $mysync = shift ;
  2101. my $previous_umask = umask_str( ) ;
  2102. my $new_umask = umask_str( $UMASK_PARANO ) ;
  2103. output( $mysync, "Umask set with $new_umask (was $previous_umask)\n" ) ;
  2104. if ( $new_umask eq $UMASK_PARANO ) {
  2105. return 1 ;
  2106. }
  2107. return ;
  2108. }
  2109. sub tests_umask_str
  2110. {
  2111. note( 'Entering tests_umask_str()' ) ;
  2112. my $save_umask = umask ;
  2113. is( umask_str( ), umask_str( ), 'umask_str: no parameters => idopotent' ) ;
  2114. is( my $save_umask_str = umask_str( ), umask_str( ), 'umask_str: no parameters => idopotent + save' ) ;
  2115. is( '0000', umask_str( q{ } ), 'umask_str: q{ } => 0000' ) ;
  2116. is( '0000', umask_str( q{} ), 'umask_str: q{} => 0000' ) ;
  2117. is( '0000', umask_str( '0000' ), 'umask_str: 0000 => 0000' ) ;
  2118. is( '0000', umask_str( '0' ), 'umask_str: 0 => 0000' ) ;
  2119. is( '0200', umask_str( '0200' ), 'umask_str: 0200 => 0200' ) ;
  2120. is( '0400', umask_str( '0400' ), 'umask_str: 0400 => 0400' ) ;
  2121. is( '0600', umask_str( '0600' ), 'umask_str: 0600 => 0600' ) ;
  2122. SKIP: {
  2123. if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests success only for Unix', 6 ) ; }
  2124. is( '0100', umask_str( '0100' ), 'umask_str: 0100 => 0100' ) ;
  2125. is( '0001', umask_str( '0001' ), 'umask_str: 0001 => 0001' ) ;
  2126. is( '0777', umask_str( '0777' ), 'umask_str: 0777 => 0777' ) ;
  2127. is( '0777', umask_str( '00777' ), 'umask_str: 00777 => 0777' ) ;
  2128. is( '0777', umask_str( ' 777 ' ), 'umask_str: 777 => 0777' ) ;
  2129. is( "$UMASK_PARANO", umask_str( $UMASK_PARANO ), "umask_str: UMASK_PARANO $UMASK_PARANO => $UMASK_PARANO" ) ;
  2130. }
  2131. is( $save_umask_str, umask_str( $save_umask_str ), 'umask_str: restore with str' ) ;
  2132. is( $save_umask, umask, 'umask_str: umask is restored, controlled by direct umask' ) ;
  2133. is( $save_umask, umask $save_umask, 'umask_str: umask is restored by direct umask' ) ;
  2134. is( $save_umask, umask, 'umask_str: umask initial controlled by direct umask' ) ;
  2135. note( 'Leaving tests_umask_str()' ) ;
  2136. return ;
  2137. }
  2138. sub umask_str
  2139. {
  2140. my $value = shift ;
  2141. if ( defined $value ) {
  2142. umask oct( $value ) ;
  2143. }
  2144. my $current = umask ;
  2145. return( sprintf( '%#04o', $current ) ) ;
  2146. }
  2147. sub tests_umask
  2148. {
  2149. note( 'Entering tests_umask()' ) ;
  2150. my $save_umask ;
  2151. is( umask, umask, 'umask: umask is umask' ) ;
  2152. is( $save_umask = umask, umask, "umask: umask is umask again + save it: $save_umask" ) ;
  2153. is( $save_umask, umask oct(0000), 'umask: umask 0000' ) ;
  2154. is( oct(0000), umask, 'umask: umask is now 0000' ) ;
  2155. is( oct(0000), umask oct(777), 'umask: umask 0777 call, previous 0000' ) ;
  2156. SKIP: {
  2157. if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests success only for Unix', 2 ) ; }
  2158. is( oct(777), umask, 'umask: umask is now 0777' ) ;
  2159. is( oct(777), umask $save_umask, "umask: umask $save_umask restore inital value, previous 0777" ) ;
  2160. }
  2161. ok( defined umask $save_umask, "umask: umask $save_umask restore inital value, previous defined" ) ;
  2162. is( $save_umask, umask, 'umask: umask is umask restored' ) ;
  2163. note( 'Leaving tests_umask()' ) ;
  2164. return ;
  2165. }
  2166. sub cgisetcontext
  2167. {
  2168. my $mysync = shift ;
  2169. if ( ! under_cgi_context( $mysync ) ) { return ; }
  2170. output( $mysync, "Under cgi context\n" ) ;
  2171. set_umask( $mysync ) ;
  2172. # Remove all content in unsafe evaled options
  2173. @{ $mysync->{ regextrans2 } } = ( ) ;
  2174. @regexflag = ( ) ;
  2175. @regexmess = ( ) ;
  2176. @skipmess = ( ) ;
  2177. @pipemess = ( ) ;
  2178. $delete2foldersonly = undef ;
  2179. $delete2foldersbutnot = undef ;
  2180. $maxlinelengthcmd = undef ;
  2181. # Set safe default values (I hope...)
  2182. $mysync->{pidfile} = 'imapsync.pid' ;
  2183. $mysync->{pidfilelocking} = 1 ;
  2184. $mysync->{errorsmax} = $ERRORS_MAX_CGI ;
  2185. $modulesversion = 0 ;
  2186. $mysync->{releasecheck} = defined $mysync->{releasecheck} ? $mysync->{releasecheck} : 1 ;
  2187. $usecache = 0 ;
  2188. $mysync->{showpasswords} = 0 ;
  2189. $debugimap1 = $debugimap2 = $debugimap = 0 ;
  2190. $reconnectretry1 = $reconnectretry2 = $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ;
  2191. $pipemesscheck = 0 ;
  2192. $mysync->{hashfile} = $CGI_HASHFILE ;
  2193. my $hashsynclocal = hashsynclocal( $mysync ) || die "Can not get hashsynclocal. Exiting\n" ;
  2194. $cgidir = $CGI_TMPDIR_TOP . '/' . $hashsynclocal ;
  2195. -d $cgidir or mkpath $cgidir or die "Can not create $cgidir: $OS_ERROR\n" ;
  2196. chdir $cgidir or die "Can not cd to $cgidir: $OS_ERROR\n" ;
  2197. $mysync->{ tmpdir } = $cgidir ;
  2198. cgioutputenvcontext( $mysync ) ;
  2199. $mysync->{ debug } and output( $mysync, 'Current directory is ' . getcwd( ) . "\n" ) ;
  2200. $mysync->{ debug } and output( $mysync, 'Real user id is ' . getpwuid_any_os( $REAL_USER_ID ) . " (uid $REAL_USER_ID)\n" ) ;
  2201. $mysync->{ debug } and output( $mysync, 'Effective user id is ' . getpwuid_any_os( $EFFECTIVE_USER_ID ). " (euid $EFFECTIVE_USER_ID)\n" ) ;
  2202. $skipemptyfolders = defined $skipemptyfolders ? $skipemptyfolders : 1 ;
  2203. # Out of memory with messages over 1 GB ?
  2204. $mysync->{ maxsize } = defined $mysync->{ maxsize } ? $mysync->{ maxsize } : 1_000_000_000 ;
  2205. # tail -f behaviour on by default
  2206. $mysync->{ tail } = defined $mysync->{ tail } ? $mysync->{ tail } : 1 ;
  2207. # not sure it's for good
  2208. @useheader = qw( Message-Id ) ;
  2209. return ;
  2210. }
  2211. sub cgioutputenvcontext
  2212. {
  2213. my $mysync = shift @ARG ;
  2214. for my $envvar ( qw( REMOTE_ADDR REMOTE_HOST HTTP_REFERER HTTP_USER_AGENT SERVER_SOFTWARE SERVER_PORT HTTP_COOKIE ) ) {
  2215. my $envval = $ENV{ $envvar } || q{} ;
  2216. if ( $envval ) { output( $mysync, "$envvar is $envval\n" ) } ;
  2217. }
  2218. return ;
  2219. }
  2220. sub debugsleep
  2221. {
  2222. my $mysync = shift @ARG ;
  2223. if ( defined $mysync->{debugsleep} ) {
  2224. myprint( "Info: sleeping $mysync->{debugsleep}s\n" ) ;
  2225. sleep $mysync->{debugsleep} ;
  2226. }
  2227. return ;
  2228. }
  2229. sub foldersizes_on_h1h2
  2230. {
  2231. my $mysync = shift ;
  2232. myprint( << 'END_SIZE' ) ;
  2233. Folders sizes before the synchronization.
  2234. You can remove foldersizes listings by using "--nofoldersizes" and "--nofoldersizesatend"
  2235. but then you will also lose the ETA (Estimation Time of Arrival) given after each message copy.
  2236. END_SIZE
  2237. ( $h1_nb_msg_start, $h1_bytes_start ) = foldersizes( 'Host1', $mysync->{imap1}, $search1, $mysync->{abletosearch1}, @h1_folders_wanted ) ;
  2238. ( $h2_nb_msg_start, $h2_bytes_start ) = foldersizes( 'Host2', $mysync->{imap2}, $search2, $mysync->{abletosearch2}, @h2_folders_from_1_wanted ) ;
  2239. if ( not all_defined( $h1_nb_msg_start, $h1_bytes_start, $h2_nb_msg_start, $h2_bytes_start ) ) {
  2240. my $error = "Failure getting foldersizes, ETA and final diff will not be displayed\n" ;
  2241. errors_incr( $mysync, $error ) ;
  2242. $foldersizes = 0 ;
  2243. $foldersizesatend = 0 ;
  2244. return ;
  2245. }
  2246. my $h2_bytes_limit = $mysync->{h2}->{quota_limit_bytes} || 0 ;
  2247. if ( $h2_bytes_limit and ( $h2_bytes_limit < $h1_bytes_start ) ) {
  2248. my $quota_percent = mysprintf( '%.0f', $NUMBER_100 * $h1_bytes_start / $h2_bytes_limit ) ;
  2249. my $error = "Host2: Quota limit will be exceeded! Over $quota_percent % ( $h1_bytes_start bytes / $h2_bytes_limit bytes )\n" ;
  2250. errors_incr( $mysync, $error ) ;
  2251. }
  2252. return ;
  2253. }
  2254. sub total_bytes_max_reached
  2255. {
  2256. my $mysync = shift ;
  2257. if ( ! $mysync->{ exitwhenover } ) {
  2258. return( 0 ) ;
  2259. }
  2260. if ( $mysync->{ total_bytes_transferred } >= $mysync->{ exitwhenover } ) {
  2261. myprint( "Maximum bytes transferred reached, $mysync->{total_bytes_transferred} >= $mysync->{ exitwhenover }, ending sync\n" ) ;
  2262. return( 1 ) ;
  2263. }
  2264. return ;
  2265. }
  2266. sub tests_mock_capability
  2267. {
  2268. note( 'Entering tests_mock_capability()' ) ;
  2269. my $myimap ;
  2270. ok( $myimap = mock_capability( ),
  2271. 'mock_capability: (1) no args => a Test::MockObject'
  2272. ) ;
  2273. ok( $myimap->isa( 'Test::MockObject' ),
  2274. 'mock_capability: (2) no args => a Test::MockObject'
  2275. ) ;
  2276. is( undef, $myimap->capability( ),
  2277. 'mock_capability: (3) no args => capability undef'
  2278. ) ;
  2279. ok( mock_capability( $myimap ),
  2280. 'mock_capability: (1) one arg => MockObject'
  2281. ) ;
  2282. is( undef, $myimap->capability( ),
  2283. 'mock_capability: (2) one arg OO style => capability undef'
  2284. ) ;
  2285. ok( mock_capability( $myimap, $NUMBER_123456 ),
  2286. 'mock_capability: (1) two args 123456 => capability 123456'
  2287. ) ;
  2288. is( $NUMBER_123456, $myimap->capability( ),
  2289. 'mock_capability: (2) two args 123456 => capability 123456'
  2290. ) ;
  2291. ok( mock_capability( $myimap, 'ABCD' ),
  2292. 'mock_capability: (1) two args ABCD => capability ABCD'
  2293. ) ;
  2294. is( 'ABCD', $myimap->capability( ),
  2295. 'mock_capability: (2) two args ABCD => capability ABCD'
  2296. ) ;
  2297. ok( mock_capability( $myimap, [ 'ABCD' ] ),
  2298. 'mock_capability: (1) two args [ ABCD ] => capability [ ABCD ]'
  2299. ) ;
  2300. is_deeply( [ 'ABCD' ], $myimap->capability( ),
  2301. 'mock_capability: (2) two args [ ABCD ] => capability [ ABCD ]'
  2302. ) ;
  2303. ok( mock_capability( $myimap, [ 'ABC', 'DEF' ] ),
  2304. 'mock_capability: (1) two args [ ABC, DEF ] => capability [ ABC, DEF ]'
  2305. ) ;
  2306. is_deeply( [ 'ABC', 'DEF' ], $myimap->capability( ),
  2307. 'mock_capability: (2) two args [ ABC, DEF ] => capability capability [ ABC, DEF ]'
  2308. ) ;
  2309. ok( mock_capability( $myimap, 'ABC', 'DEF' ),
  2310. 'mock_capability: (1) two args ABC, DEF => capability [ ABC, DEF ]'
  2311. ) ;
  2312. is_deeply( [ 'ABC', 'DEF' ], [ $myimap->capability( ) ],
  2313. 'mock_capability: (2) two args ABC, DEF => capability capability [ ABC, DEF ]'
  2314. ) ;
  2315. ok( mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ),
  2316. 'mock_capability: (1) two args IMAP4rev1, APPENDLIMIT=123456 => capability [ IMAP4rev1, APPENDLIMIT=123456 ]'
  2317. ) ;
  2318. is_deeply( [ 'IMAP4rev1', 'APPENDLIMIT=123456' ], [ $myimap->capability( ) ],
  2319. 'mock_capability: (2) two args IMAP4rev1, APPENDLIMIT=123456 => capability capability [ IMAP4rev1, APPENDLIMIT=123456 ]'
  2320. ) ;
  2321. note( 'Leaving tests_mock_capability()' ) ;
  2322. return ;
  2323. }
  2324. sub sig_install_toggle_sleep
  2325. {
  2326. my $mysync = shift ;
  2327. if ( ! 'MSWin32' eq $OSNAME ) {
  2328. sig_install( $mysync, \&toggle_sleep, 'USR1' )
  2329. }
  2330. return ;
  2331. }
  2332. sub mock_capability
  2333. {
  2334. my $myimap = shift ;
  2335. my @has_capability_value = @ARG ;
  2336. my ( $has_capability_value ) = @has_capability_value ;
  2337. if ( ! $myimap )
  2338. {
  2339. require_ok( "Test::MockObject" ) ;
  2340. $myimap = Test::MockObject->new( ) ;
  2341. }
  2342. $myimap->mock(
  2343. 'capability',
  2344. sub { return wantarray ?
  2345. @has_capability_value
  2346. : $has_capability_value ;
  2347. }
  2348. ) ;
  2349. return $myimap ;
  2350. }
  2351. sub tests_capability_of
  2352. {
  2353. note( 'Entering tests_capability_of()' ) ;
  2354. is( undef, capability_of( ),
  2355. 'capability_of: no args => undef' ) ;
  2356. my $myimap ;
  2357. is( undef, capability_of( $myimap ),
  2358. 'capability_of: undef => undef' ) ;
  2359. $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ;
  2360. is( undef, capability_of( $myimap, 'CACA' ),
  2361. 'capability_of: two args unknown capability => undef' ) ;
  2362. is( $NUMBER_123456, capability_of( $myimap, 'APPENDLIMIT' ),
  2363. 'capability_of: two args APPENDLIMIT 123456 => 123456 yeah!' ) ;
  2364. note( 'Leaving tests_capability_of()' ) ;
  2365. return ;
  2366. }
  2367. sub capability_of
  2368. {
  2369. my $imap = shift || return ;
  2370. my $capability_keyword = shift || return ;
  2371. my @capability = $imap->capability ;
  2372. if ( ! @capability ) { return ; }
  2373. my $capability_value = search_in_array( $capability_keyword, @capability ) ;
  2374. return $capability_value ;
  2375. }
  2376. sub tests_search_in_array
  2377. {
  2378. note( 'Entering tests_search_in_array()' ) ;
  2379. is( undef, search_in_array( 'KA' ),
  2380. 'search_in_array: no array => undef ' ) ;
  2381. is( 'VA', search_in_array( 'KA', ( 'KA=VA' ) ),
  2382. 'search_in_array: KA KA=VA => VA ' ) ;
  2383. is( 'VA', search_in_array( 'KA', ( 'KA=VA', 'KB=VB' ) ),
  2384. 'search_in_array: KA KA=VA KB=VB => VA ' ) ;
  2385. is( 'VB', search_in_array( 'KB', ( 'KA=VA', 'KB=VB' ) ),
  2386. 'search_in_array: KA=VA KB=VB => VB ' ) ;
  2387. note( 'Leaving tests_search_in_array()' ) ;
  2388. return ;
  2389. }
  2390. sub search_in_array
  2391. {
  2392. my ( $key, @array ) = @ARG ;
  2393. foreach my $item ( @array )
  2394. {
  2395. if ( $item =~ /([^=]+)=(.*)/ )
  2396. {
  2397. if ( $1 eq $key )
  2398. {
  2399. return $2 ;
  2400. }
  2401. }
  2402. }
  2403. return ;
  2404. }
  2405. sub tests_appendlimit_from_capability
  2406. {
  2407. note( 'Entering tests_appendlimit_from_capability()' ) ;
  2408. is( undef, appendlimit_from_capability( ),
  2409. 'appendlimit_from_capability: no args => undef'
  2410. ) ;
  2411. my $myimap ;
  2412. is( undef, appendlimit_from_capability( $myimap ),
  2413. 'appendlimit_from_capability: undef arg => undef'
  2414. ) ;
  2415. $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ;
  2416. # Normal behavior
  2417. is( $NUMBER_123456, appendlimit_from_capability( $myimap ),
  2418. 'appendlimit_from_capability: APPENDLIMIT=123456 => 123456'
  2419. ) ;
  2420. # Not a number
  2421. $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=ABC' ) ;
  2422. is( undef, appendlimit_from_capability( $myimap ),
  2423. 'appendlimit_from_capability: not a number => undef'
  2424. ) ;
  2425. note( 'Leaving tests_appendlimit_from_capability()' ) ;
  2426. return ;
  2427. }
  2428. sub appendlimit_from_capability
  2429. {
  2430. my $myimap = shift ;
  2431. if ( ! $myimap )
  2432. {
  2433. myprint( "Warn: no imap with call to appendlimit_from_capability\n" ) ;
  2434. return ;
  2435. }
  2436. #myprint( Data::Dumper->Dump( [ \$myimap ] ) ) ;
  2437. my $appendlimit = capability_of( $myimap, 'APPENDLIMIT' ) ;
  2438. #myprint( "has_capability APPENDLIMIT $appendlimit\n" ) ;
  2439. if ( is_an_integer( $appendlimit ) )
  2440. {
  2441. return $appendlimit ;
  2442. }
  2443. return ;
  2444. }
  2445. sub tests_appendlimit
  2446. {
  2447. note( 'Entering tests_appendlimit()' ) ;
  2448. is( undef, appendlimit( ),
  2449. 'appendlimit: no args => undef'
  2450. ) ;
  2451. my $mysync = { } ;
  2452. is( undef, appendlimit( $mysync ),
  2453. 'appendlimit: no imap2 => undef'
  2454. ) ;
  2455. my $myimap ;
  2456. $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ;
  2457. $mysync->{ imap2 } = $myimap ;
  2458. is( 123456, appendlimit( $mysync ),
  2459. 'appendlimit: imap2 with APPENDLIMIT=123456 => 123456'
  2460. ) ;
  2461. note( 'Leaving tests_appendlimit()' ) ;
  2462. return ;
  2463. }
  2464. sub appendlimit
  2465. {
  2466. my $mysync = shift || return ;
  2467. my $myimap = $mysync->{ imap2 } ;
  2468. my $appendlimit = appendlimit_from_capability( $myimap ) ;
  2469. if ( defined $appendlimit )
  2470. {
  2471. myprint( "Host2: found APPENDLIMIT=$appendlimit in CAPABILITY\n" ) ;
  2472. return $appendlimit ;
  2473. }
  2474. return ;
  2475. }
  2476. sub tests_maxsize_setting
  2477. {
  2478. note( 'Entering tests_maxsize_setting()' ) ;
  2479. is( undef, maxsize_setting( ),
  2480. 'maxsize_setting: no args => undef'
  2481. ) ;
  2482. my $mysync ;
  2483. is( undef, maxsize_setting( $mysync ),
  2484. 'maxsize_setting: undef arg => undef'
  2485. ) ;
  2486. $mysync = { } ;
  2487. $mysync->{ maxsize } = $NUMBER_123456 ;
  2488. is( $NUMBER_123456, maxsize_setting( $mysync ),
  2489. 'maxsize_setting: --maxsize 123456 alone => 123456'
  2490. ) ;
  2491. $mysync = { } ;
  2492. my $myimap ;
  2493. $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=654321' ) ;
  2494. $mysync->{ imap2 } = $myimap ;
  2495. # APPENDLIMIT alone
  2496. is( $NUMBER_654321, maxsize_setting( $mysync ),
  2497. 'maxsize_setting: APPENDLIMIT 654321 alone => 654321'
  2498. ) ;
  2499. is( $NUMBER_654321, $mysync->{ maxsize },
  2500. 'maxsize_setting: APPENDLIMIT 654321 alone => maxsize 654321'
  2501. ) ;
  2502. # Case: "APPENDLIMIT >= --maxsize" => maxsize.
  2503. $mysync->{ maxsize } = $NUMBER_123456 ;
  2504. is( $NUMBER_123456, maxsize_setting( $mysync ),
  2505. 'maxsize_setting: APPENDLIMIT 654321 --maxsize 123456 => 123456'
  2506. ) ;
  2507. # Case: "APPENDLIMIT < --maxsize" => APPENDLIMIT.
  2508. $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ;
  2509. $mysync->{ maxsize } = $NUMBER_654321 ;
  2510. is( $NUMBER_123456, maxsize_setting( $mysync ),
  2511. 'maxsize_setting: APPENDLIMIT 123456 --maxsize 654321 => 123456 '
  2512. ) ;
  2513. note( 'Leaving tests_maxsize_setting()' ) ;
  2514. return ;
  2515. }
  2516. sub maxsize_setting
  2517. {
  2518. my $mysync = shift || return ;
  2519. $mysync->{ appendlimit } = appendlimit( $mysync ) ;
  2520. my $maxsize ;
  2521. if ( all_defined( $mysync->{ appendlimit }, $mysync->{ maxsize } ) )
  2522. {
  2523. return min( $mysync->{ maxsize }, $mysync->{ appendlimit } ) ;
  2524. }
  2525. elsif ( defined $mysync->{ appendlimit } )
  2526. {
  2527. $mysync->{ maxsize } = $mysync->{ appendlimit } ;
  2528. return $mysync->{ maxsize } ;
  2529. }elsif ( defined $mysync->{ maxsize } )
  2530. {
  2531. return $mysync->{ maxsize } ;
  2532. }else
  2533. {
  2534. return ;
  2535. }
  2536. }
  2537. sub all_defined
  2538. {
  2539. if ( not @ARG ) {
  2540. return 0 ;
  2541. }
  2542. foreach my $elem ( @ARG ) {
  2543. if ( not defined $elem ) {
  2544. return 0 ;
  2545. }
  2546. }
  2547. return 1 ;
  2548. }
  2549. sub tests_all_defined
  2550. {
  2551. note( 'Entering tests_all_defined()' ) ;
  2552. is( 0, all_defined( ), 'all_defined: no param => 0' ) ;
  2553. is( 0, all_defined( () ), 'all_defined: void list => 0' ) ;
  2554. is( 0, all_defined( undef ), 'all_defined: undef => 0' ) ;
  2555. is( 0, all_defined( undef, undef ), 'all_defined: undef => 0' ) ;
  2556. is( 0, all_defined( 1, undef ), 'all_defined: 1 undef => 0' ) ;
  2557. is( 0, all_defined( undef, 1 ), 'all_defined: undef 1 => 0' ) ;
  2558. is( 1, all_defined( 1, 1 ), 'all_defined: 1 1 => 1' ) ;
  2559. is( 1, all_defined( (1, 1) ), 'all_defined: (1 1) => 1' ) ;
  2560. note( 'Leaving tests_all_defined()' ) ;
  2561. return ;
  2562. }
  2563. sub tests_hashsynclocal
  2564. {
  2565. note( 'Entering tests_hashsynclocal()' ) ;
  2566. my $mysync = {
  2567. host1 => '',
  2568. user1 => '',
  2569. password1 => '',
  2570. host2 => '',
  2571. user2 => '',
  2572. password2 => '',
  2573. } ;
  2574. is( undef, hashsynclocal( $mysync ), 'hashsynclocal: no hashfile name' ) ;
  2575. $mysync->{ hashfile } = '' ;
  2576. is( undef, hashsynclocal( $mysync ), 'hashsynclocal: empty hashfile name' ) ;
  2577. $mysync->{ hashfile } = './noexist/rrr' ;
  2578. is( undef, hashsynclocal( $mysync ), 'hashsynclocal: no exists hashfile dir' ) ;
  2579. SKIP: {
  2580. if ( 'MSWin32' eq $OSNAME or '0' eq $EFFECTIVE_USER_ID ) { skip( 'Tests only for non-root Unix', 1 ) ; }
  2581. $mysync->{ hashfile } = '/rrr' ;
  2582. is( undef, hashsynclocal( $mysync ), 'hashsynclocal: permission denied' ) ;
  2583. }
  2584. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'hashsynclocal: mkpath W/tmp/tests/' ) ;
  2585. $mysync->{ hashfile } = 'W/tmp/tests/imapsync_hash' ;
  2586. ok( ! -e 'W/tmp/tests/imapsync_hash' || unlink 'W/tmp/tests/imapsync_hash', 'hashsynclocal: unlink W/tmp/tests/imapsync_hash' ) ;
  2587. ok( ! -e 'W/tmp/tests/imapsync_hash', 'hashsynclocal: verify there is no W/tmp/tests/imapsync_hash' ) ;
  2588. is( 'ecdeb4ede672794d173da4e08c52b8ee19b7d252', hashsynclocal( $mysync, 'mukksyhpmbixkxkpjlqivmlqsulpictj' ), 'hashsynclocal: creating/reading W/tmp/tests/imapsync_hash' ) ;
  2589. # A second time now
  2590. is( 'ecdeb4ede672794d173da4e08c52b8ee19b7d252', hashsynclocal( $mysync ), 'hashsynclocal: reading W/tmp/tests/imapsync_hash second time => same' ) ;
  2591. note( 'Leaving tests_hashsynclocal()' ) ;
  2592. return ;
  2593. }
  2594. sub hashsynclocal
  2595. {
  2596. my $mysync = shift ;
  2597. my $hashkey = shift ; # Optional, only there for tests
  2598. my $hashfile = $mysync->{ hashfile } ;
  2599. $hashfile = createhashfileifneeded( $hashfile, $hashkey ) ;
  2600. if ( ! $hashfile ) {
  2601. return ;
  2602. }
  2603. $hashkey = firstline( $hashfile ) ;
  2604. if ( ! $hashkey ) {
  2605. myprint( "No hashkey!\n" ) ;
  2606. return ;
  2607. }
  2608. my $hashsynclocal = hashsync( $mysync, $hashkey ) ;
  2609. return( $hashsynclocal ) ;
  2610. }
  2611. sub tests_hashsync
  2612. {
  2613. note( 'Entering tests_hashsync()' ) ;
  2614. is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hashsync( {}, q{} ), 'hashsync: empty args' ) ;
  2615. my $mysync ;
  2616. $mysync->{ host1 } = 'zzz' ;
  2617. is( 'e86a28a3611c1e7bbaf8057cd00ae122781a11fe', hashsync( $mysync, q{} ), 'hashsync: host1 zzz => ' ) ;
  2618. is( 'e86a28a3611c1e7bbaf8057cd00ae122781a11fe', hashsync( $mysync, q{} ), 'hashsync: host1 zzz => ' ) ;
  2619. $mysync->{ host2 } = 'zzz' ;
  2620. is( '15959573e4a86763253a7aedb1a2b0c60d133dc2', hashsync( $mysync, q{} ), 'hashsync: + host2 zzz => ' ) ;
  2621. is( 'b8d4ab541b209c75928528020ca28ee43488bd8f', hashsync( $mysync, 'A' ), 'hashsync: + hashkey A => ' ) ;
  2622. note( 'Leaving tests_hashsync()' ) ;
  2623. return ;
  2624. }
  2625. sub hashsync
  2626. {
  2627. my $mysync = shift ;
  2628. my $hashkey = shift ;
  2629. my $mystring = join( q{},
  2630. $mysync->{ host1 } || q{},
  2631. $mysync->{ user1 } || q{},
  2632. $mysync->{ password1 } || q{},
  2633. $mysync->{ host2 } || q{},
  2634. $mysync->{ user2 } || q{},
  2635. $mysync->{ password2 } || q{},
  2636. ) ;
  2637. my $hashsync = hmac_sha1_hex( $mystring, $hashkey ) ;
  2638. #myprint( "$hashsync\n" ) ;
  2639. return( $hashsync ) ;
  2640. }
  2641. sub tests_createhashfileifneeded
  2642. {
  2643. note( 'Entering tests_createhashfileifneeded()' ) ;
  2644. is( undef, createhashfileifneeded( ), 'createhashfileifneeded: no parameters => undef' ) ;
  2645. note( 'Leaving tests_createhashfileifneeded()' ) ;
  2646. return ;
  2647. }
  2648. sub createhashfileifneeded
  2649. {
  2650. my $hashfile = shift ;
  2651. my $hashkey = shift || rand32( ) ;
  2652. # no name
  2653. if ( ! $hashfile ) {
  2654. return ;
  2655. }
  2656. # already there
  2657. if ( -e -r $hashfile ) {
  2658. return $hashfile ;
  2659. }
  2660. # not creatable
  2661. if ( ! -w dirname( $hashfile ) ) {
  2662. return ;
  2663. }
  2664. # creatable
  2665. open my $FILE_HANDLE, '>', $hashfile
  2666. or do {
  2667. myprint( "Could not open $hashfile for writing. Check permissions or disk space." ) ;
  2668. return ;
  2669. } ;
  2670. myprint( "Writing random hashkey in $hashfile, once for all times\n" ) ;
  2671. print $FILE_HANDLE $hashkey ;
  2672. close $FILE_HANDLE ;
  2673. # Should be there now
  2674. if ( -e -r $hashfile ) {
  2675. return $hashfile ;
  2676. }
  2677. # unknown failure
  2678. return ;
  2679. }
  2680. sub tests_rand32
  2681. {
  2682. note( 'Entering tests_rand32()' ) ;
  2683. my $string = rand32( ) ;
  2684. myprint( "$string\n" ) ;
  2685. is( 32, length( $string ), 'rand32: 32 characters long' ) ;
  2686. is( 32, length( rand32( ) ), 'rand32: 32 characters long, another one' ) ;
  2687. note( 'Leaving tests_rand32()' ) ;
  2688. return ;
  2689. }
  2690. sub rand32
  2691. {
  2692. my @chars = ( "a".."z" ) ;
  2693. my $string;
  2694. $string .= $chars[rand @chars] for 1..32 ;
  2695. return $string ;
  2696. }
  2697. sub imap_id_stuff
  2698. {
  2699. my $mysync = shift ;
  2700. if ( not $mysync->{id} ) { return ; } ;
  2701. $mysync->{h1_imap_id} = imap_id( $mysync, $mysync->{imap1}, 'Host1' ) ;
  2702. #myprint( 'Host1: ' . $mysync->{h1_imap_id} ) ;
  2703. $mysync->{h2_imap_id} = imap_id( $mysync, $mysync->{imap2}, 'Host2' ) ;
  2704. #myprint( 'Host2: ' . $mysync->{h2_imap_id} ) ;
  2705. return ;
  2706. }
  2707. sub imap_id
  2708. {
  2709. my ( $mysync, $imap, $Side ) = @_ ;
  2710. $Side ||= q{} ;
  2711. my $imap_id_response = q{} ;
  2712. if ( not $imap->has_capability( 'ID' ) ) {
  2713. $imap_id_response = 'No ID capability' ;
  2714. myprint( "$Side: No ID capability\n" ) ;
  2715. }else{
  2716. my $id_inp = imapsync_id( $mysync, { side => lc $Side } ) ;
  2717. myprint( "\n$Side: found ID capability. Sending/receiving ID, presented in raw IMAP for now.\n"
  2718. . "In order to avoid sending/receiving ID, use option --noid\n" ) ;
  2719. my $debug_before = $imap->Debug( ) ;
  2720. $imap->Debug( 1 ) ;
  2721. my $id_out = $imap->tag_and_run( 'ID ' . $id_inp ) ;
  2722. #my $id_out = $imap->tag_and_run( 'ID NIL' ) ;
  2723. myprint( "\n" ) ;
  2724. $imap->Debug( $debug_before ) ;
  2725. #$imap_id_response = Data::Dumper->Dump( [ $id_out ], [ 'IMAP_ID' ] ) ;
  2726. }
  2727. return( $imap_id_response ) ;
  2728. }
  2729. sub imapsync_id
  2730. {
  2731. my $mysync = shift ;
  2732. my $overhashref = shift ;
  2733. # See http://tools.ietf.org/html/rfc2971.html
  2734. my $imapsync_id = { } ;
  2735. my $imapsync_id_lamiral = {
  2736. name => 'imapsync',
  2737. version => imapsync_version( $mysync ),
  2738. os => $OSNAME,
  2739. vendor => 'Gilles LAMIRAL',
  2740. 'support-url' => 'https://imapsync.lamiral.info/',
  2741. # Example of date-time: 19-Sep-2015 08:56:07
  2742. date => date_from_rcs( q{$Date: 2019/05/01 22:14:00 $ } ),
  2743. } ;
  2744. my $imapsync_id_github = {
  2745. name => 'imapsync',
  2746. version => imapsync_version( $mysync ),
  2747. os => $OSNAME,
  2748. vendor => 'github',
  2749. 'support-url' => 'https://github.com/imapsync/imapsync',
  2750. date => date_from_rcs( q{$Date: 2019/05/01 22:14:00 $ } ),
  2751. } ;
  2752. $imapsync_id = $imapsync_id_lamiral ;
  2753. #$imapsync_id = $imapsync_id_github ;
  2754. my %mix = ( %{ $imapsync_id }, %{ $overhashref } ) ;
  2755. my $imapsync_id_str = format_for_imap_arg( \%mix ) ;
  2756. #myprint( "$imapsync_id_str\n" ) ;
  2757. return( $imapsync_id_str ) ;
  2758. }
  2759. sub tests_imapsync_id
  2760. {
  2761. note( 'Entering tests_imapsync_id()' ) ;
  2762. my $mysync ;
  2763. ok( '("name" "imapsync" "version" "111" "os" "beurk" "vendor" "Gilles LAMIRAL" "support-url" "https://imapsync.lamiral.info/" "date" "22-12-1968" "side" "host1")'
  2764. eq imapsync_id( $mysync,
  2765. {
  2766. version => 111,
  2767. os => 'beurk',
  2768. date => '22-12-1968',
  2769. side => 'host1'
  2770. }
  2771. ),
  2772. 'tests_imapsync_id override'
  2773. ) ;
  2774. note( 'Leaving tests_imapsync_id()' ) ;
  2775. return ;
  2776. }
  2777. sub format_for_imap_arg
  2778. {
  2779. my $ref = shift ;
  2780. my $string = q{} ;
  2781. my %terms = %{ $ref } ;
  2782. my @terms = ( ) ;
  2783. if ( not ( %terms ) ) { return( 'NIL' ) } ;
  2784. # sort like in RFC then add extra key/values
  2785. foreach my $key ( qw( name version os os-version vendor support-url address date command arguments environment) ) {
  2786. if ( $terms{ $key } ) {
  2787. push @terms, $key, $terms{ $key } ;
  2788. delete $terms{ $key } ;
  2789. }
  2790. }
  2791. push @terms, %terms ;
  2792. $string = '(' . ( join q{ }, map { '"' . $_ . '"' } @terms ) . ')' ;
  2793. return( $string ) ;
  2794. }
  2795. sub tests_format_for_imap_arg
  2796. {
  2797. note( 'Entering tests_format_for_imap_arg()' ) ;
  2798. ok( 'NIL' eq format_for_imap_arg( { } ), 'format_for_imap_arg empty hash ref' ) ;
  2799. ok( '("name" "toto")' eq format_for_imap_arg( { name => 'toto' } ), 'format_for_imap_arg { name => toto }' ) ;
  2800. ok( '("name" "toto" "key" "val")' eq format_for_imap_arg( { name => 'toto', key => 'val' } ), 'format_for_imap_arg 2 x key val' ) ;
  2801. note( 'Leaving tests_format_for_imap_arg()' ) ;
  2802. return ;
  2803. }
  2804. sub quota
  2805. {
  2806. my ( $mysync, $imap, $side ) = @_ ;
  2807. my %side = (
  2808. h1 => 'Host1',
  2809. h2 => 'Host2',
  2810. ) ;
  2811. my $Side = $side{ $side } ;
  2812. my $debug_before = $imap->Debug( ) ;
  2813. $imap->Debug( 1 ) ;
  2814. if ( not $imap->has_capability( 'QUOTA' ) ) {
  2815. $imap->Debug( $debug_before ) ;
  2816. return ;
  2817. } ;
  2818. myprint( "\n$Side: found quota, presented in raw IMAP\n" ) ;
  2819. my $getquotaroot = $imap->getquotaroot( 'INBOX' ) ;
  2820. # Gmail INBOX quotaroot is "" but with it Mail::IMAPClient does a literal GETQUOTA {2} \n ""
  2821. #$imap->quota( 'ROOT' ) ;
  2822. #$imap->quota( '""' ) ;
  2823. myprint( "\n" ) ;
  2824. $imap->Debug( $debug_before ) ;
  2825. my $quota_limit_bytes = quota_extract_storage_limit_in_bytes( $mysync, $getquotaroot ) ;
  2826. my $quota_current_bytes = quota_extract_storage_current_in_bytes( $mysync, $getquotaroot ) ;
  2827. $mysync->{$side}->{quota_limit_bytes} = $quota_limit_bytes ;
  2828. $mysync->{$side}->{quota_current_bytes} = $quota_current_bytes ;
  2829. my $quota_percent ;
  2830. if ( $quota_limit_bytes > 0 ) {
  2831. $quota_percent = mysprintf( '%.2f', $NUMBER_100 * $quota_current_bytes / $quota_limit_bytes ) ;
  2832. }else{
  2833. $quota_percent = 0 ;
  2834. }
  2835. myprint( "$Side: Quota current storage is $quota_current_bytes bytes. Limit is $quota_limit_bytes bytes. So $quota_percent % full\n" ) ;
  2836. if ( $QUOTA_PERCENT_LIMIT < $quota_percent ) {
  2837. my $error = "$Side: $quota_percent % full: it is time to find a bigger place! ( $quota_current_bytes bytes / $quota_limit_bytes bytes )\n" ;
  2838. errors_incr( $mysync, $error ) ;
  2839. }
  2840. return ;
  2841. }
  2842. sub tests_quota_extract_storage_limit_in_bytes
  2843. {
  2844. note( 'Entering tests_quota_extract_storage_limit_in_bytes()' ) ;
  2845. my $mysync = {} ;
  2846. my $imap_output = [
  2847. '* QUOTAROOT "INBOX" "Storage quota" "Messages quota"',
  2848. '* QUOTA "Storage quota" (STORAGE 1 104857600)',
  2849. '* QUOTA "Messages quota" (MESSAGE 2 100000)',
  2850. '5 OK Getquotaroot completed.'
  2851. ] ;
  2852. ok( $NUMBER_104_857_600 * $KIBI == quota_extract_storage_limit_in_bytes( $mysync, $imap_output ), 'quota_extract_storage_limit_in_bytes ') ;
  2853. note( 'Leaving tests_quota_extract_storage_limit_in_bytes()' ) ;
  2854. return ;
  2855. }
  2856. sub quota_extract_storage_limit_in_bytes
  2857. {
  2858. my $mysync = shift ;
  2859. my $imap_output = shift ;
  2860. my $limit_kb ;
  2861. $limit_kb = ( map { /.*\(\s*STORAGE\s+\d+\s+(\d+)\s*\)/x ? $1 : () } @{ $imap_output } )[0] ;
  2862. $limit_kb ||= 0 ;
  2863. $mysync->{ debug } and myprint( "storage_limit_kb = $limit_kb\n" ) ;
  2864. return( $KIBI * $limit_kb ) ;
  2865. }
  2866. sub tests_quota_extract_storage_current_in_bytes
  2867. {
  2868. note( 'Entering tests_quota_extract_storage_current_in_bytes()' ) ;
  2869. my $mysync = {} ;
  2870. my $imap_output = [
  2871. '* QUOTAROOT "INBOX" "Storage quota" "Messages quota"',
  2872. '* QUOTA "Storage quota" (STORAGE 1 104857600)',
  2873. '* QUOTA "Messages quota" (MESSAGE 2 100000)',
  2874. '5 OK Getquotaroot completed.'
  2875. ] ;
  2876. ok( 1*$KIBI == quota_extract_storage_current_in_bytes( $mysync, $imap_output ), 'quota_extract_storage_current_in_bytes: 1 => 1024 ') ;
  2877. note( 'Leaving tests_quota_extract_storage_current_in_bytes()' ) ;
  2878. return ;
  2879. }
  2880. sub quota_extract_storage_current_in_bytes
  2881. {
  2882. my $mysync = shift ;
  2883. my $imap_output = shift ;
  2884. my $current_kb ;
  2885. $current_kb = ( map { /.*\(\s*STORAGE\s+(\d+)\s+\d+\s*\)/x ? $1 : () } @{ $imap_output } )[0] ;
  2886. $current_kb ||= 0 ;
  2887. $mysync->{ debug } and myprint( "storage_current_kb = $current_kb\n" ) ;
  2888. return( $KIBI * $current_kb ) ;
  2889. }
  2890. sub automap
  2891. {
  2892. my ( $mysync ) = @_ ;
  2893. if ( $mysync->{automap} ) {
  2894. myprint( "Turned on automapping folders ( use --noautomap to turn off automapping )\n" ) ;
  2895. }else{
  2896. myprint( "Turned off automapping folders ( use --automap to turn on automapping )\n" ) ;
  2897. return ;
  2898. }
  2899. $mysync->{h1_special} = special_from_folders_hash( $mysync, $mysync->{imap1}, 'Host1' ) ;
  2900. $mysync->{h2_special} = special_from_folders_hash( $mysync, $mysync->{imap2}, 'Host2' ) ;
  2901. build_possible_special( $mysync ) ;
  2902. build_guess_special( $mysync ) ;
  2903. build_automap( $mysync ) ;
  2904. return ;
  2905. }
  2906. sub build_guess_special
  2907. {
  2908. my ( $mysync ) = shift ;
  2909. foreach my $h1_fold ( sort keys %{ $mysync->{h1_folders_all} } ) {
  2910. my $special = guess_special( $h1_fold, $mysync->{possible_special}, $mysync->{h1_prefix} ) ;
  2911. if ( $special ) {
  2912. $mysync->{h1_special_guessed}{$h1_fold} = $special ;
  2913. my $already_guessed = $mysync->{h1_special_guessed}{$special} ;
  2914. if ( $already_guessed ) {
  2915. myprint( "Host1: $h1_fold not $special because set to $already_guessed\n" ) ;
  2916. }else{
  2917. $mysync->{h1_special_guessed}{$special} = $h1_fold ;
  2918. }
  2919. }
  2920. }
  2921. foreach my $h2_fold ( sort keys %{ $mysync->{h2_folders_all} } ) {
  2922. my $special = guess_special( $h2_fold, $mysync->{possible_special}, $mysync->{h2_prefix} ) ;
  2923. if ( $special ) {
  2924. $mysync->{h2_special_guessed}{$h2_fold} = $special ;
  2925. my $already_guessed = $mysync->{h2_special_guessed}{$special} ;
  2926. if ( $already_guessed ) {
  2927. myprint( "Host2: $h2_fold not $special because set to $already_guessed\n" ) ;
  2928. }else{
  2929. $mysync->{h2_special_guessed}{$special} = $h2_fold ;
  2930. }
  2931. }
  2932. }
  2933. return ;
  2934. }
  2935. sub guess_special
  2936. {
  2937. my( $folder, $possible_special_ref, $prefix ) = @_ ;
  2938. my $folder_no_prefix = $folder ;
  2939. $folder_no_prefix =~ s/\Q${prefix}\E//xms ;
  2940. #$debug and myprint( "folder_no_prefix: $folder_no_prefix\n" ) ;
  2941. my $guess_special = $possible_special_ref->{ $folder }
  2942. || $possible_special_ref->{ $folder_no_prefix }
  2943. || q{} ;
  2944. return( $guess_special ) ;
  2945. }
  2946. sub tests_guess_special
  2947. {
  2948. note( 'Entering tests_guess_special()' ) ;
  2949. my $possible_special_ref = build_possible_special( my $mysync ) ;
  2950. ok( '\Sent' eq guess_special( 'Sent', $possible_special_ref, q{} ) ,'guess_special: Sent => \Sent' ) ;
  2951. ok( q{} eq guess_special( 'Blabla', $possible_special_ref, q{} ) ,'guess_special: Blabla => q{}' ) ;
  2952. ok( '\Sent' eq guess_special( 'INBOX.Sent', $possible_special_ref, 'INBOX.' ) ,'guess_special: INBOX.Sent => \Sent' ) ;
  2953. ok( '\Sent' eq guess_special( 'IN BOX.Sent', $possible_special_ref, 'IN BOX.' ) ,'guess_special: IN BOX.Sent => \Sent' ) ;
  2954. note( 'Leaving tests_guess_special()' ) ;
  2955. return ;
  2956. }
  2957. sub build_automap
  2958. {
  2959. my $mysync = shift ;
  2960. $mysync->{ debug } and myprint( "Entering build_automap\n" ) ;
  2961. foreach my $h1_fold ( @{ $mysync->{h1_folders_wanted} } ) {
  2962. my $h2_fold ;
  2963. my $h1_special = $mysync->{h1_special}{$h1_fold} ;
  2964. my $h1_special_guessed = $mysync->{h1_special_guessed}{$h1_fold} ;
  2965. # Case 1: special on both sides.
  2966. if ( $h1_special
  2967. and exists $mysync->{h2_special}{$h1_special} ) {
  2968. $h2_fold = $mysync->{h2_special}{$h1_special} ;
  2969. $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ;
  2970. next ;
  2971. }
  2972. # Case 2: special on host1, not on host2
  2973. if ( $h1_special
  2974. and ( not exists $mysync->{h2_special}{$h1_special} )
  2975. and ( exists $mysync->{h2_special_guessed}{$h1_special} )
  2976. ) {
  2977. # special_guessed on host2
  2978. $h2_fold = $mysync->{h2_special_guessed}{$h1_special} ;
  2979. $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ;
  2980. next ;
  2981. }
  2982. # Case 3: no special on host1, special on host2
  2983. if ( ( not $h1_special )
  2984. and ( $h1_special_guessed )
  2985. and ( exists $mysync->{h2_special}{$h1_special_guessed} )
  2986. ) {
  2987. $h2_fold = $mysync->{h2_special}{$h1_special_guessed} ;
  2988. $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ;
  2989. next ;
  2990. }
  2991. # Case 4: no special on both sides.
  2992. if ( ( not $h1_special )
  2993. and ( $h1_special_guessed )
  2994. and ( not exists $mysync->{h2_special}{$h1_special_guessed} )
  2995. and ( exists $mysync->{h2_special_guessed}{$h1_special_guessed} )
  2996. ) {
  2997. $h2_fold = $mysync->{h2_special_guessed}{$h1_special_guessed} ;
  2998. $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ;
  2999. next ;
  3000. }
  3001. }
  3002. return( $mysync->{f1f2auto} ) ;
  3003. }
  3004. # I will not add what there is at:
  3005. # http://stackoverflow.com/questions/2185391/localized-gmail-imap-folders/2185548#2185548
  3006. # because it works well without
  3007. sub build_possible_special
  3008. {
  3009. my $mysync = shift ;
  3010. my $possible_special = { } ;
  3011. # All|Archive|Drafts|Flagged|Junk|Sent|Trash
  3012. $possible_special->{'\All'} = [ 'All', 'All Messages', '&BBIEQQQ1-' ] ;
  3013. $possible_special->{'\Archive'} = [ 'Archive', 'Archives', '&BBAEQARFBDgEMg-' ] ;
  3014. $possible_special->{'\Drafts'} = [ 'Drafts', 'DRAFTS', '&BCcENQRABD0EPgQyBDgEOgQ4-', 'Szkice', 'Wersje robocze' ] ;
  3015. $possible_special->{'\Flagged'} = [ 'Flagged', 'Starred', '&BB8EPgQ8BDUERwQ1BD0EPQRLBDU-' ] ;
  3016. $possible_special->{'\Junk'} = [ 'Junk', 'Spam', 'SPAM', '&BCEEPwQwBDw-', 'Potwierdzony spam', 'Wiadomo&AVs-ci-&AVs-mieci' ] ;
  3017. $possible_special->{'\Sent'} = [ 'Sent', 'Sent Messages', 'Sent Items',
  3018. 'Gesendete Elemente', 'Gesendete Objekte',
  3019. '&AMk-l&AOk-ments envoy&AOk-s', 'Envoy&AOk-', 'Objets envoy&AOk-s',
  3020. 'Elementos enviados',
  3021. '&kAFP4W4IMH8wojCkMMYw4A-',
  3022. '&BB4EQgQ,BEAEMAQyBDsENQQ9BD0ESwQ1-',
  3023. 'Elementy wys&AUI-ane'] ;
  3024. $possible_special->{'\Trash'} = [ 'Trash', 'TRASH', '&BCMENAQwBDsENQQ9BD0ESwQ1-', '&BBoEPgRABDcEOAQ9BDA-', 'Kosz', 'Deleted Items' ] ;
  3025. foreach my $special ( qw( \All \Archive \Drafts \Flagged \Junk \Sent \Trash ) ){
  3026. foreach my $possible_folder ( @{ $possible_special->{$special} } ) {
  3027. $possible_special->{ $possible_folder } = $special ;
  3028. } ;
  3029. }
  3030. $mysync->{possible_special} = $possible_special ;
  3031. $mysync->{ debug } and myprint( Data::Dumper->Dump( [ $possible_special ], [ 'possible_special' ] ) ) ;
  3032. return( $possible_special ) ;
  3033. }
  3034. sub tests_special_from_folders_hash
  3035. {
  3036. note( 'Entering tests_special_from_folders_hash()' ) ;
  3037. my $mysync = {} ;
  3038. require_ok( "Test::MockObject" ) ;
  3039. my $imapT = Test::MockObject->new( ) ;
  3040. is( undef, special_from_folders_hash( ), 'special_from_folders_hash: no args' ) ;
  3041. is( undef, special_from_folders_hash( $mysync ), 'special_from_folders_hash: undef args' ) ;
  3042. is_deeply( {}, special_from_folders_hash( $mysync, $imapT ), 'special_from_folders_hash: $imap void' ) ;
  3043. $imapT->mock( 'folders_hash', sub { return( [ { name => 'Sent', attrs => [ '\Sent' ] } ] ) } ) ;
  3044. is_deeply( { Sent => '\Sent', '\Sent' => 'Sent' },
  3045. special_from_folders_hash( $mysync, $imapT ), 'special_from_folders_hash: $imap \Sent' ) ;
  3046. note( 'Leaving tests_special_from_folders_hash()' ) ;
  3047. return( ) ;
  3048. }
  3049. sub special_from_folders_hash
  3050. {
  3051. my ( $mysync, $imap, $side ) = @_ ;
  3052. my %special = ( ) ;
  3053. if ( ! defined $imap ) { return ; }
  3054. $side = defined $side ? $side : 'Host?' ;
  3055. if ( ! $imap->can( 'folders_hash' ) ) {
  3056. my $error = "$side: To have automagic rfc6154 folder mapping, upgrade Mail::IMAPClient >= 3.34\n" ;
  3057. errors_incr( $mysync, $error ) ;
  3058. return( \%special ) ; # empty hash ref
  3059. }
  3060. my $folders_hash = $imap->folders_hash( ) ;
  3061. foreach my $fhash (@{ $folders_hash } ) {
  3062. my @special = grep { /\\(?:All|Archive|Drafts|Flagged|Junk|Sent|Trash)/x } @{ $fhash->{attrs} } ;
  3063. if ( @special ) {
  3064. my $special = $special[0] ; # keep first one. Could be not very good.
  3065. if ( exists $special{ $special } ) {
  3066. myprintf( "%s: special %-20s = %s already assigned to %s\n",
  3067. $side, $fhash->{name}, join( q{ }, @special ), $special{ $special } ) ;
  3068. }else{
  3069. myprintf( "%s: special %-20s = %s\n",
  3070. $side, $fhash->{name}, join( q{ }, @special ) ) ;
  3071. $special{ $special } = $fhash->{name} ;
  3072. $special{ $fhash->{name} } = $special ; # double entry value => key
  3073. }
  3074. }
  3075. }
  3076. myprint( "\n" ) if ( %special ) ;
  3077. return( \%special ) ;
  3078. }
  3079. sub errors_incr
  3080. {
  3081. my ( $mysync, @error ) = @ARG ;
  3082. $mysync->{nb_errors}++ ;
  3083. if ( @error ) {
  3084. errors_log( $mysync, @error ) ;
  3085. myprint( @error ) ;
  3086. }
  3087. $mysync->{errorsmax} ||= $ERRORS_MAX ;
  3088. if ( $mysync->{nb_errors} >= $mysync->{errorsmax} ) {
  3089. myprint( "Maximum number of errors $mysync->{errorsmax} reached ( you can change $mysync->{errorsmax} to any value, for example 100 with --errorsmax 100 ). Exiting.\n" ) ;
  3090. if ( $mysync->{errorsdump} ) {
  3091. myprint( errorsdump( $mysync->{nb_errors}, errors_log( $mysync ) ) ) ;
  3092. # again since errorsdump( ) can be very verbose and masquerade previous warning
  3093. myprint( "Maximum number of errors $mysync->{errorsmax} reached ( you can change $mysync->{errorsmax} to any value, for example 100 with --errorsmax 100 ). Exiting.\n" ) ;
  3094. }
  3095. exit_clean( $mysync, $EXIT_WITH_ERRORS_MAX ) ;
  3096. }
  3097. return ;
  3098. }
  3099. sub tests_errors_log
  3100. {
  3101. note( 'Entering tests_errors_log()' ) ;
  3102. is( undef, errors_log( ), 'errors_log: no args => undef' ) ;
  3103. my $mysync = {} ;
  3104. is( undef, errors_log( $mysync ), 'errors_log: empty => undef' ) ;
  3105. is_deeply( [ 'aieaie' ], [ errors_log( $mysync, 'aieaie' ) ], 'errors_log: aieaie => aieaie' ) ;
  3106. # cumulative
  3107. is_deeply( [ 'aieaie' ], [ errors_log( $mysync ) ], 'errors_log: nothing more => aieaie' ) ;
  3108. is_deeply( [ 'aieaie', 'ouille' ], [ errors_log( $mysync, 'ouille' ) ], 'errors_log: ouille => aieaie ouille' ) ;
  3109. is_deeply( [ 'aieaie', 'ouille' ], [ errors_log( $mysync ) ], 'errors_log: nothing more => aieaie ouille' ) ;
  3110. note( 'Leaving tests_errors_log()' ) ;
  3111. return ;
  3112. }
  3113. sub errors_log
  3114. {
  3115. my ( $mysync, @error ) = @ARG ;
  3116. if ( ! $mysync->{errors_log} ) {
  3117. $mysync->{errors_log} = [] ;
  3118. }
  3119. if ( @error ) {
  3120. push @{ $mysync->{errors_log} }, join( q{}, @error ) ;
  3121. }
  3122. if ( @{ $mysync->{errors_log} } ) {
  3123. return @{ $mysync->{errors_log} } ;
  3124. }
  3125. else {
  3126. return ;
  3127. }
  3128. }
  3129. sub errorsdump
  3130. {
  3131. my( $nb_errors, @errors_log ) = @ARG ;
  3132. my $error_num = 0 ;
  3133. my $errors_list = q{} ;
  3134. if ( @errors_log ) {
  3135. $errors_list = "++++ Listing $nb_errors errors encountered during the sync ( avoid this listing with --noerrorsdump ).\n" ;
  3136. foreach my $error ( @errors_log ) {
  3137. $error_num++ ;
  3138. $errors_list .= "Err $error_num/$nb_errors: $error" ;
  3139. }
  3140. }
  3141. return( $errors_list ) ;
  3142. }
  3143. sub tests_live_result
  3144. {
  3145. note( 'Entering tests_live_result()' ) ;
  3146. my $nb_errors = shift ;
  3147. if ( $nb_errors ) {
  3148. myprint( "Live tests failed with $nb_errors errors\n" ) ;
  3149. } else {
  3150. myprint( "Live tests ended successfully\n" ) ;
  3151. }
  3152. note( 'Leaving tests_live_result()' ) ;
  3153. return ;
  3154. }
  3155. sub foldersizesatend
  3156. {
  3157. my $mysync = shift ;
  3158. timenext( ) ;
  3159. return if ( $mysync->{imap1}->IsUnconnected( ) ) ;
  3160. return if ( $mysync->{imap2}->IsUnconnected( ) ) ;
  3161. # Get all folders on host2 again since new were created
  3162. @h2_folders_all = sort $mysync->{imap2}->folders();
  3163. for ( @h2_folders_all ) {
  3164. $h2_folders_all{ $_ } = 1 ;
  3165. $h2_folders_all_UPPER{ uc $_ } = 1 ;
  3166. } ;
  3167. ( $h1_nb_msg_end, $h1_bytes_end ) = foldersizes( 'Host1', $mysync->{imap1}, $search1, $mysync->{abletosearch1}, @h1_folders_wanted ) ;
  3168. ( $h2_nb_msg_end, $h2_bytes_end ) = foldersizes( 'Host2', $mysync->{imap2}, $search2, $mysync->{abletosearch2}, @h2_folders_from_1_wanted ) ;
  3169. if ( not all_defined( $h1_nb_msg_end, $h1_bytes_end, $h2_nb_msg_end, $h2_bytes_end ) ) {
  3170. my $error = "Failure getting foldersizes, final differences will not be calculated\n" ;
  3171. errors_incr( $mysync, $error ) ;
  3172. }
  3173. return ;
  3174. }
  3175. sub size_filtered_flag
  3176. {
  3177. my $mysync = shift ;
  3178. my $h1_size = shift ;
  3179. if ( defined $mysync->{ maxsize } and $h1_size >= $mysync->{ maxsize } ) {
  3180. return( 1 ) ;
  3181. }
  3182. if ( defined $minsize and $h1_size <= $minsize ) {
  3183. return( 1 ) ;
  3184. }
  3185. return( 0 ) ;
  3186. }
  3187. sub sync_flags_fir
  3188. {
  3189. my ( $mysync, $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) = @_ ;
  3190. if ( not defined $h1_msg ) { return } ;
  3191. if ( not defined $h2_msg ) { return } ;
  3192. my $h1_size = $h1_fir_ref->{$h1_msg}->{'RFC822.SIZE'} ;
  3193. return if size_filtered_flag( $mysync, $h1_size ) ;
  3194. # used cached flag values for efficiency
  3195. my $h1_flags = $h1_fir_ref->{ $h1_msg }->{ 'FLAGS' } || q{} ;
  3196. my $h2_flags = $h2_fir_ref->{ $h2_msg }->{ 'FLAGS' } || q{} ;
  3197. sync_flags( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) ;
  3198. return ;
  3199. }
  3200. sub sync_flags_after_copy
  3201. {
  3202. # Activated with option --syncflagsaftercopy
  3203. my( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $permanentflags2 ) = @_ ;
  3204. if ( my @h2_flags = $mysync->{imap2}->flags( $h2_msg ) ) {
  3205. my $h2_flags = "@h2_flags" ;
  3206. ( $mysync->{ debug } or $debugflags ) and myprint( "Host2: msg $h2_fold/$h2_msg flags before sync flags after copy ( $h2_flags )\n" ) ;
  3207. sync_flags( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) ;
  3208. }else{
  3209. myprint( "Host2: msg $h2_fold/$h2_msg could not get its flags for sync flags after copy\n" ) ;
  3210. }
  3211. return ;
  3212. }
  3213. # Globals
  3214. # $debug
  3215. # $debugflags
  3216. # $permanentflags2
  3217. sub sync_flags
  3218. {
  3219. my( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) = @_ ;
  3220. ( $mysync->{ debug } or $debugflags ) and
  3221. myprint( "Host1: flags init msg $h1_fold/$h1_msg flags( $h1_flags ) Host2 msg $h2_fold/$h2_msg flags( $h2_flags )\n" ) ;
  3222. $h1_flags = flags_for_host2( $h1_flags, $permanentflags2 ) ;
  3223. $h2_flags = flagscase( $h2_flags ) ;
  3224. ( $mysync->{ debug } or $debugflags ) and
  3225. myprint( "Host1: flags filt msg $h1_fold/$h1_msg flags( $h1_flags ) Host2 msg $h2_fold/$h2_msg flags( $h2_flags )\n" ) ;
  3226. # compare flags - set flags if there a difference
  3227. my @h1_flags = sort split(q{ }, $h1_flags );
  3228. my @h2_flags = sort split(q{ }, $h2_flags );
  3229. my $diff = compare_lists( \@h1_flags, \@h2_flags );
  3230. $diff and ( $mysync->{ debug } or $debugflags )
  3231. and myprint( "Host2: flags msg $h2_fold/$h2_msg replacing h2 flags( $h2_flags ) with h1 flags( $h1_flags )\n" ) ;
  3232. # This sets flags exactly. So flags can be removed with this.
  3233. # When you remove a \Seen flag on host1 you want it
  3234. # to be removed on host2. Just add flags is not what
  3235. # we need most of the time, so no + like in "+FLAGS.SILENT".
  3236. if ( not $mysync->{dry} and $diff and not $mysync->{imap2}->store( $h2_msg, "FLAGS.SILENT (@h1_flags)" ) ) {
  3237. my $error_msg = join q{}, "Host2: flags msg $h2_fold/$h2_msg could not add flags [@h1_flags]: ",
  3238. $mysync->{imap2}->LastError || q{}, "\n" ;
  3239. errors_incr( $mysync, $error_msg ) ;
  3240. }
  3241. return ;
  3242. }
  3243. sub _filter
  3244. {
  3245. my $mysync = shift ;
  3246. my $str = shift or return q{} ;
  3247. my $sz = $SIZE_MAX_STR ;
  3248. my $len = length $str ;
  3249. if ( not $mysync->{ debug } and $len > $sz*2 ) {
  3250. my $beg = substr $str, 0, $sz ;
  3251. my $end = substr $str, -$sz, $sz ;
  3252. $str = $beg . '...' . $end ;
  3253. }
  3254. $str =~ s/\012?\015$//x ;
  3255. return "(len=$len) " . $str ;
  3256. }
  3257. sub lost_connection
  3258. {
  3259. my( $mysync, $imap, $error_message ) = @_;
  3260. if ( $imap->IsUnconnected( ) ) {
  3261. $mysync->{nb_errors}++ ;
  3262. my $lcomm = $imap->LastIMAPCommand || q{} ;
  3263. my $einfo = $imap->LastError || @{$imap->History}[$LAST] || q{} ;
  3264. # if string is long try reduce to a more reasonable size
  3265. $lcomm = _filter( $mysync, $lcomm ) ;
  3266. $einfo = _filter( $mysync, $einfo ) ;
  3267. myprint( "Failure: last command: $lcomm\n") if ( $mysync->{ debug } && $lcomm) ;
  3268. myprint( "Failure: lost connection $error_message: ", $einfo, "\n") ;
  3269. return( 1 ) ;
  3270. }
  3271. else{
  3272. return( 0 ) ;
  3273. }
  3274. }
  3275. sub tests_max
  3276. {
  3277. note( 'Entering tests_max()' ) ;
  3278. is( 0, max( 0 ), 'max 0 => 0' ) ;
  3279. is( 1, max( 1 ), 'max 1 => 1' ) ;
  3280. is( $MINUS_ONE, max( $MINUS_ONE ), 'max -1 => -1') ;
  3281. is( undef, max( ), 'max no arg => undef' ) ;
  3282. is( $NUMBER_100, max( 1, $NUMBER_100 ), 'max 1 100 => 100' ) ;
  3283. is( $NUMBER_100, max( $NUMBER_100, 1 ), 'max 100 1 => 100' ) ;
  3284. is( $NUMBER_100, max( $NUMBER_100, $NUMBER_42, 1 ), 'max 100 42 1 => 100' ) ;
  3285. is( $NUMBER_100, max( $NUMBER_100, '42', 1 ), 'max 100 42 1 => 100' ) ;
  3286. is( $NUMBER_100, max( '100', '42', 1 ), 'max 100 42 1 => 100' ) ;
  3287. is( $NUMBER_100, max( $NUMBER_100, 'haha', 1 ), 'max 100 haha 1 => 100') ;
  3288. is( $NUMBER_100, max( 'bb', $NUMBER_100, 'haha' ), 'max bb 100 haha => 100') ;
  3289. is( $MINUS_ONE, max( q{}, $MINUS_ONE, 'haha' ), 'max "" -1 haha => -1') ;
  3290. is( $MINUS_ONE, max( q{}, $MINUS_ONE, $MINUS_TWO ), 'max "" -1 -2 => -1') ;
  3291. is( $MINUS_ONE, max( 'haha', $MINUS_ONE, $MINUS_TWO ), 'max haha -1 -2 => -1') ;
  3292. is( 1, max( $MINUS_ONE, 1 ), 'max -1 1 => 1') ;
  3293. is( 1, max( undef, 1 ), 'max undef 1 => 1' ) ;
  3294. is( 0, max( undef, 0 ), 'max undef 0 => 0' ) ;
  3295. is( 'haha', max( 'haha' ), 'max haha => haha') ;
  3296. is( 'bb', max( 'aa', 'bb' ), 'max aa bb => bb') ;
  3297. is( 'bb', max( 'bb', 'aa' ), 'max bb aa => bb') ;
  3298. is( 'bb', max( 'bb', 'aa', 'bb' ), 'max bb aa bb => bb') ;
  3299. note( 'Leaving tests_max()' ) ;
  3300. return ;
  3301. }
  3302. sub max
  3303. {
  3304. my @list = @_ ;
  3305. return( undef ) if ( 0 == scalar @list ) ;
  3306. my( @numbers, @notnumbers ) ;
  3307. foreach my $item ( @list ) {
  3308. if ( is_number( $item ) ) {
  3309. push @numbers, $item ;
  3310. }else{
  3311. push @notnumbers, $item ;
  3312. }
  3313. }
  3314. my @sorted ;
  3315. if ( @numbers ) {
  3316. @sorted = sort { $a <=> $b } @numbers ;
  3317. }elsif( @notnumbers ) {
  3318. @sorted = sort { $a cmp $b } @notnumbers ;
  3319. }else{
  3320. return ;
  3321. }
  3322. return( pop @sorted ) ;
  3323. }
  3324. sub tests_is_number
  3325. {
  3326. note( 'Entering tests_is_number()' ) ;
  3327. ok( ! is_number( ), 'is_number: no args => undef ' ) ;
  3328. ok( is_number( 1 ), 'is_number: 1 => 1' ) ;
  3329. ok( is_number( 1.1 ), 'is_number: 1.1 => 1' ) ;
  3330. ok( is_number( 0 ), 'is_number: 0 => 1' ) ;
  3331. ok( is_number( -1 ), 'is_number: -1 => 1' ) ;
  3332. ok( ! is_number( 1.1.1 ), 'is_number: 1.1.1 => no' ) ;
  3333. ok( ! is_number( q{} ), 'is_number: q{} => no' ) ;
  3334. ok( ! is_number( 'haha' ), 'is_number: haha => no' ) ;
  3335. ok( ! is_number( '0haha' ), 'is_number: 0haha => no' ) ;
  3336. ok( ! is_number( '2haha' ), 'is_number: 2haha => no' ) ;
  3337. ok( ! is_number( 'haha2' ), 'is_number: haha2 => no' ) ;
  3338. note( 'Leaving tests_is_number()' ) ;
  3339. return ;
  3340. }
  3341. sub is_number
  3342. {
  3343. my $item = shift ;
  3344. if ( ! defined $item ) { return ; }
  3345. if ( $item =~ /\A$RE{num}{real}\Z/ ) {
  3346. return 1 ;
  3347. }
  3348. return ;
  3349. }
  3350. sub tests_min
  3351. {
  3352. note( 'Entering tests_min()' ) ;
  3353. is( 0, min( 0 ), 'min 0 => 0' ) ;
  3354. is( 1, min( 1 ), 'min 1 => 1' ) ;
  3355. is( $MINUS_ONE, min( $MINUS_ONE ), 'min -1 => -1' ) ;
  3356. is( undef, min( ), 'min no arg => undef' ) ;
  3357. is( 1, min( 1, $NUMBER_100 ), 'min 1 100 => 1' ) ;
  3358. is( 1, min( $NUMBER_100, 1 ), 'min 100 1 => 1' ) ;
  3359. is( 1, min( $NUMBER_100, $NUMBER_42, 1 ), 'min 100 42 1 => 1' ) ;
  3360. is( 1, min( $NUMBER_100, '42', 1 ), 'min 100 42 1 => 1' ) ;
  3361. is( 1, min( '100', '42', 1 ), 'min 100 42 1 => 1' ) ;
  3362. is( 1, min( $NUMBER_100, 'haha', 1 ), 'min 100 haha 1 => 1') ;
  3363. is( $MINUS_ONE, min( $MINUS_ONE, 1 ), 'min -1 1 => -1') ;
  3364. is( 1, min( undef, 1 ), 'min undef 1 => 1' ) ;
  3365. is( 0, min( undef, 0 ), 'min undef 0 => 0' ) ;
  3366. is( 1, min( undef, 1 ), 'min undef 1 => 1' ) ;
  3367. is( 0, min( undef, 2, 0, 1 ), 'min undef, 2, 0, 1 => 0' ) ;
  3368. is( 'haha', min( 'haha' ), 'min haha => haha') ;
  3369. is( 'aa', min( 'aa', 'bb' ), 'min aa bb => aa') ;
  3370. is( 'aa', min( 'bb', 'aa' ), 'min bb aa bb => aa') ;
  3371. is( 'aa', min( 'bb', 'aa', 'bb' ), 'min bb aa bb => aa') ;
  3372. note( 'Leaving tests_min()' ) ;
  3373. return ;
  3374. }
  3375. sub min
  3376. {
  3377. my @list = @_ ;
  3378. return( undef ) if ( 0 == scalar @list ) ;
  3379. my( @numbers, @notnumbers ) ;
  3380. foreach my $item ( @list ) {
  3381. if ( is_number( $item ) ) {
  3382. push @numbers, $item ;
  3383. }else{
  3384. push @notnumbers, $item ;
  3385. }
  3386. }
  3387. my @sorted ;
  3388. if ( @numbers ) {
  3389. @sorted = sort { $a <=> $b } @numbers ;
  3390. }elsif( @notnumbers ) {
  3391. @sorted = sort { $a cmp $b } @notnumbers ;
  3392. }else{
  3393. return ;
  3394. }
  3395. return( shift @sorted ) ;
  3396. }
  3397. sub check_lib_version
  3398. {
  3399. my $mysync = shift ;
  3400. $mysync->{ debug } and myprint( "IMAPClient $Mail::IMAPClient::VERSION\n" ) ;
  3401. if ( '2.2.9' eq $Mail::IMAPClient::VERSION ) {
  3402. myprint( "imapsync no longer supports Mail::IMAPClient 2.2.9, upgrade it\n" ) ;
  3403. return 0 ;
  3404. }
  3405. else{
  3406. # 3.x.x is no longer buggy with imapsync.
  3407. # 3.30 or currently superior is imposed in the Perl "use Mail::IMAPClient line".
  3408. return 1 ;
  3409. }
  3410. return ;
  3411. }
  3412. sub module_version_str
  3413. {
  3414. my( $module_name, $module_version ) = @_ ;
  3415. my $str = mysprintf( "%-20s %s\n", $module_name, $module_version ) ;
  3416. return( $str ) ;
  3417. }
  3418. sub modulesversion
  3419. {
  3420. my @list_version;
  3421. my %modulesversion = (
  3422. 'Authen::NTLM' => sub { $Authen::NTLM::VERSION },
  3423. 'CGI' => sub { $CGI::VERSION },
  3424. 'Compress::Zlib' => sub { $Compress::Zlib::VERSION },
  3425. 'Crypt::OpenSSL::RSA' => sub { $Crypt::OpenSSL::RSA::VERSION },
  3426. 'Data::Uniqid' => sub { $Data::Uniqid::VERSION },
  3427. 'Digest::HMAC_MD5' => sub { $Digest::HMAC_MD5::VERSION },
  3428. 'Digest::HMAC_SHA1' => sub { $Digest::HMAC_SHA1::VERSION },
  3429. 'Digest::MD5' => sub { $Digest::MD5::VERSION },
  3430. 'File::Copy::Recursive' => sub { $File::Copy::Recursive::VERSION },
  3431. 'File::Spec' => sub { $File::Spec::VERSION },
  3432. 'Getopt::Long' => sub { $Getopt::Long::VERSION },
  3433. 'HTML::Entities' => sub { $HTML::Entities::VERSION },
  3434. 'IO::Socket' => sub { $IO::Socket::VERSION },
  3435. 'IO::Socket::INET' => sub { $IO::Socket::INET::VERSION },
  3436. 'IO::Socket::INET6' => sub { $IO::Socket::INET6::VERSION },
  3437. 'IO::Socket::IP' => sub { $IO::Socket::IP::VERSION },
  3438. 'IO::Socket::SSL' => sub { $IO::Socket::SSL::VERSION },
  3439. 'IO::Tee' => sub { $IO::Tee::VERSION },
  3440. 'JSON' => sub { $JSON::VERSION },
  3441. 'JSON::WebToken' => sub { $JSON::WebToken::VERSION },
  3442. 'LWP' => sub { $LWP::VERSION },
  3443. 'Mail::IMAPClient' => sub { $Mail::IMAPClient::VERSION },
  3444. 'Net::Ping' => sub { $Net::Ping::VERSION },
  3445. 'Net::SSLeay' => sub { $Net::SSLeay::VERSION },
  3446. 'Term::ReadKey' => sub { $Term::ReadKey::VERSION },
  3447. 'Test::MockObject' => sub { $Test::MockObject::VERSION },
  3448. 'Time::HiRes' => sub { $Time::HiRes::VERSION },
  3449. 'Unicode::String' => sub { $Unicode::String::VERSION },
  3450. 'URI::Escape' => sub { $URI::Escape::VERSION },
  3451. #'Lalala' => sub { $Lalala::VERSION },
  3452. ) ;
  3453. foreach my $module_name ( sort keys %modulesversion ) {
  3454. # trick from http://www.perlmonks.org/?node_id=152122
  3455. my $file_name = $module_name . '.pm' ;
  3456. $file_name =~s,::,/,xmgs; # Foo::Bar::Baz => Foo/Bar/Baz.pm
  3457. my $v ;
  3458. eval {
  3459. require $file_name ;
  3460. $v = defined $modulesversion{ $module_name } ? $modulesversion{ $module_name }->() : q{?} ;
  3461. } or $v = q{Not installed} ;
  3462. push @list_version, module_version_str( $module_name, $v ) ;
  3463. }
  3464. return( @list_version ) ;
  3465. }
  3466. sub tests_command_line_nopassword
  3467. {
  3468. note( 'Entering tests_command_line_nopassword()' ) ;
  3469. ok( q{} eq command_line_nopassword(), 'command_line_nopassword void' );
  3470. my $mysync = {} ;
  3471. ok( '--blabla' eq command_line_nopassword( $mysync, '--blabla' ), 'command_line_nopassword --blabla' );
  3472. #myprint( command_line_nopassword((qw{ --password1 secret1 })), "\n" ) ;
  3473. ok( '--password1 MASKED' eq command_line_nopassword( $mysync, qw{ --password1 secret1}), 'command_line_nopassword --password1' );
  3474. ok( '--blabla --password1 MASKED --blibli'
  3475. eq command_line_nopassword( $mysync, qw{ --blabla --password1 secret1 --blibli } ), 'command_line_nopassword --password1 --blibli' );
  3476. $mysync->{showpasswords} = 1 ;
  3477. ok( q{} eq command_line_nopassword(), 'command_line_nopassword void' );
  3478. ok( '--blabla' eq command_line_nopassword( $mysync, '--blabla'), 'command_line_nopassword --blabla' );
  3479. #myprint( command_line_nopassword((qw{ --password1 secret1 })), "\n" ) ;
  3480. ok( '--password1 secret1' eq command_line_nopassword( $mysync, qw{ --password1 secret1} ), 'command_line_nopassword --password1' );
  3481. ok( '--blabla --password1 secret1 --blibli'
  3482. eq command_line_nopassword( $mysync, qw{ --blabla --password1 secret1 --blibli } ), 'command_line_nopassword --password1 --blibli' );
  3483. note( 'Leaving tests_command_line_nopassword()' ) ;
  3484. return ;
  3485. }
  3486. # Construct a command line copy with passwords replaced by MASKED.
  3487. sub command_line_nopassword
  3488. {
  3489. my $mysync = shift @ARG ;
  3490. my @argv = @ARG ;
  3491. my @argv_nopassword ;
  3492. if ( $mysync->{ cmdcgi } ) {
  3493. @argv_nopassword = mask_password_value( @{ $mysync->{ cmdcgi } } ) ;
  3494. return( "@argv_nopassword" ) ;
  3495. }
  3496. if ( $mysync->{showpasswords} )
  3497. {
  3498. return( "@argv" ) ;
  3499. }
  3500. @argv_nopassword = mask_password_value( @argv ) ;
  3501. return("@argv_nopassword") ;
  3502. }
  3503. sub mask_password_value
  3504. {
  3505. my @argv = @ARG ;
  3506. my @argv_nopassword ;
  3507. while ( @argv ) {
  3508. my $arg = shift @argv ; # option name or value
  3509. if ( $arg =~ m/-password[12]/x ) {
  3510. shift @argv ; # password value
  3511. push @argv_nopassword, $arg, 'MASKED' ; # option name and fake value
  3512. }else{
  3513. push @argv_nopassword, $arg ; # same option or value
  3514. }
  3515. }
  3516. return @argv_nopassword ;
  3517. }
  3518. sub tests_get_stdin_masked
  3519. {
  3520. note( 'Entering tests_get_stdin_masked()' ) ;
  3521. is( q{}, get_stdin_masked( ), 'get_stdin_masked: no args' ) ;
  3522. is( q{}, get_stdin_masked( 'Please ENTER: ' ), 'get_stdin_masked: ENTER' ) ;
  3523. note( 'Leaving tests_get_stdin_masked()' ) ;
  3524. return ;
  3525. }
  3526. #######################################################
  3527. # The issue is that prompt() does not prompt the prompt
  3528. # when the program is used like
  3529. # { sleep 2 ; echo blablabla ; } | ./imapsync ...--host1 lo --user1 tata --host2 lo --user2 titi
  3530. # use IO::Prompter ;
  3531. sub get_stdin_masked
  3532. {
  3533. my $prompt = shift || 'Say something: ' ;
  3534. local @ARGV = () ;
  3535. my $input = prompt(
  3536. -prompt => $prompt,
  3537. -echo => '*',
  3538. ) ;
  3539. #myprint( "You said: $input\n" ) ;
  3540. return $input ;
  3541. }
  3542. sub ask_for_password_new
  3543. {
  3544. my $prompt = shift ;
  3545. my $password = get_stdin_masked( $prompt ) ;
  3546. return $password ;
  3547. }
  3548. #########################################################
  3549. sub ask_for_password
  3550. {
  3551. my $prompt = shift ;
  3552. myprint( $prompt ) ;
  3553. Term::ReadKey::ReadMode( 2 ) ;
  3554. ## no critic (InputOutput::ProhibitExplicitStdin)
  3555. my $password = <STDIN> ;
  3556. chomp $password ;
  3557. myprint( "\nGot it\n" ) ;
  3558. Term::ReadKey::ReadMode( 0 ) ;
  3559. return $password ;
  3560. }
  3561. # Have to refactor get_password1() get_password2()
  3562. # to have only get_password() and two calls
  3563. sub get_password1
  3564. {
  3565. my $mysync = shift ;
  3566. $mysync->{password1}
  3567. || $mysync->{ passfile1 }
  3568. || 'PREAUTH' eq $authmech1
  3569. || 'EXTERNAL' eq $authmech1
  3570. || $ENV{IMAPSYNC_PASSWORD1}
  3571. || do
  3572. {
  3573. myprint( << 'FIN_PASSFILE' ) ;
  3574. If you are afraid of giving password on the command line arguments, you can put the
  3575. password of user1 in a file named file1 and use "--passfile1 file1" instead of typing it.
  3576. Then give this file restrictive permissions with the command "chmod 600 file1".
  3577. An other solution is to set the environment variable IMAPSYNC_PASSWORD1
  3578. FIN_PASSFILE
  3579. my $user = $authuser1 || $mysync->{user1} ;
  3580. my $host = $mysync->{host1} ;
  3581. my $prompt = "What's the password for $user" . ' at ' . "$host? (not visible while you type, then enter RETURN) " ;
  3582. $mysync->{password1} = ask_for_password( $prompt ) ;
  3583. } ;
  3584. if ( defined $mysync->{ passfile1 } ) {
  3585. if ( ! -e -r $mysync->{ passfile1 } ) {
  3586. myprint( "Failure: file from parameter --passfile1 $mysync->{ passfile1 } does not exist or is not readable\n" ) ;
  3587. exit_clean( $mysync, $EX_NOINPUT ) ;
  3588. }
  3589. # passfile1 readable
  3590. $mysync->{password1} = firstline ( $mysync->{ passfile1 } ) ;
  3591. return ;
  3592. }
  3593. if ( $ENV{IMAPSYNC_PASSWORD1} ) {
  3594. $mysync->{password1} = $ENV{IMAPSYNC_PASSWORD1} ;
  3595. return ;
  3596. }
  3597. return ;
  3598. }
  3599. sub get_password2
  3600. {
  3601. my $mysync = shift ;
  3602. $mysync->{password2}
  3603. || $mysync->{ passfile2 }
  3604. || 'PREAUTH' eq $authmech2
  3605. || 'EXTERNAL' eq $authmech2
  3606. || $ENV{IMAPSYNC_PASSWORD2}
  3607. || do
  3608. {
  3609. myprint( << 'FIN_PASSFILE' ) ;
  3610. If you are afraid of giving password on the command line arguments, you can put the
  3611. password of user2 in a file named file2 and use "--passfile2 file2" instead of typing it.
  3612. Then give this file restrictive permissions with the command "chmod 600 file2".
  3613. An other solution is to set the environment variable IMAPSYNC_PASSWORD2
  3614. FIN_PASSFILE
  3615. my $user = $authuser2 || $mysync->{user2} ;
  3616. my $host = $mysync->{host2} ;
  3617. my $prompt = "What's the password for $user" . ' at ' . "$host? (not visible while you type, then enter RETURN) " ;
  3618. $mysync->{password2} = ask_for_password( $prompt ) ;
  3619. } ;
  3620. if ( defined $mysync->{ passfile2 } ) {
  3621. if ( ! -e -r $mysync->{ passfile2 } ) {
  3622. myprint( "Failure: file from parameter --passfile2 $mysync->{ passfile2 } does not exist or is not readable\n" ) ;
  3623. exit_clean( $mysync, $EX_NOINPUT ) ;
  3624. }
  3625. # passfile2 readable
  3626. $mysync->{password2} = firstline ( $mysync->{ passfile2 } ) ;
  3627. return ;
  3628. }
  3629. if ( $ENV{IMAPSYNC_PASSWORD2} ) {
  3630. $mysync->{password2} = $ENV{IMAPSYNC_PASSWORD2} ;
  3631. return ;
  3632. }
  3633. return ;
  3634. }
  3635. sub remove_tmp_files
  3636. {
  3637. my $mysync = shift or return ;
  3638. $mysync->{pidfile} or return ;
  3639. if ( -e $mysync->{pidfile} ) {
  3640. unlink $mysync->{pidfile} ;
  3641. }
  3642. return ;
  3643. }
  3644. sub cleanup_before_exit
  3645. {
  3646. my $mysync = shift ;
  3647. remove_tmp_files( $mysync ) ;
  3648. if ( $mysync->{imap1} and $mysync->{imap1}->IsConnected() )
  3649. {
  3650. myprint( "Disconnecting from host1 $mysync->{ host1 } user1 $mysync->{ user1 }\n" ) ;
  3651. $mysync->{imap1}->logout( ) ;
  3652. }
  3653. if ( $mysync->{imap2} and $mysync->{imap2}->IsConnected() )
  3654. {
  3655. myprint( "Disconnecting from host2 $mysync->{ host2 } user2 $mysync->{ user2 }\n" ) ;
  3656. $mysync->{imap2}->logout( ) ;
  3657. }
  3658. if ( $mysync->{log} ) {
  3659. myprint( "Log file is $mysync->{logfile} ( to change it, use --logfile filepath ; or use --nolog to turn off logging )\n" ) ;
  3660. }
  3661. if ( $mysync->{log} and $mysync->{logfile_handle} ) {
  3662. #myprint( "Closing $mysync->{ logfile }\n" ) ;
  3663. close $mysync->{logfile_handle} ;
  3664. }
  3665. return ;
  3666. }
  3667. sub exit_clean
  3668. {
  3669. my $mysync = shift @ARG ;
  3670. my $status = shift @ARG ;
  3671. my @messages = @ARG ;
  3672. if ( @messages )
  3673. {
  3674. myprint( @messages ) ;
  3675. }
  3676. myprint( "Exiting with return value $status\n" ) ;
  3677. cleanup_before_exit( $mysync ) ;
  3678. exit $status ;
  3679. }
  3680. sub missing_option
  3681. {
  3682. my $mysync = shift ;
  3683. my $option = shift ;
  3684. exit_clean( $mysync, $EX_USAGE, "$option option is mandatory, for help run $PROGRAM_NAME --help\n" ) ;
  3685. return ;
  3686. }
  3687. sub catch_ignore
  3688. {
  3689. my $mysync = shift ;
  3690. my $signame = shift ;
  3691. my $sigcounter = ++$mysync->{ sigcounter }{ $signame } ;
  3692. myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ),
  3693. "). Received $sigcounter $signame signals so far. Thanks!\n" ) ;
  3694. stats( $mysync ) ;
  3695. return ;
  3696. }
  3697. sub catch_exit
  3698. {
  3699. my $mysync = shift ;
  3700. my $signame = shift || q{} ;
  3701. if ( $signame ) {
  3702. myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ),
  3703. "). Asked to terminate\n" ) ;
  3704. if ( $mysync->{stats} ) {
  3705. myprint( "Here are the final stats of this sync not completely finished so far\n" ) ;
  3706. stats( $mysync ) ;
  3707. myprint( "Ended by a signal $signame (my PID is $PROCESS_ID my PPID is ",
  3708. getppid( ), "). I am asked to terminate immediately.\n" ) ;
  3709. myprint( "You should resynchronize those accounts by running a sync again,\n",
  3710. "since some messages and entire folders might still be missing on host2.\n" ) ;
  3711. }
  3712. ## no critic (RequireLocalizedPunctuationVars)
  3713. $SIG{ $signame } = 'DEFAULT'; # restore default action
  3714. # kill myself with $signame
  3715. # https://www.cons.org/cracauer/sigint.html
  3716. myprint( "Killing myself with signal $signame\n" ) ;
  3717. cleanup_before_exit( $mysync ) ;
  3718. kill( $signame, $PROCESS_ID ) ;
  3719. }
  3720. else
  3721. {
  3722. exit_clean( $mysync, $EXIT_BY_SIGNAL ) ;
  3723. }
  3724. return ;
  3725. }
  3726. sub catch_print
  3727. {
  3728. my $mysync = shift ;
  3729. my $signame = shift ;
  3730. my $sigcounter = ++$mysync->{ sigcounter }{ $signame } ;
  3731. myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ),
  3732. "). Received $sigcounter $signame signals so far. Thanks!\n" ) ;
  3733. return ;
  3734. }
  3735. sub catch_reconnect
  3736. {
  3737. my $mysync = shift ;
  3738. my $signame = shift ;
  3739. if ( here_twice( $mysync ) ) {
  3740. myprint( "Got two signals $signame within $INTERVAL_TO_EXIT seconds. Exiting...\n" ) ;
  3741. catch_exit( $mysync, $signame ) ;
  3742. }else{
  3743. myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ), ")\n",
  3744. "Hit 2 ctr-c within 2 seconds to exit the program\n",
  3745. "Hit only 1 ctr-c to reconnect to both imap servers\n",
  3746. ) ;
  3747. myprint( "For now only one signal $signame within $INTERVAL_TO_EXIT seconds.\n" ) ;
  3748. if ( ! defined $mysync->{imap1} ) { return ; }
  3749. if ( ! defined $mysync->{imap2} ) { return ; }
  3750. myprint( "Info: reconnecting to host1 imap server $mysync->{host1}\n" ) ;
  3751. $mysync->{imap1}->State( Mail::IMAPClient::Unconnected ) ;
  3752. $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT} += 1 ;
  3753. if ( $mysync->{imap1}->reconnect( ) )
  3754. {
  3755. myprint( "Info: reconnected to host1 imap server $mysync->{host1}\n" ) ;
  3756. }
  3757. else
  3758. {
  3759. exit_clean( $mysync, $EXIT_CONNECTION_FAILURE ) ;
  3760. }
  3761. myprint( "Info: reconnecting to host2 imap server\n" ) ;
  3762. $mysync->{imap2}->State( Mail::IMAPClient::Unconnected ) ;
  3763. $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT} += 1 ;
  3764. if ( $mysync->{imap2}->reconnect( ) )
  3765. {
  3766. myprint( "Info: reconnected to host2 imap server $mysync->{host2}\n" ) ;
  3767. }
  3768. else
  3769. {
  3770. exit_clean( $mysync, $EXIT_CONNECTION_FAILURE ) ;
  3771. }
  3772. myprint( "Info: reconnected to both imap servers\n" ) ;
  3773. }
  3774. return ;
  3775. }
  3776. sub tests_reconnect_12_if_needed
  3777. {
  3778. note( 'Entering tests_reconnect_12_if_needed()' ) ;
  3779. my $mysync ;
  3780. $mysync->{imap1} = Mail::IMAPClient->new( ) ;
  3781. $mysync->{imap2} = Mail::IMAPClient->new( ) ;
  3782. $mysync->{imap1}->Server( 'test1.lamiral.info' ) ;
  3783. $mysync->{imap2}->Server( 'test2.lamiral.info' ) ;
  3784. is( 2, reconnect_12_if_needed( $mysync ), 'reconnect_12_if_needed: test1&test2 .lamiral.info => 1' ) ;
  3785. is( 1, $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_12_if_needed: test1.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ;
  3786. is( 1, $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_12_if_needed: test2.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ;
  3787. note( 'Leaving tests_reconnect_12_if_needed()' ) ;
  3788. return ;
  3789. }
  3790. sub reconnect_12_if_needed
  3791. {
  3792. my $mysync = shift ;
  3793. #return 2 ;
  3794. if ( ! reconnect_if_needed( $mysync->{imap1} ) ) {
  3795. return ;
  3796. }
  3797. if ( ! reconnect_if_needed( $mysync->{imap2} ) ) {
  3798. return ;
  3799. }
  3800. # both were good
  3801. return 2 ;
  3802. }
  3803. sub tests_reconnect_if_needed
  3804. {
  3805. note( 'Entering tests_reconnect_if_needed()' ) ;
  3806. my $myimap ;
  3807. is( undef, reconnect_if_needed( ), 'reconnect_if_needed: no args => undef' ) ;
  3808. is( undef, reconnect_if_needed( $myimap ), 'reconnect_if_needed: undef arg => undef' ) ;
  3809. $myimap = Mail::IMAPClient->new( ) ;
  3810. $myimap->Debug( 1 ) ;
  3811. is( undef, reconnect_if_needed( $myimap ), 'reconnect_if_needed: empty new Mail::IMAPClient => undef' ) ;
  3812. $myimap->Server( 'test.lamiral.info' ) ;
  3813. is( 1, reconnect_if_needed( $myimap ), 'reconnect_if_needed: test.lamiral.info => 1' ) ;
  3814. is( 1, $myimap->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_if_needed: test.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ;
  3815. note( 'Leaving tests_reconnect_if_needed()' ) ;
  3816. return ;
  3817. }
  3818. sub reconnect_if_needed
  3819. {
  3820. # return undef upon failure.
  3821. # return 1 upon connection success, with or without reconnection.
  3822. my $imap = shift ;
  3823. if ( ! defined $imap ) { return ; }
  3824. if ( ! $imap->Server( ) ) { return ; }
  3825. if ( $imap->IsUnconnected( ) ) {
  3826. $imap->{IMAPSYNC_RECONNECT_COUNT} += 1 ;
  3827. if ( $imap->reconnect( ) ) {
  3828. return 1 ;
  3829. }
  3830. }else{
  3831. return 1 ;
  3832. }
  3833. # A last forced one
  3834. $imap->State( Mail::IMAPClient::Unconnected ) ;
  3835. $imap->reconnect( ) ;
  3836. $imap->{IMAPSYNC_RECONNECT_COUNT} += 1 ;
  3837. if ( $imap->noop ) {
  3838. # NOOP is ok
  3839. return 1 ;
  3840. }
  3841. return ;
  3842. }
  3843. sub here_twice
  3844. {
  3845. my $mysync = shift ;
  3846. my $now = time ;
  3847. my $previous = $mysync->{lastcatch} || 0 ;
  3848. $mysync->{lastcatch} = $now ;
  3849. if ( $INTERVAL_TO_EXIT >= $now - $previous ) {
  3850. return $TRUE ;
  3851. }else{
  3852. return $FALSE ;
  3853. }
  3854. }
  3855. sub justconnect
  3856. {
  3857. my $mysync = shift ;
  3858. $mysync->{imap1} = connect_imap( $mysync->{host1}, $mysync->{port1}, $debugimap1,
  3859. $mysync->{ssl1}, $mysync->{tls1}, 'Host1', $mysync->{h1}->{timeout}, $mysync->{h1} ) ;
  3860. $mysync->{imap2} = connect_imap( $mysync->{host2}, $mysync->{port2}, $debugimap2,
  3861. $mysync->{ssl2}, $mysync->{tls2}, 'Host2', $mysync->{h2}->{timeout}, $mysync->{h2} ) ;
  3862. $mysync->{imap1}->logout( ) ;
  3863. $mysync->{imap2}->logout( ) ;
  3864. return ;
  3865. }
  3866. sub skip_macosx
  3867. {
  3868. return ;
  3869. return( 'macosx.polarhome.com' eq hostname() ) ;
  3870. }
  3871. sub tests_mailimapclient_connect
  3872. {
  3873. note( 'Entering tests_mailimapclient_connect()' ) ;
  3874. my $imap ;
  3875. # ipv4
  3876. ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect ipv4: new' ) ;
  3877. is( 'Mail::IMAPClient', ref( $imap ), 'mailimapclient_connect ipv4: ref is Mail::IMAPClient' ) ;
  3878. # Mail::IMAPClient 3.40 die on this... So we skip it, thanks to "mature" IO::Socket::IP
  3879. # is( undef, $imap->connect( ), 'mailimapclient_connect ipv4: connect with no server => failure' ) ;
  3880. is( 'test.lamiral.info', $imap->Server( 'test.lamiral.info' ), 'mailimapclient_connect ipv4: setting Server(test.lamiral.info)' ) ;
  3881. is( 1, $imap->Debug( 1 ), 'mailimapclient_connect ipv4: setting Debug( 1 )' ) ;
  3882. is( 143, $imap->Port( 143 ), 'mailimapclient_connect ipv4: setting Port( 143 )' ) ;
  3883. is( 3, $imap->Timeout( 3 ), 'mailimapclient_connect ipv4: setting Timout( 30 )' ) ;
  3884. like( ref( $imap->connect( ) ), qr/IO::Socket::INET|IO::Socket::IP/, 'mailimapclient_connect ipv4: connect to test.lamiral.info' ) ;
  3885. like( $imap->logout( ), qr/Mail::IMAPClient/, 'mailimapclient_connect ipv4: logout' ) ;
  3886. is( undef, undef $imap, 'mailimapclient_connect ipv4: free variable' ) ;
  3887. # ipv4 + ssl
  3888. ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect ipv4 + ssl: new' ) ;
  3889. is( 'test.lamiral.info', $imap->Server( 'test.lamiral.info' ), 'mailimapclient_connect ipv4 + ssl: setting Server(test.lamiral.info)' ) ;
  3890. is( 1, $imap->Debug( 1 ), 'mailimapclient_connect ipv4 + ssl: setting Debug( 1 )' ) ;
  3891. ok( $imap->Ssl( [ SSL_verify_mode => SSL_VERIFY_NONE ] ), 'mailimapclient_connect ipv4 + ssl: setting Ssl( SSL_VERIFY_NONE )' ) ;
  3892. is( 993, $imap->Port( 993 ), 'mailimapclient_connect ipv4 + ssl: setting Port( 993 )' ) ;
  3893. like( ref( $imap->connect( ) ), qr/IO::Socket::SSL/, 'mailimapclient_connect ipv4 + ssl: connect to test.lamiral.info' ) ;
  3894. is( $imap->logout( ), undef, 'mailimapclient_connect ipv4 + ssl: logout in ssl causes failure' ) ;
  3895. is( undef, undef $imap, 'mailimapclient_connect ipv4 + ssl: free variable' ) ;
  3896. # ipv6 + ssl
  3897. ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect ipv6 + ssl: new' ) ;
  3898. is( 'ks2ipv6.lamiral.info', $imap->Server( 'ks2ipv6.lamiral.info' ), 'mailimapclient_connect ipv6 + ssl: setting Server(ks2ipv6.lamiral.info)' ) ;
  3899. ok( $imap->Ssl( [ SSL_verify_mode => SSL_VERIFY_NONE ] ), 'mailimapclient_connect ipv6 + ssl: setting Ssl( SSL_VERIFY_NONE )' ) ;
  3900. is( 993, $imap->Port( 993 ), 'mailimapclient_connect ipv6 + ssl: setting Port( 993 )' ) ;
  3901. SKIP: {
  3902. if (
  3903. 'CUILLERE' eq hostname()
  3904. or
  3905. skip_macosx()
  3906. or
  3907. -e '/.dockerenv'
  3908. )
  3909. {
  3910. skip( 'Tests avoided on CUILLERE can not do ipv6', 2 ) ;
  3911. }
  3912. like( ref( $imap->connect( ) ), qr/IO::Socket::SSL/, 'mailimapclient_connect ipv6 + ssl: connect to ks2ipv6.lamiral.info' ) ;
  3913. is( $imap->logout( ), undef, 'mailimapclient_connect ipv6 + ssl: logout in ssl causes failure' ) ;
  3914. }
  3915. is( undef, undef $imap, 'mailimapclient_connect ipv6 + ssl: free variable' ) ;
  3916. note( 'Leaving tests_mailimapclient_connect()' ) ;
  3917. return ;
  3918. }
  3919. sub tests_mailimapclient_connect_bug
  3920. {
  3921. note( 'Entering tests_mailimapclient_connect_bug()' ) ;
  3922. my $imap ;
  3923. # ipv6
  3924. ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect_bug ipv6: new' ) ;
  3925. is( 'ks2ipv6.lamiral.info', $imap->Server( 'ks2ipv6.lamiral.info' ), 'mailimapclient_connect_bug ipv6: setting Server(ks2ipv6.lamiral.info)' ) ;
  3926. is( 143, $imap->Port( 143 ), 'mailimapclient_connect_bug ipv6: setting Port( 993 )' ) ;
  3927. SKIP: {
  3928. if (
  3929. 'CUILLERE' eq hostname()
  3930. or
  3931. skip_macosx()
  3932. or
  3933. -e '/.dockerenv'
  3934. )
  3935. {
  3936. skip( 'Tests avoided on CUILLERE can not do ipv6', 1 ) ;
  3937. }
  3938. like( ref( $imap->connect( ) ), qr/IO::Socket::INET/, 'mailimapclient_connect_bug ipv6: connect to ks2ipv6.lamiral.info' )
  3939. or diag( 'mailimapclient_connect_bug ipv6: ', $imap->LastError( ), $!, ) ;
  3940. }
  3941. #is( $imap->logout( ), undef, 'mailimapclient_connect_bug ipv6: logout in ssl causes failure' ) ;
  3942. is( undef, undef $imap, 'mailimapclient_connect_bug ipv6: free variable' ) ;
  3943. note( 'Leaving tests_mailimapclient_connect_bug()' ) ;
  3944. return ;
  3945. }
  3946. sub tests_connect_socket
  3947. {
  3948. note( 'Entering tests_connect_socket()' ) ;
  3949. is( undef, connect_socket( ), 'connect_socket: no args' ) ;
  3950. my $socket ;
  3951. my $imap ;
  3952. SKIP: {
  3953. if (
  3954. 'CUILLERE' eq hostname()
  3955. or
  3956. skip_macosx()
  3957. or
  3958. -e '/.dockerenv'
  3959. )
  3960. {
  3961. skip( 'Tests avoided on CUILLERE/macosx.polarhome.com/docker cannot do ipv6', 2 ) ;
  3962. }
  3963. $socket = IO::Socket::INET6->new(
  3964. PeerAddr => 'ks2ipv6.lamiral.info',
  3965. PeerPort => 143,
  3966. ) ;
  3967. ok( $imap = connect_socket( $socket ), 'connect_socket: ks2ipv6.lamiral.info port 143 IO::Socket::INET6' ) ;
  3968. #$imap->Debug( 1 ) ;
  3969. # myprint( $imap->capability( ) ) ;
  3970. if ( $imap ) {
  3971. $imap->logout( ) ;
  3972. }
  3973. #$IO::Socket::SSL::DEBUG = 4 ;
  3974. $socket = IO::Socket::SSL->new(
  3975. PeerHost => 'ks2ipv6.lamiral.info',
  3976. PeerPort => 993,
  3977. SSL_verify_mode => SSL_VERIFY_NONE,
  3978. ) ;
  3979. # myprint( $socket ) ;
  3980. ok( $imap = connect_socket( $socket ), 'connect_socket: ks2ipv6.lamiral.info port 993 IO::Socket::SSL' ) ;
  3981. #$imap->Debug( 1 ) ;
  3982. # myprint( $imap->capability( ) ) ;
  3983. # $socket->close( ) ;
  3984. if ( $imap ) {
  3985. $socket->close( ) ;
  3986. }
  3987. #$socket->close(SSL_no_shutdown => 1) ;
  3988. #$imap->logout( ) ;
  3989. #myprint( "\n" ) ;
  3990. #$imap->logout( ) ;
  3991. }
  3992. note( 'Leaving tests_connect_socket()' ) ;
  3993. return ;
  3994. }
  3995. sub connect_socket
  3996. {
  3997. my( $socket ) = @ARG ;
  3998. if ( ! defined $socket ) { return ; }
  3999. my $host = $socket->peerhost( ) ;
  4000. my $port = $socket->peerport( ) ;
  4001. #print "socket->peerhost: ", $socket->peerhost( ), "\n" ;
  4002. #print "socket->peerport: ", $socket->peerport( ), "\n" ;
  4003. my $imap = Mail::IMAPClient->new( ) ;
  4004. $imap->Socket( $socket ) ;
  4005. my $banner = $imap->Results()->[0] ;
  4006. #myprint( "banner: $banner" ) ;
  4007. return $imap ;
  4008. }
  4009. sub tests_probe_imapssl
  4010. {
  4011. note( 'Entering tests_probe_imapssl()' ) ;
  4012. is( undef, probe_imapssl( ), 'probe_imapssl: no args => undef' ) ;
  4013. is( undef, probe_imapssl( 'unknown' ), 'probe_imapssl: unknown => undef' ) ;
  4014. SKIP: {
  4015. if (
  4016. 'CUILLERE' eq hostname()
  4017. or
  4018. skip_macosx()
  4019. or
  4020. -e '/.dockerenv'
  4021. )
  4022. {
  4023. skip( 'Tests avoided on CUILLERE/macosx.polarhome.com/docker cannot do ipv6', 2 ) ;
  4024. }
  4025. like( probe_imapssl( 'ks2ipv6.lamiral.info' ), qr/^\* OK/, 'probe_imapssl: ks2ipv6.lamiral.info matches "* OK"' ) ;
  4026. like( probe_imapssl( 'imap.gmail.com' ), qr/^\* OK/, 'probe_imapssl: imap.gmail.com matches "* OK"' ) ;
  4027. } ;
  4028. like( probe_imapssl( 'test1.lamiral.info' ), qr/^\* OK/, 'probe_imapssl: test1.lamiral.info matches "* OK"' ) ;
  4029. note( 'Leaving tests_probe_imapssl()' ) ;
  4030. return ;
  4031. }
  4032. sub probe_imapssl
  4033. {
  4034. my $host = shift ;
  4035. if ( ! $host ) { return ; }
  4036. my $socket = IO::Socket::SSL->new(
  4037. PeerHost => $host,
  4038. PeerPort => $IMAP_SSL_PORT,
  4039. SSL_verify_mode => SSL_VERIFY_NONE,
  4040. ) ;
  4041. #print "$socket\n" ;
  4042. if ( ! $socket ) { return ; }
  4043. my $banner ;
  4044. $socket->sysread( $banner, 65_536 ) ;
  4045. #print "$banner" ;
  4046. $socket->close( ) ;
  4047. return $banner ;
  4048. }
  4049. sub connect_imap
  4050. {
  4051. my( $host, $port, $mydebugimap, $ssl, $tls, $Side, $mytimeout, $h ) = @_ ;
  4052. my $imap = Mail::IMAPClient->new( ) ;
  4053. if ( $ssl ) { set_ssl( $imap, $h ) }
  4054. $imap->Server( $host ) ;
  4055. $imap->Port( $port ) ;
  4056. $imap->Debug( $mydebugimap ) ;
  4057. $imap->Timeout( $mytimeout ) ;
  4058. my $side = lc $Side ;
  4059. myprint( "$Side: connecting on $side [$host] port [$port]\n" ) ;
  4060. $imap->connect( )
  4061. or exit_clean( $sync, $EXIT_CONNECTION_FAILURE, "$Side: Can not open imap connection on [$host]: " . $imap->LastError . " $OS_ERROR\n" ) ;
  4062. myprint( "$Side IP address: ", $imap->Socket->peerhost(), "\n" ) ;
  4063. my $banner = $imap->Results()->[0] ;
  4064. myprint( "$Side banner: $banner" ) ;
  4065. myprint( "$Side capability: ", join(q{ }, @{ $imap->capability() || [] }), "\n" ) ;
  4066. if ( $tls ) {
  4067. set_tls( $imap, $h ) ;
  4068. $imap->starttls( )
  4069. or exit_clean( $sync, $EXIT_TLS_FAILURE, "$Side: Can not go to tls encryption on $side [$host]:", $imap->LastError, "\n" ) ;
  4070. myprint( "$Side: Socket successfuly converted to SSL\n" ) ;
  4071. }
  4072. return( $imap ) ;
  4073. }
  4074. sub login_imap
  4075. {
  4076. my @allargs = @_ ;
  4077. my(
  4078. $host, $port, $user, $domain, $password,
  4079. $mydebugimap, $mytimeout, $fastio,
  4080. $ssl, $tls, $authmech, $authuser, $reconnectretry,
  4081. $proxyauth, $uid, $split, $Side, $h, $mysync ) = @allargs ;
  4082. my $side = lc $Side ;
  4083. myprint( "$Side: connecting and login on $side [$host] port [$port] with user [$user]\n" ) ;
  4084. my $imap = init_imap( @allargs ) ;
  4085. $imap->connect()
  4086. or exit_clean( $mysync, $EXIT_CONNECTION_FAILURE, "$Side failure: can not open imap connection on $side [$host] with user [$user]: " . $imap->LastError . " $OS_ERROR\n" ) ;
  4087. myprint( "$Side IP address: ", $imap->Socket->peerhost(), "\n" ) ;
  4088. my $banner = $imap->Results()->[0] ;
  4089. myprint( "$Side banner: $banner" ) ;
  4090. myprint( "$Side capability before authentication: ", join(q{ }, @{ $imap->capability() || [] }), "\n" ) ;
  4091. if ( (! $ssl) and (! defined $tls ) and $imap->has_capability( 'STARTTLS' ) ) {
  4092. myprint( "$Side: going to ssl because STARTTLS is in CAPABILITY. Use --notls1 or --notls2 to avoid that behavior\n" ) ;
  4093. $tls = 1 ;
  4094. }
  4095. if ( $authmech eq 'PREAUTH' ) {
  4096. if ( $imap->IsAuthenticated( ) ) {
  4097. $imap->Socket ;
  4098. myprintf("%s: Assuming PREAUTH for %s\n", $Side, $imap->Server ) ;
  4099. }else{
  4100. exit_clean( $mysync, $EXIT_AUTHENTICATION_FAILURE, "$Side failure: error login on $side [$host] with user [$user] auth [PREAUTH]" ) ;
  4101. }
  4102. }
  4103. if ( $tls ) {
  4104. set_tls( $imap, $h ) ;
  4105. $imap->starttls( )
  4106. or exit_clean( $mysync, $EXIT_TLS_FAILURE, "$Side failure: Can not go to tls encryption on $side [$host]:", $imap->LastError, "\n" ) ;
  4107. myprint( "$Side: Socket successfuly converted to SSL\n" ) ;
  4108. }
  4109. authenticate_imap( $imap, @allargs ) ;
  4110. myprint( "$Side: success login on [$host] with user [$user] auth [$authmech]\n" ) ;
  4111. return( $imap ) ;
  4112. }
  4113. sub authenticate_imap
  4114. {
  4115. my( $imap,
  4116. $host, $port, $user, $domain, $password,
  4117. $mydebugimap, $mytimeout, $fastio,
  4118. $ssl, $tls, $authmech, $authuser, $reconnectretry,
  4119. $proxyauth, $uid, $split, $Side, $h, $mysync ) = @_ ;
  4120. check_capability( $imap, $authmech, $Side ) ;
  4121. $imap->User( $user ) ;
  4122. $imap->Domain( $domain ) if ( defined $domain ) ;
  4123. $imap->Authuser( $authuser ) ;
  4124. $imap->Password( $password ) ;
  4125. if ( 'X-MASTERAUTH' eq $authmech )
  4126. {
  4127. xmasterauth( $imap ) ;
  4128. return ;
  4129. }
  4130. if ( $proxyauth ) {
  4131. $imap->Authmechanism(q{}) ;
  4132. $imap->User( $authuser ) ;
  4133. } else {
  4134. $imap->Authmechanism( $authmech ) unless ( $authmech eq 'LOGIN' or $authmech eq 'PREAUTH' ) ;
  4135. }
  4136. $imap->Authcallback(\&xoauth) if ( 'XOAUTH' eq $authmech ) ;
  4137. $imap->Authcallback(\&xoauth2) if ( 'XOAUTH2' eq $authmech ) ;
  4138. $imap->Authcallback(\&plainauth) if ( ( 'PLAIN' eq $authmech ) or ( 'EXTERNAL' eq $authmech ) ) ;
  4139. unless ( $authmech eq 'PREAUTH' or $authmech eq 'X-MASTERAUTH' or $imap->login( ) ) {
  4140. my $info = "$Side failure: Error login on [$host] with user [$user] auth" ;
  4141. my $einfo = $imap->LastError || @{$imap->History}[$LAST] ;
  4142. chomp $einfo ;
  4143. my $error = "$info [$authmech]: $einfo\n" ;
  4144. if ( $authmech eq 'LOGIN' or $imap->IsUnconnected( ) or $authuser ) {
  4145. exit_clean( $mysync, $EXIT_AUTHENTICATION_FAILURE, $error ) ;
  4146. }else{
  4147. myprint( $error ) ;
  4148. }
  4149. myprint( "$Side info: trying LOGIN Auth mechanism on [$host] with user [$user]\n" ) ;
  4150. $imap->Authmechanism(q{}) ;
  4151. $imap->login() or
  4152. exit_clean( $mysync, $EXIT_AUTHENTICATION_FAILURE, "$info [LOGIN]: ", $imap->LastError, "\n") ;
  4153. }
  4154. if ( $proxyauth ) {
  4155. if ( ! $imap->proxyauth( $user ) ) {
  4156. my $info = "$Side failure: Error doing proxyauth as user [$user] on [$host] using proxy-login as [$authuser]" ;
  4157. my $einfo = $imap->LastError || @{$imap->History}[$LAST] ;
  4158. chomp $einfo ;
  4159. exit_clean( $mysync, $EXIT_AUTHENTICATION_FAILURE, "$info: $einfo\n" ) ;
  4160. }
  4161. }
  4162. return ;
  4163. }
  4164. sub check_capability
  4165. {
  4166. my( $imap, $authmech, $Side ) = @_ ;
  4167. if ( $imap->has_capability( "AUTH=$authmech" )
  4168. or $imap->has_capability( $authmech ) )
  4169. {
  4170. myprintf("%s: %s says it has CAPABILITY for AUTHENTICATE %s\n",
  4171. $Side, $imap->Server, $authmech) ;
  4172. return ;
  4173. }
  4174. if ( $authmech eq 'LOGIN' )
  4175. {
  4176. # Well, the warning is so common and useless that I prefer to remove it
  4177. # No more "... says it has NO CAPABILITY for AUTHENTICATE LOGIN"
  4178. return ;
  4179. }
  4180. myprintf( "%s: %s says it has NO CAPABILITY for AUTHENTICATE %s\n",
  4181. $Side, $imap->Server, $authmech ) ;
  4182. if ( $authmech eq 'PLAIN' )
  4183. {
  4184. myprint( "$Side: frequently PLAIN is only supported with SSL, try --ssl or --tls options\n" ) ;
  4185. }
  4186. return ;
  4187. }
  4188. sub set_ssl
  4189. {
  4190. my ( $imap, $h ) = @_ ;
  4191. # SSL_version can be
  4192. # SSLv3 SSLv2 SSLv23 SSLv23:!SSLv2 (last one is the default in IO-Socket-SSL-1.953)
  4193. #
  4194. my $sslargs_hash = $h->{sslargs} ;
  4195. my $sslargs_default = {
  4196. SSL_verify_mode => $SSL_VERIFY_POLICY,
  4197. SSL_verifycn_scheme => 'imap',
  4198. SSL_cipher_list => 'DEFAULT:!DH',
  4199. } ;
  4200. # initiate with default values
  4201. my %sslargs_mix = %{ $sslargs_default } ;
  4202. # now override with passed values
  4203. @sslargs_mix{ keys %{ $sslargs_hash } } = values %{ $sslargs_hash } ;
  4204. # remove keys with undef values
  4205. foreach my $key ( keys %sslargs_mix ) {
  4206. delete $sslargs_mix{ $key } if ( not defined $sslargs_mix{ $key } ) ;
  4207. }
  4208. # back to an ARRAY
  4209. my @sslargs_mix = %sslargs_mix ;
  4210. #myprint( Data::Dumper->Dump( [ $sslargs_hash, $sslargs_default, \%sslargs_mix, \@sslargs_mix ] ) ) ;
  4211. $imap->Ssl( \@sslargs_mix ) ;
  4212. return ;
  4213. }
  4214. sub set_tls
  4215. {
  4216. my ( $imap, $h ) = @_ ;
  4217. my $sslargs_hash = $h->{sslargs} ;
  4218. my $sslargs_default = {
  4219. SSL_verify_mode => $SSL_VERIFY_POLICY,
  4220. SSL_cipher_list => 'DEFAULT:!DH',
  4221. } ;
  4222. # initiate with default values
  4223. my %sslargs_mix = %{ $sslargs_default } ;
  4224. # now override with passed values
  4225. @sslargs_mix{ keys %{ $sslargs_hash } } = values %{ $sslargs_hash } ;
  4226. # remove keys with undef values
  4227. foreach my $key ( keys %sslargs_mix ) {
  4228. delete $sslargs_mix{ $key } if ( not defined $sslargs_mix{ $key } ) ;
  4229. }
  4230. # back to an ARRAY
  4231. my @sslargs_mix = %sslargs_mix ;
  4232. $imap->Starttls( \@sslargs_mix ) ;
  4233. return ;
  4234. }
  4235. sub init_imap
  4236. {
  4237. my(
  4238. $host, $port, $user, $domain, $password,
  4239. $mydebugimap, $mytimeout, $fastio,
  4240. $ssl, $tls, $authmech, $authuser, $reconnectretry,
  4241. $proxyauth, $uid, $split, $Side, $h, $mysync ) = @_ ;
  4242. my ( $imap ) ;
  4243. $imap = Mail::IMAPClient->new() ;
  4244. if ( $ssl ) { set_ssl( $imap, $h ) }
  4245. if ( $tls ) { } # can not do set_tls() here because connect() will directly do a STARTTLS
  4246. $imap->Clear(1);
  4247. $imap->Server($host);
  4248. $imap->Port($port);
  4249. $imap->Fast_io($fastio);
  4250. $imap->Buffer($buffersize || $DEFAULT_BUFFER_SIZE);
  4251. $imap->Uid($uid);
  4252. $imap->Peek(1);
  4253. $imap->Debug($mydebugimap);
  4254. if ( $mysync->{ showpasswords } ) {
  4255. $imap->Showcredentials( 1 ) ;
  4256. }
  4257. defined $mytimeout and $imap->Timeout( $mytimeout ) ;
  4258. $imap->Reconnectretry( $reconnectretry ) if ( $reconnectretry ) ;
  4259. $imap->{IMAPSYNC_RECONNECT_COUNT} = 0 ;
  4260. $imap->Ignoresizeerrors( $allowsizemismatch ) ;
  4261. $split and $imap->Maxcommandlength( $SPLIT_FACTOR * $split ) ;
  4262. return( $imap ) ;
  4263. }
  4264. sub plainauth
  4265. {
  4266. my $code = shift;
  4267. my $imap = shift;
  4268. my $string = mysprintf("%s\x00%s\x00%s", $imap->User,
  4269. $imap->Authuser, $imap->Password);
  4270. return encode_base64("$string", q{});
  4271. }
  4272. # Copy from https://github.com/imapsync/imapsync/pull/25/files
  4273. # Changes "use" pragmas to "require".
  4274. # The openssl system call shall be replaced by pure Perl and
  4275. # https://metacpan.org/pod/Crypt::OpenSSL::PKCS12
  4276. # Now the Joaquin Lopez code:
  4277. #
  4278. # Used this as an example: https://gist.github.com/gsainio/6322375
  4279. #
  4280. # And this as a reference: https://developers.google.com/accounts/docs/OAuth2ServiceAccount
  4281. # (note there is an http/rest tab, where the real info is hidden away... went on a witch hunt
  4282. # until I noticed that...)
  4283. #
  4284. # This is targeted at gmail to maintain compatibility after google's oauth1 service is deactivated
  4285. # on May 5th, 2015: https://developers.google.com/gmail/oauth_protocol
  4286. # If there are other oauth2 implementations out there, this would need to be modified to be
  4287. # compatible
  4288. #
  4289. # This is a good guide on setting up the google api/apps side of the equation:
  4290. # http://www.limilabs.com/blog/oauth2-gmail-imap-service-account
  4291. #
  4292. # 2016/05/27: Updated to support oauth/key data in the .json files Google now defaults to
  4293. # when creating gmail service accounts. They're easier to work with since they neither
  4294. # requiring decrypting nor specifying the oauth2 client id separately.
  4295. #
  4296. # If the password arg ends in .json, it will assume this new json method, otherwise it
  4297. # will fallback to the "oauth client id;.p12" format it was previously using.
  4298. sub xoauth2
  4299. {
  4300. require JSON::WebToken ;
  4301. require LWP::UserAgent ;
  4302. require HTML::Entities ;
  4303. require JSON ;
  4304. require JSON::WebToken::Crypt::RSA ;
  4305. require Crypt::OpenSSL::RSA ;
  4306. require Encode::Byte ;
  4307. require IO::Socket::SSL ;
  4308. my $code = shift;
  4309. my $imap = shift;
  4310. my ($iss,$key);
  4311. if( $imap->Password =~ /^(.*\.json)$/x ) {
  4312. my $json = JSON->new( ) ;
  4313. my $filename = $1;
  4314. $sync->{ debug } and myprint( "XOAUTH2 json file: $filename\n" ) ;
  4315. open( my $FILE, '<', $filename ) or exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE, "error [$filename]: $OS_ERROR " ) ;
  4316. my $jsonfile = $json->decode( join q{}, <$FILE> ) ;
  4317. close $FILE ;
  4318. $iss = $jsonfile->{client_id};
  4319. $key = $jsonfile->{private_key};
  4320. $sync->{ debug } and myprint( "Service account: $iss\n");
  4321. $sync->{ debug } and myprint( "Private key:\n$key\n");
  4322. }
  4323. else {
  4324. # Get iss (service account address), keyfile name, and keypassword if necessary
  4325. ( $iss, my $keyfile, my $keypass ) = $imap->Password =~ /([\-\d\w\@\.]+);([a-zA-Z0-9 \_\-\.\/]+);?(.*)?/x ;
  4326. # Assume key password is google default if not provided
  4327. $keypass = 'notasecret' if not $keypass;
  4328. $sync->{ debug } and myprint( "Service account: $iss\nKey file: $keyfile\nKey password: $keypass\n");
  4329. # Get private key from p12 file (would be better in perl...)
  4330. $key = `openssl pkcs12 -in "$keyfile" -nodes -nocerts -passin pass:$keypass -nomacver`;
  4331. $sync->{ debug } and myprint( "Private key:\n$key\n");
  4332. }
  4333. # Create jwt of oauth2 request
  4334. my $time = time ;
  4335. my $jwt = JSON::WebToken->encode( {
  4336. 'iss' => $iss, # service account
  4337. 'scope' => 'https://mail.google.com/',
  4338. 'aud' => 'https://www.googleapis.com/oauth2/v3/token',
  4339. 'exp' => $time + $DEFAULT_EXPIRATION_TIME_OAUTH2_PK12,
  4340. 'iat' => $time,
  4341. 'prn' => $imap->User # user to auth as
  4342. },
  4343. $key, 'RS256', {'typ' => 'JWT'} ); # Crypt::OpenSSL::RSA needed here.
  4344. # Post oauth2 request
  4345. my $ua = LWP::UserAgent->new( ) ;
  4346. $ua->env_proxy( ) ;
  4347. my $response = $ua->post('https://www.googleapis.com/oauth2/v3/token',
  4348. { grant_type => HTML::Entities::encode_entities('urn:ietf:params:oauth:grant-type:jwt-bearer'),
  4349. assertion => $jwt } ) ;
  4350. unless( $response->is_success( ) ) {
  4351. exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE, $response->code, "\n", $response->content, "\n" ) ;
  4352. }else{
  4353. $sync->{ debug } and myprint( $response->content ) ;
  4354. }
  4355. # access_token in response is what we need
  4356. my $data = JSON::decode_json( $response->content ) ;
  4357. # format as oauth2 auth data
  4358. my $xoauth2_string = encode_base64( 'user=' . $imap->User . "\1auth=Bearer " . $data->{access_token} . "\1\1", q{} ) ;
  4359. $sync->{ debug } and myprint( "XOAUTH2 String: $xoauth2_string\n");
  4360. return($xoauth2_string);
  4361. }
  4362. # xoauth() thanks to Eduardo Bortoluzzi Junior
  4363. sub xoauth
  4364. {
  4365. require URI::Escape ;
  4366. require Data::Uniqid ;
  4367. my $code = shift;
  4368. my $imap = shift;
  4369. # The base information needed to construct the OAUTH authentication
  4370. my $method = 'GET' ;
  4371. my $url = mysprintf( 'https://mail.google.com/mail/b/%s/imap/', $imap->User ) ;
  4372. my $urlparm = mysprintf( 'xoauth_requestor_id=%s', URI::Escape::uri_escape( $imap->User ) ) ;
  4373. # For Google Apps, the consumer key is the primary domain
  4374. # TODO: create a command line argument to define the consumer key
  4375. my @user_parts = split /@/x, $imap->User ;
  4376. $sync->{ debug } and myprint( "XOAUTH: consumer key: $user_parts[1]\n" ) ;
  4377. # All the parameters needed to be signed on the XOAUTH
  4378. my %hash = ();
  4379. $hash { 'xoauth_requestor_id' } = URI::Escape::uri_escape($imap->User);
  4380. $hash { 'oauth_consumer_key' } = $user_parts[1];
  4381. $hash { 'oauth_nonce' } = md5_hex(Data::Uniqid::uniqid(rand(), 1==1));
  4382. $hash { 'oauth_signature_method' } = 'HMAC-SHA1';
  4383. $hash { 'oauth_timestamp' } = time ;
  4384. $hash { 'oauth_version' } = '1.0';
  4385. # Base will hold the string to be signed
  4386. my $base = "$method&" . URI::Escape::uri_escape( $url ) . q{&} ;
  4387. # The parameters must be in dictionary order before signing
  4388. my $baseparms = q{} ;
  4389. foreach my $key ( sort keys %hash ) {
  4390. if ( length( $baseparms ) > 0 ) {
  4391. $baseparms .= q{&} ;
  4392. }
  4393. $baseparms .= "$key=$hash{$key}" ;
  4394. }
  4395. $base .= URI::Escape::uri_escape($baseparms);
  4396. $sync->{ debug } and myprint( "XOAUTH: base request to sign: $base\n" ) ;
  4397. # Sign it with the consumer secret, informed on the command line (password)
  4398. my $digest = hmac_sha1( $base, URI::Escape::uri_escape( $imap->Password ) . q{&} ) ;
  4399. # The parameters signed become a parameter and...
  4400. $hash { 'oauth_signature' } = URI::Escape::uri_escape( substr encode_base64( $digest ), 0, $MINUS_ONE ) ;
  4401. # ... we don't need the requestor_id anymore.
  4402. delete $hash{'xoauth_requestor_id'} ;
  4403. # Create the final authentication string
  4404. my $string = $method . q{ } . $url . q{?} . $urlparm .q{ } ;
  4405. # All the parameters must be sorted
  4406. $baseparms = q{};
  4407. foreach my $key (sort keys %hash) {
  4408. if(length($baseparms)>0) {
  4409. $baseparms .= q{,} ;
  4410. }
  4411. $baseparms .= "$key=\"$hash{$key}\"";
  4412. }
  4413. $string .= $baseparms;
  4414. $sync->{ debug } and myprint( "XOAUTH: authentication string: $string\n" ) ;
  4415. # It must be base64 encoded
  4416. return encode_base64("$string", q{});
  4417. }
  4418. sub xmasterauth
  4419. {
  4420. # This is Kerio auth admin
  4421. # This code comes from
  4422. # https://github.com/imapsync/imapsync/pull/53/files
  4423. my $imap = shift ;
  4424. my $user = $imap->User( ) ;
  4425. my $password = $imap->Password( ) ;
  4426. my $authmech = 'X-MASTERAUTH' ;
  4427. my @challenge = $imap->tag_and_run( $authmech, "+" ) ;
  4428. if ( not defined $challenge[0] )
  4429. {
  4430. exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE, "Failure authenticate with $authmech: ", $imap->LastError, "\n") ;
  4431. return ; # hahaha!
  4432. }
  4433. $sync->{ debug } and myprint( "X-MASTERAUTH challenge: [@challenge]\n" ) ;
  4434. $challenge[1] =~ s/^\+ |^\s+|\s+$//g ;
  4435. $imap->_imap_command( { addcrlf => 1, addtag => 0, tag => $imap->Count }, md5_hex( $challenge[1] . $password ) )
  4436. or exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE, "Failure authenticate with $authmech: ", $imap->LastError, "\n") ;
  4437. $imap->tag_and_run( 'X-SETUSER ' . $user )
  4438. or exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE, "Failure authenticate with $authmech: ", "X-SETUSER ", $imap->LastError, "\n") ;
  4439. $imap->State( Mail::IMAPClient::Authenticated ) ;
  4440. # I comment this state because "Selected" state is usually done by SELECT or EXAMINE imap commands
  4441. # $imap->State( Mail::IMAPClient::Selected ) ;
  4442. return ;
  4443. }
  4444. sub tests_do_valid_directory
  4445. {
  4446. note( 'Entering tests_do_valid_directory()' ) ;
  4447. Readonly my $NB_UNIX_tests_do_valid_directory => 2 ;
  4448. SKIP: {
  4449. skip( 'Tests only for Unix', $NB_UNIX_tests_do_valid_directory ) if ( 'MSWin32' eq $OSNAME ) ;
  4450. ok( 1 == do_valid_directory( '.'), 'do_valid_directory: . good' ) ;
  4451. ok( 1 == do_valid_directory( './W/tmp/tests/valid/sub'), 'do_valid_directory: ./W/tmp/tests/valid/sub good' ) ;
  4452. }
  4453. Readonly my $NB_UNIX_tests_do_valid_directory_non_root => 2 ;
  4454. SKIP: {
  4455. skip( 'Tests only for Unix', $NB_UNIX_tests_do_valid_directory_non_root ) if ( 'MSWin32' eq $OSNAME or '0' eq $EFFECTIVE_USER_ID ) ;
  4456. diag( 'Error / not writable is on purpose' ) ;
  4457. ok( 0 == do_valid_directory( '/'), 'do_valid_directory: / bad' ) ;
  4458. diag( 'Error permission denied on /noway is on purpose' ) ;
  4459. ok( 0 == do_valid_directory( '/noway'), 'do_valid_directory: /noway bad' ) ;
  4460. }
  4461. note( 'Leaving tests_do_valid_directory()' ) ;
  4462. return ;
  4463. }
  4464. sub banner_imapsync
  4465. {
  4466. my $mysync = shift @ARG ;
  4467. my @argv = @ARG ;
  4468. my $banner_imapsync = join q{},
  4469. q{$RCSfile: imapsync,v $ },
  4470. q{$Revision: 1.937 $ },
  4471. q{$Date: 2019/05/01 22:14:00 $ },
  4472. "\n",
  4473. "Command line used, run by $EXECUTABLE_NAME:\n",
  4474. "$PROGRAM_NAME ", command_line_nopassword( $mysync, @argv ), "\n" ;
  4475. return( $banner_imapsync ) ;
  4476. }
  4477. sub do_valid_directory
  4478. {
  4479. my $dir = shift @ARG ;
  4480. # all good => return ok.
  4481. return( 1 ) if ( -d $dir and -r _ and -w _ ) ;
  4482. # exist but bad
  4483. if ( -e $dir and not -d _ ) {
  4484. myprint( "Error: $dir exists but is not a directory\n" ) ;
  4485. return( 0 ) ;
  4486. }
  4487. if ( -e $dir and not -w _ ) {
  4488. my $sb = stat $dir ;
  4489. myprintf( "Error: directory %s is not writable for user %s, permissions are %04o and owner is %s ( uid %s )\n",
  4490. $dir, getpwuid_any_os( $EFFECTIVE_USER_ID ), ($sb->mode & oct($PERMISSION_FILTER) ), getpwuid_any_os( $sb->uid ), $sb->uid( ) ) ;
  4491. return( 0 ) ;
  4492. }
  4493. # Trying to create it
  4494. myprint( "Creating directory $dir\n" ) ;
  4495. if ( ! eval { mkpath( $dir ) } ) {
  4496. myprint( "$EVAL_ERROR" ) if ( $EVAL_ERROR ) ;
  4497. }
  4498. return( 1 ) if ( -d $dir and -r _ and -w _ ) ;
  4499. return( 0 ) ;
  4500. }
  4501. sub tests_match_a_pid_number
  4502. {
  4503. note( 'Entering tests_match_a_pid_number()' ) ;
  4504. is( undef, match_a_pid_number( ), 'match_a_pid_number: no args => undef' ) ;
  4505. is( undef, match_a_pid_number( '' ), 'match_a_pid_number: "" => undef' ) ;
  4506. is( undef, match_a_pid_number( 'lalala' ), 'match_a_pid_number: lalala => undef' ) ;
  4507. is( 1, match_a_pid_number( 1 ), 'match_a_pid_number: 1 => 1' ) ;
  4508. is( 1, match_a_pid_number( 123 ), 'match_a_pid_number: 123 => 1' ) ;
  4509. is( 1, match_a_pid_number( '123' ), 'match_a_pid_number: "123" => 1' ) ;
  4510. is( undef, match_a_pid_number( 'a123' ), 'match_a_pid_number: a123 => undef' ) ;
  4511. is( 1, match_a_pid_number( 99999 ), 'match_a_pid_number: 99999 => 1' ) ;
  4512. is( undef, match_a_pid_number( 0 ), 'match_a_pid_number: 0 => undef' ) ;
  4513. is( undef, match_a_pid_number( 100000 ), 'match_a_pid_number: 100000 => undef' ) ;
  4514. is( undef, match_a_pid_number( 123456 ), 'match_a_pid_number: 123456 => undef' ) ;
  4515. note( 'Leaving tests_match_a_pid_number()' ) ;
  4516. return ;
  4517. }
  4518. sub match_a_pid_number
  4519. {
  4520. my $pid = shift @ARG ;
  4521. if ( ! $pid ) { return ; }
  4522. if ( ! match( $pid, '^\d+$' ) ) { return ; }
  4523. if ( 0 > $pid ) { return ; }
  4524. #if ( 65535 < $pid ) { return ; }
  4525. if ( 99999 < $pid ) { return ; }
  4526. return 1 ;
  4527. }
  4528. sub tests_remove_pidfile_not_running
  4529. {
  4530. note( 'Entering tests_remove_pidfile_not_running()' ) ;
  4531. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'remove_pidfile_not_running: mkpath W/tmp/tests/' ) ;
  4532. is( undef, remove_pidfile_not_running( ), 'remove_pidfile_not_running: no args => undef' ) ;
  4533. is( undef, remove_pidfile_not_running( './W' ), 'remove_pidfile_not_running: a dir => undef' ) ;
  4534. is( undef, remove_pidfile_not_running( 'noexists' ), 'remove_pidfile_not_running: noexists => undef' ) ;
  4535. is( 1, touch( 'W/tmp/tests/empty.pid' ), 'remove_pidfile_not_running: prepa empty W/tmp/tests/empty.pid' ) ;
  4536. is( undef, remove_pidfile_not_running( 'W/tmp/tests/empty.pid' ), 'remove_pidfile_not_running: W/tmp/tests/empty.pid => undef' ) ;
  4537. is( 'lalala', string_to_file( 'lalala', 'W/tmp/tests/lalala.pid' ), 'remove_pidfile_not_running: prepa W/tmp/tests/lalala.pid' ) ;
  4538. is( undef, remove_pidfile_not_running( 'W/tmp/tests/lalala.pid' ), 'remove_pidfile_not_running: W/tmp/tests/lalala.pid => undef' ) ;
  4539. is( '55555', string_to_file( '55555', 'W/tmp/tests/notrunning.pid' ), 'remove_pidfile_not_running: prepa W/tmp/tests/notrunning.pid' ) ;
  4540. is( 1, remove_pidfile_not_running( 'W/tmp/tests/notrunning.pid' ), 'remove_pidfile_not_running: W/tmp/tests/notrunning.pid => 1' ) ;
  4541. is( $PROCESS_ID, string_to_file( $PROCESS_ID, 'W/tmp/tests/running.pid' ), 'remove_pidfile_not_running: prepa W/tmp/tests/running.pid' ) ;
  4542. is( undef, remove_pidfile_not_running( 'W/tmp/tests/running.pid' ), 'remove_pidfile_not_running: W/tmp/tests/running.pid => undef' ) ;
  4543. note( 'Leaving tests_remove_pidfile_not_running()' ) ;
  4544. return ;
  4545. }
  4546. sub remove_pidfile_not_running
  4547. {
  4548. #
  4549. my $pid_filename = shift @ARG ;
  4550. if ( ! $pid_filename ) { myprint( "No variable pid_filename\n" ) ; return } ;
  4551. if ( ! -e $pid_filename ) { myprint( "File $pid_filename does not exist\n" ) ; return } ;
  4552. if ( ! -f $pid_filename ) { myprint( "File $pid_filename is not a file\n" ) ; return } ;
  4553. my $pid = firstline( $pid_filename ) ;
  4554. if ( ! match_a_pid_number( $pid ) ) { myprint( "pid $pid in $pid_filename is not a number\n" ) ; return } ;
  4555. # can't kill myself => do nothing
  4556. if ( ! kill 'ZERO', $PROCESS_ID ) { myprint( "Can not kill ZERO myself $PROCESS_ID\n" ) ; return } ;
  4557. # can't kill ZERO the pid => it is gone or own by another user => remove pidfile
  4558. if ( ! kill 'ZERO', $pid ) {
  4559. myprint( "Removing old $pid_filename since its PID $pid is not running anymore (oo-killed?)\n" ) ;
  4560. if ( unlink $pid_filename ) {
  4561. myprint( "Removed old $pid_filename\n" ) ;
  4562. return 1 ;
  4563. }else{
  4564. myprint( "Could not remove old $pid_filename because $!\n" ) ;
  4565. return ;
  4566. }
  4567. }
  4568. myprint( "Another imapsync process $pid is running as says pidfile $pid_filename\n" ) ;
  4569. return ;
  4570. }
  4571. sub tests_tail
  4572. {
  4573. note( 'Entering tests_tail()' ) ;
  4574. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'tail: mkpath W/tmp/tests/' ) ;
  4575. ok( ( ! -e 'W/tmp/tests/tail.pid' || unlink 'W/tmp/tests/tail.pid' ), 'tail: unlink W/tmp/tests/tail.pid' ) ;
  4576. ok( ( ! -e 'W/tmp/tests/tail.txt' || unlink 'W/tmp/tests/tail.txt' ), 'tail: unlink W/tmp/tests/tail.txt' ) ;
  4577. is( undef, tail( ), 'tail: no args => undef' ) ;
  4578. my $mysync ;
  4579. is( undef, tail( $mysync ), 'tail: no pidfile => undef' ) ;
  4580. $mysync->{pidfile} = 'W/tmp/tests/tail.pid' ;
  4581. is( undef, tail( $mysync ), 'tail: no pidfilelocking => undef' ) ;
  4582. $mysync->{pidfilelocking} = 1 ;
  4583. is( undef, tail( $mysync ), 'tail: pidfile no exists => undef' ) ;
  4584. my $pidandlog = "33333\nW/tmp/tests/tail.txt\n" ;
  4585. is( $pidandlog, string_to_file( $pidandlog, $mysync->{pidfile} ), 'tail: put pid 33333 and tail.txt in pidfile' ) ;
  4586. is( undef, tail( $mysync ), 'tail: logfile to tail no exists => undef' ) ;
  4587. my $tailcontent = "L1\nL2\nL3\nL4\nL5\n" ;
  4588. is( $tailcontent, string_to_file( $tailcontent, 'W/tmp/tests/tail.txt' ),
  4589. 'tail: put L1\nL2\nL3\nL4\nL5\n in W/tmp/tests/tail.txt' ) ;
  4590. is( undef, tail( $mysync ), 'tail: fake pid in pidfile + tail off => 1' ) ;
  4591. $mysync->{ tail } = 1 ;
  4592. is( 1, tail( $mysync ), 'tail: fake pid in pidfile + tail on=> 1' ) ;
  4593. # put my own pid, won't do tail
  4594. $pidandlog = "$PROCESS_ID\nW/tmp/tests/tail.txt\n" ;
  4595. is( $pidandlog, string_to_file( $pidandlog, $mysync->{pidfile} ), 'tail: put my own PID in pidfile' ) ;
  4596. is( undef, tail( $mysync ), 'tail: my own pid in pidfile => undef' ) ;
  4597. note( 'Leaving tests_tail()' ) ;
  4598. return ;
  4599. }
  4600. sub tail
  4601. {
  4602. # return undef on failures
  4603. # return 1 on success
  4604. my $mysync = shift ;
  4605. # no tail when aborting!
  4606. if ( $mysync->{ abort } ) { return ; }
  4607. my $pidfile = $mysync->{pidfile} ;
  4608. my $lock = $mysync->{pidfilelocking} ;
  4609. my $tail = $mysync->{tail} ;
  4610. if ( ! $pidfile ) { return ; }
  4611. if ( ! $lock ) { return ; }
  4612. if ( ! $tail ) { return ; }
  4613. my $pidtotail = firstline( $pidfile ) ;
  4614. if ( ! $pidtotail ) { return ; }
  4615. # It should not happen but who knows...
  4616. if ( $pidtotail eq $PROCESS_ID ) { return ; }
  4617. my $filetotail = secondline( $pidfile ) ;
  4618. if ( ! $filetotail ) { return ; }
  4619. if ( ! -r $filetotail )
  4620. {
  4621. #myprint( "Error: can not read $filetotail\n" ) ;
  4622. return ;
  4623. }
  4624. myprint( "Doing a tail -f on $filetotail for processus pid $pidtotail until it is finished.\n" ) ;
  4625. my $file = File::Tail->new(
  4626. name => $filetotail,
  4627. nowait => 1,
  4628. interval => 1,
  4629. tail => 1,
  4630. adjustafter => 2
  4631. );
  4632. my $moretimes = 200 ;
  4633. # print one line at least
  4634. my $line = $file->read ;
  4635. myprint( $line ) ;
  4636. while ( isrunning( $pidtotail, \$moretimes ) and defined( $line = $file->read ) )
  4637. {
  4638. myprint( $line );
  4639. sleep( 0.02 ) ;
  4640. }
  4641. return 1 ;
  4642. }
  4643. sub isrunning
  4644. {
  4645. my $pidtocheck = shift ;
  4646. my $moretimes_ref = shift ;
  4647. if ( kill 'ZERO', $pidtocheck )
  4648. {
  4649. #myprint( "$pidtocheck running\n" ) ;
  4650. return 1 ;
  4651. }
  4652. elsif ( $$moretimes_ref >= 0 )
  4653. {
  4654. # continue to consider it running
  4655. $$moretimes_ref-- ;
  4656. return 1 ;
  4657. }
  4658. else
  4659. {
  4660. myprint( "Tailed processus $pidtocheck ended\n" ) ;
  4661. return ;
  4662. }
  4663. }
  4664. sub tests_write_pidfile
  4665. {
  4666. note( 'Entering tests_write_pidfile()' ) ;
  4667. my $mysync ;
  4668. is( 1, write_pidfile( ), 'write_pidfile: no args => 1' ) ;
  4669. # no pidfile => ok
  4670. $mysync->{pidfile} = q{} ;
  4671. is( 1, write_pidfile( $mysync ), 'write_pidfile: no pidfile => undef' ) ;
  4672. # The pidfile path is bad => failure
  4673. $mysync->{pidfile} = '/no/no/no.pid' ;
  4674. is( undef, write_pidfile( $mysync ), 'write_pidfile: no permission for /no/no/no.pid, no lock => undef' ) ;
  4675. $mysync->{pidfilelocking} = 1 ;
  4676. is( undef, write_pidfile( $mysync ), 'write_pidfile: no permission for /no/no/no.pid + lock => undef' ) ;
  4677. $mysync->{pidfile} = 'W/tmp/tests/test.pid' ;
  4678. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'write_pidfile: mkpath W/tmp/tests/' ) ;
  4679. is( 1, touch( $mysync->{pidfile} ), 'write_pidfile: lock prepa' ) ;
  4680. $mysync->{pidfilelocking} = 0 ;
  4681. is( 1, write_pidfile( $mysync ), 'write_pidfile: W/tmp/tests/test.pid + no lock => 1' ) ;
  4682. is( $PROCESS_ID, firstline( 'W/tmp/tests/test.pid' ), "write_pidfile: W/tmp/tests/test.pid contains $PROCESS_ID" ) ;
  4683. is( q{}, secondline( 'W/tmp/tests/test.pid' ), "write_pidfile: W/tmp/tests/test.pid contains no second line" ) ;
  4684. $mysync->{pidfilelocking} = 1 ;
  4685. is( undef, write_pidfile( $mysync ), 'write_pidfile: W/tmp/tests/test.pid + lock => undef' ) ;
  4686. $mysync->{pidfilelocking} = 0 ;
  4687. $mysync->{ logfile } = 'rrrr.txt' ;
  4688. is( 1, write_pidfile( $mysync ), 'write_pidfile: W/tmp/tests/test.pid + no lock + logfile => 1' ) ;
  4689. is( $PROCESS_ID, firstline( 'W/tmp/tests/test.pid' ), "write_pidfile: + no lock + logfile W/tmp/tests/test.pid contains $PROCESS_ID" ) ;
  4690. is( q{rrrr.txt}, secondline( 'W/tmp/tests/test.pid' ), "write_pidfile: + no lock + logfile W/tmp/tests/test.pid contains rrrr.txt" ) ;
  4691. note( 'Leaving tests_write_pidfile()' ) ;
  4692. return ;
  4693. }
  4694. sub write_pidfile
  4695. {
  4696. # returns undef if something is considered fatal
  4697. # returns 1 otherwise
  4698. if ( ! @ARG ) { return 1 ; }
  4699. my $mysync = shift @ARG ;
  4700. # Do not write the pid file if this process goal is to abort the process designed by the pid file
  4701. if ( $mysync->{abort} ) { return 1 ; }
  4702. #
  4703. my $pid_filename = $mysync->{ pidfile } ;
  4704. my $lock = $mysync->{ pidfilelocking } ;
  4705. if ( ! $pid_filename )
  4706. {
  4707. myprint( "PID file is unset ( to set it, use --pidfile filepath ; to avoid it use --pidfile \"\" )\n" ) ;
  4708. return( 1 ) ;
  4709. }
  4710. myprint( "PID file is $pid_filename ( to change it, use --pidfile filepath ; to avoid it use --pidfile \"\" )\n" ) ;
  4711. if ( -e $pid_filename and $lock ) {
  4712. myprint( "$pid_filename already exists, another imapsync may be curently running. Aborting imapsync.\n" ) ;
  4713. return ;
  4714. }
  4715. if ( -e $pid_filename ) {
  4716. myprint( "$pid_filename already exists, overwriting it ( use --pidfilelocking to avoid concurrent runs )\n" ) ;
  4717. }
  4718. my $pid_string = "$PROCESS_ID\n" ;
  4719. my $pid_message = "Writing my PID $PROCESS_ID in $pid_filename\n" ;
  4720. if ( $mysync->{ logfile } )
  4721. {
  4722. $pid_string .= "$mysync->{ logfile }\n" ;
  4723. $pid_message .= "Writing also my logfile name in $pid_filename : $mysync->{ logfile }\n" ;
  4724. }
  4725. if ( open my $FILE_HANDLE, '>', $pid_filename ) {
  4726. myprint( $pid_message ) ;
  4727. print $FILE_HANDLE $pid_string ;
  4728. close $FILE_HANDLE ;
  4729. return( 1 ) ;
  4730. }
  4731. else
  4732. {
  4733. myprint( "Could not open $pid_filename for writing. Check permissions or disk space: $OS_ERROR\n" ) ;
  4734. return ;
  4735. }
  4736. }
  4737. sub fix_Inbox_INBOX_mapping
  4738. {
  4739. my( $h1_all, $h2_all ) = @_ ;
  4740. my $regex = q{} ;
  4741. SWITCH: {
  4742. if ( exists $h1_all->{INBOX} and exists $h2_all->{INBOX} ) { $regex = q{} ; last SWITCH ; } ;
  4743. if ( exists $h1_all->{Inbox} and exists $h2_all->{Inbox} ) { $regex = q{} ; last SWITCH ; } ;
  4744. if ( exists $h1_all->{INBOX} and exists $h2_all->{Inbox} ) { $regex = q{s/^INBOX$/Inbox/x} ; last SWITCH ; } ;
  4745. if ( exists $h1_all->{Inbox} and exists $h2_all->{INBOX} ) { $regex = q{s/^Inbox$/INBOX/x} ; last SWITCH ; } ;
  4746. } ;
  4747. return( $regex ) ;
  4748. }
  4749. sub tests_fix_Inbox_INBOX_mapping
  4750. {
  4751. note( 'Entering tests_fix_Inbox_INBOX_mapping()' ) ;
  4752. my( $h1_all, $h2_all ) ;
  4753. $h1_all = { 'INBOX' => q{} } ;
  4754. $h2_all = { 'INBOX' => q{} } ;
  4755. ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX INBOX' ) ;
  4756. $h1_all = { 'Inbox' => q{} } ;
  4757. $h2_all = { 'Inbox' => q{} } ;
  4758. ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: Inbox Inbox' ) ;
  4759. $h1_all = { 'INBOX' => q{} } ;
  4760. $h2_all = { 'Inbox' => q{} } ;
  4761. ok( q{s/^INBOX$/Inbox/x} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX Inbox' ) ;
  4762. $h1_all = { 'Inbox' => q{} } ;
  4763. $h2_all = { 'INBOX' => q{} } ;
  4764. ok( q{s/^Inbox$/INBOX/x} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: Inbox INBOX' ) ;
  4765. $h1_all = { 'INBOX' => q{} } ;
  4766. $h2_all = { 'rrrrr' => q{} } ;
  4767. ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX rrrrrr' ) ;
  4768. $h1_all = { 'rrrrr' => q{} } ;
  4769. $h2_all = { 'Inbox' => q{} } ;
  4770. ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: rrrrr Inbox' ) ;
  4771. note( 'Leaving tests_fix_Inbox_INBOX_mapping()' ) ;
  4772. return ;
  4773. }
  4774. sub jux_utf8_list
  4775. {
  4776. my @s_inp = @_ ;
  4777. my $s_out = q{} ;
  4778. foreach my $s ( @s_inp ) {
  4779. $s_out .= jux_utf8( $s ) . "\n" ;
  4780. }
  4781. return( $s_out ) ;
  4782. }
  4783. sub tests_jux_utf8_list
  4784. {
  4785. note( 'Entering tests_jux_utf8_list()' ) ;
  4786. ok( q{} eq jux_utf8_list( ), 'jux_utf8_list: void' ) ;
  4787. ok( "[]\n" eq jux_utf8_list( q{} ), 'jux_utf8_list: empty string' ) ;
  4788. ok( "[INBOX]\n" eq jux_utf8_list( 'INBOX' ), 'jux_utf8_list: INBOX' ) ;
  4789. ok( "[&ANY-] = [Ö]\n" eq jux_utf8_list( '&ANY-' ), 'jux_utf8_list: &ANY-' ) ;
  4790. note( 'Leaving tests_jux_utf8_list()' ) ;
  4791. return( 0 ) ;
  4792. }
  4793. sub jux_utf8
  4794. {
  4795. # juxtapose utf8 at the right if different
  4796. my ( $s_utf7 ) = shift ;
  4797. my ( $s_utf8 ) = imap_utf7_decode( $s_utf7 ) ;
  4798. if ( $s_utf7 eq $s_utf8 ) {
  4799. #myprint( "[$s_utf7]\n" ) ;
  4800. return( "[$s_utf7]" ) ;
  4801. }else{
  4802. #myprint( "[$s_utf7] = [$s_utf8]\n" ) ;
  4803. return( "[$s_utf7] = [$s_utf8]" ) ;
  4804. }
  4805. }
  4806. # editing utf8 can be tricky without an utf8 editor
  4807. sub tests_jux_utf8
  4808. {
  4809. note( 'Entering tests_jux_utf8()' ) ;
  4810. ok( '[INBOX]' eq jux_utf8( 'INBOX'), 'jux_utf8: INBOX => [INBOX]' ) ;
  4811. ok( '[&ZTZO9nux-] = [æ”¶ä»¶ç®±]' eq jux_utf8( '&ZTZO9nux-'), 'jux_utf8: => [&ZTZO9nux-] = [æ”¶ä»¶ç®±]' ) ;
  4812. ok( '[&ANY-] = [Ö]' eq jux_utf8( '&ANY-'), 'jux_utf8: &ANY- => [&ANY-] = [Ö]' ) ;
  4813. ok( '[]' eq jux_utf8( q{} ), 'jux_utf8: void => []' ) ;
  4814. ok( '[+BD8EQAQ1BDQEOwQ+BDM-] = [предлог]' eq jux_utf8( '+BD8EQAQ1BDQEOwQ+BDM-' ), 'jux_utf8: => [+BD8EQAQ1BDQEOwQ+BDM-] = [предлог]' ) ;
  4815. ok( '[&BB8EQAQ+BDUEOgRC-] = [Проект]' eq jux_utf8( '&BB8EQAQ+BDUEOgRC-' ), 'jux_utf8: => [&BB8EQAQ+BDUEOgRC-] = [Проект]' ) ;
  4816. note( 'Leaving tests_jux_utf8()' ) ;
  4817. return ;
  4818. }
  4819. # Copied from http://cpansearch.perl.org/src/FABPOT/Unicode-IMAPUtf7-2.01/lib/Unicode/IMAPUtf7.pm
  4820. # and then fixed with
  4821. # https://rt.cpan.org/Public/Bug/Display.html?id=11172
  4822. sub imap_utf7_decode
  4823. {
  4824. my ( $s ) = shift ;
  4825. # Algorithm
  4826. # On remplace , par / dans les BASE 64 (, entre & et -)
  4827. # On remplace les &, non suivi d'un - par +
  4828. # On remplace les &- par &
  4829. $s =~ s/&([^,&\-]*),([^,\-&]*)\-/&$1\/$2\-/xg ;
  4830. $s =~ s/&(?!\-)/\+/xg ;
  4831. $s =~ s/&\-/&/xg ;
  4832. return( Unicode::String::utf7( $s )->utf8 ) ;
  4833. }
  4834. sub imap_utf7_encode
  4835. {
  4836. my ( $s ) = @_ ;
  4837. $s = Unicode::String::utf8( $s )->utf7 ;
  4838. $s =~ s/\+([^\/&\-]*)\/([^\/\-&]*)\-/\+$1,$2\-/xg ;
  4839. $s =~ s/&/&\-/xg ;
  4840. $s =~ s/\+([^+\-]+)?\-/&$1\-/xg ;
  4841. return( $s ) ;
  4842. }
  4843. sub select_folder
  4844. {
  4845. my ( $mysync, $imap, $folder, $hostside ) = @_ ;
  4846. if ( ! $imap->select( $folder ) ) {
  4847. my $error = join q{},
  4848. "$hostside folder $folder: Could not select: ",
  4849. $imap->LastError, "\n" ;
  4850. errors_incr( $mysync, $error ) ;
  4851. return( 0 ) ;
  4852. }else{
  4853. # ok select succeeded
  4854. return( 1 ) ;
  4855. }
  4856. }
  4857. sub examine_folder
  4858. {
  4859. my ( $mysync, $imap, $folder, $hostside ) = @_ ;
  4860. if ( ! $imap->examine( $folder ) ) {
  4861. my $error = join q{},
  4862. "$hostside folder $folder: Could not examine: ",
  4863. $imap->LastError, "\n" ;
  4864. errors_incr( $mysync, $error ) ;
  4865. return( 0 ) ;
  4866. }else{
  4867. # ok select succeeded
  4868. return( 1 ) ;
  4869. }
  4870. }
  4871. sub count_from_select
  4872. {
  4873. my @lines = @ARG ;
  4874. my $count ;
  4875. foreach my $line ( @lines ) {
  4876. #myprint( "line = [$line]\n" ) ;
  4877. if ( $line =~ m/^\*\s+(\d+)\s+EXISTS/x ) {
  4878. $count = $1 ;
  4879. return( $count ) ;
  4880. }
  4881. }
  4882. return( undef ) ;
  4883. }
  4884. sub create_folder_old
  4885. {
  4886. my $mysync = shift @ARG ;
  4887. my( $imap, $h2_fold, $h1_fold ) = @ARG ;
  4888. myprint( "Creating (old way) folder [$h2_fold] on host2\n" ) ;
  4889. if ( ( 'INBOX' eq uc $h2_fold )
  4890. and ( $imap->exists( $h2_fold ) ) ) {
  4891. myprint( "Folder [$h2_fold] already exists\n" ) ;
  4892. return( 1 ) ;
  4893. }
  4894. if ( ! $mysync->{dry} ){
  4895. if ( ! $imap->create( $h2_fold ) ) {
  4896. my $error = join q{},
  4897. "Could not create folder [$h2_fold] from [$h1_fold]: ",
  4898. $imap->LastError( ), "\n" ;
  4899. errors_incr( $mysync, $error ) ;
  4900. # success if folder exists ("already exists" error)
  4901. return( 1 ) if $imap->exists( $h2_fold ) ;
  4902. # failure since create failed
  4903. return( 0 ) ;
  4904. }else{
  4905. #create succeeded
  4906. myprint( "Created ( the old way ) folder [$h2_fold] on host2\n" ) ;
  4907. return( 1 ) ;
  4908. }
  4909. }else{
  4910. # dry mode, no folder so many imap will fail, assuming failure
  4911. myprint( "Created ( the old way ) folder [$h2_fold] on host2 $mysync->{dry_message}\n" ) ;
  4912. return( 0 ) ;
  4913. }
  4914. }
  4915. sub create_folder
  4916. {
  4917. my $mysync = shift @ARG ;
  4918. my( $myimap2 , $h2_fold , $h1_fold ) = @ARG ;
  4919. my( @parts , $parent ) ;
  4920. if ( $myimap2->IsUnconnected( ) ) {
  4921. myprint( "Host2: Unconnected state\n" ) ;
  4922. return( 0 ) ;
  4923. }
  4924. if ( $create_folder_old ) {
  4925. return( create_folder_old( $mysync, $myimap2 , $h2_fold , $h1_fold ) ) ;
  4926. }
  4927. myprint( "Creating folder [$h2_fold] on host2\n" ) ;
  4928. if ( ( 'INBOX' eq uc $h2_fold )
  4929. and ( $myimap2->exists( $h2_fold ) ) ) {
  4930. myprint( "Folder [$h2_fold] already exists\n" ) ;
  4931. return( 1 ) ;
  4932. }
  4933. if ( $mixfolders and $myimap2->exists( $h2_fold ) ) {
  4934. myprint( "Folder [$h2_fold] already exists (--nomixfolders is not set)\n" ) ;
  4935. return( 1 ) ;
  4936. }
  4937. if ( ( not $mixfolders ) and ( $myimap2->exists( $h2_fold ) ) ) {
  4938. myprint( "Folder [$h2_fold] already exists and --nomixfolders is set\n" ) ;
  4939. return( 0 ) ;
  4940. }
  4941. @parts = split /\Q$mysync->{ h2_sep }\E/x, $h2_fold ;
  4942. pop @parts ;
  4943. $parent = join $mysync->{ h2_sep }, @parts ;
  4944. $parent =~ s/^\s+|\s+$//xg ;
  4945. if ( ( $parent ne q{} ) and ( ! $myimap2->exists( $parent ) ) ) {
  4946. create_folder( $mysync, $myimap2 , $parent , $h1_fold ) ;
  4947. }
  4948. if ( ! $mysync->{dry} ) {
  4949. if ( ! $myimap2->create( $h2_fold ) ) {
  4950. my $error = join q{},
  4951. "Could not create folder [$h2_fold] from [$h1_fold]: " ,
  4952. $myimap2->LastError( ), "\n" ;
  4953. errors_incr( $mysync, $error ) ;
  4954. # success if folder exists ("already exists" error)
  4955. return( 1 ) if $myimap2->exists( $h2_fold ) ;
  4956. # failure since create failed
  4957. return( 0 ) ;
  4958. }else{
  4959. #create succeeded
  4960. myprint( "Created folder [$h2_fold] on host2\n" ) ;
  4961. return( 1 ) ;
  4962. }
  4963. }else{
  4964. # dry mode, no folder so many imap will fail, assuming failure
  4965. myprint( "Created folder [$h2_fold] on host2 $mysync->{dry_message}\n" ) ;
  4966. if ( ! $mysync->{ justfolders } ) {
  4967. myprint( "Since --dry mode is on and folder [$h2_fold] on host2 does not exist yet, syncing messages will not be simulated.\n"
  4968. . "To simulate message syncing, use --justfolders without --dry to first create the missing folders then rerun the --dry sync.\n" ) ;
  4969. }
  4970. return( 0 ) ;
  4971. }
  4972. }
  4973. sub tests_folder_routines
  4974. {
  4975. note( 'Entering tests_folder_routines()' ) ;
  4976. ok( !is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 1' );
  4977. ok( add_to_requested_folders('folder_foo'), 'add_to_requested_folders folder_foo' );
  4978. ok( is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 2' );
  4979. ok( !is_requested_folder('folder_NO_EXIST'), 'is_requested_folder folder_NO_EXIST' );
  4980. is_deeply( [ 'folder_foo' ], [ remove_from_requested_folders( 'folder_foo' ) ], 'removed folder_foo => folder_foo' ) ;
  4981. ok( !is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 3' );
  4982. my @f ;
  4983. ok( @f = add_to_requested_folders('folder_bar', 'folder_toto'), "add result: @f" );
  4984. ok( is_requested_folder('folder_bar'), 'is_requested_folder 4' );
  4985. ok( is_requested_folder('folder_toto'), 'is_requested_folder 5' );
  4986. ok( remove_from_requested_folders('folder_toto'), 'remove_from_requested_folders: ' );
  4987. ok( !is_requested_folder('folder_toto'), 'is_requested_folder 6' );
  4988. is_deeply( [ 'folder_bar' ], [ remove_from_requested_folders('folder_bar') ], 'remove_from_requested_folders: empty' ) ;
  4989. ok( 0 == compare_lists( [ sort_requested_folders( ) ], [] ), 'sort_requested_folders: all empty' ) ;
  4990. ok( add_to_requested_folders( 'A_99', 'M_55', 'Z_11' ), 'add_to_requested_folders M_55 Z_11' );
  4991. ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'A_99', 'M_55', 'Z_11' ] ), 'sort_requested_folders: middle' ) ;
  4992. @folderfirst = ( 'Z_11' ) ;
  4993. ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'A_99', 'M_55' ] ), 'sort_requested_folders: first+middle' ) ;
  4994. is_deeply( [ 'Z_11', 'A_99', 'M_55' ], [ sort_requested_folders( ) ], 'sort_requested_folders: first+middle is_deeply' ) ;
  4995. @folderlast = ( 'A_99' ) ;
  4996. ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'M_55', 'A_99' ] ), 'sort_requested_folders: first+middle+last 1' ) ;
  4997. ok( add_to_requested_folders('M_55', 'M_44',), 'add_to_requested_folders M_55 M_44' ) ;
  4998. ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'M_44', 'M_55', 'A_99'] ), 'sort_requested_folders: first+middle+last 2' ) ;
  4999. ok( add_to_requested_folders('A_88', 'Z_22',), 'add_to_requested_folders A_88 Z_22' ) ;
  5000. @folderfirst = qw( Z_22 Z_11 ) ;
  5001. @folderlast = qw( A_99 A_88 ) ;
  5002. 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' ) ;
  5003. undef @folderfirst ;
  5004. undef @folderlast ;
  5005. note( 'Leaving tests_folder_routines()' ) ;
  5006. return ;
  5007. }
  5008. sub sort_requested_folders
  5009. {
  5010. my @requested_folders_sorted = () ;
  5011. #myprint "folderfirst: @folderfirst\n" ;
  5012. my @folderfirst_requested = remove_from_requested_folders( @folderfirst ) ;
  5013. #myprint "folderfirst_requested: @folderfirst_requested\n" ;
  5014. my @folderlast_requested = remove_from_requested_folders( @folderlast ) ;
  5015. my @middle = sort keys %requested_folder ;
  5016. @requested_folders_sorted = ( @folderfirst_requested, @middle, @folderlast_requested ) ;
  5017. #myprint "requested_folders_sorted: @requested_folders_sorted\n" ;
  5018. add_to_requested_folders( @requested_folders_sorted ) ;
  5019. return( @requested_folders_sorted ) ;
  5020. }
  5021. sub is_requested_folder
  5022. {
  5023. my ( $folder ) = @_;
  5024. return( defined $requested_folder{ $folder } ) ;
  5025. }
  5026. sub add_to_requested_folders
  5027. {
  5028. my @wanted_folders = @_ ;
  5029. foreach my $folder ( @wanted_folders ) {
  5030. ++$requested_folder{ $folder } ;
  5031. }
  5032. return( keys %requested_folder ) ;
  5033. }
  5034. sub tests_remove_from_requested_folders
  5035. {
  5036. note( 'Entering tests_remove_from_requested_folders()' ) ;
  5037. is( undef, undef, 'remove_from_requested_folders: undef is undef' ) ;
  5038. is_deeply( [], [ remove_from_requested_folders( ) ], 'remove_from_requested_folders: no args' ) ;
  5039. %requested_folder = (
  5040. 'F1' => 1,
  5041. ) ;
  5042. is_deeply( [], [ remove_from_requested_folders( ) ], 'remove_from_requested_folders: remove nothing among F1 => nothing' ) ;
  5043. is_deeply( [], [ remove_from_requested_folders( 'Fno' ) ], 'remove_from_requested_folders: remove Fno among F1 => nothing' ) ;
  5044. is_deeply( [ 'F1' ], [ remove_from_requested_folders( 'F1' ) ], 'remove_from_requested_folders: remove F1 among F1 => F1' ) ;
  5045. is_deeply( { }, { %requested_folder }, 'remove_from_requested_folders: remove F1 among F1 => %requested_folder emptied' ) ;
  5046. %requested_folder = (
  5047. 'F1' => 1,
  5048. 'F2' => 1,
  5049. ) ;
  5050. is_deeply( [], [ remove_from_requested_folders( ) ], 'remove_from_requested_folders: remove nothing among F1 F2 => nothing' ) ;
  5051. is_deeply( [], [ remove_from_requested_folders( 'Fno' ) ], 'remove_from_requested_folders: remove Fno among F1 F2 => nothing' ) ;
  5052. is_deeply( [ 'F1' ], [ remove_from_requested_folders( 'F1' ) ], 'remove_from_requested_folders: remove F1 among F1 F2 => F1' ) ;
  5053. is_deeply( { 'F2' => 1 }, { %requested_folder }, 'remove_from_requested_folders: remove F1 among F1 F2 => %requested_folder F2' ) ;
  5054. is_deeply( [], [ remove_from_requested_folders( 'F1' ) ], 'remove_from_requested_folders: remove F1 among F2 => nothing' ) ;
  5055. is_deeply( [ 'F2' ], [ remove_from_requested_folders( 'F1', 'F2' ) ], 'remove_from_requested_folders: remove F1 F2 among F2 => F2' ) ;
  5056. is_deeply( {}, { %requested_folder }, 'remove_from_requested_folders: remove F1 among F1 F2 => %requested_folder F2' ) ;
  5057. %requested_folder = (
  5058. 'F1' => 1,
  5059. 'F2' => 1,
  5060. 'F3' => 1,
  5061. ) ;
  5062. is_deeply( [ 'F1', 'F2' ], [ remove_from_requested_folders( 'F1', 'F2' ) ], 'remove_from_requested_folders: remove F1 F2 among F1 F2 F3 => F1 F2' ) ;
  5063. is_deeply( { 'F3' => 1 }, { %requested_folder }, 'remove_from_requested_folders: remove F1 F2 among F1 F2 F3 => %requested_folder F3' ) ;
  5064. note( 'Leaving tests_remove_from_requested_folders()' ) ;
  5065. return ;
  5066. }
  5067. sub remove_from_requested_folders
  5068. {
  5069. my @unwanted_folders = @_ ;
  5070. my @removed_folders = () ;
  5071. foreach my $folder ( @unwanted_folders ) {
  5072. if ( exists $requested_folder{ $folder } )
  5073. {
  5074. delete $requested_folder{ $folder } ;
  5075. push @removed_folders, $folder ;
  5076. }
  5077. }
  5078. return( @removed_folders ) ;
  5079. }
  5080. sub compare_lists
  5081. {
  5082. my ($list_1_ref, $list_2_ref) = @_;
  5083. return($MINUS_ONE) if ((not defined $list_1_ref) and defined $list_2_ref);
  5084. return(0) if ((not defined $list_1_ref) and not defined $list_2_ref); # end if no list
  5085. return(1) if (not defined $list_2_ref); # end if only one list
  5086. if (not ref $list_1_ref ) {$list_1_ref = [$list_1_ref]};
  5087. if (not ref $list_2_ref ) {$list_2_ref = [$list_2_ref]};
  5088. my $last_used_indice = $MINUS_ONE;
  5089. ELEMENT:
  5090. foreach my $indice ( 0 .. $#{ $list_1_ref } ) {
  5091. $last_used_indice = $indice ;
  5092. # End of list_2
  5093. return 1 if ($indice > $#{ $list_2_ref } ) ;
  5094. my $element_list_1 = $list_1_ref->[$indice] ;
  5095. my $element_list_2 = $list_2_ref->[$indice] ;
  5096. my $balance = $element_list_1 cmp $element_list_2 ;
  5097. next ELEMENT if ($balance == 0) ;
  5098. return $balance ;
  5099. }
  5100. # each element equal until last indice of list_1
  5101. return $MINUS_ONE if ($last_used_indice < $#{ $list_2_ref } ) ;
  5102. # same size, each element equal
  5103. return 0 ;
  5104. }
  5105. sub tests_compare_lists
  5106. {
  5107. note( 'Entering tests_compare_lists()' ) ;
  5108. my $empty_list_ref = [];
  5109. ok( 0 == compare_lists() , 'compare_lists, no args');
  5110. ok( 0 == compare_lists(undef) , 'compare_lists, undef = nothing');
  5111. ok( 0 == compare_lists(undef, undef) , 'compare_lists, undef = undef');
  5112. ok($MINUS_ONE == compare_lists(undef , []) , 'compare_lists, undef < []');
  5113. ok($MINUS_ONE == compare_lists(undef , [1]) , 'compare_lists, undef < [1]');
  5114. ok($MINUS_ONE == compare_lists(undef , [0]) , 'compare_lists, undef < [0]');
  5115. ok(+1 == compare_lists([]) , 'compare_lists, [] > nothing');
  5116. ok(+1 == compare_lists([], undef) , 'compare_lists, [] > undef');
  5117. ok( 0 == compare_lists([] , []) , 'compare_lists, [] = []');
  5118. ok($MINUS_ONE == compare_lists([] , [1]) , 'compare_lists, [] < [1]');
  5119. ok(+1 == compare_lists([1] , []) , 'compare_lists, [1] > []');
  5120. ok( 0 == compare_lists([1], 1 ) , 'compare_lists, [1] = 1 ') ;
  5121. ok( 0 == compare_lists( 1 , [1]) , 'compare_lists, 1 = [1]') ;
  5122. ok( 0 == compare_lists( 1 , 1 ) , 'compare_lists, 1 = 1 ') ;
  5123. ok($MINUS_ONE == compare_lists( 0 , 1 ) , 'compare_lists, 0 < 1 ') ;
  5124. ok($MINUS_ONE == compare_lists($MINUS_ONE , 0 ) , 'compare_lists, -1 < 0 ') ;
  5125. ok($MINUS_ONE == compare_lists( 1 , 2 ) , 'compare_lists, 1 < 2 ') ;
  5126. ok(+1 == compare_lists( 2 , 1 ) , 'compare_lists, 2 > 1 ') ;
  5127. ok( 0 == compare_lists([1,2], [1,2]) , 'compare_lists, [1,2] = [1,2]' ) ;
  5128. ok($MINUS_ONE == compare_lists([1], [1,2]) , 'compare_lists, [1] < [1,2]' ) ;
  5129. ok(+1 == compare_lists([2], [1,2]) , 'compare_lists, [2] > [1,2]' ) ;
  5130. ok($MINUS_ONE == compare_lists([1], [1,1]) , 'compare_lists, [1] < [1,1]' ) ;
  5131. ok(+1 == compare_lists([1, 1], [1]) , 'compare_lists, [1, 1] > [1]' ) ;
  5132. ok( 0 == compare_lists([1 .. $NUMBER_20_000] , [1 .. $NUMBER_20_000])
  5133. , 'compare_lists, [1..20_000] = [1..20_000]' ) ;
  5134. ok($MINUS_ONE == compare_lists([1], [2]) , 'compare_lists, [1] < [2]') ;
  5135. ok( 0 == compare_lists([2], [2]) , 'compare_lists, [0] = [2]') ;
  5136. ok(+1 == compare_lists([2], [1]) , 'compare_lists, [2] > [1]') ;
  5137. ok($MINUS_ONE == compare_lists(['a'], ['b']) , 'compare_lists, ["a"] < ["b"]') ;
  5138. ok( 0 == compare_lists(['a'], ['a']) , 'compare_lists, ["a"] = ["a"]') ;
  5139. ok( 0 == compare_lists(['ab'], ['ab']) , 'compare_lists, ["ab"] = ["ab"]') ;
  5140. ok(+1 == compare_lists(['b'], ['a']) , 'compare_lists, ["b"] > ["a"]') ;
  5141. ok($MINUS_ONE == compare_lists(['a'], ['aa']) , 'compare_lists, ["a"] < ["aa"]') ;
  5142. ok($MINUS_ONE == compare_lists(['a'], ['a', 'a']), 'compare_lists, ["a"] < ["a", "a"]') ;
  5143. ok( 0 == compare_lists([split q{ }, 'a b' ], ['a', 'b']), 'compare_lists, split') ;
  5144. ok( 0 == compare_lists([sort split q{ }, 'b a' ], ['a', 'b']), 'compare_lists, sort split') ;
  5145. note( 'Leaving tests_compare_lists()' ) ;
  5146. return ;
  5147. }
  5148. sub guess_prefix
  5149. {
  5150. my @foldernames = @_ ;
  5151. my $prefix_guessed = q{} ;
  5152. foreach my $folder ( @foldernames ) {
  5153. next if ( $folder =~ m{^INBOX$}xi ) ; # no guessing from INBOX
  5154. if ( $folder !~ m{^INBOX}xi ) {
  5155. $prefix_guessed = q{} ; # prefix empty guessed
  5156. last ;
  5157. }
  5158. if ( $folder =~ m{^(INBOX(?:\.|\/))}xi ) {
  5159. $prefix_guessed = $1 ; # prefix Inbox/ or INBOX. guessed
  5160. }
  5161. }
  5162. return( $prefix_guessed ) ;
  5163. }
  5164. sub tests_guess_prefix
  5165. {
  5166. note( 'Entering tests_guess_prefix()' ) ;
  5167. is( guess_prefix( ), q{}, 'guess_prefix: no args => empty string' ) ;
  5168. is( q{} , guess_prefix( 'INBOX' ), 'guess_prefix: INBOX alone' ) ;
  5169. is( q{} , guess_prefix( 'Inbox' ), 'guess_prefix: Inbox alone' ) ;
  5170. is( q{} , guess_prefix( 'INBOX' ), 'guess_prefix: INBOX alone' ) ;
  5171. is( 'INBOX/' , guess_prefix( 'INBOX', 'INBOX/Junk' ), 'guess_prefix: INBOX INBOX/Junk' ) ;
  5172. is( 'INBOX.' , guess_prefix( 'INBOX', 'INBOX.Junk' ), 'guess_prefix: INBOX INBOX.Junk' ) ;
  5173. is( 'Inbox/' , guess_prefix( 'Inbox', 'Inbox/Junk' ), 'guess_prefix: Inbox Inbox/Junk' ) ;
  5174. is( 'Inbox.' , guess_prefix( 'Inbox', 'Inbox.Junk' ), 'guess_prefix: Inbox Inbox.Junk' ) ;
  5175. is( 'INBOX/' , guess_prefix( 'INBOX', 'INBOX/Junk', 'INBOX/rrr' ), 'guess_prefix: INBOX INBOX/Junk INBOX/rrr' ) ;
  5176. is( q{} , guess_prefix( 'INBOX', 'INBOX/Junk', 'INBOX/rrr', 'zzz' ), 'guess_prefix: INBOX INBOX/Junk INBOX/rrr zzz' ) ;
  5177. is( q{} , guess_prefix( 'INBOX', 'Junk' ), 'guess_prefix: INBOX Junk' ) ;
  5178. is( q{} , guess_prefix( 'INBOX', 'Junk' ), 'guess_prefix: INBOX Junk' ) ;
  5179. note( 'Leaving tests_guess_prefix()' ) ;
  5180. return ;
  5181. }
  5182. sub get_prefix
  5183. {
  5184. my( $imap, $prefix_in, $prefix_opt, $Side, $folders_ref ) = @_ ;
  5185. my( $prefix_out, $prefix_guessed ) ;
  5186. ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "$Side: Getting prefix\n" ) ;
  5187. $prefix_guessed = guess_prefix( @{ $folders_ref } ) ;
  5188. myprint( "$Side: guessing prefix from folder listing: [$prefix_guessed]\n" ) ;
  5189. ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "$Side: Calling namespace capability\n" ) ;
  5190. if ( $imap->has_capability( 'namespace' ) ) {
  5191. my $r_namespace = $imap->namespace( ) ;
  5192. $prefix_out = $r_namespace->[0][0][0] ;
  5193. myprint( "$Side: prefix given by NAMESPACE: [$prefix_out]\n" ) ;
  5194. if ( defined $prefix_in ) {
  5195. myprint( "$Side: but using [$prefix_in] given by $prefix_opt\n" ) ;
  5196. $prefix_out = $prefix_in ;
  5197. return( $prefix_out ) ;
  5198. }else{
  5199. # all good
  5200. return( $prefix_out ) ;
  5201. }
  5202. }
  5203. else{
  5204. if ( defined $prefix_in ) {
  5205. myprint( "$Side: using [$prefix_in] given by $prefix_opt\n" ) ;
  5206. $prefix_out = $prefix_in ;
  5207. return( $prefix_out ) ;
  5208. }else{
  5209. myprint(
  5210. "$Side: No NAMESPACE capability so using guessed prefix [$prefix_guessed]\n",
  5211. help_to_guess_prefix( $imap, $prefix_opt ) ) ;
  5212. return( $prefix_guessed ) ;
  5213. }
  5214. }
  5215. return ;
  5216. }
  5217. sub guess_separator
  5218. {
  5219. my @foldernames = @_ ;
  5220. #return( undef ) unless ( @foldernames ) ;
  5221. my $sep_guessed ;
  5222. my %counter ;
  5223. foreach my $folder ( @foldernames ) {
  5224. $counter{'/'}++ while ( $folder =~ m{/}xg ) ; # count /
  5225. $counter{'.'}++ while ( $folder =~ m{\.}xg ) ; # count .
  5226. $counter{'\\\\'}++ while ( $folder =~ m{(\\){2}}xg ) ; # count \\
  5227. $counter{'\\'}++ while ( $folder =~ m{[^\\](\\){1}(?=[^\\])}xg ) ; # count \
  5228. }
  5229. my @race_sorted = sort { $counter{ $b } <=> $counter{ $a } } keys %counter ;
  5230. $sync->{ debug } and myprint( "@foldernames\n@race_sorted\n", %counter, "\n" ) ;
  5231. $sep_guessed = shift @race_sorted || $LAST_RESSORT_SEPARATOR ; # / when nothing found.
  5232. return( $sep_guessed ) ;
  5233. }
  5234. sub tests_guess_separator
  5235. {
  5236. note( 'Entering tests_guess_separator()' ) ;
  5237. ok( '/' eq guess_separator( ), 'guess_separator: no args' ) ;
  5238. ok( '/' eq guess_separator( 'abcd' ), 'guess_separator: abcd' ) ;
  5239. ok( '/' eq guess_separator( 'a/b/c.d' ), 'guess_separator: a/b/c.d' ) ;
  5240. ok( '.' eq guess_separator( 'a.b/c.d' ), 'guess_separator: a.b/c.d' ) ;
  5241. ok( '\\\\' eq guess_separator( 'a\\\\b\\\\c.c\\\\d/e/f' ), 'guess_separator: a\\\\b\\\\c.c\\\\d/e/f' ) ;
  5242. ok( '\\' eq guess_separator( 'a\\b\\c.c\\d/e/f' ), 'guess_separator: a\\b\\c.c\\d/e/f' ) ;
  5243. ok( '\\' eq guess_separator( 'a\\b' ), 'guess_separator: a\\b' ) ;
  5244. ok( '\\' eq guess_separator( 'a\\b\\c' ), 'guess_separator: a\\b\\c' ) ;
  5245. note( 'Leaving tests_guess_separator()' ) ;
  5246. return ;
  5247. }
  5248. sub get_separator
  5249. {
  5250. my( $imap, $sep_in, $sep_opt, $Side, $folders_ref ) = @_ ;
  5251. my( $sep_out, $sep_guessed ) ;
  5252. ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "$Side: Getting separator\n" ) ;
  5253. $sep_guessed = guess_separator( @{ $folders_ref } ) ;
  5254. myprint( "$Side: guessing separator from folder listing: [$sep_guessed]\n" ) ;
  5255. ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "$Side: calling namespace capability\n" ) ;
  5256. if ( $imap->has_capability( 'namespace' ) )
  5257. {
  5258. $sep_out = $imap->separator( ) ;
  5259. if ( defined $sep_out ) {
  5260. myprint( "$Side: separator given by NAMESPACE: [$sep_out]\n" ) ;
  5261. if ( defined $sep_in ) {
  5262. myprint( "$Side: but using [$sep_in] given by $sep_opt\n" ) ;
  5263. $sep_out = $sep_in ;
  5264. return( $sep_out ) ;
  5265. }else{
  5266. return( $sep_out ) ;
  5267. }
  5268. }else{
  5269. if ( defined $sep_in ) {
  5270. myprint( "$Side: NAMESPACE request failed but using [$sep_in] given by $sep_opt\n" ) ;
  5271. $sep_out = $sep_in ;
  5272. return( $sep_out ) ;
  5273. }else{
  5274. myprint(
  5275. "$Side: NAMESPACE request failed so using guessed separator [$sep_guessed]\n",
  5276. help_to_guess_sep( $imap, $sep_opt ) ) ;
  5277. return( $sep_guessed ) ;
  5278. }
  5279. }
  5280. }
  5281. else
  5282. {
  5283. if ( defined $sep_in ) {
  5284. myprint( "$Side: No NAMESPACE capability but using [$sep_in] given by $sep_opt\n" ) ;
  5285. $sep_out = $sep_in ;
  5286. return( $sep_out ) ;
  5287. }else{
  5288. myprint(
  5289. "$Side: No NAMESPACE capability, so using guessed separator [$sep_guessed]\n",
  5290. help_to_guess_sep( $imap, $sep_opt ) ) ;
  5291. return( $sep_guessed ) ;
  5292. }
  5293. }
  5294. return ;
  5295. }
  5296. sub help_to_guess_sep
  5297. {
  5298. my( $imap, $sep_opt ) = @_ ;
  5299. my $help_to_guess_sep = "You can set the separator character with the $sep_opt option,\n"
  5300. . "the complete listing of folders may help you to find it\n"
  5301. . folders_list_to_help( $imap ) ;
  5302. return( $help_to_guess_sep ) ;
  5303. }
  5304. sub help_to_guess_prefix
  5305. {
  5306. my( $imap, $prefix_opt ) = @_ ;
  5307. my $help_to_guess_prefix = "You can set the prefix namespace with the $prefix_opt option,\n"
  5308. . "the folowing listing of folders may help you to find it:\n"
  5309. . folders_list_to_help( $imap ) ;
  5310. return( $help_to_guess_prefix ) ;
  5311. }
  5312. sub folders_list_to_help
  5313. {
  5314. my( $imap ) = shift ;
  5315. my @folders = $imap->folders ;
  5316. my $listing = join q{}, map { "[$_]\n" } @folders ;
  5317. return( $listing ) ;
  5318. }
  5319. sub private_folders_separators_and_prefixes
  5320. {
  5321. # what are the private folders separators and prefixes for each server ?
  5322. ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "Getting separators\n" ) ;
  5323. $sync->{ h1_sep } = get_separator( $sync->{imap1}, $sync->{ sep1 }, '--sep1', 'Host1', \@h1_folders_all ) ;
  5324. $sync->{ h2_sep } = get_separator( $sync->{imap2}, $sync->{ sep2 }, '--sep2', 'Host2', \@h2_folders_all ) ;
  5325. $sync->{ h1_prefix } = get_prefix( $sync->{imap1}, $prefix1, '--prefix1', 'Host1', \@h1_folders_all ) ;
  5326. $sync->{ h2_prefix } = get_prefix( $sync->{imap2}, $prefix2, '--prefix2', 'Host2', \@h2_folders_all ) ;
  5327. myprint( "Host1: separator and prefix: [$sync->{ h1_sep }][$sync->{ h1_prefix }]\n" ) ;
  5328. myprint( "Host2: separator and prefix: [$sync->{ h2_sep }][$sync->{ h2_prefix }]\n" ) ;
  5329. return ;
  5330. }
  5331. sub subfolder1
  5332. {
  5333. my $mysync = shift ;
  5334. my $subfolder1 = sanitize_subfolder( $mysync->{ subfolder1 } ) ;
  5335. if ( $subfolder1 )
  5336. {
  5337. # turns off automap
  5338. myprint( "Turning off automapping folders because of --subfolder1\n" ) ;
  5339. $mysync->{ automap } = undef ;
  5340. myprint( "Sanitizing subfolder1: [$mysync->{ subfolder1 }] => [$subfolder1]\n" ) ;
  5341. $mysync->{ subfolder1 } = $subfolder1 ;
  5342. add_subfolder1_to_folderrec( $mysync ) || exit_clean( $mysync, $EXIT_SUBFOLDER1_NO_EXISTS ) ;
  5343. }
  5344. else
  5345. {
  5346. $mysync->{ subfolder1 } = undef ;
  5347. }
  5348. }
  5349. sub subfolder2
  5350. {
  5351. my $mysync = shift ;
  5352. my $subfolder2 = sanitize_subfolder( $mysync->{ subfolder2 } ) ;
  5353. if ( $subfolder2 )
  5354. {
  5355. # turns off automap
  5356. myprint( "Turning off automapping folders because of --subfolder2\n" ) ;
  5357. $mysync->{ automap } = undef ;
  5358. myprint( "Sanitizing subfolder2: [$mysync->{ subfolder2 }] => [$subfolder2]\n" ) ;
  5359. $mysync->{ subfolder2 } = $subfolder2 ;
  5360. set_regextrans2_for_subfolder2( $mysync ) ;
  5361. }
  5362. else
  5363. {
  5364. $mysync->{ subfolder2 } = undef ;
  5365. }
  5366. }
  5367. sub tests_sanitize_subfolder
  5368. {
  5369. note( 'Entering tests_sanitize_subfolder()' ) ;
  5370. is( undef, sanitize_subfolder( ), 'sanitize_subfolder: no args => undef' ) ;
  5371. is( undef, sanitize_subfolder( '' ), 'sanitize_subfolder: empty => undef' ) ;
  5372. is( undef, sanitize_subfolder( ' ' ), 'sanitize_subfolder: blank => undef' ) ;
  5373. is( undef, sanitize_subfolder( ' ' ), 'sanitize_subfolder: blanks => undef' ) ;
  5374. is( 'abcd', sanitize_subfolder( 'abcd' ), 'sanitize_subfolder: abcd => abcd' ) ;
  5375. is( 'ab cd', sanitize_subfolder( ' ab cd ' ), 'sanitize_subfolder: " ab cd " => "ab cd"' ) ;
  5376. is( 'abcd', sanitize_subfolder( q{a&~b#\\c[]=d;} ), 'sanitize_subfolder: "a&~b#\\c[]=d;" => "abcd"' ) ;
  5377. is( 'aA.b-_ 8c/dD', sanitize_subfolder( 'aA.b-_ 8c/dD' ), 'sanitize_subfolder: aA.b-_ 8c/dD => aA.b-_ 8c/dD' ) ;
  5378. note( 'Leaving tests_sanitize_subfolder()' ) ;
  5379. return ;
  5380. }
  5381. sub sanitize_subfolder
  5382. {
  5383. my $subfolder = shift ;
  5384. if ( ! $subfolder )
  5385. {
  5386. return ;
  5387. }
  5388. # Remove edging blanks
  5389. $subfolder =~ s,^ +| +$,,g ;
  5390. # Keep only abcd...ABCD...0123... and -_./
  5391. $subfolder =~ tr,-_a-zA-Z0-9./ ,,cd ;
  5392. # A blank subfolder is not a subfolder
  5393. if ( ! $subfolder )
  5394. {
  5395. return ;
  5396. }
  5397. else
  5398. {
  5399. return $subfolder ;
  5400. }
  5401. }
  5402. sub tests_add_subfolder1_to_folderrec
  5403. {
  5404. note( 'Entering tests_add_subfolder1_to_folderrec()' ) ;
  5405. is( undef, add_subfolder1_to_folderrec( ), 'add_subfolder1_to_folderrec: undef => undef' ) ;
  5406. is_deeply( [], [ add_subfolder1_to_folderrec( ) ], 'add_subfolder1_to_folderrec: no args => empty array' ) ;
  5407. @folderrec = () ;
  5408. my $mysync = {} ;
  5409. is_deeply( [ ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: empty => empty array' ) ;
  5410. is_deeply( [ ], [ @folderrec ], 'add_subfolder1_to_folderrec: empty => empty folderrec' ) ;
  5411. $mysync->{ subfolder1 } = 'SUBI' ;
  5412. $h1_folders_all{ 'SUBI' } = 1 ;
  5413. $mysync->{ h1_prefix } = 'INBOX/' ;
  5414. is_deeply( [ 'SUBI' ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: SUBI => SUBI' ) ;
  5415. is_deeply( [ 'SUBI' ], [ @folderrec ], 'add_subfolder1_to_folderrec: SUBI => folderrec SUBI ' ) ;
  5416. @folderrec = () ;
  5417. $mysync->{ subfolder1 } = 'SUBO' ;
  5418. is_deeply( [ ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: SUBO no exists => empty array' ) ;
  5419. is_deeply( [ ], [ @folderrec ], 'add_subfolder1_to_folderrec: SUBO no exists => empty folderrec' ) ;
  5420. $h1_folders_all{ 'INBOX/SUBO' } = 1 ;
  5421. is_deeply( [ 'INBOX/SUBO' ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: SUBO + INBOX/SUBO exists => INBOX/SUBO' ) ;
  5422. is_deeply( [ 'INBOX/SUBO' ], [ @folderrec ], 'add_subfolder1_to_folderrec: SUBO + INBOX/SUBO exists => INBOX/SUBO folderrec' ) ;
  5423. note( 'Leaving tests_add_subfolder1_to_folderrec()' ) ;
  5424. return ;
  5425. }
  5426. sub add_subfolder1_to_folderrec
  5427. {
  5428. my $mysync = shift ;
  5429. if ( ! $mysync || ! $mysync->{ subfolder1 } )
  5430. {
  5431. return ;
  5432. }
  5433. my $subfolder1 = $mysync->{ subfolder1 } ;
  5434. my $subfolder1_extended = $mysync->{ h1_prefix } . $subfolder1 ;
  5435. if ( exists $h1_folders_all{ $subfolder1 } )
  5436. {
  5437. myprint( qq{Acting like --folderrec "$subfolder1"\n} ) ;
  5438. push @folderrec, $subfolder1 ;
  5439. }
  5440. elsif ( exists $h1_folders_all{ $subfolder1_extended } )
  5441. {
  5442. myprint( qq{Acting like --folderrec "$subfolder1_extended"\n} ) ;
  5443. push @folderrec, $subfolder1_extended ;
  5444. }
  5445. else
  5446. {
  5447. myprint( qq{Nor folder "$subfolder1" nor "$subfolder1_extended" exists on host1\n} ) ;
  5448. }
  5449. return @folderrec ;
  5450. }
  5451. sub set_regextrans2_for_subfolder2
  5452. {
  5453. my $mysync = shift ;
  5454. unshift @{ $mysync->{ regextrans2 } },
  5455. q(s,^$mysync->{ h2_prefix }(.*),$mysync->{ h2_prefix }$mysync->{ subfolder2 }$mysync->{ h2_sep }$1,),
  5456. q(s,^INBOX$,$mysync->{ h2_prefix }$mysync->{ subfolder2 }$mysync->{ h2_sep }INBOX,),
  5457. q(s,^($mysync->{ h2_prefix }){2},$mysync->{ h2_prefix },);
  5458. #myprint( "@{ $mysync->{ regextrans2 } }\n" ) ;
  5459. return ;
  5460. }
  5461. # Looks like no globals here
  5462. sub tests_imap2_folder_name
  5463. {
  5464. note( 'Entering tests_imap2_folder_name()' ) ;
  5465. my $mysync = {} ;
  5466. $mysync->{ h1_prefix } = q{} ;
  5467. $mysync->{ h2_prefix } = q{} ;
  5468. $mysync->{ h1_sep } = '/';
  5469. $mysync->{ h2_sep } = '.';
  5470. $mysync->{ debug } and myprint( <<"EOS"
  5471. prefix1: [$mysync->{ h1_prefix }]
  5472. prefix2: [$mysync->{ h2_prefix }]
  5473. sep1: [$sync->{ h1_sep }]
  5474. sep2: [$sync->{ h2_sep }]
  5475. EOS
  5476. ) ;
  5477. $mysync->{ fixslash2 } = 0 ;
  5478. is( q{INBOX}, imap2_folder_name( $mysync, q{} ), 'imap2_folder_name: empty string' ) ;
  5479. is( 'blabla', imap2_folder_name( $mysync, 'blabla' ), 'imap2_folder_name: blabla' ) ;
  5480. is('spam.spam', imap2_folder_name( $mysync, 'spam/spam' ), 'imap2_folder_name: spam/spam' ) ;
  5481. is( 'spam/spam', imap2_folder_name( $mysync, 'spam.spam' ), 'imap2_folder_name: spam.spam') ;
  5482. is( 'spam.spam/spam', imap2_folder_name( $mysync, 'spam/spam.spam' ), 'imap2_folder_name: spam/spam.spam' ) ;
  5483. is( 's pam.spam/sp am', imap2_folder_name( $mysync, 's pam/spam.sp am' ), 'imap2_folder_name: s pam/spam.sp am' ) ;
  5484. $mysync->{f1f2h}{ 'auto' } = 'moto' ;
  5485. is( 'moto', imap2_folder_name( $mysync, 'auto' ), 'imap2_folder_name: auto' ) ;
  5486. $mysync->{f1f2h}{ 'auto/auto' } = 'moto x 2' ;
  5487. is( 'moto x 2', imap2_folder_name( $mysync, 'auto/auto' ), 'imap2_folder_name: auto/auto' ) ;
  5488. @{ $mysync->{ regextrans2 } } = ( 's,/,X,g' ) ;
  5489. is( q{INBOX}, imap2_folder_name( $mysync, q{} ), 'imap2_folder_name: empty string [s,/,X,g]' ) ;
  5490. is( 'blabla', imap2_folder_name( $mysync, 'blabla' ), 'imap2_folder_name: blabla [s,/,X,g]' ) ;
  5491. is('spam.spam', imap2_folder_name( $mysync, 'spam/spam'), 'imap2_folder_name: spam/spam [s,/,X,g]');
  5492. is('spamXspam', imap2_folder_name( $mysync, 'spam.spam'), 'imap2_folder_name: spam.spam [s,/,X,g]');
  5493. is('spam.spamXspam', imap2_folder_name( $mysync, 'spam/spam.spam'), 'imap2_folder_name: spam/spam.spam [s,/,X,g]');
  5494. @{ $mysync->{ regextrans2 } } = ( 's, ,_,g' ) ;
  5495. is('blabla', imap2_folder_name( $mysync, 'blabla'), 'imap2_folder_name: blabla [s, ,_,g]');
  5496. is('bla_bla', imap2_folder_name( $mysync, 'bla bla'), 'imap2_folder_name: blabla [s, ,_,g]');
  5497. @{ $mysync->{ regextrans2 } } = ( q{s,(.*),\U$1,} ) ;
  5498. is( 'BLABLA', imap2_folder_name( $mysync, 'blabla' ), q{imap2_folder_name: blabla [s,\U(.*)\E,$1,]} ) ;
  5499. $mysync->{ fixslash2 } = 1 ;
  5500. @{ $mysync->{ regextrans2 } } = ( ) ;
  5501. is(q{INBOX}, imap2_folder_name( $mysync, q{}), 'imap2_folder_name: empty string');
  5502. is('blabla', imap2_folder_name( $mysync, 'blabla'), 'imap2_folder_name: blabla');
  5503. is('spam.spam', imap2_folder_name( $mysync, 'spam/spam'), 'imap2_folder_name: spam/spam -> spam.spam');
  5504. is('spam_spam', imap2_folder_name( $mysync, 'spam.spam'), 'imap2_folder_name: spam.spam -> spam_spam');
  5505. is('spam.spam_spam', imap2_folder_name( $mysync, 'spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam_spam');
  5506. is('s pam.spam_spa m', imap2_folder_name( $mysync, 's pam/spam.spa m'), 'imap2_folder_name: s pam/spam.spa m -> s pam.spam_spa m');
  5507. $mysync->{ h1_sep } = '.';
  5508. $mysync->{ h2_sep } = '/';
  5509. is( q{INBOX}, imap2_folder_name( $mysync, q{}), 'imap2_folder_name: empty string');
  5510. is('blabla', imap2_folder_name( $mysync, 'blabla'), 'imap2_folder_name: blabla');
  5511. is('spam.spam', imap2_folder_name( $mysync, 'spam/spam'), 'imap2_folder_name: spam/spam -> spam.spam');
  5512. is('spam/spam', imap2_folder_name( $mysync, 'spam.spam'), 'imap2_folder_name: spam.spam -> spam/spam');
  5513. is('spam.spam/spam', imap2_folder_name( $mysync, 'spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam');
  5514. $mysync->{ fixslash2 } = 0 ;
  5515. $mysync->{ h1_prefix } = q{ };
  5516. is( 'spam.spam/spam', imap2_folder_name( $mysync, 'spam/spam.spam' ), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam' ) ;
  5517. is( 'spam.spam/spam', imap2_folder_name( $mysync, ' spam/spam.spam' ), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam' ) ;
  5518. $mysync->{ h1_sep } = '.' ;
  5519. $mysync->{ h2_sep } = '/' ;
  5520. $mysync->{ h1_prefix } = 'INBOX.' ;
  5521. $mysync->{ h2_prefix } = q{} ;
  5522. @{ $mysync->{ regextrans2 } } = ( q{s,(.*),\U$1,} ) ;
  5523. is( 'BLABLA', imap2_folder_name( $mysync, 'blabla' ), 'imap2_folder_name: blabla' ) ;
  5524. is( 'TEST/TEST/TEST/TEST', imap2_folder_name( $mysync, 'INBOX.TEST.test.Test.tesT' ), 'imap2_folder_name: INBOX.TEST.test.Test.tesT' ) ;
  5525. @{ $mysync->{ regextrans2 } } = ( q{s,(.*),\L$1,} ) ;
  5526. is( 'test/test/test/test', imap2_folder_name( $mysync, 'INBOX.TEST.test.Test.tesT' ), 'imap2_folder_name: INBOX.TEST.test.Test.tesT' ) ;
  5527. # INBOX
  5528. $mysync = {} ;
  5529. $mysync->{ h1_prefix } = q{Pf1.} ;
  5530. $mysync->{ h2_prefix } = q{Pf2/} ;
  5531. $mysync->{ h1_sep } = '.';
  5532. $mysync->{ h2_sep } = '/';
  5533. #
  5534. #$mysync->{ debug } = 1 ;
  5535. is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'F1.F2.F3' ), 'imap2_folder_name: F1.F2.F3 -> Pf2/F1/F2/F3' ) ;
  5536. is( 'Pf2/F1/INBOX', imap2_folder_name( $mysync, 'F1.INBOX' ), 'imap2_folder_name: F1.INBOX -> Pf2/F1/INBOX' ) ;
  5537. is( 'INBOX', imap2_folder_name( $mysync, 'INBOX' ), 'imap2_folder_name: INBOX -> INBOX' ) ;
  5538. is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'Pf1.F1.F2.F3' ), 'imap2_folder_name: Pf1.F1.F2.F3 -> Pf2/F1/F2/F3' ) ;
  5539. is( 'Pf2/F1/INBOX', imap2_folder_name( $mysync, 'Pf1.F1.INBOX' ), 'imap2_folder_name: Pf1.F1.INBOX -> Pf2/F1/INBOX' ) ;
  5540. is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.INBOX' ), 'imap2_folder_name: Pf1.INBOX -> INBOX' ) ; # not Pf2/INBOX: Yes I can!
  5541. # subfolder2
  5542. $mysync = {} ;
  5543. $mysync->{ h1_prefix } = q{} ;
  5544. $mysync->{ h2_prefix } = q{} ;
  5545. $mysync->{ h1_sep } = '/';
  5546. $mysync->{ h2_sep } = '.';
  5547. set_regextrans2_for_subfolder2( $mysync ) ;
  5548. $mysync->{ subfolder2 } = 'S1.S2' ;
  5549. is( 'S1.S2.F1.F2.F3', imap2_folder_name( $mysync, 'F1/F2/F3' ), 'imap2_folder_name: F1/F2/F3 -> S1.S2.F1.F2.F3' ) ;
  5550. is( 'S1.S2.INBOX', imap2_folder_name( $mysync, 'INBOX' ), 'imap2_folder_name: F1/F2/F3 -> S1.S2.INBOX' ) ;
  5551. $mysync = {} ;
  5552. $mysync->{ h1_prefix } = q{Pf1/} ;
  5553. $mysync->{ h2_prefix } = q{Pf2.} ;
  5554. $mysync->{ h1_sep } = '/';
  5555. $mysync->{ h2_sep } = '.';
  5556. #$mysync->{ debug } = 1 ;
  5557. set_regextrans2_for_subfolder2( $mysync ) ;
  5558. $mysync->{ subfolder2 } = 'Pf2.S1.S2' ;
  5559. is( 'Pf2.S1.S2.F1.F2.F3', imap2_folder_name( $mysync, 'F1/F2/F3' ), 'imap2_folder_name: F1/F2/F3 -> Pf2.S1.S2.F1.F2.F3' ) ;
  5560. is( 'Pf2.S1.S2.INBOX', imap2_folder_name( $mysync, 'INBOX' ), 'imap2_folder_name: INBOX -> Pf2.S1.S2.INBOX' ) ;
  5561. is( 'Pf2.S1.S2.F1.F2.F3', imap2_folder_name( $mysync, 'Pf1/F1/F2/F3' ), 'imap2_folder_name: F1/F2/F3 -> Pf2.S1.S2.F1.F2.F3' ) ;
  5562. is( 'Pf2.S1.S2.INBOX', imap2_folder_name( $mysync, 'Pf1/INBOX' ), 'imap2_folder_name: INBOX -> Pf2.S1.S2.INBOX' ) ;
  5563. # subfolder1
  5564. # scenario as the reverse of the previous tests, separators point of vue
  5565. $mysync = {} ;
  5566. $mysync->{ h1_prefix } = q{Pf1.} ;
  5567. $mysync->{ h2_prefix } = q{Pf2/} ;
  5568. $mysync->{ h1_sep } = '.';
  5569. $mysync->{ h2_sep } = '/';
  5570. #$mysync->{ debug } = 1 ;
  5571. $mysync->{ subfolder1 } = 'S1.S2' ;
  5572. is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'S1.S2.F1.F2.F3' ), 'imap2_folder_name: S1.S2.F1.F2.F3 -> Pf2/F1/F2/F3' ) ;
  5573. is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'Pf1.S1.S2.F1.F2.F3' ), 'imap2_folder_name: Pf1.S1.S2.F1.F2.F3 -> Pf2/F1/F2/F3' ) ;
  5574. is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.INBOX' ), 'imap2_folder_name: S1.S2.INBOX -> INBOX' ) ;
  5575. is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2' ), 'imap2_folder_name: S1.S2 -> INBOX' ) ;
  5576. is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.' ), 'imap2_folder_name: S1.S2. -> INBOX' ) ;
  5577. is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.INBOX' ), 'imap2_folder_name: Pf1.S1.S2.INBOX -> INBOX' ) ;
  5578. is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2' ), 'imap2_folder_name: Pf1.S1.S2 -> INBOX' ) ;
  5579. is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.' ), 'imap2_folder_name: Pf1.S1.S2. -> INBOX' ) ;
  5580. $mysync->{ subfolder1 } = 'S1.S2.' ;
  5581. is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'S1.S2.F1.F2.F3' ), 'imap2_folder_name: S1.S2.F1.F2.F3 -> Pf2/F1/F2/F3' ) ;
  5582. is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'Pf1.S1.S2.F1.F2.F3' ), 'imap2_folder_name: Pf1.S1.S2.F1.F2.F3 -> Pf2/F1/F2/F3' ) ;
  5583. is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.INBOX' ), 'imap2_folder_name: S1.S2.INBOX -> INBOX' ) ;
  5584. is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2' ), 'imap2_folder_name: S1.S2 -> INBOX' ) ;
  5585. is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.' ), 'imap2_folder_name: S1.S2. -> INBOX' ) ;
  5586. is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.INBOX' ), 'imap2_folder_name: Pf1.S1.S2.INBOX -> INBOX' ) ;
  5587. is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2' ), 'imap2_folder_name: Pf1.S1.S2 -> INBOX' ) ;
  5588. is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.' ), 'imap2_folder_name: Pf1.S1.S2. -> INBOX' ) ;
  5589. # subfolder1
  5590. # scenario as Gmail
  5591. $mysync = {} ;
  5592. $mysync->{ h1_prefix } = q{} ;
  5593. $mysync->{ h2_prefix } = q{} ;
  5594. $mysync->{ h1_sep } = '/';
  5595. $mysync->{ h2_sep } = '/';
  5596. #$mysync->{ debug } = 1 ;
  5597. $mysync->{ subfolder1 } = 'S1/S2' ;
  5598. is( 'F1/F2/F3', imap2_folder_name( $mysync, 'S1/S2/F1/F2/F3' ), 'imap2_folder_name: S1/S2/F1/F2/F3 -> F1/F2/F3' ) ;
  5599. is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/INBOX' ), 'imap2_folder_name: S1/S2/INBOX -> INBOX' ) ;
  5600. is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2' ), 'imap2_folder_name: S1/S2 -> INBOX' ) ;
  5601. is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/' ), 'imap2_folder_name: S1/S2/ -> INBOX' ) ;
  5602. $mysync->{ subfolder1 } = 'S1/S2/' ;
  5603. is( 'F1/F2/F3', imap2_folder_name( $mysync, 'S1/S2/F1/F2/F3' ), 'imap2_folder_name: S1/S2/F1/F2/F3 -> F1/F2/F3' ) ;
  5604. is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/INBOX' ), 'imap2_folder_name: S1/S2/INBOX -> INBOX' ) ;
  5605. is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2' ), 'imap2_folder_name: S1/S2 -> INBOX' ) ;
  5606. is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/' ), 'imap2_folder_name: S1/S2/ -> INBOX' ) ;
  5607. note( 'Leaving tests_imap2_folder_name()' ) ;
  5608. return ;
  5609. }
  5610. # Global variables to remove:
  5611. #
  5612. sub imap2_folder_name
  5613. {
  5614. my $mysync = shift ;
  5615. my ( $h1_fold ) = shift ;
  5616. my ( $h2_fold ) ;
  5617. if ( $mysync->{f1f2h}{ $h1_fold } ) {
  5618. $h2_fold = $mysync->{f1f2h}{ $h1_fold } ;
  5619. ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "f1f2 [$h1_fold] -> [$h2_fold]\n" ) ;
  5620. return( $h2_fold ) ;
  5621. }
  5622. if ( $mysync->{f1f2auto}{ $h1_fold } ) {
  5623. $h2_fold = $mysync->{f1f2auto}{ $h1_fold } ;
  5624. ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "automap [$h1_fold] -> [$h2_fold]\n" ) ;
  5625. return( $h2_fold ) ;
  5626. }
  5627. if ( $mysync->{ subfolder1 } )
  5628. {
  5629. my $esc_h1_sep = "\\" . $mysync->{ h1_sep } ;
  5630. # case where subfolder1 has the sep1 at the end, then remove it
  5631. my $part_to_removed = remove_last_char_if_is( $mysync->{ subfolder1 }, $mysync->{ h1_sep } ) ;
  5632. # remove the subfolder1 part and the sep1 if present after
  5633. $h1_fold =~ s{$part_to_removed($esc_h1_sep)?}{} ;
  5634. #myprint( "h1_fold=$h1_fold\n" ) ;
  5635. }
  5636. if ( ( '' eq $h1_fold ) or ( $mysync->{ h1_prefix } eq $h1_fold ) )
  5637. {
  5638. $h1_fold = 'INBOX' ;
  5639. }
  5640. $h2_fold = prefix_seperator_invertion( $mysync, $h1_fold ) ;
  5641. $h2_fold = regextrans2( $mysync, $h2_fold ) ;
  5642. return( $h2_fold ) ;
  5643. }
  5644. sub tests_remove_last_char_if_is
  5645. {
  5646. note( 'Entering tests_remove_last_char_if_is()' ) ;
  5647. is( undef, remove_last_char_if_is( ), 'remove_last_char_if_is: no args => undef' ) ;
  5648. is( '', remove_last_char_if_is( '' ), 'remove_last_char_if_is: empty => empty' ) ;
  5649. is( '', remove_last_char_if_is( '', 'Z' ), 'remove_last_char_if_is: empty Z => empty' ) ;
  5650. is( '', remove_last_char_if_is( 'Z', 'Z' ), 'remove_last_char_if_is: Z Z => empty' ) ;
  5651. is( 'abc', remove_last_char_if_is( 'abcZ', 'Z' ), 'remove_last_char_if_is: abcZ Z => abc' ) ;
  5652. is( 'abcY', remove_last_char_if_is( 'abcY', 'Z' ), 'remove_last_char_if_is: abcY Z => abcY' ) ;
  5653. note( 'Leaving tests_remove_last_char_if_is()' ) ;
  5654. return ;
  5655. }
  5656. sub remove_last_char_if_is
  5657. {
  5658. my $string = shift ;
  5659. my $char = shift ;
  5660. if ( ! defined $string )
  5661. {
  5662. return ;
  5663. }
  5664. if ( ! defined $char )
  5665. {
  5666. return $string ;
  5667. }
  5668. my $last_char = substr $string, -1 ;
  5669. if ( $char eq $last_char )
  5670. {
  5671. chop $string ;
  5672. return $string ;
  5673. }
  5674. else
  5675. {
  5676. return $string ;
  5677. }
  5678. }
  5679. sub tests_prefix_seperator_invertion
  5680. {
  5681. note( 'Entering tests_prefix_seperator_invertion()' ) ;
  5682. is( undef, prefix_seperator_invertion( ), 'prefix_seperator_invertion: no args => undef' ) ;
  5683. is( q{}, prefix_seperator_invertion( undef, q{} ), 'prefix_seperator_invertion: empty string => empty string' ) ;
  5684. is( 'lalala', prefix_seperator_invertion( undef, 'lalala' ), 'prefix_seperator_invertion: lalala => lalala' ) ;
  5685. is( 'lal/ala', prefix_seperator_invertion( undef, 'lal/ala' ), 'prefix_seperator_invertion: lal/ala => lal/ala' ) ;
  5686. is( 'lal.ala', prefix_seperator_invertion( undef, 'lal.ala' ), 'prefix_seperator_invertion: lal.ala => lal.ala' ) ;
  5687. is( '////', prefix_seperator_invertion( undef, '////' ), 'prefix_seperator_invertion: //// => ////' ) ;
  5688. is( '.....', prefix_seperator_invertion( undef, '.....' ), 'prefix_seperator_invertion: ..... => .....' ) ;
  5689. my $mysync = {
  5690. h1_prefix => '',
  5691. h2_prefix => '',
  5692. h1_sep => '/',
  5693. h2_sep => '/',
  5694. } ;
  5695. is( q{}, prefix_seperator_invertion( $mysync, q{} ), 'prefix_seperator_invertion: $mysync empty string => empty string' ) ;
  5696. is( 'lalala', prefix_seperator_invertion( $mysync, 'lalala' ), 'prefix_seperator_invertion: $mysync lalala => lalala' ) ;
  5697. is( 'lal/ala', prefix_seperator_invertion( $mysync, 'lal/ala' ), 'prefix_seperator_invertion: $mysync lal/ala => lal/ala' ) ;
  5698. is( 'lal.ala', prefix_seperator_invertion( $mysync, 'lal.ala' ), 'prefix_seperator_invertion: $mysync lal.ala => lal.ala' ) ;
  5699. is( '////', prefix_seperator_invertion( $mysync, '////' ), 'prefix_seperator_invertion: $mysync //// => ////' ) ;
  5700. is( '.....', prefix_seperator_invertion( $mysync, '.....' ), 'prefix_seperator_invertion: $mysync ..... => .....' ) ;
  5701. $mysync = {
  5702. h1_prefix => 'PPP',
  5703. h2_prefix => 'QQQ',
  5704. h1_sep => 's',
  5705. h2_sep => 't',
  5706. } ;
  5707. is( q{QQQ}, prefix_seperator_invertion( $mysync, q{} ), 'prefix_seperator_invertion: PPPQQQst empty string => QQQ' ) ;
  5708. is( 'QQQlalala', prefix_seperator_invertion( $mysync, 'lalala' ), 'prefix_seperator_invertion: PPPQQQst lalala => QQQlalala' ) ;
  5709. is( 'QQQlal/ala', prefix_seperator_invertion( $mysync, 'lal/ala' ), 'prefix_seperator_invertion: PPPQQQst lal/ala => QQQlal/ala' ) ;
  5710. is( 'QQQlal.ala', prefix_seperator_invertion( $mysync, 'lal.ala' ), 'prefix_seperator_invertion: PPPQQQst lal.ala => QQQlal.ala' ) ;
  5711. is( 'QQQ////', prefix_seperator_invertion( $mysync, '////' ), 'prefix_seperator_invertion: PPPQQQst //// => QQQ////' ) ;
  5712. is( 'QQQ.....', prefix_seperator_invertion( $mysync, '.....' ), 'prefix_seperator_invertion: PPPQQQst ..... => QQQ.....' ) ;
  5713. is( 'QQQPlalala', prefix_seperator_invertion( $mysync, 'PPPPlalala' ), 'prefix_seperator_invertion: PPPQQQst PPPPlalala => QQQPlalala' ) ;
  5714. is( 'QQQ', prefix_seperator_invertion( $mysync, 'PPP' ), 'prefix_seperator_invertion: PPPQQQst PPP => QQQ' ) ;
  5715. is( 'QQQttt', prefix_seperator_invertion( $mysync, 'sss' ), 'prefix_seperator_invertion: PPPQQQst sss => QQQttt' ) ;
  5716. is( 'QQQt', prefix_seperator_invertion( $mysync, 's' ), 'prefix_seperator_invertion: PPPQQQst s => QQQt' ) ;
  5717. is( 'QQQtAAAtBBB', prefix_seperator_invertion( $mysync, 'PPPsAAAsBBB' ), 'prefix_seperator_invertion: PPPQQQst PPPsAAAsBBB => QQQtAAAtBBB' ) ;
  5718. note( 'Leaving tests_prefix_seperator_invertion()' ) ;
  5719. return ;
  5720. }
  5721. # Global variables to remove:
  5722. sub prefix_seperator_invertion
  5723. {
  5724. my $mysync = shift ;
  5725. my $h1_fold = shift ;
  5726. my $h2_fold ;
  5727. if ( not defined $h1_fold ) { return ; }
  5728. my $my_h1_prefix = $mysync->{ h1_prefix } || q{} ;
  5729. my $my_h2_prefix = $mysync->{ h2_prefix } || q{} ;
  5730. my $my_h1_sep = $mysync->{ h1_sep } || '/' ;
  5731. my $my_h2_sep = $mysync->{ h2_sep } || '/' ;
  5732. # first we remove the prefix
  5733. $h1_fold =~ s/^\Q$my_h1_prefix\E//x ;
  5734. ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "removed host1 prefix: [$h1_fold]\n" ) ;
  5735. $h2_fold = separator_invert( $mysync, $h1_fold, $my_h1_sep, $my_h2_sep ) ;
  5736. ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "inverted separators: [$h2_fold]\n" ) ;
  5737. # Adding the prefix supplied by namespace or the --prefix2 option
  5738. # except for INBOX or Inbox
  5739. if ( $h2_fold !~ m/^INBOX$/xi )
  5740. {
  5741. $h2_fold = $my_h2_prefix . $h2_fold ;
  5742. }
  5743. ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "added host2 prefix: [$h2_fold]\n" ) ;
  5744. return( $h2_fold ) ;
  5745. }
  5746. sub tests_separator_invert
  5747. {
  5748. note( 'Entering tests_separator_invert()' ) ;
  5749. my $mysync = {} ;
  5750. $mysync->{ fixslash2 } = 0 ;
  5751. ok( not( defined separator_invert( ) ), 'separator_invert: no args' ) ;
  5752. ok( not( defined separator_invert( q{} ) ), 'separator_invert: not enough args' ) ;
  5753. ok( not( defined separator_invert( q{}, q{} ) ), 'separator_invert: not enough args' ) ;
  5754. ok( q{} eq separator_invert( $mysync, q{}, q{}, q{} ), 'separator_invert: 3 empty strings' ) ;
  5755. ok( 'lalala' eq separator_invert( $mysync, 'lalala', q{}, q{} ), 'separator_invert: empty separator' ) ;
  5756. ok( 'lalala' eq separator_invert( $mysync, 'lalala', '/', '/' ), 'separator_invert: same separator /' ) ;
  5757. ok( 'lal/ala' eq separator_invert( $mysync, 'lal/ala', '/', '/' ), 'separator_invert: same separator / 2' ) ;
  5758. ok( 'lal.ala' eq separator_invert( $mysync, 'lal/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
  5759. ok( 'lal/ala' eq separator_invert( $mysync, 'lal.ala', '.', '/' ), 'separator_invert: separators ./' ) ;
  5760. ok( 'la.l/ala' eq separator_invert( $mysync, 'la/l.ala', '.', '/' ), 'separator_invert: separators ./' ) ;
  5761. ok( 'l/al.ala' eq separator_invert( $mysync, 'l.al/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
  5762. $mysync->{ fixslash2 } = 1 ;
  5763. ok( 'l_al.ala' eq separator_invert( $mysync, 'l.al/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
  5764. note( 'Leaving tests_separator_invert()' ) ;
  5765. return ;
  5766. }
  5767. # Global variables to remove:
  5768. #
  5769. sub separator_invert
  5770. {
  5771. my( $mysync, $h1_fold, $h1_separator, $h2_separator ) = @_ ;
  5772. return( undef ) if ( not all_defined( $mysync, $h1_fold, $h1_separator, $h2_separator ) ) ;
  5773. # The separator we hope we'll never encounter: 00000000 == 0x00
  5774. my $o_sep = "\000" ;
  5775. my $h2_fold = $h1_fold ;
  5776. $h2_fold =~ s,\Q$h2_separator,$o_sep,xg ;
  5777. $h2_fold =~ s,\Q$h1_separator,$h2_separator,xg ;
  5778. $h2_fold =~ s,\Q$o_sep,$h1_separator,xg ;
  5779. $h2_fold =~ s,/,_,xg if( $mysync->{ fixslash2 } and '/' ne $h2_separator and '/' eq $h1_separator ) ;
  5780. return( $h2_fold ) ;
  5781. }
  5782. sub regextrans2
  5783. {
  5784. my( $mysync, $h2_fold ) = @_ ;
  5785. # Transforming the folder name by the --regextrans2 option(s)
  5786. foreach my $regextrans2 ( @{ $mysync->{ regextrans2 } } ) {
  5787. my $h2_fold_before = $h2_fold ;
  5788. my $ret = eval "\$h2_fold =~ $regextrans2 ; 1 " ;
  5789. ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "[$h2_fold_before] -> [$h2_fold] using regextrans2 [$regextrans2]\n" ) ;
  5790. if ( not ( defined $ret ) or $EVAL_ERROR ) {
  5791. exit_clean( $mysync, $EX_USAGE, "error: eval regextrans2 '$regextrans2': $EVAL_ERROR\n" ) ;
  5792. }
  5793. }
  5794. return( $h2_fold ) ;
  5795. }
  5796. sub tests_decompose_regex
  5797. {
  5798. note( 'Entering tests_decompose_regex()' ) ;
  5799. ok( 1, 'decompose_regex 1' ) ;
  5800. ok( 0 == compare_lists( [ q{}, q{} ], [ decompose_regex( q{} ) ] ), 'decompose_regex empty string' ) ;
  5801. ok( 0 == compare_lists( [ '.*', 'lala' ], [ decompose_regex( 's/.*/lala/' ) ] ), 'decompose_regex s/.*/lala/' ) ;
  5802. note( 'Leaving tests_decompose_regex()' ) ;
  5803. return ;
  5804. }
  5805. sub decompose_regex
  5806. {
  5807. my $regex = shift ;
  5808. my( $left_part, $right_part ) ;
  5809. ( $left_part, $right_part ) = $regex =~ m{^s/((?:[^/]|\\/)+)/((?:[^/]|\\/)+)/}x;
  5810. return( q{}, q{} ) if not $left_part ;
  5811. return( $left_part, $right_part ) ;
  5812. }
  5813. sub foldersizes
  5814. {
  5815. my ( $side, $imap, $search_cmd, $abletosearch, @folders ) = @_ ;
  5816. my $total_size = 0 ;
  5817. my $total_nb = 0 ;
  5818. my $biggest_in_all = 0 ;
  5819. my $nb_folders = scalar @folders ;
  5820. my $ct_folders = 0 ; # folder counter.
  5821. myprint( "++++ Calculating sizes of $nb_folders folders on $side\n" ) ;
  5822. foreach my $folder ( @folders ) {
  5823. my $stot = 0 ;
  5824. my $nb_msgs = 0 ;
  5825. $ct_folders++ ;
  5826. myprintf( "$side folder %7s %-35s", "$ct_folders/$nb_folders", jux_utf8( $folder ) ) ;
  5827. if ( 'Host2' eq $side and not exists $h2_folders_all_UPPER{ uc $folder } ) {
  5828. myprint( " does not exist yet\n") ;
  5829. next ;
  5830. }
  5831. if ( 'Host1' eq $side and not exists $h1_folders_all{ $folder } ) {
  5832. myprint( " does not exist\n" ) ;
  5833. next ;
  5834. }
  5835. last if $imap->IsUnconnected( ) ;
  5836. # FTGate is RFC buggy with EXAMINE it does not act as SELECT
  5837. #unless ( $imap->examine( $folder ) ) {
  5838. unless ( $imap->select( $folder ) ) {
  5839. my $error = join q{},
  5840. "$side Folder $folder: Could not select: ",
  5841. $imap->LastError, "\n" ;
  5842. errors_incr( $sync, $error ) ;
  5843. next ;
  5844. }
  5845. last if $imap->IsUnconnected( ) ;
  5846. my $hash_ref = { } ;
  5847. my @msgs = select_msgs( $imap, undef, $search_cmd, $abletosearch, $folder ) ;
  5848. $nb_msgs = scalar @msgs ;
  5849. my $biggest_in_folder = 0 ;
  5850. @{ $hash_ref }{ @msgs } = ( undef ) if @msgs ;
  5851. last if $imap->IsUnconnected( ) ;
  5852. if ( $nb_msgs > 0 and @msgs ) {
  5853. if ( $abletosearch ) {
  5854. if ( ! $imap->fetch_hash( \@msgs, 'RFC822.SIZE', $hash_ref) ) {
  5855. my $error = "$side failure with fetch_hash: $EVAL_ERROR\n" ;
  5856. errors_incr( $sync, $error ) ;
  5857. return ;
  5858. }
  5859. }else{
  5860. my $uidnext = $imap->uidnext( $folder ) || $uidnext_default ;
  5861. my $fetch_hash_uids = $fetch_hash_set || "1:$uidnext" ;
  5862. if ( ! $imap->fetch_hash( $fetch_hash_uids, 'RFC822.SIZE', $hash_ref ) ) {
  5863. my $error = "$side failure with fetch_hash: $EVAL_ERROR\n" ;
  5864. errors_incr( $sync, $error ) ;
  5865. return ;
  5866. }
  5867. }
  5868. for ( keys %{ $hash_ref } ) {
  5869. my $size = $hash_ref->{ $_ }->{ 'RFC822.SIZE' } ;
  5870. $stot += $size ;
  5871. $biggest_in_folder = max( $biggest_in_folder, $size ) ;
  5872. }
  5873. }
  5874. myprintf( ' Size: %9s', $stot ) ;
  5875. myprintf( ' Messages: %5s', $nb_msgs ) ;
  5876. myprintf( " Biggest: %9s\n", $biggest_in_folder ) ;
  5877. $total_size += $stot ;
  5878. $total_nb += $nb_msgs ;
  5879. $biggest_in_all = max( $biggest_in_all, $biggest_in_folder ) ;
  5880. }
  5881. myprintf( "%s Nb folders: %11s folders\n", $side, $nb_folders ) ;
  5882. myprintf( "%s Nb messages: %11s messages\n", $side, $total_nb ) ;
  5883. myprintf( "%s Total size: %11s bytes (%s)\n", $side, $total_size, bytes_display_string( $total_size ) ) ;
  5884. myprintf( "%s Biggest message: %11s bytes (%s)\n", $side, $biggest_in_all, bytes_display_string( $biggest_in_all ) ) ;
  5885. myprintf( "%s Time spent: %11.1f seconds\n", $side, timenext( ) ) ;
  5886. return( $total_nb, $total_size ) ;
  5887. }
  5888. sub timenext
  5889. {
  5890. my ( $timenow, $timediff ) ;
  5891. # $timebefore is global, beurk !
  5892. $timenow = time ;
  5893. $timediff = $timenow - $timebefore ;
  5894. $timebefore = $timenow ;
  5895. return( $timediff ) ;
  5896. }
  5897. sub timesince
  5898. {
  5899. my $timeinit = shift || 0 ;
  5900. my ( $timenow, $timediff ) ;
  5901. $timenow = time ;
  5902. $timediff = $timenow - $timeinit ;
  5903. # Often used in a division so no 0
  5904. return( max( 1, $timediff) ) ;
  5905. }
  5906. sub tests_flags_regex
  5907. {
  5908. note( 'Entering tests_flags_regex()' ) ;
  5909. ok( q{} eq flags_regex(q{} ), 'flags_regex, null string q{}' ) ;
  5910. ok( q{\Seen NonJunk $Spam} eq flags_regex( q{\Seen NonJunk $Spam} ), q{flags_regex, nothing to do} ) ;
  5911. @regexflag = ('I am BAD' ) ;
  5912. ok( not ( defined flags_regex( q{} ) ), 'flags_regex, bad regex' ) ;
  5913. @regexflag = ( 's/NonJunk//g' ) ;
  5914. ok( q{\Seen $Spam} eq flags_regex( q{\Seen NonJunk $Spam} ), q{flags_regex, remove NonJunk: 's/NonJunk//g'} ) ;
  5915. @regexflag = ( q{s/\$Spam//g} ) ;
  5916. ok( q{\Seen NonJunk } eq flags_regex( q{\Seen NonJunk $Spam} ), q{flags_regex, remove $Spam: 's/\$Spam//g'} ) ;
  5917. @regexflag = ( 's/\\\\Seen//g' ) ;
  5918. ok( q{ NonJunk $Spam} eq flags_regex( q{\Seen NonJunk $Spam} ), q{flags_regex, remove \Seen: 's/\\\\\\\\Seen//g'} ) ;
  5919. @regexflag = ( 's/(\s|^)[^\\\\]\w+//g' ) ;
  5920. 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} ) ;
  5921. ok( q{ \Seen \Middle \End1} eq flags_regex( q{Begin \Seen NonJunk \Middle $Spam \End1 End} ),
  5922. q{flags_regex: only \word among Begin \Seen NonJunk \Middle $Spam \End1 End} ) ;
  5923. @regexflag = ( q{s/.*?(Keep1|Keep2|Keep3)/$1 /g} ) ;
  5924. ok( 'Keep1 Keep2 ReB' eq flags_regex('ReA Keep1 REM Keep2 ReB'), 'Keep only regex' ) ;
  5925. ok( 'Keep1 Keep2 ' eq flags_regex( 'REM REM Keep1 Keep2'), 'Keep only regex' ) ;
  5926. ok( 'Keep1 Keep2 ' eq flags_regex( 'Keep1 REM REM Keep2'), 'Keep only regex' ) ;
  5927. ok( 'Keep1 Keep2 ' eq flags_regex( 'REM Keep1 REM REM Keep2'), 'Keep only regex' ) ;
  5928. ok( 'Keep1 Keep2 ' eq flags_regex( 'Keep1 Keep2'), 'Keep only regex' ) ;
  5929. ok( 'Keep1 ' eq flags_regex( 'REM Keep1'), 'Keep only regex' ) ;
  5930. @regexflag = ( q{s/(Keep1|Keep2|Keep3) (?!(Keep1|Keep2|Keep3)).*/$1 /g} ) ;
  5931. ok( 'Keep1 Keep2 ' eq flags_regex( 'Keep1 Keep2 ReB'), 'Keep only regex' ) ;
  5932. ok( 'Keep1 Keep2 ' eq flags_regex( 'Keep1 Keep2 REM REM REM'), 'Keep only regex' ) ;
  5933. ok( 'Keep2 ' eq flags_regex('Keep2 REM REM REM'), 'Keep only regex' ) ;
  5934. @regexflag = ( q{s/.*?(Keep1|Keep2|Keep3)/$1 /g},
  5935. 's/(Keep1|Keep2|Keep3) (?!(Keep1|Keep2|Keep3)).*/$1 /g' ) ;
  5936. ok( 'Keep1 Keep2 ' eq flags_regex('REM Keep1 REM Keep2 REM'), 'Keep only regex' ) ;
  5937. ok( 'Keep1 Keep2 ' eq flags_regex('Keep1 REM Keep2 REM'), 'Keep only regex' ) ;
  5938. ok( 'Keep1 Keep2 ' eq flags_regex('REM Keep1 Keep2 REM'), 'Keep only regex' ) ;
  5939. ok( 'Keep1 Keep2 ' eq flags_regex('REM Keep1 REM Keep2'), 'Keep only regex' ) ;
  5940. ok( 'Keep1 Keep2 Keep3 ' eq flags_regex('REM Keep1 REM Keep2 REM REM Keep3 REM'), 'Keep only regex' ) ;
  5941. ok( 'Keep1 ' eq flags_regex('REM REM Keep1 REM REM REM '), 'Keep only regex' ) ;
  5942. ok( 'Keep1 Keep3 ' eq flags_regex('RE1 Keep1 RE2 Keep3 RE3 RE4 RE5 '), 'Keep only regex' ) ;
  5943. @regexflag = ( 's/(.*)/$1 jrdH8u/' ) ;
  5944. ok('REM REM REM REM REM jrdH8u' eq flags_regex('REM REM REM REM REM'), q{Add jrdH8u 's/(.*)/\$1 jrdH8u/'} ) ;
  5945. @regexflag = ('s/jrdH8u *//');
  5946. ok('REM REM REM REM REM ' eq flags_regex('REM REM REM REM REM jrdH8u'), q{Remove jrdH8u s/jrdH8u *//} ) ;
  5947. @regexflag = (
  5948. 's/.*?(?:(\\\\(?:Answered|Flagged|Deleted|Seen|Draft)\s?)|$)/defined($1)?$1:q()/eg'
  5949. );
  5950. ok( '\\Deleted \\Answered '
  5951. eq flags_regex('Blabla \$Junk \\Deleted machin \\Answered truc'),
  5952. 'Keep only regex: Exchange case (Phil)' ) ;
  5953. ok( q{} eq flags_regex( q{} ), 'Keep only regex: Exchange case, null string (Phil)' ) ;
  5954. ok( q{}
  5955. eq flags_regex('Blabla $Junk machin truc'),
  5956. 'Keep only regex: Exchange case, no accepted flags (Phil)' ) ;
  5957. ok('\\Deleted \\Answered \\Draft \\Flagged '
  5958. eq flags_regex('\\Deleted \\Answered \\Draft \\Flagged '),
  5959. 'Keep only regex: Exchange case (Phil)' ) ;
  5960. @regexflag = ( 's/\\\\Flagged//g' ) ;
  5961. is('\Deleted \Answered \Draft ',
  5962. flags_regex('\\Deleted \\Answered \\Draft \\Flagged '),
  5963. 'flags_regex: remove \Flagged 1' ) ;
  5964. is('\\Deleted \\Answered \\Draft',
  5965. flags_regex('\\Deleted \\Flagged \\Answered \\Draft'),
  5966. 'flags_regex: remove \Flagged 2' ) ;
  5967. # I didn't understand why it gives \F
  5968. # https://perldoc.perl.org/perlrebackslash.html
  5969. # \F Foldcase till \E. Not in [].
  5970. # https://perldoc.perl.org/functions/fc.html
  5971. # \F Not available in old Perl so I comment the test
  5972. # @regexflag = ( 's/\\Flagged/X/g' ) ;
  5973. #is('\Deleted FX \Answered \FX \Draft \FX',
  5974. #flags_regex( '\Deleted Flagged \Answered \Flagged \Draft \Flagged' ),
  5975. # 'flags_regex: remove \Flagged 3 mistery...' ) ;
  5976. note( 'Leaving tests_flags_regex()' ) ;
  5977. return ;
  5978. }
  5979. sub flags_regex
  5980. {
  5981. my ( $h1_flags ) = @_ ;
  5982. foreach my $regexflag ( @regexflag ) {
  5983. my $h1_flags_orig = $h1_flags ;
  5984. $debugflags and myprint( "eval \$h1_flags =~ $regexflag\n" ) ;
  5985. my $ret = eval "\$h1_flags =~ $regexflag ; 1 " ;
  5986. $debugflags and myprint( "regexflag $regexflag [$h1_flags_orig] -> [$h1_flags]\n" ) ;
  5987. if( not ( defined $ret ) or $EVAL_ERROR ) {
  5988. myprint( "Error: eval regexflag '$regexflag': $EVAL_ERROR\n" ) ;
  5989. return( undef ) ;
  5990. }
  5991. }
  5992. return( $h1_flags ) ;
  5993. }
  5994. sub acls_sync
  5995. {
  5996. my($h1_fold, $h2_fold) = @_ ;
  5997. if ( $syncacls ) {
  5998. my $h1_hash = $sync->{imap1}->getacl($h1_fold)
  5999. or myprint( "Could not getacl for $h1_fold: $EVAL_ERROR\n" ) ;
  6000. my $h2_hash = $sync->{imap2}->getacl($h2_fold)
  6001. or myprint( "Could not getacl for $h2_fold: $EVAL_ERROR\n" ) ;
  6002. my %users = map { ($_, 1) } ( keys %{ $h1_hash} , keys %{ $h2_hash } ) ;
  6003. foreach my $user (sort keys %users ) {
  6004. my $acl = $h1_hash->{$user} || 'none' ;
  6005. myprint( "acl $user: [$acl]\n" ) ;
  6006. next if ($h1_hash->{$user} && $h2_hash->{$user} &&
  6007. $h1_hash->{$user} eq $h2_hash->{$user});
  6008. unless ($sync->{dry}) {
  6009. myprint( "setting acl $h2_fold $user $acl\n" ) ;
  6010. $sync->{imap2}->setacl($h2_fold, $user, $acl)
  6011. or myprint( "Could not set acl: $EVAL_ERROR\n" ) ;
  6012. }
  6013. }
  6014. }
  6015. return ;
  6016. }
  6017. sub tests_permanentflags
  6018. {
  6019. note( 'Entering tests_permanentflags()' ) ;
  6020. my $string;
  6021. ok(q{} eq permanentflags(' * OK [PERMANENTFLAGS (\* \Draft \Answered)] Limited'),
  6022. 'permanentflags \*');
  6023. ok('\Draft \Answered' eq permanentflags(' * OK [PERMANENTFLAGS (\Draft \Answered)] Limited'),
  6024. 'permanentflags \Draft \Answered');
  6025. ok('\Draft \Answered'
  6026. eq permanentflags('Blabla',
  6027. ' * OK [PERMANENTFLAGS (\Draft \Answered)] Limited',
  6028. 'Blabla'),
  6029. 'permanentflags \Draft \Answered'
  6030. );
  6031. ok(q{} eq permanentflags('Blabla'), 'permanentflags nothing');
  6032. note( 'Leaving tests_permanentflags()' ) ;
  6033. return ;
  6034. }
  6035. sub permanentflags
  6036. {
  6037. my @lines = @_ ;
  6038. foreach my $line (@lines) {
  6039. if ( $line =~ m{\[PERMANENTFLAGS\s\(([^)]+?)\)\]}x ) {
  6040. ( $debugflags or $sync->{ debug } ) and myprint( "permanentflags: $line" ) ;
  6041. my $permanentflags = $1 ;
  6042. if ( $permanentflags =~ m{\\\*}x ) {
  6043. $permanentflags = q{} ;
  6044. }
  6045. return($permanentflags) ;
  6046. } ;
  6047. }
  6048. return( q{} ) ;
  6049. }
  6050. sub tests_flags_filter
  6051. {
  6052. note( 'Entering tests_flags_filter()' ) ;
  6053. ok( '\Seen' eq flags_filter('\Seen', '\Draft \Seen \Answered'), 'flags_filter ' );
  6054. ok( q{} eq flags_filter('\Seen', '\Draft \Answered'), 'flags_filter ' );
  6055. ok( '\Seen' eq flags_filter('\Seen', '\Seen'), 'flags_filter ' );
  6056. ok( '\Seen' eq flags_filter('\Seen', ' \Seen '), 'flags_filter ' );
  6057. ok( '\Seen \Draft'
  6058. eq flags_filter('\Seen \Draft', '\Draft \Seen \Answered'), 'flags_filter ' );
  6059. ok( '\Seen \Draft'
  6060. eq flags_filter('\Seen \Draft', ' \Draft \Seen \Answered '), 'flags_filter ' );
  6061. note( 'Leaving tests_flags_filter()' ) ;
  6062. return ;
  6063. }
  6064. sub flags_filter
  6065. {
  6066. my( $flags, $allowed_flags ) = @_ ;
  6067. my @flags = split /\s+/x, $flags ;
  6068. my %allowed_flags = map { $_ => 1 } split q{ }, $allowed_flags ;
  6069. my @flags_out = map { exists $allowed_flags{$_} ? $_ : () } @flags ;
  6070. my $flags_out = join q{ }, @flags_out ;
  6071. return( $flags_out ) ;
  6072. }
  6073. sub flagscase
  6074. {
  6075. my $flags = shift ;
  6076. my @flags = split /\s+/x, $flags ;
  6077. my %rfc_flags = map { $_ => 1 } split q{ }, '\Answered \Flagged \Deleted \Seen \Draft' ;
  6078. my @flags_out = map { exists $rfc_flags{ ucsecond( lc $_ ) } ? ucsecond( lc $_ ) : $_ } @flags ;
  6079. my $flags_out = join q{ }, @flags_out ;
  6080. return( $flags_out ) ;
  6081. }
  6082. sub tests_flagscase
  6083. {
  6084. note( 'Entering tests_flagscase()' ) ;
  6085. ok( '\Seen' eq flagscase( '\Seen' ), 'flagscase: \Seen -> \Seen' ) ;
  6086. ok( '\Seen' eq flagscase( '\SEEN' ), 'flagscase: \SEEN -> \Seen' ) ;
  6087. ok( '\Seen \Draft' eq flagscase( '\SEEN \DRAFT' ), 'flagscase: \SEEN \DRAFT -> \Seen \Draft' ) ;
  6088. ok( '\Draft \Seen' eq flagscase( '\DRAFT \SEEN' ), 'flagscase: \DRAFT \SEEN -> \Draft \Seen' ) ;
  6089. ok( '\Draft LALA \Seen' eq flagscase( '\DRAFT LALA \SEEN' ), 'flagscase: \DRAFT LALA \SEEN -> \Draft LALA \Seen' ) ;
  6090. ok( '\Draft lala \Seen' eq flagscase( '\DRAFT lala \SEEN' ), 'flagscase: \DRAFT lala \SEEN -> \Draft lala \Seen' ) ;
  6091. note( 'Leaving tests_flagscase()' ) ;
  6092. return ;
  6093. }
  6094. sub ucsecond
  6095. {
  6096. my $string = shift ;
  6097. my $output ;
  6098. return( $string ) if ( 1 >= length $string ) ;
  6099. $output = ( substr( $string, 0, 1) ) . ( uc substr $string, 1, 1 ) . ( substr $string, 2 ) ;
  6100. #myprint( "UUU $string -> $output\n" ) ;
  6101. return( $output ) ;
  6102. }
  6103. sub tests_ucsecond
  6104. {
  6105. note( 'Entering tests_ucsecond()' ) ;
  6106. ok( 'aBcde' eq ucsecond( 'abcde' ), 'ucsecond: abcde -> aBcde' ) ;
  6107. ok( 'ABCDE' eq ucsecond( 'ABCDE' ), 'ucsecond: ABCDE -> ABCDE' ) ;
  6108. ok( 'ABCDE' eq ucsecond( 'AbCDE' ), 'ucsecond: AbCDE -> ABCDE' ) ;
  6109. ok( 'ABCde' eq ucsecond( 'AbCde' ), 'ucsecond: AbCde -> ABCde' ) ;
  6110. ok( 'A' eq ucsecond( 'A' ), 'ucsecond: A -> A' ) ;
  6111. ok( 'AB' eq ucsecond( 'Ab' ), 'ucsecond: Ab -> AB' ) ;
  6112. ok( '\B' eq ucsecond( '\b' ), 'ucsecond: \b -> \B' ) ;
  6113. ok( '\Bcde' eq ucsecond( '\bcde' ), 'ucsecond: \bcde -> \Bcde' ) ;
  6114. note( 'Leaving tests_ucsecond()' ) ;
  6115. return ;
  6116. }
  6117. sub select_msgs
  6118. {
  6119. my ( $imap, $msgs_all_hash_ref, $search_cmd, $abletosearch, $folder ) = @_ ;
  6120. my ( @msgs ) ;
  6121. if ( $abletosearch ) {
  6122. @msgs = select_msgs_by_search( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) ;
  6123. }else{
  6124. @msgs = select_msgs_by_fetch( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) ;
  6125. }
  6126. return( @msgs ) ;
  6127. }
  6128. sub select_msgs_by_search
  6129. {
  6130. my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ;
  6131. my ( @msgs, @msgs_all ) ;
  6132. # Need to have the whole list in msgs_all_hash_ref
  6133. # without calling messages() several times.
  6134. # Need all messages list to avoid deleting useful cache part
  6135. # in case of --search or --minage or --maxage
  6136. if ( ( defined $msgs_all_hash_ref and $usecache )
  6137. or ( not defined $maxage and not defined $minage and not defined $search_cmd )
  6138. ) {
  6139. $debugdev and myprint( "Calling messages()\n" ) ;
  6140. @msgs_all = $imap->messages( ) ;
  6141. return if ( $#msgs_all == 0 && !defined $msgs_all[0] ) ;
  6142. if ( defined $msgs_all_hash_ref ) {
  6143. @{ $msgs_all_hash_ref }{ @msgs_all } = () ;
  6144. }
  6145. # return all messages
  6146. if ( not defined $maxage and not defined $minage and not defined $search_cmd ) {
  6147. return( @msgs_all ) ;
  6148. }
  6149. }
  6150. if ( defined $search_cmd ) {
  6151. @msgs = $imap->search( $search_cmd ) ;
  6152. return( @msgs ) ;
  6153. }
  6154. # we are here only if $maxage or $minage is defined
  6155. @msgs = select_msgs_by_age( $imap ) ;
  6156. return( @msgs );
  6157. }
  6158. sub select_msgs_by_fetch
  6159. {
  6160. my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ;
  6161. my ( @msgs, @msgs_all, %fetch ) ;
  6162. # Need to have the whole list in msgs_all_hash_ref
  6163. # without calling messages() several times.
  6164. # Need all messages list to avoid deleting useful cache part
  6165. # in case of --search or --minage or --maxage
  6166. $debugdev and myprint( "Calling fetch_hash()\n" ) ;
  6167. my $uidnext = $imap->uidnext( $folder ) || $uidnext_default ;
  6168. my $fetch_hash_uids = $fetch_hash_set || "1:$uidnext" ;
  6169. %fetch = %{$imap->fetch_hash( $fetch_hash_uids, 'INTERNALDATE' ) } ;
  6170. @msgs_all = sort { $a <=> $b } keys %fetch ;
  6171. $debugdev and myprint( "Done fetch_hash()\n" ) ;
  6172. return if ( $#msgs_all == 0 && !defined $msgs_all[0] ) ;
  6173. if ( defined $msgs_all_hash_ref ) {
  6174. @{ $msgs_all_hash_ref }{ @msgs_all } = () ;
  6175. }
  6176. # return all messages
  6177. if ( not defined $maxage and not defined $minage and not defined $search_cmd ) {
  6178. return( @msgs_all ) ;
  6179. }
  6180. if ( defined $search_cmd ) {
  6181. myprint( "Warning: strange to see --search with --noabletosearch, an error can happen\n" ) ;
  6182. @msgs = $imap->search( $search_cmd ) ;
  6183. return( @msgs ) ;
  6184. }
  6185. # we are here only if $maxage or $minage is defined
  6186. my( @max, @min, $maxage_epoch, $minage_epoch ) ;
  6187. if ( defined $maxage ) { $maxage_epoch = $timestart_int - $NB_SECONDS_IN_A_DAY * $maxage ; }
  6188. if ( defined $minage ) { $minage_epoch = $timestart_int - $NB_SECONDS_IN_A_DAY * $minage ; }
  6189. foreach my $msg ( @msgs_all ) {
  6190. my $idate = $fetch{ $msg }->{'INTERNALDATE'} ;
  6191. #myprint( "$idate\n" ) ;
  6192. if ( defined $maxage and ( epoch( $idate ) >= $maxage_epoch ) ) {
  6193. push @max, $msg ;
  6194. }
  6195. if ( defined $minage and ( epoch( $idate ) <= $minage_epoch ) ) {
  6196. push @min, $msg ;
  6197. }
  6198. }
  6199. @msgs = msgs_from_maxmin( \@max, \@min ) ;
  6200. return( @msgs ) ;
  6201. }
  6202. sub select_msgs_by_age
  6203. {
  6204. my( $imap ) = @_ ;
  6205. my( @max, @min, @msgs, @inter, @union ) ;
  6206. if ( defined $maxage ) {
  6207. @max = $imap->sentsince( $timestart_int - $NB_SECONDS_IN_A_DAY * $maxage ) ;
  6208. }
  6209. if ( defined $minage ) {
  6210. @min = $imap->sentbefore( $timestart_int - $NB_SECONDS_IN_A_DAY * $minage ) ;
  6211. }
  6212. @msgs = msgs_from_maxmin( \@max, \@min ) ;
  6213. return( @msgs ) ;
  6214. }
  6215. sub msgs_from_maxmin
  6216. {
  6217. my( $max_ref, $min_ref ) = @_ ;
  6218. my( @max, @min, @msgs, @inter, @union ) ;
  6219. @max = @{ $max_ref } ;
  6220. @min = @{ $min_ref } ;
  6221. SWITCH: {
  6222. unless( defined $minage ) { @msgs = @max ; last SWITCH } ;
  6223. unless( defined $maxage ) { @msgs = @min ; last SWITCH } ;
  6224. my ( %union, %inter ) ;
  6225. foreach my $m ( @min, @max ) { $union{ $m }++ && $inter{ $m }++ }
  6226. @inter = sort { $a <=> $b } keys %inter ;
  6227. @union = sort { $a <=> $b } keys %union ;
  6228. # normal case
  6229. if ( $minage <= $maxage ) { @msgs = @inter ; last SWITCH } ;
  6230. # just exclude messages between
  6231. if ( $minage > $maxage ) { @msgs = @union ; last SWITCH } ;
  6232. }
  6233. return( @msgs ) ;
  6234. }
  6235. sub tests_msgs_from_maxmin
  6236. {
  6237. note( 'Entering tests_msgs_from_maxmin()' ) ;
  6238. my @msgs ;
  6239. $maxage = $NUMBER_200 ;
  6240. @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
  6241. ok( 0 == compare_lists( [ '1', '2' ], \@msgs ), 'msgs_from_maxmin: maxage++' ) ;
  6242. $minage = $NUMBER_100 ;
  6243. @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
  6244. ok( 0 == compare_lists( [ '2' ], \@msgs ), 'msgs_from_maxmin: -maxage++minage-' ) ;
  6245. $minage = $NUMBER_300 ;
  6246. @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
  6247. ok( 0 == compare_lists( [ '1', '2', '3' ], \@msgs ), 'msgs_from_maxmin: ++maxage-minage++' ) ;
  6248. $maxage = undef ;
  6249. @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
  6250. ok( 0 == compare_lists( [ '2', '3' ], \@msgs ), 'msgs_from_maxmin: ++minage-' ) ;
  6251. note( 'Leaving tests_msgs_from_maxmin()' ) ;
  6252. return ;
  6253. }
  6254. sub tests_info_date_from_uid
  6255. {
  6256. note( 'Entering tests_info_date_from_uid()' ) ;
  6257. note( 'Leaving tests_info_date_from_uid()' ) ;
  6258. return ;
  6259. }
  6260. sub info_date_from_uid
  6261. {
  6262. #my $first_uid = $msgs_all[ 0 ] ;
  6263. #my $first_idate = $fetch{ $first_uid }->{'INTERNALDATE'} ;
  6264. #my $first_epoch = epoch( $first_idate ) ;
  6265. #my $first_days = ( $timestart_int - $first_epoch ) / $NB_SECONDS_IN_A_DAY ;
  6266. #myprint( "\nOldest msg has UID $first_uid INTERNALDATE $first_idate EPOCH $first_epoch DAYS AGO $first_days\n" ) ;
  6267. }
  6268. sub lastuid
  6269. {
  6270. my $imap = shift ;
  6271. my $folder = shift ;
  6272. my $lastuid_guess = shift ;
  6273. my $lastuid ;
  6274. # rfc3501: The only reliable way to identify recent messages is to
  6275. # look at message flags to see which have the \Recent flag
  6276. # set, or to do a SEARCH RECENT.
  6277. # SEARCH RECENT doesn't work this way on courrier.
  6278. my @recent_messages ;
  6279. # SEARCH RECENT for each transfer can be expensive with a big folder
  6280. # Call commented for now
  6281. #@recent_messages = $imap->recent( ) ;
  6282. #myprint( "Recent: @recent_messages\n" ) ;
  6283. my $max_recent ;
  6284. $max_recent = max( @recent_messages ) ;
  6285. if ( defined $max_recent and ($lastuid_guess <= $max_recent ) ) {
  6286. $lastuid = $max_recent ;
  6287. }else{
  6288. $lastuid = $lastuid_guess
  6289. }
  6290. return( $lastuid ) ;
  6291. }
  6292. sub size_filtered
  6293. {
  6294. my( $h1_size, $h1_msg, $h1_fold, $h2_fold ) = @_ ;
  6295. $h1_size = 0 if ( ! $h1_size ) ; # null if empty or undef
  6296. if ( defined $sync->{ maxsize } and $h1_size > $sync->{ maxsize } ) {
  6297. myprint( "msg $h1_fold/$h1_msg skipped ($h1_size exceeds maxsize limit $sync->{ maxsize } bytes)\n" ) ;
  6298. $sync->{ total_bytes_skipped } += $h1_size;
  6299. $sync->{ nb_msg_skipped } += 1;
  6300. return( 1 ) ;
  6301. }
  6302. if ( defined $minsize and $h1_size <= $minsize ) {
  6303. myprint( "msg $h1_fold/$h1_msg skipped ($h1_size smaller than minsize $minsize bytes)\n" ) ;
  6304. $sync->{ total_bytes_skipped } += $h1_size;
  6305. $sync->{ nb_msg_skipped } += 1;
  6306. return( 1 ) ;
  6307. }
  6308. return( 0 ) ;
  6309. }
  6310. sub message_exists
  6311. {
  6312. my( $imap, $msg ) = @_ ;
  6313. return( 1 ) if not $imap->Uid( ) ;
  6314. my $search_uid ;
  6315. ( $search_uid ) = $imap->search( "UID $msg" ) ;
  6316. #myprint( "$search ? $msg\n" ) ;
  6317. return( 1 ) if ( $search_uid eq $msg ) ;
  6318. return( 0 ) ;
  6319. }
  6320. # Globals
  6321. # $sync->{ total_bytes_skipped }
  6322. # $sync->{ nb_msg_skipped }
  6323. # $mysync->{ h1_nb_msg_processed }
  6324. sub stats_update_skip_message
  6325. {
  6326. my $mysync = shift ; # to be used
  6327. my $h1_size = shift ;
  6328. $mysync->{ total_bytes_skipped } += $h1_size ;
  6329. $mysync->{ nb_msg_skipped } += 1 ;
  6330. $mysync->{ h1_nb_msg_processed } +=1 ;
  6331. return ;
  6332. }
  6333. sub copy_message
  6334. {
  6335. # copy
  6336. my ( $mysync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) = @_ ;
  6337. ( $mysync->{ debug } or $mysync->{dry}) and myprint( "msg $h1_fold/$h1_msg copying to $h2_fold $mysync->{dry_message}\n" ) ;
  6338. my $h1_size = $h1_fir_ref->{$h1_msg}->{'RFC822.SIZE'} || 0 ;
  6339. my $h1_flags = $h1_fir_ref->{$h1_msg}->{'FLAGS'} || q{} ;
  6340. my $h1_idate = $h1_fir_ref->{$h1_msg}->{'INTERNALDATE'} || q{} ;
  6341. if ( size_filtered( $h1_size, $h1_msg, $h1_fold, $h2_fold ) ) {
  6342. $mysync->{ h1_nb_msg_processed } +=1 ;
  6343. return ;
  6344. }
  6345. debugsleep( $mysync ) ;
  6346. myprint( "- msg $h1_fold/$h1_msg S[$h1_size] F[$h1_flags] I[$h1_idate] has RFC822.SIZE null!\n" ) if ( ! $h1_size ) ;
  6347. if ( $checkmessageexists and not message_exists( $mysync->{imap1}, $h1_msg ) ) {
  6348. stats_update_skip_message( $mysync, $h1_size ) ;
  6349. return ;
  6350. }
  6351. myprint( debugmemory( $mysync, " at C1" ) ) ;
  6352. my ( $string, $string_len ) ;
  6353. ( $string_len ) = message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, \$string ) ;
  6354. myprint( debugmemory( $mysync, " at C2" ) ) ;
  6355. # not defined or empty $string
  6356. if ( ( not $string ) or ( not $string_len ) ) {
  6357. myprint( "- msg $h1_fold/$h1_msg skipped.\n" ) ;
  6358. stats_update_skip_message( $mysync, $h1_size ) ;
  6359. return ;
  6360. }
  6361. # Lines too long (or not enough) => do no copy or fix
  6362. if ( ( defined $maxlinelength ) or ( defined $minmaxlinelength ) ) {
  6363. $string = linelengthstuff( $string, $h1_fold, $h1_msg, $string_len, $h1_size, $h1_flags, $h1_idate ) ;
  6364. if ( not defined $string ) {
  6365. stats_update_skip_message( $mysync, $h1_size ) ;
  6366. return ;
  6367. }
  6368. }
  6369. my $h1_date = date_for_host2( $h1_msg, $h1_idate ) ;
  6370. ( $mysync->{ debug } or $debugflags ) and
  6371. myprint( "Host1: flags init msg $h1_fold/$h1_msg date [$h1_date] flags [$h1_flags] size [$h1_size]\n" ) ;
  6372. $h1_flags = flags_for_host2( $h1_flags, $permanentflags2 ) ;
  6373. ( $mysync->{ debug } or $debugflags ) and
  6374. myprint( "Host1: flags filt msg $h1_fold/$h1_msg date [$h1_date] flags [$h1_flags] size [$h1_size]\n" ) ;
  6375. $h1_date = undef if ( $h1_date eq q{} ) ;
  6376. my $new_id = append_message_on_host2( $mysync, \$string, $h1_fold, $h1_msg, $string_len, $h2_fold, $h1_size, $h1_flags, $h1_date, $cache_dir ) ;
  6377. if ( $new_id and $syncflagsaftercopy ) {
  6378. sync_flags_after_copy( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $new_id, $permanentflags2 ) ;
  6379. }
  6380. myprint( debugmemory( $mysync, " at C3" ) ) ;
  6381. return $new_id ;
  6382. }
  6383. sub linelengthstuff
  6384. {
  6385. my( $string, $h1_fold, $h1_msg, $string_len, $h1_size, $h1_flags, $h1_idate ) = @_ ;
  6386. my $maxlinelength_string = max_line_length( $string ) ;
  6387. $debugmaxlinelength and myprint( "msg $h1_fold/$h1_msg maxlinelength: $maxlinelength_string\n" ) ;
  6388. if ( ( defined $minmaxlinelength ) and ( $maxlinelength_string <= $minmaxlinelength ) ) {
  6389. my $subject = subject( $string ) ;
  6390. $debugdev and myprint( "- msg $h1_fold/$h1_msg skipped S[$h1_size] F[$h1_flags] I[$h1_idate] "
  6391. . "(Subject:[$subject]) (max line length under minmaxlinelength $minmaxlinelength bytes)\n" ) ;
  6392. return ;
  6393. }
  6394. if ( ( defined $maxlinelength ) and ( $maxlinelength_string > $maxlinelength ) ) {
  6395. my $subject = subject( $string ) ;
  6396. if ( $maxlinelengthcmd ) {
  6397. $string = pipemess( $string, $maxlinelengthcmd ) ;
  6398. # string undef means something was bad.
  6399. if ( not ( defined $string ) ) {
  6400. myprint( "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate] "
  6401. . "(Subject:[$subject]) could not be successfully transformed by --maxlinelengthcmd option\n" ) ;
  6402. return ;
  6403. }else{
  6404. return $string ;
  6405. }
  6406. }
  6407. myprint( "- msg $h1_fold/$h1_msg skipped S[$h1_size] F[$h1_flags] I[$h1_idate] "
  6408. . "(Subject:[$subject]) (line length exceeds maxlinelength $maxlinelength bytes)\n" ) ;
  6409. return ;
  6410. }
  6411. return $string ;
  6412. }
  6413. sub message_for_host2
  6414. {
  6415. # global variable list:
  6416. # @skipmess
  6417. # @regexmess
  6418. # @pipemess
  6419. # $debugcontent
  6420. # $debug
  6421. #
  6422. # API current
  6423. #
  6424. # at failure:
  6425. # * return nothing ( will then be undef or () )
  6426. # * $string_ref content is undef or empty
  6427. # at success:
  6428. # * return string length ($string_ref content length)
  6429. # * $string_ref content filled with message
  6430. # API future
  6431. #
  6432. #
  6433. my ( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ) = @_ ;
  6434. # abort when missing a parameter
  6435. if ( ( ! $mysync ) or ( ! $h1_msg ) or ( ! $h1_fold ) or ( ! defined $h1_size )
  6436. or ( ! defined $h1_flags) or ( ! defined $h1_idate )
  6437. or ( ! $h1_fir_ref) or ( ! $string_ref ) )
  6438. {
  6439. return ;
  6440. }
  6441. myprint( debugmemory( $mysync, " at M1" ) ) ;
  6442. my $string_ok = $mysync->{imap1}->message_to_file( $string_ref, $h1_msg ) ;
  6443. myprint( debugmemory( $mysync, " at M2" ) ) ;
  6444. my $string_len = length_ref( $string_ref ) ;
  6445. unless ( defined $string_ok and $string_len ) {
  6446. # undef or 0 length
  6447. my $error = join q{},
  6448. "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate] could not be fetched: ",
  6449. $mysync->{imap1}->LastError || q{}, "\n" ;
  6450. errors_incr( $mysync, $error ) ;
  6451. $mysync->{ h1_nb_msg_processed } +=1 ;
  6452. return ;
  6453. }
  6454. if ( @skipmess ) {
  6455. my $match = skipmess( ${ $string_ref } ) ;
  6456. # string undef means the eval regex was bad.
  6457. if ( not ( defined $match ) ) {
  6458. myprint(
  6459. "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
  6460. . " could not be skipped by --skipmess option, bad regex\n" ) ;
  6461. return ;
  6462. }
  6463. if ( $match ) {
  6464. my $subject = subject( ${ $string_ref } ) ;
  6465. myprint( "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
  6466. . " (Subject:[$subject]) skipped by --skipmess\n" ) ;
  6467. return ;
  6468. }
  6469. }
  6470. if ( @regexmess ) {
  6471. ${ $string_ref } = regexmess( ${ $string_ref } ) ;
  6472. # string undef means the eval regex was bad.
  6473. if ( not ( defined ${ $string_ref } ) ) {
  6474. myprint(
  6475. "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
  6476. . " could not be transformed by --regexmess\n" ) ;
  6477. return ;
  6478. }
  6479. }
  6480. if ( @pipemess ) {
  6481. ${ $string_ref } = pipemess( ${ $string_ref }, @pipemess ) ;
  6482. # string undef means something was bad.
  6483. if ( not ( defined ${ $string_ref } ) ) {
  6484. myprint(
  6485. "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
  6486. . " could not be successfully transformed by --pipemess option\n" ) ;
  6487. return ;
  6488. }
  6489. }
  6490. if ( $mysync->{addheader} and defined $h1_fir_ref->{$h1_msg}->{'NO_HEADER'} ) {
  6491. my $header = add_header( $h1_msg ) ;
  6492. $mysync->{ debug } and myprint( "msg $h1_fold/$h1_msg adding custom header [$header]\n" ) ;
  6493. ${ $string_ref } = $header . "\r\n" . ${ $string_ref } ;
  6494. }
  6495. $string_len = length_ref( $string_ref ) ;
  6496. $debugcontent and myprint(
  6497. q{=} x $STD_CHAR_PER_LINE, "\n",
  6498. "F message content begin next line ($string_len characters long)\n",
  6499. ${ $string_ref },
  6500. "F message content ended on previous line\n", q{=} x $STD_CHAR_PER_LINE, "\n" ) ;
  6501. myprint( debugmemory( $mysync, " at M3" ) ) ;
  6502. return $string_len ;
  6503. }
  6504. sub tests_message_for_host2
  6505. {
  6506. note( 'Entering tests_message_for_host2()' ) ;
  6507. my ( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ) ;
  6508. is( undef, message_for_host2( ), q{message_for_host2: no args} ) ;
  6509. 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} ) ;
  6510. require_ok( "Test::MockObject" ) ;
  6511. my $imapT = Test::MockObject->new( ) ;
  6512. $mysync->{imap1} = $imapT ;
  6513. my $string ;
  6514. $h1_msg = 1 ;
  6515. $h1_fold = 'FoldFoo';
  6516. $h1_size = 9 ;
  6517. $h1_flags = '' ;
  6518. $h1_idate = '10-Jul-2015 09:00:00 +0200' ;
  6519. $h1_fir_ref = {} ;
  6520. $string_ref = \$string ;
  6521. $imapT->mock( 'message_to_file',
  6522. sub {
  6523. my ( $imap, $mystring_ref, $msg ) = @_ ;
  6524. ${$mystring_ref} = 'blablabla' ;
  6525. return length ${$mystring_ref} ;
  6526. }
  6527. ) ;
  6528. is( 9, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
  6529. q{message_for_host2: msg 1 == "blablabla", length} ) ;
  6530. is( 'blablabla', $string, q{message_for_host2: msg 1 == "blablabla", value} ) ;
  6531. # so far so good
  6532. # now the --pipemess stuff
  6533. SKIP: {
  6534. Readonly my $NB_WIN_tests_message_for_host2 => 0 ;
  6535. skip( 'Not on MSWin32', $NB_WIN_tests_message_for_host2 ) if ('MSWin32' ne $OSNAME) ;
  6536. # Windows
  6537. # "type" command does not accept redirection of STDIN with <
  6538. # "sort" does
  6539. } ;
  6540. SKIP: {
  6541. Readonly my $NB_UNX_tests_message_for_host2 => 6 ;
  6542. skip( 'Not on Unix', $NB_UNX_tests_message_for_host2 ) if ('MSWin32' eq $OSNAME) ;
  6543. # Unix
  6544. # no change by cat
  6545. @pipemess = ( 'cat' ) ;
  6546. is( 9, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
  6547. q{message_for_host2: --pipemess 'cat', length} ) ;
  6548. is( 'blablabla', $string, q{message_for_host2: --pipemess 'cat', value} ) ;
  6549. # failure by false
  6550. @pipemess = ( 'false' ) ;
  6551. is( undef, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
  6552. q{message_for_host2: --pipemess 'false', length} ) ;
  6553. is( undef, $string, q{message_for_host2: --pipemess 'false', value} ) ;
  6554. # failure by true since no output
  6555. @pipemess = ( 'true' ) ;
  6556. is( undef, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
  6557. q{message_for_host2: --pipemess 'true', length} ) ;
  6558. is( undef, $string, q{message_for_host2: --pipemess 'true', value} ) ;
  6559. }
  6560. note( 'Leaving tests_message_for_host2()' ) ;
  6561. return ;
  6562. }
  6563. sub tests_labels_remove_subfolder1
  6564. {
  6565. note( 'Entering tests_labels_remove_subfolder1()' ) ;
  6566. is( undef, labels_remove_subfolder1( ), 'labels_remove_subfolder1: no parameters => undef' ) ;
  6567. is( 'Blabla', labels_remove_subfolder1( 'Blabla' ), 'labels_remove_subfolder1: one parameter Blabla => Blabla' ) ;
  6568. is( 'Blan blue', labels_remove_subfolder1( 'Blan blue' ), 'labels_remove_subfolder1: one parameter Blan blue => Blan blue' ) ;
  6569. is( '\Bla "Blan blan" Blabla', labels_remove_subfolder1( '\Bla "Blan blan" Blabla' ),
  6570. 'labels_remove_subfolder1: one parameter \Bla "Blan blan" Blabla => \Bla "Blan blan" Blabla' ) ;
  6571. is( 'Bla', labels_remove_subfolder1( 'Subf/Bla', 'Subf' ), 'labels_remove_subfolder1: Subf/Bla Subf => "Bla"' ) ;
  6572. is( '"\\\\Bla"', labels_remove_subfolder1( '"\\\\Bla"', 'Subf' ), 'labels_remove_subfolder1: "\\\\Bla" Subf => "\\\\Bla"' ) ;
  6573. is( 'Bla Kii', labels_remove_subfolder1( 'Subf/Bla Subf/Kii', 'Subf' ),
  6574. 'labels_remove_subfolder1: Subf/Bla Subf/Kii, Subf => "Bla" "Kii"' ) ;
  6575. is( '"\\\\Bla" Kii', labels_remove_subfolder1( '"\\\\Bla" Subf/Kii', 'Subf' ),
  6576. 'labels_remove_subfolder1: "\\\\Bla" Subf/Kii Subf => "\\\\Bla" Kii' ) ;
  6577. is( '"Blan blan"', labels_remove_subfolder1( '"Subf/Blan blan"', 'Subf' ),
  6578. 'labels_remove_subfolder1: "Subf/Blan blan" Subf => "Blan blan"' ) ;
  6579. is( '"\\\\Loo" "Blan blan" Kii', labels_remove_subfolder1( '"\\\\Loo" "Subf/Blan blan" Subf/Kii', 'Subf' ),
  6580. 'labels_remove_subfolder1: "\\\\Loo" "Subf/Blan blan" Subf/Kii + Subf => "\\\\Loo" "Blan blan" Kii' ) ;
  6581. is( '"\\\\Inbox"', labels_remove_subfolder1( 'Subf/INBOX', 'Subf' ),
  6582. 'labels_remove_subfolder1: Subf/INBOX + Subf => "\\\\Inbox"' ) ;
  6583. is( '"\\\\Loo" "Blan blan" Kii "\\\\Inbox"', labels_remove_subfolder1( '"\\\\Loo" "Subf/Blan blan" Subf/Kii Subf/INBOX', 'Subf' ),
  6584. 'labels_remove_subfolder1: "\\\\Loo" "Subf/Blan blan" Subf/Kii Subf/INBOX + Subf => "\\\\Loo" "Blan blan" Kii "\\\\Inbox"' ) ;
  6585. note( 'Leaving tests_labels_remove_subfolder1()' ) ;
  6586. return ;
  6587. }
  6588. sub labels_remove_subfolder1
  6589. {
  6590. my $labels = shift ;
  6591. my $subfolder1 = shift ;
  6592. if ( not defined $labels ) { return ; }
  6593. if ( not defined $subfolder1 ) { return $labels ; }
  6594. my @labels = quotewords('\s+', 1, $labels ) ;
  6595. #myprint( "@labels\n" ) ;
  6596. my @labels_subfolder2 ;
  6597. foreach my $label ( @labels )
  6598. {
  6599. if ( $label =~ m{zzzzzzzzzz} )
  6600. {
  6601. # \Seen \Deleted ... stay the same
  6602. push @labels_subfolder2, $label ;
  6603. }
  6604. else
  6605. {
  6606. # Remove surrounding quotes if any, to add them again in case of space
  6607. $label = join( '', quotewords('\s+', 0, $label ) ) ;
  6608. $label =~ s{$subfolder1/?}{} ;
  6609. if ( 'INBOX' eq $label )
  6610. {
  6611. push @labels_subfolder2, q{"\\\\Inbox"} ;
  6612. }
  6613. elsif ( $label =~ m{\\} )
  6614. {
  6615. push @labels_subfolder2, qq{"\\$label"} ;
  6616. }
  6617. elsif ( $label =~ m{ } )
  6618. {
  6619. push @labels_subfolder2, qq{"$label"} ;
  6620. }
  6621. else
  6622. {
  6623. push @labels_subfolder2, $label ;
  6624. }
  6625. }
  6626. }
  6627. my $labels_subfolder2 = join( ' ', sort uniq( @labels_subfolder2 ) ) ;
  6628. return $labels_subfolder2 ;
  6629. }
  6630. sub tests_labels_remove_special
  6631. {
  6632. note( 'Entering tests_labels_remove_special()' ) ;
  6633. is( undef, labels_remove_special( ), 'labels_remove_special: no parameters => undef' ) ;
  6634. is( '', labels_remove_special( '' ), 'labels_remove_special: empty string => empty string' ) ;
  6635. is( '', labels_remove_special( '"\\\\Inbox"' ), 'labels_remove_special:"\\\\Inbox" => empty string' ) ;
  6636. is( '', labels_remove_special( '"\\\\Inbox" "\\\\Starred"' ), 'labels_remove_special:"\\\\Inbox" "\\\\Starred" => empty string' ) ;
  6637. is( 'Bar Foo', labels_remove_special( 'Foo Bar' ), 'labels_remove_special:Foo Bar => Bar Foo' ) ;
  6638. is( 'Bar Foo', labels_remove_special( 'Foo Bar "\\\\Inbox"' ), 'labels_remove_special:Foo Bar "\\\\Inbox" => Bar Foo' ) ;
  6639. note( 'Leaving tests_labels_remove_special()' ) ;
  6640. return ;
  6641. }
  6642. sub labels_remove_special
  6643. {
  6644. my $labels = shift ;
  6645. if ( not defined $labels ) { return ; }
  6646. my @labels = quotewords('\s+', 1, $labels ) ;
  6647. myprint( "labels before remove_non_folded: @labels\n" ) ;
  6648. my @labels_remove_special ;
  6649. foreach my $label ( @labels )
  6650. {
  6651. if ( $label =~ m{^\"\\\\} )
  6652. {
  6653. # not kept
  6654. }
  6655. else
  6656. {
  6657. push @labels_remove_special, $label ;
  6658. }
  6659. }
  6660. my $labels_remove_special = join( ' ', sort @labels_remove_special ) ;
  6661. return $labels_remove_special ;
  6662. }
  6663. sub tests_labels_add_subfolder2
  6664. {
  6665. note( 'Entering tests_labels_add_subfolder2()' ) ;
  6666. is( undef, labels_add_subfolder2( ), 'labels_add_subfolder2: no parameters => undef' ) ;
  6667. is( 'Blabla', labels_add_subfolder2( 'Blabla' ), 'labels_add_subfolder2: one parameter Blabla => Blabla' ) ;
  6668. is( 'Blan blue', labels_add_subfolder2( 'Blan blue' ), 'labels_add_subfolder2: one parameter Blan blue => Blan blue' ) ;
  6669. is( '\Bla "Blan blan" Blabla', labels_add_subfolder2( '\Bla "Blan blan" Blabla' ),
  6670. 'labels_add_subfolder2: one parameter \Bla "Blan blan" Blabla => \Bla "Blan blan" Blabla' ) ;
  6671. is( 'Subf/Bla', labels_add_subfolder2( 'Bla', 'Subf' ), 'labels_add_subfolder2: Bla Subf => "Subf/Bla"' ) ;
  6672. is( 'Subf/\Bla', labels_add_subfolder2( '\\\\Bla', 'Subf' ), 'labels_add_subfolder2: \Bla Subf => \Bla' ) ;
  6673. is( 'Subf/Bla Subf/Kii', labels_add_subfolder2( 'Bla Kii', 'Subf' ),
  6674. 'labels_add_subfolder2: Bla Kii Subf => "Subf/Bla" "Subf/Kii"' ) ;
  6675. is( 'Subf/Kii Subf/\Bla', labels_add_subfolder2( '\\\\Bla Kii', 'Subf' ),
  6676. 'labels_add_subfolder2: \Bla Kii Subf => \Bla Subf/Kii' ) ;
  6677. is( '"Subf/Blan blan"', labels_add_subfolder2( '"Blan blan"', 'Subf' ),
  6678. 'labels_add_subfolder2: "Blan blan" Subf => "Subf/Blan blan"' ) ;
  6679. is( '"Subf/Blan blan" Subf/Kii Subf/\Loo', labels_add_subfolder2( '\\\\Loo "Blan blan" Kii', 'Subf' ),
  6680. 'labels_add_subfolder2: \Loo "Blan blan" Kii + Subf => "Subf/Blan blan" Subf/Kii Subf/\Loo' ) ;
  6681. # "\\Inbox" is special, add to subfolder INBOX also because Gmail will but ...
  6682. is( '"Subf/\\\\Inbox" Subf/INBOX', labels_add_subfolder2( '"\\\\Inbox"', 'Subf' ),
  6683. 'labels_add_subfolder2: "\\\\Inbox" Subf => "Subf/\\\\Inbox" Subf/INBOX' ) ;
  6684. # but not with INBOX folder
  6685. is( '"Subf/\\\\Inbox"', labels_add_subfolder2( '"\\\\Inbox"', 'Subf', 'INBOX' ),
  6686. 'labels_add_subfolder2: "\\\\Inbox" Subf INBOX => "Subf/\\\\Inbox"' ) ;
  6687. # two times => one time
  6688. is( '"Subf/\\\\Inbox" Subf/INBOX', labels_add_subfolder2( '"\\\\Inbox" "\\\\Inbox"', 'Subf' ),
  6689. 'labels_add_subfolder2: "\\\\Inbox" "\\\\Inbox" Subf => "Subf/\\\\Inbox"' ) ;
  6690. is( '"Subf/\\\\Starred"', labels_add_subfolder2( '"\\\\Starred"', 'Subf' ),
  6691. 'labels_add_subfolder2: "\\\\Starred" Subf => "Subf/\\\\Starred"' ) ;
  6692. note( 'Leaving tests_labels_add_subfolder2()' ) ;
  6693. return ;
  6694. }
  6695. sub labels_add_subfolder2
  6696. {
  6697. my $labels = shift ;
  6698. my $subfolder2 = shift ;
  6699. my $h1_folder = shift || q{} ;
  6700. if ( not defined $labels ) { return ; }
  6701. if ( not defined $subfolder2 ) { return $labels ; }
  6702. # Isn't it messy?
  6703. if ( 'INBOX' eq $h1_folder )
  6704. {
  6705. $labels .= ' "\\\\Inbox"' ;
  6706. }
  6707. my @labels = uniq( quotewords('\s+', 1, $labels ) ) ;
  6708. myprint( "labels before subfolder2: @labels\n" ) ;
  6709. my @labels_subfolder2 ;
  6710. foreach my $label ( @labels )
  6711. {
  6712. # Isn't it more messy?
  6713. if ( ( q{"\\\\Inbox"} eq $label ) and ( 'INBOX' ne $h1_folder ) )
  6714. {
  6715. if ( $subfolder2 =~ m{ } )
  6716. {
  6717. push @labels_subfolder2, qq{"$subfolder2/INBOX"} ;
  6718. }
  6719. else
  6720. {
  6721. push @labels_subfolder2, "$subfolder2/INBOX" ;
  6722. }
  6723. }
  6724. if ( $label =~ m{^\"\\\\} )
  6725. {
  6726. # \Seen \Deleted ... stay the same
  6727. #push @labels_subfolder2, $label ;
  6728. # Remove surrounding quotes if any, to add them again
  6729. $label = join( '', quotewords('\s+', 0, $label ) ) ;
  6730. push @labels_subfolder2, qq{"$subfolder2/\\$label"} ;
  6731. }
  6732. else
  6733. {
  6734. # Remove surrounding quotes if any, to add them again in case of space
  6735. $label = join( '', quotewords('\s+', 0, $label ) ) ;
  6736. if ( $label =~ m{ } )
  6737. {
  6738. push @labels_subfolder2, qq{"$subfolder2/$label"} ;
  6739. }
  6740. else
  6741. {
  6742. push @labels_subfolder2, "$subfolder2/$label" ;
  6743. }
  6744. }
  6745. }
  6746. my $labels_subfolder2 = join( ' ', sort @labels_subfolder2 ) ;
  6747. return $labels_subfolder2 ;
  6748. }
  6749. sub tests_labels
  6750. {
  6751. note( 'Entering tests_labels()' ) ;
  6752. is( undef, labels( ), 'labels: no parameters => undef' ) ;
  6753. is( undef, labels( undef ), 'labels: undef => undef' ) ;
  6754. require_ok( "Test::MockObject" ) ;
  6755. my $myimap = Test::MockObject->new( ) ;
  6756. $myimap->mock( 'fetch_hash',
  6757. sub {
  6758. return(
  6759. { '1' => {
  6760. 'X-GM-LABELS' => '\Seen Blabla'
  6761. }
  6762. }
  6763. ) ;
  6764. }
  6765. ) ;
  6766. $myimap->mock( 'Debug' , sub { } ) ;
  6767. $myimap->mock( 'Unescape', sub { return Mail::IMAPClient::Unescape( @_ ) } ) ; # real one
  6768. is( undef, labels( $myimap ), 'labels: one parameter => undef' ) ;
  6769. is( '\Seen Blabla', labels( $myimap, '1' ), 'labels: $mysync UID_1 => \Seen Blabla' ) ;
  6770. note( 'Leaving tests_labels()' ) ;
  6771. return ;
  6772. }
  6773. sub labels
  6774. {
  6775. my ( $myimap, $uid ) = @ARG ;
  6776. if ( not all_defined( $myimap, $uid ) ) {
  6777. return ;
  6778. }
  6779. my $hash = $myimap->fetch_hash( [ $uid ], 'X-GM-LABELS' ) ;
  6780. my $labels = $hash->{ $uid }->{ 'X-GM-LABELS' } ;
  6781. #$labels = $myimap->Unescape( $labels ) ;
  6782. return $labels ;
  6783. }
  6784. sub tests_synclabels
  6785. {
  6786. note( 'Entering tests_synclabels()' ) ;
  6787. is( undef, synclabels( ), 'synclabels: no parameters => undef' ) ;
  6788. is( undef, synclabels( undef ), 'synclabels: undef => undef' ) ;
  6789. my $mysync ;
  6790. is( undef, synclabels( $mysync ), 'synclabels: var undef => undef' ) ;
  6791. require_ok( "Test::MockObject" ) ;
  6792. $mysync = {} ;
  6793. my $myimap1 = Test::MockObject->new( ) ;
  6794. $myimap1->mock( 'fetch_hash',
  6795. sub {
  6796. return(
  6797. { '1' => {
  6798. 'X-GM-LABELS' => '\Seen Blabla'
  6799. }
  6800. }
  6801. ) ;
  6802. }
  6803. ) ;
  6804. $myimap1->mock( 'Debug', sub { } ) ;
  6805. $myimap1->mock( 'Unescape', sub { return Mail::IMAPClient::Unescape( @_ ) } ) ; # real one
  6806. my $myimap2 = Test::MockObject->new( ) ;
  6807. $myimap2->mock( 'store',
  6808. sub {
  6809. return 1 ;
  6810. }
  6811. ) ;
  6812. $mysync->{imap1} = $myimap1 ;
  6813. $mysync->{imap2} = $myimap2 ;
  6814. is( undef, synclabels( $mysync ), 'synclabels: fresh $mysync => undef' ) ;
  6815. is( undef, synclabels( $mysync, '1' ), 'synclabels: $mysync UID_1 alone => undef' ) ;
  6816. is( 1, synclabels( $mysync, '1', '2' ), 'synclabels: $mysync UID_1 UID_2 => 1' ) ;
  6817. note( 'Leaving tests_synclabels()' ) ;
  6818. return ;
  6819. }
  6820. sub synclabels
  6821. {
  6822. my( $mysync, $uid1, $uid2 ) = @ARG ;
  6823. if ( not all_defined( $mysync, $uid1, $uid2 ) ) {
  6824. return ;
  6825. }
  6826. my $myimap1 = $mysync->{ 'imap1' } || return ;
  6827. my $myimap2 = $mysync->{ 'imap2' } || return ;
  6828. $mysync->{debuglabels} and $myimap1->Debug( 1 ) ;
  6829. my $labels1 = labels( $myimap1, $uid1 ) ;
  6830. $mysync->{debuglabels} and $myimap1->Debug( 0 ) ;
  6831. $mysync->{debuglabels} and myprint( "Host1 labels: $labels1\n" ) ;
  6832. if ( $mysync->{ subfolder1 } and $labels1 )
  6833. {
  6834. $labels1 = labels_remove_subfolder1( $labels1, $mysync->{ subfolder1 } ) ;
  6835. $mysync->{debuglabels} and myprint( "Host1 labels with subfolder1: $labels1\n" ) ;
  6836. }
  6837. if ( $mysync->{ subfolder2 } and $labels1 )
  6838. {
  6839. $labels1 = labels_add_subfolder2( $labels1, $mysync->{ subfolder2 } ) ;
  6840. $mysync->{debuglabels} and myprint( "Host1 labels with subfolder2: $labels1\n" ) ;
  6841. }
  6842. my $store ;
  6843. if ( $labels1 and not $mysync->{ dry } )
  6844. {
  6845. $mysync->{ debuglabels } and $myimap2->Debug( 1 ) ;
  6846. $store = $myimap2->store( $uid2, "X-GM-LABELS ($labels1)" ) ;
  6847. $mysync->{ debuglabels } and $myimap2->Debug( 0 ) ;
  6848. }
  6849. return $store ;
  6850. }
  6851. sub tests_resynclabels
  6852. {
  6853. note( 'Entering tests_resynclabels()' ) ;
  6854. is( undef, resynclabels( ), 'resynclabels: no parameters => undef' ) ;
  6855. is( undef, resynclabels( undef ), 'resynclabels: undef => undef' ) ;
  6856. my $mysync ;
  6857. is( undef, resynclabels( $mysync ), 'resynclabels: var undef => undef' ) ;
  6858. my ( $h1_fir_ref, $h2_fir_ref ) ;
  6859. $mysync->{ debuglabels } = 1 ;
  6860. $h1_fir_ref->{ 11 }->{ 'X-GM-LABELS' } = '\Seen Baa Kii' ;
  6861. $h2_fir_ref->{ 22 }->{ 'X-GM-LABELS' } = '\Seen Baa Kii' ;
  6862. # labels are equal
  6863. is( 1, resynclabels( $mysync, 11, 22, $h1_fir_ref, $h2_fir_ref ),
  6864. 'resynclabels: $mysync UID_1 UID_2 labels are equal => 1' ) ;
  6865. # labels are different
  6866. $h2_fir_ref->{ 22 }->{ 'X-GM-LABELS' } = '\Seen Zuu' ;
  6867. require_ok( "Test::MockObject" ) ;
  6868. my $myimap2 = Test::MockObject->new( ) ;
  6869. $myimap2->mock( 'store',
  6870. sub {
  6871. return 1 ;
  6872. }
  6873. ) ;
  6874. $myimap2->mock( 'Debug', sub { } ) ;
  6875. $mysync->{imap2} = $myimap2 ;
  6876. is( 1, resynclabels( $mysync, 11, 22, $h1_fir_ref, $h2_fir_ref ),
  6877. 'resynclabels: $mysync UID_1 UID_2 labels are not equal => store => 1' ) ;
  6878. note( 'Leaving tests_resynclabels()' ) ;
  6879. return ;
  6880. }
  6881. sub resynclabels
  6882. {
  6883. my( $mysync, $uid1, $uid2, $h1_fir_ref, $h2_fir_ref, $h1_folder ) = @ARG ;
  6884. if ( not all_defined( $mysync, $uid1, $uid2, $h1_fir_ref, $h2_fir_ref ) ) {
  6885. return ;
  6886. }
  6887. my $labels1 = $h1_fir_ref->{ $uid1 }->{ 'X-GM-LABELS' } || q{} ;
  6888. my $labels2 = $h2_fir_ref->{ $uid2 }->{ 'X-GM-LABELS' } || q{} ;
  6889. if ( $mysync->{ subfolder1 } and $labels1 )
  6890. {
  6891. $labels1 = labels_remove_subfolder1( $labels1, $mysync->{ subfolder1 } ) ;
  6892. }
  6893. if ( $mysync->{ subfolder2 } and $labels1 )
  6894. {
  6895. $labels1 = labels_add_subfolder2( $labels1, $mysync->{ subfolder2 }, $h1_folder ) ;
  6896. $labels2 = labels_remove_special( $labels2 ) ;
  6897. }
  6898. $mysync->{ debuglabels } and myprint( "Host1 labels fixed: $labels1\n" ) ;
  6899. $mysync->{ debuglabels } and myprint( "Host2 labels : $labels2\n" ) ;
  6900. my $store ;
  6901. if ( $labels1 eq $labels2 )
  6902. {
  6903. # no sync needed
  6904. $mysync->{ debuglabels } and myprint( "Labels are already equal\n" ) ;
  6905. return 1 ;
  6906. }
  6907. elsif ( not $mysync->{ dry } )
  6908. {
  6909. # sync needed
  6910. $mysync->{debuglabels} and $mysync->{imap2}->Debug( 1 ) ;
  6911. $store = $mysync->{imap2}->store( $uid2, "X-GM-LABELS ($labels1)" ) ;
  6912. $mysync->{debuglabels} and $mysync->{imap2}->Debug( 0 ) ;
  6913. }
  6914. return $store ;
  6915. }
  6916. sub tests_uniq
  6917. {
  6918. note( 'Entering tests_uniq()' ) ;
  6919. is( 0, uniq( ), 'uniq: undef => 0' ) ;
  6920. is_deeply( [ 'one' ], [ uniq( 'one' ) ], 'uniq: one => one' ) ;
  6921. is_deeply( [ 'one' ], [ uniq( 'one', 'one' ) ], 'uniq: one one => one' ) ;
  6922. is_deeply( [ 'one', 'two' ], [ uniq( 'one', 'one', 'two', 'one', 'two' ) ], 'uniq: one one two one two => one two' ) ;
  6923. note( 'Leaving tests_uniq()' ) ;
  6924. return ;
  6925. }
  6926. sub uniq
  6927. {
  6928. my @list = @ARG ;
  6929. my %seen = ( ) ;
  6930. my @uniq = ( ) ;
  6931. foreach my $item ( @list ) {
  6932. if ( ! $seen{ $item } ) {
  6933. $seen{ $item } = 1 ;
  6934. push( @uniq, $item ) ;
  6935. }
  6936. }
  6937. return @uniq ;
  6938. }
  6939. sub length_ref
  6940. {
  6941. my $string_ref = shift ;
  6942. my $string_len = defined ${ $string_ref } ? length( ${ $string_ref } ) : q{} ; # length or empty string
  6943. return $string_len ;
  6944. }
  6945. sub tests_length_ref
  6946. {
  6947. note( 'Entering tests_length_ref()' ) ;
  6948. my $notdefined ;
  6949. is( q{}, length_ref( \$notdefined ), q{length_ref: value not defined} ) ;
  6950. my $notref ;
  6951. is( q{}, length_ref( $notref ), q{length_ref: param not a ref} ) ;
  6952. my $lala = 'lala' ;
  6953. is( 4, length_ref( \$lala ), q{length_ref: lala length == 4} ) ;
  6954. is( 4, length_ref( \'lili' ), q{length_ref: lili length == 4} ) ;
  6955. note( 'Leaving tests_length_ref()' ) ;
  6956. return ;
  6957. }
  6958. sub date_for_host2
  6959. {
  6960. my( $h1_msg, $h1_idate ) = @_ ;
  6961. my $h1_date = q{} ;
  6962. if ( $syncinternaldates ) {
  6963. $h1_date = $h1_idate ;
  6964. $sync->{ debug } and myprint( "internal date from host1: [$h1_date]\n" ) ;
  6965. $h1_date = good_date( $h1_date ) ;
  6966. $sync->{ debug } and myprint( "internal date from host1: [$h1_date] (fixed)\n" ) ;
  6967. }
  6968. if ( $idatefromheader ) {
  6969. $h1_date = $sync->{imap1}->get_header( $h1_msg, 'Date' ) ;
  6970. $sync->{ debug } and myprint( "header date from host1: [$h1_date]\n" ) ;
  6971. $h1_date = good_date( $h1_date ) ;
  6972. $sync->{ debug } and myprint( "header date from host1: [$h1_date] (fixed)\n" ) ;
  6973. }
  6974. return( $h1_date ) ;
  6975. }
  6976. sub flags_for_host2
  6977. {
  6978. my( $h1_flags, $permanentflags2 ) = @_ ;
  6979. # RFC 2060: This flag can not be altered by any client
  6980. $h1_flags =~ s@\\Recent\s?@@xgi ;
  6981. my $h1_flags_re ;
  6982. if ( @regexflag and defined( $h1_flags_re = flags_regex( $h1_flags ) ) ) {
  6983. $h1_flags = $h1_flags_re ;
  6984. }
  6985. $h1_flags = flagscase( $h1_flags ) if $flagscase ;
  6986. $h1_flags = flags_filter( $h1_flags, $permanentflags2) if ( $permanentflags2 and $filterflags ) ;
  6987. return( $h1_flags ) ;
  6988. }
  6989. sub subject
  6990. {
  6991. my $string = shift ;
  6992. my $subject = q{} ;
  6993. my $header = extract_header( $string ) ;
  6994. if( $header =~ m/^Subject:\s*([^\n\r]*)\r?$/msx ) {
  6995. #myprint( "MMM[$1]\n" ) ;
  6996. $subject = $1 ;
  6997. }
  6998. return( $subject ) ;
  6999. }
  7000. sub tests_subject
  7001. {
  7002. note( 'Entering tests_subject()' ) ;
  7003. ok( q{} eq subject( q{} ), 'subject: null') ;
  7004. ok( 'toto le hero' eq subject( 'Subject: toto le hero' ), 'subject: toto le hero') ;
  7005. ok( 'toto le hero' eq subject( 'Subject:toto le hero' ), 'subject: toto le hero blank') ;
  7006. ok( 'toto le hero' eq subject( "Subject:toto le hero\r\n" ), 'subject: toto le hero\r\n') ;
  7007. my $MESS ;
  7008. $MESS = <<'EOF';
  7009. From: lalala
  7010. Subject: toto le hero
  7011. Date: zzzzzz
  7012. Boogie boogie
  7013. EOF
  7014. ok( 'toto le hero' eq subject( $MESS ), 'subject: toto le hero 2') ;
  7015. $MESS = <<'EOF';
  7016. Subject: toto le hero
  7017. From: lalala
  7018. Date: zzzzzz
  7019. Boogie boogie
  7020. EOF
  7021. ok( 'toto le hero' eq subject( $MESS ), 'subject: toto le hero 3') ;
  7022. $MESS = <<'EOF';
  7023. From: lalala
  7024. Subject: cuicui
  7025. Date: zzzzzz
  7026. Subject: toto le hero
  7027. EOF
  7028. ok( 'cuicui' eq subject( $MESS ), 'subject: cuicui') ;
  7029. $MESS = <<'EOF';
  7030. From: lalala
  7031. Date: zzzzzz
  7032. Subject: toto le hero
  7033. EOF
  7034. ok( q{} eq subject( $MESS ), 'subject: null but body could') ;
  7035. note( 'Leaving tests_subject()' ) ;
  7036. return ;
  7037. }
  7038. # GlobVar
  7039. # $max_msg_size_in_bytes
  7040. # $h2_uidguess
  7041. # ...
  7042. #
  7043. #
  7044. sub append_message_on_host2
  7045. {
  7046. my( $mysync, $string_ref, $h1_fold, $h1_msg, $string_len, $h2_fold, $h1_size, $h1_flags, $h1_date, $cache_dir ) = @_ ;
  7047. myprint( debugmemory( $mysync, " at A1" ) ) ;
  7048. my $new_id ;
  7049. if ( ! $mysync->{dry} ) {
  7050. $max_msg_size_in_bytes = max( $h1_size, $max_msg_size_in_bytes ) ;
  7051. $new_id = $mysync->{imap2}->append_string( $h2_fold, ${ $string_ref }, $h1_flags, $h1_date ) ;
  7052. myprint( debugmemory( $mysync, " at A2" ) ) ;
  7053. if ( ! $new_id){
  7054. my $subject = subject( ${ $string_ref } ) ;
  7055. my $error_imap = $mysync->{imap2}->LastError || q{} ;
  7056. my $error = "- msg $h1_fold/$h1_msg {$string_len} could not append ( Subject:[$subject], Date:[$h1_date], Size:[$h1_size], Flags:[$h1_flags] ) to folder $h2_fold: $error_imap\n" ;
  7057. errors_incr( $mysync, $error ) ;
  7058. $mysync->{ h1_nb_msg_processed } +=1 ;
  7059. return ;
  7060. }
  7061. else{
  7062. # good
  7063. # $new_id is an id if the IMAP server has the
  7064. # UIDPLUS capability else just a ref
  7065. if ( $new_id !~ m{^\d+$}x ) {
  7066. $new_id = lastuid( $mysync->{imap2}, $h2_fold, $h2_uidguess ) ;
  7067. }
  7068. if ( $mysync->{ synclabels } ) { synclabels( $mysync, $h1_msg, $new_id ) }
  7069. $h2_uidguess += 1 ;
  7070. $mysync->{total_bytes_transferred} += $h1_size ;
  7071. $mysync->{nb_msg_transferred} += 1 ;
  7072. $mysync->{ h1_nb_msg_processed } +=1 ;
  7073. my $time_spent = timesince( $mysync->{begin_transfer_time} ) ;
  7074. my $rate = bytes_display_string( $mysync->{total_bytes_transferred} / $time_spent ) ;
  7075. my $eta = eta( $time_spent,
  7076. $mysync->{ h1_nb_msg_processed }, $h1_nb_msg_start, $mysync->{nb_msg_transferred} ) ;
  7077. my $amount_transferred = bytes_display_string( $mysync->{total_bytes_transferred} ) ;
  7078. myprintf( "msg %s/%-19s copied to %s/%-10s %.2f msgs/s %s/s %s copied %s\n",
  7079. $h1_fold, "$h1_msg {$string_len}", $h2_fold, $new_id, $mysync->{nb_msg_transferred}/$time_spent, $rate,
  7080. $amount_transferred,
  7081. $eta );
  7082. sleep_if_needed( $mysync ) ;
  7083. if ( $usecache and $cacheaftercopy and $new_id =~ m{^\d+$}x ) {
  7084. $debugcache and myprint( "touch $cache_dir/${h1_msg}_$new_id\n" ) ;
  7085. touch( "$cache_dir/${h1_msg}_$new_id" )
  7086. or croak( "Couldn't touch $cache_dir/${h1_msg}_$new_id" ) ;
  7087. }
  7088. if ( $mysync->{ delete1 } ) {
  7089. delete_message_on_host1( $mysync, $h1_fold, $mysync->{ expungeaftereach }, $h1_msg ) ;
  7090. }
  7091. #myprint( "PRESS ENTER" ) and my $a = <> ;
  7092. return( $new_id ) ;
  7093. }
  7094. }
  7095. else{
  7096. $nb_msg_skipped_dry_mode += 1 ;
  7097. $mysync->{ h1_nb_msg_processed } +=1 ;
  7098. }
  7099. return ;
  7100. }
  7101. sub tests_sleep_if_needed
  7102. {
  7103. note( 'Entering tests_sleep_if_needed()' ) ;
  7104. is( undef, sleep_if_needed( ), 'sleep_if_needed: no args => undef' ) ;
  7105. my $mysync ;
  7106. is( undef, sleep_if_needed( $mysync ), 'sleep_if_needed: arg undef => undef' ) ;
  7107. $mysync->{maxbytespersecond} = 1000 ;
  7108. is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: maxbytespersecond only => no sleep => 0' ) ;
  7109. $mysync->{begin_transfer_time} = time ; # now
  7110. is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: begin_transfer_time now => no sleep => 0' ) ;
  7111. $mysync->{begin_transfer_time} = time - 2 ; # 2 s before
  7112. is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 0 => no sleep => 0' ) ;
  7113. $mysync->{total_bytes_transferred} = 2200 ;
  7114. $mysync->{begin_transfer_time} = time - 2 ; # 2 s before
  7115. is( '0.20', sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 2200 since 2s => sleep 0.2s' ) ;
  7116. is( '0', sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 2200 since 2+2 == 4s => no sleep' ) ;
  7117. $mysync->{maxsleep} = 0.1 ;
  7118. $mysync->{begin_transfer_time} = time - 2 ; # 2 s before again
  7119. is( '0.10', sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 4000 since 2s but maxsleep 0.1s => sleep 0.1s' ) ;
  7120. $mysync->{maxbytesafter} = 4000 ;
  7121. $mysync->{begin_transfer_time} = time - 2 ; # 2 s before again
  7122. is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: maxbytesafter == total_bytes_transferred => no sleep => 0' ) ;
  7123. note( 'Leaving tests_sleep_if_needed()' ) ;
  7124. return ;
  7125. }
  7126. sub sleep_if_needed
  7127. {
  7128. my( $mysync ) = shift ;
  7129. if ( ! $mysync ) {
  7130. return ;
  7131. }
  7132. # No need to go further if there is no limit set
  7133. if ( not ( $mysync->{maxmessagespersecond}
  7134. or $mysync->{maxbytespersecond} )
  7135. ) {
  7136. return ;
  7137. }
  7138. $mysync->{maxsleep} = defined $mysync->{maxsleep} ? $mysync->{maxsleep} : $MAX_SLEEP ;
  7139. # Must be positive
  7140. $mysync->{maxsleep} = max( 0, $mysync->{maxsleep} ) ;
  7141. my $time_spent = timesince( $mysync->{begin_transfer_time} ) ;
  7142. my $sleep_max_messages = sleep_max_messages( $mysync->{nb_msg_transferred}, $time_spent, $mysync->{maxmessagespersecond} ) ;
  7143. my $maxbytesafter = $mysync->{maxbytesafter} || 0 ;
  7144. my $total_bytes_transferred = $mysync->{total_bytes_transferred} || 0 ;
  7145. my $total_bytes_to_consider = $total_bytes_transferred - $maxbytesafter ;
  7146. #myprint( "maxbytesafter:$maxbytesafter\n" ) ;
  7147. #myprint( "total_bytes_to_consider:$total_bytes_to_consider\n" ) ;
  7148. my $sleep_max_bytes = sleep_max_bytes( $total_bytes_to_consider, $time_spent, $mysync->{maxbytespersecond} ) ;
  7149. my $sleep_max = min( $mysync->{maxsleep}, max( $sleep_max_messages, $sleep_max_bytes ) ) ;
  7150. $sleep_max = mysprintf( "%.2f", $sleep_max ) ; # round with 2 decimals.
  7151. if ( $sleep_max > 0 ) {
  7152. myprint( "sleeping $sleep_max s\n" ) ;
  7153. sleep $sleep_max ;
  7154. # Slept
  7155. return $sleep_max ;
  7156. }
  7157. # No sleep
  7158. return 0 ;
  7159. }
  7160. sub sleep_max_messages
  7161. {
  7162. # how long we have to sleep to go under max_messages_per_second
  7163. my( $nb_msg_transferred, $time_spent, $maxmessagespersecond ) = @_ ;
  7164. if ( ( not defined $maxmessagespersecond ) or $maxmessagespersecond <= 0 ) { return( 0 ) } ;
  7165. my $sleep = ( $nb_msg_transferred / $maxmessagespersecond ) - $time_spent ;
  7166. # the sleep must be positive
  7167. return( max( 0, $sleep ) ) ;
  7168. }
  7169. sub tests_sleep_max_messages
  7170. {
  7171. note( 'Entering tests_sleep_max_messages()' ) ;
  7172. ok( 0 == sleep_max_messages( 4, 2, undef ), 'sleep_max_messages: maxmessagespersecond = undef') ;
  7173. ok( 0 == sleep_max_messages( 4, 2, 0 ), 'sleep_max_messages: maxmessagespersecond = 0') ;
  7174. ok( 0 == sleep_max_messages( 4, 2, $MINUS_ONE ), 'sleep_max_messages: maxmessagespersecond = -1') ;
  7175. ok( 0 == sleep_max_messages( 4, 2, 2 ), 'sleep_max_messages: maxmessagespersecond = 2 max reached') ;
  7176. ok( 2 == sleep_max_messages( 8, 2, 2 ), 'sleep_max_messages: maxmessagespersecond = 2 max over') ;
  7177. ok( 0 == sleep_max_messages( 2, 2, 2 ), 'sleep_max_messages: maxmessagespersecond = 2 max not reached') ;
  7178. note( 'Leaving tests_sleep_max_messages()' ) ;
  7179. return ;
  7180. }
  7181. sub sleep_max_bytes
  7182. {
  7183. # how long we have to sleep to go under max_bytes_per_second
  7184. my( $total_bytes_to_consider, $time_spent, $maxbytespersecond ) = @_ ;
  7185. $total_bytes_to_consider ||= 0 ;
  7186. $time_spent ||= 0 ;
  7187. if ( ( not defined $maxbytespersecond ) or $maxbytespersecond <= 0 ) { return( 0 ) } ;
  7188. #myprint( "total_bytes_to_consider:$total_bytes_to_consider\n" ) ;
  7189. my $sleep = ( $total_bytes_to_consider / $maxbytespersecond ) - $time_spent ;
  7190. # the sleep must be positive
  7191. return( max( 0, $sleep ) ) ;
  7192. }
  7193. sub tests_sleep_max_bytes
  7194. {
  7195. note( 'Entering tests_sleep_max_bytes()' ) ;
  7196. ok( 0 == sleep_max_bytes( 4000, 2, undef ), 'sleep_max_bytes: maxbytespersecond == undef => sleep 0' ) ;
  7197. ok( 0 == sleep_max_bytes( 4000, 2, 0 ), 'sleep_max_bytes: maxbytespersecond = 0 => sleep 0') ;
  7198. ok( 0 == sleep_max_bytes( 4000, 2, $MINUS_ONE ), 'sleep_max_bytes: maxbytespersecond = -1 => sleep 0') ;
  7199. ok( 0 == sleep_max_bytes( 4000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max reached sharp => sleep 0') ;
  7200. ok( 2 == sleep_max_bytes( 8000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max over => sleep a little') ;
  7201. ok( 0 == sleep_max_bytes( -8000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max not reached => sleep 0') ;
  7202. ok( 0 == sleep_max_bytes( 2000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max not reached => sleep 0') ;
  7203. ok( 0 == sleep_max_bytes( -2000, 2, 1000 ), 'sleep_max_bytes: maxbytespersecond = 1k max not reached => sleep 0') ;
  7204. note( 'Leaving tests_sleep_max_bytes()' ) ;
  7205. return ;
  7206. }
  7207. sub delete_message_on_host1
  7208. {
  7209. my( $mysync, $h1_fold, $expunge, @h1_msg ) = @_ ;
  7210. if ( ! $mysync->{ delete1 } ) { return ; }
  7211. if ( ! @h1_msg ) { return ; }
  7212. delete_messages_on_any(
  7213. $mysync,
  7214. $mysync->{imap1},
  7215. "Host1: $h1_fold",
  7216. $expunge,
  7217. $split1,
  7218. @h1_msg ) ;
  7219. return ;
  7220. }
  7221. sub tests_operators_and_exclam_precedence
  7222. {
  7223. note( 'Entering tests_operators_and_exclam_precedence()' ) ;
  7224. is( 1, ! 0, 'tests_operators_and_exclam_precedence: ! 0 => 1' ) ;
  7225. is( "", ! 1, 'tests_operators_and_exclam_precedence: ! 1 => ""' ) ;
  7226. is( 1, not( 0 ), 'tests_operators_and_exclam_precedence: not( 0 ) => 1' ) ;
  7227. is( "", not( 1 ), 'tests_operators_and_exclam_precedence: not( 1 ) => ""' ) ;
  7228. # I wrote those tests to avoid perlcrit "Mixed high and low-precedence booleans"
  7229. # and change sub delete_messages_on_any() but got 4 more warnings... So now commented.
  7230. #is( 0, ( ! 0 and 0 ), 'tests_operators_and_exclam_precedence: ! 0 and 0 ) => 0' ) ;
  7231. #is( 1, ( ! 0 and 1 ), 'tests_operators_and_exclam_precedence: ! 0 and 1 ) => 1' ) ;
  7232. #is( "", ( ! 1 and 0 ), 'tests_operators_and_exclam_precedence: ! 1 and 0 ) => ""' ) ;
  7233. #is( "", ( ! 1 and 1 ), 'tests_operators_and_exclam_precedence: ! 1 and 1 ) => ""' ) ;
  7234. is( 0, ( ! 0 && 0 ), 'tests_operators_and_exclam_precedence: ! 0 && 0 ) => 0' ) ;
  7235. is( 1, ( ! 0 && 1 ), 'tests_operators_and_exclam_precedence: ! 0 && 1 ) => 1' ) ;
  7236. is( "", ( ! 1 && 0 ), 'tests_operators_and_exclam_precedence: ! 1 && 0 ) => ""' ) ;
  7237. is( "", ( ! 1 && 1 ), 'tests_operators_and_exclam_precedence: ! 1 && 1 ) => ""' ) ;
  7238. is( 2, ( ! 0 && 2 ), 'tests_operators_and_exclam_precedence: ! 0 && 2 ) => 1' ) ;
  7239. note( 'Leaving tests_operators_and_exclam_precedence()' ) ;
  7240. return ;
  7241. }
  7242. sub delete_messages_on_any
  7243. {
  7244. my( $mysync, $imap, $hostX_folder, $expunge, $split, @messages ) = @_ ;
  7245. my $expunge_message = q{} ;
  7246. my $dry_message = $mysync->{ dry_message } ;
  7247. $expunge_message = 'and expunged' if ( $expunge ) ;
  7248. # "Host1: msg "
  7249. $imap->Debug( 1 ) ;
  7250. while ( my @messages_part = splice @messages, 0, $split )
  7251. {
  7252. foreach my $message ( @messages_part )
  7253. {
  7254. myprint( "$hostX_folder/$message marking deleted $expunge_message $dry_message\n" ) ;
  7255. }
  7256. if ( ! $mysync->{dry} && @messages_part )
  7257. {
  7258. my $nb_deleted = $imap->delete_message( $imap->Range( @messages_part ) ) ;
  7259. if ( defined $nb_deleted )
  7260. {
  7261. $mysync->{ h1_nb_msg_deleted } += $nb_deleted ;
  7262. }
  7263. else
  7264. {
  7265. my $error_imap = $imap->LastError || q{} ;
  7266. my $error = join( q{}, "$hostX_folder folder, could not delete ",
  7267. scalar @messages_part, ' messages: ', $error_imap, "\n" ) ;
  7268. errors_incr( $mysync, $error ) ;
  7269. }
  7270. }
  7271. }
  7272. if ( $expunge ) {
  7273. uidexpunge_or_expunge( $mysync, $imap, @messages ) ;
  7274. }
  7275. $imap->Debug( 0 ) ;
  7276. return ;
  7277. }
  7278. sub tests_uidexpunge_or_expunge
  7279. {
  7280. note( 'Entering tests_uidexpunge_or_expunge()' ) ;
  7281. is( undef, uidexpunge_or_expunge( ), 'uidexpunge_or_expunge: no args => undef' ) ;
  7282. my $mysync ;
  7283. is( undef, uidexpunge_or_expunge( $mysync ), 'uidexpunge_or_expunge: undef args => undef' ) ;
  7284. $mysync = {} ;
  7285. is( undef, uidexpunge_or_expunge( $mysync ), 'uidexpunge_or_expunge: arg empty => undef' ) ;
  7286. my $imap ;
  7287. is( undef, uidexpunge_or_expunge( $mysync, $imap ), 'uidexpunge_or_expunge: undef Mail-IMAPClient instance => undef' ) ;
  7288. require_ok( "Test::MockObject" ) ;
  7289. $imap = Test::MockObject->new( ) ;
  7290. is( undef, uidexpunge_or_expunge( $mysync, $imap ), 'uidexpunge_or_expunge: no message (1) to uidexpunge => undef' ) ;
  7291. my @messages = ( ) ;
  7292. is( undef, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: no message (2) to uidexpunge => undef' ) ;
  7293. @messages = ( '2', '1' ) ;
  7294. $imap->mock( 'uidexpunge', sub { return ; } ) ;
  7295. $imap->mock( 'expunge', sub { return ; } ) ;
  7296. is( undef, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: uidexpunge failure => expunge failure => undef' ) ;
  7297. $imap->mock( 'expunge', sub { return 1 ; } ) ;
  7298. is( 1, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: uidexpunge failure => expunge ok => 1' ) ;
  7299. $imap->mock( 'uidexpunge', sub { return 1 ; } ) ;
  7300. is( 1, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: messages to uidexpunge ok => 1' ) ;
  7301. note( 'Leaving tests_uidexpunge_or_expunge()' ) ;
  7302. return ;
  7303. }
  7304. sub uidexpunge_or_expunge
  7305. {
  7306. my $mysync = shift ;
  7307. my $imap = shift ;
  7308. my @messages = @ARG ;
  7309. if ( ! $imap ) { return ; } ;
  7310. if ( ! @messages ) { return ; } ;
  7311. # Doing uidexpunge
  7312. my @uidexpunge_result = $imap->uidexpunge( @messages ) ;
  7313. if ( @uidexpunge_result ) {
  7314. return 1 ;
  7315. }
  7316. # Failure so doing expunge
  7317. my $expunge_result = $imap->expunge( ) ;
  7318. if ( $expunge_result ) {
  7319. return 1 ;
  7320. }
  7321. # bad trip
  7322. return ;
  7323. }
  7324. sub eta
  7325. {
  7326. my( $my_time_spent, $h1_nb_processed, $my_h1_nb_msg_start, $nb_transferred ) = @_ ;
  7327. return( q{} ) if not $foldersizes ;
  7328. my $time_remaining = time_remaining( $my_time_spent, $h1_nb_processed, $my_h1_nb_msg_start, $nb_transferred ) ;
  7329. my $nb_msg_remaining = $my_h1_nb_msg_start - $h1_nb_processed ;
  7330. my $eta_date = localtime( time + $time_remaining ) ;
  7331. return( mysprintf( 'ETA: %s %1.0f s %s/%s msgs left', $eta_date, $time_remaining, $nb_msg_remaining, $my_h1_nb_msg_start ) ) ;
  7332. }
  7333. sub time_remaining
  7334. {
  7335. my( $my_time_spent, $h1_nb_processed, $my_h1_nb_msg_start, $nb_transferred ) = @_ ;
  7336. my $time_remaining = ( $my_time_spent / $nb_transferred ) * ( $my_h1_nb_msg_start - $h1_nb_processed ) ;
  7337. return( $time_remaining ) ;
  7338. }
  7339. sub tests_time_remaining
  7340. {
  7341. note( 'Entering tests_time_remaining()' ) ;
  7342. ok( 1 == time_remaining( 1, 1, 2, 1 ), 'time_remaining: 1, 1, 2, 1 -> 1' ) ;
  7343. ok( 1 == time_remaining( 9, 9, 10, 9 ), 'time_remaining: 9, 9, 10, 9 -> 1' ) ;
  7344. ok( 9 == time_remaining( 1, 1, 10, 1 ), 'time_remaining: 1, 1, 10, 1 -> 1' ) ;
  7345. note( 'Leaving tests_time_remaining()' ) ;
  7346. return ;
  7347. }
  7348. sub cache_map
  7349. {
  7350. my ( $cache_files_ref, $h1_msgs_ref, $h2_msgs_ref ) = @_;
  7351. my ( %map1_2, %map2_1, %done2 ) ;
  7352. my $h1_msgs_hash_ref = { } ;
  7353. my $h2_msgs_hash_ref = { } ;
  7354. @{ $h1_msgs_hash_ref }{ @{ $h1_msgs_ref } } = ( ) ;
  7355. @{ $h2_msgs_hash_ref }{ @{ $h2_msgs_ref } } = ( ) ;
  7356. foreach my $file ( sort @{ $cache_files_ref } ) {
  7357. $debugcache and myprint( "C12: $file\n" ) ;
  7358. ( $uid1, $uid2 ) = match_a_cache_file( $file ) ;
  7359. if ( exists( $h1_msgs_hash_ref->{ defined $uid1 ? $uid1 : q{} } )
  7360. and exists( $h2_msgs_hash_ref->{ defined $uid2 ? $uid2 : q{} } ) ) {
  7361. # keep only the greatest uid2
  7362. # 130_2301 and
  7363. # 130_231 => keep only 130 -> 2301
  7364. # keep only the greatest uid1
  7365. # 1601_260 and
  7366. # 161_260 => keep only 1601 -> 260
  7367. my $max_uid2 = max( $uid2, $map1_2{ $uid1 } || $MINUS_ONE ) ;
  7368. if ( exists $done2{ $max_uid2 } ) {
  7369. if ( $done2{ $max_uid2 } < $uid1 ) {
  7370. $map1_2{ $uid1 } = $max_uid2 ;
  7371. delete $map1_2{ $done2{ $max_uid2 } } ;
  7372. $done2{ $max_uid2 } = $uid1 ;
  7373. }
  7374. }else{
  7375. $map1_2{ $uid1 } = $max_uid2 ;
  7376. $done2{ $max_uid2 } = $uid1 ;
  7377. }
  7378. };
  7379. }
  7380. %map2_1 = reverse %map1_2 ;
  7381. return( \%map1_2, \%map2_1) ;
  7382. }
  7383. sub tests_cache_map
  7384. {
  7385. note( 'Entering tests_cache_map()' ) ;
  7386. #$debugcache = 1 ;
  7387. my @cache_files = qw (
  7388. 100_200
  7389. 101_201
  7390. 120_220
  7391. 142_242
  7392. 143_243
  7393. 177_277
  7394. 177_278
  7395. 177_279
  7396. 155_255
  7397. 180_280
  7398. 181_280
  7399. 182_280
  7400. 130_231
  7401. 130_2301
  7402. 161_260
  7403. 1601_260
  7404. ) ;
  7405. my $msgs_1 = [120, 142, 143, 144, 161, 1601, 177, 182, 130 ];
  7406. my $msgs_2 = [ 242, 243, 260, 299, 377, 279, 255, 280, 231, 2301 ];
  7407. my( $c12, $c21 ) ;
  7408. ok( ( $c12, $c21 ) = cache_map( \@cache_files, $msgs_1, $msgs_2 ), 'cache_map: 02' );
  7409. my $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
  7410. my $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
  7411. ok( 0 == compare_lists( [ 130, 142, 143, 177, 182, 1601 ], $a1 ), 'cache_map: 03' );
  7412. ok( 0 == compare_lists( [ 242, 243, 260, 279, 280, 2301 ], $a2 ), 'cache_map: 04' );
  7413. ok( ! $c12->{161}, 'cache_map: ! 161 -> 260' );
  7414. ok( 260 == $c12->{1601}, 'cache_map: 1601 -> 260' );
  7415. ok( 2301 == $c12->{130}, 'cache_map: 130 -> 2301' );
  7416. #myprint( $c12->{1601}, "\n" ) ;
  7417. note( 'Leaving tests_cache_map()' ) ;
  7418. return ;
  7419. }
  7420. sub cache_dir_fix
  7421. {
  7422. my $cache_dir = shift ;
  7423. $cache_dir =~ s/([;<>\*\|`&\$!#\(\)\[\]\{\}:'"\\])/\\$1/xg ;
  7424. #myprint( "cache_dir_fix: $cache_dir\n" ) ;
  7425. return( $cache_dir ) ;
  7426. }
  7427. sub tests_cache_dir_fix
  7428. {
  7429. note( 'Entering tests_cache_dir_fix()' ) ;
  7430. ok( 'lalala' eq cache_dir_fix('lalala'), 'cache_dir_fix: lalala -> lalala' );
  7431. ok( 'ii\\\\ii' eq cache_dir_fix('ii\ii'), 'cache_dir_fix: ii\ii -> ii\\\\ii' );
  7432. ok( 'ii@ii' eq cache_dir_fix('ii@ii'), 'cache_dir_fix: ii@ii -> ii@ii' );
  7433. ok( 'ii@ii\\:ii' eq cache_dir_fix('ii@ii:ii'), 'cache_dir_fix: ii@ii:ii -> ii@ii\\:ii' );
  7434. ok( 'i\\\\i\\\\ii' eq cache_dir_fix('i\i\ii'), 'cache_dir_fix: i\i\ii -> i\\\\i\\\\ii' );
  7435. ok( 'i\\\\ii' eq cache_dir_fix('i\\ii'), 'cache_dir_fix: i\\ii -> i\\\\\\\\ii' );
  7436. ok( '\\\\ ' eq cache_dir_fix('\\ '), 'cache_dir_fix: \\ -> \\\\\ ' );
  7437. ok( '\\\\ ' eq cache_dir_fix('\ '), 'cache_dir_fix: \ -> \\\\\ ' );
  7438. ok( '\[bracket\]' eq cache_dir_fix('[bracket]'), 'cache_dir_fix: [bracket] -> \[bracket\]' );
  7439. note( 'Leaving tests_cache_dir_fix()' ) ;
  7440. return ;
  7441. }
  7442. sub cache_dir_fix_win
  7443. {
  7444. my $cache_dir = shift ;
  7445. $cache_dir =~ s/(\[|\])/[$1]/xg ;
  7446. #myprint( "cache_dir_fix_win: $cache_dir\n" ) ;
  7447. return( $cache_dir ) ;
  7448. }
  7449. sub tests_cache_dir_fix_win
  7450. {
  7451. note( 'Entering tests_cache_dir_fix_win()' ) ;
  7452. ok( 'lalala' eq cache_dir_fix_win('lalala'), 'cache_dir_fix_win: lalala -> lalala' );
  7453. ok( '[[]bracket[]]' eq cache_dir_fix_win('[bracket]'), 'cache_dir_fix_win: [bracket] -> [[]bracket[]]' );
  7454. note( 'Leaving tests_cache_dir_fix_win()' ) ;
  7455. return ;
  7456. }
  7457. sub get_cache
  7458. {
  7459. my ( $cache_dir, $h1_msgs_ref, $h2_msgs_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) = @_;
  7460. $debugcache and myprint( "Entering get_cache\n" ) ;
  7461. -d $cache_dir or return( undef ); # exit if cache directory doesn't exist
  7462. $debugcache and myprint( "cache_dir : $cache_dir\n" ) ;
  7463. if ( 'MSWin32' ne $OSNAME ) {
  7464. $cache_dir = cache_dir_fix( $cache_dir ) ;
  7465. }else{
  7466. $cache_dir = cache_dir_fix_win( $cache_dir ) ;
  7467. }
  7468. $debugcache and myprint( "cache_dir_fix: $cache_dir\n" ) ;
  7469. my @cache_files = bsd_glob( "$cache_dir/*" ) ;
  7470. #$debugcache and myprint( "cache_files: [@cache_files]\n" ) ;
  7471. $debugcache and myprint( 'cache_files: ', scalar @cache_files , " files found\n" ) ;
  7472. my( $cache_1_2_ref, $cache_2_1_ref )
  7473. = cache_map( \@cache_files, $h1_msgs_ref, $h2_msgs_ref ) ;
  7474. clean_cache( \@cache_files, $cache_1_2_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) ;
  7475. $debugcache and myprint( "Exiting get_cache\n" ) ;
  7476. return( $cache_1_2_ref, $cache_2_1_ref ) ;
  7477. }
  7478. sub tests_get_cache
  7479. {
  7480. note( 'Entering tests_get_cache()' ) ;
  7481. ok( not( get_cache('/cache_no_exist') ), 'get_cache: /cache_no_exist' );
  7482. ok( ( not -d 'W/tmp/cache/F1/F2' or rmtree( 'W/tmp/cache/F1/F2' ) ), 'get_cache: rmtree W/tmp/cache/F1/F2' ) ;
  7483. ok( mkpath( 'W/tmp/cache/F1/F2' ), 'get_cache: mkpath W/tmp/cache/F1/F2' ) ;
  7484. my @test_files_cache = ( qw(
  7485. W/tmp/cache/F1/F2/100_200
  7486. W/tmp/cache/F1/F2/101_201
  7487. W/tmp/cache/F1/F2/120_220
  7488. W/tmp/cache/F1/F2/142_242
  7489. W/tmp/cache/F1/F2/143_243
  7490. W/tmp/cache/F1/F2/177_277
  7491. W/tmp/cache/F1/F2/177_377
  7492. W/tmp/cache/F1/F2/177_777
  7493. W/tmp/cache/F1/F2/155_255
  7494. ) ) ;
  7495. ok( touch( @test_files_cache ), 'get_cache: touch W/tmp/cache/F1/F2/...' ) ;
  7496. # on cache: 100_200 101_201 142_242 143_243 177_277 177_377 177_777 155_255
  7497. # on live:
  7498. my $msgs_1 = [120, 142, 143, 144, 177 ];
  7499. my $msgs_2 = [ 242, 243, 299, 377, 777, 255 ];
  7500. my $msgs_all_1 = { 120 => 0, 142 => 0, 143 => 0, 144 => 0, 177 => 0 } ;
  7501. my $msgs_all_2 = { 242 => 0, 243 => 0, 299 => 0, 377 => 0, 777 => 0, 255 => 0 } ;
  7502. my( $c12, $c21 ) ;
  7503. ok( ( $c12, $c21 ) = get_cache( 'W/tmp/cache/F1/F2', $msgs_1, $msgs_2, $msgs_all_1, $msgs_all_2 ), 'get_cache: 02' );
  7504. my $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
  7505. my $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
  7506. ok( 0 == compare_lists( [ 142, 143, 177 ], $a1 ), 'get_cache: 03' );
  7507. ok( 0 == compare_lists( [ 242, 243, 777 ], $a2 ), 'get_cache: 04' );
  7508. ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 142_242');
  7509. ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 143_243');
  7510. ok( ! -f 'W/tmp/cache/F1/F2/100_200', 'get_cache: file removed 100_200');
  7511. ok( ! -f 'W/tmp/cache/F1/F2/101_201', 'get_cache: file removed 101_201');
  7512. # test clean_cache executed
  7513. $maxage = 2 ;
  7514. ok( touch(@test_files_cache), 'get_cache: touch W/tmp/cache/F1/F2/...' ) ;
  7515. ok( ( $c12, $c21 ) = get_cache('W/tmp/cache/F1/F2', $msgs_1, $msgs_2, $msgs_all_1, $msgs_all_2 ), 'get_cache: 02' );
  7516. ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 142_242');
  7517. ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 143_243');
  7518. ok( ! -f 'W/tmp/cache/F1/F2/100_200', 'get_cache: file NOT removed 100_200');
  7519. ok( ! -f 'W/tmp/cache/F1/F2/101_201', 'get_cache: file NOT removed 101_201');
  7520. # strange files
  7521. #$debugcache = 1 ;
  7522. $maxage = undef ;
  7523. ok( ( not -d 'W/tmp/cache/rr\uee' or rmtree( 'W/tmp/cache/rr\uee' )), 'get_cache: rmtree W/tmp/cache/rr\uee' ) ;
  7524. ok( mkpath( 'W/tmp/cache/rr\uee' ), 'get_cache: mkpath W/tmp/cache/rr\uee' ) ;
  7525. @test_files_cache = ( qw(
  7526. W/tmp/cache/rr\uee/100_200
  7527. W/tmp/cache/rr\uee/101_201
  7528. W/tmp/cache/rr\uee/120_220
  7529. W/tmp/cache/rr\uee/142_242
  7530. W/tmp/cache/rr\uee/143_243
  7531. W/tmp/cache/rr\uee/177_277
  7532. W/tmp/cache/rr\uee/177_377
  7533. W/tmp/cache/rr\uee/177_777
  7534. W/tmp/cache/rr\uee/155_255
  7535. ) ) ;
  7536. ok( touch(@test_files_cache), 'get_cache: touch strange W/tmp/cache/...' ) ;
  7537. # on cache: 100_200 101_201 142_242 143_243 177_277 177_377 177_777 155_255
  7538. # on live:
  7539. $msgs_1 = [120, 142, 143, 144, 177 ] ;
  7540. $msgs_2 = [ 242, 243, 299, 377, 777, 255 ] ;
  7541. $msgs_all_1 = { 120 => q{}, 142 => q{}, 143 => q{}, 144 => q{}, 177 => q{} } ;
  7542. $msgs_all_2 = { 242 => q{}, 243 => q{}, 299 => q{}, 377 => q{}, 777 => q{}, 255 => q{} } ;
  7543. 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' );
  7544. $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
  7545. $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
  7546. ok( 0 == compare_lists( [ 142, 143, 177 ], $a1 ), 'get_cache: strange path 03' );
  7547. ok( 0 == compare_lists( [ 242, 243, 777 ], $a2 ), 'get_cache: strange path 04' );
  7548. ok( -f 'W/tmp/cache/rr\uee/142_242', 'get_cache: strange path file kept 142_242');
  7549. ok( -f 'W/tmp/cache/rr\uee/142_242', 'get_cache: strange path file kept 143_243');
  7550. ok( ! -f 'W/tmp/cache/rr\uee/100_200', 'get_cache: strange path file removed 100_200');
  7551. ok( ! -f 'W/tmp/cache/rr\uee/101_201', 'get_cache: strange path file removed 101_201');
  7552. note( 'Leaving tests_get_cache()' ) ;
  7553. return ;
  7554. }
  7555. sub match_a_cache_file
  7556. {
  7557. my $file = shift ;
  7558. my ( $cache_uid1, $cache_uid2 ) ;
  7559. return( ( undef, undef ) ) if ( ! $file ) ;
  7560. if ( $file =~ m{(?:^|/)(\d+)_(\d+)$}x ) {
  7561. $cache_uid1 = $1 ;
  7562. $cache_uid2 = $2 ;
  7563. }
  7564. return( $cache_uid1, $cache_uid2 ) ;
  7565. }
  7566. sub tests_match_a_cache_file
  7567. {
  7568. note( 'Entering tests_match_a_cache_file()' ) ;
  7569. my ( $tuid1, $tuid2 ) ;
  7570. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( ), 'match_a_cache_file: no arg' ) ;
  7571. ok( ! defined $tuid1 , 'match_a_cache_file: no arg 1' ) ;
  7572. ok( ! defined $tuid2 , 'match_a_cache_file: no arg 2' ) ;
  7573. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( q{} ), 'match_a_cache_file: empty arg' ) ;
  7574. ok( ! defined $tuid1 , 'match_a_cache_file: empty arg 1' ) ;
  7575. ok( ! defined $tuid2 , 'match_a_cache_file: empty arg 2' ) ;
  7576. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '000_000' ), 'match_a_cache_file: 000_000' ) ;
  7577. ok( '000' eq $tuid1, 'match_a_cache_file: 000_000 1' ) ;
  7578. ok( '000' eq $tuid2, 'match_a_cache_file: 000_000 2' ) ;
  7579. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '123_456' ), 'match_a_cache_file: 123_456' ) ;
  7580. ok( '123' eq $tuid1, 'match_a_cache_file: 123_456 1' ) ;
  7581. ok( '456' eq $tuid2, 'match_a_cache_file: 123_456 2' ) ;
  7582. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '/tmp/truc/123_456' ), 'match_a_cache_file: /tmp/truc/123_456' ) ;
  7583. ok( '123' eq $tuid1, 'match_a_cache_file: /tmp/truc/123_456 1' ) ;
  7584. ok( '456' eq $tuid2, 'match_a_cache_file: /tmp/truc/123_456 2' ) ;
  7585. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '/lala123_456' ), 'match_a_cache_file: NO /lala123_456' ) ;
  7586. ok( ! $tuid1, 'match_a_cache_file: /lala123_456 1' ) ;
  7587. ok( ! $tuid2, 'match_a_cache_file: /lala123_456 2' ) ;
  7588. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( 'la123_456' ), 'match_a_cache_file: NO la123_456' ) ;
  7589. ok( ! $tuid1, 'match_a_cache_file: la123_456 1' ) ;
  7590. ok( ! $tuid2, 'match_a_cache_file: la123_456 2' ) ;
  7591. note( 'Leaving tests_match_a_cache_file()' ) ;
  7592. return ;
  7593. }
  7594. sub clean_cache
  7595. {
  7596. my ( $cache_files_ref, $cache_1_2_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) = @_ ;
  7597. $debugcache and myprint( "Entering clean_cache\n" ) ;
  7598. $debugcache and myprint( map { "$_ -> " . $cache_1_2_ref->{ $_ } . "\n" } keys %{ $cache_1_2_ref } ) ;
  7599. foreach my $file ( @{ $cache_files_ref } ) {
  7600. $debugcache and myprint( "$file\n" ) ;
  7601. my ( $cache_uid1, $cache_uid2 ) = match_a_cache_file( $file ) ;
  7602. $debugcache and myprint( "u1: $cache_uid1 u2: $cache_uid2 c12: ", $cache_1_2_ref->{ $cache_uid1 } || q{}, "\n") ;
  7603. # or ( ! exists( $cache_1_2_ref->{ $cache_uid1 } ) )
  7604. # or ( ! ( $cache_uid2 == $cache_1_2_ref->{ $cache_uid1 } ) )
  7605. if ( ( not defined $cache_uid1 )
  7606. or ( not defined $cache_uid2 )
  7607. or ( not exists $h1_msgs_all_hash_ref->{ $cache_uid1 } )
  7608. or ( not exists $h2_msgs_all_hash_ref->{ $cache_uid2 } )
  7609. ) {
  7610. $debugcache and myprint( "remove $file\n" ) ;
  7611. unlink $file or myprint( "$OS_ERROR" ) ;
  7612. }
  7613. }
  7614. $debugcache and myprint( "Exiting clean_cache\n" ) ;
  7615. return( 1 ) ;
  7616. }
  7617. sub tests_clean_cache
  7618. {
  7619. note( 'Entering tests_clean_cache()' ) ;
  7620. ok( ( not -d 'W/tmp/cache/G1/G2' or rmtree( 'W/tmp/cache/G1/G2' )), 'clean_cache: rmtree W/tmp/cache/G1/G2' ) ;
  7621. ok( mkpath( 'W/tmp/cache/G1/G2' ), 'clean_cache: mkpath W/tmp/cache/G1/G2' ) ;
  7622. my @test_files_cache = ( qw(
  7623. W/tmp/cache/G1/G2/100_200
  7624. W/tmp/cache/G1/G2/101_201
  7625. W/tmp/cache/G1/G2/120_220
  7626. W/tmp/cache/G1/G2/142_242
  7627. W/tmp/cache/G1/G2/143_243
  7628. W/tmp/cache/G1/G2/177_277
  7629. W/tmp/cache/G1/G2/177_377
  7630. W/tmp/cache/G1/G2/177_777
  7631. W/tmp/cache/G1/G2/155_255
  7632. ) ) ;
  7633. ok( touch(@test_files_cache), 'clean_cache: touch W/tmp/cache/G1/G2/...' ) ;
  7634. ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache: 100_200 before' );
  7635. ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache: 142_242 before' );
  7636. ok( -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache: 177_277 before' );
  7637. ok( -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache: 177_377 before' );
  7638. ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache: 177_777 before' );
  7639. ok( -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache: 155_255 before' );
  7640. my $cache = {
  7641. 142 => 242,
  7642. 177 => 777,
  7643. } ;
  7644. my $all_1 = {
  7645. 142 => q{},
  7646. 177 => q{},
  7647. } ;
  7648. my $all_2 = {
  7649. 200 => q{},
  7650. 242 => q{},
  7651. 777 => q{},
  7652. } ;
  7653. ok( clean_cache( \@test_files_cache, $cache, $all_1, $all_2 ), 'clean_cache: ' ) ;
  7654. ok( ! -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache: 100_200 after' );
  7655. ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache: 142_242 after' );
  7656. ok( ! -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache: 177_277 after' );
  7657. ok( ! -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache: 177_377 after' );
  7658. ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache: 177_777 after' );
  7659. ok( ! -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache: 155_255 after' );
  7660. note( 'Leaving tests_clean_cache()' ) ;
  7661. return ;
  7662. }
  7663. sub tests_clean_cache_2
  7664. {
  7665. note( 'Entering tests_clean_cache_2()' ) ;
  7666. ok( ( not -d 'W/tmp/cache/G1/G2' or rmtree( 'W/tmp/cache/G1/G2' )), 'clean_cache_2: rmtree W/tmp/cache/G1/G2' ) ;
  7667. ok( mkpath( 'W/tmp/cache/G1/G2' ), 'clean_cache_2: mkpath W/tmp/cache/G1/G2' ) ;
  7668. my @test_files_cache = ( qw(
  7669. W/tmp/cache/G1/G2/100_200
  7670. W/tmp/cache/G1/G2/101_201
  7671. W/tmp/cache/G1/G2/120_220
  7672. W/tmp/cache/G1/G2/142_242
  7673. W/tmp/cache/G1/G2/143_243
  7674. W/tmp/cache/G1/G2/177_277
  7675. W/tmp/cache/G1/G2/177_377
  7676. W/tmp/cache/G1/G2/177_777
  7677. W/tmp/cache/G1/G2/155_255
  7678. ) ) ;
  7679. ok( touch(@test_files_cache), 'clean_cache_2: touch W/tmp/cache/G1/G2/...' ) ;
  7680. ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache_2: 100_200 before' );
  7681. ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache_2: 142_242 before' );
  7682. ok( -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache_2: 177_277 before' );
  7683. ok( -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache_2: 177_377 before' );
  7684. ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache_2: 177_777 before' );
  7685. ok( -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache_2: 155_255 before' );
  7686. my $cache = {
  7687. 142 => 242,
  7688. 177 => 777,
  7689. } ;
  7690. my $all_1 = {
  7691. $NUMBER_100 => q{},
  7692. 142 => q{},
  7693. 177 => q{},
  7694. } ;
  7695. my $all_2 = {
  7696. 200 => q{},
  7697. 242 => q{},
  7698. 777 => q{},
  7699. } ;
  7700. ok( clean_cache( \@test_files_cache, $cache, $all_1, $all_2 ), 'clean_cache_2: ' ) ;
  7701. ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache_2: 100_200 after' );
  7702. ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache_2: 142_242 after' );
  7703. ok( ! -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache_2: 177_277 after' );
  7704. ok( ! -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache_2: 177_377 after' );
  7705. ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache_2: 177_777 after' );
  7706. ok( ! -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache_2: 155_255 after' );
  7707. note( 'Leaving tests_clean_cache_2()' ) ;
  7708. return ;
  7709. }
  7710. sub tests_mkpath
  7711. {
  7712. note( 'Entering tests_mkpath()' ) ;
  7713. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' )), 'mkpath: mkpath W/tmp/tests/' ) ;
  7714. SKIP: {
  7715. skip( 'Tests only for Unix', 10 ) if ( 'MSWin32' eq $OSNAME ) ;
  7716. my $long_path_unix = '123456789/' x 30 ;
  7717. ok( ( -d "W/tmp/tests/long/$long_path_unix" or mkpath( "W/tmp/tests/long/$long_path_unix" ) ), 'mkpath: mkpath 300 char' ) ;
  7718. ok( -d "W/tmp/tests/long/$long_path_unix", 'mkpath: mkpath > 300 char verified' ) ;
  7719. ok( ( -d "W/tmp/tests/long/$long_path_unix" and rmtree( 'W/tmp/tests/long/' ) ), 'mkpath: rmtree 300 char' ) ;
  7720. ok( ! -d "W/tmp/tests/long/$long_path_unix", 'mkpath: rmtree 300 char verified' ) ;
  7721. ok( ( -d 'W/tmp/tests/trailing_dots...' or mkpath( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: mkpath trailing_dots...' ) ;
  7722. ok( -d 'W/tmp/tests/trailing_dots...', 'mkpath: mkpath trailing_dots... verified' ) ;
  7723. ok( ( -d 'W/tmp/tests/trailing_dots...' and rmtree( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: rmtree trailing_dots...' ) ;
  7724. ok( ! -d 'W/tmp/tests/trailing_dots...', 'mkpath: rmtree trailing_dots... verified' ) ;
  7725. eval { ok( 1 / 0, 'mkpath: divide by 0' ) ; } or ok( 1, 'mkpath: can not divide by 0' ) ;
  7726. ok( 1, 'mkpath: still alive' ) ;
  7727. } ;
  7728. SKIP: {
  7729. skip( 'Tests only for MSWin32', 13 ) if ( 'MSWin32' ne $OSNAME ) ;
  7730. my $long_path_2_prefix = ".\\imapsync_tests" || '\\\?\\E:\\TEMP\\imapsync_tests' ;
  7731. myprint( "long_path_2_prefix: $long_path_2_prefix\n" ) ;
  7732. my $long_path_100 = $long_path_2_prefix . '\\' . '123456789\\' x 10 . 'END' ;
  7733. my $long_path_300 = $long_path_2_prefix . '\\' . '123456789\\' x 30 . 'END' ;
  7734. #myprint( "$long_path_100\n" ) ;
  7735. ok( ( -d $long_path_2_prefix or mkpath( $long_path_2_prefix ) ), 'mkpath: -d mkpath small path' ) ;
  7736. ok( ( -d $long_path_2_prefix ), 'mkpath: -d mkpath small path done' ) ;
  7737. ok( ( -d $long_path_100 or mkpath( $long_path_100 ) ), 'mkpath: mkpath > 100 char' ) ;
  7738. ok( ( -d $long_path_100 ), 'mkpath: -d mkpath > 200 char done' ) ;
  7739. ok( ( -d $long_path_2_prefix and rmtree( $long_path_2_prefix ) ), 'mkpath: rmtree > 100 char' ) ;
  7740. ok( (! -d $long_path_2_prefix ), 'mkpath: ! -d rmtree done' ) ;
  7741. # Without the eval the following mkpath 300 just kill the whole process without a whisper
  7742. #myprint( "$long_path_300\n" ) ;
  7743. eval { ok( ( -d $long_path_300 or mkpath( $long_path_300 ) ), 'mkpath: create a path with 300 characters' ) ; }
  7744. or ok( 1, 'mkpath: can not create a path with 300 characters' ) ;
  7745. ok( ( ( ! -d $long_path_300 ) or -d $long_path_300 and rmtree( $long_path_300 ) ), 'mkpath: rmtree the 300 character path' ) ;
  7746. ok( 1, 'mkpath: still alive' ) ;
  7747. ok( ( -d 'W/tmp/tests/trailing_dots...' or mkpath( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: mkpath trailing_dots...' ) ;
  7748. ok( -d 'W/tmp/tests/trailing_dots...', 'mkpath: mkpath trailing_dots... verified' ) ;
  7749. ok( ( -d 'W/tmp/tests/trailing_dots...' and rmtree( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: rmtree trailing_dots...' ) ;
  7750. ok( ! -d 'W/tmp/tests/trailing_dots...', 'mkpath: rmtree trailing_dots... verified' ) ;
  7751. } ;
  7752. note( 'Leaving tests_mkpath()' ) ;
  7753. # Keep this because of the eval used by the caller (failed badly?)
  7754. return 1 ;
  7755. }
  7756. sub tests_touch
  7757. {
  7758. note( 'Entering tests_touch()' ) ;
  7759. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' )), 'touch: mkpath W/tmp/tests/' ) ;
  7760. ok( 1 == touch( 'W/tmp/tests/lala'), 'touch: W/tmp/tests/lala') ;
  7761. ok( 1 == touch( 'W/tmp/tests/\y'), 'touch: W/tmp/tests/\y') ;
  7762. ok( 0 == touch( '/no/no/no/aaa'), 'touch: not /aaa') ;
  7763. ok( 1 == touch( 'W/tmp/tests/lili', 'W/tmp/tests/lolo'), 'touch: 2 files') ;
  7764. ok( 0 == touch( 'W/tmp/tests/\y', '/no/no/aaa'), 'touch: 2 files, 1 fails' ) ;
  7765. note( 'Leaving tests_touch()' ) ;
  7766. return ;
  7767. }
  7768. sub touch
  7769. {
  7770. my @files = @_ ;
  7771. my $failures = 0 ;
  7772. foreach my $file ( @files ) {
  7773. my $fh = IO::File->new ;
  7774. if ( $fh->open(">> $file" ) ) {
  7775. $fh->close ;
  7776. }else{
  7777. myprint( "Could not open file $file in write/append mode\n" ) ;
  7778. $failures++ ;
  7779. }
  7780. }
  7781. return( ! $failures );
  7782. }
  7783. sub tests_tmpdir_has_colon_bug
  7784. {
  7785. note( 'Entering tests_tmpdir_has_colon_bug()' ) ;
  7786. ok( 0 == tmpdir_has_colon_bug( q{} ), 'tmpdir_has_colon_bug: ' ) ;
  7787. ok( 0 == tmpdir_has_colon_bug( '/tmp' ), 'tmpdir_has_colon_bug: /tmp' ) ;
  7788. ok( 1 == tmpdir_has_colon_bug( 'C:' ), 'tmpdir_has_colon_bug: C:' ) ;
  7789. ok( 1 == tmpdir_has_colon_bug( 'C:\temp' ), 'tmpdir_has_colon_bug: C:\temp' ) ;
  7790. note( 'Leaving tests_tmpdir_has_colon_bug()' ) ;
  7791. return ;
  7792. }
  7793. sub tmpdir_has_colon_bug
  7794. {
  7795. my $path = shift ;
  7796. my $path_filtered = filter_forbidden_characters( $path ) ;
  7797. if ( $path_filtered ne $path ) {
  7798. ( -d $path_filtered ) and myprint( "Path $path was previously mistakely changed to $path_filtered\n" ) ;
  7799. return( 1 ) ;
  7800. }
  7801. return( 0 ) ;
  7802. }
  7803. sub tmpdir_fix_colon_bug
  7804. {
  7805. my $mysync = shift ;
  7806. my $err = 0 ;
  7807. if ( not (-d $mysync->{ tmpdir } and -r _ and -w _) ) {
  7808. myprint( "tmpdir $mysync->{ tmpdir } is not valid\n" ) ;
  7809. return( 0 ) ;
  7810. }
  7811. my $cachedir_new = "$mysync->{ tmpdir }/imapsync_cache" ;
  7812. if ( not tmpdir_has_colon_bug( $cachedir_new ) ) { return( 0 ) } ;
  7813. # check if old cache directory already exists
  7814. my $cachedir_old = filter_forbidden_characters( $cachedir_new ) ;
  7815. if ( not ( -d $cachedir_old ) ) {
  7816. myprint( "Old cache directory $cachedir_new no exists, nothing to do\n" ) ;
  7817. return( 1 ) ;
  7818. }
  7819. # check if new cache directory already exists
  7820. if ( -d $cachedir_new ) {
  7821. myprint( "New fixed cache directory $cachedir_new already exists, not moving the old one $cachedir_old. Fix this manually.\n" ) ;
  7822. return( 0 ) ;
  7823. }else{
  7824. # move the old one to the new place
  7825. myprint( "Moving $cachedir_old to $cachedir_new Do not interrupt this task.\n" ) ;
  7826. File::Copy::Recursive::rmove( $cachedir_old, $cachedir_new )
  7827. or do {
  7828. myprint( "Could not move $cachedir_old to $cachedir_new\n" ) ;
  7829. $err++ ;
  7830. } ;
  7831. # check it succeeded
  7832. if ( -d $cachedir_new and -r _ and -w _ ) {
  7833. myprint( "New fixed cache directory $cachedir_new ok\n" ) ;
  7834. }else{
  7835. myprint( "New fixed cache directory $cachedir_new does not exist\n" ) ;
  7836. $err++ ;
  7837. }
  7838. if ( -d $cachedir_old ) {
  7839. myprint( "Old cache directory $cachedir_old still exists\n" ) ;
  7840. $err++ ;
  7841. }else{
  7842. myprint( "Old cache directory $cachedir_old successfuly moved\n" ) ;
  7843. }
  7844. }
  7845. return( not $err ) ;
  7846. }
  7847. sub tests_cache_folder
  7848. {
  7849. note( 'Entering tests_cache_folder()' ) ;
  7850. ok( '/path/fold1/fold2' eq cache_folder( q{}, '/path', 'fold1', 'fold2'), 'cache_folder: /path, fold1, fold2 -> /path/fold1/fold2' ) ;
  7851. ok( '/pa_th/fold1/fold2' eq cache_folder( q{}, '/pa*th', 'fold1', 'fold2'), 'cache_folder: /pa*th, fold1, fold2 -> /path/fold1/fold2' ) ;
  7852. 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' ) ;
  7853. ok( 'D:/path/fold1/fold2' eq cache_folder( 'D:', '/path', 'fold1', 'fold2'), 'cache_folder: /path, fold1, fold2 -> /path/fold1/fold2' ) ;
  7854. ok( 'D:/pa_th/fold1/fold2' eq cache_folder( 'D:', '/pa*th', 'fold1', 'fold2'), 'cache_folder: /pa*th, fold1, fold2 -> /path/fold1/fold2' ) ;
  7855. 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' ) ;
  7856. ok( '//' eq cache_folder( q{}, q{}, q{}, q{}), 'cache_folder: -> //' ) ;
  7857. ok( '//_______' eq cache_folder( q{}, q{}, q{}, '*|?:"<>'), 'cache_folder: *|?:"<> -> //_______' ) ;
  7858. note( 'Leaving tests_cache_folder()' ) ;
  7859. return ;
  7860. }
  7861. sub cache_folder
  7862. {
  7863. my( $cache_base, $cache_dir, $h1_fold, $h2_fold ) = @_ ;
  7864. my $sep_1 = $sync->{ h1_sep } || '/';
  7865. my $sep_2 = $sync->{ h2_sep } || '/';
  7866. #myprint( "$cache_dir h1_fold $h1_fold sep1 $sep_1 h2_fold $h2_fold sep2 $sep_2\n" ) ;
  7867. $h1_fold = convert_sep_to_slash( $h1_fold, $sep_1 ) ;
  7868. $h2_fold = convert_sep_to_slash( $h2_fold, $sep_2 ) ;
  7869. my $cache_folder = "$cache_base" . filter_forbidden_characters( "$cache_dir/$h1_fold/$h2_fold" ) ;
  7870. #myprint( "cache_folder [$cache_folder]\n" ) ;
  7871. return( $cache_folder ) ;
  7872. }
  7873. sub tests_filter_forbidden_characters
  7874. {
  7875. note( 'Entering tests_filter_forbidden_characters()' ) ;
  7876. ok( 'a_b' eq filter_forbidden_characters( 'a_b' ), 'filter_forbidden_characters: a_b -> a_b' ) ;
  7877. ok( 'a_b' eq filter_forbidden_characters( 'a*b' ), 'filter_forbidden_characters: a*b -> a_b' ) ;
  7878. ok( 'a_b' eq filter_forbidden_characters( 'a|b' ), 'filter_forbidden_characters: a|b -> a_b' ) ;
  7879. ok( 'a_b' eq filter_forbidden_characters( 'a?b' ), 'filter_forbidden_characters: a?b -> a_b' ) ;
  7880. ok( 'a_______b' eq filter_forbidden_characters( 'a*|?:"<>b' ), 'filter_forbidden_characters: a*|?:"<>b -> a_______b' ) ;
  7881. SKIP: {
  7882. skip( 'Not on MSWin32', 1 ) if ( 'MSWin32' eq $OSNAME ) ;
  7883. ok( ( 'a b ' eq filter_forbidden_characters( 'a b ' ) ), 'filter_forbidden_characters: "a b " -> "a b "' ) ;
  7884. } ;
  7885. SKIP: {
  7886. skip( 'Only on MSWin32', 2 ) if ( 'MSWin32' ne $OSNAME ) ;
  7887. ok( ( ' a b_' eq filter_forbidden_characters( ' a b ' ) ), 'filter_forbidden_characters: "a b " -> "a b_"' ) ;
  7888. ok( ( ' a b_/ c d_' eq filter_forbidden_characters( ' a b / c d ' ) ), 'filter_forbidden_characters: " a b / c d " -> "a b_/ c d_"' ) ;
  7889. } ;
  7890. ok( 'a_b' eq filter_forbidden_characters( "a\tb" ), 'filter_forbidden_characters: a\tb -> a_b' ) ;
  7891. ok( "a_b" eq filter_forbidden_characters( "a\rb" ), 'filter_forbidden_characters: a\rb -> a_b' ) ;
  7892. ok( "a_b" eq filter_forbidden_characters( "a\nb" ), 'filter_forbidden_characters: a\nb -> a_b' ) ;
  7893. ok( "a_b" eq filter_forbidden_characters( "a\\b" ), 'filter_forbidden_characters: a\b -> a_b' ) ;
  7894. note( 'Leaving tests_filter_forbidden_characters()' ) ;
  7895. return ;
  7896. }
  7897. sub filter_forbidden_characters
  7898. {
  7899. my $string = shift ;
  7900. if ( ! defined $string ) { return ; }
  7901. if ( 'MSWin32' eq $OSNAME ) {
  7902. # Move trailing whitespace to _ " a b /c d " -> " a b_/c d_"
  7903. $string =~ s{\ (/|$)}{_$1}xg ;
  7904. }
  7905. $string =~ s{[\Q*|?:"<>\E\t\r\n\\]}{_}xg ;
  7906. #myprint( "[$string]\n" ) ;
  7907. return( $string ) ;
  7908. }
  7909. sub tests_convert_sep_to_slash
  7910. {
  7911. note( 'Entering tests_convert_sep_to_slash()' ) ;
  7912. ok(q{} eq convert_sep_to_slash(q{}, '/'), 'convert_sep_to_slash: no folder');
  7913. ok('INBOX' eq convert_sep_to_slash('INBOX', '/'), 'convert_sep_to_slash: INBOX');
  7914. ok('INBOX/foo' eq convert_sep_to_slash('INBOX/foo', '/'), 'convert_sep_to_slash: INBOX/foo');
  7915. ok('INBOX/foo' eq convert_sep_to_slash('INBOX_foo', '_'), 'convert_sep_to_slash: INBOX_foo');
  7916. ok('INBOX/foo/zob' eq convert_sep_to_slash('INBOX_foo_zob', '_'), 'convert_sep_to_slash: INBOX_foo_zob');
  7917. ok('INBOX/foo' eq convert_sep_to_slash('INBOX.foo', '.'), 'convert_sep_to_slash: INBOX.foo');
  7918. ok('INBOX/foo/hi' eq convert_sep_to_slash('INBOX.foo.hi', '.'), 'convert_sep_to_slash: INBOX.foo.hi');
  7919. note( 'Leaving tests_convert_sep_to_slash()' ) ;
  7920. return ;
  7921. }
  7922. sub convert_sep_to_slash
  7923. {
  7924. my ( $folder, $sep ) = @_ ;
  7925. $folder =~ s{\Q$sep\E}{/}xg ;
  7926. return( $folder ) ;
  7927. }
  7928. sub tests_regexmess
  7929. {
  7930. note( 'Entering tests_regexmess()' ) ;
  7931. ok( 'blabla' eq regexmess( 'blabla' ), 'regexmess, no regexmess, nothing to do' ) ;
  7932. @regexmess = ( 'lalala' ) ;
  7933. ok( not( defined regexmess( 'popopo' ) ), 'regexmess, bad regex lalala' ) ;
  7934. @regexmess = ( 's/p/Z/g' ) ;
  7935. ok( 'ZoZoZo' eq regexmess( 'popopo' ), 'regexmess, s/p/Z/g' ) ;
  7936. @regexmess = ( 's{c}{C}gxms' ) ;
  7937. ok("H1: abC\nH2: Cde\n\nBody abC"
  7938. eq regexmess( "H1: abc\nH2: cde\n\nBody abc"),
  7939. 'regexmess, c->C');
  7940. @regexmess = ( 's{\AFrom\ }{From:}gxms' ) ;
  7941. ok( q{}
  7942. eq regexmess(q{}),
  7943. 'From mbox 1 add colon blank');
  7944. ok( 'From:<tartanpion@machin.truc>'
  7945. eq regexmess('From <tartanpion@machin.truc>'),
  7946. 'From mbox 2 add colo');
  7947. ok( "\n" . 'From <tartanpion@machin.truc>'
  7948. eq regexmess("\n" . 'From <tartanpion@machin.truc>'),
  7949. 'From mbox 3 add colo') ;
  7950. ok( "From: zzz\n" . 'From <tartanpion@machin.truc>'
  7951. eq regexmess("From zzz\n" . 'From <tartanpion@machin.truc>'),
  7952. 'From mbox 4 add colo') ;
  7953. @regexmess = ( 's{\AFrom\ [^\n]*(\n)?}{}gxms' ) ;
  7954. ok( q{}
  7955. eq regexmess(q{}),
  7956. 'From mbox 1 remove, blank');
  7957. ok( q{}
  7958. eq regexmess('From <tartanpion@machin.truc>'),
  7959. 'From mbox 2 remove');
  7960. ok( "\n" . 'From <tartanpion@machin.truc>'
  7961. eq regexmess("\n" . 'From <tartanpion@machin.truc>'),
  7962. 'From mbox 3 remove');
  7963. #myprint( "[", regexmess("From zzz\n" . 'From <tartanpion@machin.truc>'), "]" ) ;
  7964. ok( q{} . 'From <tartanpion@machin.truc>'
  7965. eq regexmess("From zzz\n" . 'From <tartanpion@machin.truc>'),
  7966. 'From mbox 4 remove');
  7967. ok(
  7968. <<'EOM'
  7969. Date: Sat, 10 Jul 2010 05:34:45 -0700
  7970. From:<tartanpion@machin.truc>
  7971. Hello,
  7972. Bye.
  7973. EOM
  7974. eq regexmess(
  7975. <<'EOM'
  7976. From zzz
  7977. Date: Sat, 10 Jul 2010 05:34:45 -0700
  7978. From:<tartanpion@machin.truc>
  7979. Hello,
  7980. Bye.
  7981. EOM
  7982. ), 'From mbox 5 remove');
  7983. @regexmess = ( 's{\A((?:[^\n]+\n)+|)^Disposition-Notification-To:[^\n]*\n(\r?\n|.*\n\r?\n)}{$1$2}xms' ) ; # SUPER SUPER BEST!
  7984. ok(
  7985. <<'EOM'
  7986. Date: Sat, 10 Jul 2010 05:34:45 -0700
  7987. From:<tartanpion@machin.truc>
  7988. Hello,
  7989. Bye.
  7990. EOM
  7991. eq regexmess(
  7992. <<'EOM'
  7993. Date: Sat, 10 Jul 2010 05:34:45 -0700
  7994. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  7995. From:<tartanpion@machin.truc>
  7996. Hello,
  7997. Bye.
  7998. EOM
  7999. ),
  8000. 'regexmess: 1 Delete header Disposition-Notification-To:');
  8001. ok(
  8002. <<'EOM'
  8003. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8004. From:<tartanpion@machin.truc>
  8005. Hello,
  8006. Bye.
  8007. EOM
  8008. eq regexmess(
  8009. <<'EOM'
  8010. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8011. From:<tartanpion@machin.truc>
  8012. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8013. Hello,
  8014. Bye.
  8015. EOM
  8016. ),
  8017. 'regexmess: 2 Delete header Disposition-Notification-To:');
  8018. ok(
  8019. <<'EOM'
  8020. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8021. From:<tartanpion@machin.truc>
  8022. Hello,
  8023. Bye.
  8024. EOM
  8025. eq regexmess(
  8026. <<'EOM'
  8027. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8028. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8029. From:<tartanpion@machin.truc>
  8030. Hello,
  8031. Bye.
  8032. EOM
  8033. ),
  8034. 'regexmess: 3 Delete header Disposition-Notification-To:');
  8035. ok(
  8036. <<'EOM'
  8037. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8038. From:<tartanpion@machin.truc>
  8039. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8040. Bye.
  8041. EOM
  8042. eq regexmess(
  8043. <<'EOM'
  8044. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8045. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8046. From:<tartanpion@machin.truc>
  8047. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8048. Bye.
  8049. EOM
  8050. ),
  8051. 'regexmess: 4 Delete header Disposition-Notification-To:');
  8052. ok(
  8053. <<'EOM'
  8054. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8055. From:<tartanpion@machin.truc>
  8056. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8057. Bye.
  8058. EOM
  8059. eq regexmess(
  8060. <<'EOM'
  8061. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8062. From:<tartanpion@machin.truc>
  8063. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8064. Bye.
  8065. EOM
  8066. ),
  8067. 'regexmess: 5 Delete header Disposition-Notification-To:');
  8068. ok(
  8069. <<'EOM'
  8070. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8071. From:<tartanpion@machin.truc>
  8072. Hello,
  8073. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8074. Bye.
  8075. EOM
  8076. eq regexmess(
  8077. <<'EOM'
  8078. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8079. From:<tartanpion@machin.truc>
  8080. Hello,
  8081. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8082. Bye.
  8083. EOM
  8084. ),
  8085. 'regexmess: 6 Delete header Disposition-Notification-To:');
  8086. ok(
  8087. <<'EOM'
  8088. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8089. From:<tartanpion@machin.truc>
  8090. Hello,
  8091. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8092. Bye.
  8093. EOM
  8094. eq regexmess(
  8095. <<'EOM'
  8096. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8097. From:<tartanpion@machin.truc>
  8098. Hello,
  8099. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8100. Bye.
  8101. EOM
  8102. ),
  8103. 'regexmess: 7 Delete header Disposition-Notification-To:');
  8104. ok(
  8105. <<'EOM'
  8106. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8107. From:<tartanpion@machin.truc>
  8108. Hello,
  8109. Bye.
  8110. EOM
  8111. eq regexmess(
  8112. <<'EOM'
  8113. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8114. From:<tartanpion@machin.truc>
  8115. Hello,
  8116. Bye.
  8117. EOM
  8118. ),
  8119. 'regexmess: 8 Delete header Disposition-Notification-To:');
  8120. ok(
  8121. <<'EOM'
  8122. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8123. From:<tartanpion@machin.truc>
  8124. Hello,
  8125. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8126. Bye.
  8127. EOM
  8128. eq regexmess(
  8129. <<'EOM'
  8130. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8131. From:<tartanpion@machin.truc>
  8132. Hello,
  8133. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8134. Bye.
  8135. EOM
  8136. ),
  8137. 'regexmess: 9 Delete header Disposition-Notification-To:');
  8138. ok(
  8139. <<'EOM'
  8140. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8141. From:<tartanpion@machin.truc>
  8142. Hello,
  8143. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8144. Bye.
  8145. EOM
  8146. eq regexmess(
  8147. <<'EOM'
  8148. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8149. From:<tartanpion@machin.truc>
  8150. Hello,
  8151. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8152. Bye.
  8153. EOM
  8154. ),
  8155. 'regexmess: 10 Delete header Disposition-Notification-To:');
  8156. ok(
  8157. <<'EOM'
  8158. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8159. From:<tartanpion@machin.truc>
  8160. Hello,
  8161. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8162. Bye.
  8163. EOM
  8164. eq regexmess(
  8165. <<'EOM'
  8166. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8167. From:<tartanpion@machin.truc>
  8168. Hello,
  8169. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8170. Bye.
  8171. EOM
  8172. ),
  8173. 'regexmess: 11 Delete header Disposition-Notification-To:');
  8174. ok(
  8175. <<'EOM'
  8176. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8177. From:<tartanpion@machin.truc>
  8178. Hello,
  8179. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8180. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8181. Bye.
  8182. EOM
  8183. eq regexmess(
  8184. <<'EOM'
  8185. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8186. From:<tartanpion@machin.truc>
  8187. Hello,
  8188. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8189. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8190. Bye.
  8191. EOM
  8192. ),
  8193. 'regexmess: 12 Delete header Disposition-Notification-To:');
  8194. @regexmess = ( 's{\A(.*?(?! ^$))^Disposition-Notification-To:(.*?)$}{$1X-Disposition-Notification-To:$2}igxms' ) ; # BAD!
  8195. @regexmess = ( 's{\A((?:[^\n]+\n)+|)(^Disposition-Notification-To:[^\n]*\n)(\r?\n|.*\n\r?\n)}{$1X-$2$3}ims' ) ;
  8196. ok(
  8197. <<'EOM'
  8198. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8199. From:<tartanpion@machin.truc>
  8200. Hello,
  8201. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8202. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8203. Bye.
  8204. EOM
  8205. eq regexmess(
  8206. <<'EOM'
  8207. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8208. From:<tartanpion@machin.truc>
  8209. Hello,
  8210. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8211. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8212. Bye.
  8213. EOM
  8214. ),
  8215. 'regexmess: 13 Delete header Disposition-Notification-To:');
  8216. ok(
  8217. <<'EOM'
  8218. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8219. X-Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8220. From:<tartanpion@machin.truc>
  8221. Hello,
  8222. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8223. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8224. Bye.
  8225. EOM
  8226. eq regexmess(
  8227. <<'EOM'
  8228. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8229. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8230. From:<tartanpion@machin.truc>
  8231. Hello,
  8232. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8233. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8234. Bye.
  8235. EOM
  8236. ),
  8237. 'regexmess: 14 Delete header Disposition-Notification-To:');
  8238. ok(
  8239. <<'EOM'
  8240. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8241. X-Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8242. From:<tartanpion@machin.truc>
  8243. Hello,
  8244. Bye.
  8245. EOM
  8246. eq regexmess(
  8247. <<'EOM'
  8248. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8249. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8250. From:<tartanpion@machin.truc>
  8251. Hello,
  8252. Bye.
  8253. EOM
  8254. ),
  8255. 'regexmess: 15 Delete header Disposition-Notification-To:');
  8256. ok(
  8257. <<'EOM'
  8258. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8259. From:<tartanpion@machin.truc>
  8260. X-Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8261. Hello,
  8262. Bye.
  8263. EOM
  8264. eq regexmess(
  8265. <<'EOM'
  8266. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8267. From:<tartanpion@machin.truc>
  8268. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8269. Hello,
  8270. Bye.
  8271. EOM
  8272. ),
  8273. 'regexmess: 16 Delete header Disposition-Notification-To:');
  8274. ok(
  8275. <<'EOM'
  8276. X-Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8277. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8278. From:<tartanpion@machin.truc>
  8279. Hello,
  8280. Bye.
  8281. EOM
  8282. eq regexmess(
  8283. <<'EOM'
  8284. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  8285. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8286. From:<tartanpion@machin.truc>
  8287. Hello,
  8288. Bye.
  8289. EOM
  8290. ),
  8291. 'regexmess: 17 Delete header Disposition-Notification-To:');
  8292. # regex to play with Date: from the FAQ
  8293. #@regexmess = 's{\A(.*?(?! ^$))^Date:(.*?)$}{$1Date:$2\nX-Date:$2}gxms'
  8294. note( 'Leaving tests_regexmess()' ) ;
  8295. return ;
  8296. }
  8297. sub regexmess
  8298. {
  8299. my ( $string ) = @_ ;
  8300. foreach my $regexmess ( @regexmess ) {
  8301. $sync->{ debug } and myprint( "eval \$string =~ $regexmess\n" ) ;
  8302. my $ret = eval "\$string =~ $regexmess ; 1" ;
  8303. #myprint( "eval [$ret]\n" ) ;
  8304. if ( ( not $ret ) or $EVAL_ERROR ) {
  8305. myprint( "Error: eval regexmess '$regexmess': $EVAL_ERROR" ) ;
  8306. return( undef ) ;
  8307. }
  8308. }
  8309. $sync->{ debug } and myprint( "$string\n" ) ;
  8310. return( $string ) ;
  8311. }
  8312. sub tests_skipmess
  8313. {
  8314. note( 'Entering tests_skipmess()' ) ;
  8315. ok( not( defined skipmess( 'blabla' ) ), 'skipmess, no skipmess, no skip' ) ;
  8316. @skipmess = ('[') ;
  8317. ok( not( defined skipmess( 'popopo' ) ), 'skipmess, bad regex [' ) ;
  8318. @skipmess = ('lalala') ;
  8319. ok( not( defined skipmess( 'popopo' ) ), 'skipmess, bad regex lalala' ) ;
  8320. @skipmess = ('/popopo/') ;
  8321. ok( 1 == skipmess( 'popopo' ), 'skipmess, popopo match regex /popopo/' ) ;
  8322. @skipmess = ('/popopo/') ;
  8323. ok( 0 == skipmess( 'rrrrrr' ), 'skipmess, rrrrrr does not match regex /popopo/' ) ;
  8324. @skipmess = ('m{^$}') ;
  8325. ok( 1 == skipmess( q{} ), 'skipmess: empty string yes' ) ;
  8326. ok( 0 == skipmess( 'Hi!' ), 'skipmess: empty string no' ) ;
  8327. @skipmess = ('m{i}') ;
  8328. ok( 1 == skipmess( 'Hi!' ), 'skipmess: i string yes' ) ;
  8329. ok( 0 == skipmess( 'Bye!' ), 'skipmess: i string no' ) ;
  8330. @skipmess = ('m{[\x80-\xff]}') ;
  8331. ok( 0 == skipmess( 'Hi!' ), 'skipmess: i 8bit no' ) ;
  8332. ok( 1 == skipmess( "\xff" ), 'skipmess: \xff 8bit yes' ) ;
  8333. @skipmess = ('m{A}', 'm{B}') ;
  8334. ok( 0 == skipmess( 'Hi!' ), 'skipmess: A or B no' ) ;
  8335. ok( 0 == skipmess( 'lala' ), 'skipmess: A or B no' ) ;
  8336. ok( 0 == skipmess( "\xff" ), 'skipmess: A or B no' ) ;
  8337. ok( 1 == skipmess( 'AB' ), 'skipmess: A or B yes' ) ;
  8338. ok( 1 == skipmess( 'BA' ), 'skipmess: A or B yes' ) ;
  8339. ok( 1 == skipmess( 'AA' ), 'skipmess: A or B yes' ) ;
  8340. ok( 1 == skipmess( 'Ok Bye' ), 'skipmess: A or B yes' ) ;
  8341. @skipmess = ( 'm#\A((?:[^\n]+\n)+|)^Content-Type: Message/Partial;[^\n]*\n(?:\n|.*\n\n)#ism' ) ; # SUPER BEST!
  8342. ok( 1 == skipmess(
  8343. <<'EOM'
  8344. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8345. Content-Type: Message/Partial; blabla
  8346. From:<tartanpion@machin.truc>
  8347. Hello!
  8348. Bye.
  8349. EOM
  8350. ),
  8351. 'skipmess: 1 match Content-Type: Message/Partial' ) ;
  8352. ok( 0 == skipmess(
  8353. <<'EOM'
  8354. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8355. From:<tartanpion@machin.truc>
  8356. Hello!
  8357. Bye.
  8358. EOM
  8359. ),
  8360. 'skipmess: 2 not match Content-Type: Message/Partial' ) ;
  8361. ok( 1 == skipmess(
  8362. <<'EOM'
  8363. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8364. From:<tartanpion@machin.truc>
  8365. Content-Type: Message/Partial; blabla
  8366. Hello!
  8367. Bye.
  8368. EOM
  8369. ),
  8370. 'skipmess: 3 match Content-Type: Message/Partial' ) ;
  8371. ok( 0 == skipmess(
  8372. <<'EOM'
  8373. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8374. From:<tartanpion@machin.truc>
  8375. Hello!
  8376. Content-Type: Message/Partial; blabla
  8377. Bye.
  8378. EOM
  8379. ),
  8380. 'skipmess: 4 not match Content-Type: Message/Partial' ) ;
  8381. ok( 0 == skipmess(
  8382. <<'EOM'
  8383. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8384. From:<tartanpion@machin.truc>
  8385. Hello!
  8386. Content-Type: Message/Partial; blabla
  8387. Bye.
  8388. EOM
  8389. ),
  8390. 'skipmess: 5 not match Content-Type: Message/Partial' ) ;
  8391. ok( 1 == skipmess(
  8392. <<'EOM'
  8393. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8394. Content-Type: Message/Partial; blabla
  8395. From:<tartanpion@machin.truc>
  8396. Hello!
  8397. Content-Type: Message/Partial; blabla
  8398. Bye.
  8399. EOM
  8400. ),
  8401. 'skipmess: 6 match Content-Type: Message/Partial' ) ;
  8402. ok( 1 == skipmess(
  8403. <<'EOM'
  8404. Date: Sat, 10 Jul 2010 05:34:45 -0700
  8405. Content-Type: Message/Partial;
  8406. From:<tartanpion@machin.truc>
  8407. Hello!
  8408. Bye.
  8409. EOM
  8410. ),
  8411. 'skipmess: 7 match Content-Type: Message/Partial' ) ;
  8412. ok( 1 == skipmess(
  8413. <<'EOM'
  8414. Date: Wed, 2 Jul 2014 02:26:40 +0000
  8415. MIME-Version: 1.0
  8416. Content-Type: message/partial;
  8417. id="TAN_U_P<1404267997.00007489ed17>";
  8418. number=3;
  8419. total=3
  8420. 6HQ6Hh3CdXj77qEGixerQ6zHx0OnQ/Cf5On4W0Y6vtU2crABZQtD46Hx1EOh8dDz4+OnTr1G
  8421. Hello!
  8422. Bye.
  8423. EOM
  8424. ),
  8425. 'skipmess: 8 match Content-Type: Message/Partial' ) ;
  8426. ok( 1 == skipmess(
  8427. <<'EOM'
  8428. Return-Path: <gilles@lamiral.info>
  8429. Received: by lamiral.info (Postfix, from userid 1000)
  8430. id 21EB12443BF; Mon, 2 Mar 2015 15:38:35 +0100 (CET)
  8431. Subject: test: aethaecohngiexao
  8432. To: <tata@petite.lamiral.info>
  8433. X-Mailer: mail (GNU Mailutils 2.2)
  8434. Message-Id: <20150302143835.21EB12443BF@lamiral.info>
  8435. Content-Type: message/partial;
  8436. id="TAN_U_P<1404267997.00007489ed17>";
  8437. number=3;
  8438. total=3
  8439. Date: Mon, 2 Mar 2015 15:38:34 +0100 (CET)
  8440. From: gilles@lamiral.info (Gilles LAMIRAL)
  8441. test: aethaecohngiexao
  8442. EOM
  8443. ),
  8444. 'skipmess: 9 match Content-Type: Message/Partial' ) ;
  8445. ok( 1 == skipmess(
  8446. <<'EOM'
  8447. Date: Mon, 2 Mar 2015 15:38:34 +0100 (CET)
  8448. From: gilles@lamiral.info (Gilles LAMIRAL)
  8449. Content-Type: message/partial;
  8450. id="TAN_U_P<1404267997.00007489ed17>";
  8451. number=3;
  8452. total=3
  8453. test: aethaecohngiexao
  8454. EOM
  8455. . "lalala\n" x 3_000_000
  8456. ),
  8457. 'skipmess: 10 match Content-Type: Message/Partial' ) ;
  8458. ok( 0 == skipmess(
  8459. <<'EOM'
  8460. Date: Mon, 2 Mar 2015 15:38:34 +0100 (CET)
  8461. From: gilles@lamiral.info (Gilles LAMIRAL)
  8462. test: aethaecohngiexao
  8463. EOM
  8464. . "lalala\n" x 3_000_000
  8465. ),
  8466. 'skipmess: 11 match Content-Type: Message/Partial' ) ;
  8467. ok( 0 == skipmess(
  8468. <<"EOM"
  8469. From: fff\r
  8470. To: fff\r
  8471. Subject: Testing imapsync --skipmess\r
  8472. Date: Mon, 22 Aug 2011 08:40:20 +0800\r
  8473. Mime-Version: 1.0\r
  8474. Content-Type: text/plain; charset=iso-8859-1\r
  8475. Content-Transfer-Encoding: 7bit\r
  8476. \r
  8477. EOM
  8478. . qq{!#"d%&'()*+,-./0123456789:;<=>?\@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefg\r\n } x 32_730
  8479. ),
  8480. 'skipmess: 12 not match Content-Type: Message/Partial' ) ;
  8481. # Complex regular subexpression recursion limit (32766) exceeded with more lines
  8482. # exit;
  8483. note( 'Leaving tests_skipmess()' ) ;
  8484. return ;
  8485. }
  8486. sub skipmess
  8487. {
  8488. my ( $string ) = @_ ;
  8489. my $match ;
  8490. #myprint( "$string\n" ) ;
  8491. foreach my $skipmess ( @skipmess ) {
  8492. $sync->{ debug } and myprint( "eval \$match = \$string =~ $skipmess\n" ) ;
  8493. my $ret = eval "\$match = \$string =~ $skipmess ; 1" ;
  8494. #myprint( "eval [$ret]\n" ) ;
  8495. $sync->{ debug } and myprint( "match [$match]\n" ) ;
  8496. if ( ( not $ret ) or $EVAL_ERROR ) {
  8497. myprint( "Error: eval skipmess '$skipmess': $EVAL_ERROR" ) ;
  8498. return( undef ) ;
  8499. }
  8500. return( $match ) if ( $match ) ;
  8501. }
  8502. return( $match ) ;
  8503. }
  8504. sub tests_bytes_display_string
  8505. {
  8506. note( 'Entering tests_bytes_display_string()' ) ;
  8507. is( 'NA', bytes_display_string( ), 'bytes_display_string: no args => NA' ) ;
  8508. is( 'NA', bytes_display_string( undef ), 'bytes_display_string: undef => NA' ) ;
  8509. is( 'NA', bytes_display_string( 'blabla' ), 'bytes_display_string: blabla => NA' ) ;
  8510. ok( '0.000 KiB' eq bytes_display_string( 0 ), 'bytes_display_string: 0' ) ;
  8511. ok( '0.001 KiB' eq bytes_display_string( 1 ), 'bytes_display_string: 1' ) ;
  8512. ok( '0.010 KiB' eq bytes_display_string( 10 ), 'bytes_display_string: 10' ) ;
  8513. ok( '1.000 MiB' eq bytes_display_string( 1_048_575 ), 'bytes_display_string: 1_048_575' ) ;
  8514. ok( '1.000 MiB' eq bytes_display_string( 1_048_576 ), 'bytes_display_string: 1_048_576' ) ;
  8515. ok( '1.000 GiB' eq bytes_display_string( 1_073_741_823 ), 'bytes_display_string: 1_073_741_823 ' ) ;
  8516. ok( '1.000 GiB' eq bytes_display_string( 1_073_741_824 ), 'bytes_display_string: 1_073_741_824 ' ) ;
  8517. ok( '1.000 TiB' eq bytes_display_string( 1_099_511_627_775 ), 'bytes_display_string: 1_099_511_627_775' ) ;
  8518. ok( '1.000 TiB' eq bytes_display_string( 1_099_511_627_776 ), 'bytes_display_string: 1_099_511_627_776' ) ;
  8519. ok( '1.000 PiB' eq bytes_display_string( 1_125_899_906_842_623 ), 'bytes_display_string: 1_125_899_906_842_623' ) ;
  8520. ok( '1.000 PiB' eq bytes_display_string( 1_125_899_906_842_624 ), 'bytes_display_string: 1_125_899_906_842_624' ) ;
  8521. 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' ) ;
  8522. 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' ) ;
  8523. 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' ) ;
  8524. #myprint( bytes_display_string( 1_180_591_620_717_411_303_424 ), "\n" ) ;
  8525. note( 'Leaving tests_bytes_display_string()' ) ;
  8526. return ;
  8527. }
  8528. sub bytes_display_string
  8529. {
  8530. my ( $bytes ) = @_ ;
  8531. my $readable_value = q{} ;
  8532. if ( ! defined( $bytes ) ) {
  8533. return( 'NA' ) ;
  8534. }
  8535. if ( not match_number( $bytes ) ) {
  8536. return( 'NA' ) ;
  8537. }
  8538. SWITCH: {
  8539. if ( abs( $bytes ) < ( 1000 * $KIBI ) ) {
  8540. $readable_value = mysprintf( '%.3f KiB', $bytes / $KIBI) ;
  8541. last SWITCH ;
  8542. }
  8543. if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI ) ) {
  8544. $readable_value = mysprintf( '%.3f MiB', $bytes / ($KIBI * $KIBI) ) ;
  8545. last SWITCH ;
  8546. }
  8547. if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI * $KIBI) ) {
  8548. $readable_value = mysprintf( '%.3f GiB', $bytes / ($KIBI * $KIBI * $KIBI) ) ;
  8549. last SWITCH ;
  8550. }
  8551. if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI * $KIBI * $KIBI) ) {
  8552. $readable_value = mysprintf( '%.3f TiB', $bytes / ($KIBI * $KIBI * $KIBI * $KIBI) ) ;
  8553. last SWITCH ;
  8554. } else {
  8555. $readable_value = mysprintf( '%.3f PiB', $bytes / ($KIBI * $KIBI * $KIBI * $KIBI * $KIBI) ) ;
  8556. }
  8557. # if you have exabytes (EiB) of email to transfer, you have too much email!
  8558. }
  8559. #myprint( "$bytes = $readable_value\n" ) ;
  8560. return( $readable_value ) ;
  8561. }
  8562. sub tests_useheader_suggestion
  8563. {
  8564. note( 'Entering tests_useheader_suggestion()' ) ;
  8565. is( undef, useheader_suggestion( ), 'useheader_suggestion: no args => undef' ) ;
  8566. my $mysync = {} ;
  8567. $mysync->{ h1_nb_msg_noheader } = 0 ;
  8568. is( q{}, useheader_suggestion( $mysync ), 'useheader_suggestion: h1_nb_msg_noheader count null => no suggestion' ) ;
  8569. $mysync->{ h1_nb_msg_noheader } = 2 ;
  8570. is( q{in order to sync those 2 unidentified messages, add option --addheader}, useheader_suggestion( $mysync ),
  8571. 'useheader_suggestion: h1_nb_msg_noheader count 2 => suggestion of --addheader' ) ;
  8572. note( 'Leaving tests_useheader_suggestion()' ) ;
  8573. return ;
  8574. }
  8575. sub useheader_suggestion
  8576. {
  8577. my $mysync = shift ;
  8578. if ( ! defined $mysync->{ h1_nb_msg_noheader } )
  8579. {
  8580. return ;
  8581. }
  8582. elsif ( 1 <= $mysync->{ h1_nb_msg_noheader } )
  8583. {
  8584. return qq{in order to sync those $mysync->{ h1_nb_msg_noheader } unidentified messages, add option --addheader} ;
  8585. }
  8586. else
  8587. {
  8588. return q{} ;
  8589. }
  8590. return ;
  8591. }
  8592. sub stats
  8593. {
  8594. my $mysync = shift ;
  8595. if ( ! $mysync->{stats} ) {
  8596. return ;
  8597. }
  8598. my $timeend = time ;
  8599. my $timediff = $timeend - $mysync->{timestart} ;
  8600. my $timeend_str = localtime $timeend ;
  8601. my $memory_consumption_at_end = memory_consumption( ) || 0 ;
  8602. my $memory_consumption_at_start = $mysync->{ memory_consumption_at_start } || 0 ;
  8603. my $memory_ratio = ($max_msg_size_in_bytes) ?
  8604. mysprintf('%.1f', $memory_consumption_at_end / $max_msg_size_in_bytes) : 'NA' ;
  8605. # my $useheader_suggestion = useheader_suggestion( $mysync ) ;
  8606. myprint( "++++ Statistics\n" ) ;
  8607. myprint( "Transfer started on : $timestart_str\n" ) ;
  8608. myprint( "Transfer ended on : $timeend_str\n" ) ;
  8609. myprintf( "Transfer time : %.1f sec\n", $timediff ) ;
  8610. myprint( "Folders synced : $h1_folders_wanted_ct/$h1_folders_wanted_nb synced\n" ) ;
  8611. myprint( "Messages transferred : $mysync->{nb_msg_transferred} " ) ;
  8612. myprint( "(could be $nb_msg_skipped_dry_mode without dry mode)" ) if ( $mysync->{dry} ) ;
  8613. myprint( "\n" ) ;
  8614. myprint( "Messages skipped : $mysync->{ nb_msg_skipped }\n" ) ;
  8615. myprint( "Messages found duplicate on host1 : $h1_nb_msg_duplicate\n" ) ;
  8616. myprint( "Messages found duplicate on host2 : $h2_nb_msg_duplicate\n" ) ;
  8617. myprint( "Messages found crossduplicate on host2 : $mysync->{ h2_nb_msg_crossdup }\n" ) ;
  8618. myprint( "Messages void (noheader) on host1 : $mysync->{ h1_nb_msg_noheader } ", useheader_suggestion( $mysync ), "\n" ) ;
  8619. myprint( "Messages void (noheader) on host2 : $h2_nb_msg_noheader\n" ) ;
  8620. nb_messages_in_1_not_in_2( $mysync ) ;
  8621. nb_messages_in_2_not_in_1( $mysync ) ;
  8622. myprintf( "Messages found in host1 not in host2 : %s messages\n", $mysync->{ nb_messages_in_1_not_in_2 } ) ;
  8623. myprintf( "Messages found in host2 not in host1 : %s messages\n", $mysync->{ nb_messages_in_2_not_in_1 } ) ;
  8624. myprint( "Messages deleted on host1 : $mysync->{ h1_nb_msg_deleted }\n" ) ;
  8625. myprint( "Messages deleted on host2 : $h2_nb_msg_deleted\n" ) ;
  8626. myprintf( "Total bytes transferred : %s (%s)\n",
  8627. $mysync->{total_bytes_transferred},
  8628. bytes_display_string( $mysync->{total_bytes_transferred} ) ) ;
  8629. myprintf( "Total bytes skipped : %s (%s)\n",
  8630. $mysync->{ total_bytes_skipped },
  8631. bytes_display_string( $mysync->{ total_bytes_skipped } ) ) ;
  8632. $timediff ||= 1 ; # No division per 0
  8633. myprintf("Message rate : %.1f messages/s\n", $mysync->{nb_msg_transferred} / $timediff ) ;
  8634. myprintf("Average bandwidth rate : %.1f KiB/s\n", $mysync->{total_bytes_transferred} / $KIBI / $timediff ) ;
  8635. myprint( "Reconnections to host1 : $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT}\n" ) ;
  8636. myprint( "Reconnections to host2 : $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT}\n" ) ;
  8637. myprintf("Memory consumption at the end : %.1f MiB (started with %.1f MiB)\n",
  8638. $memory_consumption_at_end / $KIBI / $KIBI,
  8639. $memory_consumption_at_start / $KIBI / $KIBI ) ;
  8640. myprint( "Load end is : " . ( join( q{ }, loadavg( ) ) || 'unknown' ), " on $mysync->{cpu_number} cores\n" ) ;
  8641. myprintf("Biggest message : %s bytes (%s)\n",
  8642. $max_msg_size_in_bytes,
  8643. bytes_display_string( $max_msg_size_in_bytes) ) ;
  8644. myprint( "Memory/biggest message ratio : $memory_ratio\n" ) ;
  8645. if ( $foldersizesatend and $foldersizes ) {
  8646. my $nb_msg_start_diff = diff_or_NA( $h2_nb_msg_start, $h1_nb_msg_start ) ;
  8647. my $bytes_start_diff = diff_or_NA( $h2_bytes_start, $h1_bytes_start ) ;
  8648. myprintf("Start difference host2 - host1 : %s messages, %s bytes (%s)\n", $nb_msg_start_diff,
  8649. $bytes_start_diff,
  8650. bytes_display_string( $bytes_start_diff ) ) ;
  8651. my $nb_msg_end_diff = diff_or_NA( $h2_nb_msg_end, $h1_nb_msg_end ) ;
  8652. my $bytes_end_diff = diff_or_NA( $h2_bytes_end, $h1_bytes_end ) ;
  8653. myprintf("Final difference host2 - host1 : %s messages, %s bytes (%s)\n", $nb_msg_end_diff,
  8654. $bytes_end_diff,
  8655. bytes_display_string( $bytes_end_diff ) ) ;
  8656. }
  8657. comment_on_final_diff_in_1_not_in_2( $mysync ) ;
  8658. comment_on_final_diff_in_2_not_in_1( $mysync ) ;
  8659. myprint( "Detected $mysync->{nb_errors} errors\n\n" ) ;
  8660. myprint( $warn_release, "\n" ) ;
  8661. myprint( homepage( ), "\n" ) ;
  8662. return ;
  8663. }
  8664. sub diff_or_NA
  8665. {
  8666. my( $n1, $n2 ) = @ARG ;
  8667. if ( not defined $n1 or not defined $n2 ) {
  8668. return 'NA' ;
  8669. }
  8670. if ( not match_number( $n1 )
  8671. or not match_number( $n2 ) ) {
  8672. return 'NA' ;
  8673. }
  8674. return( $n1 - $n2 ) ;
  8675. }
  8676. sub match_number
  8677. {
  8678. my $n = shift @ARG ;
  8679. if ( not defined $n ) {
  8680. return 0 ;
  8681. }
  8682. if ( $n =~ /[0-9]+\.?[0-9]?/x ) {
  8683. return 1 ;
  8684. }
  8685. else {
  8686. return 0 ;
  8687. }
  8688. }
  8689. sub tests_match_number
  8690. {
  8691. note( 'Entering tests_match_number()' ) ;
  8692. is( 0, match_number( ), 'match_number: no parameters => 0' ) ;
  8693. is( 0, match_number( undef ), 'match_number: undef => 0' ) ;
  8694. is( 0, match_number( 'blabla' ), 'match_number: blabla => 0' ) ;
  8695. is( 1, match_number( 0 ), 'match_number: 0 => 1' ) ;
  8696. is( 1, match_number( 1 ), 'match_number: 1 => 1' ) ;
  8697. is( 1, match_number( 1.0 ), 'match_number: 1.0 => 1' ) ;
  8698. is( 1, match_number( 0.0 ), 'match_number: 0.0 => 1' ) ;
  8699. note( 'Leaving tests_match_number()' ) ;
  8700. return ;
  8701. }
  8702. sub tests_diff_or_NA
  8703. {
  8704. note( 'Entering tests_diff_or_NA()' ) ;
  8705. is( 'NA', diff_or_NA( ), 'diff_or_NA: no parameters => NA' ) ;
  8706. is( 'NA', diff_or_NA( undef ), 'diff_or_NA: undef => NA' ) ;
  8707. is( 'NA', diff_or_NA( undef, undef ), 'diff_or_NA: undef undef => NA' ) ;
  8708. is( 'NA', diff_or_NA( undef, 1 ), 'diff_or_NA: undef 1 => NA' ) ;
  8709. is( 'NA', diff_or_NA( 1, undef ), 'diff_or_NA: 1 undef => NA' ) ;
  8710. is( 'NA', diff_or_NA( 'blabla', 1 ), 'diff_or_NA: blabla 1 => NA' ) ;
  8711. is( 'NA', diff_or_NA( 1, 'blabla' ), 'diff_or_NA: 1 blabla => NA' ) ;
  8712. is( 0, diff_or_NA( 1, 1 ), 'diff_or_NA: 1 1 => 0' ) ;
  8713. is( 1, diff_or_NA( 1, 0 ), 'diff_or_NA: 1 0 => 1' ) ;
  8714. is( -1, diff_or_NA( 0, 1 ), 'diff_or_NA: 0 1 => -1' ) ;
  8715. is( 0, diff_or_NA( 1.0, 1 ), 'diff_or_NA: 1.0 1 => 0' ) ;
  8716. is( 1, diff_or_NA( 1.0, 0 ), 'diff_or_NA: 1.0 0 => 1' ) ;
  8717. is( -1, diff_or_NA( 0, 1.0 ), 'diff_or_NA: 0 1.0 => -1' ) ;
  8718. note( 'Leaving tests_diff_or_NA()' ) ;
  8719. return ;
  8720. }
  8721. sub homepage
  8722. {
  8723. return( 'Homepage: https://imapsync.lamiral.info/' ) ;
  8724. }
  8725. sub load_modules
  8726. {
  8727. if ( $sync->{ssl1}
  8728. or $sync->{ssl2}
  8729. or $sync->{tls1}
  8730. or $sync->{tls2}) {
  8731. if ( $sync->{inet4} ) {
  8732. IO::Socket::SSL->import( 'inet4' ) ;
  8733. }
  8734. if ( $sync->{inet6} ) {
  8735. IO::Socket::SSL->import( 'inet6' ) ;
  8736. }
  8737. }
  8738. return ;
  8739. }
  8740. sub parse_header_msg
  8741. {
  8742. my ( $mysync, $imap, $m_uid, $s_heads, $s_fir, $side, $s_hash ) = @_ ;
  8743. my $head = $s_heads->{$m_uid} ;
  8744. my $headnum = scalar keys %{ $head } ;
  8745. $mysync->{ debug } and myprint( "$side: uid $m_uid number of headers, pass one: ", $headnum, "\n" ) ;
  8746. if ( ( ! $headnum ) and ( $wholeheaderifneeded ) ){
  8747. myprint( "$side: uid $m_uid no header by parse_headers so taking whole header with BODY.PEEK[HEADER]\n" ) ;
  8748. $imap->fetch($m_uid, 'BODY.PEEK[HEADER]' ) ;
  8749. my $whole_header = $imap->_transaction_literals ;
  8750. #myprint( $whole_header ) ;
  8751. $head = decompose_header( $whole_header ) ;
  8752. $headnum = scalar keys %{ $head } ;
  8753. $mysync->{ debug } and myprint( "$side: uid $m_uid number of headers, pass two: ", $headnum, "\n" ) ;
  8754. }
  8755. #myprint( Data::Dumper->Dump( [ $head, \%useheader ] ) ) ;
  8756. my $headstr ;
  8757. $headstr = header_construct( $head, $side, $m_uid ) ;
  8758. if ( ( ! $headstr ) and ( $mysync->{addheader} ) and ( $side eq 'Host1' ) ) {
  8759. my $header = add_header( $m_uid ) ;
  8760. myprint( "$side: uid $m_uid no header found so adding our own [$header]\n" ) ;
  8761. $headstr .= uc $header ;
  8762. $s_fir->{$m_uid}->{NO_HEADER} = 1;
  8763. }
  8764. return if ( ! $headstr ) ;
  8765. my $size = $s_fir->{$m_uid}->{'RFC822.SIZE'} ;
  8766. my $flags = $s_fir->{$m_uid}->{'FLAGS'} ;
  8767. my $idate = $s_fir->{$m_uid}->{'INTERNALDATE'} ;
  8768. $size = length $headstr unless ( $size ) ;
  8769. my $m_md5 = md5_base64( $headstr ) ;
  8770. $mysync->{ debug } and myprint( "$side: uid $m_uid sig $m_md5 size $size idate $idate\n" ) ;
  8771. my $key ;
  8772. if ($skipsize) {
  8773. $key = "$m_md5";
  8774. }
  8775. else {
  8776. $key = "$m_md5:$size";
  8777. }
  8778. # 0 return code is used to identify duplicate message hash
  8779. return 0 if exists $s_hash->{"$key"};
  8780. $s_hash->{"$key"}{'5'} = $m_md5;
  8781. $s_hash->{"$key"}{'s'} = $size;
  8782. $s_hash->{"$key"}{'D'} = $idate;
  8783. $s_hash->{"$key"}{'F'} = $flags;
  8784. $s_hash->{"$key"}{'m'} = $m_uid;
  8785. return( 1 ) ;
  8786. }
  8787. sub header_construct
  8788. {
  8789. my( $head, $side, $m_uid ) = @_ ;
  8790. my $headstr ;
  8791. foreach my $h ( sort keys %{ $head } ) {
  8792. next if ( not ( exists $useheader{ uc $h } )
  8793. and ( not exists $useheader{ 'ALL' } )
  8794. ) ;
  8795. foreach my $val ( sort @{$head->{$h}} ) {
  8796. my $H = header_line_normalize( $h, $val ) ;
  8797. # show stuff in debug mode
  8798. $sync->{ debug } and myprint( "$side uid $m_uid header [$H]", "\n" ) ;
  8799. if ($skipheader and $H =~ m/$skipheader/xi) {
  8800. $sync->{ debug } and myprint( "$side uid $m_uid skipping header [$H]\n" ) ;
  8801. next ;
  8802. }
  8803. $headstr .= "$H" ;
  8804. }
  8805. }
  8806. return( $headstr ) ;
  8807. }
  8808. sub header_line_normalize
  8809. {
  8810. my( $header_key, $header_val ) = @_ ;
  8811. # no 8-bit data in headers !
  8812. $header_val =~ s/[\x80-\xff]/X/xog;
  8813. # change tabulations to space (Gmail bug on with "Received:" on multilines)
  8814. $header_val =~ s/\t/\ /xgo ;
  8815. # remove the first blanks ( dbmail bug? )
  8816. $header_val =~ s/^\s*//xo;
  8817. # remove the last blanks ( Gmail bug )
  8818. $header_val =~ s/\s*$//xo;
  8819. # remove successive blanks ( Mailenable does it )
  8820. $header_val =~ s/\s+/ /xgo;
  8821. # remove Message-Id value domain part ( Mailenable changes it )
  8822. if ( ( $messageidnodomain ) and ( 'MESSAGE-ID' eq uc $header_key ) ) { $header_val =~ s/^([^@]+).*$/$1/xo ; }
  8823. # and uppercase header line
  8824. # (dbmail and dovecot)
  8825. my $header_line = uc "$header_key: $header_val" ;
  8826. return( $header_line ) ;
  8827. }
  8828. sub tests_header_line_normalize
  8829. {
  8830. note( 'Entering tests_header_line_normalize()' ) ;
  8831. ok( ': ' eq header_line_normalize( q{}, q{} ), 'header_line_normalize: empty args' ) ;
  8832. ok( 'HHH: VVV' eq header_line_normalize( 'hhh', 'vvv' ), 'header_line_normalize: hhh vvv ' ) ;
  8833. ok( 'HHH: VVV' eq header_line_normalize( 'hhh', ' vvv' ), 'header_line_normalize: remove first blancs' ) ;
  8834. ok( 'HHH: AA BB CCC D' eq header_line_normalize( 'hhh', 'aa bb ccc d' ), 'header_line_normalize: remove succesive blanks' ) ;
  8835. ok( 'HHH: AA BB CCC' eq header_line_normalize( 'hhh', 'aa bb ccc ' ), 'header_line_normalize: remove last blanks' ) ;
  8836. ok( 'HHH: VVV XX YY' eq header_line_normalize( 'hhh', "vvv\t\txx\tyy" ), 'header_line_normalize: tabs' ) ;
  8837. ok( 'HHH: XABX' eq header_line_normalize( 'hhh', "\x80AB\xff" ), 'header_line_normalize: 8bit' ) ;
  8838. note( 'Leaving tests_header_line_normalize()' ) ;
  8839. return ;
  8840. }
  8841. sub tests_firstline
  8842. {
  8843. note( 'Entering tests_firstline()' ) ;
  8844. is( q{}, firstline( 'W/tmp/tests/noexist.txt' ), 'firstline: getting empty string from inexisting W/tmp/tests/noexist.txt' ) ;
  8845. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'firstline: mkpath W/tmp/tests/' ) ;
  8846. is( "blabla\n" , string_to_file( "blabla\n", 'W/tmp/tests/firstline.txt' ), 'firstline: put blabla in W/tmp/tests/firstline.txt' ) ;
  8847. is( 'blabla' , firstline( 'W/tmp/tests/firstline.txt' ), 'firstline: get blabla from W/tmp/tests/firstline.txt' ) ;
  8848. is( q{} , string_to_file( q{}, 'W/tmp/tests/firstline2.txt' ), 'firstline: put empty string in W/tmp/tests/firstline2.txt' ) ;
  8849. is( q{} , firstline( 'W/tmp/tests/firstline2.txt' ), 'firstline: get empty string from W/tmp/tests/firstline2.txt' ) ;
  8850. is( "\n" , string_to_file( "\n", 'W/tmp/tests/firstline3.txt' ), 'firstline: put CR in W/tmp/tests/firstline3.txt' ) ;
  8851. is( q{} , firstline( 'W/tmp/tests/firstline3.txt' ), 'firstline: get empty string from W/tmp/tests/firstline3.txt' ) ;
  8852. is( "blabla\nTiti\n" , string_to_file( "blabla\nTiti\n", 'W/tmp/tests/firstline4.txt' ), 'firstline: put blabla\nTiti\n in W/tmp/tests/firstline4.txt' ) ;
  8853. is( 'blabla' , firstline( 'W/tmp/tests/firstline4.txt' ), 'firstline: get blabla from W/tmp/tests/firstline4.txt' ) ;
  8854. note( 'Leaving tests_firstline()' ) ;
  8855. return ;
  8856. }
  8857. sub firstline
  8858. {
  8859. # extract the first line of a file (without \n)
  8860. # return empty string if error or empty string
  8861. my $file = shift ;
  8862. my $line ;
  8863. $line = nthline( $file, 1 ) ;
  8864. return $line ;
  8865. }
  8866. sub tests_secondline
  8867. {
  8868. note( 'Entering tests_secondline()' ) ;
  8869. is( q{}, secondline( 'W/tmp/tests/noexist.txt' ), 'secondline: getting empty string from inexisting W/tmp/tests/noexist.txt' ) ;
  8870. is( q{}, secondline( 'W/tmp/tests/noexist.txt', 2 ), 'secondline: 2nd getting empty string from inexisting W/tmp/tests/noexist.txt' ) ;
  8871. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'secondline: mkpath W/tmp/tests/' ) ;
  8872. is( "L1\nL2\nL3\nL4\n" , string_to_file( "L1\nL2\nL3\nL4\n", 'W/tmp/tests/secondline.txt' ), 'secondline: put L1\nL2\nL3\nL4\n in W/tmp/tests/secondline.txt' ) ;
  8873. is( 'L2' , secondline( 'W/tmp/tests/secondline.txt' ), 'secondline: get L2 from W/tmp/tests/secondline.txt' ) ;
  8874. note( 'Leaving tests_secondline()' ) ;
  8875. return ;
  8876. }
  8877. sub secondline
  8878. {
  8879. # extract the second line of a file (without \n)
  8880. # return empty string if error or empty string
  8881. my $file = shift ;
  8882. my $line ;
  8883. $line = nthline( $file, 2 ) ;
  8884. return $line ;
  8885. }
  8886. sub tests_nthline
  8887. {
  8888. note( 'Entering tests_nthline()' ) ;
  8889. is( q{}, nthline( 'W/tmp/tests/noexist.txt' ), 'nthline: getting empty string from inexisting W/tmp/tests/noexist.txt' ) ;
  8890. is( q{}, nthline( 'W/tmp/tests/noexist.txt', 2 ), 'nthline: 2nd getting empty string from inexisting W/tmp/tests/noexist.txt' ) ;
  8891. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'nthline: mkpath W/tmp/tests/' ) ;
  8892. is( "L1\nL2\nL3\nL4\n" , string_to_file( "L1\nL2\nL3\nL4\n", 'W/tmp/tests/nthline.txt' ), 'nthline: put L1\nL2\nL3\nL4\n in W/tmp/tests/nthline.txt' ) ;
  8893. is( 'L3' , nthline( 'W/tmp/tests/nthline.txt', 3 ), 'nthline: get L3 from W/tmp/tests/nthline.txt' ) ;
  8894. note( 'Leaving tests_nthline()' ) ;
  8895. return ;
  8896. }
  8897. sub nthline
  8898. {
  8899. # extract the nth line of a file (without \n)
  8900. # return empty string if error or empty string
  8901. my $file = shift ;
  8902. my $num = shift ;
  8903. if ( ! all_defined( $file, $num ) ) { return q{} ; }
  8904. my $line ;
  8905. $line = ( file_to_array( $file ) )[$num - 1] ;
  8906. if ( ! defined $line )
  8907. {
  8908. return q{} ;
  8909. }
  8910. else
  8911. {
  8912. chomp $line ;
  8913. return $line ;
  8914. }
  8915. }
  8916. # Should be unit tested and then be used by file_to_string, refactoring file_to_string
  8917. sub file_to_array
  8918. {
  8919. my( $file ) = shift ;
  8920. my @string ;
  8921. open my $FILE, '<', $file or do {
  8922. myprint( "Error reading file $file : $OS_ERROR\n" ) ;
  8923. return ;
  8924. } ;
  8925. @string = <$FILE> ;
  8926. close $FILE ;
  8927. return( @string ) ;
  8928. }
  8929. sub tests_file_to_string
  8930. {
  8931. note( 'Entering tests_file_to_string()' ) ;
  8932. is( undef, file_to_string( ), 'file_to_string: no args => undef' ) ;
  8933. is( undef, file_to_string( '/noexist' ), 'file_to_string: /noexist => undef' ) ;
  8934. is( undef, file_to_string( '/' ), 'file_to_string: reading a directory => undef' ) ;
  8935. ok( file_to_string( $PROGRAM_NAME ), 'file_to_string: reading myself' ) ;
  8936. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'file_to_string: mkpath W/tmp/tests/' ) ;
  8937. is( 'lilili', string_to_file( 'lilili', 'W/tmp/tests/canbewritten' ), 'file_to_string: string_to_file filling W/tmp/tests/canbewritten with lilili' ) ;
  8938. is( 'lilili', file_to_string( 'W/tmp/tests/canbewritten' ), 'file_to_string: reading W/tmp/tests/canbewritten is lilili' ) ;
  8939. is( q{}, string_to_file( q{}, 'W/tmp/tests/empty' ), 'file_to_string: string_to_file filling W/tmp/tests/empty with empty string' ) ;
  8940. is( q{}, file_to_string( 'W/tmp/tests/empty' ), 'file_to_string: reading W/tmp/tests/empty is empty' ) ;
  8941. note( 'Leaving tests_file_to_string()' ) ;
  8942. return ;
  8943. }
  8944. sub file_to_string
  8945. {
  8946. my $file = shift ;
  8947. if ( ! $file ) { return ; }
  8948. if ( ! -e $file ) { return ; }
  8949. if ( ! -f $file ) { return ; }
  8950. if ( ! -r $file ) { return ; }
  8951. my @string ;
  8952. if ( open my $FILE, '<', $file ) {
  8953. @string = <$FILE> ;
  8954. close $FILE ;
  8955. return( join q{}, @string ) ;
  8956. }else{
  8957. myprint( "Error reading file $file : $OS_ERROR\n" ) ;
  8958. return ;
  8959. }
  8960. }
  8961. sub tests_string_to_file
  8962. {
  8963. note( 'Entering tests_string_to_file()' ) ;
  8964. is( undef, string_to_file( ), 'string_to_file: no args => undef' ) ;
  8965. is( undef, string_to_file( 'lalala' ), 'string_to_file: one arg => undef' ) ;
  8966. is( undef, string_to_file( 'lalala', '.' ), 'string_to_file: writing a directory => undef' ) ;
  8967. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'string_to_file: mkpath W/tmp/tests/' ) ;
  8968. is( 'lalala', string_to_file( 'lalala', 'W/tmp/tests/canbewritten' ), 'string_to_file: W/tmp/tests/canbewritten with lalala' ) ;
  8969. is( q{}, string_to_file( q{}, 'W/tmp/tests/empty' ), 'string_to_file: W/tmp/tests/empty with empty string' ) ;
  8970. SKIP: {
  8971. Readonly my $NB_UNX_tests_string_to_file => 1 ;
  8972. skip( 'Not on Unix non-root', $NB_UNX_tests_string_to_file ) if ('MSWin32' eq $OSNAME or '0' eq $EFFECTIVE_USER_ID ) ;
  8973. is( undef, string_to_file( 'lalala', '/cantouch' ), 'string_to_file: /cantouch denied => undef' ) ;
  8974. }
  8975. note( 'Leaving tests_string_to_file()' ) ;
  8976. return ;
  8977. }
  8978. sub string_to_file
  8979. {
  8980. my( $string, $file ) = @_ ;
  8981. if( ! defined $string ) { return ; }
  8982. if( ! defined $file ) { return ; }
  8983. if ( ! -e $file && ! -w dirname( $file ) ) {
  8984. myprint( "string_to_file: directory of $file is not writable\n" ) ;
  8985. return ;
  8986. }
  8987. if ( ! sysopen( FILE, $file, O_WRONLY|O_TRUNC|O_CREAT, 0600) ) {
  8988. myprint( "string_to_file: failure writing to $file with error: $OS_ERROR\n" ) ;
  8989. return ;
  8990. }
  8991. print FILE $string ;
  8992. close FILE ;
  8993. return $string ;
  8994. }
  8995. 0 and <<'MULTILINE_COMMENT' ;
  8996. This is a multiline comment.
  8997. Based on David Carter discussion, to do:
  8998. * Call parameters stay the same.
  8999. * Now always "return( $string, $error )". Descriptions below.
  9000. OK * Still capture STDOUT via "1> $output_tmpfile" to finish in $string and "return( $string, $error )"
  9001. OK * Now also capture STDERR via "2> $error_tmpfile" to finish in $error and "return( $string, $error )"
  9002. OK * in case of CHILD_ERROR, return( undef, $error )
  9003. and print $error, with folder/UID/maybeSubject context,
  9004. on console and at the end with the final error listing. Count this as a sync error.
  9005. * in case of good command, take final $string as is, unless void. In case $error with value then print it.
  9006. * in case of good command and final $string empty, consider it like CHILD_ERROR =>
  9007. return( undef, $error ) and print $error, with folder/UID/maybeSubject context,
  9008. on console and at the end with the final error listing. Count this as a sync error.
  9009. MULTILINE_COMMENT
  9010. # End of multiline comment.
  9011. sub pipemess
  9012. {
  9013. my ( $string, @commands ) = @_ ;
  9014. my $error = q{} ;
  9015. foreach my $command ( @commands ) {
  9016. my $input_tmpfile = "$sync->{ tmpdir }/imapsync_tmp_file.$PROCESS_ID.inp.txt" ;
  9017. my $output_tmpfile = "$sync->{ tmpdir }/imapsync_tmp_file.$PROCESS_ID.out.txt" ;
  9018. my $error_tmpfile = "$sync->{ tmpdir }/imapsync_tmp_file.$PROCESS_ID.err.txt" ;
  9019. string_to_file( $string, $input_tmpfile ) ;
  9020. ` $command < $input_tmpfile 1> $output_tmpfile 2> $error_tmpfile ` ;
  9021. my $is_command_ko = $CHILD_ERROR ;
  9022. my $error_cmd = file_to_string( $error_tmpfile ) ;
  9023. chomp( $error_cmd ) ;
  9024. $string = file_to_string( $output_tmpfile ) ;
  9025. my $string_len = length( $string ) ;
  9026. unlink $input_tmpfile, $output_tmpfile, $error_tmpfile ;
  9027. if ( $is_command_ko or ( ! $string_len ) ) {
  9028. my $cmd_exit_value = $CHILD_ERROR >> 8 ;
  9029. my $cmd_end_signal = $CHILD_ERROR & 127 ;
  9030. my $signal_log = ( $cmd_end_signal ) ? " signal $cmd_end_signal and" : q{} ;
  9031. 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} ;
  9032. myprint( $error_log ) ;
  9033. if ( wantarray ) {
  9034. return @{ [ undef, $error_log ] }
  9035. }else{
  9036. return ;
  9037. }
  9038. }
  9039. if ( $error_cmd ) {
  9040. $error .= qq{STDERR of --pipemess "$command": $error_cmd\n} ;
  9041. myprint( qq{STDERR of --pipemess "$command": $error_cmd\n} ) ;
  9042. }
  9043. }
  9044. #myprint( "[$string]\n" ) ;
  9045. if ( wantarray ) {
  9046. return ( $string, $error ) ;
  9047. }else{
  9048. return $string ;
  9049. }
  9050. }
  9051. sub tests_pipemess
  9052. {
  9053. note( 'Entering tests_pipemess()' ) ;
  9054. SKIP: {
  9055. Readonly my $NB_WIN_tests_pipemess => 3 ;
  9056. skip( 'Not on MSWin32', $NB_WIN_tests_pipemess ) if ('MSWin32' ne $OSNAME) ;
  9057. # Windows
  9058. # "type" command does not accept redirection of STDIN with <
  9059. # "sort" does
  9060. ok( "nochange\n" eq pipemess( 'nochange', 'sort' ), 'pipemess: nearly no change by sort' ) ;
  9061. ok( "nochange2\n" eq pipemess( 'nochange2', qw( sort sort ) ), 'pipemess: nearly no change by sort,sort' ) ;
  9062. # command not found
  9063. #diag( 'Warning and failure about cacaprout are on purpose' ) ;
  9064. ok( ! defined( pipemess( q{}, 'cacaprout' ) ), 'pipemess: command not found' ) ;
  9065. } ;
  9066. my ( $stringT, $errorT ) ;
  9067. SKIP: {
  9068. Readonly my $NB_UNX_tests_pipemess => 25 ;
  9069. skip( 'Not on Unix', $NB_UNX_tests_pipemess ) if ('MSWin32' eq $OSNAME) ;
  9070. # Unix
  9071. ok( 'nochange' eq pipemess( 'nochange', 'cat' ), 'pipemess: no change by cat' ) ;
  9072. ok( 'nochange2' eq pipemess( 'nochange2', 'cat', 'cat' ), 'pipemess: no change by cat,cat' ) ;
  9073. ok( " 1\tnumberize\n" eq pipemess( "numberize\n", 'cat -n' ), 'pipemess: numberize by cat -n' ) ;
  9074. ok( " 1\tnumberize\n 2\tnumberize\n" eq pipemess( "numberize\nnumberize\n", 'cat -n' ), 'pipemess: numberize by cat -n' ) ;
  9075. ok( "A\nB\nC\n" eq pipemess( "A\nC\nB\n", 'sort' ), 'pipemess: sort' ) ;
  9076. # command not found
  9077. #diag( 'Warning and failure about cacaprout are on purpose' ) ;
  9078. is( undef, pipemess( q{}, 'cacaprout' ), 'pipemess: command not found' ) ;
  9079. # success with true but no output at all
  9080. is( undef, pipemess( q{blabla}, 'true' ), 'pipemess: true but no output' ) ;
  9081. # failure with false and no output at all
  9082. is( undef, pipemess( q{blabla}, 'false' ), 'pipemess: false and no output' ) ;
  9083. # Failure since pipemess is not a real pipe, so first cat wait for standard input
  9084. is( q{blabla}, pipemess( q{blabla}, '( cat|cat ) ' ), 'pipemess: ok by ( cat|cat )' ) ;
  9085. ( $stringT, $errorT ) = pipemess( 'nochange', 'cat' ) ;
  9086. is( $stringT, 'nochange', 'pipemess: list context, no change by cat, string' ) ;
  9087. is( $errorT, q{}, 'pipemess: list context, no change by cat, no error' ) ;
  9088. ( $stringT, $errorT ) = pipemess( 'dontcare', 'true' ) ;
  9089. is( $stringT, undef, 'pipemess: list context, true but no output, string' ) ;
  9090. 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' ) ;
  9091. ( $stringT, $errorT ) = pipemess( 'dontcare', 'false' ) ;
  9092. is( $stringT, undef, 'pipemess: list context, false and no output, string' ) ;
  9093. like( $errorT, qr{\QFailure: --pipemess command "false" ended with "0" characters exit value "1" and STDERR ""\E}xm,
  9094. 'pipemess: list context, false and no output, error' ) ;
  9095. ( $stringT, $errorT ) = pipemess( 'dontcare', '/bin/echo -n blablabla' ) ;
  9096. is( $stringT, q{blablabla}, 'pipemess: list context, "echo -n blablabla", string' ) ;
  9097. is( $errorT, q{}, 'pipemess: list context, "echo blablabla", error' ) ;
  9098. ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo -n blablabla 3>&1 1>&2 2>&3 )' ) ;
  9099. is( $stringT, undef, 'pipemess: list context, "no output STDERR blablabla", string' ) ;
  9100. like( $errorT, qr{blablabla"}xm, 'pipemess: list context, "no output STDERR blablabla", error' ) ;
  9101. ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo -n blablabla 3>&1 1>&2 2>&3 )', 'false' ) ;
  9102. is( $stringT, undef, 'pipemess: list context, "no output STDERR blablabla then false", string' ) ;
  9103. like( $errorT, qr{blablabla"}xm, 'pipemess: list context, "no output STDERR blablabla then false", error' ) ;
  9104. ( $stringT, $errorT ) = pipemess( 'dontcare', 'false', '( echo -n blablabla 3>&1 1>&2 2>&3 )' ) ;
  9105. is( $stringT, undef, 'pipemess: list context, "false then STDERR blablabla", string' ) ;
  9106. like( $errorT, qr{\QFailure: --pipemess command "false" ended with "0" characters exit value "1" and STDERR ""\E}xm,
  9107. 'pipemess: list context, "false then STDERR blablabla", error' ) ;
  9108. ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo rrrrr ; echo -n error_blablabla 3>&1 1>&2 2>&3 )' ) ;
  9109. like( $stringT, qr{rrrrr}xm, 'pipemess: list context, "STDOUT rrrrr STDERR error_blablabla", string' ) ;
  9110. like( $errorT, qr{STDERR.*error_blablabla}xm, 'pipemess: list context, "STDOUT rrrrr STDERR error_blablabla", error' ) ;
  9111. }
  9112. ( $stringT, $errorT ) = pipemess( 'dontcare', 'cacaprout' ) ;
  9113. is( $stringT, undef, 'pipemess: list context, cacaprout not found, string' ) ;
  9114. like( $errorT, qr{\QFailure: --pipemess command "cacaprout" ended with "0" characters exit value\E}xm,
  9115. 'pipemess: list context, cacaprout not found, error' ) ;
  9116. note( 'Leaving tests_pipemess()' ) ;
  9117. return ;
  9118. }
  9119. sub tests_is_a_release_number
  9120. {
  9121. note( 'Entering tests_is_a_release_number()' ) ;
  9122. is( undef, is_a_release_number( ), 'is_a_release_number: no args => undef' ) ;
  9123. ok( is_a_release_number( $RELEASE_NUMBER_EXAMPLE_1 ), 'is_a_release_number 1.351' ) ;
  9124. ok( is_a_release_number( $RELEASE_NUMBER_EXAMPLE_2 ), 'is_a_release_number 42.4242' ) ;
  9125. ok( is_a_release_number( imapsync_version( $sync ) ), 'is_a_release_number imapsync_version( )' ) ;
  9126. ok( ! is_a_release_number( 'blabla' ), '! is_a_release_number blabla' ) ;
  9127. note( 'Leaving tests_is_a_release_number()' ) ;
  9128. return ;
  9129. }
  9130. sub is_a_release_number
  9131. {
  9132. my $number = shift ;
  9133. if ( ! defined $number ) { return ; }
  9134. return( $number =~ m{^\d+\.\d+$}xo ) ;
  9135. }
  9136. sub imapsync_version_public
  9137. {
  9138. my $local_version = imapsync_version( $sync ) ;
  9139. my $imapsync_basename = imapsync_basename( ) ;
  9140. my $agent_info = "$OSNAME system, perl "
  9141. . mysprintf( '%vd', $PERL_VERSION)
  9142. . ", Mail::IMAPClient $Mail::IMAPClient::VERSION"
  9143. . " $imapsync_basename" ;
  9144. my $sock = IO::Socket::INET->new(
  9145. PeerAddr => 'imapsync.lamiral.info',
  9146. PeerPort => 80,
  9147. Proto => 'tcp',
  9148. ) ;
  9149. return( 'unknown' ) if not $sock ;
  9150. print $sock
  9151. "GET /prj/imapsync/VERSION HTTP/1.0\r\n",
  9152. "User-Agent: imapsync/$local_version ($agent_info)\r\n",
  9153. "Host: ks.lamiral.info\r\n\r\n" ;
  9154. my @line = <$sock> ;
  9155. close $sock ;
  9156. my $last_release = $line[$LAST] ;
  9157. chomp $last_release ;
  9158. return( $last_release ) ;
  9159. }
  9160. sub not_long_imapsync_version_public
  9161. {
  9162. #myprint( "Entering not_long_imapsync_version_public\n" ) ;
  9163. my $fake = shift ;
  9164. if ( $fake ) { return $fake }
  9165. my $val ;
  9166. # Doesn't work with gethostbyname (see perlipc)
  9167. #local $SIG{ALRM} = sub { die "alarm\n" } ;
  9168. if ('MSWin32' eq $OSNAME) {
  9169. local $SIG{ALRM} = sub { die "alarm\n" } ;
  9170. }else{
  9171. POSIX::sigaction(SIGALRM,
  9172. POSIX::SigAction->new(sub { croak 'alarm' } ) )
  9173. or myprint( "Error setting SIGALRM handler: $OS_ERROR\n" ) ;
  9174. }
  9175. my $ret = eval {
  9176. alarm 3 ;
  9177. {
  9178. $val = imapsync_version_public( ) ;
  9179. #sleep 4 ;
  9180. #myprint( "End of imapsync_version_public\n" ) ;
  9181. }
  9182. alarm 0 ;
  9183. 1 ;
  9184. } ;
  9185. #myprint( "eval [$ret]\n" ) ;
  9186. if ( ( not $ret ) or $EVAL_ERROR ) {
  9187. #myprint( "$EVAL_ERROR" ) ;
  9188. if ($EVAL_ERROR =~ /alarm/) {
  9189. # timed out
  9190. return('timeout') ;
  9191. }else{
  9192. alarm 0 ;
  9193. return( 'unknown' ) ; # propagate unexpected errors
  9194. }
  9195. }else {
  9196. # Good!
  9197. return( $val ) ;
  9198. }
  9199. }
  9200. sub tests_not_long_imapsync_version_public
  9201. {
  9202. note( 'Entering tests_not_long_imapsync_version_public()' ) ;
  9203. is( 1, is_a_release_number( not_long_imapsync_version_public( ) ),
  9204. 'not_long_imapsync_version_public: public release is a number' ) ;
  9205. note( 'Leaving tests_not_long_imapsync_version_public()' ) ;
  9206. return ;
  9207. }
  9208. sub check_last_release
  9209. {
  9210. my $fake = shift ;
  9211. my $public_release = not_long_imapsync_version_public( $fake ) ;
  9212. $sync->{ debug } and myprint( "check_last_release: [$public_release]\n" ) ;
  9213. my $inline_help_when_on = '( Use --noreleasecheck to avoid this release check. )' ;
  9214. if ( $public_release eq 'unknown' ) {
  9215. return( 'Imapsync public release is unknown.' . $inline_help_when_on ) ;
  9216. }
  9217. if ( $public_release eq 'timeout' ) {
  9218. return( 'Imapsync public release is unknown (timeout).' . $inline_help_when_on ) ;
  9219. }
  9220. if ( ! is_a_release_number( $public_release ) ) {
  9221. return( "Imapsync public release is unknown ($public_release)." . $inline_help_when_on ) ;
  9222. }
  9223. my $imapsync_here = imapsync_version( $sync ) ;
  9224. if ( $public_release > $imapsync_here ) {
  9225. return( 'This imapsync is not up to date. ' . "( local $imapsync_here < official $public_release )" . $inline_help_when_on ) ;
  9226. }else{
  9227. return( 'This imapsync is up to date. ' . "( local $imapsync_here >= official $public_release )" . $inline_help_when_on ) ;
  9228. }
  9229. return( 'really unknown' ) ; # Should never arrive here
  9230. }
  9231. sub tests_check_last_release
  9232. {
  9233. note( 'Entering tests_check_last_release()' ) ;
  9234. diag( check_last_release( 1.1 ) ) ;
  9235. # \Q \E here to avoid putting \ before each space
  9236. like( check_last_release( 1.1 ), qr/\Qis up to date\E/mxs, 'check_last_release: up to date' ) ;
  9237. like( check_last_release( 1.1 ), qr/1\.1/mxs, 'check_last_release: up to date, include number' ) ;
  9238. diag( check_last_release( 999.999 ) ) ;
  9239. like( check_last_release( 999.999 ), qr/\Qnot up to date\E/mxs, 'check_last_release: not up to date' ) ;
  9240. like( check_last_release( 999.999 ), qr/999\.999/mxs, 'check_last_release: not up to date, include number' ) ;
  9241. like( check_last_release( 'unknown' ), qr/\QImapsync public release is unknown\E/mxs, 'check_last_release: unknown' ) ;
  9242. like( check_last_release( 'timeout' ), qr/\QImapsync public release is unknown (timeout)\E/mxs, 'check_last_release: timeout' ) ;
  9243. like( check_last_release( 'lalala' ), qr/\QImapsync public release is unknown (lalala)\E/mxs, 'check_last_release: lalala' ) ;
  9244. diag( check_last_release( ) ) ;
  9245. note( 'Leaving tests_check_last_release()' ) ;
  9246. return ;
  9247. }
  9248. sub imapsync_version
  9249. {
  9250. my $mysync = shift ;
  9251. my $rcs = $mysync->{rcs} ;
  9252. my $version ;
  9253. $version = version_from_rcs( $rcs ) ;
  9254. return( $version ) ;
  9255. }
  9256. sub tests_version_from_rcs
  9257. {
  9258. note( 'Entering tests_version_from_rcs()' ) ;
  9259. is( undef, version_from_rcs( ), 'version_from_rcs: no args => UNKNOWN' ) ;
  9260. 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' ) ;
  9261. is( 'UNKNOWN', version_from_rcs( 1.831 ), 'version_from_rcs: 1.831 => UNKNOWN' ) ;
  9262. note( 'Leaving tests_version_from_rcs()' ) ;
  9263. return ;
  9264. }
  9265. sub version_from_rcs
  9266. {
  9267. my $rcs = shift ;
  9268. if ( ! $rcs ) { return ; }
  9269. my $version = 'UNKNOWN' ;
  9270. if ( $rcs =~ m{,v\s+(\d+\.\d+)}mxso ) {
  9271. $version = $1
  9272. }
  9273. return( $version ) ;
  9274. }
  9275. sub tests_imapsync_basename
  9276. {
  9277. note( 'Entering tests_imapsync_basename()' ) ;
  9278. ok( imapsync_basename() =~ m/imapsync/, 'imapsync_basename: match imapsync');
  9279. ok( 'blabla' ne imapsync_basename(), 'imapsync_basename: do not equal blabla');
  9280. note( 'Leaving tests_imapsync_basename()' ) ;
  9281. return ;
  9282. }
  9283. sub imapsync_basename
  9284. {
  9285. return basename( $PROGRAM_NAME ) ;
  9286. }
  9287. sub localhost_info
  9288. {
  9289. my $mysync = shift ;
  9290. my( $infos ) = join( q{},
  9291. "Here is imapsync ", imapsync_version( $mysync ),
  9292. " on host " . hostname(),
  9293. ", a $OSNAME system with ",
  9294. ram_memory_info( ),
  9295. "\n",
  9296. 'with Perl ',
  9297. mysprintf( '%vd ', $PERL_VERSION),
  9298. "and Mail::IMAPClient $Mail::IMAPClient::VERSION",
  9299. ) ;
  9300. return( $infos ) ;
  9301. }
  9302. sub tests_cpu_number
  9303. {
  9304. note( 'Entering tests_cpu_number()' ) ;
  9305. is( 1, is_an_integer( cpu_number( ) ), "cpu_number: is_an_integer" ) ;
  9306. ok( 1 <= cpu_number( ), "cpu_number: 1 or more" ) ;
  9307. is( 1, cpu_number( 1 ), "cpu_number: 1 => 1" ) ;
  9308. is( 1, cpu_number( $MINUS_ONE ), "cpu_number: -1 => 1" ) ;
  9309. is( 1, cpu_number( 'lalala' ), "cpu_number: lalala => 1" ) ;
  9310. is( $NUMBER_42, cpu_number( $NUMBER_42 ), "cpu_number: $NUMBER_42 => $NUMBER_42" ) ;
  9311. note( 'Leaving tests_cpu_number()' ) ;
  9312. return ;
  9313. }
  9314. sub cpu_number
  9315. {
  9316. my $cpu_number_forced = shift ;
  9317. # Well, here 1 is better than 0 or undef
  9318. my $cpu_number = 1 ; # Default value, erased if better found
  9319. my @cpuinfo ;
  9320. if ( $ENV{"NUMBER_OF_PROCESSORS"} ) {
  9321. # might be under a Windows system
  9322. $cpu_number = $ENV{"NUMBER_OF_PROCESSORS"} ;
  9323. $sync->{ debug } and myprint( "Number of processors found by env var NUMBER_OF_PROCESSORS: $cpu_number\n" ) ;
  9324. }elsif ( 'darwin' eq $OSNAME or 'freebsd' eq $OSNAME ) {
  9325. $cpu_number = backtick( "sysctl -n hw.ncpu" ) ;
  9326. chomp( $cpu_number ) ;
  9327. $sync->{ debug } and myprint( "Number of processors found by cmd 'sysctl -n hw.ncpu': $cpu_number\n" ) ;
  9328. }elsif ( ! -e '/proc/cpuinfo' ) {
  9329. $sync->{ debug } and myprint( "Number of processors not found so I might assume there is only 1\n" ) ;
  9330. $cpu_number = 1 ;
  9331. }elsif( @cpuinfo = file_to_array( '/proc/cpuinfo' ) ) {
  9332. $cpu_number = grep { /^processor/mxs } @cpuinfo ;
  9333. $sync->{ debug } and myprint( "Number of processors found via /proc/cpuinfo: $cpu_number\n" ) ;
  9334. }
  9335. if ( defined $cpu_number_forced ) {
  9336. $cpu_number = $cpu_number_forced ;
  9337. }
  9338. return( integer_or_1( $cpu_number ) ) ;
  9339. }
  9340. sub tests_integer_or_1
  9341. {
  9342. note( 'Entering tests_integer_or_1()' ) ;
  9343. is( 1, integer_or_1( ), 'integer_or_1: no args => 1' ) ;
  9344. is( 1, integer_or_1( undef ), 'integer_or_1: undef => 1' ) ;
  9345. is( $NUMBER_10, integer_or_1( $NUMBER_10 ), 'integer_or_1: 10 => 10' ) ;
  9346. is( 1, integer_or_1( q{} ), 'integer_or_1: empty string => 1' ) ;
  9347. is( 1, integer_or_1( 'lalala' ), 'integer_or_1: lalala => 1' ) ;
  9348. note( 'Leaving tests_integer_or_1()' ) ;
  9349. return ;
  9350. }
  9351. sub integer_or_1
  9352. {
  9353. my $number = shift ;
  9354. if ( is_an_integer( $number ) ) {
  9355. return $number ;
  9356. }
  9357. # else
  9358. return 1 ;
  9359. }
  9360. sub tests_is_an_integer
  9361. {
  9362. note( 'Entering tests_is_an_integer()' ) ;
  9363. is( undef, is_an_integer( ), 'is_an_integer: no args => undef ' ) ;
  9364. ok( is_an_integer( 1 ), 'is_an_integer: 1 => yes ') ;
  9365. ok( is_an_integer( $NUMBER_42 ), 'is_an_integer: 42 => yes ') ;
  9366. ok( is_an_integer( "$NUMBER_42" ), 'is_an_integer: "$NUMBER_42" => yes ') ;
  9367. ok( is_an_integer( '42' ), 'is_an_integer: "42" => yes ') ;
  9368. ok( is_an_integer( $NUMBER_104_857_600 ), 'is_an_integer: 104_857_600 => yes') ;
  9369. ok( is_an_integer( "$NUMBER_104_857_600" ), 'is_an_integer: "$NUMBER_104_857_600" => yes') ;
  9370. ok( is_an_integer( '104857600' ), 'is_an_integer: 104857600 => yes') ;
  9371. ok( ! is_an_integer( 'blabla' ), 'is_an_integer: blabla => no' ) ;
  9372. ok( ! is_an_integer( q{} ), 'is_an_integer: empty string => no' ) ;
  9373. note( 'Leaving tests_is_an_integer()' ) ;
  9374. return ;
  9375. }
  9376. sub is_an_integer
  9377. {
  9378. my $number = shift ;
  9379. if ( ! defined $number ) { return ; }
  9380. return( $number =~ m{^\d+$}xo ) ;
  9381. }
  9382. sub tests_loadavg
  9383. {
  9384. note( 'Entering tests_loadavg()' ) ;
  9385. SKIP: {
  9386. skip( 'Tests for darwin', 2 ) if ('darwin' ne $OSNAME) ;
  9387. is( undef, loadavg( '/noexist' ), 'loadavg: /noexist => undef' ) ;
  9388. is_deeply( [ '0.11', '0.22', '0.33' ],
  9389. [ loadavg( 'W/t/loadavg.out' ) ],
  9390. 'loadavg W/t/loadavg.out => 0.11 0.22 0.33' ) ;
  9391. } ;
  9392. SKIP: {
  9393. skip( 'Tests for linux', 3 ) if ('linux' ne $OSNAME) ;
  9394. is( undef, loadavg( '/noexist' ), 'loadavg: /noexist => undef' ) ;
  9395. ok( loadavg( ), 'loadavg: no args' ) ;
  9396. is_deeply( [ '0.39', '0.30', '0.37', '1/602' ],
  9397. [ loadavg( '0.39 0.30 0.37 1/602 6073' ) ],
  9398. 'loadavg 0.39 0.30 0.37 1/602 6073 => [0.39, 0.30, 0.37, 1/602]' ) ;
  9399. } ;
  9400. SKIP: {
  9401. skip( 'Tests for Windows', 1 ) if ('MSWin32' ne $OSNAME) ;
  9402. is_deeply( [ 0 ],
  9403. [ loadavg( ) ],
  9404. 'loadavg on MSWin32 => 0' ) ;
  9405. } ;
  9406. note( 'Leaving tests_loadavg()' ) ;
  9407. return ;
  9408. }
  9409. sub loadavg
  9410. {
  9411. if ( 'linux' eq $OSNAME ) {
  9412. return ( loadavg_linux( @ARG ) ) ;
  9413. }
  9414. if ( 'freebsd' eq $OSNAME ) {
  9415. return ( loadavg_freebsd( @ARG ) ) ;
  9416. }
  9417. if ( 'darwin' eq $OSNAME ) {
  9418. return ( loadavg_darwin( @ARG ) ) ;
  9419. }
  9420. if ( 'MSWin32' eq $OSNAME ) {
  9421. return ( loadavg_windows( @ARG ) ) ;
  9422. }
  9423. return( 'unknown' ) ;
  9424. }
  9425. sub loadavg_linux
  9426. {
  9427. my $line = shift ;
  9428. if ( ! $line ) {
  9429. $line = firstline( '/proc/loadavg' ) or return ;
  9430. }
  9431. my ( $avg_1_min, $avg_5_min, $avg_15_min, $current_runs ) = split /\s/mxs, $line ;
  9432. if ( all_defined( $avg_1_min, $avg_5_min, $avg_15_min ) ) {
  9433. $sync->{ debug } and myprint( "System load: $avg_1_min $avg_5_min $avg_15_min $current_runs\n" ) ;
  9434. return ( $avg_1_min, $avg_5_min, $avg_15_min, $current_runs ) ;
  9435. }
  9436. return ;
  9437. }
  9438. sub loadavg_freebsd
  9439. {
  9440. my $file = shift ;
  9441. # Example of output of command "sysctl vm.loadavg":
  9442. # vm.loadavg: { 0.15 0.08 0.08 }
  9443. my $loadavg ;
  9444. if ( ! defined $file ) {
  9445. eval {
  9446. $loadavg = `/sbin/sysctl vm.loadavg` ;
  9447. #myprint( "LOADAVG FREEBSD: $loadavg\n" ) ;
  9448. } ;
  9449. if ( $EVAL_ERROR ) { myprint( "[$EVAL_ERROR]\n" ) ; return ; }
  9450. }else{
  9451. $loadavg = firstline( $file ) or return ;
  9452. }
  9453. my ( $avg_1_min, $avg_5_min, $avg_15_min )
  9454. = $loadavg =~ /vm\.loadavg\s*[:=]\s*\{?\s*(\d+\.?\d*)\s+(\d+\.?\d*)\s+(\d+\.?\d*)/mxs ;
  9455. $sync->{ debug } and myprint( "System load: $avg_1_min $avg_5_min $avg_15_min\n" ) ;
  9456. return ( $avg_1_min, $avg_5_min, $avg_15_min ) ;
  9457. }
  9458. sub loadavg_darwin
  9459. {
  9460. my $file = shift ;
  9461. # Example of output of command "sysctl vm.loadavg":
  9462. # vm.loadavg: { 0.15 0.08 0.08 }
  9463. my $loadavg ;
  9464. if ( ! defined $file ) {
  9465. eval {
  9466. $loadavg = `/usr/sbin/sysctl vm.loadavg` ;
  9467. #myprint( "LOADAVG DARWIN: $loadavg\n" ) ;
  9468. } ;
  9469. if ( $EVAL_ERROR ) { myprint( "[$EVAL_ERROR]\n" ) ; return ; }
  9470. }else{
  9471. $loadavg = firstline( $file ) or return ;
  9472. }
  9473. my ( $avg_1_min, $avg_5_min, $avg_15_min )
  9474. = $loadavg =~ /vm\.loadavg\s*[:=]\s*\{?\s*(\d+\.?\d*)\s+(\d+\.?\d*)\s+(\d+\.?\d*)/mxs ;
  9475. $sync->{ debug } and myprint( "System load: $avg_1_min $avg_5_min $avg_15_min\n" ) ;
  9476. return ( $avg_1_min, $avg_5_min, $avg_15_min ) ;
  9477. }
  9478. sub loadavg_windows
  9479. {
  9480. my $file = shift ;
  9481. # Example of output of command "wmic cpu get loadpercentage":
  9482. # LoadPercentage
  9483. # 12
  9484. my $loadavg ;
  9485. if ( ! defined $file ) {
  9486. eval {
  9487. #$loadavg = `CMD wmic cpu get loadpercentage` ;
  9488. $loadavg = "LoadPercentage\n0\n" ;
  9489. #myprint( "LOADAVG WIN: $loadavg\n" ) ;
  9490. } ;
  9491. if ( $EVAL_ERROR ) { myprint( "[$EVAL_ERROR]\n" ) ; return ; }
  9492. }else{
  9493. $loadavg = file_to_string( $file ) or return ;
  9494. #myprint( "$loadavg" ) ;
  9495. }
  9496. $loadavg =~ /LoadPercentage\n(\d+)/xms ;
  9497. my $num = $1 ;
  9498. $num /= 100 ;
  9499. $sync->{ debug } and myprint( "System load: $num\n" ) ;
  9500. return ( $num ) ;
  9501. }
  9502. sub tests_load_and_delay
  9503. {
  9504. note( 'Entering tests_load_and_delay()' ) ;
  9505. is( undef, load_and_delay( ), 'load_and_delay: no args => undef ' ) ;
  9506. is( undef, load_and_delay( 1 ), 'load_and_delay: not 4 args => undef ' ) ;
  9507. is( undef, load_and_delay( 0, 1, 1, 1 ), 'load_and_delay: division per 0 => undef ' ) ;
  9508. is( 0, load_and_delay( 1, 1, 1, 1 ), 'load_and_delay: one core, loads are all 1 => ok ' ) ;
  9509. is( 0, load_and_delay( 1, 1, 1, 1, 'lalala' ), 'load_and_delay: five arguments is ok' ) ;
  9510. is( 0, load_and_delay( 2, 2, 2, 2 ), 'load_and_delay: two core, loads are all 2 => ok ' ) ;
  9511. is( 0, load_and_delay( 2, 2, 4, 5 ), 'load_and_delay: two core, load1m is 2 => ok ' ) ;
  9512. # Old behavior, rather strict
  9513. # is( 0, load_and_delay( 1, 0, 0, 0 ), 'load_and_delay: one core, load1m=0 load5m=0 load15m=0 => 0 ' ) ;
  9514. # is( 0, load_and_delay( 1, 0, 0, 2 ), 'load_and_delay: one core, load1m=0 load5m=0 load15m=2 => 0 ' ) ;
  9515. # is( 0, load_and_delay( 1, 0, 2, 0 ), 'load_and_delay: one core, load1m=0 load5m=2 load15m=0 => 0 ' ) ;
  9516. # is( 0, load_and_delay( 1, 0, 2, 2 ), 'load_and_delay: one core, load1m=0 load5m=2 load15m=2 => 0 ' ) ;
  9517. # is( 1, load_and_delay( 1, 2, 0, 0 ), 'load_and_delay: one core, load1m=2 load5m=0 load15m=0 => 1 ' ) ;
  9518. # is( 1, load_and_delay( 1, 2, 0, 2 ), 'load_and_delay: one core, load1m=2 load5m=0 load15m=2 => 1 ' ) ;
  9519. # is( 5, load_and_delay( 1, 2, 2, 0 ), 'load_and_delay: one core, load1m=2 load5m=2 load15m=0 => 5 ' ) ;
  9520. # is( 15, load_and_delay( 1, 2, 2, 2 ), 'load_and_delay: one core, load1m=2 load5m=2 load15m=2 => 15 ' ) ;
  9521. # is( 0, load_and_delay( 4, 0, 2, 2 ), 'load_and_delay: four core, load1m=0 load5m=2 load15m=2 => 0 ' ) ;
  9522. # is( 1, load_and_delay( 4, 8, 0, 0 ), 'load_and_delay: four core, load1m=2 load5m=0 load15m=0 => 1 ' ) ;
  9523. # is( 1, load_and_delay( 4, 8, 0, 2 ), 'load_and_delay: four core, load1m=2 load5m=0 load15m=2 => 1 ' ) ;
  9524. # is( 5, load_and_delay( 4, 8, 8, 0 ), 'load_and_delay: four core, load1m=2 load5m=2 load15m=0 => 5 ' ) ;
  9525. # is( 15, load_and_delay( 4, 8, 8, 8 ), 'load_and_delay: four core, load1m=2 load5m=2 load15m=2 => 15 ' ) ;
  9526. # New behavior, tolerate more load
  9527. is( 0, load_and_delay( 1, 0, 0, 0 ), 'load_and_delay: one core, load1m=0 load5m=0 load15m=0 => 0 ' ) ;
  9528. is( 0, load_and_delay( 1, 0, 0, 2 ), 'load_and_delay: one core, load1m=0 load5m=0 load15m=2 => 0 ' ) ;
  9529. is( 0, load_and_delay( 1, 0, 2, 0 ), 'load_and_delay: one core, load1m=0 load5m=2 load15m=0 => 0 ' ) ;
  9530. is( 0, load_and_delay( 1, 0, 2, 2 ), 'load_and_delay: one core, load1m=0 load5m=2 load15m=2 => 0 ' ) ;
  9531. is( 0, load_and_delay( 1, 2, 0, 0 ), 'load_and_delay: one core, load1m=2 load5m=0 load15m=0 => 1 ' ) ;
  9532. is( 0, load_and_delay( 1, 2, 0, 2 ), 'load_and_delay: one core, load1m=2 load5m=0 load15m=2 => 1 ' ) ;
  9533. is( 0, load_and_delay( 1, 2, 2, 0 ), 'load_and_delay: one core, load1m=2 load5m=2 load15m=0 => 5 ' ) ;
  9534. is( 0, load_and_delay( 1, 2, 2, 2 ), 'load_and_delay: one core, load1m=2 load5m=2 load15m=2 => 15 ' ) ;
  9535. is( 1, load_and_delay( 1, 4, 0, 0 ), 'load_and_delay: one core, load1m=4 load5m=0 load15m=0 => 1 ' ) ;
  9536. is( 1, load_and_delay( 1, 4, 0, 4 ), 'load_and_delay: one core, load1m=4 load5m=0 load15m=4 => 1 ' ) ;
  9537. is( 5, load_and_delay( 1, 4, 4, 0 ), 'load_and_delay: one core, load1m=4 load5m=4 load15m=0 => 5 ' ) ;
  9538. is( 15, load_and_delay( 1, 4, 4, 4 ), 'load_and_delay: one core, load1m=4 load5m=4 load15m=4 => 15 ' ) ;
  9539. is( 0, load_and_delay( 4, 0, 9, 9 ), 'load_and_delay: four core, load1m=0 load5m=9 load15m=9 => 0 ' ) ;
  9540. is( 1, load_and_delay( 4, 9, 0, 0 ), 'load_and_delay: four core, load1m=9 load5m=0 load15m=0 => 1 ' ) ;
  9541. is( 1, load_and_delay( 4, 9, 0, 9 ), 'load_and_delay: four core, load1m=9 load5m=0 load15m=9 => 1 ' ) ;
  9542. is( 5, load_and_delay( 4, 9, 9, 0 ), 'load_and_delay: four core, load1m=9 load5m=9 load15m=0 => 5 ' ) ;
  9543. is( 15, load_and_delay( 4, 9, 9, 9 ), 'load_and_delay: four core, load1m=9 load5m=9 load15m=9 => 15 ' ) ;
  9544. note( 'Leaving tests_load_and_delay()' ) ;
  9545. return ;
  9546. }
  9547. sub load_and_delay
  9548. {
  9549. # Basically return 0 if load is not heavy, ie <= 1 per processor
  9550. # Not enough arguments
  9551. if ( 4 > scalar @ARG ) { return ; }
  9552. my ( $cpu_num, $avg_1_min, $avg_5_min, $avg_15_min ) = @ARG ;
  9553. if ( 0 == $cpu_num ) { return ; }
  9554. # Let divide by number of cores
  9555. ( $avg_1_min, $avg_5_min, $avg_15_min ) = map { $_ / $cpu_num } ( $avg_1_min, $avg_5_min, $avg_15_min ) ;
  9556. # One of avg ok => ok, for now it is a OR
  9557. if ( $avg_1_min <= 2 ) { return 0 ; }
  9558. if ( $avg_5_min <= 2 ) { return 1 ; } # Retry in 1 minute
  9559. if ( $avg_15_min <= 2 ) { return 5 ; } # Retry in 5 minutes
  9560. return 15 ; # Retry in 15 minutes
  9561. }
  9562. sub ram_memory_info
  9563. {
  9564. # In GigaBytes so division by 1024 * 1024 * 1024
  9565. #
  9566. return(
  9567. sprintf( "%.1f/%.1f free GiB of RAM",
  9568. Sys::MemInfo::get("freemem") / ( $KIBI ** 3 ),
  9569. Sys::MemInfo::get("totalmem") / ( $KIBI ** 3 ),
  9570. )
  9571. ) ;
  9572. }
  9573. sub tests_memory_stress
  9574. {
  9575. note( 'Entering tests_memory_stress()' ) ;
  9576. is( undef, memory_stress( ), 'memory_stress: => undef' ) ;
  9577. note( 'Leaving tests_memory_stress()' ) ;
  9578. return ;
  9579. }
  9580. sub memory_stress
  9581. {
  9582. my $total_ram_in_MB = Sys::MemInfo::get("totalmem") / ( $KIBI * $KIBI ) ;
  9583. my $i = 1 ;
  9584. myprintf("Stress memory consumption before: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI ) ;
  9585. while ( $i < $total_ram_in_MB / 1.7 ) { $a .= "A" x 1000_000; $i++ } ;
  9586. myprintf("Stress memory consumption after: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI ) ;
  9587. return ;
  9588. }
  9589. sub tests_memory_consumption
  9590. {
  9591. note( 'Entering tests_memory_consumption()' ) ;
  9592. like( memory_consumption( ), qr{\d+}xms,'memory_consumption no args') ;
  9593. like( memory_consumption( 1 ), qr{\d+}xms,'memory_consumption 1') ;
  9594. like( memory_consumption( $PROCESS_ID ), qr{\d+}xms,"memory_consumption_of_pids $PROCESS_ID") ;
  9595. like( memory_consumption_ratio(), qr{\d+}xms, 'memory_consumption_ratio' ) ;
  9596. like( memory_consumption_ratio(1), qr{\d+}xms, 'memory_consumption_ratio 1' ) ;
  9597. like( memory_consumption_ratio(10), qr{\d+}xms, 'memory_consumption_ratio 10' ) ;
  9598. like( memory_consumption(), qr{\d+}xms, "memory_consumption\n" ) ;
  9599. note( 'Leaving tests_memory_consumption()' ) ;
  9600. return ;
  9601. }
  9602. sub memory_consumption
  9603. {
  9604. # memory consumed by imapsync until now in bytes
  9605. return( ( memory_consumption_of_pids( ) )[0] );
  9606. }
  9607. sub debugmemory
  9608. {
  9609. my $mysync = shift ;
  9610. if ( ! $mysync->{debugmemory} ) { return q{} ; }
  9611. my $precision = shift ;
  9612. return( mysprintf( "Memory consumption$precision: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI ) ) ;
  9613. }
  9614. sub memory_consumption_of_pids
  9615. {
  9616. my @pid = @_;
  9617. @pid = ( @pid ) ? @pid : ( $PROCESS_ID ) ;
  9618. $sync->{ debug } and myprint( "memory_consumption_of_pids PIDs: @pid\n" ) ;
  9619. my @val ;
  9620. if ( ( 'MSWin32' eq $OSNAME ) or ( 'cygwin' eq $OSNAME ) ) {
  9621. @val = memory_consumption_of_pids_win32( @pid ) ;
  9622. }else{
  9623. # Unix
  9624. my @ps = qx{ ps -o vsz -p @pid } ;
  9625. #myprint( "ps: @ps" ) ;
  9626. # Use IPC::Open3 from perlcrit -3
  9627. # It stalls on Darwin, don't understand why!
  9628. #my @ps = backtick( "ps -o vsz -p @pid" ) ;
  9629. #myprint( "ps: @ps" ) ;
  9630. shift @ps; # First line is column name "VSZ"
  9631. chomp @ps;
  9632. # convert to octets
  9633. @val = map { $_ * $KIBI } @ps ;
  9634. }
  9635. $sync->{ debug } and myprint( "@val\n" ) ;
  9636. return( @val ) ;
  9637. }
  9638. sub memory_consumption_of_pids_win32
  9639. {
  9640. # Windows
  9641. my @PID = @_;
  9642. my %PID;
  9643. # hash of pids as key values
  9644. map { $PID{$_}++ } @PID;
  9645. # Does not work but should work reading the tasklist documentation
  9646. #@ps = qx{ tasklist /FI "PID eq @PID" };
  9647. my @ps = qx{ tasklist /NH /FO CSV } ;
  9648. #my @ps = backtick( 'tasklist /NH /FO CSV' ) ;
  9649. #myprint( "-" x $STD_CHAR_PER_LINE, "\n", @ps, "-" x $STD_CHAR_PER_LINE, "\n" ) ;
  9650. my @val;
  9651. foreach my $line (@ps) {
  9652. my($name, $pid, $mem) = (split ',', $line )[0,1,4];
  9653. next if (! $pid);
  9654. #myprint( "[$name][$pid][$mem]" ) ;
  9655. if ($PID{remove_qq($pid)}) {
  9656. #myprint( "MATCH !\n" ) ;
  9657. chomp $mem ;
  9658. $mem = remove_qq($mem);
  9659. $mem = remove_Ko($mem);
  9660. $mem = remove_not_num($mem);
  9661. #myprint( "[$mem]\n" ) ;
  9662. push @val, $mem * $KIBI;
  9663. }
  9664. }
  9665. return(@val);
  9666. }
  9667. sub tests_backtick
  9668. {
  9669. note( 'Entering tests_backtick()' ) ;
  9670. is( undef, backtick( ), 'backtick: no args' ) ;
  9671. is( undef, backtick( q{} ), 'backtick: empty command' ) ;
  9672. SKIP: {
  9673. skip( 'test for MSWin32', 5 ) if ('MSWin32' ne $OSNAME) ;
  9674. my @output ;
  9675. @output = backtick( 'echo Hello World!' ) ;
  9676. # Add \r on Windows.
  9677. ok( "Hello World!\r\n" eq $output[0], 'backtick: echo Hello World!' ) ;
  9678. $sync->{ debug } and myprint( "[@output]" ) ;
  9679. @output = backtick( 'echo Hello & echo World!' ) ;
  9680. ok( "Hello \r\n" eq $output[0], 'backtick: echo Hello & echo World! line 1' ) ;
  9681. ok( "World!\r\n" eq $output[1], 'backtick: echo Hello & echo World! line 2' ) ;
  9682. $sync->{ debug } and myprint( "[@output][$output[0]][$output[1]]" ) ;
  9683. # Scalar context
  9684. ok( "Hello World!\r\n" eq backtick( 'echo Hello World!' ),
  9685. 'backtick: echo Hello World! scalar' ) ;
  9686. ok( "Hello \r\nWorld!\r\n" eq backtick( 'echo Hello & echo World!' ),
  9687. 'backtick: echo Hello & echo World! scalar 2 lines' ) ;
  9688. } ;
  9689. SKIP: {
  9690. skip( 'test for Unix', 7 ) if ('MSWin32' eq $OSNAME) ;
  9691. is( undef, backtick( 'aaaarrrg' ), 'backtick: aaaarrrg command not found' ) ;
  9692. # Array context
  9693. my @output ;
  9694. @output = backtick( 'echo Hello World!' ) ;
  9695. ok( "Hello World!\n" eq $output[0], 'backtick: echo Hello World!' ) ;
  9696. $sync->{ debug } and myprint( "[@output]" ) ;
  9697. @output = backtick( "echo Hello\necho World!" ) ;
  9698. ok( "Hello\n" eq $output[0], 'backtick: echo Hello; echo World! line 1' ) ;
  9699. ok( "World!\n" eq $output[1], 'backtick: echo Hello; echo World! line 2' ) ;
  9700. $sync->{ debug } and myprint( "[@output]" ) ;
  9701. # Scalar context
  9702. ok( "Hello World!\n" eq backtick( 'echo Hello World!' ),
  9703. 'backtick: echo Hello World! scalar' ) ;
  9704. ok( "Hello\nWorld!\n" eq backtick( "echo Hello\necho World!" ),
  9705. 'backtick: echo Hello; echo World! scalar 2 lines' ) ;
  9706. # Return error positive value, that's ok
  9707. is( undef, backtick( 'false' ), 'backtick: false returns no output' ) ;
  9708. my $mem = backtick( "ps -o vsz -p $PROCESS_ID" ) ;
  9709. $sync->{ debug } and myprint( "MEM=$mem\n" ) ;
  9710. }
  9711. note( 'Leaving tests_backtick()' ) ;
  9712. return ;
  9713. }
  9714. sub backtick
  9715. {
  9716. my $command = shift ;
  9717. if ( ! $command ) { return ; }
  9718. my ( $writer, $reader, $err ) ;
  9719. my @output ;
  9720. my $pid ;
  9721. my $eval = eval {
  9722. $pid = IPC::Open3::open3( $writer, $reader, $err, $command ) ;
  9723. } ;
  9724. if ( $EVAL_ERROR ) {
  9725. myprint( $EVAL_ERROR ) ;
  9726. return ;
  9727. }
  9728. if ( ! $eval ) { return ; }
  9729. if ( ! $pid ) { return ; }
  9730. waitpid( $pid, 0 ) ;
  9731. @output = <$reader>; # Output here
  9732. #
  9733. #my @errors = <$err>; #Errors here, instead of the console
  9734. if ( not @output ) { return ; }
  9735. #myprint( @output ) ;
  9736. if ( $output[0] =~ /\Qopen3: exec of $command failed\E/mxs ) { return ; }
  9737. if ( wantarray ) {
  9738. return( @output ) ;
  9739. } else {
  9740. return( join( q{}, @output) ) ;
  9741. }
  9742. }
  9743. sub tests_check_binary_embed_all_dyn_libs
  9744. {
  9745. note( 'Entering tests_check_binary_embed_all_dyn_libs()' ) ;
  9746. is( 1, check_binary_embed_all_dyn_libs( ), 'check_binary_embed_all_dyn_libs: no args => 1' ) ;
  9747. note( 'Leaving tests_check_binary_embed_all_dyn_libs()' ) ;
  9748. return ;
  9749. }
  9750. sub check_binary_embed_all_dyn_libs
  9751. {
  9752. my @search_dyn_lib_locale = search_dyn_lib_locale( ) ;
  9753. if ( @search_dyn_lib_locale )
  9754. {
  9755. myprint( "Found myself $PROGRAM_NAME pid $PROCESS_ID using locale dynamic libraries that seems out of myself:\n" ) ;
  9756. myprint( @search_dyn_lib_locale ) ;
  9757. if ( $PROGRAM_NAME =~ m{imapsync_bin_Darwin} )
  9758. {
  9759. return 0 ;
  9760. }
  9761. elsif ( $PROGRAM_NAME =~ m{imapsync.*\.exe} )
  9762. {
  9763. return 0 ;
  9764. }
  9765. else
  9766. {
  9767. # is always ok for non binary
  9768. return 1 ;
  9769. }
  9770. }
  9771. else
  9772. {
  9773. # Found only embedded dynamic lib
  9774. myprint( "Found nothing\n" ) ;
  9775. return 1 ;
  9776. }
  9777. }
  9778. sub search_dyn_lib_locale
  9779. {
  9780. if ( 'darwin' eq $OSNAME )
  9781. {
  9782. return search_dyn_lib_locale_darwin( ) ;
  9783. }
  9784. if ( 'linux' eq $OSNAME )
  9785. {
  9786. return search_dyn_lib_locale_linux( ) ;
  9787. }
  9788. if ( 'MSWin32' eq $OSNAME )
  9789. {
  9790. return search_dyn_lib_locale_MSWin32( ) ;
  9791. }
  9792. }
  9793. sub search_dyn_lib_locale_darwin
  9794. {
  9795. my $command = qq{ lsof -p $PID | grep ' REG ' | grep .dylib | grep -v '/par-' } ;
  9796. myprint( "Search non embeded dynamic libs with the command: $command\n" ) ;
  9797. return backtick( $command ) ;
  9798. }
  9799. sub search_dyn_lib_locale_linux
  9800. {
  9801. my $command = qq{ lsof -p $PID | grep ' REG ' | grep -v '/tmp/par-' | grep '\.so' } ;
  9802. myprint( "Search non embeded dynamic libs with the command: $command\n" ) ;
  9803. return backtick( $command ) ;
  9804. }
  9805. sub search_dyn_lib_locale_MSWin32
  9806. {
  9807. my $command = qq{ Listdlls.exe $PID|findstr Strawberry } ;
  9808. # $command = qq{ Listdlls.exe $PID|findstr Strawberry } ;
  9809. myprint( "Search non embeded dynamic libs with the command: $command\n" ) ;
  9810. return qx( $command ) ;
  9811. }
  9812. sub remove_not_num
  9813. {
  9814. my $string = shift ;
  9815. $string =~ tr/0-9//cd ;
  9816. #myprint( "tr [$string]\n" ) ;
  9817. return( $string ) ;
  9818. }
  9819. sub tests_remove_not_num
  9820. {
  9821. note( 'Entering tests_remove_not_num()' ) ;
  9822. ok( '123' eq remove_not_num( 123 ), 'remove_not_num( 123 )' ) ;
  9823. ok( '123' eq remove_not_num( '123' ), q{remove_not_num( '123' )} ) ;
  9824. ok( '123' eq remove_not_num( '12 3' ), q{remove_not_num( '12 3' )} ) ;
  9825. ok( '123' eq remove_not_num( 'a 12 3 Ko' ), q{remove_not_num( 'a 12 3 Ko' )} ) ;
  9826. note( 'Leaving tests_remove_not_num()' ) ;
  9827. return ;
  9828. }
  9829. sub remove_Ko
  9830. {
  9831. my $string = shift;
  9832. if ($string =~ /^(.*)\sKo$/xo) {
  9833. return($1);
  9834. }else{
  9835. return($string);
  9836. }
  9837. }
  9838. sub remove_qq
  9839. {
  9840. my $string = shift;
  9841. if ($string =~ /^"(.*)"$/xo) {
  9842. return($1);
  9843. }else{
  9844. return($string);
  9845. }
  9846. }
  9847. sub memory_consumption_ratio
  9848. {
  9849. my ($base) = @_;
  9850. $base ||= 1;
  9851. my $consu = memory_consumption();
  9852. return($consu / $base);
  9853. }
  9854. sub date_from_rcs
  9855. {
  9856. my $d = shift ;
  9857. 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 ) ;
  9858. if ($d =~ m{(\d{4})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) {
  9859. # Handles the following format
  9860. # 2015/07/10 11:05:59 -- Generated by RCS Date tag.
  9861. #myprint( "$d\n" ) ;
  9862. #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
  9863. my ($year, $month, $day, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6) ;
  9864. $month = $num2mon{$month} ;
  9865. $d = "$day-$month-$year $hour:$min:$sec +0000" ;
  9866. #myprint( "$d\n" ) ;
  9867. }
  9868. return( $d ) ;
  9869. }
  9870. sub tests_date_from_rcs
  9871. {
  9872. note( 'Entering tests_date_from_rcs()' ) ;
  9873. ok('19-Sep-2015 16:11:07 +0000'
  9874. eq date_from_rcs('Date: 2015/09/19 16:11:07 '), 'date_from_rcs from RCS date' ) ;
  9875. note( 'Leaving tests_date_from_rcs()' ) ;
  9876. return ;
  9877. }
  9878. sub good_date
  9879. {
  9880. # two incoming formats:
  9881. # header Tue, 24 Aug 2010 16:00:00 +0200
  9882. # internal 24-Aug-2010 16:00:00 +0200
  9883. # outgoing format: internal date format
  9884. # 24-Aug-2010 16:00:00 +0200
  9885. my $d = shift ;
  9886. return(q{}) if not defined $d;
  9887. SWITCH: {
  9888. if ( $d =~ m{(\d?)(\d-...-\d{4})(\s\d{2}:\d{2}:\d{2})(\s(?:\+|-)\d{4})?}xo ) {
  9889. #myprint( "internal: [$1][$2][$3][$4]\n" ) ;
  9890. my ($day_1, $date_rest, $hour, $zone) = ($1,$2,$3,$4) ;
  9891. $day_1 = '0' if ($day_1 eq q{}) ;
  9892. $zone = ' +0000' if not defined $zone ;
  9893. $d = $day_1 . $date_rest . $hour . $zone ;
  9894. last SWITCH ;
  9895. }
  9896. 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 ) {
  9897. # Handles any combination of following formats
  9898. # Tue, 24 Aug 2010 16:00:00 +0200 -- Standard
  9899. # 24 Aug 2010 16:00:00 +0200 -- Missing Day of Week
  9900. # Tue, 24 Aug 97 16:00:00 +0200 -- Two digit year
  9901. # Tue, 24 Aug 1997 16.00.00 +0200 -- Periods instead of colons
  9902. # Tue, 24 Aug 1997 16:00:00 +0200 -- Extra whitespace between year and hour
  9903. # Tue, 24 Aug 1997 6:5:2 +0200 -- Single digit hour, min, or second
  9904. # Tue, 24, Aug 1997 16:00:00 +0200 -- Extra comma
  9905. #myprint( "header: [$1][$2][$3][$4][$5][$6][$7][$8]\n" ) ;
  9906. my ($day, $month, $year, $hour, $min, $sec, $zone) = ($1,$2,$3,$4,$5,$6,$7,$8);
  9907. $year = '19' . $year if length($year) == 2 && $year =~ m/^[789]/xo;
  9908. $year = '20' . $year if length($year) == 2;
  9909. $month = substr $month, 0, 3 if length($month) > 4;
  9910. $day = mysprintf( '%02d', $day);
  9911. $hour = mysprintf( '%02d', $hour);
  9912. $min = mysprintf( '%02d', $min);
  9913. $sec = '00' if not defined $sec ;
  9914. $sec = mysprintf( '%02d', $sec ) ;
  9915. $zone = '+0000' if not defined $zone ;
  9916. $d = "$day-$month-$year $hour:$min:$sec $zone" ;
  9917. last SWITCH ;
  9918. }
  9919. 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 ) {
  9920. # Handles any combination of following formats
  9921. # Sun Aug 20 11:55:09 2006
  9922. # Wed Jan 24 11:58:38 MST 2007
  9923. # Wed Jan 2 08:40:57 2008
  9924. #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
  9925. my ($month, $day, $hour, $min, $sec, $year) = ($1,$2,$3,$4,$5,$6);
  9926. $day = mysprintf( '%02d', $day ) ;
  9927. $hour = mysprintf( '%02d', $hour ) ;
  9928. $min = mysprintf( '%02d', $min ) ;
  9929. $sec = mysprintf( '%02d', $sec ) ;
  9930. $d = "$day-$month-$year $hour:$min:$sec +0000" ;
  9931. last SWITCH ;
  9932. }
  9933. 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 ) ;
  9934. if ($d =~ m{(\d{4})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) {
  9935. # Handles the following format
  9936. # 2015/07/10 11:05:59 -- Generated by RCS Date tag.
  9937. #myprint( "$d\n" ) ;
  9938. #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
  9939. my ($year, $month, $day, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6) ;
  9940. $month = $num2mon{$month} ;
  9941. $d = "$day-$month-$year $hour:$min:$sec +0000" ;
  9942. #myprint( "$d\n" ) ;
  9943. last SWITCH ;
  9944. }
  9945. if ($d =~ m{(\d{2})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) {
  9946. # Handles the following format
  9947. # 02/06/09 22:18:08 -- Generated by AVTECH TemPageR devices
  9948. #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
  9949. my ($month, $day, $year, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6);
  9950. $year = '20' . $year;
  9951. $month = $num2mon{$month};
  9952. $d = "$day-$month-$year $hour:$min:$sec +0000";
  9953. last SWITCH ;
  9954. }
  9955. 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 ) {
  9956. # Handles the following format
  9957. # Saturday, December 14, 2002 05:00 PM - KBtoys.com order confirmations
  9958. my ($month, $day, $year, $hour, $min, $apm) = ($1,$2,$3,$4,$5,$6);
  9959. $hour += 12 if $apm eq 'PM' ;
  9960. $day = mysprintf( '%02d', $day ) ;
  9961. $d = "$day-$month-$year $hour:$min:00 +0000" ;
  9962. last SWITCH ;
  9963. }
  9964. if ($d =~ m{(\w{3})\s(\d{1,2})\s(\d{4})\s(\d{2}):(\d{2}):(\d{2})\s((?:\+|-)\d{4})}xo ) {
  9965. # Handles the following format
  9966. # Saturday, December 14, 2002 05:00 PM - jr.com order confirmations
  9967. my ($month, $day, $year, $hour, $min, $sec, $zone) = ($1,$2,$3,$4,$5,$6,$7);
  9968. $day = mysprintf( '%02d', $day ) ;
  9969. $d = "$day-$month-$year $hour:$min:$sec $zone";
  9970. last SWITCH ;
  9971. }
  9972. if ($d =~ m{(\d{1,2})-(\w{3})-(\d{4})}xo ) {
  9973. # Handles the following format
  9974. # 21-Jun-2001 - register.com domain transfer email circa 2001
  9975. my ($day, $month, $year) = ($1,$2,$3);
  9976. $day = mysprintf( '%02d', $day);
  9977. $d = "$day-$month-$year 11:11:11 +0000";
  9978. last SWITCH ;
  9979. }
  9980. # unknown or unmatch => return same string
  9981. return($d);
  9982. }
  9983. $d = qq("$d") ;
  9984. return( $d ) ;
  9985. }
  9986. sub tests_good_date
  9987. {
  9988. note( 'Entering tests_good_date()' ) ;
  9989. ok(q{} eq good_date(), 'good_date no arg');
  9990. ok('"24-Aug-2010 16:00:00 +0200"' eq good_date('24-Aug-2010 16:00:00 +0200'), 'good_date internal 2digit zone');
  9991. ok('"24-Aug-2010 16:00:00 +0000"' eq good_date('24-Aug-2010 16:00:00'), 'good_date internal 2digit no zone');
  9992. ok('"01-Sep-2010 16:00:00 +0200"' eq good_date( '1-Sep-2010 16:00:00 +0200'), 'good_date internal SP 1digit');
  9993. ok('"24-Aug-2010 16:00:00 +0200"' eq good_date('Tue, 24 Aug 2010 16:00:00 +0200'), 'good_date header 2digit zone');
  9994. ok('"01-Sep-2010 16:00:00 +0000"' eq good_date('Wed, 1 Sep 2010 16:00:00'), 'good_date header SP 1digit zone');
  9995. 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');
  9996. 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');
  9997. ok('"06-Feb-2009 22:18:08 +0000"' eq good_date('02/06/09 22:18:08'), 'good_date header TemPageR');
  9998. 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');
  9999. 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');
  10000. 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');
  10001. 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');
  10002. ok('"24-Aug-2067 16:00:00 +0200"' eq good_date('Tue, 24 Aug 67 16:00:00 +0200'), 'good_date header 2digit year');
  10003. ok('"24-Aug-1977 16:00:00 +0200"' eq good_date('Tue, 24 Aug 77 16:00:00 +0200'), 'good_date header 2digit year');
  10004. ok('"24-Aug-1987 16:00:00 +0200"' eq good_date('Tue, 24 Aug 87 16:00:00 +0200'), 'good_date header 2digit year');
  10005. ok('"24-Aug-1997 16:00:00 +0200"' eq good_date('Tue, 24 Aug 97 16:00:00 +0200'), 'good_date header 2digit year');
  10006. ok('"24-Aug-2004 16:00:00 +0200"' eq good_date('Tue, 24 Aug 04 16:00:00 +0200'), 'good_date header 2digit year');
  10007. 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');
  10008. 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');
  10009. 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');
  10010. ok('"24-Aug-1997 05:06:02 +0200"' eq good_date('Tue, 24, Aug 1997 05:06:02 +0200'), 'good_date header extra commas');
  10011. ok('"01-Oct-2003 12:45:24 +0000"' eq good_date('Wednesday, 01 October 2003 12:45:24 CDT'), 'good_date header no abbrev');
  10012. ok('"11-Jan-2005 17:58:27 -0500"' eq good_date('Tue, 11 Jan 2005 17:58:27 -0500'), 'good_date extra white space');
  10013. ok('"18-Dec-2002 15:07:00 +0000"' eq good_date('Wednesday, December 18, 2002 03:07 PM'), 'good_date kbtoys.com orders');
  10014. ok('"16-Dec-2004 02:01:49 -0500"' eq good_date('Dec 16 2004 02:01:49 -0500'), 'good_date jr.com orders');
  10015. ok('"21-Jun-2001 11:11:11 +0000"' eq good_date('21-Jun-2001'), 'good_date register.com domain transfer');
  10016. 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)');
  10017. ok('"19-Sep-2015 16:11:07 +0000"' eq good_date('Date: 2015/09/19 16:11:07 '), 'good_date from RCS date' ) ;
  10018. note( 'Leaving tests_good_date()' ) ;
  10019. return ;
  10020. }
  10021. sub tests_list_keys_in_2_not_in_1
  10022. {
  10023. note( 'Entering tests_list_keys_in_2_not_in_1()' ) ;
  10024. my @list;
  10025. ok( ! list_keys_in_2_not_in_1( {}, {}), 'list_keys_in_2_not_in_1: {} {}');
  10026. ok( 0 == compare_lists( [], [ list_keys_in_2_not_in_1( {}, {} ) ] ), 'list_keys_in_2_not_in_1: {} {}');
  10027. 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}');
  10028. 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}');
  10029. 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}');
  10030. 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}');
  10031. 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}');
  10032. note( 'Leaving tests_list_keys_in_2_not_in_1()' ) ;
  10033. return ;
  10034. }
  10035. sub list_keys_in_2_not_in_1
  10036. {
  10037. my $hash_1_ref = shift;
  10038. my $hash_2_ref = shift;
  10039. my @list;
  10040. foreach my $key ( sort keys %{ $hash_2_ref } ) {
  10041. #$debug and print "$folder\n" ;
  10042. next if exists $hash_1_ref->{$key} ;
  10043. push @list, $key ;
  10044. }
  10045. #$debug and print "@list\n" ;
  10046. return( @list ) ;
  10047. }
  10048. sub list_folders_in_2_not_in_1
  10049. {
  10050. my ( @h2_folders_not_in_h1, %h2_folders_not_in_h1 ) ;
  10051. @h2_folders_not_in_h1 = list_keys_in_2_not_in_1( \%h1_folders_all, \%h2_folders_all ) ;
  10052. map { $h2_folders_not_in_h1{$_} = 1} @h2_folders_not_in_h1 ;
  10053. @h2_folders_not_in_h1 = list_keys_in_2_not_in_1( \%h2_folders_from_1_all, \%h2_folders_not_in_h1 ) ;
  10054. return( reverse @h2_folders_not_in_h1 ) ;
  10055. }
  10056. sub tests_nb_messages_in_2_not_in_1
  10057. {
  10058. note( 'Entering tests_stats_across_folders()' ) ;
  10059. is( undef, nb_messages_in_2_not_in_1( ), 'nb_messages_in_2_not_in_1: no args => undef' ) ;
  10060. my $mysync->{ h1_folders_of_md5 }->{ 'some_id_01' }->{ 'some_folder_01' } = 1 ;
  10061. is( 0, nb_messages_in_2_not_in_1( $mysync ), 'nb_messages_in_2_not_in_1: no messages in 2 => 0' ) ;
  10062. $mysync->{ h1_folders_of_md5 }->{ 'some_id_in_1_and_2' }->{ 'some_folder_01' } = 2 ;
  10063. $mysync->{ h2_folders_of_md5 }->{ 'some_id_in_1_and_2' }->{ 'some_folder_02' } = 4 ;
  10064. is( 0, nb_messages_in_2_not_in_1( $mysync ), 'nb_messages_in_2_not_in_1: a common message => 0' ) ;
  10065. $mysync->{ h2_folders_of_md5 }->{ 'some_id_in_2_not_in_1' }->{ 'some_folder_02' } = 1 ;
  10066. is( 1, nb_messages_in_2_not_in_1( $mysync ), 'nb_messages_in_2_not_in_1: one message in_2_not_in_1 => 1' ) ;
  10067. $mysync->{ h2_folders_of_md5 }->{ 'some_other_id_in_2_not_in_1' }->{ 'some_folder_02' } = 3 ;
  10068. is( 2, nb_messages_in_2_not_in_1( $mysync ), 'nb_messages_in_2_not_in_1: two messages in_2_not_in_1 => 2' ) ;
  10069. note( 'Leaving tests_stats_across_folders()' ) ;
  10070. return ;
  10071. }
  10072. sub nb_messages_in_2_not_in_1
  10073. {
  10074. my $mysync = shift ;
  10075. if ( not defined $mysync ) { return ; }
  10076. $mysync->{ nb_messages_in_2_not_in_1 } = scalar(
  10077. list_keys_in_2_not_in_1(
  10078. $mysync->{ h1_folders_of_md5 },
  10079. $mysync->{ h2_folders_of_md5 } ) ) ;
  10080. return $mysync->{ nb_messages_in_2_not_in_1 } ;
  10081. }
  10082. sub nb_messages_in_1_not_in_2
  10083. {
  10084. my $mysync = shift ;
  10085. if ( not defined $mysync ) { return ; }
  10086. $mysync->{ nb_messages_in_1_not_in_2 } = scalar(
  10087. list_keys_in_2_not_in_1(
  10088. $mysync->{ h2_folders_of_md5 },
  10089. $mysync->{ h1_folders_of_md5 } ) ) ;
  10090. return $mysync->{ nb_messages_in_1_not_in_2 } ;
  10091. }
  10092. sub comment_on_final_diff_in_1_not_in_2
  10093. {
  10094. my $mysync = shift ;
  10095. if ( not defined $mysync
  10096. or $mysync->{ justfolders }
  10097. or $mysync->{ useuid }
  10098. )
  10099. {
  10100. return ;
  10101. }
  10102. my $nb_identified_h1_messages = scalar( keys %{ $mysync->{ h1_folders_of_md5 } } ) ;
  10103. my $nb_identified_h2_messages = scalar( keys %{ $mysync->{ h2_folders_of_md5 } } ) ;
  10104. $mysync->{ debug } and myprint( "nb_keys h1_folders_of_md5 $nb_identified_h1_messages\n" ) ;
  10105. $mysync->{ debug } and myprint( "nb_keys h2_folders_of_md5 $nb_identified_h2_messages\n" ) ;
  10106. if ( 0 == $nb_identified_h1_messages ) { return ; }
  10107. # Calculate if not yet done
  10108. if ( not defined $mysync->{ nb_messages_in_1_not_in_2 } )
  10109. {
  10110. nb_messages_in_1_not_in_2( $mysync ) ;
  10111. }
  10112. if ( 0 == $mysync->{ nb_messages_in_1_not_in_2 } )
  10113. {
  10114. myprint( "The sync looks good, all $nb_identified_h1_messages identified messages in host1 are on host2.\n" ) ;
  10115. }
  10116. else
  10117. {
  10118. myprint( "The sync is not finished, there are $mysync->{ nb_messages_in_1_not_in_2 } identified messages in host1 that are not on host2.\n" ) ;
  10119. }
  10120. if ( 1 <= $mysync->{ h1_nb_msg_noheader } )
  10121. {
  10122. myprint( "There are $mysync->{ h1_nb_msg_noheader } unidentified messages (usually Sent or Draft messages). To sync them add option --addheader\n" ) ;
  10123. }
  10124. return ;
  10125. }
  10126. sub comment_on_final_diff_in_2_not_in_1
  10127. {
  10128. my $mysync = shift ;
  10129. if ( not defined $mysync
  10130. or $mysync->{ justfolders }
  10131. or $mysync->{ useuid }
  10132. )
  10133. {
  10134. return ;
  10135. }
  10136. my $nb_identified_h2_messages = scalar( keys %{ $mysync->{ h2_folders_of_md5 } } ) ;
  10137. # Calculate if not yet done
  10138. if ( not defined $mysync->{ nb_messages_in_2_not_in_1 } )
  10139. {
  10140. nb_messages_in_2_not_in_1( $mysync ) ;
  10141. }
  10142. if ( 0 == $mysync->{ nb_messages_in_2_not_in_1 } )
  10143. {
  10144. myprint( "The sync is strict, all $nb_identified_h2_messages identified messages in host2 are on host1.\n" ) ;
  10145. }
  10146. else
  10147. {
  10148. myprint( "The sync is not strict, there are ",
  10149. $mysync->{ nb_messages_in_2_not_in_1 },
  10150. " messages in host2 that are not on host1.",
  10151. " Use --delete2 to delete them and have a strict sync.\n" ) ;
  10152. }
  10153. return ;
  10154. }
  10155. sub tests_match
  10156. {
  10157. note( 'Entering tests_match()' ) ;
  10158. # undef serie
  10159. is( undef, match( ), 'match: no args => undef' ) ;
  10160. is( undef, match( 'lalala' ), 'match: one args => undef' ) ;
  10161. # This one gives 0 under a binary made by pp
  10162. # but 1 under "normal" Perl interpreter. So a PAR bug?
  10163. #is( 1, match( q{}, q{} ), 'match: q{} =~ q{} => 1' ) ;
  10164. is( 'lalala', match( 'lalala', 'lalala' ), 'match: lalala =~ lalala => lalala' ) ;
  10165. is( 'lalala', match( 'lalala', '^lalala' ), 'match: lalala =~ ^lalala => lalala' ) ;
  10166. is( 'lalala', match( 'lalala', 'lalala$' ), 'match: lalala =~ lalala$ => lalala' ) ;
  10167. is( 'lalala', match( 'lalala', '^lalala$' ), 'match: lalala =~ ^lalala$ => lalala' ) ;
  10168. is( '_lalala_', match( '_lalala_', 'lalala' ), 'match: _lalala_ =~ lalala => _lalala_' ) ;
  10169. is( 'lalala', match( 'lalala', '.*' ), 'match: lalala =~ .* => lalala' ) ;
  10170. is( 'lalala', match( 'lalala', '.' ), 'match: lalala =~ . => lalala' ) ;
  10171. is( '/lalala/', match( '/lalala/', '/lalala/' ), 'match: /lalala/ =~ /lalala/ => /lalala/' ) ;
  10172. is( 0, match( 'foo', 's/foo/bar/g' ), 'match: foo =~ s/foo/bar/g => 0' ) ;
  10173. is( 's/foo/bar/g', match( 's/foo/bar/g', 's/foo/bar/g' ), 'match: s/foo/bar/g =~ s/foo/bar/g => s/foo/bar/g' ) ;
  10174. is( 0, match( 'lalala', 'ooo' ), 'match: lalala =~ ooo => 0' ) ;
  10175. is( 0, match( 'lalala', 'lal_ala' ), 'match: lalala =~ lal_ala => 0' ) ;
  10176. is( 0, match( 'lalala', '\.' ), 'match: lalala =~ \. => 0' ) ;
  10177. is( 0, match( 'lalalaX', '^lalala$' ), 'match: lalalaX =~ ^lalala$ => 0' ) ;
  10178. is( 0, match( 'lalala', '/lalala/' ), 'match: lalala =~ /lalala/ => 0' ) ;
  10179. is( 'LALALA', match( 'LALALA', '(?i:lalala)' ), 'match: LALALA =~ (?i:lalala) => 1' ) ;
  10180. is( undef, match( 'LALALA', '(?{`ls /`})' ), 'match: LALALA =~ (?{`ls /`}) => undef' ) ;
  10181. is( undef, match( 'LALALA', '(?{print "CACA"})' ), 'match: LALALA =~ (?{print "CACA"}) => undef' ) ;
  10182. is( undef, match( 'CACA', '(??{print "CACA"})' ), 'match: CACA =~ (??{print "CACA"}) => undef' ) ;
  10183. note( 'Leaving tests_match()' ) ;
  10184. return ;
  10185. }
  10186. sub match
  10187. {
  10188. my( $var, $regex ) = @ARG ;
  10189. # undef cases
  10190. if ( ( ! defined $var ) or ( ! defined $regex ) ) { return ; }
  10191. # normal cases
  10192. if ( eval { $var =~ qr{$regex} } ) {
  10193. return $var ;
  10194. }elsif ( $EVAL_ERROR ) {
  10195. myprint( "Fatal regex $regex\n" ) ;
  10196. return ;
  10197. } else {
  10198. return 0 ;
  10199. }
  10200. return ;
  10201. }
  10202. sub tests_notmatch
  10203. {
  10204. note( 'Entering tests_notmatch()' ) ;
  10205. # undef serie
  10206. is( undef, notmatch( ), 'notmatch: no args => undef' ) ;
  10207. is( undef, notmatch( 'lalala' ), 'notmatch: one args => undef' ) ;
  10208. is( 1, notmatch( 'lalala', '/lalala/' ), 'notmatch: lalala !~ /lalala/ => 1' ) ;
  10209. is( 0, notmatch( '/lalala/', '/lalala/' ), 'notmatch: /lalala/ !~ /lalala/ => 0' ) ;
  10210. is( 1, notmatch( 'lalala', '/ooo/' ), 'notmatch: lalala !~ /ooo/ => 1' ) ;
  10211. # This one gives 1 under a binary made by pp
  10212. # but 0 under "normal" Perl interpreter. So a PAR bug, same in tests_match .
  10213. #is( 0, notmatch( q{}, q{} ), 'notmatch: q{} !~ q{} => 0' ) ;
  10214. is( 0, notmatch( 'lalala', 'lalala' ), 'notmatch: lalala !~ lalala => 0' ) ;
  10215. is( 0, notmatch( 'lalala', '^lalala' ), 'notmatch: lalala !~ ^lalala => 0' ) ;
  10216. is( 0, notmatch( 'lalala', 'lalala$' ), 'notmatch: lalala !~ lalala$ => 0' ) ;
  10217. is( 0, notmatch( 'lalala', '^lalala$' ), 'notmatch: lalala !~ ^lalala$ => 0' ) ;
  10218. is( 0, notmatch( '_lalala_', 'lalala' ), 'notmatch: _lalala_ !~ lalala => 0' ) ;
  10219. is( 0, notmatch( 'lalala', '.*' ), 'notmatch: lalala !~ .* => 0' ) ;
  10220. is( 0, notmatch( 'lalala', '.' ), 'notmatch: lalala !~ . => 0' ) ;
  10221. is( 1, notmatch( 'lalala', 'ooo' ), 'notmatch: does not match regex => 1' ) ;
  10222. is( 1, notmatch( 'lalala', 'lal_ala' ), 'notmatch: does not match regex => 1' ) ;
  10223. is( 1, notmatch( 'lalala', '\.' ), 'notmatch: matches regex => 0' ) ;
  10224. is( 1, notmatch( 'lalalaX', '^lalala$' ), 'notmatch: does not match regex => 1' ) ;
  10225. note( 'Leaving tests_notmatch()' ) ;
  10226. return ;
  10227. }
  10228. sub notmatch
  10229. {
  10230. my( $var, $regex ) = @ARG ;
  10231. # undef cases
  10232. if ( ( ! defined $var ) or ( ! defined $regex ) ) { return ; }
  10233. # normal cases
  10234. if ( eval { $var !~ $regex } ) {
  10235. return 1 ;
  10236. }elsif ( $EVAL_ERROR ) {
  10237. myprint( "Fatal regex $regex\n" ) ;
  10238. return ;
  10239. }else{
  10240. return 0 ;
  10241. }
  10242. return ;
  10243. }
  10244. sub delete_folders_in_2_not_in_1
  10245. {
  10246. foreach my $folder (@h2_folders_not_in_1) {
  10247. if ( defined $delete2foldersonly and eval "\$folder !~ $delete2foldersonly" ) {
  10248. myprint( "Not deleting $folder because of --delete2foldersonly $delete2foldersonly\n" ) ;
  10249. next ;
  10250. }
  10251. if ( defined $delete2foldersbutnot and eval "\$folder =~ $delete2foldersbutnot" ) {
  10252. myprint( "Not deleting $folder because of --delete2foldersbutnot $delete2foldersbutnot\n" ) ;
  10253. next ;
  10254. }
  10255. my $res = $sync->{dry} ; # always success in dry mode!
  10256. $sync->{imap2}->unsubscribe( $folder ) if ( ! $sync->{dry} ) ;
  10257. $res = $sync->{imap2}->delete( $folder ) if ( ! $sync->{dry} ) ;
  10258. if ( $res ) {
  10259. myprint( "Deleted $folder", "$sync->{dry_message}", "\n" ) ;
  10260. }else{
  10261. myprint( "Deleting $folder failed", "\n" ) ;
  10262. }
  10263. }
  10264. return ;
  10265. }
  10266. sub delete_folder
  10267. {
  10268. my ( $mysync, $imap, $folder, $Side ) = @_ ;
  10269. if ( ! $mysync ) { return ; }
  10270. if ( ! $imap ) { return ; }
  10271. if ( ! $folder ) { return ; }
  10272. $Side ||= 'HostX' ;
  10273. my $res = $mysync->{dry} ; # always success in dry mode!
  10274. if ( ! $mysync->{dry} ) {
  10275. $imap->unsubscribe( $folder ) ;
  10276. $res = $imap->delete( $folder ) ;
  10277. }
  10278. if ( $res ) {
  10279. myprint( "$Side deleted $folder", $mysync->{dry_message}, "\n" ) ;
  10280. return 1 ;
  10281. }else{
  10282. myprint( "$Side deleting $folder failed", "\n" ) ;
  10283. return ;
  10284. }
  10285. }
  10286. sub delete1emptyfolders
  10287. {
  10288. my $mysync = shift ;
  10289. if ( ! $mysync ) { return ; } # abort if no parameter
  10290. if ( ! $mysync->{delete1emptyfolders} ) { return ; } # abort if --delete1emptyfolders off
  10291. my $imap = $mysync->{imap1} ;
  10292. if ( ! $imap ) { return ; } # abort if no imap
  10293. if ( $imap->IsUnconnected( ) ) { return ; } # abort if disconnected
  10294. my %folders_kept ;
  10295. myprint( qq{Host1 deleting empty folders\n} ) ;
  10296. foreach my $folder ( reverse sort @{ $mysync->{h1_folders_wanted} } ) {
  10297. my $parenthood = $imap->is_parent( $folder ) ;
  10298. if ( defined $parenthood and $parenthood ) {
  10299. myprint( "Host1: folder $folder has subfolders\n" ) ;
  10300. $folders_kept{ $folder }++ ;
  10301. next ;
  10302. }
  10303. my $nb_messages_select = examine_folder_and_count( $mysync, $imap, $folder, 'Host1' ) ;
  10304. if ( ! defined $nb_messages_select ) { next ; } # Select failed => Neither continue nor keep this folder }
  10305. my $nb_messages_search = scalar( @{ $imap->messages( ) } ) ;
  10306. if ( 0 != $nb_messages_select and 0 != $nb_messages_search ) {
  10307. myprint( "Host1: folder $folder has messages: $nb_messages_search (search) $nb_messages_select (select)\n" ) ;
  10308. $folders_kept{ $folder }++ ;
  10309. next ;
  10310. }
  10311. if ( 0 != $nb_messages_select + $nb_messages_search ) {
  10312. myprint( "Host1: folder $folder odd messages count: $nb_messages_search (search) $nb_messages_select (select)\n" ) ;
  10313. $folders_kept{ $folder }++ ;
  10314. next ;
  10315. }
  10316. # Here we must have 0 messages by messages() aka "SEARCH ALL" and also "EXAMINE"
  10317. if ( uc $folder eq 'INBOX' ) {
  10318. myprint( "Host1: Not deleting $folder\n" ) ;
  10319. $folders_kept{ $folder }++ ;
  10320. next ;
  10321. }
  10322. myprint( "Host1: deleting empty folder $folder\n" ) ;
  10323. # can not delete a SELECTed or EXAMINEd folder so closing it
  10324. # could changed be SELECT INBOX
  10325. $imap->close( ) ; # close after examine does not expunge; anyway expunging an empty folder...
  10326. if ( delete_folder( $mysync, $imap, $folder, 'Host1' ) ) {
  10327. next ; # Deleted, good!
  10328. }else{
  10329. $folders_kept{ $folder }++ ;
  10330. next ; # Not deleted, bad!
  10331. }
  10332. }
  10333. remove_deleted_folders_from_wanted_list( $mysync, %folders_kept ) ;
  10334. myprint( qq{Host1 ended deleting empty folders\n} ) ;
  10335. return ;
  10336. }
  10337. sub remove_deleted_folders_from_wanted_list
  10338. {
  10339. my ( $mysync, %folders_kept ) = @ARG ;
  10340. my @h1_folders_wanted_init = @{ $mysync->{h1_folders_wanted} } ;
  10341. my @h1_folders_wanted_last ;
  10342. foreach my $folder ( @h1_folders_wanted_init ) {
  10343. if ( $folders_kept{ $folder } ) {
  10344. push @h1_folders_wanted_last, $folder ;
  10345. }
  10346. }
  10347. @{ $mysync->{h1_folders_wanted} } = @h1_folders_wanted_last ;
  10348. return ;
  10349. }
  10350. sub examine_folder_and_count
  10351. {
  10352. my ( $mysync, $imap, $folder, $Side ) = @_ ;
  10353. $Side ||= 'HostX' ;
  10354. if ( ! examine_folder( $mysync, $imap, $folder, $Side ) ) {
  10355. return ;
  10356. }
  10357. my $nb_messages_select = count_from_select( $imap->History ) ;
  10358. return $nb_messages_select ;
  10359. }
  10360. sub tests_delete1emptyfolders
  10361. {
  10362. note( 'Entering tests_delete1emptyfolders()' ) ;
  10363. is( undef, delete1emptyfolders( ), q{delete1emptyfolders: undef} ) ;
  10364. my $syncT ;
  10365. is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: undef 2} ) ;
  10366. my $imapT ;
  10367. $syncT->{imap1} = $imapT ;
  10368. is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: undef imap} ) ;
  10369. require_ok( "Test::MockObject" ) ;
  10370. $imapT = Test::MockObject->new( ) ;
  10371. $syncT->{imap1} = $imapT ;
  10372. $imapT->set_true( 'IsUnconnected' ) ;
  10373. is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: Unconnected imap} ) ;
  10374. # Now connected tests
  10375. $imapT->set_false( 'IsUnconnected' ) ;
  10376. $imapT->mock( 'LastError', sub { q{LastError mocked} } ) ;
  10377. $syncT->{delete1emptyfolders} = 0 ;
  10378. tests_delete1emptyfolders_unit(
  10379. $syncT,
  10380. [ qw{ INBOX DELME1 DELME2 } ],
  10381. [ qw{ INBOX DELME1 DELME2 } ],
  10382. q{tests_delete1emptyfolders: --delete1emptyfolders OFF}
  10383. ) ;
  10384. # All are parents => no deletion at all
  10385. $imapT->set_true( 'is_parent' ) ;
  10386. $syncT->{delete1emptyfolders} = 1 ;
  10387. tests_delete1emptyfolders_unit(
  10388. $syncT,
  10389. [ qw{ INBOX DELME1 DELME2 } ],
  10390. [ qw{ INBOX DELME1 DELME2 } ],
  10391. q{tests_delete1emptyfolders: --delete1emptyfolders ON}
  10392. ) ;
  10393. # No parents but examine false for all => skip all
  10394. $imapT->set_false( 'is_parent', 'examine' ) ;
  10395. tests_delete1emptyfolders_unit(
  10396. $syncT,
  10397. [ qw{ INBOX DELME1 DELME2 } ],
  10398. [ ],
  10399. q{tests_delete1emptyfolders: EXAMINE fails}
  10400. ) ;
  10401. # examine ok for all but History bad => skip all
  10402. $imapT->set_true( 'examine' ) ;
  10403. $imapT->mock( 'History', sub { ( q{History badly mocked} ) } ) ;
  10404. tests_delete1emptyfolders_unit(
  10405. $syncT,
  10406. [ qw{ INBOX DELME1 DELME2 } ],
  10407. [ ],
  10408. q{tests_delete1emptyfolders: examine ok but History badly mocked so count messages fails}
  10409. ) ;
  10410. # History good but some messages EXISTS == messages() => no deletion
  10411. $imapT->mock( 'History', sub { ( q{* 2 EXISTS} ) } ) ;
  10412. $imapT->mock( 'messages', sub { [ qw{ UID_1 UID_2 } ] } ) ;
  10413. tests_delete1emptyfolders_unit(
  10414. $syncT,
  10415. [ qw{ INBOX DELME1 DELME2 } ],
  10416. [ qw{ INBOX DELME1 DELME2 } ],
  10417. q{tests_delete1emptyfolders: History EXAMINE ok, several messages}
  10418. ) ;
  10419. # 0 EXISTS but != messages() => no deletion
  10420. $imapT->mock( 'History', sub { ( q{* 0 EXISTS} ) } ) ;
  10421. $imapT->mock( 'messages', sub { [ qw{ UID_1 UID_2 } ] } ) ;
  10422. tests_delete1emptyfolders_unit(
  10423. $syncT,
  10424. [ qw{ INBOX DELME1 DELME2 } ],
  10425. [ qw{ INBOX DELME1 DELME2 } ],
  10426. q{tests_delete1emptyfolders: 0 EXISTS but 2 by messages()}
  10427. ) ;
  10428. # 1 EXISTS but != 0 == messages() => no deletion
  10429. $imapT->mock( 'History', sub { ( q{* 1 EXISTS} ) } ) ;
  10430. $imapT->mock( 'messages', sub { [ ] } ) ;
  10431. tests_delete1emptyfolders_unit(
  10432. $syncT,
  10433. [ qw{ INBOX DELME1 DELME2 } ],
  10434. [ qw{ INBOX DELME1 DELME2 } ],
  10435. q{tests_delete1emptyfolders: 1 EXISTS but 0 by messages()}
  10436. ) ;
  10437. # 0 EXISTS and 0 == messages() => deletion except INBOX
  10438. $imapT->mock( 'History', sub { ( q{* 0 EXISTS} ) } ) ;
  10439. $imapT->mock( 'messages', sub { [ ] } ) ;
  10440. $imapT->set_true( qw{ delete close unsubscribe } ) ;
  10441. $syncT->{dry_message} = q{ (not really since in a mocked test)} ;
  10442. tests_delete1emptyfolders_unit(
  10443. $syncT,
  10444. [ qw{ INBOX DELME1 DELME2 } ],
  10445. [ qw{ INBOX } ],
  10446. q{tests_delete1emptyfolders: 0 EXISTS 0 by messages() delete folders, keep INBOX}
  10447. ) ;
  10448. note( 'Leaving tests_delete1emptyfolders()' ) ;
  10449. return ;
  10450. }
  10451. sub tests_delete1emptyfolders_unit
  10452. {
  10453. note( 'Entering tests_delete1emptyfolders_unit()' ) ;
  10454. my $syncT = shift ;
  10455. my $folders1wanted_init_ref = shift ;
  10456. my $folders1wanted_after_ref = shift ;
  10457. my $comment = shift || q{delete1emptyfolders:} ;
  10458. my @folders1wanted_init = @{ $folders1wanted_init_ref } ;
  10459. my @folders1wanted_after = @{ $folders1wanted_after_ref } ;
  10460. @{ $syncT->{h1_folders_wanted} } = @folders1wanted_init ;
  10461. is_deeply( $syncT->{h1_folders_wanted}, \@folders1wanted_init, qq{$comment, init check} ) ;
  10462. delete1emptyfolders( $syncT ) ;
  10463. is_deeply( $syncT->{h1_folders_wanted}, \@folders1wanted_after, qq{$comment, after check} ) ;
  10464. note( 'Leaving tests_delete1emptyfolders_unit()' ) ;
  10465. return ;
  10466. }
  10467. sub extract_header
  10468. {
  10469. my $string = shift ;
  10470. my ( $header ) = split /\n\n/x, $string ;
  10471. if ( ! $header ) { return( q{} ) ; }
  10472. #myprint( "[$header]\n" ) ;
  10473. return( $header ) ;
  10474. }
  10475. sub tests_extract_header
  10476. {
  10477. note( 'Entering tests_extract_header()' ) ;
  10478. my $h = <<'EOM';
  10479. Message-Id: <20100428101817.A66CB162474E@plume.est.belle>
  10480. Date: Wed, 28 Apr 2010 12:18:17 +0200 (CEST)
  10481. From: gilles@louloutte.dyndns.org (Gilles LAMIRAL)
  10482. EOM
  10483. chomp $h ;
  10484. ok( $h eq extract_header(
  10485. <<'EOM'
  10486. Message-Id: <20100428101817.A66CB162474E@plume.est.belle>
  10487. Date: Wed, 28 Apr 2010 12:18:17 +0200 (CEST)
  10488. From: gilles@louloutte.dyndns.org (Gilles LAMIRAL)
  10489. body
  10490. lalala
  10491. EOM
  10492. ), 'extract_header: 1') ;
  10493. note( 'Leaving tests_extract_header()' ) ;
  10494. return ;
  10495. }
  10496. sub decompose_header{
  10497. my $string = shift ;
  10498. # a hash, for a keyword header KEY value are list of strings [VAL1, VAL1_other, etc]
  10499. # Think of multiple "Received:" header lines.
  10500. my $header = { } ;
  10501. my ($key, $val ) ;
  10502. my @line = split /\n|\r\n/x, $string ;
  10503. foreach my $line ( @line ) {
  10504. #myprint( "DDD $line\n" ) ;
  10505. # End of header
  10506. last if ( $line =~ m{^$}xo ) ;
  10507. # Key: value
  10508. if ( $line =~ m/(^[^:]+):\s(.*)/xo ) {
  10509. $key = $1 ;
  10510. $val = $2 ;
  10511. $debugdev and myprint( "DDD KV [$key] [$val]\n" ) ;
  10512. push @{ $header->{ $key } }, $val ;
  10513. # blanc and value => value from previous line continues
  10514. }elsif( $line =~ m/^(\s+)(.*)/xo ) {
  10515. $val = $2 ;
  10516. $debugdev and myprint( "DDD V [$val]\n" ) ;
  10517. @{ $header->{ $key } }[ $LAST ] .= " $val" if $key ;
  10518. # dirty line?
  10519. }else{
  10520. next ;
  10521. }
  10522. }
  10523. #myprint( Data::Dumper->Dump( [ $header ] ) ) ;
  10524. return( $header ) ;
  10525. }
  10526. sub tests_decompose_header{
  10527. note( 'Entering tests_decompose_header()' ) ;
  10528. my $header_dec ;
  10529. $header_dec = decompose_header(
  10530. <<'EOH'
  10531. KEY_1: VAL_1
  10532. KEY_2: VAL_2
  10533. VAL_2_+
  10534. VAL_2_++
  10535. KEY_3: VAL_3
  10536. KEY_1: VAL_1_other
  10537. KEY_4: VAL_4
  10538. VAL_4_+
  10539. KEY_5 BLANC: VAL_5
  10540. KEY_6_BAD_BODY: VAL_6
  10541. EOH
  10542. ) ;
  10543. ok( 'VAL_3'
  10544. eq $header_dec->{ 'KEY_3' }[0], 'decompose_header: VAL_3' ) ;
  10545. ok( 'VAL_1'
  10546. eq $header_dec->{ 'KEY_1' }[0], 'decompose_header: VAL_1' ) ;
  10547. ok( 'VAL_1_other'
  10548. eq $header_dec->{ 'KEY_1' }[1], 'decompose_header: VAL_1_other' ) ;
  10549. ok( 'VAL_2 VAL_2_+ VAL_2_++'
  10550. eq $header_dec->{ 'KEY_2' }[0], 'decompose_header: VAL_2 VAL_2_+ VAL_2_++' ) ;
  10551. ok( 'VAL_4 VAL_4_+'
  10552. eq $header_dec->{ 'KEY_4' }[0], 'decompose_header: VAL_4 VAL_4_+' ) ;
  10553. ok( ' VAL_5'
  10554. eq $header_dec->{ 'KEY_5 BLANC' }[0], 'decompose_header: KEY_5 BLANC' ) ;
  10555. ok( not( defined $header_dec->{ 'KEY_6_BAD_BODY' }[0] ), 'decompose_header: KEY_6_BAD_BODY' ) ;
  10556. $header_dec = decompose_header(
  10557. <<'EOH'
  10558. Message-Id: <20100428101817.A66CB162474E@plume.est.belle>
  10559. Date: Wed, 28 Apr 2010 12:18:17 +0200 (CEST)
  10560. From: gilles@louloutte.dyndns.org (Gilles LAMIRAL)
  10561. EOH
  10562. ) ;
  10563. ok( '<20100428101817.A66CB162474E@plume.est.belle>'
  10564. eq $header_dec->{ 'Message-Id' }[0], 'decompose_header: 1' ) ;
  10565. $header_dec = decompose_header(
  10566. <<'EOH'
  10567. Return-Path: <gilles@louloutte.dyndns.org>
  10568. Received: by plume.est.belle (Postfix, from userid 1000)
  10569. id 120A71624742; Wed, 28 Apr 2010 01:46:40 +0200 (CEST)
  10570. Subject: test:eekahceishukohpe
  10571. EOH
  10572. ) ;
  10573. ok(
  10574. 'by plume.est.belle (Postfix, from userid 1000) id 120A71624742; Wed, 28 Apr 2010 01:46:40 +0200 (CEST)'
  10575. eq $header_dec->{ 'Received' }[0], 'decompose_header: 2' ) ;
  10576. $header_dec = decompose_header(
  10577. <<'EOH'
  10578. Received: from plume (localhost [127.0.0.1])
  10579. by plume.est.belle (Postfix) with ESMTP id C6EB73F6C9
  10580. for <gilles@localhost>; Mon, 26 Nov 2007 10:39:06 +0100 (CET)
  10581. Received: from plume [192.168.68.7]
  10582. by plume with POP3 (fetchmail-6.3.6)
  10583. for <gilles@localhost> (single-drop); Mon, 26 Nov 2007 10:39:06 +0100 (CET)
  10584. EOH
  10585. ) ;
  10586. ok(
  10587. '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)'
  10588. eq $header_dec->{ 'Received' }[0], 'decompose_header: 3' ) ;
  10589. ok(
  10590. '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)'
  10591. eq $header_dec->{ 'Received' }[1], 'decompose_header: 3' ) ;
  10592. # Bad header beginning with a blank character
  10593. $header_dec = decompose_header(
  10594. <<'EOH'
  10595. KEY_1: VAL_1
  10596. KEY_2: VAL_2
  10597. VAL_2_+
  10598. VAL_2_++
  10599. KEY_3: VAL_3
  10600. KEY_1: VAL_1_other
  10601. EOH
  10602. ) ;
  10603. ok( 'VAL_3'
  10604. eq $header_dec->{ 'KEY_3' }[0], 'decompose_header: Bad header VAL_3' ) ;
  10605. ok( 'VAL_1_other'
  10606. eq $header_dec->{ 'KEY_1' }[0], 'decompose_header: Bad header VAL_1_other' ) ;
  10607. ok( 'VAL_2 VAL_2_+ VAL_2_++'
  10608. eq $header_dec->{ 'KEY_2' }[0], 'decompose_header: Bad header VAL_2 VAL_2_+ VAL_2_++' ) ;
  10609. note( 'Leaving tests_decompose_header()' ) ;
  10610. return ;
  10611. }
  10612. sub tests_epoch
  10613. {
  10614. note( 'Entering tests_epoch()' ) ;
  10615. ok( '1282658400' eq epoch( '24-Aug-2010 16:00:00 +0200' ), 'epoch 24-Aug-2010 16:00:00 +0200 -> 1282658400' ) ;
  10616. ok( '1282658400' eq epoch( '24-Aug-2010 14:00:00 +0000' ), 'epoch 24-Aug-2010 14:00:00 +0000 -> 1282658400' ) ;
  10617. ok( '1282658400' eq epoch( '24-Aug-2010 12:00:00 -0200' ), 'epoch 24-Aug-2010 12:00:00 -0200 -> 1282658400' ) ;
  10618. ok( '1282658400' eq epoch( '24-Aug-2010 16:01:00 +0201' ), 'epoch 24-Aug-2010 16:01:00 +0201 -> 1282658400' ) ;
  10619. ok( '1282658400' eq epoch( '24-Aug-2010 14:01:00 +0001' ), 'epoch 24-Aug-2010 14:01:00 +0001 -> 1282658400' ) ;
  10620. ok( '1280671200' eq epoch( '1-Aug-2010 16:00:00 +0200' ), 'epoch 1-Aug-2010 16:00:00 +0200 -> 1280671200' ) ;
  10621. ok( '1280671200' eq epoch( '1-Aug-2010 14:00:00 +0000' ), 'epoch 1-Aug-2010 14:00:00 +0000 -> 1280671200' ) ;
  10622. ok( '1280671200' eq epoch( '1-Aug-2010 12:00:00 -0200' ), 'epoch 1-Aug-2010 12:00:00 -0200 -> 1280671200' ) ;
  10623. ok( '1280671200' eq epoch( '1-Aug-2010 16:01:00 +0201' ), 'epoch 1-Aug-2010 16:01:00 +0201 -> 1280671200' ) ;
  10624. ok( '1280671200' eq epoch( '1-Aug-2010 14:01:00 +0001' ), 'epoch 1-Aug-2010 14:01:00 +0001 -> 1280671200' ) ;
  10625. is( '1280671200', epoch( '1-Aug-2010 14:01:00 +0001' ), 'epoch 1-Aug-2010 14:01:00 +0001 -> 1280671200' ) ;
  10626. is( '946684800', epoch( '00-Jan-0000 00:00:00 +0000' ), 'epoch 1-Aug-2010 14:01:00 +0001 -> 1280671200' ) ;
  10627. note( 'Leaving tests_epoch()' ) ;
  10628. return ;
  10629. }
  10630. sub epoch
  10631. {
  10632. # incoming format:
  10633. # internal date 24-Aug-2010 16:00:00 +0200
  10634. # outgoing format: epoch
  10635. my $d = shift ;
  10636. return(q{}) if not defined $d;
  10637. my ( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m ) ;
  10638. my $time ;
  10639. 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 ) {
  10640. #myprint( "internal: [$1][$2][$3][$4][$5][$6][$7][$8][$9]\n" ) ;
  10641. ( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m )
  10642. = ( $1, $2, $3, $4, $5, $6, $7, $8, $9 ) ;
  10643. #myprint( "( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m )\n" ) ;
  10644. $sign = +1 if ( '+' eq $sign ) ;
  10645. $sign = $MINUS_ONE if ( '-' eq $sign ) ;
  10646. if ( 0 == $mday ) {
  10647. myprint( "buggy day in $d. Fixed to 01\n" ) ;
  10648. $mday = '01' ;
  10649. }
  10650. $time = timegm( $sec, $min, $hour, $mday, $month_abrev{$month}, $year )
  10651. - $sign * ( 3600 * $zone_h + 60 * $zone_m ) ;
  10652. #myprint( "$time ", scalar localtime($time), "\n");
  10653. }
  10654. return( $time ) ;
  10655. }
  10656. sub tests_add_header
  10657. {
  10658. note( 'Entering tests_add_header()' ) ;
  10659. ok( 'Message-Id: <mistake@imapsync>' eq add_header(), 'add_header no arg' ) ;
  10660. ok( 'Message-Id: <123456789@imapsync>' eq add_header( '123456789' ), 'add_header 123456789' ) ;
  10661. note( 'Leaving tests_add_header()' ) ;
  10662. return ;
  10663. }
  10664. sub add_header
  10665. {
  10666. my $header_uid = shift || 'mistake' ;
  10667. my $header_Message_Id = 'Message-Id: <' . $header_uid . '@imapsync>' ;
  10668. return( $header_Message_Id ) ;
  10669. }
  10670. sub tests_max_line_length
  10671. {
  10672. note( 'Entering tests_max_line_length()' ) ;
  10673. ok( 0 == max_line_length( q{} ), 'max_line_length: 0 == null string' ) ;
  10674. ok( 1 == max_line_length( "\n" ), 'max_line_length: 1 == \n' ) ;
  10675. ok( 1 == max_line_length( "\n\n" ), 'max_line_length: 1 == \n\n' ) ;
  10676. ok( 1 == max_line_length( "\n" x 500 ), 'max_line_length: 1 == 500 \n' ) ;
  10677. ok( 1 == max_line_length( 'a' ), 'max_line_length: 1 == a' ) ;
  10678. ok( 2 == max_line_length( "a\na" ), 'max_line_length: 2 == a\na' ) ;
  10679. ok( 2 == max_line_length( "a\na\n" ), 'max_line_length: 2 == a\na\n' ) ;
  10680. ok( 3 == max_line_length( "a\nab\n" ), 'max_line_length: 3 == a\nab\n' ) ;
  10681. ok( 3 == max_line_length( "a\nab\n" x 1_000 ), 'max_line_length: 3 == 1_000 a\nab\n' ) ;
  10682. ok( 3 == max_line_length( "a\nab\nabc" ), 'max_line_length: 3 == a\nab\nabc' ) ;
  10683. ok( 4 == max_line_length( "a\nab\nabc\n" ), 'max_line_length: 4 == a\nab\nabc\n' ) ;
  10684. ok( 5 == max_line_length( "a\nabcd\nabc\n" ), 'max_line_length: 5 == a\nabcd\nabc\n' ) ;
  10685. 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' ) ;
  10686. note( 'Leaving tests_max_line_length()' ) ;
  10687. return ;
  10688. }
  10689. sub max_line_length
  10690. {
  10691. my $string = shift ;
  10692. my $max = 0 ;
  10693. while ( $string =~ m/([^\n]*\n?)/msxg ) {
  10694. $max = max( $max, length $1 ) ;
  10695. }
  10696. return( $max ) ;
  10697. }
  10698. sub tests_setlogfile
  10699. {
  10700. note( 'Entering tests_setlogfile()' ) ;
  10701. my $mysync = {} ;
  10702. $mysync->{logdir} = 'vallogdir' ;
  10703. $mysync->{logfile} = 'vallogfile.txt' ;
  10704. is( 'vallogdir/vallogfile.txt', setlogfile( $mysync ),
  10705. 'setlogfile: logdir vallogdir, logfile vallogfile.txt, vallogdir/vallogfile.txt' ) ;
  10706. SKIP: {
  10707. skip( 'Too hard to have a well known timezone on Windows', 9 ) if ( 'MSWin32' eq $OSNAME ) ;
  10708. local $ENV{TZ} = 'GMT' ;
  10709. $mysync = {
  10710. timestart => 2,
  10711. } ;
  10712. is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000__.txt", setlogfile( $mysync ),
  10713. "setlogfile: default is like $DEFAULT_LOGDIR/1970_01_01_00_00_02_000__.txt" ) ;
  10714. $mysync = {
  10715. timestart => 2,
  10716. user1 => 'user1',
  10717. user2 => 'user2',
  10718. abort => 1,
  10719. } ;
  10720. is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_abort.txt", setlogfile( $mysync ),
  10721. "setlogfile: default is like $DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_abort.txt" ) ;
  10722. $mysync = {
  10723. timestart => 2,
  10724. user1 => 'user1',
  10725. user2 => 'user2',
  10726. remote => 'zzz',
  10727. } ;
  10728. is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_remote.txt", setlogfile( $mysync ),
  10729. "setlogfile: default is like $DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_remote.txt" ) ;
  10730. $mysync = {
  10731. timestart => 2,
  10732. user1 => 'user1',
  10733. user2 => 'user2',
  10734. remote => 'zzz',
  10735. abort => 1,
  10736. } ;
  10737. is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_remote_abort.txt", setlogfile( $mysync ),
  10738. "setlogfile: default is like $DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_remote_abort.txt" ) ;
  10739. $mysync = {
  10740. timestart => 2,
  10741. user1 => 'user1',
  10742. user2 => 'user2',
  10743. } ;
  10744. is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2.txt", setlogfile( $mysync ),
  10745. "setlogfile: default is like $DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2.txt" ) ;
  10746. $mysync->{logdir} = undef ;
  10747. $mysync->{logfile} = undef ;
  10748. is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2.txt", setlogfile( $mysync ),
  10749. "setlogfile: logdir undef, $DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2.txt" ) ;
  10750. $mysync->{logdir} = q{} ;
  10751. $mysync->{logfile} = undef ;
  10752. is( '1970_01_01_00_00_02_000_user1_user2.txt', setlogfile( $mysync ),
  10753. 'setlogfile: logdir empty, 1970_01_01_00_00_02_000_user1_user2.txt' ) ;
  10754. $mysync->{logdir} = 'vallogdir' ;
  10755. $mysync->{logfile} = undef ;
  10756. is( 'vallogdir/1970_01_01_00_00_02_000_user1_user2.txt', setlogfile( $mysync ),
  10757. 'setlogfile: logdir vallogdir, vallogdir/1970_01_01_00_00_02_000_user1_user2.txt' ) ;
  10758. $mysync = {
  10759. user1 => 'us/er1a*|?:"<>b',
  10760. user2 => 'u/ser2a*|?:"<>b',
  10761. } ;
  10762. is( "$DEFAULT_LOGDIR/1970_01_01_00_00_00_000_us_er1a_______b_u_ser2a_______b.txt", setlogfile( $mysync ),
  10763. "setlogfile: logdir undef, $DEFAULT_LOGDIR/1970_01_01_00_00_00_000_us_er1a_______b_u_ser2a_______b.txt" ) ;
  10764. } ;
  10765. note( 'Leaving tests_setlogfile()' ) ;
  10766. return ;
  10767. }
  10768. sub setlogfile
  10769. {
  10770. my( $mysync ) = shift ;
  10771. # When aborting another process the log file name finishes with "_abort.txt"
  10772. my $abort_suffix = ( $mysync->{abort} ) ? '_abort' : q{} ;
  10773. # When acting as a proxy the log file name finishes with "_remote.txt"
  10774. # proxy mode is not done yet
  10775. my $remote_suffix = ( $mysync->{remote} ) ? '_remote' : q{} ;
  10776. my $suffix = ( filter_forbidden_characters( move_slash( $mysync->{user1} ) ) || q{} )
  10777. . '_'
  10778. . ( filter_forbidden_characters( move_slash( $mysync->{user2} ) ) || q{} )
  10779. . $remote_suffix . $abort_suffix ;
  10780. $mysync->{logdir} = defined $mysync->{logdir} ? $mysync->{logdir} : $DEFAULT_LOGDIR ;
  10781. $mysync->{logfile} = defined $mysync->{logfile}
  10782. ? "$mysync->{logdir}/$mysync->{logfile}"
  10783. : logfile( $mysync->{timestart}, $suffix, $mysync->{logdir} ) ;
  10784. return( $mysync->{logfile} ) ;
  10785. }
  10786. sub tests_logfile
  10787. {
  10788. note( 'Entering tests_logfile()' ) ;
  10789. SKIP: {
  10790. # Too hard to have a well known timezone on Windows
  10791. skip( 'Too hard to have a well known timezone on Windows', 10 ) if ( 'MSWin32' eq $OSNAME ) ;
  10792. local $ENV{TZ} = 'GMT' ;
  10793. { POSIX::tzset unless ('MSWin32' eq $OSNAME) ;
  10794. is( '1970_01_01_00_00_00_000.txt', logfile( ), 'logfile: no args => 1970_01_01_00_00_00.txt' ) ;
  10795. is( '1970_01_01_00_00_00_000.txt', logfile( 0 ), 'logfile: 0 => 1970_01_01_00_00_00.txt' ) ;
  10796. is( '1970_01_01_00_01_01_000.txt', logfile( 61 ), 'logfile: 0 => 1970_01_01_00_01_01.txt' ) ;
  10797. is( '1970_01_01_00_01_01_234.txt', logfile( 61.234 ), 'logfile: 0 => 1970_01_01_00_01_01.txt' ) ;
  10798. 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' ) ;
  10799. 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' ) ;
  10800. 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' ) ;
  10801. 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' ) ;
  10802. is( '2010_08_24_14_01_01_234_poup.txt', logfile( 1_282_658_461.2347, 'poup' ),
  10803. 'logfile: 1_282_658_461.2347 poup => 2010_08_24_14_01_01_234_poup.txt' ) ;
  10804. is( 'dirdir/2010_08_24_14_01_01_234_poup.txt', logfile( 1_282_658_461.2347, 'poup', 'dirdir' ),
  10805. 'logfile: 1_282_658_461.2347 poup dirdir => dirdir/2010_08_24_14_01_01_234_poup.txt' ) ;
  10806. }
  10807. POSIX::tzset unless ('MSWin32' eq $OSNAME) ;
  10808. } ;
  10809. note( 'Leaving tests_logfile()' ) ;
  10810. return ;
  10811. }
  10812. sub logfile
  10813. {
  10814. my ( $time, $suffix, $dir ) = @_ ;
  10815. $time ||= 0 ;
  10816. $suffix ||= q{} ;
  10817. $suffix =~ tr/ //ds ;
  10818. my $sep_suffix = ( $suffix ) ? '_' : q{} ;
  10819. $dir ||= q{} ;
  10820. my $sep_dir = ( $dir ) ? '/' : q{} ;
  10821. my $date_str = POSIX::strftime( '%Y_%m_%d_%H_%M_%S', localtime $time ) ;
  10822. # Because of ab tests or web access, more than one sync withing one second is possible
  10823. # so we add also milliseconds
  10824. $date_str .= sprintf "_%03d", ($time - int( $time ) ) * 1000 ; # without rounding
  10825. my $logfile = "${dir}${sep_dir}${date_str}${sep_suffix}${suffix}.txt" ;
  10826. return( $logfile ) ;
  10827. }
  10828. sub tests_move_slash
  10829. {
  10830. note( 'Entering tests_move_slash()' ) ;
  10831. is( undef, move_slash( ), 'move_slash: no parameters => undef' ) ;
  10832. is( '_', move_slash( '/' ), 'move_slash: / => _' ) ;
  10833. is( '_abc_def_', move_slash( '/abc/def/' ), 'move_slash: /abc/def/ => _abc_def_' ) ;
  10834. note( 'Leaving tests_move_slash()' ) ;
  10835. return ;
  10836. }
  10837. sub move_slash
  10838. {
  10839. my $string = shift ;
  10840. if ( ! defined $string ) { return ; }
  10841. $string =~ tr{/}{_} ;
  10842. return( $string ) ;
  10843. }
  10844. sub tests_million_folders_baby_2
  10845. {
  10846. note( 'Entering tests_million_folders_baby_2()' ) ;
  10847. my %long ;
  10848. @long{ 1 .. 900_000 } = (1) x 900_000 ;
  10849. #myprint( %long, "\n" ) ;
  10850. my $pasglop = 0 ;
  10851. foreach my $elem ( 1 .. 900_000 ) {
  10852. #$debug and myprint( "$elem " ) ;
  10853. if ( not exists $long{ $elem } ) {
  10854. $pasglop++ ;
  10855. }
  10856. }
  10857. ok( 0 == $pasglop, 'tests_million_folders_baby_2: search among 900_000' ) ;
  10858. # myprint( "$pasglop\n" ) ;
  10859. note( 'Leaving tests_million_folders_baby_2()' ) ;
  10860. return ;
  10861. }
  10862. sub tests_always_fail
  10863. {
  10864. note( 'Entering tests_always_fail()' ) ;
  10865. is( 0, 1, 'always_fail: 0 is 1' ) ;
  10866. note( 'Leaving tests_always_fail()' ) ;
  10867. return ;
  10868. }
  10869. sub tests_logfileprepa
  10870. {
  10871. note( 'Entering tests_logfileprepa()' ) ;
  10872. is( undef, logfileprepa( ), 'logfileprepa: no args => undef' ) ;
  10873. my $logfile = 'W/tmp/tests/tests_logfileprepa.txt' ;
  10874. is( 1, logfileprepa( $logfile ), 'logfileprepa: W/tmp/tests/tests_logfileprepa.txt => 1' ) ;
  10875. note( 'Leaving tests_logfileprepa()' ) ;
  10876. return ;
  10877. }
  10878. sub logfileprepa
  10879. {
  10880. my $logfile = shift ;
  10881. if ( ! defined( $logfile ) )
  10882. {
  10883. return ;
  10884. }else
  10885. {
  10886. #myprint( "[$logfile]\n" ) ;
  10887. my $dirname = dirname( $logfile ) ;
  10888. do_valid_directory( $dirname ) || return( 0 ) ;
  10889. return( 1 ) ;
  10890. }
  10891. }
  10892. sub tests_teelaunch
  10893. {
  10894. note( 'Entering tests_teelaunch()' ) ;
  10895. is( undef, teelaunch( ), 'teelaunch: no args => undef' ) ;
  10896. my $mysync = {} ;
  10897. is( undef, teelaunch( $mysync ), 'teelaunch: arg empty {} => undef' ) ;
  10898. $mysync->{logfile} = '' ;
  10899. is( undef, teelaunch( $mysync ), 'teelaunch: logfile empty string => undef' ) ;
  10900. $mysync->{logfile} = 'W/tmp/tests/tests_teelaunch.txt' ;
  10901. isa_ok( my $tee = teelaunch( $mysync ), 'IO::Tee' , 'teelaunch: logfile W/tmp/tests/tests_teelaunch.txt' ) ;
  10902. is( 1, print( $tee "Hi!\n" ), 'teelaunch: write Hi!') ;
  10903. is( "Hi!\n", file_to_string( 'W/tmp/tests/tests_teelaunch.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch.txt is Hi!\n' ) ;
  10904. is( 1, print( $tee "Hoo\n" ), 'teelaunch: write Hoo') ;
  10905. is( "Hi!\nHoo\n", file_to_string( 'W/tmp/tests/tests_teelaunch.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch.txt is Hi!\nHoo\n' ) ;
  10906. note( 'Leaving tests_teelaunch()' ) ;
  10907. return ;
  10908. }
  10909. sub teelaunch
  10910. {
  10911. my $mysync = shift ;
  10912. if ( ! defined( $mysync ) )
  10913. {
  10914. return ;
  10915. }
  10916. my $logfile = $mysync->{logfile} ;
  10917. if ( ! $logfile )
  10918. {
  10919. return ;
  10920. }
  10921. logfileprepa( $logfile ) || croak "Error no valid directory to write log file $logfile : $OS_ERROR" ;
  10922. # This is a log file opened during the whole sync
  10923. ## no critic (InputOutput::RequireBriefOpen)
  10924. open my $logfile_handle, '>', $logfile
  10925. or croak( "Can not open $logfile for write: $OS_ERROR" ) ;
  10926. my $tee = IO::Tee->new( $logfile_handle, \*STDOUT ) ;
  10927. $tee->autoflush( 1 ) ;
  10928. $mysync->{logfile_handle} = $logfile_handle ;
  10929. $mysync->{tee} = $tee ;
  10930. return $tee ;
  10931. }
  10932. sub getpwuid_any_os
  10933. {
  10934. my $uid = shift ;
  10935. return( scalar getlogin ) if ( 'MSWin32' eq $OSNAME ) ; # Windows system
  10936. return( scalar getpwuid $uid ) ; # Unix system
  10937. }
  10938. sub simulong
  10939. {
  10940. my $max_seconds = shift ;
  10941. my $division = 5 ;
  10942. my $last_count = $division * $max_seconds ;
  10943. foreach my $i ( 1 .. ( $last_count ) ) {
  10944. myprint( "Are you still here $i/$last_count\n" ) ;
  10945. #myprint( "Are you still here $i/$last_count\n" . ( "Ah" x 40 . "\n") x 4000 ) ;
  10946. sleep( 1 / $division ) ;
  10947. }
  10948. return ;
  10949. }
  10950. sub printenv
  10951. {
  10952. myprint( "Environment variables listing:\n",
  10953. ( map { "$_ => $ENV{$_}\n" } sort keys %ENV),
  10954. "Environment variables listing end\n" ) ;
  10955. return ;
  10956. }
  10957. sub testsexit
  10958. {
  10959. my $mysync = shift ;
  10960. if ( ! ( $mysync->{ tests } or $mysync->{ testsdebug } or $mysync->{ testsunit } ) ) {
  10961. return ;
  10962. }
  10963. my $test_builder = Test::More->builder ;
  10964. tests( $mysync ) ;
  10965. testsdebug( $mysync ) ;
  10966. testunitsession( $mysync ) ;
  10967. my @summary = $test_builder->summary() ;
  10968. my @details = $test_builder->details() ;
  10969. my $nb_tests_run = scalar( @summary ) ;
  10970. my $nb_tests_expected = $test_builder->expected_tests() ;
  10971. my $nb_tests_failed = count_0s( @summary ) ;
  10972. my $tests_failed = report_failures( @details ) ;
  10973. if ( $nb_tests_failed or ( $nb_tests_run != $nb_tests_expected ) ) {
  10974. #$test_builder->reset( ) ;
  10975. myprint( "Summary of tests: failed $nb_tests_failed tests, run $nb_tests_run tests, expected to run $nb_tests_expected tests.\n",
  10976. "List of failed tests:\n", $tests_failed ) ;
  10977. exit $EXIT_TESTS_FAILED ;
  10978. }
  10979. cleanup_mess_from_tests( ) ;
  10980. # Cover is larger with --tests --testslive
  10981. if ( ! $mysync->{ testslive } )
  10982. {
  10983. exit ;
  10984. }
  10985. # $eeee ;
  10986. return ;
  10987. }
  10988. sub cleanup_mess_from_tests
  10989. {
  10990. undef @pipemess ;
  10991. return ;
  10992. }
  10993. sub after_get_options
  10994. {
  10995. my $mysync = shift ;
  10996. my $numopt = shift ;
  10997. # exit with --help option or no option at all
  10998. $mysync->{ debug } and myprint( "numopt:$numopt\n" ) ;
  10999. if ( $help or not $numopt ) {
  11000. myprint( usage( $mysync ) ) ;
  11001. exit ;
  11002. }
  11003. return ;
  11004. }
  11005. sub tests_remove_edging_blanks
  11006. {
  11007. note( 'Entering tests_remove_edging_blanks()' ) ;
  11008. is( undef, remove_edging_blanks( ), 'remove_edging_blanks: no args => undef' ) ;
  11009. is( 'abcd', remove_edging_blanks( 'abcd' ), 'remove_edging_blanks: abcd => abcd' ) ;
  11010. is( 'ab cd', remove_edging_blanks( ' ab cd ' ), 'remove_edging_blanks: " ab cd " => "ab cd"' ) ;
  11011. note( 'Leaving tests_remove_edging_blanks()' ) ;
  11012. return ;
  11013. }
  11014. sub remove_edging_blanks
  11015. {
  11016. my $string = shift ;
  11017. if ( ! defined $string )
  11018. {
  11019. return ;
  11020. }
  11021. $string =~ s,^ +| +$,,g ;
  11022. return $string ;
  11023. }
  11024. sub tests_sanitize
  11025. {
  11026. note( 'Entering tests_remove_edging_blanks()' ) ;
  11027. is( undef, sanitize( ), 'sanitize: no args => undef' ) ;
  11028. my $mysync = {} ;
  11029. $mysync->{ host1 } = ' example.com ' ;
  11030. $mysync->{ user1 } = ' to to ' ;
  11031. $mysync->{ password1 } = ' sex is good! ' ;
  11032. is( undef, sanitize( $mysync ), 'sanitize: => undef' ) ;
  11033. is( 'example.com', $mysync->{ host1 }, 'sanitize: host1 " example.com " => "example.com"' ) ;
  11034. is( 'to to', $mysync->{ user1 }, 'sanitize: user1 " to to " => "to to"' ) ;
  11035. is( 'sex is good!', $mysync->{ password1 }, 'sanitize: password1 " sex is good! " => "sex is good!"' ) ;
  11036. note( 'Leaving tests_remove_edging_blanks()' ) ;
  11037. return ;
  11038. }
  11039. sub sanitize
  11040. {
  11041. my $mysync = shift ;
  11042. if ( ! defined $mysync )
  11043. {
  11044. return ;
  11045. }
  11046. foreach my $parameter ( qw( host1 host2 user1 user2 password1 password2 ) )
  11047. {
  11048. $mysync->{ $parameter } = remove_edging_blanks( $mysync->{ $parameter } ) ;
  11049. }
  11050. return ;
  11051. }
  11052. sub easyany
  11053. {
  11054. my $mysync = shift ;
  11055. # Gmail
  11056. if ( $mysync->{gmail1} and $mysync->{gmail2} ) {
  11057. $mysync->{ debug } and myprint( "gmail1 gmail2\n") ;
  11058. gmail12( $mysync ) ;
  11059. return ;
  11060. }
  11061. if ( $mysync->{gmail1} ) {
  11062. $mysync->{ debug } and myprint( "gmail1\n" ) ;
  11063. gmail1( $mysync ) ;
  11064. }
  11065. if ( $mysync->{gmail2} ) {
  11066. $mysync->{ debug } and myprint( "gmail2\n" ) ;
  11067. gmail2( $mysync ) ;
  11068. }
  11069. # Office 365
  11070. if ( $mysync->{office1} ) {
  11071. office1( $mysync ) ;
  11072. }
  11073. if ( $mysync->{office2} ) {
  11074. office2( $mysync ) ;
  11075. }
  11076. # Exchange
  11077. if ( $mysync->{exchange1} ) {
  11078. exchange1( $mysync ) ;
  11079. }
  11080. if ( $mysync->{exchange2} ) {
  11081. exchange2( $mysync ) ;
  11082. }
  11083. # Domino
  11084. if ( $mysync->{domino1} ) {
  11085. domino1( $mysync ) ;
  11086. }
  11087. if ( $mysync->{domino2} ) {
  11088. domino2( $mysync ) ;
  11089. }
  11090. return ;
  11091. }
  11092. # From https://imapsync.lamiral.info/FAQ.d/FAQ.Gmail.txt
  11093. sub gmail12
  11094. {
  11095. my $mysync = shift ;
  11096. # Gmail at host1 and host2
  11097. $mysync->{host1} ||= 'imap.gmail.com' ;
  11098. $mysync->{ssl1} = ( defined $mysync->{ssl1} ) ? $mysync->{ssl1} : 1 ;
  11099. $mysync->{host2} ||= 'imap.gmail.com' ;
  11100. $mysync->{ssl2} = ( defined $mysync->{ssl2} ) ? $mysync->{ssl2} : 1 ;
  11101. $mysync->{maxbytespersecond} ||= 20_000 ; # should be 10_000 when computed from Gmail documentation
  11102. $mysync->{maxbytesafter} ||= 1_000_000_000 ;
  11103. $mysync->{automap} = ( defined $mysync->{automap} ) ? $mysync->{automap} : 1 ;
  11104. $mysync->{maxsleep} = ( defined $mysync->{maxsleep} ) ? $mysync->{maxsleep} : $MAX_SLEEP ; ;
  11105. $skipcrossduplicates = ( defined $skipcrossduplicates ) ? $skipcrossduplicates : 0 ;
  11106. $mysync->{ synclabels } = ( defined $mysync->{ synclabels } ) ? $mysync->{ synclabels } : 1 ;
  11107. $mysync->{ reynclabels } = ( defined $mysync->{ reynclabels } ) ? $mysync->{ reynclabels } : 1 ;
  11108. push @exclude, '\[Gmail\]$' ;
  11109. push @folderlast, '[Gmail]/All Mail' ;
  11110. return ;
  11111. }
  11112. sub gmail1
  11113. {
  11114. my $mysync = shift ;
  11115. # Gmail at host2
  11116. $mysync->{host1} ||= 'imap.gmail.com' ;
  11117. $mysync->{ssl1} = ( defined $mysync->{ssl1} ) ? $mysync->{ssl1} : 1 ;
  11118. $mysync->{maxbytespersecond} ||= 40_000 ; # should be 20_000 computed from by Gmail documentation
  11119. $mysync->{maxbytesafter} ||= 2_500_000_000 ;
  11120. $mysync->{automap} = ( defined $mysync->{automap} ) ? $mysync->{automap} : 1 ;
  11121. $mysync->{maxsleep} = ( defined $mysync->{maxsleep} ) ? $mysync->{maxsleep} : $MAX_SLEEP ; ;
  11122. $skipcrossduplicates = ( defined $skipcrossduplicates ) ? $skipcrossduplicates : 1 ;
  11123. push @useheader, 'X-Gmail-Received', 'Message-Id' ;
  11124. push @{ $mysync->{ regextrans2 } }, 's,\[Gmail\].,,' ;
  11125. push @folderlast, '[Gmail]/All Mail' ;
  11126. return ;
  11127. }
  11128. sub gmail2
  11129. {
  11130. my $mysync = shift ;
  11131. # Gmail at host2
  11132. $mysync->{host2} ||= 'imap.gmail.com' ;
  11133. $mysync->{ssl2} = ( defined $mysync->{ssl2} ) ? $mysync->{ssl2} : 1 ;
  11134. $mysync->{maxbytespersecond} ||= 20_000 ; # should be 10_000 computed from by Gmail documentation
  11135. $mysync->{maxbytesafter} ||= 1_000_000_000 ; # In fact it is documented as half: 500_000_000
  11136. #$mysync->{ maxsize } ||= 25_000_000 ;
  11137. $mysync->{automap} = ( defined $mysync->{automap} ) ? $mysync->{automap} : 1 ;
  11138. #$skipcrossduplicates = ( defined $skipcrossduplicates ) ? $skipcrossduplicates : 1 ;
  11139. $mysync->{ expunge1 } = ( defined $mysync->{ expunge1 } ) ? $mysync->{ expunge1 } : 1 ;
  11140. $mysync->{addheader} = ( defined $mysync->{addheader} ) ? $mysync->{addheader} : 1 ;
  11141. $mysync->{maxsleep} = ( defined $mysync->{maxsleep} ) ? $mysync->{maxsleep} : $MAX_SLEEP ; ;
  11142. $mysync->{maxsize} = ( defined $mysync->{maxsize} ) ? $mysync->{maxsize} : $GMAIL_MAXSIZE ;
  11143. if ( ! $mysync->{noexclude} ) {
  11144. push @exclude, '\[Gmail\]$' ;
  11145. }
  11146. push @useheader, 'Message-Id' ;
  11147. push @{ $mysync->{ regextrans2 } }, 's,\[Gmail\].,,' ;
  11148. # push @{ $mysync->{ regextrans2 } }, 's/[ ]+/_/g' ; # is now replaced
  11149. # by the two more specific following regexes,
  11150. # they remove just the beginning and trailing blanks, not all.
  11151. push @{ $mysync->{ regextrans2 } }, 's,^ +| +$,,g' ;
  11152. push @{ $mysync->{ regextrans2 } }, 's,/ +| +/,/,g' ;
  11153. #
  11154. push @{ $mysync->{ regextrans2 } }, q{s/['\\^"]/_/g} ; # Verified this
  11155. push @folderlast, '[Gmail]/All Mail' ;
  11156. return ;
  11157. }
  11158. # From https://imapsync.lamiral.info/FAQ.d/FAQ.Exchange.txt
  11159. sub office1
  11160. {
  11161. # Office 365 at host1
  11162. my $mysync = shift ;
  11163. output( $mysync, q{Option --office1 is like: --host1 outlook.office365.com --ssl1 --exclude "^Files$"} . "\n" ) ;
  11164. output( $mysync, "Option --office1 (cont) : unless overrided with --host1 otherhost --nossl1 --noexclude\n" ) ;
  11165. $mysync->{host1} ||= 'outlook.office365.com' ;
  11166. $mysync->{ssl1} = ( defined $mysync->{ssl1} ) ? $mysync->{ssl1} : 1 ;
  11167. if ( ! $mysync->{noexclude} ) {
  11168. push @exclude, '^Files$' ;
  11169. }
  11170. return ;
  11171. }
  11172. sub office2
  11173. {
  11174. # Office 365 at host2
  11175. my $mysync = shift ;
  11176. output( $mysync, qq{Option --office2 is like: --host2 outlook.office365.com --ssl2 --maxsize 45_000_000 --maxmessagespersecond 4\n} ) ;
  11177. output( $mysync, qq{Option --office2 (cont) : --disarmreadreceipts --regexmess "wrap 10500" --f1f2 "Files=Files_renamed_by_imapsync"\n} ) ;
  11178. output( $mysync, qq{Option --office2 (cont) : unless overrided with --host2 otherhost --nossl2 ... --nodisarmreadreceipts --noregexmess\n} ) ;
  11179. output( $mysync, qq{Option --office2 (cont) : and --nof1f2 to avoid Files folder renamed to Files_renamed_by_imapsync\n} ) ;
  11180. $mysync->{host2} ||= 'outlook.office365.com' ;
  11181. $mysync->{ssl2} = ( defined $mysync->{ssl2} ) ? $mysync->{ssl2} : 1 ;
  11182. $mysync->{ maxsize } ||= 45_000_000 ;
  11183. $mysync->{maxmessagespersecond} ||= 4 ;
  11184. #push @regexflag, 's/\\\\Flagged//g' ; # No problem without! tested 2018_09_10
  11185. $disarmreadreceipts = ( defined $disarmreadreceipts ) ? $disarmreadreceipts : 1 ;
  11186. # I dislike double negation but here is one
  11187. if ( ! $mysync->{noregexmess} )
  11188. {
  11189. push @regexmess, 's,(.{10500}),$1\r\n,g' ;
  11190. }
  11191. # and another...
  11192. if ( ! $mysync->{nof1f2} )
  11193. {
  11194. push @{ $mysync->{f1f2} }, 'Files=Files_renamed_by_imapsync' ;
  11195. }
  11196. return ;
  11197. }
  11198. sub exchange1
  11199. {
  11200. # Exchange 2010/2013 at host1
  11201. my $mysync = shift ;
  11202. output( $mysync, "Option --exchange1 does nothing (except printing this line...)\n" ) ;
  11203. # Well nothing to do so far
  11204. return ;
  11205. }
  11206. sub exchange2
  11207. {
  11208. # Exchange 2010/2013 at host2
  11209. my $mysync = shift ;
  11210. output( $mysync, "Option --exchange2 is like: --maxsize 10_000_000 --maxmessagespersecond 4 --disarmreadreceipts\n" ) ;
  11211. output( $mysync, "Option --exchange2 (cont) : --regexflag del Flagged --regexmess wrap 10500\n" ) ;
  11212. output( $mysync, "Option --exchange2 (cont) : unless overrided with --maxsize xxx --nodisarmreadreceipts --noregexflag --noregexmess\n" ) ;
  11213. $mysync->{ maxsize } ||= 10_000_000 ;
  11214. $mysync->{maxmessagespersecond} ||= 4 ;
  11215. $disarmreadreceipts = ( defined $disarmreadreceipts ) ? $disarmreadreceipts : 1 ;
  11216. # I dislike double negation but here are two
  11217. if ( ! $mysync->{noregexflag} ) {
  11218. push @regexflag, 's/\\\\Flagged//g' ;
  11219. }
  11220. if ( ! $mysync->{noregexmess} ) {
  11221. push @regexmess, 's,(.{10500}),$1\r\n,g' ;
  11222. }
  11223. return ;
  11224. }
  11225. sub domino1
  11226. {
  11227. # Domino at host1
  11228. my $mysync = shift ;
  11229. $mysync->{ sep1 } = q{\\} ;
  11230. $prefix1 = q{} ;
  11231. $messageidnodomain = ( defined $messageidnodomain ) ? $messageidnodomain : 1 ;
  11232. return ;
  11233. }
  11234. sub domino2
  11235. {
  11236. # Domino at host1
  11237. my $mysync = shift ;
  11238. $mysync->{ sep2 } = q{\\} ;
  11239. $prefix2 = q{} ;
  11240. $messageidnodomain = ( defined $messageidnodomain ) ? $messageidnodomain : 1 ;
  11241. push @{ $mysync->{ regextrans2 } }, 's,^Inbox\\\\(.*),$1,i' ;
  11242. return ;
  11243. }
  11244. sub tests_resolv
  11245. {
  11246. note( 'Entering tests_resolv()' ) ;
  11247. # is( , resolv( ), 'resolv: => ' ) ;
  11248. is( undef, resolv( ), 'resolv: no args => undef' ) ;
  11249. is( undef, resolv( '' ), 'resolv: empty string => undef' ) ;
  11250. is( undef, resolv( 'hostnotexist' ), 'resolv: hostnotexist => undef' ) ;
  11251. is( '127.0.0.1', resolv( '127.0.0.1' ), 'resolv: 127.0.0.1 => 127.0.0.1' ) ;
  11252. is( '127.0.0.1', resolv( 'localhost' ), 'resolv: localhost => 127.0.0.1' ) ;
  11253. is( '5.135.158.182', resolv( 'imapsync.lamiral.info' ), 'resolv: imapsync.lamiral.info => 5.135.158.182' ) ;
  11254. # ip6-localhost ( in /etc/hosts )
  11255. is( '::1', resolv( 'ip6-localhost' ), 'resolv: ip6-localhost => ::1' ) ;
  11256. is( '::1', resolv( '::1' ), 'resolv: ::1 => ::1' ) ;
  11257. # ks2
  11258. is( '2001:41d0:8:d8b6::1', resolv( '2001:41d0:8:d8b6::1' ), 'resolv: 2001:41d0:8:d8b6::1 => 2001:41d0:8:d8b6::1' ) ;
  11259. is( '2001:41d0:8:d8b6::1', resolv( 'ks2ipv6.lamiral.info' ), 'resolv: ks2ipv6.lamiral.info => 2001:41d0:8:d8b6::1' ) ;
  11260. # ks3
  11261. is( '2001:41d0:8:bebd::1', resolv( '2001:41d0:8:bebd::1' ), 'resolv: 2001:41d0:8:bebd::1 => 2001:41d0:8:bebd::1' ) ;
  11262. is( '2001:41d0:8:bebd::1', resolv( 'ks3ipv6.lamiral.info' ), 'resolv: ks3ipv6.lamiral.info => 2001:41d0:8:bebd::1' ) ;
  11263. note( 'Leaving tests_resolv()' ) ;
  11264. return ;
  11265. }
  11266. sub resolv
  11267. {
  11268. my $host = shift @ARG ;
  11269. if ( ! $host ) { return ; }
  11270. my $addr ;
  11271. if ( defined &Socket::getaddrinfo ) {
  11272. $addr = resolv_with_getaddrinfo( $host ) ;
  11273. return( $addr ) ;
  11274. }
  11275. my $iaddr = inet_aton( $host ) ;
  11276. if ( ! $iaddr ) { return ; }
  11277. $addr = inet_ntoa( $iaddr ) ;
  11278. return $addr ;
  11279. }
  11280. sub resolv_with_getaddrinfo
  11281. {
  11282. my $host = shift @ARG ;
  11283. if ( ! $host ) { return ; }
  11284. my ( $err_getaddrinfo, @res ) = Socket::getaddrinfo( $host, "", { socktype => Socket::SOCK_RAW } ) ;
  11285. if ( $err_getaddrinfo ) {
  11286. myprint( "Cannot getaddrinfo of $host: $err_getaddrinfo\n" ) ;
  11287. return ;
  11288. }
  11289. my @addr ;
  11290. while( my $ai = shift @res ) {
  11291. my ( $err_getnameinfo, $ipaddr ) = Socket::getnameinfo( $ai->{addr}, Socket::NI_NUMERICHOST(), Socket::NIx_NOSERV() ) ;
  11292. if ( $err_getnameinfo ) {
  11293. myprint( "Cannot getnameinfo of $host: $err_getnameinfo\n" ) ;
  11294. return ;
  11295. }
  11296. $sync->{ debug } and myprint( "$host => $ipaddr\n" ) ;
  11297. push @addr, $ipaddr ;
  11298. my $reverse ;
  11299. ( $err_getnameinfo, $reverse ) = Socket::getnameinfo( $ai->{addr}, 0, Socket::NIx_NOSERV() ) ;
  11300. $sync->{ debug } and myprint( "$host => $ipaddr => $reverse\n" ) ;
  11301. }
  11302. return $addr[0] ;
  11303. }
  11304. sub tests_resolvrev
  11305. {
  11306. note( 'Entering tests_resolvrev()' ) ;
  11307. # is( , resolvrev( ), 'resolvrev: => ' ) ;
  11308. is( undef, resolvrev( ), 'resolvrev: no args => undef' ) ;
  11309. is( undef, resolvrev( '' ), 'resolvrev: empty string => undef' ) ;
  11310. is( undef, resolvrev( 'hostnotexist' ), 'resolvrev: hostnotexist => undef' ) ;
  11311. is( 'localhost', resolvrev( '127.0.0.1' ), 'resolvrev: 127.0.0.1 => localhost' ) ;
  11312. is( 'localhost', resolvrev( 'localhost' ), 'resolvrev: localhost => localhost' ) ;
  11313. is( 'ks.lamiral.info', resolvrev( 'imapsync.lamiral.info' ), 'resolvrev: imapsync.lamiral.info => ks.lamiral.info' ) ;
  11314. # ip6-localhost ( in /etc/hosts )
  11315. is( 'ip6-localhost', resolvrev( 'ip6-localhost' ), 'resolvrev: ip6-localhost => ip6-localhost' ) ;
  11316. is( 'ip6-localhost', resolvrev( '::1' ), 'resolvrev: ::1 => ip6-localhost' ) ;
  11317. # ks2
  11318. is( 'ks2ipv6.lamiral.info', resolvrev( '2001:41d0:8:d8b6::1' ), 'resolvrev: 2001:41d0:8:d8b6::1 => ks2ipv6.lamiral.info' ) ;
  11319. is( 'ks2ipv6.lamiral.info', resolvrev( 'ks2ipv6.lamiral.info' ), 'resolvrev: ks2ipv6.lamiral.info => ks2ipv6.lamiral.info' ) ;
  11320. # ks3
  11321. is( 'ks3ipv6.lamiral.info', resolvrev( '2001:41d0:8:bebd::1' ), 'resolvrev: 2001:41d0:8:bebd::1 => ks3ipv6.lamiral.info' ) ;
  11322. is( 'ks3ipv6.lamiral.info', resolvrev( 'ks3ipv6.lamiral.info' ), 'resolvrev: ks3ipv6.lamiral.info => ks3ipv6.lamiral.info' ) ;
  11323. note( 'Leaving tests_resolvrev()' ) ;
  11324. return ;
  11325. }
  11326. sub resolvrev
  11327. {
  11328. my $host = shift @ARG ;
  11329. if ( ! $host ) { return ; }
  11330. if ( defined &Socket::getaddrinfo ) {
  11331. my $name = resolvrev_with_getaddrinfo( $host ) ;
  11332. return( $name ) ;
  11333. }
  11334. return ;
  11335. }
  11336. sub resolvrev_with_getaddrinfo
  11337. {
  11338. my $host = shift @ARG ;
  11339. if ( ! $host ) { return ; }
  11340. my ( $err, @res ) = Socket::getaddrinfo( $host, "", { socktype => Socket::SOCK_RAW } ) ;
  11341. if ( $err ) {
  11342. myprint( "Cannot getaddrinfo of $host: $err\n" ) ;
  11343. return ;
  11344. }
  11345. my @name ;
  11346. while( my $ai = shift @res ) {
  11347. my ( $err, $reverse ) = Socket::getnameinfo( $ai->{addr}, 0, Socket::NIx_NOSERV() ) ;
  11348. if ( $err ) {
  11349. myprint( "Cannot getnameinfo of $host: $err\n" ) ;
  11350. return ;
  11351. }
  11352. $sync->{ debug } and myprint( "$host => $reverse\n" ) ;
  11353. push @name, $reverse ;
  11354. }
  11355. return $name[0] ;
  11356. }
  11357. sub tests_imapsping
  11358. {
  11359. note( 'Entering tests_imapsping()' ) ;
  11360. is( undef, imapsping( ), 'imapsping: no args => undef' ) ;
  11361. is( undef, imapsping( 'hostnotexist' ), 'imapsping: hostnotexist => undef' ) ;
  11362. is( 1, imapsping( 'imapsync.lamiral.info' ), 'imapsping: imapsync.lamiral.info => 1' ) ;
  11363. is( 1, imapsping( 'ks2ipv6.lamiral.info' ), 'imapsping: ks2ipv6.lamiral.info => 1' ) ;
  11364. note( 'Leaving tests_imapsping()' ) ;
  11365. return ;
  11366. }
  11367. sub imapsping
  11368. {
  11369. my $host = shift ;
  11370. return tcpping( $host, $IMAP_SSL_PORT ) ;
  11371. }
  11372. sub tests_tcpping
  11373. {
  11374. note( 'Entering tests_tcpping()' ) ;
  11375. is( undef, tcpping( ), 'tcpping: no args => undef' ) ;
  11376. is( undef, tcpping( 'hostnotexist' ), 'tcpping: one arg => undef' ) ;
  11377. is( undef, tcpping( undef, 888 ), 'tcpping: arg undef, port => undef' ) ;
  11378. is( undef, tcpping( 'hostnotexist', 993 ), 'tcpping: hostnotexist 993 => undef' ) ;
  11379. is( undef, tcpping( 'hostnotexist', 888 ), 'tcpping: hostnotexist 888 => undef' ) ;
  11380. is( 1, tcpping( 'imapsync.lamiral.info', 993 ), 'tcpping: imapsync.lamiral.info 993 => 1' ) ;
  11381. is( 0, tcpping( 'imapsync.lamiral.info', 888 ), 'tcpping: imapsync.lamiral.info 888 => 0' ) ;
  11382. is( 1, tcpping( '5.135.158.182', 993 ), 'tcpping: 5.135.158.182 993 => 1' ) ;
  11383. is( 0, tcpping( '5.135.158.182', 888 ), 'tcpping: 5.135.158.182 888 => 0' ) ;
  11384. # Net::Ping supports ipv6 only after release 1.50
  11385. # http://cpansearch.perl.org/src/RURBAN/Net-Ping-2.59/Changes
  11386. # Anyway I plan to avoid Net-Ping for that too long standing feature
  11387. # Net-Ping is integrated in Perl itself, who knows ipv6 for a long time
  11388. is( 1, tcpping( '2001:41d0:8:d8b6::1', 993 ), 'tcpping: 2001:41d0:8:d8b6::1 993 => 1' ) ;
  11389. is( 0, tcpping( '2001:41d0:8:d8b6::1', 888 ), 'tcpping: 2001:41d0:8:d8b6::1 888 => 0' ) ;
  11390. note( 'Leaving tests_tcpping()' ) ;
  11391. return ;
  11392. }
  11393. sub tcpping
  11394. {
  11395. if ( 2 != scalar( @ARG ) ) {
  11396. return ;
  11397. }
  11398. my ( $host, $port ) = @ARG ;
  11399. if ( ! $host ) { return ; }
  11400. if ( ! $port ) { return ; }
  11401. my $mytimeout = $TCP_PING_TIMEOUT ;
  11402. require Net::Ping ;
  11403. #my $p = Net::Ping->new( 'tcp' ) ;
  11404. my $p = Net::Ping->new( ) ;
  11405. $p->{port_num} = $port ;
  11406. $p->service_check( 1 ) ;
  11407. $p->hires( 1 ) ;
  11408. my ($ping_ok, $rtt, $ip ) = $p->ping( $host, $mytimeout ) ;
  11409. if ( ! defined $ping_ok ) { return ; }
  11410. my $rtt_approx = sprintf( "%.3f", $rtt ) ;
  11411. $sync->{ debug } and myprint( "Host $host timeout $mytimeout port $port ok $ping_ok ip $ip acked in $rtt_approx s\n" ) ;
  11412. $p->close( ) ;
  11413. if( $ping_ok ) {
  11414. return 1 ;
  11415. }else{
  11416. return 0 ;
  11417. }
  11418. }
  11419. sub tests_sslcheck
  11420. {
  11421. note( 'Entering tests_sslcheck()' ) ;
  11422. my $mysync ;
  11423. is( undef, sslcheck( $mysync ), 'sslcheck: no sslcheck => undef' ) ;
  11424. $mysync = {
  11425. sslcheck => 1,
  11426. } ;
  11427. is( 0, sslcheck( $mysync ), 'sslcheck: no host => 0' ) ;
  11428. $mysync = {
  11429. sslcheck => 1,
  11430. host1 => 'imapsync.lamiral.info',
  11431. tls1 => 1,
  11432. } ;
  11433. is( 0, sslcheck( $mysync ), 'sslcheck: tls1 => 0' ) ;
  11434. $mysync = {
  11435. sslcheck => 1,
  11436. host1 => 'imapsync.lamiral.info',
  11437. } ;
  11438. is( 1, sslcheck( $mysync ), 'sslcheck: imapsync.lamiral.info => 1' ) ;
  11439. is( 1, $mysync->{ssl1}, 'sslcheck: imapsync.lamiral.info => ssl1 1' ) ;
  11440. $mysync->{sslcheck} = 0 ;
  11441. is( undef, sslcheck( $mysync ), 'sslcheck: sslcheck off => undef' ) ;
  11442. $mysync = {
  11443. sslcheck => 1,
  11444. host1 => 'imapsync.lamiral.info',
  11445. host2 => 'test2.lamiral.info',
  11446. } ;
  11447. is( 2, sslcheck( $mysync ), 'sslcheck: imapsync.lamiral.info + test2.lamiral.info => 2' ) ;
  11448. $mysync = {
  11449. sslcheck => 1,
  11450. host1 => 'imapsync.lamiral.info',
  11451. host2 => 'test2.lamiral.info',
  11452. tls1 => 1,
  11453. } ;
  11454. is( 1, sslcheck( $mysync ), 'sslcheck: imapsync.lamiral.info + test2.lamiral.info + tls1 => 1' ) ;
  11455. note( 'Leaving tests_sslcheck()' ) ;
  11456. return ;
  11457. }
  11458. sub sslcheck
  11459. {
  11460. my $mysync = shift ;
  11461. if ( ! $mysync->{sslcheck} ) {
  11462. return ;
  11463. }
  11464. my $nb_on = 0 ;
  11465. $mysync->{ debug } and myprint( "sslcheck\n" ) ;
  11466. if (
  11467. ( ! defined $mysync->{port1} )
  11468. and
  11469. ( ! defined $mysync->{tls1} )
  11470. and
  11471. ( ! defined $mysync->{ssl1} )
  11472. and
  11473. ( defined $mysync->{host1} )
  11474. ) {
  11475. myprint( "Host1: probing ssl on port $IMAP_SSL_PORT ( use --nosslcheck to avoid this ssl probe ) \n" ) ;
  11476. if ( probe_imapssl( $mysync->{host1} ) ) {
  11477. $mysync->{ssl1} = 1 ;
  11478. 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" ) ;
  11479. $nb_on++ ;
  11480. }else{
  11481. myprint( "Host1: sslcheck did not detected open ssl port $IMAP_SSL_PORT. Will use standard $IMAP_PORT port.\n" ) ;
  11482. }
  11483. }
  11484. if (
  11485. ( ! defined $mysync->{port2} )
  11486. and
  11487. ( ! defined $mysync->{tls2} )
  11488. and
  11489. ( ! defined $mysync->{ssl2} )
  11490. and
  11491. ( defined $mysync->{host2} )
  11492. ) {
  11493. myprint( "Host2: probing ssl on port $IMAP_SSL_PORT ( use --nosslcheck to avoid this ssl probe ) \n" ) ;
  11494. if ( probe_imapssl( $mysync->{host2} ) ) {
  11495. $mysync->{ssl2} = 1 ;
  11496. 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" ) ;
  11497. $nb_on++ ;
  11498. }else{
  11499. myprint( "Host2: sslcheck did not detected open ssl port $IMAP_SSL_PORT. Will use standard $IMAP_PORT port.\n" ) ;
  11500. }
  11501. }
  11502. return $nb_on ;
  11503. }
  11504. sub testslive
  11505. {
  11506. my $mysync = shift ;
  11507. $mysync->{host1} ||= 'test1.lamiral.info' ;
  11508. $mysync->{user1} ||= 'test1' ;
  11509. $mysync->{password1} ||= 'secret1' ;
  11510. $mysync->{host2} ||= 'test2.lamiral.info' ;
  11511. $mysync->{user2} ||= 'test2' ;
  11512. $mysync->{password2} ||= 'secret2' ;
  11513. return ;
  11514. }
  11515. sub testslive6
  11516. {
  11517. my $mysync = shift ;
  11518. $mysync->{host1} ||= 'ks2ipv6.lamiral.info' ;
  11519. $mysync->{user1} ||= 'test1' ;
  11520. $mysync->{password1} ||= 'secret1' ;
  11521. $mysync->{host2} ||= 'ks2ipv6.lamiral.info' ;
  11522. $mysync->{user2} ||= 'test2' ;
  11523. $mysync->{password2} ||= 'secret2' ;
  11524. return ;
  11525. }
  11526. sub tests_backslash_caret
  11527. {
  11528. note( 'Entering tests_backslash_caret()' ) ;
  11529. is( "lalala", backslash_caret( "lalala" ), 'backslash_caret: lalala => lalala' ) ;
  11530. is( "lalala\n", backslash_caret( "lalala\n" ), 'backslash_caret: lalala => lalala 2nd' ) ;
  11531. is( '^', backslash_caret( '\\' ), 'backslash_caret: \\ => ^' ) ;
  11532. is( "^\n", backslash_caret( "\\\n" ), 'backslash_caret: \\ => ^' ) ;
  11533. is( "\\lalala", backslash_caret( "\\lalala" ), 'backslash_caret: \\lalala => \\lalala' ) ;
  11534. is( "\\lal\\ala", backslash_caret( "\\lal\\ala" ), 'backslash_caret: \\lal\\ala => \\lal\\ala' ) ;
  11535. is( "\\lalala\n", backslash_caret( "\\lalala\n" ), 'backslash_caret: \\lalala => \\lalala 2nd' ) ;
  11536. is( "lalala^\n", backslash_caret( "lalala\\\n" ), 'backslash_caret: lalala\\\n => lalala^\n' ) ;
  11537. is( "lalala^\nlalala^\n", backslash_caret( "lalala\\\nlalala\\\n" ), 'backslash_caret: lalala\\\nlalala\\\n => lalala^\nlalala^\n' ) ;
  11538. is( "lal\\ala^\nlalala^\n", backslash_caret( "lal\\ala\\\nlalala\\\n" ), 'backslash_caret: lal\\ala\\\nlalala\\\n => lal\\ala^\nlalala^\n' ) ;
  11539. note( 'Leaving tests_backslash_caret()' ) ;
  11540. return ;
  11541. }
  11542. sub backslash_caret
  11543. {
  11544. my $string = shift ;
  11545. $string =~ s{\\ $ }{^}gxms ;
  11546. return $string ;
  11547. }
  11548. sub tests_split_around_equal
  11549. {
  11550. note( 'Entering tests_split_around_equal()' ) ;
  11551. is( undef, split_around_equal( ), 'split_around_equal: no args => undef' ) ;
  11552. is_deeply( { toto => 'titi' }, { split_around_equal( 'toto=titi' ) }, 'split_around_equal: toto=titi => toto => titi' ) ;
  11553. is_deeply( { A => 'B', C => 'D' }, { split_around_equal( 'A=B=C=D' ) }, 'split_around_equal: toto=titi => toto => titi' ) ;
  11554. is_deeply( { A => 'B', C => 'D' }, { split_around_equal( 'A=B', 'C=D' ) }, 'split_around_equal: A=B C=D => A => B, C=>D' ) ;
  11555. note( 'Leaving tests_split_around_equal()' ) ;
  11556. return ;
  11557. }
  11558. sub split_around_equal
  11559. {
  11560. if ( ! @ARG ) { return ; } ;
  11561. return map { split /=/mxs, $_ } @ARG ;
  11562. }
  11563. sub tests_sig_install
  11564. {
  11565. note( 'Entering tests_sig_install()' ) ;
  11566. my $mysync ;
  11567. is( undef, sig_install( ), 'sig_install: no args => undef' ) ;
  11568. is( undef, sig_install( $mysync ), 'sig_install: arg undef => undef' ) ;
  11569. $mysync = { } ;
  11570. is( undef, sig_install( $mysync ), 'sig_install: empty hash => undef' ) ;
  11571. SKIP: {
  11572. Readonly my $SKIP_15 => 15 ;
  11573. if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests only for Unix', $SKIP_15 ) ; }
  11574. # Default to ignore USR1 USR2 in case future install fails
  11575. local $SIG{ USR1 } = local $SIG{ USR2 } = sub { } ;
  11576. kill( 'USR1', $PROCESS_ID ) ;
  11577. $mysync->{ debugsig } = 1 ;
  11578. # Assign USR1 to call sub tototo
  11579. # Surely a better value than undef should be returned when doing real signal stuff
  11580. is( undef, sig_install( $mysync, \&tototo, 'USR1' ), 'sig_install: USR1 tototo' ) ;
  11581. is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 1' ) ;
  11582. is( 1, $mysync->{ tototo_calls }, 'sig_install: tototo call nb 1' ) ;
  11583. # Assign USR2 to call sub tototo
  11584. is( undef, sig_install( $mysync, \&tototo, 'USR2' ), 'sig_install: USR2 tototo' ) ;
  11585. is( 1, kill( 'USR2', $PROCESS_ID ), 'sig_install: kill USR2 myself 1' ) ;
  11586. is( 2, $mysync->{ tototo_calls }, 'sig_install: tototo call nb 2' ) ;
  11587. is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 2' ) ;
  11588. is( 3, $mysync->{ tototo_calls }, 'sig_install: tototo call nb 3' ) ;
  11589. local $SIG{ USR1 } = local $SIG{ USR2 } = sub { } ;
  11590. is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 3' ) ;
  11591. is( 3, $mysync->{ tototo_calls }, 'sig_install: tototo call still nb 3' ) ;
  11592. # Assign USR1 + USR2 to call sub tototo
  11593. is( undef, sig_install( $mysync, \&tototo, 'USR1', 'USR2' ), 'sig_install: USR1 USR2 tototo' ) ;
  11594. is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 4' ) ;
  11595. is( 4, $mysync->{ tototo_calls }, 'sig_install: tototo call now nb 4' ) ;
  11596. is( 1, kill( 'USR2', $PROCESS_ID ), 'sig_install: kill USR1 myself 2' ) ;
  11597. is( 5, $mysync->{ tototo_calls }, 'sig_install: tototo call now nb 5' ) ;
  11598. }
  11599. note( 'Leaving tests_sig_install()' ) ;
  11600. return ;
  11601. }
  11602. #
  11603. sub sig_install
  11604. {
  11605. my $mysync = shift ;
  11606. if ( ! $mysync ) { return ; }
  11607. my $mysub = shift ;
  11608. if ( ! $mysub ) { return ; }
  11609. my @signals = @ARG ;
  11610. $mysync->{ debugsig } and myprint( "In sig_install with $mysync and $mysub\n" ) ;
  11611. my $subsignal = sub {
  11612. my $signame = shift ;
  11613. $mysync->{ debugsig } and myprint( "In subsignal with $signame and $mysync\n" ) ;
  11614. &$mysub( $mysync, $signame ) ;
  11615. } ;
  11616. foreach my $signal ( @signals ) {
  11617. $mysync->{ debugsig } and myprint( "Installing signal $signal for $subsignal\n") ;
  11618. output( $mysync, "kill -$signal $PROCESS_ID # special behavior\n" ) ;
  11619. ## no critic (RequireLocalizedPunctuationVars)
  11620. $SIG{ $signal } = $subsignal ;
  11621. }
  11622. return ;
  11623. }
  11624. sub tototo
  11625. {
  11626. my $mysync = shift ;
  11627. myprint("In tototo with @ARG\n" ) ;
  11628. $mysync->{ tototo_calls } += 1 ;
  11629. return ;
  11630. }
  11631. sub mygetppid
  11632. {
  11633. if ( 'MSWin32' eq $OSNAME ) {
  11634. return( 'unknown under MSWin32 (too complicated)' ) ;
  11635. } else {
  11636. # Unix
  11637. return( getppid( ) ) ;
  11638. }
  11639. }
  11640. sub tests_toggle_sleep
  11641. {
  11642. note( 'Entering tests_toggle_sleep()' ) ;
  11643. is( undef, toggle_sleep( ), 'toggle_sleep: no args => undef' ) ;
  11644. my $mysync ;
  11645. is( undef, toggle_sleep( $mysync ), 'toggle_sleep: undef => undef' ) ;
  11646. $mysync = { } ;
  11647. is( undef, toggle_sleep( $mysync ), 'toggle_sleep: no maxsleep => undef' ) ;
  11648. $mysync->{maxsleep} = 3 ;
  11649. is( 0, toggle_sleep( $mysync ), 'toggle_sleep: 3 => 0' ) ;
  11650. is( $MAX_SLEEP, toggle_sleep( $mysync ), "toggle_sleep: 0 => $MAX_SLEEP" ) ;
  11651. is( 0, toggle_sleep( $mysync ), "toggle_sleep: $MAX_SLEEP => 0" ) ;
  11652. is( $MAX_SLEEP, toggle_sleep( $mysync ), "toggle_sleep: 0 => $MAX_SLEEP" ) ;
  11653. is( 0, toggle_sleep( $mysync ), "toggle_sleep: $MAX_SLEEP => 0" ) ;
  11654. SKIP: {
  11655. Readonly my $SKIP_9 => 9 ;
  11656. if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests only for Unix', $SKIP_9 ) ; }
  11657. # Default to ignore USR1 USR2 in case future install fails
  11658. local $SIG{ USR1 } = sub { } ;
  11659. kill( 'USR1', $PROCESS_ID ) ;
  11660. $mysync->{ debugsig } = 1 ;
  11661. # Assign USR1 to call sub toggle_sleep
  11662. is( undef, sig_install( $mysync, \&toggle_sleep, 'USR1' ), 'toggle_sleep: install USR1 toggle_sleep' ) ;
  11663. $mysync->{maxsleep} = 4 ;
  11664. is( 1, kill( 'USR1', $PROCESS_ID ), 'toggle_sleep: kill USR1 myself' ) ;
  11665. is( 0, $mysync->{ maxsleep }, 'toggle_sleep: toggle_sleep called => sleeps are 0s' ) ;
  11666. is( 1, kill( 'USR1', $PROCESS_ID ), 'toggle_sleep: kill USR1 myself again' ) ;
  11667. is( $MAX_SLEEP, $mysync->{ maxsleep }, "toggle_sleep: toggle_sleep called => sleeps are ${MAX_SLEEP}s" ) ;
  11668. is( 1, kill( 'USR1', $PROCESS_ID ), 'toggle_sleep: kill USR1 myself' ) ;
  11669. is( 0, $mysync->{ maxsleep }, 'toggle_sleep: toggle_sleep called => sleeps are 0s' ) ;
  11670. is( 1, kill( 'USR1', $PROCESS_ID ), 'toggle_sleep: kill USR1 myself again' ) ;
  11671. is( $MAX_SLEEP, $mysync->{ maxsleep }, "toggle_sleep: toggle_sleep called => sleeps are ${MAX_SLEEP}s" ) ;
  11672. }
  11673. note( 'Leaving tests_toggle_sleep()' ) ;
  11674. return ;
  11675. }
  11676. sub toggle_sleep
  11677. {
  11678. my $mysync = shift ;
  11679. myprint("In toggle_sleep with @ARG\n" ) ;
  11680. if ( !defined( $mysync ) ) { return ; }
  11681. if ( !defined( $mysync->{maxsleep} ) ) { return ; }
  11682. $mysync->{ maxsleep } = max( 0, $MAX_SLEEP - $mysync->{maxsleep} ) ;
  11683. myprint("Resetting maxsleep to ", $mysync->{maxsleep}, "s\n" ) ;
  11684. return $mysync->{maxsleep} ;
  11685. }
  11686. sub mypod2usage
  11687. {
  11688. my $fh_pod2usage = shift ;
  11689. pod2usage(
  11690. -exitval => 'NOEXIT',
  11691. -noperldoc => 1,
  11692. -verbose => 99,
  11693. -sections => [ qw(NAME VERSION USAGE OPTIONS) ],
  11694. -indent => 1,
  11695. -loose => 1,
  11696. -output => $fh_pod2usage,
  11697. ) ;
  11698. return ;
  11699. }
  11700. sub usage
  11701. {
  11702. my $mysync = shift ;
  11703. if ( ! defined $mysync ) { return ; }
  11704. my $usage = q{} ;
  11705. my $usage_from_pod ;
  11706. my $usage_footer = usage_footer( $mysync ) ;
  11707. # pod2usage writes on a filehandle only and I want a variable
  11708. open my $fh_pod2usage, ">", \$usage_from_pod
  11709. or do { warn $OS_ERROR ; return ; } ;
  11710. mypod2usage( $fh_pod2usage ) ;
  11711. close $fh_pod2usage ;
  11712. if ( 'MSWin32' eq $OSNAME ) {
  11713. $usage_from_pod = backslash_caret( $usage_from_pod ) ;
  11714. }
  11715. $usage = join( q{}, $usage_from_pod, $usage_footer ) ;
  11716. return( $usage ) ;
  11717. }
  11718. sub tests_usage
  11719. {
  11720. note( 'Entering tests_usage()' ) ;
  11721. my $usage ;
  11722. like( $usage = usage( $sync ), qr/Name:/, 'usage: contains Name:' ) ;
  11723. myprint( $usage ) ;
  11724. like( $usage, qr/Version:/, 'usage: contains Version:' ) ;
  11725. like( $usage, qr/Usage:/, 'usage: contains Usage:' ) ;
  11726. like( $usage, qr/imapsync/, 'usage: contains imapsync' ) ;
  11727. is( undef, usage( ), 'usage: no args => undef' ) ;
  11728. note( 'Leaving tests_usage()' ) ;
  11729. return ;
  11730. }
  11731. sub usage_footer
  11732. {
  11733. my $mysync = shift ;
  11734. my $footer = q{} ;
  11735. my $localhost_info = localhost_info( $mysync ) ;
  11736. my $rcs = $mysync->{rcs} ;
  11737. my $homepage = homepage( ) ;
  11738. my $imapsync_release = $STR_use_releasecheck ;
  11739. if ( $mysync->{releasecheck} ) {
  11740. $imapsync_release = check_last_release( ) ;
  11741. }
  11742. $footer = qq{$localhost_info
  11743. $rcs
  11744. $imapsync_release
  11745. $homepage
  11746. } ;
  11747. return( $footer ) ;
  11748. }
  11749. sub usage_complete
  11750. {
  11751. # Unused, I guess this function could be deleted
  11752. my $usage = <<'EOF' ;
  11753. --skipheader reg : Don't take into account header keyword
  11754. matching reg ex: --skipheader 'X.*'
  11755. --skipsize : Don't take message size into account to compare
  11756. messages on both sides. On by default.
  11757. Use --no-skipsize for using size comparaison.
  11758. --allowsizemismatch : allow RFC822.SIZE != fetched msg size
  11759. consider also --skipsize to avoid duplicate messages
  11760. when running syncs more than one time per mailbox
  11761. --reconnectretry1 int : reconnect to host1 if connection is lost up to
  11762. int times per imap command (default is 3)
  11763. --reconnectretry2 int : same as --reconnectretry1 but for host2
  11764. --split1 int : split the requests in several parts on host1.
  11765. int is the number of messages handled per request.
  11766. default is like --split1 100.
  11767. --split2 int : same thing on host2.
  11768. --nofixInboxINBOX : Don't fix Inbox INBOX mapping.
  11769. EOF
  11770. return( $usage ) ;
  11771. }
  11772. sub myGetOptions
  11773. {
  11774. # Started as a copy of Luke Ross Getopt::Long::CGI
  11775. # https://metacpan.org/release/Getopt-Long-CGI
  11776. # So this sub function is under the same license as Getopt-Long-CGI Luke Ross wants it,
  11777. # which was Perl 5.6 or later licenses at the date of the copy.
  11778. my $mysync = shift @ARG ;
  11779. my $arguments_ref = shift @ARG ;
  11780. my %options = @ARG ;
  11781. my $mycgi = $mysync->{cgi} ;
  11782. if ( not under_cgi_context() ) {
  11783. # Not CGI - pass upstream for normal command line handling
  11784. return Getopt::Long::GetOptionsFromArray( $arguments_ref, %options ) ;
  11785. }
  11786. # We must be in CGI context now
  11787. if ( ! defined( $mycgi ) ) { return ; }
  11788. my $badthings = 0 ;
  11789. foreach my $key ( sort keys %options ) {
  11790. my $val = $options{$key} ;
  11791. if ( $key !~ m/^([\w\d\|]+)([=:][isf])?([\+!\@\%])?$/mxs ) {
  11792. $badthings++ ;
  11793. next ; # Unknown item
  11794. }
  11795. my $name = [ split '|', $1, 1 ]->[0] ;
  11796. if ( ( $3 || q{} ) eq '+' ) {
  11797. ${$val} = $mycgi->param( $name ) ; # "Incremental" integer
  11798. }
  11799. elsif ( $2 ) {
  11800. my @values = $mycgi->multi_param( $name ) ;
  11801. my $type = $2 ;
  11802. #myprint( "type[$type]values[@values]\$3[", $3 || q{}, "]val[$val]ref(val)[", ref($val), "]\n" ) ;
  11803. if ( ( $3 || q{} ) eq '%' or ref( $val ) eq 'HASH' ) {
  11804. my %values = map { split /=/mxs, $_ } @values ;
  11805. if ( $type =~ m/i$/mxs ) {
  11806. foreach my $k ( keys %values ) {
  11807. $values{$k} = int $values{$k} ;
  11808. }
  11809. }
  11810. elsif ( $type =~ m/f$/mxs ) {
  11811. foreach my $k ( keys %values ) {
  11812. $values{$k} = 0 + $values{$k};
  11813. }
  11814. }
  11815. if ( 'REF' eq ref $val ) {
  11816. %{ ${$val} } = %values ;
  11817. }
  11818. else {
  11819. %{$val} = %values ;
  11820. }
  11821. }
  11822. else {
  11823. if ( $type =~ m/i$/mxs ) {
  11824. @values = map { q{} ne $_ ? int $_ : undef } @values ;
  11825. }
  11826. elsif ( $type =~ m/f$/mxs ) {
  11827. @values = map { 0 + $_ } @values ;
  11828. }
  11829. if ( ( $3 || q{} ) eq '@' ) {
  11830. @{ ${$val} } = @values ;
  11831. my @option = map +( "--$name", "$_" ), @values ;
  11832. push @{ $mysync->{ cmdcgi } }, @option ;
  11833. }
  11834. elsif ( ref( $val ) eq 'ARRAY' ) {
  11835. @{$val} = @values ;
  11836. }
  11837. elsif ( my $value = $values[0] )
  11838. {
  11839. ${$val} = $value ;
  11840. push @{ $mysync->{ cmdcgi } }, "--$name", $value ;
  11841. }
  11842. else
  11843. {
  11844. }
  11845. }
  11846. }
  11847. else
  11848. {
  11849. # Checkbox
  11850. # Considers only --name
  11851. # Should consider also --no-name and --noname
  11852. my $value = $mycgi->param( $name ) ;
  11853. if ( $value )
  11854. {
  11855. ${$val} = 1 ;
  11856. push @{ $mysync->{ cmdcgi } }, "--$name" ;
  11857. }
  11858. else
  11859. {
  11860. ${$val} = undef ;
  11861. }
  11862. }
  11863. }
  11864. if ( $badthings ) {
  11865. return ; # undef or ()
  11866. }
  11867. else {
  11868. return ( 1 ) ;
  11869. }
  11870. }
  11871. my @blabla ; # just used to check get_options_cgi() with an array
  11872. sub tests_get_options_cgi_context
  11873. {
  11874. note( 'Entering tests_get_options_cgi()' ) ;
  11875. # Temporary, have to think harder about testing CGI context in command line --tests
  11876. # API:
  11877. # * input arguments: two ways, command line or CGI
  11878. # * the program arguments
  11879. # * QUERY_STRING env variable
  11880. # * return
  11881. # * QUERY_STRING length
  11882. # CGI context
  11883. local $ENV{SERVER_SOFTWARE} = 'Votre serviteur' ;
  11884. # Real full test
  11885. # = 'host1=test1.lamiral.info&user1=test1&password1=secret1&host2=test2.lamiral.info&user2=test2&password2=secret2&debugenv=on'
  11886. my $mysync ;
  11887. is( undef, get_options( $mysync ), 'get_options cgi context: no CGI module => undef' ) ;
  11888. require CGI ;
  11889. CGI->import( qw( -no_debug ) ) ;
  11890. is( undef, get_options( $mysync ), 'get_options cgi context: no CGI param => undef' ) ;
  11891. # Testing boolean
  11892. $mysync->{cgi} = CGI->new( 'version=on&debugenv=on' ) ;
  11893. local $ENV{'QUERY_STRING'} = 'version=on&debugenv=on' ;
  11894. is( 22, get_options( $mysync ), 'get_options cgi context: QUERY_STRING => 22' ) ;
  11895. is( 1, $mysync->{ version }, 'get_options cgi context: --version => 1' ) ;
  11896. # debugenv is not allowed in cgi context
  11897. is( undef, $mysync->{debugenv}, 'get_options cgi context: $mysync->{debugenv} => undef' ) ;
  11898. # QUERY_STRING in this test is only for return value of get_options
  11899. # Have to think harder, GET/POST context, is this return value a good thing?
  11900. local $ENV{'QUERY_STRING'} = 'host1=test1.lamiral.info&user1=test1' ;
  11901. $mysync->{cgi} = CGI->new( 'host1=test1.lamiral.info&user1=test1' ) ;
  11902. is( 36, get_options( $mysync, ), 'get_options cgi context: QUERY_STRING => 36' ) ;
  11903. is( 'test1', $mysync->{user1}, 'get_options cgi context: $mysync->{user1} => test1' ) ;
  11904. #local $ENV{'QUERY_STRING'} = undef ;
  11905. # Testing @
  11906. $mysync->{cgi} = CGI->new( 'blabla=fd1' ) ;
  11907. get_options( $mysync ) ;
  11908. is_deeply( [ 'fd1' ], [ @blabla ], 'get_options cgi context: @blabla => fd1' ) ;
  11909. $mysync->{cgi} = CGI->new( 'blabla=fd1&blabla=fd2' ) ;
  11910. get_options( $mysync ) ;
  11911. is_deeply( [ 'fd1', 'fd2' ], [ @blabla ], 'get_options cgi context: @blabla => fd1, fd2' ) ;
  11912. # Testing s@ as ref
  11913. $mysync->{cgi} = CGI->new( 'folder=fd1' ) ;
  11914. get_options( $mysync ) ;
  11915. is_deeply( [ 'fd1' ], $mysync->{ folder }, 'get_options cgi context: $mysync->{ folder } => fd1' ) ;
  11916. $mysync->{cgi} = CGI->new( 'folder=fd1&folder=fd2' ) ;
  11917. get_options( $mysync ) ;
  11918. is_deeply( [ 'fd1', 'fd2' ], $mysync->{ folder }, 'get_options cgi context: $mysync->{ folder } => fd1, fd2' ) ;
  11919. # Testing %
  11920. $mysync->{cgi} = CGI->new( 'f1f2h=s1=d1&f1f2h=s2=d2&f1f2h=s3=d3' ) ;
  11921. get_options( $mysync ) ;
  11922. is_deeply( { 's1' => 'd1', 's2' => 'd2', 's3' => 'd3' },
  11923. $mysync->{f1f2h}, 'get_options cgi context: f1f2h => s1=d1 s2=d2 s3=d3' ) ;
  11924. # Testing boolean ! with --noxxx, doesnot work
  11925. $mysync->{cgi} = CGI->new( 'nodry=on' ) ;
  11926. get_options( $mysync ) ;
  11927. is( undef, $mysync->{dry}, 'get_options cgi context: --nodry => $mysync->{dry} => undef' ) ;
  11928. $mysync->{cgi} = CGI->new( 'host1=example.com' ) ;
  11929. get_options( $mysync ) ;
  11930. is( 'example.com', $mysync->{host1}, 'get_options cgi context: --host1=example.com => $mysync->{host1} => example.com' ) ;
  11931. #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
  11932. $mysync->{cgi} = CGI->new( 'simulong=' ) ;
  11933. get_options( $mysync ) ;
  11934. is( undef, $mysync->{simulong}, 'get_options cgi context: --simulong= => $mysync->{simulong} => undef' ) ;
  11935. $mysync->{cgi} = CGI->new( 'simulong' ) ;
  11936. get_options( $mysync ) ;
  11937. is( undef, $mysync->{simulong}, 'get_options cgi context: --simulong => $mysync->{simulong} => undef' ) ;
  11938. $mysync->{cgi} = CGI->new( 'simulong=4' ) ;
  11939. get_options( $mysync ) ;
  11940. is( 4, $mysync->{simulong}, 'get_options cgi context: --simulong=4 => $mysync->{simulong} => 4' ) ;
  11941. is( undef, $mysync->{ folder }, 'get_options cgi context: --simulong=4 => $mysync->{ folder } => undef' ) ;
  11942. #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
  11943. $mysync ={} ;
  11944. $mysync->{cgi} = CGI->new( 'justfoldersizes=on' ) ;
  11945. get_options( $mysync ) ;
  11946. is( 1, $mysync->{ justfoldersizes }, 'get_options cgi context: --justfoldersizes=1 => justfoldersizes => 1' ) ;
  11947. myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
  11948. note( 'Leaving tests_get_options_cgi_context()' ) ;
  11949. return ;
  11950. }
  11951. sub get_options_cgi
  11952. {
  11953. # In CGI context arguments are not in @ARGV but in QUERY_STRING variable (with GET).
  11954. my $mysync = shift @ARG ;
  11955. $mysync->{cgi} || return ;
  11956. my @arguments = @ARG ;
  11957. # final 0 is used to print usage when no option is given
  11958. my $numopt = length $ENV{'QUERY_STRING'} || 1 ;
  11959. $mysync->{f1f2h} = {} ;
  11960. my $opt_ret = myGetOptions(
  11961. $mysync,
  11962. \@arguments,
  11963. 'abort' => \$mysync->{abort},
  11964. 'host1=s' => \$mysync->{host1},
  11965. 'host2=s' => \$mysync->{host2},
  11966. 'user1=s' => \$mysync->{user1},
  11967. 'user2=s' => \$mysync->{user2},
  11968. 'password1=s' => \$mysync->{password1},
  11969. 'password2=s' => \$mysync->{password2},
  11970. 'dry!' => \$mysync->{dry},
  11971. 'version' => \$mysync->{version},
  11972. 'ssl1!' => \$mysync->{ssl1},
  11973. 'ssl2!' => \$mysync->{ssl2},
  11974. 'tls1!' => \$mysync->{tls1},
  11975. 'tls2!' => \$mysync->{tls2},
  11976. 'justlogin!' => \$mysync->{justlogin},
  11977. 'justconnect!' => \$mysync->{justconnect},
  11978. 'addheader!' => \$mysync->{addheader},
  11979. 'automap!' => \$mysync->{automap},
  11980. 'justautomap!' => \$mysync->{justautomap},
  11981. 'gmail1' => \$mysync->{gmail1},
  11982. 'gmail2' => \$mysync->{gmail2},
  11983. 'office1' => \$mysync->{office1},
  11984. 'office2' => \$mysync->{office2},
  11985. 'exchange1' => \$mysync->{exchange1},
  11986. 'exchange2' => \$mysync->{exchange2},
  11987. 'domino1' => \$mysync->{domino1},
  11988. 'domino2' => \$mysync->{domino2},
  11989. 'f1f2=s@' => \$mysync->{f1f2},
  11990. 'f1f2h=s%' => \$mysync->{f1f2h},
  11991. 'folder=s@' => \$mysync->{ folder },
  11992. 'blabla=s' => \@blabla,
  11993. 'testslive!' => \$mysync->{testslive},
  11994. 'testslive6!' => \$mysync->{testslive6},
  11995. 'releasecheck!' => \$mysync->{releasecheck},
  11996. 'simulong=i' => \$mysync->{simulong},
  11997. 'debugsleep=f' => \$mysync->{debugsleep},
  11998. 'subfolder1=s' => \$mysync->{ subfolder1 },
  11999. 'subfolder2=s' => \$mysync->{ subfolder2 },
  12000. 'justfolders!' => \$mysync->{ justfolders },
  12001. 'justfoldersizes!' => \$mysync->{ justfoldersizes },
  12002. 'delete1!' => \$mysync->{ delete1 },
  12003. 'delete2!' => \$mysync->{ delete2 },
  12004. 'delete2duplicates!' => \$mysync->{ delete2duplicates },
  12005. 'tail!' => \$mysync->{tail},
  12006. # blabla and f1f2h=s% could be removed but
  12007. # tests_get_options_cgi() should be split before
  12008. # with a sub tests_myGetOptions()
  12009. ) ;
  12010. $mysync->{ debug } and output( $mysync, "get options: [$opt_ret][$numopt]\n" ) ;
  12011. if ( ! $opt_ret ) {
  12012. return ;
  12013. }
  12014. return $numopt ;
  12015. }
  12016. sub get_options_cmd
  12017. {
  12018. my $mysync = shift @ARG ;
  12019. my @arguments = @ARG ;
  12020. my $mycgi = $mysync->{cgi} ;
  12021. # final 0 is used to print usage when no option is given on command line
  12022. my $numopt = scalar @arguments || 0 ;
  12023. my $argv = join "\x00", @arguments ;
  12024. if ( $argv =~ m/-delete\x002/x ) {
  12025. output( $mysync, "May be you mean --delete2 instead of --delete 2\n" ) ;
  12026. return ;
  12027. }
  12028. $mysync->{f1f2h} = {} ;
  12029. my $opt_ret = myGetOptions(
  12030. $mysync,
  12031. \@arguments,
  12032. 'debug!' => \$mysync->{ debug },
  12033. 'debuglist!' => \$debuglist,
  12034. 'debugcontent!' => \$debugcontent,
  12035. 'debugsleep=f' => \$mysync->{debugsleep},
  12036. 'debugflags!' => \$debugflags,
  12037. 'debugimap!' => \$debugimap,
  12038. 'debugimap1!' => \$debugimap1,
  12039. 'debugimap2!' => \$debugimap2,
  12040. 'debugdev!' => \$debugdev,
  12041. 'debugmemory!' => \$mysync->{debugmemory},
  12042. 'debugfolders!' => \$mysync->{debugfolders},
  12043. 'debugssl=i' => \$mysync->{debugssl},
  12044. 'debugcgi!' => \$debugcgi,
  12045. 'debugenv!' => \$mysync->{debugenv},
  12046. 'debugsig!' => \$mysync->{debugsig},
  12047. 'debuglabels!' => \$mysync->{debuglabels},
  12048. 'simulong=i' => \$mysync->{simulong},
  12049. 'abort' => \$mysync->{abort},
  12050. 'host1=s' => \$mysync->{host1},
  12051. 'host2=s' => \$mysync->{host2},
  12052. 'port1=i' => \$mysync->{port1},
  12053. 'port2=i' => \$mysync->{port2},
  12054. 'inet4|ipv4' => \$mysync->{inet4},
  12055. 'inet6|ipv6' => \$mysync->{inet6},
  12056. 'user1=s' => \$mysync->{user1},
  12057. 'user2=s' => \$mysync->{user2},
  12058. 'gmail1' => \$mysync->{gmail1},
  12059. 'gmail2' => \$mysync->{gmail2},
  12060. 'office1' => \$mysync->{office1},
  12061. 'office2' => \$mysync->{office2},
  12062. 'exchange1' => \$mysync->{exchange1},
  12063. 'exchange2' => \$mysync->{exchange2},
  12064. 'domino1' => \$mysync->{domino1},
  12065. 'domino2' => \$mysync->{domino2},
  12066. 'domain1=s' => \$domain1,
  12067. 'domain2=s' => \$domain2,
  12068. 'password1=s' => \$mysync->{password1},
  12069. 'password2=s' => \$mysync->{password2},
  12070. 'passfile1=s' => \$mysync->{ passfile1 },
  12071. 'passfile2=s' => \$mysync->{ passfile2 },
  12072. 'authmd5!' => \$authmd5,
  12073. 'authmd51!' => \$authmd51,
  12074. 'authmd52!' => \$authmd52,
  12075. 'sep1=s' => \$mysync->{ sep1 },
  12076. 'sep2=s' => \$mysync->{ sep2 },
  12077. 'sanitize!' => \$mysync->{ sanitize },
  12078. 'folder=s@' => \$mysync->{ folder },
  12079. 'folderrec=s' => \@folderrec,
  12080. 'include=s' => \@include,
  12081. 'exclude=s' => \@exclude,
  12082. 'noexclude' => \$mysync->{noexclude},
  12083. 'folderfirst=s' => \@folderfirst,
  12084. 'folderlast=s' => \@folderlast,
  12085. 'prefix1=s' => \$prefix1,
  12086. 'prefix2=s' => \$prefix2,
  12087. 'subfolder1=s' => \$mysync->{ subfolder1 },
  12088. 'subfolder2=s' => \$mysync->{ subfolder2 },
  12089. 'fixslash2!' => \$mysync->{ fixslash2 },
  12090. 'fixInboxINBOX!' => \$fixInboxINBOX,
  12091. 'regextrans2=s@' => \$mysync->{ regextrans2 },
  12092. 'mixfolders!' => \$mixfolders,
  12093. 'skipemptyfolders!' => \$skipemptyfolders,
  12094. 'regexmess=s' => \@regexmess,
  12095. 'noregexmess' => \$mysync->{noregexmess},
  12096. 'skipmess=s' => \@skipmess,
  12097. 'pipemess=s' => \@pipemess,
  12098. 'pipemesscheck!' => \$pipemesscheck,
  12099. 'disarmreadreceipts!' => \$disarmreadreceipts,
  12100. 'regexflag=s' => \@regexflag,
  12101. 'noregexflag' => \$mysync->{noregexflag},
  12102. 'filterflags!' => \$filterflags,
  12103. 'flagscase!' => \$flagscase,
  12104. 'syncflagsaftercopy!' => \$syncflagsaftercopy,
  12105. 'resyncflags!' => \$mysync->{ resyncflags },
  12106. 'synclabels!' => \$mysync->{ synclabels },
  12107. 'resynclabels!' => \$mysync->{ resynclabels },
  12108. 'delete|delete1!' => \$mysync->{ delete1 },
  12109. 'delete2!' => \$mysync->{ delete2 },
  12110. 'delete2duplicates!' => \$mysync->{ delete2duplicates },
  12111. 'delete2folders!' => \$delete2folders,
  12112. 'delete2foldersonly=s' => \$delete2foldersonly,
  12113. 'delete2foldersbutnot=s' => \$delete2foldersbutnot,
  12114. 'syncinternaldates!' => \$syncinternaldates,
  12115. 'idatefromheader!' => \$idatefromheader,
  12116. 'syncacls!' => \$syncacls,
  12117. 'maxsize=i' => \$mysync->{ maxsize },
  12118. 'minsize=i' => \$minsize,
  12119. 'maxage=i' => \$maxage,
  12120. 'minage=i' => \$minage,
  12121. 'search=s' => \$search,
  12122. 'search1=s' => \$search1,
  12123. 'search2=s' => \$search2,
  12124. 'foldersizes!' => \$foldersizes,
  12125. 'foldersizesatend!' => \$foldersizesatend,
  12126. 'dry!' => \$mysync->{dry},
  12127. 'expunge1|expunge!' => \$mysync->{ expunge1 },
  12128. 'expunge2!' => \$mysync->{ expunge2 },
  12129. 'uidexpunge2!' => \$mysync->{ uidexpunge2 },
  12130. 'subscribed' => \$subscribed,
  12131. 'subscribe!' => \$subscribe,
  12132. 'subscribeall|subscribe_all!' => \$subscribeall,
  12133. 'justbanner!' => \$justbanner,
  12134. 'justfolders!'=> \$mysync->{ justfolders },
  12135. 'justfoldersizes!' => \$mysync->{ justfoldersizes },
  12136. 'fast!' => \$fast,
  12137. 'version' => \$mysync->{version},
  12138. 'help' => \$help,
  12139. 'timeout=i' => \$timeout,
  12140. 'timeout1=i' => \$mysync->{h1}->{timeout},
  12141. 'timeout2=i' => \$mysync->{h2}->{timeout},
  12142. 'skipheader=s' => \$skipheader,
  12143. 'useheader=s' => \@useheader,
  12144. 'wholeheaderifneeded!' => \$wholeheaderifneeded,
  12145. 'messageidnodomain!' => \$messageidnodomain,
  12146. 'skipsize!' => \$skipsize,
  12147. 'allowsizemismatch!' => \$allowsizemismatch,
  12148. 'fastio1!' => \$fastio1,
  12149. 'fastio2!' => \$fastio2,
  12150. 'sslcheck!' => \$mysync->{sslcheck},
  12151. 'ssl1!' => \$mysync->{ssl1},
  12152. 'ssl2!' => \$mysync->{ssl2},
  12153. 'ssl1_ssl_version=s' => \$mysync->{h1}->{sslargs}->{SSL_version},
  12154. 'ssl2_ssl_version=s' => \$mysync->{h2}->{sslargs}->{SSL_version},
  12155. 'sslargs1=s%' => \$mysync->{h1}->{sslargs},
  12156. 'sslargs2=s%' => \$mysync->{h2}->{sslargs},
  12157. 'tls1!' => \$mysync->{tls1},
  12158. 'tls2!' => \$mysync->{tls2},
  12159. 'uid1!' => \$uid1,
  12160. 'uid2!' => \$uid2,
  12161. 'authmech1=s' => \$authmech1,
  12162. 'authmech2=s' => \$authmech2,
  12163. 'authuser1=s' => \$authuser1,
  12164. 'authuser2=s' => \$authuser2,
  12165. 'proxyauth1' => \$proxyauth1,
  12166. 'proxyauth2' => \$proxyauth2,
  12167. 'split1=i' => \$split1,
  12168. 'split2=i' => \$split2,
  12169. 'buffersize=i' => \$buffersize,
  12170. 'reconnectretry1=i' => \$reconnectretry1,
  12171. 'reconnectretry2=i' => \$reconnectretry2,
  12172. 'tests!' => \$mysync->{ tests },
  12173. 'testsdebug|tests_debug!' => \$mysync->{ testsdebug },
  12174. 'testsunit=s@' => \$mysync->{testsunit},
  12175. 'testslive!' => \$mysync->{testslive},
  12176. 'testslive6!' => \$mysync->{testslive6},
  12177. 'justlogin!' => \$mysync->{justlogin},
  12178. 'justconnect!' => \$mysync->{justconnect},
  12179. 'tmpdir=s' => \$mysync->{ tmpdir },
  12180. 'pidfile=s' => \$mysync->{pidfile},
  12181. 'pidfilelocking!' => \$mysync->{pidfilelocking},
  12182. 'sigexit=s@' => \$mysync->{ sigexit },
  12183. 'sigreconnect=s@' => \$mysync->{ sigreconnect },
  12184. 'sigignore=s@' => \$mysync->{ sigignore },
  12185. 'releasecheck!' => \$mysync->{releasecheck},
  12186. 'modulesversion|modules_version!' => \$modulesversion,
  12187. 'usecache!' => \$usecache,
  12188. 'cacheaftercopy!' => \$cacheaftercopy,
  12189. 'debugcache!' => \$debugcache,
  12190. 'useuid!' => \$useuid,
  12191. 'addheader!' => \$mysync->{addheader},
  12192. 'exitwhenover=i' => \$mysync->{ exitwhenover },
  12193. 'checkselectable!' => \$mysync->{ checkselectable },
  12194. 'checkfoldersexist!' => \$mysync->{ checkfoldersexist },
  12195. 'checkmessageexists!' => \$checkmessageexists,
  12196. 'expungeaftereach!' => \$mysync->{ expungeaftereach },
  12197. 'abletosearch!' => \$mysync->{abletosearch},
  12198. 'abletosearch1!' => \$mysync->{abletosearch1},
  12199. 'abletosearch2!' => \$mysync->{abletosearch2},
  12200. 'showpasswords!' => \$mysync->{showpasswords},
  12201. 'maxlinelength=i' => \$maxlinelength,
  12202. 'maxlinelengthcmd=s' => \$maxlinelengthcmd,
  12203. 'minmaxlinelength=i' => \$minmaxlinelength,
  12204. 'debugmaxlinelength!' => \$debugmaxlinelength,
  12205. 'fixcolonbug!' => \$fixcolonbug,
  12206. 'create_folder_old!' => \$create_folder_old,
  12207. 'maxmessagespersecond=f' => \$mysync->{maxmessagespersecond},
  12208. 'maxbytespersecond=i' => \$mysync->{maxbytespersecond},
  12209. 'maxbytesafter=i' => \$mysync->{maxbytesafter},
  12210. 'maxsleep=f' => \$mysync->{maxsleep},
  12211. 'skipcrossduplicates!' => \$skipcrossduplicates,
  12212. 'debugcrossduplicates!' => \$debugcrossduplicates,
  12213. 'log!' => \$mysync->{log},
  12214. 'tail!' => \$mysync->{tail},
  12215. 'logfile=s' => \$mysync->{logfile},
  12216. 'logdir=s' => \$mysync->{logdir},
  12217. 'errorsmax=i' => \$mysync->{errorsmax},
  12218. 'errorsdump!' => \$mysync->{errorsdump},
  12219. 'fetch_hash_set=s' => \$fetch_hash_set,
  12220. 'automap!' => \$mysync->{automap},
  12221. 'justautomap!' => \$mysync->{justautomap},
  12222. 'id!' => \$mysync->{id},
  12223. 'f1f2=s@' => \$mysync->{f1f2},
  12224. 'nof1f2' => \$mysync->{nof1f2},
  12225. 'f1f2h=s%' => \$mysync->{f1f2h},
  12226. 'justfolderlists!' => \$mysync->{justfolderlists},
  12227. 'delete1emptyfolders' => \$mysync->{delete1emptyfolders},
  12228. ) ;
  12229. #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
  12230. $mysync->{ debug } and output( $mysync, "get options: [$opt_ret][$numopt]\n" ) ;
  12231. my $numopt_after = scalar @arguments ;
  12232. #myprint( "get options: [$opt_ret][$numopt][$numopt_after]\n" ) ;
  12233. if ( $numopt_after ) {
  12234. myprint(
  12235. "Extra arguments found: @arguments\n",
  12236. "It usually means a quoting issue in the command line ",
  12237. "or some misspelling options.\n",
  12238. ) ;
  12239. return ;
  12240. }
  12241. if ( ! $opt_ret ) {
  12242. return ;
  12243. }
  12244. return $numopt ;
  12245. }
  12246. sub tests_get_options
  12247. {
  12248. note( 'Entering tests_get_options()' ) ;
  12249. # CAVEAT: still setting global variables, be careful
  12250. # with tests, the context increases! $debug stays on for example.
  12251. # API:
  12252. # * input arguments: two ways, command line or CGI
  12253. # * the program arguments
  12254. # * QUERY_STRING env variable
  12255. # * return
  12256. # * undef if bad things happened like
  12257. # * options not known
  12258. # * --delete 2 input
  12259. # * number of arguments or QUERY_STRING length
  12260. my $mysync = { } ;
  12261. is( undef, get_options( $mysync, qw( --noexist ) ), 'get_options: --noexist => undef' ) ;
  12262. is( undef, $mysync->{ noexist }, 'get_options: --noexist => undef' ) ;
  12263. $mysync = { } ;
  12264. is( undef, get_options( $mysync, qw( --lalala --noexist --version ) ), 'get_options: --lalala --noexist --version => undef' ) ;
  12265. is( 1, $mysync->{ version }, 'get_options: --version => 1' ) ;
  12266. is( undef, $mysync->{ noexist }, 'get_options: --noexist => undef' ) ;
  12267. $mysync = { } ;
  12268. is( 1, get_options( $mysync, qw( --delete2 ) ), 'get_options: --delete2 => 1' ) ;
  12269. is( 1, $mysync->{ delete2 }, 'get_options: --delete2 => var delete2 = 1' ) ;
  12270. $mysync = { } ;
  12271. is( undef, get_options( $mysync, qw( --delete 2 ) ), 'get_options: --delete 2 => var undef' ) ;
  12272. is( undef, $mysync->{ delete1 }, 'get_options: --delete 2 => var still undef ; good!' ) ;
  12273. $mysync = { } ;
  12274. is( undef, get_options( $mysync, "--delete 2" ), 'get_options: --delete 2 => undef' ) ;
  12275. is( 1, get_options( $mysync, "--version" ), 'get_options: --version => 1' ) ;
  12276. is( 1, get_options( $mysync, "--help" ), 'get_options: --help => 1' ) ;
  12277. is( undef, get_options( $mysync, qw( --noexist --version ) ), 'get_options: --debug --noexist --version => undef' ) ;
  12278. is( 1, get_options( $mysync, qw( --version ) ), 'get_options: --version => 1' ) ;
  12279. is( undef, get_options( $mysync, qw( extra ) ), 'get_options: extra => undef' ) ;
  12280. is( undef, get_options( $mysync, qw( extra1 --version extra2 ) ), 'get_options: extra1 --version extra2 => undef' ) ;
  12281. $mysync = { } ;
  12282. is( 2, get_options( $mysync, qw( --host1 HOST_01) ), 'get_options: --host1 HOST_01 => 1' ) ;
  12283. is( 'HOST_01', $mysync->{ host1 }, 'get_options: --host1 HOST_01 => HOST_01' ) ;
  12284. #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
  12285. note( 'Leaving tests_get_options()' ) ;
  12286. return ;
  12287. }
  12288. sub get_options
  12289. {
  12290. my $mysync = shift @ARG ;
  12291. my @arguments = @ARG ;
  12292. #myprint( "1 mysync: ", Data::Dumper->Dump( [ $mysync ] ) ) ;
  12293. my $ret ;
  12294. if ( under_cgi_context( ) ) {
  12295. # CGI context
  12296. $ret = get_options_cgi( $mysync, @arguments ) ;
  12297. }else{
  12298. # Command line context ;
  12299. $ret = get_options_cmd( $mysync, @arguments ) ;
  12300. } ;
  12301. #myprint( "2 mysync: ", Data::Dumper->Dump( [ $mysync ] ) ) ;
  12302. foreach my $key ( sort keys %{ $mysync } ) {
  12303. if ( ! defined $mysync->{$key} ) {
  12304. delete $mysync->{$key} ;
  12305. next ;
  12306. }
  12307. if ( 'ARRAY' eq ref( $mysync->{$key} )
  12308. and 0 == scalar( @{ $mysync->{$key} } ) ) {
  12309. delete $mysync->{$key} ;
  12310. }
  12311. }
  12312. return $ret ;
  12313. }
  12314. sub testunitsession
  12315. {
  12316. my $mysync = shift ;
  12317. if ( ! $mysync ) { return ; }
  12318. if ( ! $mysync->{ testsunit } ) { return ; }
  12319. my @functions = @{ $mysync->{ testsunit } } ;
  12320. if ( ! @functions ) { return ; }
  12321. SKIP: {
  12322. if ( ! @functions ) { skip 'No test in normal run' ; }
  12323. testsunit( @functions ) ;
  12324. done_testing( ) ;
  12325. }
  12326. return ;
  12327. }
  12328. sub tests_count_0s
  12329. {
  12330. note( 'Entering tests_count_zeros()' ) ;
  12331. is( 0, count_0s( ), 'count_0s: no parameters => 0' ) ;
  12332. is( 1, count_0s( 0 ), 'count_0s: 0 => 1' ) ;
  12333. is( 0, count_0s( 1 ), 'count_0s: 1 => 0' ) ;
  12334. is( 1, count_0s( 1, 0, 1 ), 'count_0s: 1, 0, 1 => 1' ) ;
  12335. is( 2, count_0s( 1, 0, 1, 0 ), 'count_0s: 1, 0, 1, 0 => 2' ) ;
  12336. note( 'Leaving tests_count_zeros()' ) ;
  12337. return ;
  12338. }
  12339. sub count_0s
  12340. {
  12341. my @array = @ARG ;
  12342. if ( ! @array ) { return 0 ; }
  12343. my $nb_zeros = 0 ;
  12344. map { $_ == 0 and $nb_zeros += 1 } @array ;
  12345. return $nb_zeros ;
  12346. }
  12347. sub tests_report_failures
  12348. {
  12349. note( 'Entering tests_report_failures()' ) ;
  12350. is( undef, report_failures( ), 'report_failures: no parameters => undef' ) ;
  12351. is( "nb 1 - first\n", report_failures( ({'ok' => 0, name => 'first'}) ), 'report_failures: "first" failed => nb 1 - first' ) ;
  12352. is( q{}, report_failures( ( {'ok' => 1, name => 'first'} ) ), 'report_failures: "first" success =>' ) ;
  12353. is( "nb 2 - second\n", report_failures( ( {'ok' => 1, name => 'second'}, {'ok' => 0, name => 'second'} ) ), 'report_failures: "second" failed => nb 2 - second' ) ;
  12354. is( "nb 1 - first\nnb 2 - second\n", report_failures( ( {'ok' => 0, name => 'first'}, {'ok' => 0, name => 'second'} ) ), 'report_failures: both failed => nb 1 - first nb 2 - second' ) ;
  12355. note( 'Leaving tests_report_failures()' ) ;
  12356. return ;
  12357. }
  12358. sub report_failures
  12359. {
  12360. my @details = @ARG ;
  12361. if ( ! @details ) { return ; }
  12362. my $counter = 1 ;
  12363. my $report = q{} ;
  12364. foreach my $details ( @details ) {
  12365. if ( ! $details->{ 'ok' } ) {
  12366. my $name = $details->{ 'name' } || 'NONAME' ;
  12367. $report .= "nb $counter - $name\n" ;
  12368. }
  12369. $counter += 1 ;
  12370. }
  12371. return $report ;
  12372. }
  12373. sub tests_true
  12374. {
  12375. note( 'Entering tests_true()' ) ;
  12376. is( 1, 1, 'true: 1 is 1' ) ;
  12377. note( 'Leaving tests_true()' ) ;
  12378. return ;
  12379. }
  12380. sub tests_testsunit
  12381. {
  12382. note( 'Entering tests_testunit()' ) ;
  12383. is( undef, testsunit( ), 'testsunit: no parameters => undef' ) ;
  12384. is( undef, testsunit( undef ), 'testsunit: an undef parameter => undef' ) ;
  12385. is( undef, testsunit( q{} ), 'testsunit: an empty parameter => undef' ) ;
  12386. is( undef, testsunit( 'idonotexist' ), 'testsunit: a do not exist function as parameter => undef' ) ;
  12387. is( undef, testsunit( 'tests_true' ), 'testsunit: tests_true => undef' ) ;
  12388. note( 'Leaving tests_testunit()' ) ;
  12389. return ;
  12390. }
  12391. sub testsunit
  12392. {
  12393. my @functions = @ARG ;
  12394. if ( ! @functions ) { #
  12395. myprint( "testsunit warning: no argument given\n" ) ;
  12396. return ;
  12397. }
  12398. foreach my $function ( @functions ) {
  12399. if ( ! $function ) {
  12400. myprint( "testsunit warning: argument is empty\n" ) ;
  12401. next ;
  12402. }
  12403. if ( ! exists &$function ) {
  12404. myprint( "testsunit warning: function $function does not exist\n" ) ;
  12405. next ;
  12406. }
  12407. if ( ! defined &$function ) {
  12408. myprint( "testsunit warning: function $function is not defined\n" ) ;
  12409. next ;
  12410. }
  12411. my $function_ref = \&{ $function } ;
  12412. &$function_ref() ;
  12413. }
  12414. return ;
  12415. }
  12416. sub testsdebug
  12417. {
  12418. # Now a little obsolete since there is
  12419. # imapsync ... --testsunit "anyfunction"
  12420. my $mysync = shift ;
  12421. if ( ! $mysync->{ testsdebug } ) { return ; }
  12422. SKIP: {
  12423. if ( ! $mysync->{ testsdebug } ) {
  12424. skip 'No test in normal run' ;
  12425. }
  12426. note( 'Entering testsdebug()' ) ;
  12427. ok( ( ( not -d 'W/tmp/tests' ) or rmtree( 'W/tmp/tests/' ) ), 'testsdebug: rmtree W/tmp/tests' ) ;
  12428. tests_check_binary_embed_all_dyn_libs( ) ;
  12429. note( 'Leaving testsdebug()' ) ;
  12430. done_testing( ) ;
  12431. }
  12432. return ;
  12433. }
  12434. sub tests
  12435. {
  12436. my $mysync = shift ;
  12437. if ( ! $mysync->{ tests } ) { return ; }
  12438. SKIP: {
  12439. skip 'No test in normal run' if ( ! $mysync->{ tests } ) ;
  12440. note( 'Entering tests()' ) ;
  12441. tests_folder_routines( ) ;
  12442. tests_compare_lists( ) ;
  12443. tests_regexmess( ) ;
  12444. tests_skipmess( ) ;
  12445. tests_flags_regex();
  12446. tests_ucsecond( ) ;
  12447. tests_permanentflags();
  12448. tests_flags_filter( ) ;
  12449. tests_separator_invert( ) ;
  12450. tests_imap2_folder_name( ) ;
  12451. tests_command_line_nopassword( ) ;
  12452. tests_good_date( ) ;
  12453. tests_max( ) ;
  12454. tests_remove_not_num();
  12455. tests_memory_consumption( ) ;
  12456. tests_is_a_release_number();
  12457. tests_imapsync_basename();
  12458. tests_list_keys_in_2_not_in_1();
  12459. tests_convert_sep_to_slash( ) ;
  12460. tests_match_a_cache_file( ) ;
  12461. tests_cache_map( ) ;
  12462. tests_get_cache( ) ;
  12463. tests_clean_cache( ) ;
  12464. tests_clean_cache_2( ) ;
  12465. tests_touch( ) ;
  12466. tests_flagscase( ) ;
  12467. tests_mkpath( ) ;
  12468. tests_extract_header( ) ;
  12469. tests_decompose_header( ) ;
  12470. tests_epoch( ) ;
  12471. tests_add_header( ) ;
  12472. tests_cache_dir_fix( ) ;
  12473. tests_cache_dir_fix_win( ) ;
  12474. tests_filter_forbidden_characters( ) ;
  12475. tests_cache_folder( ) ;
  12476. tests_time_remaining( ) ;
  12477. tests_decompose_regex( ) ;
  12478. tests_backtick( ) ;
  12479. tests_bytes_display_string( ) ;
  12480. tests_header_line_normalize( ) ;
  12481. tests_fix_Inbox_INBOX_mapping( ) ;
  12482. tests_max_line_length( ) ;
  12483. tests_subject( ) ;
  12484. tests_msgs_from_maxmin( ) ;
  12485. tests_tmpdir_has_colon_bug( ) ;
  12486. tests_sleep_max_messages( ) ;
  12487. tests_sleep_max_bytes( ) ;
  12488. tests_logfile( ) ;
  12489. tests_setlogfile( ) ;
  12490. tests_jux_utf8( ) ;
  12491. tests_pipemess( ) ;
  12492. tests_jux_utf8_list( ) ;
  12493. tests_guess_prefix( ) ;
  12494. tests_guess_separator( ) ;
  12495. tests_format_for_imap_arg( ) ;
  12496. tests_imapsync_id( ) ;
  12497. tests_date_from_rcs( ) ;
  12498. tests_quota_extract_storage_limit_in_bytes( ) ;
  12499. tests_quota_extract_storage_current_in_bytes( ) ;
  12500. tests_guess_special( ) ;
  12501. tests_do_valid_directory( ) ;
  12502. tests_delete1emptyfolders( ) ;
  12503. tests_message_for_host2( ) ;
  12504. tests_length_ref( ) ;
  12505. tests_firstline( ) ;
  12506. tests_diff_or_NA( ) ;
  12507. tests_match_number( ) ;
  12508. tests_all_defined( ) ;
  12509. tests_special_from_folders_hash( ) ;
  12510. tests_notmatch( ) ;
  12511. tests_match( ) ;
  12512. tests_get_options( ) ;
  12513. tests_get_options_cgi_context( ) ;
  12514. tests_rand32( ) ;
  12515. tests_hashsynclocal( ) ;
  12516. tests_hashsync( ) ;
  12517. tests_output( ) ;
  12518. tests_output_reset_with( ) ;
  12519. tests_output_start( ) ;
  12520. tests_check_last_release( ) ;
  12521. tests_loadavg( ) ;
  12522. tests_cpu_number( ) ;
  12523. tests_load_and_delay( ) ;
  12524. #tests_imapsping( ) ;
  12525. #tests_tcpping( ) ;
  12526. tests_sslcheck( ) ;
  12527. tests_not_long_imapsync_version_public( ) ;
  12528. tests_reconnect_if_needed( ) ;
  12529. tests_reconnect_12_if_needed( ) ;
  12530. tests_sleep_if_needed( ) ;
  12531. tests_string_to_file( ) ;
  12532. tests_file_to_string( ) ;
  12533. tests_under_cgi_context( ) ;
  12534. tests_umask( ) ;
  12535. tests_umask_str( ) ;
  12536. tests_set_umask( ) ;
  12537. tests_createhashfileifneeded( ) ;
  12538. tests_move_slash( ) ;
  12539. tests_testsunit( ) ;
  12540. tests_count_0s( ) ;
  12541. tests_report_failures( ) ;
  12542. tests_min( ) ;
  12543. #tests_resolv( ) ;
  12544. #tests_resolvrev( ) ;
  12545. tests_connect_socket( ) ;
  12546. tests_probe_imapssl( ) ;
  12547. tests_mailimapclient_connect( ) ;
  12548. tests_usage( ) ;
  12549. tests_version_from_rcs( ) ;
  12550. tests_backslash_caret( ) ;
  12551. #tests_mailimapclient_connect_bug( ) ; # it fails with Mail-IMAPClient <= 3.39
  12552. tests_write_pidfile( ) ;
  12553. tests_remove_pidfile_not_running( ) ;
  12554. tests_match_a_pid_number( ) ;
  12555. tests_prefix_seperator_invertion( ) ;
  12556. tests_is_an_integer( ) ;
  12557. tests_integer_or_1( ) ;
  12558. tests_is_number( ) ;
  12559. tests_sig_install( ) ;
  12560. tests_template( ) ;
  12561. tests_split_around_equal( ) ;
  12562. tests_toggle_sleep( ) ;
  12563. tests_labels( ) ;
  12564. tests_synclabels( ) ;
  12565. tests_uidexpunge_or_expunge( ) ;
  12566. tests_appendlimit_from_capability( ) ;
  12567. tests_maxsize_setting( ) ;
  12568. tests_mock_capability( ) ;
  12569. tests_appendlimit( ) ;
  12570. tests_capability_of( ) ;
  12571. tests_search_in_array( ) ;
  12572. tests_operators_and_exclam_precedence( ) ;
  12573. tests_teelaunch( ) ;
  12574. tests_logfileprepa( ) ;
  12575. tests_useheader_suggestion( ) ;
  12576. tests_nb_messages_in_2_not_in_1( ) ;
  12577. tests_labels_add_subfolder2( ) ;
  12578. tests_labels_remove_subfolder1( ) ;
  12579. tests_resynclabels( ) ;
  12580. tests_labels_remove_special( ) ;
  12581. tests_uniq( ) ;
  12582. tests_remove_from_requested_folders( ) ;
  12583. tests_errors_log( ) ;
  12584. tests_add_subfolder1_to_folderrec( ) ;
  12585. tests_sanitize_subfolder( ) ;
  12586. tests_remove_edging_blanks( ) ;
  12587. tests_sanitize( ) ;
  12588. tests_remove_last_char_if_is( ) ;
  12589. tests_check_binary_embed_all_dyn_libs( ) ;
  12590. tests_nthline( ) ;
  12591. tests_secondline( ) ;
  12592. tests_tail( ) ;
  12593. #tests_always_fail( ) ;
  12594. done_testing( 1441 ) ;
  12595. note( 'Leaving tests()' ) ;
  12596. }
  12597. return ;
  12598. }
  12599. sub tests_template
  12600. {
  12601. note( 'Entering tests_template()' ) ;
  12602. is( undef, undef, 'template: undef is undef' ) ;
  12603. is_deeply( {}, {}, 'template: a hash is a hash' ) ;
  12604. is_deeply( [], [], 'template: an array is an array' ) ;
  12605. note( 'Leaving tests_template()' ) ;
  12606. return ;
  12607. }