imapsync 756 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449114501145111452114531145411455114561145711458114591146011461114621146311464114651146611467114681146911470114711147211473114741147511476114771147811479114801148111482114831148411485114861148711488114891149011491114921149311494114951149611497114981149911500115011150211503115041150511506115071150811509115101151111512115131151411515115161151711518115191152011521115221152311524115251152611527115281152911530115311153211533115341153511536115371153811539115401154111542115431154411545115461154711548115491155011551115521155311554115551155611557115581155911560115611156211563115641156511566115671156811569115701157111572115731157411575115761157711578115791158011581115821158311584115851158611587115881158911590115911159211593115941159511596115971159811599116001160111602116031160411605116061160711608116091161011611116121161311614116151161611617116181161911620116211162211623116241162511626116271162811629116301163111632116331163411635116361163711638116391164011641116421164311644116451164611647116481164911650116511165211653116541165511656116571165811659116601166111662116631166411665116661166711668116691167011671116721167311674116751167611677116781167911680116811168211683116841168511686116871168811689116901169111692116931169411695116961169711698116991170011701117021170311704117051170611707117081170911710117111171211713117141171511716117171171811719117201172111722117231172411725117261172711728117291173011731117321173311734117351173611737117381173911740117411174211743117441174511746117471174811749117501175111752117531175411755117561175711758117591176011761117621176311764117651176611767117681176911770117711177211773117741177511776117771177811779117801178111782117831178411785117861178711788117891179011791117921179311794117951179611797117981179911800118011180211803118041180511806118071180811809118101181111812118131181411815118161181711818118191182011821118221182311824118251182611827118281182911830118311183211833118341183511836118371183811839118401184111842118431184411845118461184711848118491185011851118521185311854118551185611857118581185911860118611186211863118641186511866118671186811869118701187111872118731187411875118761187711878118791188011881118821188311884118851188611887118881188911890118911189211893118941189511896118971189811899119001190111902119031190411905119061190711908119091191011911119121191311914119151191611917119181191911920119211192211923119241192511926119271192811929119301193111932119331193411935119361193711938119391194011941119421194311944119451194611947119481194911950119511195211953119541195511956119571195811959119601196111962119631196411965119661196711968119691197011971119721197311974119751197611977119781197911980119811198211983119841198511986119871198811989119901199111992119931199411995119961199711998119991200012001120021200312004120051200612007120081200912010120111201212013120141201512016120171201812019120201202112022120231202412025120261202712028120291203012031120321203312034120351203612037120381203912040120411204212043120441204512046120471204812049120501205112052120531205412055120561205712058120591206012061120621206312064120651206612067120681206912070120711207212073120741207512076120771207812079120801208112082120831208412085120861208712088120891209012091120921209312094120951209612097120981209912100121011210212103121041210512106121071210812109121101211112112121131211412115121161211712118121191212012121121221212312124121251212612127121281212912130121311213212133121341213512136121371213812139121401214112142121431214412145121461214712148121491215012151121521215312154121551215612157121581215912160121611216212163121641216512166121671216812169121701217112172121731217412175121761217712178121791218012181121821218312184121851218612187121881218912190121911219212193121941219512196121971219812199122001220112202122031220412205122061220712208122091221012211122121221312214122151221612217122181221912220122211222212223122241222512226122271222812229122301223112232122331223412235122361223712238122391224012241122421224312244122451224612247122481224912250122511225212253122541225512256122571225812259122601226112262122631226412265122661226712268122691227012271122721227312274122751227612277122781227912280122811228212283122841228512286122871228812289122901229112292122931229412295122961229712298122991230012301123021230312304123051230612307123081230912310123111231212313123141231512316123171231812319123201232112322123231232412325123261232712328123291233012331123321233312334123351233612337123381233912340123411234212343123441234512346123471234812349123501235112352123531235412355123561235712358123591236012361123621236312364123651236612367123681236912370123711237212373123741237512376123771237812379123801238112382123831238412385123861238712388123891239012391123921239312394123951239612397123981239912400124011240212403124041240512406124071240812409124101241112412124131241412415124161241712418124191242012421124221242312424124251242612427124281242912430124311243212433124341243512436124371243812439124401244112442124431244412445124461244712448124491245012451124521245312454124551245612457124581245912460124611246212463124641246512466124671246812469124701247112472124731247412475124761247712478124791248012481124821248312484124851248612487124881248912490124911249212493124941249512496124971249812499125001250112502125031250412505125061250712508125091251012511125121251312514125151251612517125181251912520125211252212523125241252512526125271252812529125301253112532125331253412535125361253712538125391254012541125421254312544125451254612547125481254912550125511255212553125541255512556125571255812559125601256112562125631256412565125661256712568125691257012571125721257312574125751257612577125781257912580125811258212583125841258512586125871258812589125901259112592125931259412595125961259712598125991260012601126021260312604126051260612607126081260912610126111261212613126141261512616126171261812619126201262112622126231262412625126261262712628126291263012631126321263312634126351263612637126381263912640126411264212643126441264512646126471264812649126501265112652126531265412655126561265712658126591266012661126621266312664126651266612667126681266912670126711267212673126741267512676126771267812679126801268112682126831268412685126861268712688126891269012691126921269312694126951269612697126981269912700127011270212703127041270512706127071270812709127101271112712127131271412715127161271712718127191272012721127221272312724127251272612727127281272912730127311273212733127341273512736127371273812739127401274112742127431274412745127461274712748127491275012751127521275312754127551275612757127581275912760127611276212763127641276512766127671276812769127701277112772127731277412775127761277712778127791278012781127821278312784127851278612787127881278912790127911279212793127941279512796127971279812799128001280112802128031280412805128061280712808128091281012811128121281312814128151281612817128181281912820128211282212823128241282512826128271282812829128301283112832128331283412835128361283712838128391284012841128421284312844128451284612847128481284912850128511285212853128541285512856128571285812859128601286112862128631286412865128661286712868128691287012871128721287312874128751287612877128781287912880128811288212883128841288512886128871288812889128901289112892128931289412895128961289712898128991290012901129021290312904129051290612907129081290912910129111291212913129141291512916129171291812919129201292112922129231292412925129261292712928129291293012931129321293312934129351293612937129381293912940129411294212943129441294512946129471294812949129501295112952129531295412955129561295712958129591296012961129621296312964129651296612967129681296912970129711297212973129741297512976129771297812979129801298112982129831298412985129861298712988129891299012991129921299312994129951299612997129981299913000130011300213003130041300513006130071300813009130101301113012130131301413015130161301713018130191302013021130221302313024130251302613027130281302913030130311303213033130341303513036130371303813039130401304113042130431304413045130461304713048130491305013051130521305313054130551305613057130581305913060130611306213063130641306513066130671306813069130701307113072130731307413075130761307713078130791308013081130821308313084130851308613087130881308913090130911309213093130941309513096130971309813099131001310113102131031310413105131061310713108131091311013111131121311313114131151311613117131181311913120131211312213123131241312513126131271312813129131301313113132131331313413135131361313713138131391314013141131421314313144131451314613147131481314913150131511315213153131541315513156131571315813159131601316113162131631316413165131661316713168131691317013171131721317313174131751317613177131781317913180131811318213183131841318513186131871318813189131901319113192131931319413195131961319713198131991320013201132021320313204132051320613207132081320913210132111321213213132141321513216132171321813219132201322113222132231322413225132261322713228132291323013231132321323313234132351323613237132381323913240132411324213243132441324513246132471324813249132501325113252132531325413255132561325713258132591326013261132621326313264132651326613267132681326913270132711327213273132741327513276132771327813279132801328113282132831328413285132861328713288132891329013291132921329313294132951329613297132981329913300133011330213303133041330513306133071330813309133101331113312133131331413315133161331713318133191332013321133221332313324133251332613327133281332913330133311333213333133341333513336133371333813339133401334113342133431334413345133461334713348133491335013351133521335313354133551335613357133581335913360133611336213363133641336513366133671336813369133701337113372133731337413375133761337713378133791338013381133821338313384133851338613387133881338913390133911339213393133941339513396133971339813399134001340113402134031340413405134061340713408134091341013411134121341313414134151341613417134181341913420134211342213423134241342513426134271342813429134301343113432134331343413435134361343713438134391344013441134421344313444134451344613447134481344913450134511345213453134541345513456134571345813459134601346113462134631346413465134661346713468134691347013471134721347313474134751347613477134781347913480134811348213483134841348513486134871348813489134901349113492134931349413495134961349713498134991350013501135021350313504135051350613507135081350913510135111351213513135141351513516135171351813519135201352113522135231352413525135261352713528135291353013531135321353313534135351353613537135381353913540135411354213543135441354513546135471354813549135501355113552135531355413555135561355713558135591356013561135621356313564135651356613567135681356913570135711357213573135741357513576135771357813579135801358113582135831358413585135861358713588135891359013591135921359313594135951359613597135981359913600136011360213603136041360513606136071360813609136101361113612136131361413615136161361713618136191362013621136221362313624136251362613627136281362913630136311363213633136341363513636136371363813639136401364113642136431364413645136461364713648136491365013651136521365313654136551365613657136581365913660136611366213663136641366513666136671366813669136701367113672136731367413675136761367713678136791368013681136821368313684136851368613687136881368913690136911369213693136941369513696136971369813699137001370113702137031370413705137061370713708137091371013711137121371313714137151371613717137181371913720137211372213723137241372513726137271372813729137301373113732137331373413735137361373713738137391374013741137421374313744137451374613747137481374913750137511375213753137541375513756137571375813759137601376113762137631376413765137661376713768137691377013771137721377313774137751377613777137781377913780137811378213783137841378513786137871378813789137901379113792137931379413795137961379713798137991380013801138021380313804138051380613807138081380913810138111381213813138141381513816138171381813819138201382113822138231382413825138261382713828138291383013831138321383313834138351383613837138381383913840138411384213843138441384513846138471384813849138501385113852138531385413855138561385713858138591386013861138621386313864138651386613867138681386913870138711387213873138741387513876138771387813879138801388113882138831388413885138861388713888138891389013891138921389313894138951389613897138981389913900139011390213903139041390513906139071390813909139101391113912139131391413915139161391713918139191392013921139221392313924139251392613927139281392913930139311393213933139341393513936139371393813939139401394113942139431394413945139461394713948139491395013951139521395313954139551395613957139581395913960139611396213963139641396513966139671396813969139701397113972139731397413975139761397713978139791398013981139821398313984139851398613987139881398913990139911399213993139941399513996139971399813999140001400114002140031400414005140061400714008140091401014011140121401314014140151401614017140181401914020140211402214023140241402514026140271402814029140301403114032140331403414035140361403714038140391404014041140421404314044140451404614047140481404914050140511405214053140541405514056140571405814059140601406114062140631406414065140661406714068140691407014071140721407314074140751407614077140781407914080140811408214083140841408514086140871408814089140901409114092140931409414095140961409714098140991410014101141021410314104141051410614107141081410914110141111411214113141141411514116141171411814119141201412114122141231412414125141261412714128141291413014131141321413314134141351413614137141381413914140141411414214143141441414514146141471414814149141501415114152141531415414155141561415714158141591416014161141621416314164141651416614167141681416914170141711417214173141741417514176141771417814179141801418114182141831418414185141861418714188141891419014191141921419314194141951419614197141981419914200142011420214203142041420514206142071420814209142101421114212142131421414215142161421714218142191422014221142221422314224142251422614227142281422914230142311423214233142341423514236142371423814239142401424114242142431424414245142461424714248142491425014251142521425314254142551425614257142581425914260142611426214263142641426514266142671426814269142701427114272142731427414275142761427714278142791428014281142821428314284142851428614287142881428914290142911429214293142941429514296142971429814299143001430114302143031430414305143061430714308143091431014311143121431314314143151431614317143181431914320143211432214323143241432514326143271432814329143301433114332143331433414335143361433714338143391434014341143421434314344143451434614347143481434914350143511435214353143541435514356143571435814359143601436114362143631436414365143661436714368143691437014371143721437314374143751437614377143781437914380143811438214383143841438514386143871438814389143901439114392143931439414395143961439714398143991440014401144021440314404144051440614407144081440914410144111441214413144141441514416144171441814419144201442114422144231442414425144261442714428144291443014431144321443314434144351443614437144381443914440144411444214443144441444514446144471444814449144501445114452144531445414455144561445714458144591446014461144621446314464144651446614467144681446914470144711447214473144741447514476144771447814479144801448114482144831448414485144861448714488144891449014491144921449314494144951449614497144981449914500145011450214503145041450514506145071450814509145101451114512145131451414515145161451714518145191452014521145221452314524145251452614527145281452914530145311453214533145341453514536145371453814539145401454114542145431454414545145461454714548145491455014551145521455314554145551455614557145581455914560145611456214563145641456514566145671456814569145701457114572145731457414575145761457714578145791458014581145821458314584145851458614587145881458914590145911459214593145941459514596145971459814599146001460114602146031460414605146061460714608146091461014611146121461314614146151461614617146181461914620146211462214623146241462514626146271462814629146301463114632146331463414635146361463714638146391464014641146421464314644146451464614647146481464914650146511465214653146541465514656146571465814659146601466114662146631466414665146661466714668146691467014671146721467314674146751467614677146781467914680146811468214683146841468514686146871468814689146901469114692146931469414695146961469714698146991470014701147021470314704147051470614707147081470914710147111471214713147141471514716147171471814719147201472114722147231472414725147261472714728147291473014731147321473314734147351473614737147381473914740147411474214743147441474514746147471474814749147501475114752147531475414755147561475714758147591476014761147621476314764147651476614767147681476914770147711477214773147741477514776147771477814779147801478114782147831478414785147861478714788147891479014791147921479314794147951479614797147981479914800148011480214803148041480514806148071480814809148101481114812148131481414815148161481714818148191482014821148221482314824148251482614827148281482914830148311483214833148341483514836148371483814839148401484114842148431484414845148461484714848148491485014851148521485314854148551485614857148581485914860148611486214863148641486514866148671486814869148701487114872148731487414875148761487714878148791488014881148821488314884148851488614887148881488914890148911489214893148941489514896148971489814899149001490114902149031490414905149061490714908149091491014911149121491314914149151491614917149181491914920149211492214923149241492514926149271492814929149301493114932149331493414935149361493714938149391494014941149421494314944149451494614947149481494914950149511495214953149541495514956149571495814959149601496114962149631496414965149661496714968149691497014971149721497314974149751497614977149781497914980149811498214983149841498514986149871498814989149901499114992149931499414995149961499714998149991500015001150021500315004150051500615007150081500915010150111501215013150141501515016150171501815019150201502115022150231502415025150261502715028150291503015031150321503315034150351503615037150381503915040150411504215043150441504515046150471504815049150501505115052150531505415055150561505715058150591506015061150621506315064150651506615067150681506915070150711507215073150741507515076150771507815079150801508115082150831508415085150861508715088150891509015091150921509315094150951509615097150981509915100151011510215103151041510515106151071510815109151101511115112151131511415115151161511715118151191512015121151221512315124151251512615127151281512915130151311513215133151341513515136151371513815139151401514115142151431514415145151461514715148151491515015151151521515315154151551515615157151581515915160151611516215163151641516515166151671516815169151701517115172151731517415175151761517715178151791518015181151821518315184151851518615187151881518915190151911519215193151941519515196151971519815199152001520115202152031520415205152061520715208152091521015211152121521315214152151521615217152181521915220152211522215223152241522515226152271522815229152301523115232152331523415235152361523715238152391524015241152421524315244152451524615247152481524915250152511525215253152541525515256152571525815259152601526115262152631526415265152661526715268152691527015271152721527315274152751527615277152781527915280152811528215283152841528515286152871528815289152901529115292152931529415295152961529715298152991530015301153021530315304153051530615307153081530915310153111531215313153141531515316153171531815319153201532115322153231532415325153261532715328153291533015331153321533315334153351533615337153381533915340153411534215343153441534515346153471534815349153501535115352153531535415355153561535715358153591536015361153621536315364153651536615367153681536915370153711537215373153741537515376153771537815379153801538115382153831538415385153861538715388153891539015391153921539315394153951539615397153981539915400154011540215403154041540515406154071540815409154101541115412154131541415415154161541715418154191542015421154221542315424154251542615427154281542915430154311543215433154341543515436154371543815439154401544115442154431544415445154461544715448154491545015451154521545315454154551545615457154581545915460154611546215463154641546515466154671546815469154701547115472154731547415475154761547715478154791548015481154821548315484154851548615487154881548915490154911549215493154941549515496154971549815499155001550115502155031550415505155061550715508155091551015511155121551315514155151551615517155181551915520155211552215523155241552515526155271552815529155301553115532155331553415535155361553715538155391554015541155421554315544155451554615547155481554915550155511555215553155541555515556155571555815559155601556115562155631556415565155661556715568155691557015571155721557315574155751557615577155781557915580155811558215583155841558515586155871558815589155901559115592155931559415595155961559715598155991560015601156021560315604156051560615607156081560915610156111561215613156141561515616156171561815619156201562115622156231562415625156261562715628156291563015631156321563315634156351563615637156381563915640156411564215643156441564515646156471564815649156501565115652156531565415655156561565715658156591566015661156621566315664156651566615667156681566915670156711567215673156741567515676156771567815679156801568115682156831568415685156861568715688156891569015691156921569315694156951569615697156981569915700157011570215703157041570515706157071570815709157101571115712157131571415715157161571715718157191572015721157221572315724157251572615727157281572915730157311573215733157341573515736157371573815739157401574115742157431574415745157461574715748157491575015751157521575315754157551575615757157581575915760157611576215763157641576515766157671576815769157701577115772157731577415775157761577715778157791578015781157821578315784157851578615787157881578915790157911579215793157941579515796157971579815799158001580115802158031580415805158061580715808158091581015811158121581315814158151581615817158181581915820158211582215823158241582515826158271582815829158301583115832158331583415835158361583715838158391584015841158421584315844158451584615847158481584915850158511585215853158541585515856158571585815859158601586115862158631586415865158661586715868158691587015871158721587315874158751587615877158781587915880158811588215883158841588515886158871588815889158901589115892158931589415895158961589715898158991590015901159021590315904159051590615907159081590915910159111591215913159141591515916159171591815919159201592115922159231592415925159261592715928159291593015931159321593315934159351593615937159381593915940159411594215943159441594515946159471594815949159501595115952159531595415955159561595715958159591596015961159621596315964159651596615967159681596915970159711597215973159741597515976159771597815979159801598115982159831598415985159861598715988159891599015991159921599315994159951599615997159981599916000160011600216003160041600516006160071600816009160101601116012160131601416015160161601716018160191602016021160221602316024160251602616027160281602916030160311603216033160341603516036160371603816039160401604116042160431604416045160461604716048160491605016051160521605316054160551605616057160581605916060160611606216063160641606516066160671606816069160701607116072160731607416075160761607716078160791608016081160821608316084160851608616087160881608916090160911609216093160941609516096160971609816099161001610116102161031610416105161061610716108161091611016111161121611316114161151611616117161181611916120161211612216123161241612516126161271612816129161301613116132161331613416135161361613716138161391614016141161421614316144161451614616147161481614916150161511615216153161541615516156161571615816159161601616116162161631616416165161661616716168161691617016171161721617316174161751617616177161781617916180161811618216183161841618516186161871618816189161901619116192161931619416195161961619716198161991620016201162021620316204162051620616207162081620916210162111621216213162141621516216162171621816219162201622116222162231622416225162261622716228162291623016231162321623316234162351623616237162381623916240162411624216243162441624516246162471624816249162501625116252162531625416255162561625716258162591626016261162621626316264162651626616267162681626916270162711627216273162741627516276162771627816279162801628116282162831628416285162861628716288162891629016291162921629316294162951629616297162981629916300163011630216303163041630516306163071630816309163101631116312163131631416315163161631716318163191632016321163221632316324163251632616327163281632916330163311633216333163341633516336163371633816339163401634116342163431634416345163461634716348163491635016351163521635316354163551635616357163581635916360163611636216363163641636516366163671636816369163701637116372163731637416375163761637716378163791638016381163821638316384163851638616387163881638916390163911639216393163941639516396163971639816399164001640116402164031640416405164061640716408164091641016411164121641316414164151641616417164181641916420164211642216423164241642516426164271642816429164301643116432164331643416435164361643716438164391644016441164421644316444164451644616447164481644916450164511645216453164541645516456164571645816459164601646116462164631646416465164661646716468164691647016471164721647316474164751647616477164781647916480164811648216483164841648516486164871648816489164901649116492164931649416495164961649716498164991650016501165021650316504165051650616507165081650916510165111651216513165141651516516165171651816519165201652116522165231652416525165261652716528165291653016531165321653316534165351653616537165381653916540165411654216543165441654516546165471654816549165501655116552165531655416555165561655716558165591656016561165621656316564165651656616567165681656916570165711657216573165741657516576165771657816579165801658116582165831658416585165861658716588165891659016591165921659316594165951659616597165981659916600166011660216603166041660516606166071660816609166101661116612166131661416615166161661716618166191662016621166221662316624166251662616627166281662916630166311663216633166341663516636166371663816639166401664116642166431664416645166461664716648166491665016651166521665316654166551665616657166581665916660166611666216663166641666516666166671666816669166701667116672166731667416675166761667716678166791668016681166821668316684166851668616687166881668916690166911669216693166941669516696166971669816699167001670116702167031670416705167061670716708167091671016711167121671316714167151671616717167181671916720167211672216723167241672516726167271672816729167301673116732167331673416735167361673716738167391674016741167421674316744167451674616747167481674916750167511675216753167541675516756167571675816759167601676116762167631676416765167661676716768167691677016771167721677316774167751677616777167781677916780167811678216783167841678516786167871678816789167901679116792167931679416795167961679716798167991680016801168021680316804168051680616807168081680916810168111681216813168141681516816168171681816819168201682116822168231682416825168261682716828168291683016831168321683316834168351683616837168381683916840168411684216843168441684516846168471684816849168501685116852168531685416855168561685716858168591686016861168621686316864168651686616867168681686916870168711687216873168741687516876168771687816879168801688116882168831688416885168861688716888168891689016891168921689316894168951689616897168981689916900169011690216903169041690516906169071690816909169101691116912169131691416915169161691716918169191692016921169221692316924169251692616927169281692916930169311693216933169341693516936169371693816939169401694116942169431694416945169461694716948169491695016951169521695316954169551695616957169581695916960169611696216963169641696516966169671696816969169701697116972169731697416975169761697716978169791698016981169821698316984169851698616987169881698916990169911699216993169941699516996169971699816999170001700117002170031700417005170061700717008170091701017011170121701317014170151701617017170181701917020170211702217023170241702517026170271702817029170301703117032170331703417035170361703717038170391704017041170421704317044170451704617047170481704917050170511705217053170541705517056170571705817059170601706117062170631706417065170661706717068170691707017071170721707317074170751707617077170781707917080170811708217083170841708517086170871708817089170901709117092170931709417095170961709717098170991710017101171021710317104171051710617107171081710917110171111711217113171141711517116171171711817119171201712117122171231712417125171261712717128171291713017131171321713317134171351713617137171381713917140171411714217143171441714517146171471714817149171501715117152171531715417155171561715717158171591716017161171621716317164171651716617167171681716917170171711717217173171741717517176171771717817179171801718117182171831718417185171861718717188171891719017191171921719317194171951719617197171981719917200172011720217203172041720517206172071720817209172101721117212172131721417215172161721717218172191722017221172221722317224172251722617227172281722917230172311723217233172341723517236172371723817239172401724117242172431724417245172461724717248172491725017251172521725317254172551725617257172581725917260172611726217263172641726517266172671726817269172701727117272172731727417275172761727717278172791728017281172821728317284172851728617287172881728917290172911729217293172941729517296172971729817299173001730117302173031730417305173061730717308173091731017311173121731317314173151731617317173181731917320173211732217323173241732517326173271732817329173301733117332173331733417335173361733717338173391734017341173421734317344173451734617347173481734917350173511735217353173541735517356173571735817359173601736117362173631736417365173661736717368173691737017371173721737317374173751737617377173781737917380173811738217383173841738517386173871738817389173901739117392173931739417395173961739717398173991740017401174021740317404174051740617407174081740917410174111741217413174141741517416174171741817419174201742117422174231742417425174261742717428174291743017431174321743317434174351743617437174381743917440174411744217443174441744517446174471744817449174501745117452174531745417455174561745717458174591746017461174621746317464174651746617467174681746917470174711747217473174741747517476174771747817479174801748117482174831748417485174861748717488174891749017491174921749317494174951749617497174981749917500175011750217503175041750517506175071750817509175101751117512175131751417515175161751717518175191752017521175221752317524175251752617527175281752917530175311753217533175341753517536175371753817539175401754117542175431754417545175461754717548175491755017551175521755317554175551755617557175581755917560175611756217563175641756517566175671756817569175701757117572175731757417575175761757717578175791758017581175821758317584175851758617587175881758917590175911759217593175941759517596175971759817599176001760117602176031760417605176061760717608176091761017611176121761317614176151761617617176181761917620176211762217623176241762517626176271762817629176301763117632176331763417635176361763717638176391764017641176421764317644176451764617647176481764917650176511765217653176541765517656176571765817659176601766117662176631766417665176661766717668176691767017671176721767317674176751767617677176781767917680176811768217683176841768517686176871768817689176901769117692176931769417695176961769717698176991770017701177021770317704177051770617707177081770917710177111771217713177141771517716177171771817719177201772117722177231772417725177261772717728177291773017731177321773317734177351773617737177381773917740177411774217743177441774517746177471774817749177501775117752177531775417755177561775717758177591776017761177621776317764177651776617767177681776917770177711777217773177741777517776177771777817779177801778117782177831778417785177861778717788177891779017791177921779317794177951779617797177981779917800178011780217803178041780517806178071780817809178101781117812178131781417815178161781717818178191782017821178221782317824178251782617827178281782917830178311783217833178341783517836178371783817839178401784117842178431784417845178461784717848178491785017851178521785317854178551785617857178581785917860178611786217863178641786517866178671786817869178701787117872178731787417875178761787717878178791788017881178821788317884178851788617887178881788917890178911789217893178941789517896178971789817899179001790117902179031790417905179061790717908179091791017911179121791317914179151791617917179181791917920179211792217923179241792517926179271792817929179301793117932179331793417935179361793717938179391794017941179421794317944179451794617947179481794917950179511795217953179541795517956179571795817959179601796117962179631796417965179661796717968179691797017971179721797317974179751797617977179781797917980179811798217983179841798517986179871798817989179901799117992179931799417995179961799717998179991800018001180021800318004180051800618007180081800918010180111801218013180141801518016180171801818019180201802118022180231802418025180261802718028180291803018031180321803318034180351803618037180381803918040180411804218043180441804518046180471804818049180501805118052180531805418055180561805718058180591806018061180621806318064180651806618067180681806918070180711807218073180741807518076180771807818079180801808118082180831808418085180861808718088180891809018091180921809318094180951809618097180981809918100181011810218103181041810518106181071810818109181101811118112181131811418115181161811718118181191812018121181221812318124181251812618127181281812918130181311813218133181341813518136181371813818139181401814118142181431814418145181461814718148181491815018151181521815318154181551815618157181581815918160181611816218163181641816518166181671816818169181701817118172181731817418175181761817718178181791818018181181821818318184181851818618187181881818918190181911819218193181941819518196181971819818199182001820118202182031820418205182061820718208182091821018211182121821318214182151821618217182181821918220182211822218223182241822518226182271822818229182301823118232182331823418235182361823718238182391824018241182421824318244182451824618247182481824918250182511825218253182541825518256182571825818259182601826118262182631826418265182661826718268182691827018271182721827318274182751827618277182781827918280182811828218283182841828518286182871828818289182901829118292182931829418295182961829718298182991830018301183021830318304183051830618307183081830918310183111831218313183141831518316183171831818319183201832118322183231832418325183261832718328183291833018331183321833318334183351833618337183381833918340183411834218343183441834518346183471834818349183501835118352183531835418355183561835718358183591836018361183621836318364183651836618367183681836918370183711837218373183741837518376183771837818379183801838118382183831838418385183861838718388183891839018391183921839318394183951839618397183981839918400184011840218403184041840518406184071840818409184101841118412184131841418415184161841718418184191842018421184221842318424184251842618427184281842918430184311843218433184341843518436184371843818439184401844118442184431844418445184461844718448184491845018451184521845318454184551845618457184581845918460184611846218463184641846518466184671846818469184701847118472184731847418475184761847718478184791848018481184821848318484184851848618487184881848918490184911849218493184941849518496184971849818499185001850118502185031850418505185061850718508185091851018511185121851318514185151851618517185181851918520185211852218523185241852518526185271852818529185301853118532185331853418535185361853718538185391854018541185421854318544185451854618547185481854918550185511855218553185541855518556185571855818559185601856118562185631856418565185661856718568185691857018571185721857318574185751857618577185781857918580185811858218583185841858518586185871858818589185901859118592185931859418595185961859718598185991860018601186021860318604186051860618607186081860918610186111861218613186141861518616186171861818619186201862118622186231862418625186261862718628186291863018631186321863318634186351863618637186381863918640186411864218643186441864518646186471864818649186501865118652186531865418655186561865718658186591866018661186621866318664186651866618667186681866918670186711867218673186741867518676186771867818679186801868118682186831868418685186861868718688186891869018691186921869318694186951869618697186981869918700187011870218703187041870518706187071870818709187101871118712187131871418715187161871718718187191872018721187221872318724187251872618727187281872918730187311873218733187341873518736187371873818739187401874118742187431874418745187461874718748187491875018751187521875318754187551875618757187581875918760187611876218763187641876518766187671876818769187701877118772187731877418775187761877718778187791878018781187821878318784187851878618787187881878918790187911879218793187941879518796187971879818799188001880118802188031880418805188061880718808188091881018811188121881318814188151881618817188181881918820188211882218823188241882518826188271882818829188301883118832188331883418835188361883718838188391884018841188421884318844188451884618847188481884918850188511885218853188541885518856188571885818859188601886118862188631886418865188661886718868188691887018871188721887318874188751887618877188781887918880188811888218883188841888518886188871888818889188901889118892188931889418895188961889718898188991890018901189021890318904189051890618907189081890918910189111891218913189141891518916189171891818919189201892118922189231892418925189261892718928189291893018931189321893318934189351893618937189381893918940189411894218943189441894518946189471894818949189501895118952189531895418955189561895718958189591896018961189621896318964189651896618967189681896918970189711897218973189741897518976189771897818979189801898118982189831898418985189861898718988189891899018991189921899318994189951899618997189981899919000190011900219003190041900519006190071900819009190101901119012190131901419015190161901719018190191902019021190221902319024190251902619027190281902919030190311903219033190341903519036190371903819039190401904119042190431904419045190461904719048190491905019051190521905319054190551905619057190581905919060190611906219063190641906519066190671906819069190701907119072190731907419075190761907719078190791908019081190821908319084190851908619087190881908919090190911909219093190941909519096190971909819099191001910119102191031910419105191061910719108191091911019111191121911319114191151911619117191181911919120191211912219123191241912519126191271912819129191301913119132191331913419135191361913719138191391914019141191421914319144191451914619147191481914919150191511915219153191541915519156191571915819159191601916119162191631916419165191661916719168191691917019171191721917319174191751917619177191781917919180191811918219183191841918519186191871918819189191901919119192191931919419195191961919719198191991920019201192021920319204192051920619207192081920919210192111921219213192141921519216192171921819219192201922119222192231922419225192261922719228192291923019231192321923319234192351923619237192381923919240192411924219243192441924519246192471924819249192501925119252192531925419255192561925719258192591926019261192621926319264192651926619267192681926919270192711927219273192741927519276192771927819279192801928119282192831928419285192861928719288192891929019291192921929319294192951929619297192981929919300193011930219303193041930519306193071930819309193101931119312193131931419315193161931719318193191932019321193221932319324193251932619327193281932919330193311933219333193341933519336193371933819339193401934119342193431934419345193461934719348193491935019351193521935319354193551935619357193581935919360193611936219363193641936519366193671936819369193701937119372193731937419375193761937719378193791938019381193821938319384193851938619387193881938919390193911939219393193941939519396193971939819399194001940119402194031940419405194061940719408194091941019411194121941319414194151941619417194181941919420194211942219423194241942519426194271942819429194301943119432194331943419435194361943719438194391944019441194421944319444194451944619447194481944919450194511945219453194541945519456194571945819459194601946119462194631946419465194661946719468194691947019471194721947319474194751947619477194781947919480194811948219483194841948519486194871948819489194901949119492194931949419495194961949719498194991950019501195021950319504195051950619507195081950919510195111951219513195141951519516195171951819519195201952119522195231952419525195261952719528195291953019531195321953319534195351953619537195381953919540195411954219543195441954519546195471954819549195501955119552195531955419555195561955719558195591956019561195621956319564195651956619567195681956919570195711957219573195741957519576195771957819579195801958119582195831958419585195861958719588195891959019591195921959319594195951959619597195981959919600196011960219603196041960519606196071960819609196101961119612196131961419615196161961719618196191962019621196221962319624196251962619627196281962919630196311963219633196341963519636196371963819639196401964119642196431964419645196461964719648196491965019651196521965319654196551965619657196581965919660196611966219663196641966519666196671966819669196701967119672196731967419675196761967719678196791968019681196821968319684196851968619687196881968919690196911969219693196941969519696196971969819699197001970119702197031970419705197061970719708197091971019711197121971319714197151971619717197181971919720197211972219723197241972519726197271972819729197301973119732197331973419735197361973719738197391974019741197421974319744197451974619747197481974919750197511975219753197541975519756197571975819759197601976119762197631976419765197661976719768197691977019771197721977319774197751977619777197781977919780197811978219783197841978519786197871978819789197901979119792197931979419795197961979719798197991980019801198021980319804198051980619807198081980919810198111981219813198141981519816198171981819819198201982119822198231982419825198261982719828198291983019831198321983319834198351983619837198381983919840198411984219843198441984519846198471984819849198501985119852198531985419855198561985719858198591986019861198621986319864198651986619867198681986919870198711987219873198741987519876198771987819879198801988119882198831988419885198861988719888198891989019891198921989319894198951989619897198981989919900199011990219903199041990519906199071990819909199101991119912199131991419915199161991719918199191992019921199221992319924199251992619927199281992919930199311993219933199341993519936199371993819939199401994119942199431994419945199461994719948199491995019951199521995319954199551995619957199581995919960199611996219963199641996519966199671996819969199701997119972199731997419975199761997719978199791998019981199821998319984199851998619987199881998919990199911999219993199941999519996199971999819999200002000120002200032000420005200062000720008200092001020011200122001320014200152001620017200182001920020200212002220023200242002520026200272002820029200302003120032200332003420035200362003720038200392004020041200422004320044200452004620047200482004920050200512005220053200542005520056200572005820059200602006120062200632006420065200662006720068200692007020071200722007320074200752007620077200782007920080200812008220083200842008520086200872008820089200902009120092200932009420095200962009720098200992010020101201022010320104201052010620107201082010920110201112011220113201142011520116201172011820119201202012120122201232012420125201262012720128201292013020131201322013320134201352013620137201382013920140201412014220143201442014520146201472014820149201502015120152201532015420155201562015720158201592016020161201622016320164201652016620167201682016920170201712017220173201742017520176201772017820179201802018120182201832018420185201862018720188201892019020191201922019320194201952019620197201982019920200202012020220203202042020520206202072020820209202102021120212202132021420215202162021720218202192022020221202222022320224202252022620227202282022920230202312023220233202342023520236202372023820239202402024120242202432024420245202462024720248202492025020251202522025320254202552025620257202582025920260202612026220263202642026520266202672026820269202702027120272202732027420275202762027720278202792028020281202822028320284202852028620287202882028920290202912029220293202942029520296202972029820299203002030120302203032030420305203062030720308203092031020311203122031320314203152031620317203182031920320203212032220323203242032520326203272032820329203302033120332203332033420335203362033720338203392034020341203422034320344203452034620347203482034920350203512035220353203542035520356203572035820359203602036120362203632036420365203662036720368203692037020371203722037320374203752037620377203782037920380203812038220383203842038520386203872038820389203902039120392203932039420395203962039720398203992040020401204022040320404204052040620407204082040920410204112041220413204142041520416204172041820419204202042120422204232042420425204262042720428204292043020431204322043320434204352043620437204382043920440204412044220443204442044520446204472044820449204502045120452204532045420455204562045720458204592046020461204622046320464204652046620467204682046920470204712047220473204742047520476204772047820479204802048120482204832048420485204862048720488204892049020491204922049320494204952049620497204982049920500205012050220503205042050520506205072050820509205102051120512205132051420515205162051720518205192052020521205222052320524205252052620527205282052920530205312053220533205342053520536205372053820539
  1. #!/usr/bin/env perl
  2. # $Id: imapsync,v 2.178 2022/01/12 21:28:37 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, migrating
  17. and archiving email mailboxes between two imap servers, one way,
  18. and without duplicates.
  19. =head1 VERSION
  20. This documentation refers to Imapsync $Revision: 2.178 $
  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. If you don't understand the previous sentence, it's normal,
  36. it's pedantic computer-oriented jargon.
  37. All folders are transferred, recursively, meaning
  38. the whole folder hierarchy is taken, all messages in them,
  39. and all message flags (\Seen \Answered \Flagged etc.)
  40. are synced too.
  41. Imapsync reduces the amount of data transferred by not transferring
  42. a given message if it already resides on the destination side.
  43. Messages that are on the destination side but not on the
  44. source side stay as they are. See the --delete2
  45. option to have strict sync and delete them.
  46. How imapsync know a message is already on both sides?
  47. Same specific headers and the transfer is done only once.
  48. By default, the identification headers are
  49. "Message-Id:" and "Received:" lines
  50. but this choice can be changed with the --useheader option,
  51. most often a duplicate problem is solved by using
  52. --useheader "Message-Id"
  53. All flags are preserved, unread messages will stay unread,
  54. read ones will stay read, deleted will stay deleted.
  55. In the IMAP protocol, a deleted message is not really deleted,
  56. it is marked \Deleted and can be undelete. Real destruction
  57. comes with the EXPUNGE or UIDEXPUNGE IMAP commands.
  58. You can abort the transfer at any time and restart it later,
  59. imapsync works well with bad connections and interruptions,
  60. by design. On a terminal hit Ctr-c twice within two seconds
  61. to abort the program. Hit Ctr-c just once makes
  62. imapsync reconnect to both imap servers.
  63. How do you know the sync is finished and well done?
  64. When imapsync ends by itself it mentions it with lines like those:
  65. Exiting with return value 0 (EX_OK: successful termination) 0/50 nb_errors/max_errors PID 301
  66. Removing pidfile /tmp/imapsync.pid
  67. Log file is LOG_imapsync/2020_11_17_15_59_22_761_test1_test2.txt ( to change it, use --logfile filepath ; or use --nolog to turn off logging )
  68. If you don't have those lines it means that either the sync process is still
  69. running (or eventually hanging indefinitely) or that it ended without
  70. a whisper, a strong kill -9 on Linux for example.
  71. If you have those final lines then it means the sync process is properly
  72. finished. It may have encountered problems though.
  73. A good synchronization is mentioned by some lines above the last ones, especially
  74. those three lines:
  75. The sync looks good, all 1745 identified messages in host1 are on host2.
  76. There is no unidentified message on host1.
  77. Detected 0 errors
  78. A classical scenario is synchronizing a mailbox B from another mailbox A
  79. where you just want to keep a strict copy of A in B. Strict meaning
  80. all messages in A will be in B but no more.
  81. For this, option --delete2 can be used, it deletes messages in the host2
  82. folder B that are not in the host1 folder A. If you also need to destroy
  83. host2 folders that are not in host1 then use --delete2folders. See also
  84. --delete2foldersonly and --delete2foldersbutnot to set up exceptions
  85. on folders to destroy. INBOX will never be destroyed, it's a mandatory
  86. folder in IMAP so imapsync doesn't even try to remove it.
  87. A different scenario is to delete the messages from the source mailbox
  88. after a successful transfer, it can be a good feature when migrating
  89. mailboxes since messages will be only on one side. The source account
  90. will only have messages that are not on the destination yet, ie,
  91. messages that arrived after a sync or that failed to be transferred.
  92. In that case, use the --delete1 option. Option --delete1 implies also
  93. the option --expunge1 so all messages marked deleted on host1 will be
  94. deleted. In IMAP protocol deleting a message does not delete it,
  95. it marks it with the flag \Deleted, allowing an undelete. Expunging
  96. a folder removes, definitively, all the messages marked as \Deleted
  97. in this folder.
  98. You can also decide to remove empty folders once all of their messages
  99. have been transferred. Add --delete1emptyfolders to obtain this
  100. behavior.
  101. Imapsync is not adequate for maintaining two active imap accounts
  102. in synchronization when the user plays independently on both sides.
  103. Use offlineimap (written by John Goerzen) or mbsync (written by
  104. Michael R. Elkins) for a 2 ways synchronization.
  105. =head1 OPTIONS
  106. usage: imapsync [options]
  107. The standard options are the six values forming the credentials.
  108. Three values on each side are needed in order to login into the IMAP
  109. servers. These six values are a hostname, a username, and a password, two times.
  110. Conventions used in the following descriptions of the options:
  111. str means string
  112. int means integer number
  113. flo means float number
  114. reg means regular expression
  115. cmd means command
  116. --dry : Makes imapsync doing nothing for real; it just print what
  117. would be done without --dry.
  118. =head2 OPTIONS/credentials
  119. --host1 str : Source or "from" imap server.
  120. --port1 int : Port to connect on host1.
  121. Optional since default ports are the
  122. well known ports imap/143 or imaps/993.
  123. --user1 str : User to login on host1.
  124. --password1 str : Password of user1.
  125. --host2 str : "destination" imap server.
  126. --port2 int : Port to connect on host2. Optional
  127. --user2 str : User to login on host2.
  128. --password2 str : Password of user2.
  129. --showpasswords : Shows passwords on output instead of "MASKED".
  130. Useful to restart a complete run by just reading
  131. the command line used in the log,
  132. or to debug passwords.
  133. It's not a secure practice at all!
  134. --passfile1 str : Password file for the user1. It must contain the
  135. password on the first line. This option avoids showing
  136. the password on the command line like --password1 does.
  137. --passfile2 str : Password file for the user2.
  138. You can also pass the passwords in the environment variables
  139. IMAPSYNC_PASSWORD1 and IMAPSYNC_PASSWORD2. If you don't pass
  140. the user1 password via --password1 nor --passfile1 nor $IMAPSYNC_PASSWORD1
  141. then imapsync will prompt to enter the password on the terminal.
  142. Same thing for user2 password.
  143. =head2 OPTIONS/encryption
  144. --nossl1 : Do not use a SSL connection on host1.
  145. --ssl1 : Use a SSL connection on host1. On by default if possible.
  146. --nossl2 : Do not use a SSL connection on host2.
  147. --ssl2 : Use a SSL connection on host2. On by default if possible.
  148. --notls1 : Do not use a TLS connection on host1.
  149. --tls1 : Use a TLS connection on host1. On by default if possible.
  150. --notls2 : Do not use a TLS connection on host2.
  151. --tls2 : Use a TLS connection on host2. On by default if possible.
  152. --debugssl int : SSL debug mode from 0 to 4.
  153. --sslargs1 str : Pass any ssl parameter for host1 ssl or tls connection. Example:
  154. --sslargs1 SSL_verify_mode=1 --sslargs1 SSL_version=SSLv3
  155. See all possibilities in the new() method of IO::Socket::SSL
  156. http://search.cpan.org/perldoc?IO::Socket::SSL#Description_Of_Methods
  157. --sslargs2 str : Pass any ssl parameter for host2 ssl or tls connection.
  158. See --sslargs1
  159. =head2 OPTIONS/authentication
  160. --authmech1 str : Auth mechanism to use with host1:
  161. PLAIN, LOGIN, CRAM-MD5 etc. Use UPPERCASE.
  162. --authmech2 str : Auth mechanism to use with host2. See --authmech1
  163. --authuser1 str : User to auth with on host1 (admin user).
  164. Avoid using --authmech1 SOMETHING with --authuser1.
  165. --authuser2 str : User to auth with on host2 (admin user).
  166. --proxyauth1 : Use proxyauth on host1. Requires --authuser1.
  167. Required by Sun/iPlanet/Netscape IMAP servers to
  168. be able to use an administrative user.
  169. --proxyauth2 : Use proxyauth on host2. Requires --authuser2.
  170. --authmd51 : Use MD5 authentication for host1.
  171. --authmd52 : Use MD5 authentication for host2.
  172. --domain1 str : Domain on host1 (NTLM authentication).
  173. --domain2 str : Domain on host2 (NTLM authentication).
  174. --oauthaccesstoken1 str : The access token to authenticate with OAUTH2.
  175. It will be combined with the --user1 value to form the
  176. string to pass with XOAUTH2 authentication.
  177. The password given by --password1 or --passfile1
  178. is ignored.
  179. Instead of the access token itself, the value can be a
  180. file containing the access token on the first line.
  181. If the value is a file, imapsync reads its first line
  182. and take this line as the access token. The advantage
  183. of the file is that if the access token changes then
  184. imapsync can read it again when it needs to reconnect
  185. during a run.
  186. --oauthaccesstoken2 str : same thing as --oauthaccesstoken1
  187. --oauthdirect1 str : The direct string to pass with XOAUTH2 authentication.
  188. The password given by --password1 or --passfile1 and
  189. the user given by --user1 are ignored.
  190. --oauthdirect2 str : same thing as oauthdirect1
  191. =head2 OPTIONS/folders
  192. --folder str : Sync this folder.
  193. --folder str : and this one, etc.
  194. --folderrec str : Sync this folder recursively.
  195. --folderrec str : and this one, etc.
  196. --folderfirst str : Sync this folder first. Ex. --folderfirst "INBOX"
  197. --folderfirst str : then this one, etc.
  198. --folderlast str : Sync this folder last. --folderlast "[Gmail]/All Mail"
  199. --folderlast str : then this one, etc.
  200. --nomixfolders : Do not merge folders when host1 is case-sensitive
  201. while host2 is not (like Exchange). Only the first
  202. similar folder is synced (example: with folders
  203. "Sent", "SENT" and "sent"
  204. on host1 only "Sent" will be synced to host2).
  205. --skipemptyfolders : Empty host1 folders are not created on host2.
  206. --include reg : Sync folders matching this regular expression
  207. --include reg : or this one, etc.
  208. If both --include --exclude options are used, then
  209. include is done before.
  210. --exclude reg : Skips folders matching this regular expression
  211. Several folders to avoid:
  212. --exclude 'fold1|fold2|f3' skips fold1, fold2 and f3.
  213. --exclude reg : or this one, etc.
  214. --automap : guesses folders mapping, for folders well known as
  215. "Sent", "Junk", "Drafts", "All", "Archive", "Flagged".
  216. --f1f2 str1=str2 : Force folder str1 to be synced to str2,
  217. --f1f2 overrides --automap and --regextrans2.
  218. Use several --f1f2 options to map several folders.
  219. Option --f1f2 is a one to one only folder mapping,
  220. str1 and str2 have to be full path folder names.
  221. --subfolder2 str : Syncs the whole host1 folders hierarchy under the
  222. host2 folder named str.
  223. It does it internally by adding three
  224. --regextrans2 options before all others.
  225. Add --debug to see what's really going on.
  226. --subfolder1 str : Syncs the host1 folders hierarchy which is under folder
  227. str to the root hierarchy of host2.
  228. It's the couterpart of a sync done by --subfolder2
  229. when doing it in the reverse order.
  230. Backup/Restore scenario:
  231. Use --subfolder2 str for a backup to the folder str
  232. on host2. Then use --subfolder1 str for restoring
  233. from the folder str, after inverting
  234. host1/host2 user1/user2 values.
  235. --subscribed : Transfers subscribed folders.
  236. --subscribe : Subscribe to the folders transferred on the
  237. host2 that are subscribed on host1. On by default.
  238. --subscribeall : Subscribe to the folders transferred on the
  239. host2 even if they are not subscribed on host1.
  240. --prefix1 str : Remove prefix str to all destination folders,
  241. usually "INBOX." or "INBOX/" or an empty string "".
  242. imapsync guesses the prefix if host1 imap server
  243. does not have NAMESPACE capability. So this option
  244. should not be used most of the time.
  245. --prefix2 str : Add prefix to all host2 folders. See --prefix1
  246. --sep1 str : Host1 separator. This option should not be used
  247. most of the time.
  248. Imapsync gets the separator from the server itself,
  249. by using NAMESPACE, or it tries to guess it
  250. from the folders listing (it counts
  251. characters / . \\ \ in folder names and choose the
  252. more frequent, or finally / if nothing is found.
  253. --sep2 str : Host2 separator. See --sep1
  254. --regextrans2 reg : Apply the whole regex to each destination folders.
  255. --regextrans2 reg : and this one. etc.
  256. When you play with the --regextrans2 option, first
  257. add also the safe options --dry --justfolders
  258. Then, when happy, remove --dry for a run, then
  259. remove --justfolders for the next ones.
  260. Have in mind that --regextrans2 is applied after
  261. the automatic prefix and separator inversion.
  262. For examples see:
  263. https://imapsync.lamiral.info/FAQ.d/FAQ.Folders_Mapping.txt
  264. =head2 OPTIONS/folders sizes
  265. --nofoldersizes : Do not calculate the size of each folder at the
  266. beginning of the sync. Default is to calculate them.
  267. --nofoldersizesatend: Do not calculate the size of each folder at the
  268. end of the sync. Default is to calculate them.
  269. --justfoldersizes : Exit after having printed the initial folder sizes.
  270. =head2 OPTIONS/tmp
  271. --tmpdir str : Where to store temporary files and subdirectories.
  272. Will be created if it doesn't exist.
  273. Default is system specific, Unix is /tmp but
  274. /tmp is often too small and deleted at reboot.
  275. --tmpdir /var/tmp should be better.
  276. --pidfile str : The file where imapsync pid is written,
  277. it can be dirname/filename complete path.
  278. The default name is imapsync.pid in tmpdir.
  279. --pidfilelocking : Abort if pidfile already exists. Useful to avoid
  280. concurrent transfers on the same mailbox.
  281. =head2 OPTIONS/log
  282. --nolog : Turn off logging on file
  283. --logfile str : Change the default log filename (can be dirname/filename).
  284. --logdir str : Change the default log directory. Default is LOG_imapsync/
  285. The default logfile name is for example
  286. LOG_imapsync/2019_12_22_23_57_59_532_user1_user2.txt
  287. where:
  288. 2019_12_22_23_57_59_532 is nearly the date of the start
  289. YYYY_MM_DD_HH_MM_SS_mmm
  290. year_month_day_hour_minute_seconde_millisecond
  291. and user1 user2 are the --user1 --user2 values.
  292. =head2 OPTIONS/messages
  293. --skipmess reg : Skips messages matching the regex.
  294. Example: 'm/[\x80-\xff]/' # to avoid 8bits messages.
  295. --skipmess is applied before --regexmess
  296. --skipmess reg : or this one, etc.
  297. --skipcrossduplicates : Avoid copying messages that are already copied
  298. in another folder, good from Gmail to XYZ when
  299. XYZ is not also Gmail.
  300. Activated with --gmail1 unless --noskipcrossduplicates
  301. --debugcrossduplicates : Prints which messages (UIDs) are skipped with
  302. --skipcrossduplicates and in what other folders
  303. they are.
  304. --pipemess cmd : Apply this cmd command to each message content
  305. before the copy.
  306. --pipemess cmd : and this one, etc.
  307. With several --pipemess, the output of each cmd
  308. command (STDOUT) is given to the input (STDIN)
  309. of the next command.
  310. For example,
  311. --pipemess cmd1 --pipemess cmd2 --pipemess cmd3
  312. is like a Unix pipe:
  313. "cat message | cmd1 | cmd2 | cmd3"
  314. --disarmreadreceipts : Disarms read receipts (host2 Exchange issue)
  315. --regexmess reg : Apply the whole regex to each message before transfer.
  316. Example: 's/\000/ /g' # to replace null characters
  317. by spaces.
  318. --regexmess reg : and this one, etc.
  319. --truncmess int : truncates messages when their size exceed the int
  320. value, specified in bytes. Good to sync too big
  321. messages or to "suppress" attachments.
  322. Have in mind that this way, messages become
  323. uncoherent somehow.
  324. =head2 OPTIONS/labels
  325. Gmail present labels as folders in imap. Imapsync can accelerate the sync
  326. by syncing X-GM-LABELS, it will avoid to transfer messages when they are
  327. already on host2 in another folder.
  328. --synclabels : Syncs also Gmail labels when a message is copied to host2.
  329. Activated by default with --gmail1 --gmail2 unless
  330. --nosynclabels is added.
  331. --resynclabels : Resyncs Gmail labels when a message is already on host2.
  332. Activated by default with --gmail1 --gmail2 unless
  333. --noresynclabels is added.
  334. For Gmail syncs, see also:
  335. https://imapsync.lamiral.info/FAQ.d/FAQ.Gmail.txt
  336. =head2 OPTIONS/flags
  337. If you encounter flag problems see also:
  338. https://imapsync.lamiral.info/FAQ.d/FAQ.Flags.txt
  339. --regexflag reg : Apply the whole regex to each flags list.
  340. Example: 's/"Junk"//g' # to remove "Junk" flag.
  341. --regexflag reg : then this one, etc.
  342. --resyncflags : Resync flags for already transferred messages.
  343. On by default.
  344. --noresyncflags : Do not resync flags for already transferred messages.
  345. May be useful when a user has already started to play
  346. with its host2 account.
  347. --filterbuggyflags : Filter flags known to be buggy and generators of errors
  348. "BAD Invalid system flag" or "NO APPEND Invalid flag list".
  349. =head2 OPTIONS/deletions
  350. --delete1 : Deletes messages on host1 server after a successful
  351. transfer. Option --delete1 has the following behavior:
  352. it marks messages as deleted with the IMAP flag
  353. \Deleted, then messages are really deleted with an
  354. EXPUNGE IMAP command. If expunging after each message
  355. slows down too much the sync then use
  356. --noexpungeaftereach to speed up, expunging will then be
  357. done only twice per folder, one at the beginning and
  358. one at the end of a folder sync.
  359. --expunge1 : Expunge messages on host1 just before syncing a folder.
  360. Expunge is done per folder.
  361. Expunge aims is to really delete messages marked deleted.
  362. An expunge is also done after each message copied
  363. if option --delete1 is set (unless --noexpungeaftereach).
  364. --noexpunge1 : Do not expunge messages on host1.
  365. --delete1emptyfolders : Deletes empty folders on host1, INBOX excepted.
  366. Useful with --delete1 since what remains on host1
  367. is only what failed to be synced.
  368. --delete2 : Delete messages in the host2 account that are not in
  369. the host1 account. Useful for backup or pre-sync.
  370. --delete2 implies --uidexpunge2
  371. --delete2duplicates : Deletes messages in host2 that are duplicates in host2.
  372. Works only without --useuid since duplicates are
  373. detected with an header part of each message.
  374. NB: --delete2duplicates is far less violent than --delete2
  375. since it removes only duplicates.
  376. --delete2folders : Delete folders in host2 that are not in host1.
  377. For safety, first try it like this, it is safe:
  378. --delete2folders --dry --justfolders --nofoldersizes
  379. and see what folders will be deleted.
  380. --delete2foldersonly reg : Delete only folders matching the regex reg.
  381. Example: --delete2foldersonly "/^Junk$|^INBOX.Junk$/"
  382. This option activates --delete2folders
  383. --delete2foldersbutnot reg : Do not delete folders matching the regex rex.
  384. Example: --delete2foldersbutnot "/Tasks$|Contacts$|Foo$/"
  385. This option activates --delete2folders
  386. --noexpunge2 : Do not expunge messages on host2.
  387. --nouidexpunge2 : Do not uidexpunge messages on the host2 account
  388. that are not on the host1 account.
  389. =head2 OPTIONS/dates
  390. If you encounter problems with dates, see also:
  391. https://imapsync.lamiral.info/FAQ.d/FAQ.Dates.txt
  392. --syncinternaldates : Sets the internal dates on host2 as the same as host1.
  393. Turned on by default. Internal date is the date
  394. a message arrived on a host (Unix mtime usually).
  395. --idatefromheader : Sets the internal dates on host2 as same as the
  396. ones in "Date:" headers.
  397. =head2 OPTIONS/message selection
  398. --maxsize int : Skip messages larger (or equal) than int bytes
  399. --minsize int : Skip messages smaller (or equal) than int bytes
  400. --maxage int : Skip messages older than int days.
  401. final stats (skipped) don't count older messages
  402. see also --minage
  403. --minage int : Skip messages newer than int days.
  404. final stats (skipped) don't count newer messages
  405. You can do (+ zone are the messages selected):
  406. past|----maxage+++++++++++++++>now
  407. past|+++++++++++++++minage---->now
  408. past|----maxage+++++minage---->now (intersection)
  409. past|++++minage-----maxage++++>now (union)
  410. --search str : Selects only messages returned by this IMAP SEARCH
  411. command. Applied on both sides.
  412. For a complete set of what can be search see
  413. https://imapsync.lamiral.info/FAQ.d/FAQ.Messages_Selection.txt
  414. --search1 str : Same as --search but for selecting host1 messages only.
  415. --search2 str : Same as --search but for selecting host2 messages only.
  416. So --search CRIT equals --search1 CRIT --search2 CRIT
  417. --noabletosearch : Makes --minage and --maxage options use the internal
  418. dates given by a FETCH imap command instead of the
  419. "Date:" header. Internal date is the arrival date
  420. in the mailbox.
  421. --noabletosearch equals --noabletosearch1 --noabletosearch2
  422. --noabletosearch1 : Like --noabletosearch but for host1 only.
  423. --noabletosearch2 : Like --noabletosearch but for host2 only.
  424. --maxlinelength int : skip messages with a line length longer than int bytes.
  425. RFC 2822 says it must be no more than 1000 bytes but
  426. real life servers and email clients do more.
  427. --useheader str : Use this header to compare messages on both sides.
  428. Example: "Message-Id" or "Received" or "Date".
  429. --useheader str and this one, etc.
  430. --syncduplicates : Sync also duplicates. Off by default.
  431. --usecache : Use cache to speed up next syncs. Off by default.
  432. --nousecache : Do not use cache. Caveat: --useuid --nousecache creates
  433. duplicates on multiple runs.
  434. --useuid : Use UIDs instead of headers as a criterion to recognize
  435. messages. Option --usecache is then implied unless
  436. --nousecache is used.
  437. =head2 OPTIONS/miscellaneous
  438. --syncacls : Synchronizes acls (Access Control Lists).
  439. Acls in IMAP are not standardized, be careful
  440. since one acl code on one side may signify something
  441. else on the other one.
  442. --nosyncacls : Does not synchronize acls. This is the default.
  443. --addheader : When a message has no headers to be identified,
  444. --addheader adds a "Message-Id" header,
  445. like "Message-Id: 12345@imapsync", where 12345
  446. is the imap UID of the message on the host1 folder.
  447. Useful to sync folders "Sent" or "Draft".
  448. =head2 OPTIONS/debugging
  449. --debug : Debug mode.
  450. --debugfolders : Debug mode for the folders part only.
  451. --debugcontent : Debug content of the messages transferred. Huge output.
  452. --debugflags : Debug mode for flags.
  453. --debugimap1 : IMAP debug mode for host1. Very verbose.
  454. --debugimap2 : IMAP debug mode for host2. Very verbose.
  455. --debugimap : IMAP debug mode for host1 and host2. Twice very verbose.
  456. --debugmemory : Debug mode showing memory consumption after each copy.
  457. --errorsmax int : Exit when int number of errors is reached. Default is 50.
  458. --tests : Run local non-regression tests. Exit code 0 means all ok.
  459. --testslive : Run a live test with test1.lamiral.info imap server.
  460. Useful to check the basics. Needs internet connection.
  461. --testslive6 : Run a live test with ks6ipv6.lamiral.info imap server.
  462. Useful to check the ipv6 connectivity. Needs internet.
  463. =head2 OPTIONS/specific
  464. --gmail1 : sets --host1 to Gmail and other options. See FAQ.Gmail.txt
  465. --gmail2 : sets --host2 to Gmail and other options. See FAQ.Gmail.txt
  466. --office1 : sets --host1 to Office365 and other options. See FAQ.Office365.txt
  467. --office2 : sets --host2 to Office365 and other options. See FAQ.Office365.txt
  468. --exchange1 : sets options for Exchange. See FAQ.Exchange.txt
  469. --exchange2 : sets options for Exchange. See FAQ.Exchange.txt
  470. --domino1 : sets options for Domino. See FAQ.Domino.txt
  471. --domino2 : sets options for Domino. See FAQ.Domino.txt
  472. =head2 OPTIONS/behavior
  473. --timeout1 flo : Connection timeout in seconds for host1.
  474. Default is 120 and 0 means no timeout at all.
  475. --timeout2 flo : Connection timeout in seconds for host2.
  476. Default is 120 and 0 means no timeout at all.
  477. Caveat, under CGI context, you may encounter a timeout
  478. from the webserver, killing imapsync and the imap connexions.
  479. See the document INSTALL.OnlineUI.txt and search
  480. for "Timeout" for how to deal with this issue.
  481. --keepalive1 : https://metacpan.org/pod/Mail::IMAPClient#Keepalive
  482. Some firewalls and network gears like to timeout connections
  483. prematurely if the connection sits idle.
  484. This option enables SO_KEEPALIVE on the host1 socket.
  485. --keepalive1 is on by default since imapsync release 2.169
  486. Use --nokeepalive1 to disable it.
  487. --keepalive2 : Same as --keepalive2 but for host2.
  488. Use --nokeepalive2 to disable it.
  489. --maxmessagespersecond flo : limits the average number of messages
  490. transferred per second.
  491. --maxbytespersecond int : limits the average transfer rate per second.
  492. --maxbytesafter int : starts --maxbytespersecond limitation only after
  493. --maxbytesafter amount of data transferred.
  494. --maxsleep flo : do not sleep more than int seconds.
  495. On by default, 2 seconds max, like --maxsleep 2
  496. --abort : terminates a previous call still running.
  497. It uses the pidfile to know what process to abort.
  498. --exitwhenover int : Stop syncing and exits when int total bytes
  499. transferred is reached.
  500. --version : Print only the software version.
  501. --noreleasecheck : Do not check for any new imapsync release.
  502. --releasecheck : Check for new imapsync release.
  503. it's an http request to
  504. http://imapsync.lamiral.info/prj/imapsync/VERSION
  505. --noid : Do not send/receive IMAP "ID" command to imap servers.
  506. --justconnect : Just connect to both servers and print useful
  507. information. Need only --host1 and --host2 options.
  508. Obsolete since "imapsync --host1 imaphost" alone
  509. implies --justconnect
  510. --justlogin : Just login to both host1 and host2 with users
  511. credentials, then exit.
  512. --justfolders : Do only things about folders (ignore messages).
  513. --help : print this help.
  514. Example: to synchronize imap account "test1" on "test1.lamiral.info"
  515. to imap account "test2" on "test2.lamiral.info"
  516. with test1 password "secret1"
  517. and test2 password "secret2"
  518. imapsync \
  519. --host1 test1.lamiral.info --user1 test1 --password1 secret1 \
  520. --host2 test2.lamiral.info --user2 test2 --password2 secret2
  521. =cut
  522. # comment
  523. =pod
  524. =head1 SECURITY
  525. You can use --passfile1 instead of --password1 to mention the
  526. password since it is safer. With --password1 option, on Linux,
  527. any user on your host can see the password by using the 'ps auxwwww'
  528. command. Using a variable (like IMAPSYNC_PASSWORD1) is also
  529. dangerous because of the 'ps auxwwwwe' command. So, saving
  530. the password in a well protected file (600 or rw-------) is
  531. the best solution.
  532. Imapsync activates ssl or tls encryption by default, if possible.
  533. What detailed behavior is under this "if possible"?
  534. Imapsync activates ssl if the well known port imaps port (993) is open
  535. on the imap servers. If the imaps port is closed then it open a
  536. normal (clear) connection on port 143 but it looks for TLS support
  537. in the CAPABILITY list of the servers. If TLS is supported
  538. then imapsync goes to encryption with STARTTLS.
  539. If the automatic ssl and the tls detections fail then imapsync will
  540. not protect against sniffing activities on the network, especially
  541. for passwords.
  542. If you want to force ssl or tls just use --ssl1 --ssl2 or --tls1 --tls2
  543. See also the document FAQ.Security.txt in the FAQ.d/ directory
  544. or at https://imapsync.lamiral.info/FAQ.d/FAQ.Security.txt
  545. =head1 EXIT STATUS
  546. Imapsync will exit with a 0 status (return code) if everything went good.
  547. Otherwise, it exits with a non-zero status. That's classical Unix behavior.
  548. Here is the list of the exit code values (an integer between 0 and 255).
  549. In Bourne Shells, this exit code value can be retrieved within the variable
  550. value "$?" if you read it just after the imapsync call.
  551. The names reflect their meaning:
  552. =for comment
  553. egrep '^Readonly my.*\$EX' imapsync | egrep -o 'EX.*' | sed 's_^_ _'
  554. EX_OK => 0 ; #/* successful termination */
  555. EX_USAGE => 64 ; #/* command line usage error */
  556. EX_NOINPUT => 66 ; #/* cannot open input */
  557. EX_UNAVAILABLE => 69 ; #/* service unavailable */
  558. EX_SOFTWARE => 70 ; #/* internal software error */
  559. EXIT_CATCH_ALL => 1 ; # Any other error
  560. EXIT_BY_SIGNAL => 6 ; # Should be 128+n where n is the sig_num
  561. EXIT_BY_FILE => 7 ;
  562. EXIT_PID_FILE_ERROR => 8 ;
  563. EXIT_CONNECTION_FAILURE => 10 ;
  564. EXIT_TLS_FAILURE => 12 ;
  565. EXIT_AUTHENTICATION_FAILURE => 16 ;
  566. EXIT_SUBFOLDER1_NO_EXISTS => 21 ;
  567. EXIT_WITH_ERRORS => 111 ;
  568. EXIT_WITH_ERRORS_MAX => 112 ;
  569. EXIT_OVERQUOTA => 113 ;
  570. EXIT_ERR_APPEND => 114 ;
  571. EXIT_ERR_FETCH => 115 ;
  572. EXIT_ERR_CREATE => 116 ;
  573. EXIT_ERR_SELECT => 117 ;
  574. EXIT_TRANSFER_EXCEEDED => 118 ;
  575. EXIT_ERR_APPEND_VIRUS => 119 ;
  576. EXIT_TESTS_FAILED => 254 ; # Like Test::More API
  577. EXIT_CONNECTION_FAILURE_HOST1 => 101 ;
  578. EXIT_CONNECTION_FAILURE_HOST2 => 102 ;
  579. EXIT_AUTHENTICATION_FAILURE_USER1 => 161 ;
  580. EXIT_AUTHENTICATION_FAILURE_USER2 => 162 ;
  581. =head1 LICENSE AND COPYRIGHT
  582. Imapsync is free, open, public but not always gratis software
  583. cover by the NOLIMIT Public License, now called NLPL.
  584. See the LICENSE file included in the distribution or just read this
  585. simple sentence as it IS the licence text:
  586. "No limits to do anything with this work and this license."
  587. In case it is not long enough, I repeat:
  588. "No limits to do anything with this work and this license."
  589. Look at https://imapsync.lamiral.info/LICENSE
  590. =head1 AUTHOR
  591. Gilles LAMIRAL <gilles@lamiral.info>
  592. Good feedback is always welcome.
  593. Bad feedback is very often welcome.
  594. Gilles LAMIRAL earns his living by writing, installing,
  595. configuring and sometimes teaching free, open and often gratis
  596. software. Imapsync used to be "always gratis" but now it is
  597. only "often gratis" because imapsync is sold by its author,
  598. your servitor, a good way to maintain and support free open public
  599. software tools over decades.
  600. =head1 BUGS AND LIMITATIONS
  601. See https://imapsync.lamiral.info/FAQ.d/FAQ.Reporting_Bugs.txt
  602. =head1 IMAP SERVERS supported
  603. See https://imapsync.lamiral.info/S/imapservers.shtml
  604. =head1 HUGE MIGRATION
  605. If you have many mailboxes to migrate think about a little
  606. shell program. Write a file called file.txt (for example)
  607. containing users and passwords.
  608. The separator used in this example is ';'
  609. The file.txt file contains:
  610. user001_1;password001_1;user001_2;password001_2
  611. user002_1;password002_1;user002_2;password002_2
  612. user003_1;password003_1;user003_2;password003_2
  613. user004_1;password004_1;user004_2;password004_2
  614. user005_1;password005_1;user005_2;password005_2
  615. ...
  616. On Unix the shell program can be:
  617. { while IFS=';' read u1 p1 u2 p2; do
  618. imapsync --host1 imap.side1.org --user1 "$u1" --password1 "$p1" \
  619. --host2 imap.side2.org --user2 "$u2" --password2 "$p2" ...
  620. done ; } < file.txt
  621. On Windows the batch program can be:
  622. FOR /F "tokens=1,2,3,4 delims=; eol=#" %%G IN (file.txt) DO imapsync ^
  623. --host1 imap.side1.org --user1 %%G --password1 %%H ^
  624. --host2 imap.side2.org --user2 %%I --password2 %%J ...
  625. The ... have to be replaced by nothing or any imapsync option.
  626. Welcome in shell or batch programming !
  627. You will find already written scripts at
  628. https://imapsync.lamiral.info/examples/
  629. =head1 INSTALL
  630. Imapsync works under any Unix with Perl.
  631. Imapsync works under most Windows (2000, XP, Vista, Seven, Eight, Ten
  632. and all Server releases 2000, 2003, 2008 and R2, 2012 and R2, 2016)
  633. as a standalone binary software called imapsync.exe,
  634. usually launched from a batch file in order to avoid always typing
  635. the options. There is also a 32bit binary called imapsync_32bit.exe
  636. Imapsync works under OS X as a standalone binary
  637. software called imapsync_bin_Darwin
  638. Purchase latest imapsync at
  639. https://imapsync.lamiral.info/
  640. You'll receive a link to a compressed tarball called imapsync-x.xx.tgz
  641. where x.xx is the version number. Untar the tarball where
  642. you want (on Unix):
  643. tar xzvf imapsync-x.xx.tgz
  644. Go into the directory imapsync-x.xx and read the INSTALL file.
  645. As mentioned at https://imapsync.lamiral.info/#install
  646. the INSTALL file can also be found at
  647. https://imapsync.lamiral.info/INSTALL.d/INSTALL.ANY.txt
  648. It is now split in several files for each system
  649. https://imapsync.lamiral.info/INSTALL.d/
  650. =head1 CONFIGURATION
  651. There is no specific configuration file for imapsync,
  652. everything is specified by the command line parameters
  653. and the default behavior.
  654. =head1 HACKING
  655. Feel free to hack imapsync as the NOLIMIT license permits it.
  656. =head1 SIMILAR SOFTWARE
  657. See also https://imapsync.lamiral.info/S/external.shtml
  658. for a better up to date list.
  659. List verified on Friday July 1, 2021.
  660. imapsync: https://github.com/imapsync/imapsync (this is an imapsync copy, sometimes delayed, with --noreleasecheck by default since release 1.592, 2014/05/22)
  661. imap_tools: https://web.archive.org/web/20161228145952/http://www.athensfbc.com/imap_tools/. The imap_tools code is now at https://github.com/andrewnimmo/rick-sanders-imap-tools
  662. imaputils: https://github.com/mtsatsenko/imaputils (very old imap_tools fork)
  663. Doveadm-Sync: https://wiki2.dovecot.org/Tools/Doveadm/Sync ( Dovecot sync tool )
  664. davmail: http://davmail.sourceforge.net/
  665. offlineimap: http://offlineimap.org/
  666. fdm: https://github.com/nicm/fdm
  667. mbsync: http://isync.sourceforge.net/
  668. mailsync: http://mailsync.sourceforge.net/
  669. mailutil: https://www.washington.edu/imap/ part of the UW IMAP toolkit. (well, seems abandoned now)
  670. imaprepl: https://bl0rg.net/software/ http://freecode.com/projects/imap-repl/
  671. imapcopy (Pascal): http://www.ardiehl.de/imapcopy/
  672. imapcopy (Java): https://code.google.com/archive/p/imapcopy/
  673. imapsize: http://www.broobles.com/imapsize/
  674. migrationtool: http://sourceforge.net/projects/migrationtool/
  675. imapmigrate: http://sourceforge.net/projects/cyrus-utils/
  676. larch: https://github.com/rgrove/larch (derived from wonko_imapsync, good at Gmail)
  677. wonko_imapsync: http://wonko.com/article/554 (superseded by larch)
  678. pop2imap: http://www.linux-france.org/prj/pop2imap/ (I wrote that too)
  679. exchange-away: http://exchange-away.sourceforge.net/
  680. SyncBackPro: http://www.2brightsparks.com/syncback/sbpro.html
  681. ImapSyncClient: https://github.com/ridaamirini/ImapSyncClient
  682. MailStore: https://www.mailstore.com/en/products/mailstore-home/
  683. mnIMAPSync: https://github.com/manusa/mnIMAPSync
  684. imap-upload: http://imap-upload.sourceforge.net/ (A tool for uploading a local mbox file to IMAP4 server)
  685. imapbackup: https://github.com/rcarmo/imapbackup (A Python script for incremental backups of IMAP mailboxes)
  686. BitRecover email-backup 99 USD, 299 USD https://www.bitrecover.com/email-backup/.
  687. ImportExportTools: https://addons.thunderbird.net/en-us/thunderbird/addon/importexporttools/ ImportExportTools for Mozilla Thunderbird by Paolo Kaosmos. ImportExportTools does not do IMAP.
  688. rximapmail: https://sourceforge.net/projects/rximapmail/
  689. CodeTwo: https://www.codetwo.com/ but CodeTwo does imap source to Office365 only.
  690. =head1 HISTORY
  691. I initially wrote imapsync in July 2001 because an enterprise,
  692. called BaSystemes, paid me to install a new imap server
  693. without losing huge old mailboxes located in a far
  694. away remote imap server, accessible by an
  695. often broken low-bandwidth ISDN link.
  696. I had to verify every mailbox was well transferred, all folders, all messages,
  697. without wasting bandwidth or creating duplicates upon resyncs. The imapsync
  698. design was made with the beautiful rsync command in mind.
  699. Imapsync started its life as a patch of the copy_folder.pl
  700. script. The script copy_folder.pl comes from the Mail-IMAPClient-2.1.3 perl
  701. module tarball source (more precisely in the examples/ directory of the
  702. Mail-IMAPClient tarball).
  703. So many changes happened since then that I wonder
  704. if it remains any lines of the original
  705. copy_folder.pl in imapsync source code.
  706. =cut
  707. # use pragmas
  708. #
  709. use strict ;
  710. use warnings ;
  711. use Carp ;
  712. use Cwd ;
  713. use Compress::Zlib ;
  714. use Data::Dumper ;
  715. use Digest::HMAC_SHA1 qw( hmac_sha1 hmac_sha1_hex ) ;
  716. use Digest::MD5 qw( md5 md5_hex md5_base64 ) ;
  717. use Encode ;
  718. use Encode::IMAPUTF7 ;
  719. use English qw( -no_match_vars ) ;
  720. use Errno qw(EAGAIN EPIPE ECONNRESET) ;
  721. use Fcntl ;
  722. use File::Basename ;
  723. use File::Copy::Recursive ;
  724. use File::Glob qw( :glob ) ;
  725. use File::Path qw( mkpath rmtree ) ;
  726. use File::Spec ;
  727. use File::stat ;
  728. use Getopt::Long ( ) ;
  729. use IO::File ;
  730. use IO::Socket qw( :crlf SOL_SOCKET SO_KEEPALIVE ) ;
  731. use IO::Socket::INET6 ;
  732. use IO::Socket::SSL ;
  733. use IO::Tee ;
  734. use IPC::Open3 'open3' ;
  735. #use locale ;
  736. use Mail::IMAPClient 3.30 ;
  737. use MIME::Base64 ;
  738. use Pod::Usage qw(pod2usage) ;
  739. use POSIX qw( uname SIGALRM :sys_wait_h ) ;
  740. use Sys::Hostname ;
  741. use Term::ReadKey ;
  742. use Test::More ;
  743. use Time::HiRes qw( time sleep ) ;
  744. use Time::Local ;
  745. use Unicode::String ;
  746. use Readonly ;
  747. use Sys::MemInfo ;
  748. use Regexp::Common ;
  749. use Text::ParseWords ; # for quotewords()
  750. use File::Tail ;
  751. local $OUTPUT_AUTOFLUSH = 1 ;
  752. # constants
  753. # Let us do like sysexits.h
  754. # /usr/include/sysexits.h
  755. # and https://www.tldp.org/LDP/abs/html/exitcodes.html
  756. # Should avoid 2 126 127 128..128+64=192 255
  757. # Should use 0 1 3..125 193..254
  758. Readonly my $EX_OK => 0 ; #/* successful termination */
  759. Readonly my $EX_USAGE => 64 ; #/* command line usage error */
  760. #Readonly my $EX_DATAERR => 65 ; #/* data format error */
  761. Readonly my $EX_NOINPUT => 66 ; #/* cannot open input */
  762. #Readonly my $EX_NOUSER => 67 ; #/* addressee unknown */
  763. #Readonly my $EX_NOHOST => 68 ; #/* host name unknown */
  764. Readonly my $EX_UNAVAILABLE => 69 ; #/* service unavailable */
  765. Readonly my $EX_SOFTWARE => 70 ; #/* internal software error */
  766. #Readonly my $EX_OSERR => 71 ; #/* system error (e.g., can't fork) */
  767. #Readonly my $EX_OSFILE => 72 ; #/* critical OS file missing */
  768. #Readonly my $EX_CANTCREAT => 73 ; #/* can't create (user) output file */
  769. #Readonly my $EX_IOERR => 74 ; #/* input/output error */
  770. #Readonly my $EX_TEMPFAIL => 75 ; #/* temp failure; user is invited to retry */
  771. #Readonly my $EX_PROTOCOL => 76 ; #/* remote error in protocol */
  772. #Readonly my $EX_NOPERM => 77 ; #/* permission denied */
  773. #Readonly my $EX_CONFIG => 78 ; #/* configuration error */
  774. # Mine
  775. Readonly my $EXIT_CATCH_ALL => 1 ; # Any other error
  776. Readonly my $EXIT_BY_SIGNAL => 6 ; # Should be 128+n where n is the sig_num
  777. Readonly my $EXIT_BY_FILE => 7 ;
  778. Readonly my $EXIT_PID_FILE_ERROR => 8 ;
  779. Readonly my $EXIT_CONNECTION_FAILURE => 10 ;
  780. Readonly my $EXIT_TLS_FAILURE => 12 ;
  781. Readonly my $EXIT_AUTHENTICATION_FAILURE => 16 ;
  782. Readonly my $EXIT_SUBFOLDER1_NO_EXISTS => 21 ;
  783. Readonly my $EXIT_WITH_ERRORS => 111 ;
  784. Readonly my $EXIT_WITH_ERRORS_MAX => 112 ;
  785. Readonly my $EXIT_OVERQUOTA => 113 ;
  786. Readonly my $EXIT_ERR_APPEND => 114 ;
  787. Readonly my $EXIT_ERR_FETCH => 115 ;
  788. Readonly my $EXIT_ERR_CREATE => 116 ;
  789. Readonly my $EXIT_ERR_SELECT => 117 ;
  790. Readonly my $EXIT_TRANSFER_EXCEEDED => 118 ;
  791. Readonly my $EXIT_ERR_APPEND_VIRUS => 119 ;
  792. Readonly my $EXIT_ERR_FLAGS => 120 ;
  793. Readonly my $EXIT_TESTS_FAILED => 254 ; # Like Test::More API
  794. Readonly my $EXIT_CONNECTION_FAILURE_HOST1 => 101 ;
  795. Readonly my $EXIT_CONNECTION_FAILURE_HOST2 => 102 ;
  796. Readonly my $EXIT_AUTHENTICATION_FAILURE_USER1 => 161 ;
  797. Readonly my $EXIT_AUTHENTICATION_FAILURE_USER2 => 162 ;
  798. Readonly my %EXIT_TXT => (
  799. $EX_OK => 'EX_OK: successful termination',
  800. $EX_USAGE => 'EX_USAGE: command line usage error',
  801. $EX_NOINPUT => 'EX_NOINPUT: cannot open input',
  802. $EX_UNAVAILABLE => 'EX_UNAVAILABLE: service unavailable',
  803. $EX_SOFTWARE => 'EX_SOFTWARE: internal software error',
  804. $EXIT_CATCH_ALL => 'EXIT_CATCH_ALL',
  805. $EXIT_BY_SIGNAL => 'EXIT_BY_SIGNAL',
  806. $EXIT_BY_FILE => 'EXIT_BY_FILE',
  807. $EXIT_PID_FILE_ERROR => 'EXIT_PID_FILE_ERROR' ,
  808. $EXIT_CONNECTION_FAILURE => 'EXIT_CONNECTION_FAILURE',
  809. $EXIT_TLS_FAILURE => 'EXIT_TLS_FAILURE',
  810. $EXIT_AUTHENTICATION_FAILURE => 'EXIT_AUTHENTICATION_FAILURE',
  811. $EXIT_SUBFOLDER1_NO_EXISTS => 'EXIT_SUBFOLDER1_NO_EXISTS',
  812. $EXIT_WITH_ERRORS => 'EXIT_WITH_ERRORS',
  813. $EXIT_WITH_ERRORS_MAX => 'EXIT_WITH_ERRORS_MAX',
  814. $EXIT_OVERQUOTA => 'EXIT_OVERQUOTA',
  815. $EXIT_ERR_APPEND => 'EXIT_ERR_APPEND',
  816. $EXIT_ERR_APPEND_VIRUS => 'EXIT_ERR_APPEND_VIRUS',
  817. $EXIT_ERR_FETCH => 'EXIT_ERR_FETCH',
  818. $EXIT_ERR_FLAGS => 'EXIT_ERR_FLAGS',
  819. $EXIT_ERR_CREATE => 'EXIT_ERR_CREATE',
  820. $EXIT_ERR_SELECT => 'EXIT_ERR_SELECT',
  821. $EXIT_TESTS_FAILED => 'EXIT_TESTS_FAILED',
  822. $EXIT_TRANSFER_EXCEEDED => 'EXIT_TRANSFER_EXCEEDED',
  823. $EXIT_CONNECTION_FAILURE_HOST1 => 'EXIT_CONNECTION_FAILURE_HOST1',
  824. $EXIT_CONNECTION_FAILURE_HOST2 => 'EXIT_CONNECTION_FAILURE_HOST2',
  825. $EXIT_AUTHENTICATION_FAILURE_USER1 => 'EXIT_AUTHENTICATION_FAILURE_USER1',
  826. $EXIT_AUTHENTICATION_FAILURE_USER2 => 'EXIT_AUTHENTICATION_FAILURE_USER2',
  827. ) ;
  828. Readonly my %EXIT_VALUE_OF_ERR_TYPE => (
  829. ERR_APPEND_SIZE => $EXIT_ERR_APPEND,
  830. ERR_OVERQUOTA => $EXIT_OVERQUOTA,
  831. ERR_APPEND => $EXIT_ERR_APPEND,
  832. ERR_APPEND_VIRUS => $EXIT_ERR_APPEND_VIRUS,
  833. ERR_CREATE => $EXIT_ERR_CREATE,
  834. ERR_SELECT => $EXIT_ERR_SELECT,
  835. ERR_Host1_FETCH => $EXIT_ERR_FETCH,
  836. ERR_FLAGS => $EXIT_ERR_FLAGS,
  837. ERR_UNCLASSIFIED => $EXIT_WITH_ERRORS,
  838. ERR_NOTHING_REPORTED => $EXIT_WITH_ERRORS,
  839. ERR_TRANSFER_EXCEEDED => $EXIT_TRANSFER_EXCEEDED,
  840. ERR_CONNECTION_FAILURE_HOST1 => $EXIT_CONNECTION_FAILURE_HOST1,
  841. ERR_CONNECTION_FAILURE_HOST2 => $EXIT_CONNECTION_FAILURE_HOST2,
  842. ERR_AUTHENTICATION_FAILURE_USER1 => $EXIT_AUTHENTICATION_FAILURE_USER1,
  843. ERR_AUTHENTICATION_FAILURE_USER2 => $EXIT_AUTHENTICATION_FAILURE_USER2,
  844. ERR_EXIT_TLS_FAILURE => $EXIT_TLS_FAILURE,
  845. ) ;
  846. Readonly my %COMMENT_OF_ERR_TYPE => (
  847. ERR_APPEND_SIZE => \&comment_err_append_size,
  848. ERR_OVERQUOTA => \&comment_err_overquota,
  849. ERR_APPEND => \&comment_err_blank,
  850. ERR_APPEND_VIRUS => \&comment_err_blank,
  851. ERR_CREATE => \&comment_err_blank,
  852. ERR_SELECT => \&comment_err_blank,
  853. ERR_Host1_FETCH => \&comment_err_blank,
  854. ERR_FLAGS => \&comment_err_flags,
  855. ERR_UNCLASSIFIED => \&comment_err_blank,
  856. ERR_NOTHING_REPORTED => \&comment_err_blank,
  857. ERR_TRANSFER_EXCEEDED => \&comment_err_transfer_exceeded,
  858. ERR_CONNECTION_FAILURE_HOST1 => \&comment_err_connection_failure_host1,
  859. ERR_CONNECTION_FAILURE_HOST2 => \&comment_err_connection_failure_host2,
  860. ERR_AUTHENTICATION_FAILURE_USER1 => \&comment_err_authentication_failure_host1,
  861. ERR_AUTHENTICATION_FAILURE_USER2 => \&comment_err_authentication_failure_host2,
  862. ERR_EXIT_TLS_FAILURE => \&comment_err_blank,
  863. ) ;
  864. sub comment_err_blank
  865. {
  866. return '' ;
  867. }
  868. sub comment_err_append_size
  869. {
  870. my $mysync = shift @ARG ;
  871. my $comment = "The destination server refuses too big messages. Use --truncmess option. Read https://imapsync.lamiral.info/FAQ.d/FAQ.Messages_Too_Big.txt" ;
  872. return $comment ;
  873. }
  874. sub comment_err_authentication_failure_host1
  875. {
  876. my $mysync = shift @ARG ;
  877. my $comment = "Check the credentials for $mysync->{ user1 }." ;
  878. return $comment ;
  879. }
  880. sub comment_err_authentication_failure_host2
  881. {
  882. my $mysync = shift @ARG ;
  883. my $comment = "Check the credentials for $mysync->{ user2 }." ;
  884. return $comment ;
  885. }
  886. sub comment_err_connection_failure_host1
  887. {
  888. my $mysync = shift @ARG ;
  889. my $comment = "Check that host1 $mysync->{ host1 } on port $mysync->{ port1 } is the right IMAP server to be contacted for your mailbox." ;
  890. return $comment ;
  891. }
  892. sub comment_err_connection_failure_host2
  893. {
  894. my $mysync = shift @ARG ;
  895. my $comment = "Check that host1 $mysync->{ host2 } on port $mysync->{ port2 } is the right IMAP server to be contacted for your mailbox." ;
  896. return $comment ;
  897. }
  898. sub comment_err_overquota
  899. {
  900. my $mysync = shift @ARG ;
  901. my $comment = 'The destination mailbox is 100% full, get free space on it and then resume the sync.' ;
  902. return $comment ;
  903. }
  904. sub comment_err_transfer_exceeded
  905. {
  906. my $mysync = shift @ARG ;
  907. my $size_limit_human = bytes_display_string_dec( $mysync->{ exitwhenover } ) ;
  908. my $comment = "The maximum transfer size for a single sync is reached ( over $size_limit_human ). Relaunch the sync to sync more." ;
  909. return $comment ;
  910. }
  911. sub comment_err_flags
  912. {
  913. my $mysync = shift @ARG ;
  914. my $comment = 'Many STORE errors with FLAGS. Retry with the option --noresyncflags' ;
  915. return $comment ;
  916. }
  917. Readonly my $DEFAULT_LOGDIR => 'LOG_imapsync' ;
  918. Readonly my $ERRORS_MAX => 50 ; # exit after 50 errors.
  919. Readonly my $ERRORS_MAX_CGI => 20 ; # exit after 20 errors in CGI context.
  920. Readonly my $INTERVAL_TO_EXIT => 2 ; # interval max to exit instead of reconnect
  921. Readonly my $SPLIT => 100 ; # By default, 100 at a time, not more.
  922. Readonly my $SPLIT_FACTOR => 10 ; # init_imap() calls Maxcommandlength( $SPLIT_FACTOR * $split )
  923. # which means default Maxcommandlength is 10*100 = 1000 characters ;
  924. Readonly my $IMAP_PORT => 143 ; # Well know port for IMAP
  925. Readonly my $IMAP_SSL_PORT => 993 ; # Well know port for IMAP over SSL
  926. Readonly my $LAST => -1 ;
  927. Readonly my $MINUS_ONE => -1 ;
  928. Readonly my $MINUS_TWO => -2 ;
  929. Readonly my $RELEASE_NUMBER_EXAMPLE_1 => '1.351' ;
  930. Readonly my $RELEASE_NUMBER_EXAMPLE_2 => 42.4242 ;
  931. Readonly my $TCP_PING_TIMEOUT => 5 ;
  932. Readonly my $DEFAULT_TIMEOUT => 120 ;
  933. Readonly my $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND => 3 ;
  934. Readonly my $DEFAULT_BUFFER_SIZE => 4096 ;
  935. Readonly my $MAX_SLEEP => 2 ; # 2 seconds max for limiting too long sleeps from --maxbytespersecond and --maxmessagespersecond
  936. Readonly my $DEFAULT_EXPIRATION_TIME_OAUTH2_PK12 => 3600 ;
  937. Readonly my $PERMISSION_FILTER => 7777 ;
  938. Readonly my $KIBI => 1024 ;
  939. Readonly my $NUMBER_10 => 10 ;
  940. Readonly my $NUMBER_42 => 42 ;
  941. Readonly my $NUMBER_100 => 100 ;
  942. Readonly my $NUMBER_200 => 200 ;
  943. Readonly my $NUMBER_300 => 300 ;
  944. Readonly my $NUMBER_123456 => 123_456 ;
  945. Readonly my $NUMBER_654321 => 654_321 ;
  946. Readonly my $NUMBER_20_000 => 20_000 ;
  947. Readonly my $QUOTA_PERCENT_LIMIT => 90 ;
  948. Readonly my $NUMBER_104_857_600 => 104_857_600 ;
  949. Readonly my $SIZE_MAX_STR => 64 ;
  950. Readonly my $NB_SECONDS_IN_A_DAY => 86_400 ;
  951. Readonly my $STD_CHAR_PER_LINE => 80 ;
  952. Readonly my $TRUE => 1 ;
  953. Readonly my $FALSE => 0 ;
  954. Readonly my $LAST_RESSORT_SEPARATOR => q{/} ;
  955. Readonly my $CGI_TMPDIR_TOP => '/var/tmp/imapsync_cgi' ;
  956. Readonly my $CGI_HASHFILE => '/var/tmp/imapsync_hash' ;
  957. Readonly my $UMASK_PARANO => '0077' ;
  958. Readonly my $STR_use_releasecheck => q{Check if a new imapsync release is available by adding --releasecheck} ;
  959. Readonly my $GMAIL_MAXSIZE => 35_651_584 ;
  960. Readonly my $FORCE => 1 ;
  961. # if ( 'MSWin32' eq $OSNAME )
  962. # if ( 'darwin' eq $OSNAME )
  963. # if ( 'linux' eq $OSNAME )
  964. # global variables
  965. # Currently working to finish with only $sync, $acc1, $acc2
  966. # Not finished yet...
  967. my(
  968. $sync, $acc1, $acc2,
  969. $debugflags,
  970. $debuglist, $debugdev, $debugmaxlinelength, $debugcgi,
  971. @include, @exclude, @folderrec,
  972. @folderfirst, @folderlast,
  973. @h1_folders_all, %h1_folders_all,
  974. @h2_folders_all, %h2_folders_all,
  975. @h2_folders_from_1_wanted, %h2_folders_from_1_all,
  976. %requested_folder,
  977. $h1_folders_wanted_nb, $h1_folders_wanted_ct,
  978. @h2_folders_not_in_1,
  979. %h1_subscribed_folder, %h2_subscribed_folder,
  980. %h2_folders_from_1_wanted,
  981. %h2_folders_from_1_several,
  982. $prefix1, $prefix2,
  983. @regexmess, @skipmess, @pipemess, $pipemesscheck,
  984. $syncflagsaftercopy,
  985. $syncinternaldates,
  986. $idatefromheader,
  987. $minsize, $maxage, $minage,
  988. $search,
  989. @useheader, %useheader,
  990. $skipsize, $allowsizemismatch, $buffersize,
  991. $authmd5, $authmd51, $authmd52,
  992. $subscribed, $subscribe, $subscribeall,
  993. $help,
  994. $nb_msg_skipped_dry_mode,
  995. $h2_nb_msg_noheader,
  996. $h1_bytes_processed,
  997. $h1_nb_msg_end, $h1_bytes_end,
  998. $h2_nb_msg_end, $h2_bytes_end,
  999. $timestart_int,
  1000. $uid1, $uid2,
  1001. $split1, $split2,
  1002. $modulesversion,
  1003. $delete2folders, $delete2foldersonly, $delete2foldersbutnot,
  1004. $usecache, $debugcache, $cacheaftercopy,
  1005. $wholeheaderifneeded, %h1_msgs_copy_by_uid, $useuid, $h2_uidguess,
  1006. $checkmessageexists,
  1007. $messageidnodomain,
  1008. $fixInboxINBOX,
  1009. $maxlinelength, $maxlinelengthcmd,
  1010. $minmaxlinelength,
  1011. $fixcolonbug,
  1012. $create_folder_old,
  1013. $skipcrossduplicates, $debugcrossduplicates,
  1014. $disarmreadreceipts,
  1015. $mixfolders,
  1016. $fetch_hash_set,
  1017. $cgidir,
  1018. %month_abrev,
  1019. $SSL_VERIFY_POLICY,
  1020. ) ;
  1021. single_sync( $sync, $acc1, $acc2 );
  1022. sub single_sync
  1023. {
  1024. # main program
  1025. # global variables initialization
  1026. # I'm currently removing all global variables except $sync $acc1 $acc2
  1027. # passing each of them under
  1028. # $sync->{variable_name}
  1029. # or $acc1->{variable_name}
  1030. # or $acc1->{variable_name}
  1031. #
  1032. $acc1 = {} ;
  1033. $acc2 = {} ;
  1034. $sync->{ acc1 } = $acc1 ;
  1035. $sync->{ acc2 } = $acc2 ;
  1036. $acc1->{ Side } = 'Host1' ;
  1037. $acc2->{ Side } = 'Host2' ;
  1038. $acc1->{ N } = '1' ;
  1039. $acc2->{ N } = '2' ;
  1040. $sync->{timestart} = time ; # Is a float because of use Time::HiRres
  1041. $sync->{rcs} = q{$Id: imapsync,v 2.178 2022/01/12 21:28:37 gilles Exp gilles $} ;
  1042. $sync->{ memory_consumption_at_start } = memory_consumption( ) || 0 ;
  1043. my @loadavg = loadavg( ) ;
  1044. $sync->{ cpu_number } = cpu_number( ) ;
  1045. $sync->{ loaddelay } = load_and_delay( $sync->{ cpu_number }, @loadavg ) ;
  1046. $sync->{ loaddelay } = 0 ;
  1047. $sync->{ loadavg } = join( q{ }, $loadavg[ 0 ] )
  1048. . " on $sync->{cpu_number} cores and "
  1049. . ram_memory_info( ) ;
  1050. $sync->{ total_bytes_transferred } = 0 ;
  1051. $sync->{ total_bytes_skipped } = 0 ;
  1052. $sync->{ nb_msg_transferred } = 0 ;
  1053. $sync->{ nb_msg_skipped } = $nb_msg_skipped_dry_mode = 0 ;
  1054. $sync->{ acc1 }->{ nb_msg_deleted } = 0 ;
  1055. $sync->{ acc2 }->{ nb_msg_deleted } = 0 ;
  1056. $sync->{ acc1 }->{ nb_msg_duplicate } = 0 ;
  1057. $sync->{ acc2 }->{ nb_msg_duplicate } = 0 ;
  1058. $sync->{ h1_nb_msg_noheader } = 0 ;
  1059. $h2_nb_msg_noheader = 0 ;
  1060. $sync->{ h1_nb_msg_start } = 0 ;
  1061. $sync->{ h1_bytes_start } = 0 ;
  1062. $sync->{ h2_nb_msg_start } = 0 ;
  1063. $sync->{ h2_bytes_start } = 0 ;
  1064. $sync->{ h1_nb_msg_processed } = $h1_bytes_processed = 0 ;
  1065. $sync->{ h2_nb_msg_crossdup } = 0 ;
  1066. #$h1_nb_msg_end = $h1_bytes_end = 0 ;
  1067. #$h2_nb_msg_end = $h2_bytes_end = 0 ;
  1068. $sync->{ nb_errors } = 0;
  1069. $sync->{ biggest_message_transferred } = 0;
  1070. %month_abrev = (
  1071. Jan => '00',
  1072. Feb => '01',
  1073. Mar => '02',
  1074. Apr => '03',
  1075. May => '04',
  1076. Jun => '05',
  1077. Jul => '06',
  1078. Aug => '07',
  1079. Sep => '08',
  1080. Oct => '09',
  1081. Nov => '10',
  1082. Dec => '11',
  1083. );
  1084. # Just create a CGI object if under cgi context only.
  1085. # Needed for the get_options() call
  1086. cgibegin( $sync ) ;
  1087. # In cgi context, printing must start by the header so we delay other prints by using output() storage
  1088. my $options_good = get_options( $sync, @ARGV ) ;
  1089. # Is it the first myprint?
  1090. cgibuildheader( $sync ) ;
  1091. docker_context( $sync ) ;
  1092. print_output_if_needed( $sync ) ;
  1093. output_reset_with( $sync ) ;
  1094. # don't go on if options are not all known.
  1095. if ( ! defined $options_good ) { exit $EX_USAGE ; }
  1096. # If you want releasecheck not to be done by default (like the github maintainer),
  1097. # then just uncomment the first "$sync->{releasecheck} =" line, the line ending with "0 ;",
  1098. # the second line (ending with "1 ;") can then stay active or be commented,
  1099. # the result will be the same: no releasecheck by default (because 0 is then the defined value).
  1100. #$sync->{releasecheck} = defined $sync->{releasecheck} ? $sync->{releasecheck} : 0 ;
  1101. $sync->{releasecheck} = defined $sync->{releasecheck} ? $sync->{releasecheck} : 1 ;
  1102. # just the version
  1103. if ( $sync->{ version } ) {
  1104. myprint( imapsync_version( $sync ), "\n" ) ;
  1105. return 0 ;
  1106. }
  1107. #$sync->{debugenv} = 1 ;
  1108. $sync->{debugenv} and printenv( $sync ) ; # if option --debugenv
  1109. load_modules( ) ;
  1110. # after_get_options call usage and exit if --help or options were not well got
  1111. after_get_options( $sync, $options_good ) ;
  1112. #local $ENV{TZ} = 'GMT' if ( under_cgi_context( $sync ) and 'MSWin32' ne $OSNAME ) ;
  1113. #output( $sync, localtime(time) . " " . gmtime(time) . "\n" ) ;
  1114. # Under CGI environment, fix caveat emptor potential issues
  1115. cgisetcontext( $sync ) ;
  1116. get_options_extra( $sync ) ;
  1117. # --gmail --gmail --exchange --office etc.
  1118. easyany( $sync ) ;
  1119. $sync->{ sanitize } = defined $sync->{ sanitize } ? $sync->{ sanitize } : 1 ;
  1120. sanitize( $sync ) ;
  1121. $sync->{ tmpdir } ||= File::Spec->tmpdir( ) ;
  1122. # Unit tests
  1123. my $unittestssuite = unittestssuite( $sync ) ;
  1124. if ( condition_to_leave_after_tests( $sync ) )
  1125. {
  1126. return $unittestssuite ;
  1127. }
  1128. # init live varaiables
  1129. if ( $sync->{ testslive } )
  1130. {
  1131. testslive_init( $sync ) ;
  1132. }
  1133. if ( $sync->{ testslive6 } )
  1134. {
  1135. testslive6_init( $sync ) ;
  1136. }
  1137. define_pidfile( $sync ) ;
  1138. if ( $sync->{ abortbyfile } ) { $sync->{ abort } = 1 ; }
  1139. install_signals( $sync ) ;
  1140. $sync->{ log } = defined $sync->{ log } ? $sync->{ log } : 1 ;
  1141. $sync->{ errorsdump } = defined $sync->{ errorsdump } ? $sync->{ errorsdump } : 1 ;
  1142. $sync->{ errorsmax } = defined $sync->{ errorsmax } ? $sync->{ errorsmax } : $ERRORS_MAX ;
  1143. # log and output
  1144. binmode STDOUT, ":encoding(UTF-8)" ;
  1145. if ( $sync->{ log } ) {
  1146. setlogfile( $sync ) ;
  1147. teelaunch( $sync ) ;
  1148. # now $sync->{tee} is a filehandle to STDOUT and the logfile
  1149. }
  1150. #binmode STDERR, ":encoding(UTF-8)" ;
  1151. # STDERR goes to the same place: LOG and STDOUT (if logging is on)
  1152. # Useful only for --debugssl
  1153. $sync->{tee} and local *STDERR = *${$sync->{tee}}{IO} ;
  1154. $timestart_int = int( $sync->{timestart} ) ;
  1155. $sync->{timebefore} = $sync->{timestart} ;
  1156. $sync->{ timestart_str } = localtimez( $sync->{timestart} ) ;
  1157. # The prints in the log starts here
  1158. myprint( localhost_info( $sync ), "\n" ) ;
  1159. myprint( "Transfer started at $sync->{ timestart_str }\n" ) ;
  1160. myprint( "PID is $PROCESS_ID my PPID is ", mygetppid( ), "\n" ) ;
  1161. announcelogfile( $sync ) ;
  1162. myprint( "Load is " . ( join( q{ }, loadavg( ) ) || 'unknown' ), " on $sync->{cpu_number} cores\n" ) ;
  1163. #myprintf( "Memory consumption so far: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI ) ;
  1164. myprint( 'Current directory is ' . getcwd( ) . "\n" ) ;
  1165. myprint( 'Real user id is ' . getpwuid_any_os( $REAL_USER_ID ) . " (uid $REAL_USER_ID)\n" ) ;
  1166. myprint( 'Effective user id is ' . getpwuid_any_os( $EFFECTIVE_USER_ID ). " (euid $EFFECTIVE_USER_ID)\n" ) ;
  1167. $modulesversion = defined $modulesversion ? $modulesversion : 1 ;
  1168. $sync->{ warn_release } = ( $sync->{ releasecheck } ) ? check_last_release( ) : $STR_use_releasecheck ;
  1169. $wholeheaderifneeded = defined $wholeheaderifneeded ? $wholeheaderifneeded : 1;
  1170. # Activate --usecache if --useuid is set and there is no --nousecache
  1171. $usecache = 1 if ( $useuid and ( ! defined $usecache ) ) ;
  1172. $cacheaftercopy = 1 if ( $usecache and ( ! defined $cacheaftercopy ) ) ;
  1173. $sync->{ checkfoldersexist } = defined $sync->{ checkfoldersexist } ? $sync->{ checkfoldersexist } : 1 ;
  1174. $checkmessageexists = defined $checkmessageexists ? $checkmessageexists : 0 ;
  1175. $sync->{ expungeaftereach } = defined $sync->{ expungeaftereach } ? $sync->{ expungeaftereach } : 1 ;
  1176. # abletosearch is on by default
  1177. $sync->{abletosearch} = defined $sync->{abletosearch} ? $sync->{abletosearch} : 1 ;
  1178. $sync->{abletosearch1} = defined $sync->{abletosearch1} ? $sync->{abletosearch1} : $sync->{abletosearch} ;
  1179. $sync->{abletosearch2} = defined $sync->{abletosearch2} ? $sync->{abletosearch2} : $sync->{abletosearch} ;
  1180. $checkmessageexists = 0 if ( not $sync->{abletosearch1} ) ;
  1181. $sync->{ trylogin } = defined $sync->{ trylogin } ? $sync->{ trylogin } : 1 ;
  1182. $sync->{showpasswords} = defined $sync->{showpasswords} ? $sync->{showpasswords} : 0 ;
  1183. $sync->{ fixslash2 } = defined $sync->{ fixslash2 } ? $sync->{ fixslash2 } : 1 ;
  1184. $fixInboxINBOX = defined $fixInboxINBOX ? $fixInboxINBOX : 1 ;
  1185. $create_folder_old = defined $create_folder_old ? $create_folder_old : 0 ;
  1186. $mixfolders = defined $mixfolders ? $mixfolders : 1 ;
  1187. $sync->{automap} = defined $sync->{automap} ? $sync->{automap} : 0 ;
  1188. $sync->{ delete2duplicates } = 1 if ( $sync->{ delete2 } and ( ! defined $sync->{ delete2duplicates } ) ) ;
  1189. $sync->{maxmessagespersecond} = defined $sync->{maxmessagespersecond} ? $sync->{maxmessagespersecond} : 0 ;
  1190. $sync->{maxbytespersecond} = defined $sync->{maxbytespersecond} ? $sync->{maxbytespersecond} : 0 ;
  1191. $sync->{sslcheck} = defined $sync->{sslcheck} ? $sync->{sslcheck} : 1 ;
  1192. myprint( banner_imapsync( $sync, @ARGV ) ) ;
  1193. myprint( "Temp directory is $sync->{ tmpdir } ( to change it use --tmpdir dirpath )\n" ) ;
  1194. myprint( output( $sync ) ) ;
  1195. output_reset_with( $sync ) ;
  1196. do_valid_directory( $sync->{ tmpdir } ) || croak "Error creating tmpdir $sync->{ tmpdir } : $OS_ERROR" ;
  1197. remove_pidfile_not_running( $sync->{ pidfile } ) ;
  1198. # if another imapsync is running then tail -f its logfile and exit
  1199. # useful in cgi context
  1200. if ( $sync->{ tail } and tail( $sync ) )
  1201. {
  1202. exit_clean( $sync, $EX_OK, "Tail -f finished. Now finishing myself processus $PROCESS_ID\n" ) ;
  1203. exit $EX_OK ;
  1204. }
  1205. if ( ! write_pidfile( $sync ) ) {
  1206. myprint( "Exiting with return value $EXIT_PID_FILE_ERROR ($EXIT_TXT{$EXIT_PID_FILE_ERROR}) $sync->{nb_errors}/$sync->{errorsmax} nb_errors/max_errors PID $PROCESS_ID\n" ) ;
  1207. exit $EXIT_PID_FILE_ERROR ;
  1208. }
  1209. # New place for abort
  1210. # abort before simulong in order to be able to abort a simulong sync
  1211. if ( $sync->{ abort } )
  1212. {
  1213. abort( $sync ) ;
  1214. # well, the abort job is done, because even when not succeeded
  1215. # in aborting another run, this run has to end without doing any
  1216. # thing else
  1217. exit $EX_OK ;
  1218. }
  1219. # simulong is just a loop printing some lines for xx seconds with option "--simulong xx".
  1220. simulong( $sync ) ;
  1221. # New place for cgiload 2019_03_03
  1222. # because I want to log it
  1223. # Can break here if load is too heavy
  1224. # Have in mind the CGI header has already a 503 Service Unavailable
  1225. cgiload( $sync ) ;
  1226. $fixcolonbug = defined $fixcolonbug ? $fixcolonbug : 1 ;
  1227. if ( $usecache and $fixcolonbug ) { tmpdir_fix_colon_bug( $sync ) } ;
  1228. $modulesversion and myprint( "Modules version list ( use --no-modulesversion to turn off printing this Perl modules list ):\n", modulesversion(), "\n" ) ;
  1229. check_lib_version( $sync ) or
  1230. croak "imapsync needs perl lib Mail::IMAPClient release 3.30 or superior.\n";
  1231. if ( $sync->{ justbanner } )
  1232. {
  1233. myprint( "Exiting because of --justbanner\n" ) ;
  1234. exit_clean( $sync, $EX_OK ) ;
  1235. }
  1236. # turn on RFC standard flags correction like \SEEN -> \Seen
  1237. $sync->{ flagscase } = defined $sync->{ flagscase } ? $sync->{ flagscase } : 1 ;
  1238. # Use PERMANENTFLAGS if available
  1239. $sync->{ filterflags } = defined $sync->{ filterflags } ? $sync->{ filterflags } : 1 ;
  1240. filterbuggyflags( $sync ) ;
  1241. # sync flags just after an APPEND, some servers ignore the flags given in the APPEND
  1242. # like MailEnable IMAP server.
  1243. # Off by default since it takes time.
  1244. $syncflagsaftercopy = defined $syncflagsaftercopy ? $syncflagsaftercopy : 0 ;
  1245. # update flags on host2 for already transferred messages
  1246. $sync->{resyncflags} = defined $sync->{resyncflags} ? $sync->{resyncflags} : 1 ;
  1247. if ( $sync->{resyncflags} ) {
  1248. myprint( "Info: will resync flags for already transferred messages. Use --noresyncflags to not resync flags.\n" ) ;
  1249. }else{
  1250. myprint( "Info: will not resync flags for already transferred messages. Use --resyncflags to resync flags.\n" ) ;
  1251. }
  1252. sslcheck( $sync ) ;
  1253. #print Data::Dumper->Dump( [ \$sync ] ) ;
  1254. $split1 ||= $SPLIT ;
  1255. $split2 ||= $SPLIT ;
  1256. #$sync->{host1} || missing_option( $sync, '--host1' ) ;
  1257. $sync->{host1} = sanitize_host( $sync->{host1} ) ;
  1258. $sync->{port1} ||= ( $sync->{ssl1} ) ? $IMAP_SSL_PORT : $IMAP_PORT ;
  1259. #$sync->{host2} || missing_option( $sync, '--host2' ) ;
  1260. $sync->{host2} = sanitize_host( $sync->{host2} ) ;
  1261. $sync->{port2} ||= ( $sync->{ssl2} ) ? $IMAP_SSL_PORT : $IMAP_PORT ;
  1262. $acc1->{ debugimap } = $acc2->{ debugimap } = 1 if ( $sync->{ debugimap } ) ;
  1263. # Set on debug mode if one of the imap dialogs are in debug.
  1264. # imap dialog without the debug mode is scary and useless.
  1265. $sync->{ debug } = 1 if ( $acc1->{ debugimap } or $acc2->{ debugimap } ) ;
  1266. # By default, don't take size to compare
  1267. $skipsize = (defined $skipsize) ? $skipsize : 1;
  1268. $uid1 = defined $uid1 ? $uid1 : 1;
  1269. $uid2 = defined $uid2 ? $uid2 : 1;
  1270. $subscribe = defined $subscribe ? $subscribe : 1;
  1271. # Allow size mismatch by default
  1272. $allowsizemismatch = defined $allowsizemismatch ? $allowsizemismatch : 1;
  1273. if ( defined $delete2foldersbutnot or defined $delete2foldersonly ) {
  1274. $delete2folders = 1 ;
  1275. }
  1276. my %SSL_VERIFY_STR ;
  1277. Readonly $SSL_VERIFY_POLICY => IO::Socket::SSL::SSL_VERIFY_NONE( ) ;
  1278. Readonly %SSL_VERIFY_STR => (
  1279. IO::Socket::SSL::SSL_VERIFY_NONE( ) => 'SSL_VERIFY_NONE, ie, do not check the certificate server.' ,
  1280. IO::Socket::SSL::SSL_VERIFY_PEER( ) => 'SSL_VERIFY_PEER, ie, check the certificate server' ,
  1281. ) ;
  1282. $IO::Socket::SSL::DEBUG = defined( $sync->{debugssl} ) ? $sync->{debugssl} : 1 ;
  1283. if ( $sync->{ssl1} or $sync->{ssl2} or $sync->{tls1} or $sync->{tls2}) {
  1284. 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" ) ;
  1285. }
  1286. if ( $sync->{ssl1} ) {
  1287. 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} ) ;
  1288. 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" ) ;
  1289. # $sync->{ acc1 }->{sslargs}->{SSL_verify_mode}
  1290. }
  1291. if ( $sync->{ssl2} ) {
  1292. 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} ) ;
  1293. 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" ) ;
  1294. }
  1295. # ID on by default since 1.832
  1296. $sync->{id} = defined $sync->{id} ? $sync->{id} : 1 ;
  1297. if ( $sync->{justconnect}
  1298. or not $sync->{user1}
  1299. or not $sync->{user2}
  1300. or not $sync->{host1}
  1301. or not $sync->{host2}
  1302. )
  1303. {
  1304. my $justconnect = justconnect( $sync ) ;
  1305. myprint( debugmemory( $sync, " after justconnect() call" ) ) ;
  1306. exit_clean( $sync, $EX_OK,
  1307. "Exiting after a justconnect on host(s): $justconnect\n"
  1308. ) ;
  1309. }
  1310. #$sync->{user1} || missing_option( $sync, '--user1' ) ;
  1311. #$sync->{user2} || missing_option( $sync, '--user2' ) ;
  1312. $syncinternaldates = defined $syncinternaldates ? $syncinternaldates : 1;
  1313. # Turn on expunge if there is not explicit option --noexpunge1 and option
  1314. # --delete1 is given.
  1315. # Done because --delete1 --noexpunge1 is very dangerous on the second run:
  1316. # the Deleted flag is then synced to all previously transferred messages.
  1317. # So --delete1 implies --expunge1 is a better usability default behavior.
  1318. if ( $sync->{ delete1 } ) {
  1319. if ( ! defined $sync->{ expunge1 } ) {
  1320. myprint( "Info: turning on --expunge1 because --delete1 --noexpunge1 is very dangerous on the second run.\n" ) ;
  1321. $sync->{ expunge1 } = 1 ;
  1322. }
  1323. myprint( "Info: if expunging after each message slows down too much the sync then use --noexpungeaftereach to speed up\n" ) ;
  1324. }
  1325. if ( $sync->{ uidexpunge2 } and not Mail::IMAPClient->can( 'uidexpunge' ) ) {
  1326. myprint( "Failure: uidexpunge not supported (IMAPClient release < 3.17), use nothing or --expunge2 instead\n" ) ;
  1327. $sync->{nb_errors}++ ;
  1328. exit_clean( $sync, $EX_SOFTWARE ) ;
  1329. }
  1330. if ( ( $sync->{ delete2 } or $sync->{ delete2duplicates } ) and not defined $sync->{ uidexpunge2 } ) {
  1331. if ( Mail::IMAPClient->can( 'uidexpunge' ) ) {
  1332. myprint( "Info: will act as --uidexpunge2\n" ) ;
  1333. $sync->{ uidexpunge2 } = 1 ;
  1334. }elsif ( not defined $sync->{ expunge2 } ) {
  1335. myprint( "Info: will act as --expunge2 (no uidexpunge support)\n" ) ;
  1336. $sync->{ expunge2 } = 1 ;
  1337. }
  1338. }
  1339. if ( $sync->{ delete1 } and $sync->{ delete2 } ) {
  1340. myprint( "Warning: using --delete1 and --delete2 together is almost always a bad idea. "
  1341. . "You should probably launch two runs, the first with --delete2 for a strict sync, "
  1342. . "then the second with --delete1 to remove messages from the source account. "
  1343. . "Exiting imapsync.\n" ) ;
  1344. $sync->{ nb_errors }++ ;
  1345. exit_clean( $sync, $EX_USAGE ) ;
  1346. }
  1347. if ( $idatefromheader ) {
  1348. myprint( 'Turned ON idatefromheader, ',
  1349. "will set the internal dates on host2 from the 'Date:' header line.\n" ) ;
  1350. $syncinternaldates = 0 ;
  1351. }
  1352. if ( $syncinternaldates ) {
  1353. myprint( 'Info: turned ON syncinternaldates, ',
  1354. "will set the internal dates (arrival dates) on host2 same as host1.\n" ) ;
  1355. }else{
  1356. myprint( "Info: turned OFF syncinternaldates\n" ) ;
  1357. }
  1358. if ( defined $authmd5 and $authmd5 ) {
  1359. $authmd51 = 1 ;
  1360. $authmd52 = 1 ;
  1361. }
  1362. if ( defined $authmd51 and $authmd51 ) {
  1363. $acc1->{ authmech } ||= 'CRAM-MD5' ;
  1364. }
  1365. else{
  1366. $acc1->{ authmech } ||= $acc1->{ authuser } ? 'PLAIN' : 'LOGIN' ;
  1367. }
  1368. if ( defined $authmd52 and $authmd52 ) {
  1369. $acc2->{ authmech } ||= 'CRAM-MD5';
  1370. }
  1371. else{
  1372. $acc2->{ authmech } ||= $acc2->{ authuser } ? 'PLAIN' : 'LOGIN';
  1373. }
  1374. $acc1->{ authmech } = uc $acc1->{ authmech } ;
  1375. $acc2->{ authmech } = uc $acc2->{ authmech } ;
  1376. if ( defined $acc1->{ proxyauth } && !$acc1->{ authuser } )
  1377. {
  1378. missing_option( $sync, 'With --proxyauth1, --authuser1' ) ;
  1379. }
  1380. if ( defined $acc2->{ proxyauth } && !$acc2->{ authuser } )
  1381. {
  1382. missing_option( $sync, 'With --proxyauth2, --authuser2' ) ;
  1383. }
  1384. myprint( "Host1: will try to use $acc1->{ authmech } authentication on host1\n") ;
  1385. myprint( "Host2: will try to use $acc2->{ authmech } authentication on host2\n") ;
  1386. $sync->{ timeout } = defined $sync->{ timeout } ?$sync->{ timeout } : $DEFAULT_TIMEOUT ;
  1387. $sync->{ acc1 }->{timeout} = defined $sync->{ acc1 }->{timeout} ? $sync->{ acc1 }->{timeout} : $sync->{ timeout } ;
  1388. myprint( "Host1: imap connection timeout is $sync->{ acc1 }->{timeout} seconds\n") ;
  1389. $sync->{ acc2 }->{timeout} = defined $sync->{ acc2 }->{timeout} ? $sync->{ acc2 }->{timeout} : $sync->{ timeout } ;
  1390. myprint( "Host2: imap connection timeout is $sync->{ acc2 }->{timeout} seconds\n" ) ;
  1391. keepalive1( $sync ) ;
  1392. keepalive2( $sync ) ;
  1393. if ( under_cgi_context( $sync ) )
  1394. {
  1395. myprint( "Under CGI context, a timeout can occur from the webserver, see https://imapsync.lamiral.info/INSTALL.d/INSTALL.OnlineUI.txt\n" ) ;
  1396. }
  1397. $sync->{ syncacls } = defined $sync->{ syncacls } ? $sync->{ syncacls } : 0 ;
  1398. # No folders sizes at the beginning if --justfolders, unless really wanted.
  1399. if (
  1400. $sync->{ justfolders }
  1401. and not defined $sync->{ foldersizes }
  1402. and not $sync->{ justfoldersizes } )
  1403. {
  1404. $sync->{ foldersizes } = 0 ;
  1405. $sync->{ foldersizesatend } = 1 ;
  1406. }
  1407. $sync->{ foldersizes } = ( defined $sync->{ foldersizes } ) ? $sync->{ foldersizes } : 1 ;
  1408. $sync->{ foldersizesatend } = ( defined $sync->{ foldersizesatend } ) ? $sync->{ foldersizesatend } : $sync->{ foldersizes } ;
  1409. #$sync->{ checknoabletosearch } = ( defined $sync->{ checknoabletosearch } ) ? $sync->{ checknoabletosearch } : 1 ;
  1410. set_checknoabletosearch( $sync ) ;
  1411. $acc1->{ fastio } = defined $acc1->{ fastio } ? $acc1->{ fastio } : 0 ;
  1412. $acc2->{ fastio } = defined $acc2->{ fastio } ? $acc2->{ fastio } : 0 ;
  1413. $acc1->{ reconnectretry } = defined $acc1->{ reconnectretry } ? $acc1->{ reconnectretry } : $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ;
  1414. $acc2->{ reconnectretry } = defined $acc2->{ reconnectretry } ? $acc2->{ reconnectretry } : $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ;
  1415. # IMAP compression on by default
  1416. #$acc1->{ compress } = defined $acc1->{ compress } ? $acc1->{ compress } : 0 ;
  1417. #$acc2->{ compress } = defined $acc2->{ compress } ? $acc2->{ compress } : 0 ;
  1418. if ( ! @useheader ) { @useheader = qw( Message-Id Received ) ; }
  1419. # Make a hash %useheader of each --useheader 'key' in uppercase
  1420. for ( @useheader ) { $sync->{useheader}->{ uc $_ } = undef } ;
  1421. #myprint( Data::Dumper->Dump( [ \%useheader ] ) ) ;
  1422. #exit ;
  1423. myprint( "Host1: IMAP server [$sync->{host1}] port [$sync->{port1}] user [$sync->{user1}]\n" ) ;
  1424. myprint( "Host2: IMAP server [$sync->{host2}] port [$sync->{port2}] user [$sync->{user2}]\n" ) ;
  1425. get_password1( $sync ) ;
  1426. get_password2( $sync ) ;
  1427. # --dry1 make imapsync not fetching messages from host1, it is on when --dry is on.
  1428. # Use --dry --nodry1 to make imapsync fetching messages from host1,
  1429. # It is useful when debugging transformation options like --pipemess or --regexmess
  1430. $sync->{dry1} = defined $sync->{dry1} ? $sync->{dry1} : $sync->{dry} ;
  1431. $sync->{dry_message} = q{} ;
  1432. if( $sync->{dry} ) {
  1433. $sync->{dry_message} = "\t(not really since --dry mode)" ;
  1434. }
  1435. $sync->{ search1 } ||= $search if ( $search ) ;
  1436. $sync->{ search2 } ||= $search if ( $search ) ;
  1437. if ( $disarmreadreceipts )
  1438. {
  1439. push @regexmess, q{s{\A((?:[^\n]+\r\n)+|)(^Disposition-Notification-To:[^\n]*\n)(\r?\n|.*\n\r?\n)}{$1X-$2$3}ims} ;
  1440. }
  1441. $pipemesscheck = ( defined $pipemesscheck ) ? $pipemesscheck : 1 ;
  1442. if ( @pipemess and $pipemesscheck ) {
  1443. myprint( 'Checking each --pipemess command, '
  1444. . join( q{, }, @pipemess )
  1445. . ", with an space string. ( Can avoid this check with --nopipemesscheck )\n" ) ;
  1446. my $string = pipemess( q{ }, @pipemess ) ;
  1447. # string undef means something was bad.
  1448. if ( not ( defined $string ) ) {
  1449. $sync->{nb_errors}++ ;
  1450. exit_clean( $sync, $EX_USAGE,
  1451. "Error: one of --pipemess command is bad, check it\n"
  1452. ) ;
  1453. }
  1454. myprint( "Ok with each --pipemess @pipemess\n" ) ;
  1455. }
  1456. if ( $maxlinelengthcmd ) {
  1457. myprint( "Checking --maxlinelengthcmd command,
  1458. $maxlinelengthcmd, with an space string.\n"
  1459. ) ;
  1460. my $string = pipemess( q{ }, $maxlinelengthcmd ) ;
  1461. # string undef means something was bad.
  1462. if ( not ( defined $string ) ) {
  1463. $sync->{nb_errors}++ ;
  1464. exit_clean( $sync, $EX_USAGE,
  1465. "Error: --maxlinelengthcmd command is bad, check it\n"
  1466. ) ;
  1467. }
  1468. myprint( "Ok with --maxlinelengthcmd $maxlinelengthcmd\n" ) ;
  1469. }
  1470. if ( @regexmess ) {
  1471. my $string = regexmess( q{ } ) ;
  1472. myprint( "Checking each --regexmess command with an space string.\n" ) ;
  1473. # string undef means one of the eval regex was bad.
  1474. if ( not ( defined $string ) ) {
  1475. #errors_incr( $sync, 'Warning: one of --regexmess option may be bad, check them' ) ;
  1476. exit_clean( $sync, $EX_USAGE,
  1477. "Error: one of --regexmess option is bad, check it\n"
  1478. ) ;
  1479. }
  1480. myprint( "Ok with each --regexmess\n" ) ;
  1481. }
  1482. if ( @skipmess ) {
  1483. myprint( "Checking each --skipmess command with an space string.\n" ) ;
  1484. my $match = skipmess( q{ } ) ;
  1485. # match undef means one of the eval regex was bad.
  1486. if ( not ( defined $match ) ) {
  1487. $sync->{nb_errors}++ ;
  1488. exit_clean( $sync, $EX_USAGE,
  1489. "Error: one of --skipmess option is bad, check it\n"
  1490. ) ;
  1491. }
  1492. myprint( "Ok with each --skipmess\n" ) ;
  1493. }
  1494. if ( $sync->{ regexflag } ) {
  1495. myprint( "Checking each --regexflag command with an space string.\n" ) ;
  1496. my $string = regexflags( $sync, q{ } ) ;
  1497. # string undef means one of the eval regex was bad.
  1498. if ( not ( defined $string ) ) {
  1499. $sync->{nb_errors}++ ;
  1500. exit_clean( $sync, $EX_USAGE,
  1501. "Error: one of --regexflag option is bad, check it\n"
  1502. ) ;
  1503. }
  1504. myprint( "Ok with each --regexflag\n" ) ;
  1505. }
  1506. $sync->{imap1} = login_imap( $sync->{host1}, $sync->{port1}, $sync->{user1}, $sync->{password1},
  1507. $sync->{ssl1}, $sync->{tls1},
  1508. $uid1, $split1, $sync->{ acc1 }, $sync ) ;
  1509. $sync->{imap2} = login_imap( $sync->{host2}, $sync->{port2}, $sync->{user2}, $sync->{password2},
  1510. $sync->{ssl2}, $sync->{tls2},
  1511. $uid2, $split2, $sync->{ acc2 }, $sync ) ;
  1512. $sync->{ debug } and $sync->{imap1} and myprint( 'Host1 Buffer I/O: ', $sync->{imap1}->Buffer(), "\n" ) ;
  1513. $sync->{ debug } and $sync->{imap2} and myprint( 'Host2 Buffer I/O: ', $sync->{imap2}->Buffer(), "\n" ) ;
  1514. if ( ! $sync->{imap1} || ! $sync->{imap2} )
  1515. {
  1516. exit_most_errors( $sync ) ;
  1517. }
  1518. myprint( "Host1: state Authenticated\n" ) ;
  1519. myprint( "Host2: state Authenticated\n" ) ;
  1520. myprint( 'Host1 capability once authenticated: ', join(q{ }, @{ $sync->{imap1}->capability() || [] }), "\n" ) ;
  1521. #myprint( Data::Dumper->Dump( [ $sync->{imap1} ] ) ) ;
  1522. #myprint( "imap4rev1: " . $sync->{imap1}->imap4rev1() . "\n" ) ;
  1523. myprint( 'Host2 capability once authenticated: ', join(q{ }, @{ $sync->{imap2}->capability() || [] }), "\n" ) ;
  1524. imap_id_stuff( $sync ) ;
  1525. #quota( $sync, $sync->{imap1}, 'h1' ) ; # quota on host1 is useless and pollute host2 output.
  1526. quota( $sync, $sync->{imap2}, 'h2' ) ;
  1527. maxsize_setting( $sync ) ;
  1528. acc_compress_imap( $acc1 ) ;
  1529. acc_compress_imap( $acc2 ) ;
  1530. if ( $sync->{ justlogin } ) {
  1531. $sync->{imap1}->logout( ) ;
  1532. $sync->{imap2}->logout( ) ;
  1533. exit_clean( $sync, $EX_OK, "Exiting because of --justlogin\n" ) ;
  1534. }
  1535. #
  1536. # Folder stuff
  1537. #
  1538. $h1_folders_wanted_nb = 0 ; # counter of folders to be done.
  1539. $h1_folders_wanted_ct = 0 ; # counter of folders done.
  1540. # All folders on host1 and host2
  1541. @h1_folders_all = sort $sync->{imap1}->folders( ) ;
  1542. @h2_folders_all = sort $sync->{imap2}->folders( ) ;
  1543. myprint( 'Host1: found ', scalar @h1_folders_all , " folders.\n" ) ;
  1544. myprint( 'Host2: found ', scalar @h2_folders_all , " folders.\n" ) ;
  1545. foreach my $f ( @h1_folders_all )
  1546. {
  1547. $h1_folders_all{ $f } = 1
  1548. }
  1549. foreach my $f ( @h2_folders_all )
  1550. {
  1551. $h2_folders_all{ $f } = 1 ;
  1552. $sync->{h2_folders_all_UPPER}{ uc $f } = 1 ;
  1553. }
  1554. $sync->{h1_folders_all} = \%h1_folders_all ;
  1555. $sync->{h2_folders_all} = \%h2_folders_all ;
  1556. private_folders_separators_and_prefixes( ) ;
  1557. # Make a hash of subscribed folders in both servers.
  1558. for ( $sync->{imap1}->subscribed( ) ) { $h1_subscribed_folder{ $_ } = 1 } ;
  1559. for ( $sync->{imap2}->subscribed( ) ) { $h2_subscribed_folder{ $_ } = 1 } ;
  1560. if ( defined $sync->{ subfolder1 } ) {
  1561. subfolder1( $sync ) ;
  1562. }
  1563. if ( defined $sync->{ subfolder2 } ) {
  1564. subfolder2( $sync ) ;
  1565. }
  1566. if ( $fixInboxINBOX and ( my $reg = fix_Inbox_INBOX_mapping( \%h1_folders_all, \%h2_folders_all ) ) ) {
  1567. push @{ $sync->{ regextrans2 } }, $reg ;
  1568. }
  1569. if ( ( $sync->{ folder } and scalar @{ $sync->{ folder } } )
  1570. or $subscribed
  1571. or scalar @folderrec )
  1572. {
  1573. # folders given by option --folder
  1574. if ( $sync->{ folder } and scalar @{ $sync->{ folder } } ) {
  1575. add_to_requested_folders( @{ $sync->{ folder } } ) ;
  1576. }
  1577. # option --subscribed
  1578. if ( $subscribed ) {
  1579. add_to_requested_folders( keys %h1_subscribed_folder ) ;
  1580. }
  1581. # option --folderrec
  1582. if ( scalar @folderrec ) {
  1583. foreach my $folderrec ( @folderrec ) {
  1584. add_to_requested_folders( $sync->{imap1}->folders( $folderrec ) ) ;
  1585. }
  1586. }
  1587. }
  1588. else
  1589. {
  1590. # no include, no folder/subscribed/folderrec options => all folders
  1591. if ( not scalar @include ) {
  1592. 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" ) ;
  1593. add_to_requested_folders( @h1_folders_all ) ;
  1594. }
  1595. }
  1596. # consider (optional) includes and excludes
  1597. if ( scalar @include ) {
  1598. foreach my $include ( @include ) {
  1599. # No, do not add /x after the regex, never.
  1600. # Users would kill you!
  1601. my @included_folders = grep { /$include/ } @h1_folders_all ;
  1602. add_to_requested_folders( @included_folders ) ;
  1603. myprint( "Including folders matching pattern $include\n" . jux_utf8_list( @included_folders ) . "\n" ) ;
  1604. }
  1605. }
  1606. if ( scalar @exclude ) {
  1607. foreach my $exclude ( @exclude ) {
  1608. my @requested_folder = sort keys %requested_folder ;
  1609. # No, do not add /x after the regex, never.
  1610. # Users would kill you!
  1611. my @excluded_folders = grep { /$exclude/ } @requested_folder ;
  1612. remove_from_requested_folders( @excluded_folders ) ;
  1613. myprint( "Excluding folders matching pattern $exclude\n" . jux_utf8_list( @excluded_folders ) . "\n" ) ;
  1614. }
  1615. }
  1616. # sort before is not very powerful
  1617. # it adds --folderfirst and --folderlast even if they don't exist on host1
  1618. #@h1_folders_wanted = sort_requested_folders( ) ;
  1619. $sync->{h1_folders_wanted} = [ sort_requested_folders( ) ] ;
  1620. # Remove no selectable folders
  1621. if ( $sync->{ checkfoldersexist } ) {
  1622. my @h1_folders_wanted_exist ;
  1623. myprint( "Host1: Checking wanted folders exist. Use --nocheckfoldersexist to avoid this check (shared of public namespace targeted).\n" ) ;
  1624. foreach my $folder ( @{ $sync->{h1_folders_wanted} } ) {
  1625. ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "Checking $folder exists on host1\n" ) ;
  1626. if ( ! exists $h1_folders_all{ $folder } ) {
  1627. myprint( "Host1: warning! ignoring folder $folder because it is not in host1 whole folders list.\n" ) ;
  1628. next ;
  1629. }else{
  1630. push @h1_folders_wanted_exist, $folder ;
  1631. }
  1632. }
  1633. @{ $sync->{h1_folders_wanted} } = @h1_folders_wanted_exist ;
  1634. }else{
  1635. myprint( "Host1: Not checking that wanted folders exist. Remove --nocheckfoldersexist to get this check.\n" ) ;
  1636. }
  1637. setcheckselectable( $sync ) ;
  1638. checkselectable( $sync ) ;
  1639. # Bugfix OpenFind folders named like "kk \*123" are in fact "kk *123" (no \)
  1640. #foreach my $folder ( @{ $sync->{ h1_folders_wanted } } )
  1641. #{
  1642. # $folder =~ s{ \\\*}{ *}g ;
  1643. #}
  1644. # this hack is because LWP post does not pass well a hash in the $form parameter
  1645. # but it does pass well an array
  1646. %{ $sync->{f1f2h} } = split_around_equal( @{ $sync->{f1f2} } ) ;
  1647. automap( $sync ) ;
  1648. foreach my $h1_fold ( @{ $sync->{h1_folders_wanted} } ) {
  1649. my $h2_fold ;
  1650. $h2_fold = imap2_folder_name( $sync, $h1_fold ) ;
  1651. $h2_folders_from_1_wanted{ $h2_fold }++ ;
  1652. if ( 1 < $h2_folders_from_1_wanted{ $h2_fold } ) {
  1653. $h2_folders_from_1_several{ $h2_fold }++ ;
  1654. }
  1655. }
  1656. @h2_folders_from_1_wanted = sort keys %h2_folders_from_1_wanted;
  1657. foreach my $h1_fold ( @h1_folders_all ) {
  1658. my $h2_fold ;
  1659. $h2_fold = imap2_folder_name( $sync, $h1_fold ) ;
  1660. $h2_folders_from_1_all{ $h2_fold }++ ;
  1661. # Follows a fix to avoid deleting folder $sync->{ subfolder2 }
  1662. # because it usually does not exist on host1.
  1663. if ( $sync->{ subfolder2 } )
  1664. {
  1665. $h2_folders_from_1_all{ $sync->{ h2_prefix } . $sync->{ subfolder2 } }++ ;
  1666. $h2_folders_from_1_all{ $sync->{ subfolder2 } }++ ;
  1667. }
  1668. }
  1669. myprint( << 'END_LISTING' ) ;
  1670. ++++ Listing folders
  1671. All foldernames are presented between brackets like [X] where X is the foldername.
  1672. When a foldername contains non-ASCII characters it is presented in the form
  1673. [X] = [Y] where
  1674. X is the imap foldername you have to use in command line options and
  1675. Y is the utf8 output just printed for convenience, to recognize it.
  1676. END_LISTING
  1677. myprint(
  1678. "Host1: folders list (first the raw imap format then the [X] = [Y]):\n",
  1679. $sync->{imap1}->list( ),
  1680. "\n",
  1681. jux_utf8_list( @h1_folders_all ),
  1682. "\n",
  1683. "Host2: folders list (first the raw imap format then the [X] = [Y]):\n",
  1684. $sync->{imap2}->list( ),
  1685. "\n",
  1686. jux_utf8_list( @h2_folders_all ),
  1687. "\n",
  1688. q{}
  1689. ) ;
  1690. if ( $subscribed ) {
  1691. myprint(
  1692. 'Host1 subscribed folders list: ',
  1693. jux_utf8_list( sort keys %h1_subscribed_folder ), "\n",
  1694. ) ;
  1695. }
  1696. @h2_folders_not_in_1 = list_folders_in_2_not_in_1( ) ;
  1697. if ( @h2_folders_not_in_1 ) {
  1698. myprint( "Folders in host2 not in host1:\n",
  1699. jux_utf8_list( @h2_folders_not_in_1 ), "\n" ) ;
  1700. }
  1701. if ( keys %{ $sync->{f1f2auto} } ) {
  1702. myprint( "Folders mapping from --automap feature (use --f1f2 to override any mapping):\n" ) ;
  1703. foreach my $h1_fold ( keys %{ $sync->{f1f2auto} } ) {
  1704. my $h2_fold = $sync->{f1f2auto}{$h1_fold} ;
  1705. myprintf( "%-40s -> %-40s\n",
  1706. jux_utf8( $h1_fold ), jux_utf8( $h2_fold ) ) ;
  1707. }
  1708. myprint( "\n" ) ;
  1709. }
  1710. if ( keys %{ $sync->{f1f2h} } ) {
  1711. myprint( "Folders mapping from --f1f2 options, it overrides --automap:\n" ) ;
  1712. foreach my $h1_fold ( keys %{ $sync->{f1f2h} } ) {
  1713. my $h2_fold = $sync->{f1f2h}{$h1_fold} ;
  1714. my $warn = q{} ;
  1715. if ( not exists $h1_folders_all{ $h1_fold } ) {
  1716. $warn = "BUT $h1_fold does NOT exist on host1!" ;
  1717. }
  1718. myprintf( "%-40s -> %-40s %s\n",
  1719. jux_utf8( $h1_fold ), jux_utf8( $h2_fold ), $warn ) ;
  1720. }
  1721. myprint( "\n" ) ;
  1722. }
  1723. exit_clean( $sync, $EX_OK, "Exiting because of --justfolderlists\n" ) if ( $sync->{ justfolderlists } ) ;
  1724. exit_clean( $sync, $EX_OK, "Exiting because of --justautomap\n" ) if ( $sync->{ justautomap } ) ;
  1725. debugsleep( $sync ) ;
  1726. if ( $sync->{ skipemptyfolders } )
  1727. {
  1728. myprint( "Host1: will not syncing empty folders on host1. Use --noskipemptyfolders to create them anyway on host2\n") ;
  1729. }
  1730. if ( $sync->{ checknoabletosearch } )
  1731. {
  1732. myprint( "Checking SEARCH ALL works on both accounts. To avoid that check, use --nochecknoabletosearch\n" ) ;
  1733. my $check1 = checknoabletosearch( $sync, $sync->{ imap1 }, 'INBOX', 'Host1' ) ;
  1734. my $check2 = checknoabletosearch( $sync, $sync->{ imap2 }, 'INBOX', 'Host2' ) ;
  1735. if ( $check1 or $check2 )
  1736. {
  1737. myprint( "At least one account can not SEARCH ALL. So acting like --noabletosearch\n" ) ;
  1738. $sync->{abletosearch} = 0 ;
  1739. $sync->{abletosearch1} = 0 ;
  1740. $sync->{abletosearch2} = 0 ;
  1741. }
  1742. else
  1743. {
  1744. myprint( "Good! SEARCH ALL works on both accounts.\n" ) ;
  1745. }
  1746. }
  1747. if ( $sync->{ foldersizes } ) {
  1748. foldersizes_at_the_beggining( $sync ) ;
  1749. #foldersizes_at_the_beggining_old( $sync ) ;
  1750. }
  1751. if ( $sync->{ justfoldersizes } )
  1752. {
  1753. exit_clean( $sync, $EX_OK, "Exiting because of --justfoldersizes\n" ) ;
  1754. }
  1755. $sync->{can_do_stats} = 1 ;
  1756. if ( $sync->{ delete1emptyfolders } ) {
  1757. delete1emptyfolders( $sync ) ;
  1758. }
  1759. delete_folders_in_2_not_in_1( ) if $delete2folders ;
  1760. # folder loop
  1761. $h1_folders_wanted_nb = scalar @{ $sync->{h1_folders_wanted} } ;
  1762. myprint( "++++ Looping on each one of $h1_folders_wanted_nb folders to sync\n" ) ;
  1763. $sync->{begin_transfer_time} = time ;
  1764. my %uid_candidate_for_deletion ;
  1765. my %uid_candidate_no_deletion ;
  1766. $sync->{ h2_folders_of_md5 } = { } ;
  1767. FOLDER: foreach my $h1_fold ( @{ $sync->{h1_folders_wanted} } )
  1768. {
  1769. $sync->{ h1_current_folder } = $h1_fold ;
  1770. eta_print( $sync ) ;
  1771. abortifneeded( $sync ) ;
  1772. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1773. my $h2_fold = imap2_folder_name( $sync, $h1_fold ) ;
  1774. $sync->{ h2_current_folder } = $h2_fold ;
  1775. $h1_folders_wanted_ct++ ;
  1776. myprintf( "Folder %7s %-35s -> %-35s\n", "$h1_folders_wanted_ct/$h1_folders_wanted_nb",
  1777. jux_utf8( $h1_fold ), jux_utf8( $h2_fold ) ) ;
  1778. myprint( debugmemory( $sync, " at folder loop" ) ) ;
  1779. # host1 can not be fetched read only, select is needed because of expunge.
  1780. select_folder( $sync, $sync->{imap1}, $h1_fold, 'Host1' ) or next FOLDER ;
  1781. debugsleep( $sync ) ;
  1782. my $h1_msgs_all_hash_ref ;
  1783. my @h1_msgs ;
  1784. my $h1_msgs_nb ;
  1785. my $h1_msgs_nb_from_select ;
  1786. $h1_msgs_nb_from_select = count_from_select( $sync->{imap1}->History ) ;
  1787. myprint( "Host1: folder [$h1_fold] has $h1_msgs_nb_from_select messages in total (mentioned by SELECT)\n" ) ;
  1788. if ( $sync->{ skipemptyfolders } and 0 == $h1_msgs_nb_from_select ) {
  1789. myprint( "Host1: skipping empty host1 folder [$h1_fold]\n" ) ;
  1790. next FOLDER ;
  1791. }
  1792. # Code added from https://github.com/imapsync/imapsync/issues/95
  1793. # Thanks jh1995
  1794. # Goal: do not create folder if --search or --max/minage return 0 message.
  1795. # even if there are messages by SELECT (no not real empty, empty for the user point of vue).
  1796. if ( $sync->{ skipemptyfolders } or $sync->{ dry } )
  1797. {
  1798. $h1_msgs_all_hash_ref = { } ;
  1799. @h1_msgs = select_msgs( $sync->{imap1}, $h1_msgs_all_hash_ref, $sync->{ search1 }, $sync->{abletosearch1}, $h1_fold ) ;
  1800. $h1_msgs_nb = scalar( @h1_msgs ) ;
  1801. if ( 0 == $h1_msgs_nb and $sync->{ skipemptyfolders } ) {
  1802. myprint( "Host1: skipping empty host1 folder [$h1_fold] (0 message found by SEARCH)\n" ) ;
  1803. next FOLDER ;
  1804. }
  1805. }
  1806. if ( ! exists $h2_folders_all{ $h2_fold } ) {
  1807. # In --dry mode I could count the messages to be transfered instead of 0
  1808. # Messages transferred : 0 (could be 0 without dry mode)
  1809. if ( ! create_folder( $sync, $sync->{imap2}, $h2_fold, $h1_fold ) )
  1810. {
  1811. if ( $sync->{ dry } )
  1812. {
  1813. $nb_msg_skipped_dry_mode += $h1_msgs_nb ;
  1814. }
  1815. next FOLDER ;
  1816. }
  1817. }
  1818. acls_sync( $sync, $h1_fold, $h2_fold ) ;
  1819. # Sometimes the folder on host2 is listed (it exists) but is
  1820. # not selectable but becomes selectable by a create (Gmail)
  1821. select_folder( $sync, $sync->{imap2}, $h2_fold, 'Host2' )
  1822. or ( create_folder( $sync, $sync->{imap2}, $h2_fold, $h1_fold )
  1823. and select_folder( $sync, $sync->{imap2}, $h2_fold, 'Host2' ) )
  1824. or next FOLDER ;
  1825. my @select_results = $sync->{imap2}->Results( ) ;
  1826. my $h2_fold_nb_messages = count_from_select( @select_results ) ;
  1827. myprint( "Host2: folder [$h2_fold] has $h2_fold_nb_messages messages in total (mentioned by SELECT)\n" ) ;
  1828. my $permanentflags2 = permanentflags( $sync, @select_results ) ;
  1829. myprint( "Host2: folder [$h2_fold] permanentflags: $permanentflags2\n" ) ;
  1830. if ( $sync->{ expunge1 } )
  1831. {
  1832. myprint( "Host1: Expunging $h1_fold $sync->{dry_message}\n" ) ;
  1833. if ( ! $sync->{dry} )
  1834. {
  1835. $sync->{imap1}->expunge( ) ;
  1836. }
  1837. }
  1838. if ( ( ( $subscribe and exists $h1_subscribed_folder{ $h1_fold } ) or $subscribeall )
  1839. and not exists $h2_subscribed_folder{ $h2_fold } )
  1840. {
  1841. myprint( "Host2: Subscribing to folder $h2_fold\n" ) ;
  1842. if ( ! $sync->{dry} ) { $sync->{imap2}->subscribe( $h2_fold ) } ;
  1843. }
  1844. next FOLDER if ( $sync->{ justfolders } ) ;
  1845. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1846. if ( ! defined $h1_msgs_nb )
  1847. {
  1848. $h1_msgs_all_hash_ref = { } ;
  1849. @h1_msgs = select_msgs( $sync->{imap1}, $h1_msgs_all_hash_ref, $sync->{ search1 }, $sync->{abletosearch1}, $h1_fold );
  1850. $h1_msgs_nb = scalar @h1_msgs ;
  1851. }else{
  1852. # select_msgs already done.
  1853. }
  1854. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1855. myprint( "Host1: folder [$h1_fold] considering $h1_msgs_nb messages\n" ) ;
  1856. ( $sync->{ debug } or $debuglist ) and myprint( "Host1: folder [$h1_fold] considering $h1_msgs_nb messages, LIST gives: @h1_msgs\n" ) ;
  1857. $sync->{ debug } and myprint( "Host1: selecting messages of folder [$h1_fold] took ", timenext( $sync ), " s\n" ) ;
  1858. my $h2_msgs_all_hash_ref = { } ;
  1859. my @h2_msgs = select_msgs( $sync->{imap2}, $h2_msgs_all_hash_ref, $sync->{ search2 }, $sync->{abletosearch2}, $h2_fold ) ;
  1860. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1861. my $h2_msgs_nb = scalar @h2_msgs ;
  1862. myprint( "Host2: folder [$h2_fold] considering $h2_msgs_nb messages\n" ) ;
  1863. ( $sync->{ debug } or $debuglist ) and myprint( "Host2: folder [$h2_fold] considering $h2_msgs_nb messages, LIST gives: @h2_msgs\n" ) ;
  1864. $sync->{ debug } and myprint( "Host2: selecting messages of folder [$h2_fold] took ", timenext( $sync ), " s\n" ) ;
  1865. my $cache_base = "$sync->{ tmpdir }/imapsync_cache/" ;
  1866. my $cache_dir = cache_folder( $cache_base,
  1867. "$sync->{host1}/$sync->{user1}/$sync->{host2}/$sync->{user2}", $h1_fold, $h2_fold ) ;
  1868. my ( $cache_1_2_ref, $cache_2_1_ref ) = ( {}, {} ) ;
  1869. my $h1_uidvalidity = $sync->{imap1}->uidvalidity( ) || q{} ;
  1870. my $h2_uidvalidity = $sync->{imap2}->uidvalidity( ) || q{} ;
  1871. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1872. if ( $usecache ) {
  1873. myprint( "Local cache directory: $cache_dir ( " . length( $cache_dir ) . " characters long )\n" ) ;
  1874. mkpath( "$cache_dir" ) ;
  1875. ( $cache_1_2_ref, $cache_2_1_ref )
  1876. = get_cache( $cache_dir, \@h1_msgs, \@h2_msgs, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) ;
  1877. myprint( 'CACHE h1 h2: ', scalar keys %{ $cache_1_2_ref } , " files\n" ) ;
  1878. $sync->{ debug } and myprint( '[',
  1879. map ( { "$_->$cache_1_2_ref->{$_} " } keys %{ $cache_1_2_ref } ), " ]\n" ) ;
  1880. }
  1881. my %h1_hash = ( ) ;
  1882. my %h2_hash = ( ) ;
  1883. my ( %h1_msgs, %h2_msgs ) ;
  1884. @h1_msgs{ @h1_msgs } = ( ) ;
  1885. @h2_msgs{ @h2_msgs } = ( ) ;
  1886. my @h1_msgs_in_cache = sort { $a <=> $b } keys %{ $cache_1_2_ref } ;
  1887. my @h2_msgs_in_cache = sort { $a <=> $b } keys %{ $cache_2_1_ref } ;
  1888. my ( %h1_msgs_not_in_cache, %h2_msgs_not_in_cache ) ;
  1889. %h1_msgs_not_in_cache = %h1_msgs ;
  1890. %h2_msgs_not_in_cache = %h2_msgs ;
  1891. delete @h1_msgs_not_in_cache{ @h1_msgs_in_cache } ;
  1892. delete @h2_msgs_not_in_cache{ @h2_msgs_in_cache } ;
  1893. my @h1_msgs_not_in_cache = sort { $a <=> $b } keys %h1_msgs_not_in_cache ;
  1894. #myprint( "h1_msgs_not_in_cache: [@h1_msgs_not_in_cache]\n" ) ;
  1895. my @h2_msgs_not_in_cache = sort { $a <=> $b } keys %h2_msgs_not_in_cache ;
  1896. my @h2_msgs_delete2_not_in_cache = () ;
  1897. %h1_msgs_copy_by_uid = ( ) ;
  1898. if ( $useuid ) {
  1899. # use uid so we have to avoid getting header
  1900. @h1_msgs_copy_by_uid{ @h1_msgs_not_in_cache } = ( ) ;
  1901. @h2_msgs_delete2_not_in_cache = @h2_msgs_not_in_cache if $usecache ;
  1902. @h1_msgs_not_in_cache = ( ) ;
  1903. @h2_msgs_not_in_cache = ( ) ;
  1904. #myprint( "delete2: @h2_msgs_delete2_not_in_cache\n" ) ;
  1905. }
  1906. $sync->{ debug } and myprint( "Host1: parsing headers of folder [$h1_fold]\n" ) ;
  1907. my ($h1_heads_ref, $h1_fir_ref) = ({}, {});
  1908. $h1_heads_ref = $sync->{imap1}->parse_headers([@h1_msgs_not_in_cache], @useheader) if (@h1_msgs_not_in_cache);
  1909. $sync->{ debug } and myprint( "Host1: parsing headers of folder [$h1_fold] took ", timenext( $sync ), " s\n" ) ;
  1910. @{ $h1_fir_ref }{@h1_msgs} = ( undef ) ;
  1911. $sync->{ debug } and myprint( "Host1: getting flags idate and sizes of folder [$h1_fold]\n" ) ;
  1912. my @h1_common_fetch_param = ( 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE' ) ;
  1913. if ( $sync->{ synclabels } or $sync->{ resynclabels } ) { push @h1_common_fetch_param, 'X-GM-LABELS' ; }
  1914. if ( $sync->{abletosearch1} )
  1915. {
  1916. $h1_fir_ref = $sync->{imap1}->fetch_hash( \@h1_msgs, @h1_common_fetch_param, $h1_fir_ref )
  1917. if ( @h1_msgs ) ;
  1918. }
  1919. else
  1920. {
  1921. my $fetch_hash_uids = $fetch_hash_set || "1:*" ;
  1922. $h1_fir_ref = $sync->{imap1}->fetch_hash( $fetch_hash_uids, @h1_common_fetch_param, $h1_fir_ref )
  1923. if ( @h1_msgs ) ;
  1924. }
  1925. $sync->{ debug } and myprint( "Host1: getting flags idate and sizes of folder [$h1_fold] took ", timenext( $sync ), " s\n" ) ;
  1926. if ( ! $h1_fir_ref )
  1927. {
  1928. my $error = join( q{}, "Host1: folder $h1_fold : Could not fetch_hash ",
  1929. scalar @h1_msgs, ' msgs: ', $sync->{imap1}->LastError || q{}, "\n" ) ;
  1930. errors_incr( $sync, $error ) ;
  1931. next FOLDER ;
  1932. }
  1933. my @h1_msgs_duplicate;
  1934. foreach my $m ( @h1_msgs_not_in_cache )
  1935. {
  1936. my $rc = parse_header_msg( $sync, $sync->{imap1}, $m, $h1_heads_ref, $h1_fir_ref, 'Host1', \%h1_hash ) ;
  1937. if ( ! defined $rc )
  1938. {
  1939. my $h1_size = $h1_fir_ref->{$m}->{'RFC822.SIZE'} || 0;
  1940. myprint( "Host1: $h1_fold/$m size $h1_size ignored (no wanted headers so we ignore this message. To solve this: use --addheader)\n" ) ;
  1941. $sync->{ total_bytes_skipped } += $h1_size ;
  1942. $sync->{ nb_msg_skipped } += 1 ;
  1943. $sync->{ h1_nb_msg_noheader } +=1 ;
  1944. $sync->{ h1_nb_msg_processed } +=1 ;
  1945. } elsif( 0 == $rc )
  1946. {
  1947. # duplicate
  1948. push @h1_msgs_duplicate, $m;
  1949. # duplicate, same id same size?
  1950. my $h1_size = $h1_fir_ref->{$m}->{'RFC822.SIZE'} || 0;
  1951. $sync->{ acc1 }->{ nb_msg_duplicate } += 1;
  1952. if ( ! $sync->{ syncduplicates } ) {
  1953. $sync->{ nb_msg_skipped } += 1 ;
  1954. $sync->{ h1_nb_msg_processed } +=1 ;
  1955. }
  1956. }
  1957. }
  1958. my $h1_msgs_duplicate_nb = scalar @h1_msgs_duplicate ;
  1959. myprint( "Host1: folder [$h1_fold] selected $h1_msgs_nb messages, duplicates $h1_msgs_duplicate_nb\n" ) ;
  1960. $sync->{ debug } and myprint( 'Host1: whole time parsing headers took ', timenext( $sync ), " s\n" ) ;
  1961. # Getting headers and metada can be so long that host2 might be disconnected here
  1962. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1963. $sync->{ debug } and myprint( "Host2: parsing headers of folder [$h2_fold]\n" ) ;
  1964. my ($h2_heads_ref, $h2_fir_ref) = ( {}, {} );
  1965. $h2_heads_ref = $sync->{imap2}->parse_headers([@h2_msgs_not_in_cache], @useheader) if (@h2_msgs_not_in_cache);
  1966. $sync->{ debug } and myprint( "Host2: parsing headers of folder [$h2_fold] took ", timenext( $sync ), " s\n" ) ;
  1967. $sync->{ debug } and myprint( "Host2: getting flags idate and sizes of folder [$h2_fold]\n" ) ;
  1968. @{ $h2_fir_ref }{@h2_msgs} = ( ); # fetch_hash can select by uid with last arg as ref
  1969. my @h2_common_fetch_param = ( 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE' ) ;
  1970. if ( $sync->{ synclabels } or $sync->{ resynclabels } ) { push @h2_common_fetch_param, 'X-GM-LABELS' ; }
  1971. if ( $sync->{abletosearch2} and scalar( @h2_msgs ) ) {
  1972. $h2_fir_ref = $sync->{imap2}->fetch_hash( \@h2_msgs, @h2_common_fetch_param, $h2_fir_ref) ;
  1973. }else{
  1974. my $fetch_hash_uids = $fetch_hash_set || "1:*" ;
  1975. $h2_fir_ref = $sync->{imap2}->fetch_hash( $fetch_hash_uids, @h2_common_fetch_param, $h2_fir_ref )
  1976. if ( @h2_msgs ) ;
  1977. }
  1978. $sync->{ debug } and myprint( "Host2: getting flags idate and sizes of folder [$h2_fold] took ", timenext( $sync ), " s\n" ) ;
  1979. my @h2_msgs_duplicate;
  1980. foreach my $m (@h2_msgs_not_in_cache) {
  1981. my $rc = parse_header_msg( $sync, $sync->{imap2}, $m, $h2_heads_ref, $h2_fir_ref, 'Host2', \%h2_hash ) ;
  1982. my $h2_size = $h2_fir_ref->{$m}->{'RFC822.SIZE'} || 0 ;
  1983. if (! defined $rc ) {
  1984. myprint( "Host2: $h2_fold/$m size $h2_size ignored (no wanted headers so we ignore this message)\n" ) ;
  1985. $h2_nb_msg_noheader += 1 ;
  1986. } elsif( 0 == $rc ) {
  1987. # duplicate
  1988. $sync->{ acc2 }->{ nb_msg_duplicate } += 1 ;
  1989. push @h2_msgs_duplicate, $m ;
  1990. }
  1991. }
  1992. # %h2_folders_of_md5
  1993. foreach my $md5 ( keys %h2_hash ) {
  1994. $sync->{ h2_folders_of_md5 }->{ $md5 }->{ $h2_fold } ++ ;
  1995. }
  1996. # %h1_folders_of_md5
  1997. foreach my $md5 ( keys %h1_hash ) {
  1998. $sync->{ h1_folders_of_md5 }->{ $md5 }->{ $h2_fold } ++ ;
  1999. }
  2000. my $h2_msgs_duplicate_nb = scalar @h2_msgs_duplicate ;
  2001. myprint( "Host2: folder [$h2_fold] selected $h2_msgs_nb messages, duplicates $h2_msgs_duplicate_nb\n" ) ;
  2002. $sync->{ debug } and myprint( 'Host2 whole time parsing headers took ', timenext( $sync ), " s\n" ) ;
  2003. $sync->{ debug } and myprint( "++++ Verifying [$h1_fold] -> [$h2_fold]\n" ) ;
  2004. # messages in host1 that are not in host2
  2005. my @h1_hash_keys_sorted_by_uid
  2006. = sort {$h1_hash{$a}{'m'} <=> $h1_hash{$b}{'m'}} keys %h1_hash;
  2007. #myprint( map { $h1_hash{$_}{'m'} . q{ }} @h1_hash_keys_sorted_by_uid ) ;
  2008. my @h2_hash_keys_sorted_by_uid
  2009. = sort {$h2_hash{$a}{'m'} <=> $h2_hash{$b}{'m'}} keys %h2_hash;
  2010. # Deletions on account2.
  2011. if( $sync->{ delete2duplicates } and not exists $h2_folders_from_1_several{ $h2_fold } ) {
  2012. my @h2_expunge ;
  2013. foreach my $h2_msg ( @h2_msgs_duplicate ) {
  2014. myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted [duplicate] on host2 $sync->{dry_message}\n" ) ;
  2015. push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 } ;
  2016. if ( ! $sync->{ dry } ) {
  2017. $sync->{ imap2 }->delete_message( $h2_msg ) ;
  2018. $sync->{ acc2 }->{ nb_msg_deleted } += 1 ;
  2019. }
  2020. }
  2021. my $cnt = scalar @h2_expunge ;
  2022. if( @h2_expunge and not $sync->{ expunge2 } ) {
  2023. myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n" ) ;
  2024. $sync->{imap2}->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ;
  2025. }
  2026. if ( $sync->{ expunge2 } ){
  2027. myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n" ) ;
  2028. $sync->{imap2}->expunge( ) if ! $sync->{dry} ;
  2029. }
  2030. }
  2031. if( $sync->{ delete2 } and not exists $h2_folders_from_1_several{ $h2_fold } ) {
  2032. # No host1 folders f1a f1b ... going all to same f2 (via --regextrans2)
  2033. my @h2_expunge;
  2034. foreach my $m_id (@h2_hash_keys_sorted_by_uid) {
  2035. #myprint( "$m_id " ) ;
  2036. if ( ! exists $h1_hash{$m_id} ) {
  2037. my $h2_msg = $h2_hash{$m_id}{'m'};
  2038. my $h2_flags = $h2_hash{$m_id}{'F'} || q{};
  2039. my $isdel = $h2_flags =~ /\B\\Deleted\b/x ? 1 : 0;
  2040. myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted on host2 [$m_id] $sync->{dry_message}\n" )
  2041. if ! $isdel;
  2042. push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 };
  2043. if ( ! ( $sync->{ dry } or $isdel ) ) {
  2044. $sync->{ imap2 }->delete_message( $h2_msg );
  2045. $sync->{ acc2 }->{ nb_msg_deleted } += 1;
  2046. }
  2047. }
  2048. }
  2049. foreach my $h2_msg ( @h2_msgs_delete2_not_in_cache ) {
  2050. myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted [not in cache] on host2 $sync->{dry_message}\n" ) ;
  2051. push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 };
  2052. if ( ! $sync->{dry} ) {
  2053. $sync->{ imap2 }->delete_message( $h2_msg );
  2054. $sync->{ acc2 }->{ nb_msg_deleted } += 1;
  2055. }
  2056. }
  2057. my $cnt = scalar @h2_expunge ;
  2058. if( @h2_expunge and not $sync->{ expunge2 } ) {
  2059. myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n" ) ;
  2060. $sync->{imap2}->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ;
  2061. }
  2062. if ( $sync->{ expunge2 } ) {
  2063. myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n" ) ;
  2064. $sync->{imap2}->expunge( ) if ! $sync->{dry} ;
  2065. }
  2066. }
  2067. if( $sync->{ delete2 } and exists $h2_folders_from_1_several{ $h2_fold } ) {
  2068. myprint( "Host2: folder $h2_fold $h2_folders_from_1_several{ $h2_fold } folders left to sync there\n" ) ;
  2069. my @h2_expunge;
  2070. foreach my $m_id ( @h2_hash_keys_sorted_by_uid ) {
  2071. my $h2_msg = $h2_hash{ $m_id }{ 'm' } ;
  2072. if ( ! exists $h1_hash{ $m_id } ) {
  2073. my $h2_flags = $h2_hash{ $m_id }{ 'F' } || q{} ;
  2074. my $isdel = $h2_flags =~ /\B\\Deleted\b/x ? 1 : 0 ;
  2075. if ( ! $isdel ) {
  2076. $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion [$m_id]\n" ) ;
  2077. $uid_candidate_for_deletion{ $h2_fold }{ $h2_msg }++ ;
  2078. }
  2079. }else{
  2080. $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [$m_id]\n" ) ;
  2081. $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
  2082. }
  2083. }
  2084. foreach my $h2_msg ( @h2_msgs_delete2_not_in_cache ) {
  2085. myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion [not in cache]\n" ) ;
  2086. $uid_candidate_for_deletion{ $h2_fold }{ $h2_msg }++ ;
  2087. }
  2088. foreach my $h2_msg ( @h2_msgs_in_cache ) {
  2089. myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [in cache]\n" ) ;
  2090. $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
  2091. }
  2092. if ( 0 == $h2_folders_from_1_several{ $h2_fold } ) {
  2093. # last host1 folder going to $h2_fold
  2094. myprint( "Last host1 folder going to $h2_fold\n" ) ;
  2095. foreach my $h2_msg ( keys %{ $uid_candidate_for_deletion{ $h2_fold } } ) {
  2096. $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion\n" ) ;
  2097. if ( exists $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg } ) {
  2098. $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg canceled deletion\n" ) ;
  2099. }else{
  2100. myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted $sync->{dry_message}\n" ) ;
  2101. push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 } ;
  2102. if ( ! $sync->{ dry} ) {
  2103. $sync->{ imap2 }->delete_message( $h2_msg ) ;
  2104. $sync->{ acc2 }->{ nb_msg_deleted } += 1 ;
  2105. }
  2106. }
  2107. }
  2108. }
  2109. my $cnt = scalar @h2_expunge ;
  2110. if( @h2_expunge and not $sync->{ expunge2 } ) {
  2111. myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n" ) ;
  2112. $sync->{imap2}->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ;
  2113. }
  2114. if ( $sync->{ expunge2 } ) {
  2115. myprint( "Host2: Expunging host2 folder $h2_fold $sync->{dry_message}\n" ) ;
  2116. $sync->{imap2}->expunge( ) if ! $sync->{dry} ;
  2117. }
  2118. $h2_folders_from_1_several{ $h2_fold }-- ;
  2119. }
  2120. my $h2_uidnext = $sync->{imap2}->uidnext( $h2_fold ) ;
  2121. $sync->{ debug } and myprint( "Host2: uidnext is $h2_uidnext\n" ) ;
  2122. $h2_uidguess = $h2_uidnext ;
  2123. # Getting host2 headers, metada and delete2 stuff can be so long that host1 might be disconnected here
  2124. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  2125. my @h1_msgs_to_delete ;
  2126. MESS: foreach my $m_id (@h1_hash_keys_sorted_by_uid) {
  2127. abortifneeded( $sync ) ;
  2128. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  2129. #myprint( "h1_nb_msg_processed: $sync->{ h1_nb_msg_processed }\n" ) ;
  2130. my $h1_size = $h1_hash{$m_id}{'s'};
  2131. my $h1_msg = $h1_hash{$m_id}{'m'};
  2132. my $h1_idate = $h1_hash{$m_id}{'D'};
  2133. #my $labels = labels( $sync->{imap1}, $h1_msg ) ;
  2134. #print "LABELS: $labels\n" ;
  2135. if ( ( not exists $h2_hash{ $m_id } )
  2136. and ( not ( exists $sync->{ h2_folders_of_md5 }->{ $m_id } )
  2137. or not $skipcrossduplicates ) )
  2138. {
  2139. # copy
  2140. my $h2_msg = copy_message( $sync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) ;
  2141. if ( $h2_msg and $sync->{ delete1 } and not $sync->{ expungeaftereach } ) {
  2142. # not expunged
  2143. push @h1_msgs_to_delete, $h1_msg ;
  2144. }
  2145. # A bug here with imapsync 1.920, fixed in 1.921
  2146. # Added $h2_msg in the condition. Errors of APPEND were not counted as missing messages on host2!
  2147. if ( $h2_msg and not $sync->{ dry } )
  2148. {
  2149. $sync->{ h2_folders_of_md5 }->{ $m_id }->{ $h2_fold } ++ ;
  2150. }
  2151. #
  2152. if( $sync->{ delete2 } and ( exists $h2_folders_from_1_several{ $h2_fold } ) and $h2_msg ) {
  2153. myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [fresh copy] on host2\n" ) ;
  2154. $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
  2155. }
  2156. if ( total_bytes_max_reached( $sync ) ) {
  2157. # Still a bug when using --delete1 --noexpungeaftereach
  2158. # same thing below on all total_bytes_max_reached!
  2159. last FOLDER ;
  2160. }
  2161. next MESS;
  2162. }
  2163. else
  2164. {
  2165. # already on host2
  2166. if ( exists $h2_hash{ $m_id } )
  2167. {
  2168. my $h2_msg = $h2_hash{$m_id}{'m'} ;
  2169. $sync->{ debug } and myprint( "Host1: found that msg $h1_fold/$h1_msg equals Host2 $h2_fold/$h2_msg\n" ) ;
  2170. if ( $usecache )
  2171. {
  2172. $debugcache and myprint( "touch $cache_dir/${h1_msg}_$h2_msg\n" ) ;
  2173. touch( "$cache_dir/${h1_msg}_$h2_msg" )
  2174. or croak( "Couldn't touch $cache_dir/${h1_msg}_$h2_msg" ) ;
  2175. }
  2176. }
  2177. elsif( exists $sync->{ h2_folders_of_md5 }->{ $m_id } )
  2178. {
  2179. my @folders_dup = keys %{ $sync->{ h2_folders_of_md5 }->{ $m_id } } ;
  2180. ( $sync->{ debug } or $debugcrossduplicates ) and myprint( "Host1: found that msg $h1_fold/$h1_msg is also in Host2 folders @folders_dup\n" ) ;
  2181. $sync->{ h2_nb_msg_crossdup } +=1 ;
  2182. }
  2183. $sync->{ total_bytes_skipped } += $h1_size ;
  2184. $sync->{ nb_msg_skipped } += 1 ;
  2185. $sync->{ h1_nb_msg_processed } +=1 ;
  2186. }
  2187. if ( exists $h2_hash{ $m_id } ) {
  2188. #$debug and myprint( "MESSAGE $m_id\n" ) ;
  2189. my $h2_msg = $h2_hash{$m_id}{'m'};
  2190. if ( $sync->{resyncflags} ) {
  2191. sync_flags_fir( $sync, $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) ;
  2192. }
  2193. # Good
  2194. my $h2_size = $h2_hash{$m_id}{'s'};
  2195. $sync->{ debug } and myprint(
  2196. "Host1: size msg $h1_fold/$h1_msg = $h1_size <> $h2_size = Host2 $h2_fold/$h2_msg\n" ) ;
  2197. if ( $sync->{ resynclabels } )
  2198. {
  2199. resynclabels( $sync, $h1_msg, $h2_msg, $h1_fir_ref, $h2_fir_ref, $h1_fold )
  2200. }
  2201. }
  2202. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  2203. if ( $sync->{ delete1 } ) {
  2204. push @h1_msgs_to_delete, $h1_msg ;
  2205. }
  2206. }
  2207. # END MESS: loop
  2208. # @h1_msgs_in_cache are already synced too.
  2209. delete_message_on_host1( $sync, $h1_fold, $sync->{ expunge1 }, @h1_msgs_to_delete, @h1_msgs_in_cache ) ;
  2210. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  2211. # MESS_IN_CACHE:
  2212. if ( ! $sync->{ delete1 } )
  2213. {
  2214. foreach my $h1_msg ( @h1_msgs_in_cache )
  2215. {
  2216. my $h2_msg = $cache_1_2_ref->{ $h1_msg } ;
  2217. $debugcache and myprint( "cache messages update flags $h1_msg->$h2_msg\n" ) ;
  2218. if ( $sync->{resyncflags} )
  2219. {
  2220. sync_flags_fir( $sync, $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) ;
  2221. }
  2222. my $h1_size = $h1_fir_ref->{ $h1_msg }->{ 'RFC822.SIZE' } || 0 ;
  2223. $sync->{ total_bytes_skipped } += $h1_size;
  2224. $sync->{ nb_msg_skipped } += 1;
  2225. $sync->{ h1_nb_msg_processed } +=1 ;
  2226. }
  2227. }
  2228. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  2229. @h1_msgs_to_delete = ( ) ;
  2230. #myprint( "Messages by uid: ", map { "$_ " } keys %h1_msgs_copy_by_uid, "\n" ) ;
  2231. # MESS_BY_UID:
  2232. foreach my $h1_msg ( sort { $a <=> $b } keys %h1_msgs_copy_by_uid )
  2233. {
  2234. abortifneeded( $sync ) ;
  2235. $sync->{ debug } and myprint( "Copy by uid $h1_fold/$h1_msg\n" ) ;
  2236. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  2237. my $h2_msg = copy_message( $sync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) ;
  2238. if( $sync->{ delete2 } and exists $h2_folders_from_1_several{ $h2_fold } and $h2_msg ) {
  2239. myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [fresh copy] on host2\n" ) ;
  2240. $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
  2241. }
  2242. last FOLDER if total_bytes_max_reached( $sync ) ;
  2243. }
  2244. if ( $sync->{ expunge1 } ){
  2245. myprint( "Host1: Expunging folder $h1_fold $sync->{dry_message}\n" ) ;
  2246. if ( ! $sync->{dry} ) { $sync->{imap1}->expunge( ) } ;
  2247. }
  2248. if ( $sync->{ expunge2 } ){
  2249. myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n" ) ;
  2250. if ( ! $sync->{dry} ) { $sync->{imap2}->expunge( ) } ;
  2251. }
  2252. $sync->{ debug } and myprint( 'Time: ', timenext( $sync ), " s\n" ) ;
  2253. }
  2254. eta_print( $sync ) ;
  2255. myprint( "++++ End looping on each folder\n" ) ;
  2256. if ( $sync->{ delete1 } and $sync->{ delete1emptyfolders } ) {
  2257. delete1emptyfolders( $sync ) ;
  2258. }
  2259. ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( 'Time: ', timenext( $sync ), " s\n" ) ;
  2260. if ( $sync->{ foldersizesatend } ) {
  2261. myprint( << 'END_SIZE' ) ;
  2262. Folders sizes after the synchronization.
  2263. You can remove this foldersizes listing by using "--nofoldersizesatend"
  2264. END_SIZE
  2265. foldersizesatend( $sync ) ;
  2266. }
  2267. #$sync->{imap1}->State( 0 ); # Unconnected
  2268. if ( ! lost_connection( $sync, $sync->{imap1}, "for host1 [$sync->{host1}]" ) ) { $sync->{imap1}->logout( ) ; }
  2269. if ( ! lost_connection( $sync, $sync->{imap2}, "for host2 [$sync->{host2}]" ) ) { $sync->{imap2}->logout( ) ; }
  2270. do_and_print_stats( $sync ) ;
  2271. if ( $sync->{ nb_errors } )
  2272. {
  2273. myprint( errors_listing( $sync ) ) ;
  2274. }
  2275. if ( $sync->{ testslive } or $sync->{ testslive6 } )
  2276. {
  2277. tests_live_result( $sync->{ nb_errors } ) ;
  2278. }
  2279. if ( $sync->{ nb_errors } )
  2280. {
  2281. my $exit_value = exit_value( $sync, $sync->{ most_common_error } ) ;
  2282. exit_clean( $sync, $exit_value ) ;
  2283. }
  2284. else
  2285. {
  2286. exit_clean( $sync, $EX_OK ) ;
  2287. }
  2288. return ;
  2289. }
  2290. # END of sub single_sync
  2291. # subroutines
  2292. sub myprint
  2293. {
  2294. #print @ARG ;
  2295. print { $sync->{ tee } || \*STDOUT } @ARG ;
  2296. return ;
  2297. }
  2298. sub myprintf
  2299. {
  2300. printf { $sync->{ tee } || \*STDOUT } @ARG ;
  2301. return ;
  2302. }
  2303. sub mysprintf
  2304. {
  2305. my( $format, @list ) = @ARG ;
  2306. return sprintf $format, @list ;
  2307. }
  2308. sub output_start
  2309. {
  2310. my $mysync = shift @ARG ;
  2311. if ( not $mysync ) { return ; }
  2312. my @output = @ARG ;
  2313. $mysync->{ output } = join( q{}, @output ) . ( $mysync->{ output } || q{} ) ;
  2314. return $mysync->{ output } ;
  2315. }
  2316. sub tests_output_start
  2317. {
  2318. note( 'Entering tests_output_start()' ) ;
  2319. my $mysync = { } ;
  2320. is( undef, output_start( ), 'output_start: no args => undef' ) ;
  2321. is( q{}, output_start( $mysync ), 'output_start: one arg => ""' ) ;
  2322. is( 'rrrr', output_start( $mysync, 'rrrr' ), 'output_start: rrrr => rrrr' ) ;
  2323. is( 'aaaarrrr', output_start( $mysync, 'aaaa' ), 'output_start: aaaa => aaaarrrr' ) ;
  2324. is( "\naaaarrrr", output_start( $mysync, "\n" ), 'output_start: \n => \naaaarrrr' ) ;
  2325. is( "ABC\naaaarrrr", output_start( $mysync, 'A', 'B', 'C' ), 'output_start: A B C => ABC\naaaarrrr' ) ;
  2326. note( 'Leaving tests_output_start()' ) ;
  2327. return ;
  2328. }
  2329. sub tests_output
  2330. {
  2331. note( 'Entering tests_output()' ) ;
  2332. my $mysync = { } ;
  2333. is( undef, output( ), 'output: no args => undef' ) ;
  2334. is( q{}, output( $mysync ), 'output: one arg => ""' ) ;
  2335. is( 'rrrr', output( $mysync, 'rrrr' ), 'output: rrrr => rrrr' ) ;
  2336. is( 'rrrraaaa', output( $mysync, 'aaaa' ), 'output: aaaa => rrrraaaa' ) ;
  2337. is( "rrrraaaa\n", output( $mysync, "\n" ), 'output: \n => rrrraaaa\n' ) ;
  2338. is( "rrrraaaa\nABC", output( $mysync, 'A', 'B', 'C' ), 'output: A B C => rrrraaaaABC\n' ) ;
  2339. note( 'Leaving tests_output()' ) ;
  2340. return ;
  2341. }
  2342. sub output
  2343. {
  2344. my $mysync = shift @ARG ;
  2345. if ( not $mysync ) { return ; }
  2346. my @output = @ARG ;
  2347. $mysync->{ output } .= join( q{}, @output ) ;
  2348. return $mysync->{ output } ;
  2349. }
  2350. sub tests_output_reset_with
  2351. {
  2352. note( 'Entering tests_output_reset_with()' ) ;
  2353. my $mysync = { } ;
  2354. is( undef, output_reset_with( ), 'output_reset_with: no args => undef' ) ;
  2355. is( q{}, output_reset_with( $mysync ), 'output_reset_with: one arg => ""' ) ;
  2356. is( 'rrrr', output_reset_with( $mysync, 'rrrr' ), 'output_reset_with: rrrr => rrrr' ) ;
  2357. is( 'aaaa', output_reset_with( $mysync, 'aaaa' ), 'output_reset_with: aaaa => aaaa' ) ;
  2358. is( "\n", output_reset_with( $mysync, "\n" ), 'output_reset_with: \n => \n' ) ;
  2359. note( 'Leaving tests_output_reset_with()' ) ;
  2360. return ;
  2361. }
  2362. sub output_reset_with
  2363. {
  2364. my $mysync = shift @ARG ;
  2365. if ( not $mysync ) { return ; }
  2366. my @output = @ARG ;
  2367. $mysync->{ output } = join( q{}, @output ) ;
  2368. return $mysync->{ output } ;
  2369. }
  2370. sub tests_print_output_if_needed
  2371. {
  2372. note( 'Entering tests_print_output_if_needed()' ) ;
  2373. is( undef, print_output_if_needed( ), 'print_output_if_needed: no args => undef' ) ;
  2374. my $mysync = { } ;
  2375. is( q{}, print_output_if_needed( $mysync ), 'print_output_if_needed: undef => undef' ) ;
  2376. output( $mysync, "Hello\n" ) ;
  2377. is( "Hello\n", print_output_if_needed( $mysync ), 'print_output_if_needed: Hello => Hello' ) ;
  2378. $mysync->{ dockercontext } = 1 ;
  2379. is( "Hello\n", print_output_if_needed( $mysync ), 'print_output_if_needed: dockercontext + Hello => Hello' ) ;
  2380. $mysync->{ version } = 1 ;
  2381. is( q{}, print_output_if_needed( $mysync ), 'print_output_if_needed: dockercontext + Hello + --version => ""' ) ;
  2382. $mysync->{ dockercontext } = 0 ;
  2383. is( "Hello\n", print_output_if_needed( $mysync ), 'print_output_if_needed: Hello + --version => Hello' ) ;
  2384. note( 'Leaving tests_print_output_if_needed()' ) ;
  2385. return ;
  2386. }
  2387. sub print_output_if_needed
  2388. {
  2389. my $mysync = shift @ARG ;
  2390. if ( ! defined $mysync ) { return ; }
  2391. my $output = output( $mysync ) ;
  2392. if ( $mysync->{ version } && under_docker_context( $mysync ) )
  2393. {
  2394. return q{} ;
  2395. }
  2396. else
  2397. {
  2398. myprint( $output ) ;
  2399. return $output ;
  2400. }
  2401. }
  2402. sub define_pidfile
  2403. {
  2404. my $mysync = shift @ARG ;
  2405. $mysync->{ pidfilelocking } = defined $mysync->{ pidfilelocking } ? $mysync->{ pidfilelocking } : 0 ;
  2406. my $host1 = $mysync->{ host1 } || q{} ;
  2407. my $user1 = $mysync->{ user1 } || q{} ;
  2408. my $host2 = $mysync->{ host2 } || q{} ;
  2409. my $user2 = $mysync->{ user2 } || q{} ;
  2410. my $account1_filtered = filter_forbidden_characters( slash_to_underscore( $host1 . '_' . $user1 ) ) || q{} ;
  2411. my $account2_filtered = filter_forbidden_characters( slash_to_underscore( $host2 . '_' . $user2 ) ) || q{} ;
  2412. my $pidfile_basename ;
  2413. if ( $ENV{ 'NET_SERVER_SOFTWARE' } and ( $ENV{ 'NET_SERVER_SOFTWARE' } =~ /Net::Server::HTTP/ ) )
  2414. {
  2415. # under local webserver
  2416. $pidfile_basename = 'imapsync' . '_' . $account1_filtered . '_' . $account2_filtered . '.pid' ;
  2417. }
  2418. else
  2419. {
  2420. $pidfile_basename = 'imapsync.pid' ;
  2421. }
  2422. $mysync->{ pidfile } = defined $mysync->{ pidfile } ? $mysync-> { pidfile } : $mysync->{ tmpdir } . "/$pidfile_basename" ;
  2423. $mysync->{ abortfile } = abortfile( $mysync, $PROCESS_ID ) ;
  2424. return ;
  2425. }
  2426. sub abortfile
  2427. {
  2428. my $mysync = shift @ARG ;
  2429. my $pid = shift @ARG ;
  2430. my $abortfile ;
  2431. if ( $mysync->{ abort } )
  2432. {
  2433. $abortfile = $mysync->{ pidfile } . "abort$pid" ;
  2434. }
  2435. else
  2436. {
  2437. $abortfile = $mysync->{ pidfile } . "abort$PROCESS_ID" ;
  2438. }
  2439. return $abortfile ;
  2440. }
  2441. sub tests_kill_zero
  2442. {
  2443. note( 'Entering tests_kill_zero()' ) ;
  2444. SKIP: {
  2445. if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests tests_kill_zero avoided on Windows', 8 ) ; }
  2446. is( 1, kill( 'ZERO', $PROCESS_ID ), "kill ZERO : myself $PROCESS_ID => 1" ) ;
  2447. is( 2, kill( 'ZERO', $PROCESS_ID, $PROCESS_ID ), "kill ZERO : myself $PROCESS_ID $PROCESS_ID => 2" ) ;
  2448. if ( (-e '/.dockerenv' ) or ( 0 == $EFFECTIVE_USER_ID) )
  2449. {
  2450. is( 1, kill( 'ZERO', 1 ), "kill ZERO : pid 1 => 1 (docker context or root)" ) ;
  2451. is( 2, kill( 'ZERO', $PROCESS_ID, 1 ), "kill ZERO : myself + pid 1, $PROCESS_ID 1 => 2 (docker context or root)" ) ;
  2452. }
  2453. else
  2454. {
  2455. is( 0, kill( 'ZERO', 1 ), "kill ZERO : pid 1 => 0 (non root)" ) ;
  2456. is( 1, kill( 'ZERO', $PROCESS_ID, 1 ), "kill ZERO : myself + pid 1, $PROCESS_ID 1 => 1 (one is non root)" ) ;
  2457. }
  2458. my $pid_1 = fork( ) ;
  2459. if ( $pid_1 )
  2460. {
  2461. # parent
  2462. }
  2463. else
  2464. {
  2465. # child
  2466. sleep 3 ;
  2467. exit ;
  2468. }
  2469. my $pid_2 ;
  2470. $pid_2 = fork( ) ;
  2471. if ( $pid_2 )
  2472. {
  2473. # I am the parent
  2474. ok( defined( $pid_2 ), "kill_zero: initial fork ok. I am the parent $PROCESS_ID" ) ;
  2475. ok( $pid_2 , "kill_zero: initial fork ok, child pid is $pid_2" ) ;
  2476. is( 3, kill( 'ZERO', $PROCESS_ID, $pid_2, $pid_1 ), "kill ZERO : myself $PROCESS_ID and child $pid_2 and brother $pid_1 => 3" ) ;
  2477. is( $pid_2, waitpid( $pid_2, 0 ), "kill_zero: child $pid_2 no more there => waitpid return $pid_2" ) ;
  2478. }
  2479. else
  2480. {
  2481. # I am the child
  2482. note( 'This one fails under Windows, kill ZERO returns 0 instead of 2' ) ;
  2483. is( 2, kill( 'ZERO', $PROCESS_ID, $pid_1 ), "kill ZERO : myself child $PROCESS_ID brother $pid_1 => 2" ) ;
  2484. myprint( "I am the child pid $PROCESS_ID, Exiting\n" ) ;
  2485. exit ;
  2486. }
  2487. wait( ) ;
  2488. # End of SKIP block
  2489. }
  2490. note( 'Leaving tests_kill_zero()' ) ;
  2491. return ;
  2492. }
  2493. sub tests_killpid_by_parent
  2494. {
  2495. note( 'Entering tests_killpid_by_parent()' ) ;
  2496. SKIP: {
  2497. if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests tests_killpid_by_parent avoided on Windows', 7 ) ; }
  2498. is( undef, killpid( ), 'killpid: no args => undef' ) ;
  2499. note( "killpid: trying to kill myself pid $PROCESS_ID, hope I will not succeed" ) ;
  2500. is( undef, killpid( $PROCESS_ID ), 'killpid: myself => undef' ) ;
  2501. local $SIG{'QUIT'} = sub { myprint "GOT SIG QUIT! I am PID $PROCESS_ID. Exiting\n" ; exit ; } ;
  2502. my $pid ;
  2503. $pid = fork( ) ;
  2504. if ( $pid )
  2505. {
  2506. # I am the parent
  2507. ok( defined( $pid ), "killpid: initial fork ok. I am the parent $PROCESS_ID" ) ;
  2508. ok( $pid , "killpid: initial fork ok, child pid is $pid" ) ;
  2509. is( 2, kill( 'ZERO', $PROCESS_ID, $pid ), "kill ZERO : myself $PROCESS_ID and child $pid => 2" ) ;
  2510. is( 1, killpid( $pid ), "killpid: child $pid killed => 1" ) ;
  2511. is( -1, waitpid( $pid, 0 ), "killpid: child $pid no more there => waitpid return -1" ) ;
  2512. }
  2513. else
  2514. {
  2515. # I am the child
  2516. myprint( "I am the child pid $PROCESS_ID, sleeping 1 + 3 seconds then kill myself\n" ) ;
  2517. sleep 1 ;
  2518. myprint( "I am the child pid $PROCESS_ID, slept 1 second, should be killed by my parent now, PPID " . mygetppid( ) . "\n" ) ;
  2519. sleep 3 ;
  2520. # this test should not be run. If it happens => failure.
  2521. ok( 0 == 1, "killpid: child pid $PROCESS_ID not dead => failure" ) ;
  2522. myprint( "I am the child pid $PROCESS_ID, killing myself failure... Exiting\n" ) ;
  2523. exit ;
  2524. }
  2525. # End of SKIP block
  2526. }
  2527. note( 'Leaving tests_killpid_by_parent()' ) ;
  2528. return ;
  2529. }
  2530. sub tests_killpid_by_brother
  2531. {
  2532. note( 'Entering tests_killpid_by_brother()' ) ;
  2533. SKIP: {
  2534. if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests tests_killpid_by_brother avoided on Windows', 2 ) ; }
  2535. local $SIG{'QUIT'} = sub { myprint "GOT SIG QUIT! I am PID $PROCESS_ID. Exiting\n" ; exit ; } ;
  2536. my $pid_parent = $PROCESS_ID ;
  2537. myprint( "I am the parent pid $pid_parent\n" ) ;
  2538. my $pid_1 = fork( ) ;
  2539. if ( $pid_1 )
  2540. {
  2541. # parent
  2542. }
  2543. else
  2544. {
  2545. # child
  2546. #while ( 1 ) { } ;
  2547. sleep 2 ;
  2548. sleep 2 ;
  2549. # this test should not be run. If it happens => failure.
  2550. # Well under Windows this always fails, shit!
  2551. ok( 0 == 1 or ( 'MSWin32' eq $OSNAME ) , "killpid: child pid $PROCESS_ID killing by brother but not dead => failure" ) ;
  2552. myprint( "I am the child pid $PROCESS_ID, killing by brother failed... Exiting\n" ) ;
  2553. exit ;
  2554. }
  2555. my $pid_2 ;
  2556. $pid_2 = fork( ) ;
  2557. if ( $pid_2 )
  2558. {
  2559. # parent
  2560. }
  2561. else
  2562. {
  2563. # I am the child
  2564. myprint( "I am the child pid $PROCESS_ID, my brother has pid $pid_1\n" ) ;
  2565. is( 1, killpid( $pid_1 ), "killpid: brother $pid_1 killed => 1" ) ;
  2566. sleep 2 ;
  2567. exit ;
  2568. }
  2569. #sleep 1 ;
  2570. is( $pid_1, waitpid( $pid_1, 0), "I am the parent $PROCESS_ID waitpid _1( $pid_1 )" ) ;
  2571. is( $pid_2, waitpid( $pid_2, 0 ), "I am the parent $PROCESS_ID waitpid _2( $pid_2 )" ) ;
  2572. # End of SKIP block
  2573. }
  2574. note( 'Leaving tests_killpid_by_brother()' ) ;
  2575. return ;
  2576. }
  2577. sub killpid
  2578. {
  2579. my $pidtokill = shift ;
  2580. if ( ! $pidtokill ) {
  2581. myprint( "No process to kill.\n" ) ;
  2582. return ;
  2583. }
  2584. if ( $PROCESS_ID == $pidtokill ) {
  2585. myprint( "I will not kill myself pid $PROCESS_ID via killpid. Sractch it!\n" ) ;
  2586. return ;
  2587. }
  2588. # First ask for suicide
  2589. if ( kill( 'ZERO', $pidtokill ) or ( 'MSWin32' eq $OSNAME ) ) {
  2590. myprint( "Sending signal QUIT to PID $pidtokill \n" ) ;
  2591. kill 'QUIT', $pidtokill ;
  2592. sleep 3 ;
  2593. waitpid( $pidtokill, WNOHANG) ;
  2594. }else{
  2595. myprint( "Can not send signal kill ZERO to PID $pidtokill.\n" ) ;
  2596. return ;
  2597. }
  2598. #while ( waitpid( $pidtokill, WNOHANG) > 0 ) { } ;
  2599. # Then murder
  2600. if ( kill( 'ZERO', $pidtokill ) or ( 'MSWin32' eq $OSNAME ) ) {
  2601. myprint( "Sending signal KILL to PID $pidtokill \n" ) ;
  2602. kill 'KILL', $pidtokill ;
  2603. sleep 1 ;
  2604. waitpid( $pidtokill, WNOHANG) ;
  2605. }else{
  2606. myprint( "Process PID $pidtokill ended.\n" ) ;
  2607. return 1;
  2608. }
  2609. # Well ...
  2610. if ( kill( 'ZERO', $pidtokill ) or ( 'xMSWin32' eq $OSNAME ) ) {
  2611. myprint( "Process PID $pidtokill seems still there. Can not do much.\n" ) ;
  2612. return ;
  2613. }else{
  2614. myprint( "Process PID $pidtokill ended.\n" ) ;
  2615. return 1;
  2616. }
  2617. return ;
  2618. }
  2619. sub tests_abort
  2620. {
  2621. note( 'Entering tests_abort()' ) ;
  2622. # Well, the abort behavior is tested by test.sh
  2623. is( undef, abort( ), 'abort: no args => undef' ) ;
  2624. note( 'Leaving tests_abort()' ) ;
  2625. return ;
  2626. }
  2627. sub abort
  2628. {
  2629. my $mysync = shift @ARG ;
  2630. myprint( "In abort\n" ) ;
  2631. if ( not $mysync ) { return ; }
  2632. if ( ! -r $mysync->{pidfile} ) {
  2633. myprint( "In abort: Can not read pidfile $mysync->{pidfile}\n" ) ;
  2634. return ;
  2635. }
  2636. my $pidtokill = firstline( $mysync->{pidfile} ) ;
  2637. if ( ! $pidtokill ) {
  2638. myprint( "In abort: No process to abort in $mysync->{pidfile}\n" ) ;
  2639. return ;
  2640. }
  2641. if ( ! match_a_pid_number( $pidtokill ) )
  2642. {
  2643. myprint( "In abort: pid $pidtokill in $mysync->{pidfile} is not a pid number\n" ) ;
  2644. return ;
  2645. }
  2646. if ( $mysync->{abortbyfile} )
  2647. {
  2648. abortbyfile( $mysync, $pidtokill ) ;
  2649. }
  2650. else
  2651. {
  2652. killpid( $pidtokill ) ;
  2653. }
  2654. return ;
  2655. }
  2656. sub abortbyfile
  2657. {
  2658. my $mysync = shift @ARG ;
  2659. my $pidtokill = shift @ARG ;
  2660. my $abortfile = abortfile( $mysync, $pidtokill ) ;
  2661. myprint( "touching $abortfile\n" ) ;
  2662. touch( $abortfile ) ;
  2663. return ;
  2664. }
  2665. sub tests_under_docker_context
  2666. {
  2667. note( 'Entering tests_under_docker_context()' ) ;
  2668. is( undef, under_docker_context( ), 'under_docker_context: no args => undef' ) ;
  2669. my $mysync = { } ;
  2670. $mysync->{ dockercontext } = 1 ;
  2671. is( 1, under_docker_context( $mysync ), 'under_docker_context: --dockercontext => 1' ) ;
  2672. $mysync->{ dockercontext } = 0 ;
  2673. is( 0, under_docker_context( $mysync ), 'under_docker_context: --nodockercontext => 0' ) ;
  2674. $mysync = { } ;
  2675. # Is not it a stupid test?
  2676. if ( under_docker_context( $mysync ) )
  2677. {
  2678. is( 1, under_docker_context( $mysync ), 'under_docker_context: docker context => 1' ) ;
  2679. }
  2680. else
  2681. {
  2682. is( 0, under_docker_context( $mysync ), 'under_docker_context: not docker context => 0' ) ;
  2683. }
  2684. note( 'Leaving tests_under_docker_context()' ) ;
  2685. return ;
  2686. }
  2687. sub under_docker_context
  2688. {
  2689. my $mysync = shift ;
  2690. if ( ! defined $mysync ) { return ; }
  2691. if ( defined $mysync->{ dockercontext } )
  2692. {
  2693. return( $mysync->{ dockercontext } ) ;
  2694. }
  2695. if ( -e '/.dockerenv' )
  2696. {
  2697. return 1 ;
  2698. }
  2699. else
  2700. {
  2701. return 0 ;
  2702. }
  2703. return ;
  2704. }
  2705. sub docker_context
  2706. {
  2707. my $mysync = shift ;
  2708. if ( ! under_docker_context( $mysync ) )
  2709. {
  2710. return ;
  2711. }
  2712. output( $mysync, "Docker context detected with the file /.dockerenv\n" ) ;
  2713. # No pidfile by default
  2714. $mysync->{ pidfile } = defined( $mysync->{ pidfile } ) ? $mysync->{ pidfile } : q{} ;
  2715. # No log by default
  2716. if ( defined( $mysync->{ log } ) )
  2717. {
  2718. output( $mysync, "Logging in Docker context. Be sure you added access to it with a mount or similar. See https://docs.docker.com/storage/volumes/\n" ) ;
  2719. }
  2720. else
  2721. {
  2722. output( $mysync, "No log by default in Docker context. Use --log to trigger logging to the logfile.\n" ) ;
  2723. $mysync->{ log } = 0 ;
  2724. }
  2725. # In case something is written relatively to .
  2726. my $tmp_dir = "/var/tmp/uid_$EFFECTIVE_USER_ID" ;
  2727. mkpath( $tmp_dir ) ; # silly? No. it is for imapsync --version being ok.
  2728. do_valid_directory( $tmp_dir ) ;
  2729. output( $mysync, "Changing current directory to $tmp_dir\n" ) ;
  2730. chdir $tmp_dir ;
  2731. return ;
  2732. }
  2733. sub cgibegin
  2734. {
  2735. my $mysync = shift ;
  2736. if ( ! under_cgi_context( $mysync ) ) { return ; }
  2737. require CGI ;
  2738. CGI->import( qw( -no_debug -utf8 ) ) ;
  2739. require CGI::Carp ;
  2740. CGI::Carp->import( qw( fatalsToBrowser ) ) ;
  2741. $mysync->{cgi} = CGI->new( ) ;
  2742. return ;
  2743. }
  2744. sub tests_under_cgi_context
  2745. {
  2746. note( 'Entering tests_under_cgi_context()' ) ;
  2747. # $ENV{SERVER_SOFTWARE} = 'under imapsync' ;
  2748. do {
  2749. # Not in cgi context
  2750. delete local $ENV{SERVER_SOFTWARE} ;
  2751. is( undef, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE unset => not in cgi context' ) ;
  2752. } ;
  2753. do {
  2754. # In cgi context
  2755. local $ENV{SERVER_SOFTWARE} = 'under imapsync' ;
  2756. is( 1, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE set => in cgi context' ) ;
  2757. } ;
  2758. do {
  2759. # Not in cgi context
  2760. delete local $ENV{SERVER_SOFTWARE} ;
  2761. is( undef, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE unset => not in cgi context' ) ;
  2762. } ;
  2763. do {
  2764. # In cgi context
  2765. local $ENV{SERVER_SOFTWARE} = 'under imapsync' ;
  2766. is( 1, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE set => in cgi context' ) ;
  2767. } ;
  2768. note( 'Leaving tests_under_cgi_context()' ) ;
  2769. return ;
  2770. }
  2771. sub under_cgi_context
  2772. {
  2773. my $mysync = shift ;
  2774. # Under cgi context
  2775. if ( $ENV{SERVER_SOFTWARE} ) {
  2776. return 1 ;
  2777. }
  2778. # Not in cgi context
  2779. return ;
  2780. }
  2781. sub cgibuildheader
  2782. {
  2783. my $mysync = shift ;
  2784. if ( ! under_cgi_context( $mysync ) ) { return ; }
  2785. my $imapsync_runs = $mysync->{cgi}->cookie( 'imapsync_runs' ) || 0 ;
  2786. my $cookie = $mysync->{cgi}->cookie(
  2787. -name => 'imapsync_runs',
  2788. -value => 1 + $imapsync_runs,
  2789. -expires => '+20y',
  2790. -path => '/cgi-bin/imapsync',
  2791. ) ;
  2792. my $httpheader ;
  2793. if ( $mysync->{ abort } ) {
  2794. $httpheader = $mysync->{cgi}->header(
  2795. -type => 'text/plain; charset=UTF-8',
  2796. -status => '200 OK to abort syncing IMAP boxes' . ". Here is " . hostname(),
  2797. ) ;
  2798. }elsif( $mysync->{ loaddelay } ) {
  2799. # https://tools.ietf.org/html/rfc2616#section-10.5.4
  2800. # 503 Service Unavailable
  2801. # The server is currently unable to handle the request due to a temporary overloading or maintenance of the server.
  2802. $httpheader = $mysync->{cgi}->header(
  2803. -type => 'text/plain; charset=UTF-8',
  2804. -status => '503 Service Unavailable' . ". Be back in $mysync->{ loaddelay } min. Load on " . hostname() . " is $mysync->{ loadavg }",
  2805. ) ;
  2806. }else{
  2807. $httpheader = $mysync->{cgi}->header(
  2808. -type => 'text/plain; charset=UTF-8',
  2809. -status => '200 OK to sync IMAP boxes' . ". Load on " . hostname() . " is $mysync->{ loadavg }",
  2810. -cookie => $cookie,
  2811. ) ;
  2812. }
  2813. output_start( $mysync, $httpheader ) ;
  2814. return ;
  2815. }
  2816. sub cgiload
  2817. {
  2818. # Exit on heavy load in CGI context
  2819. my $mysync = shift ;
  2820. if ( ! under_cgi_context( $mysync ) ) { return ; }
  2821. if ( $mysync->{ abort } ) { return ; } # keep going to abort since some ressources will be free soon
  2822. if ( $mysync->{ loaddelay } )
  2823. {
  2824. $mysync->{nb_errors}++ ;
  2825. exit_clean( $mysync, $EX_UNAVAILABLE,
  2826. "Server is on heavy load. Be back in $mysync->{ loaddelay } min. Load is $mysync->{ loadavg }\n"
  2827. ) ;
  2828. }
  2829. return ;
  2830. }
  2831. sub tests_set_umask
  2832. {
  2833. note( 'Entering tests_set_umask()' ) ;
  2834. my $save_umask = umask ;
  2835. my $mysync = {} ;
  2836. if ( 'MSWin32' eq $OSNAME ) {
  2837. is( undef, set_umask( $mysync ), "set_umask: set failure to $UMASK_PARANO on MSWin32" ) ;
  2838. }else{
  2839. is( 1, set_umask( $mysync ), "set_umask: set to $UMASK_PARANO" ) ;
  2840. }
  2841. umask $save_umask ;
  2842. note( 'Leaving tests_set_umask()' ) ;
  2843. return ;
  2844. }
  2845. sub set_umask
  2846. {
  2847. my $mysync = shift ;
  2848. my $previous_umask = umask_str( ) ;
  2849. my $new_umask = umask_str( $UMASK_PARANO ) ;
  2850. output( $mysync, "Umask set with $new_umask (was $previous_umask)\n" ) ;
  2851. if ( $new_umask eq $UMASK_PARANO ) {
  2852. return 1 ;
  2853. }
  2854. return ;
  2855. }
  2856. sub tests_umask_str
  2857. {
  2858. note( 'Entering tests_umask_str()' ) ;
  2859. my $save_umask = umask ;
  2860. is( umask_str( ), umask_str( ), 'umask_str: no parameters => idopotent' ) ;
  2861. is( my $save_umask_str = umask_str( ), umask_str( ), 'umask_str: no parameters => idopotent + save' ) ;
  2862. is( '0000', umask_str( q{ } ), 'umask_str: q{ } => 0000' ) ;
  2863. is( '0000', umask_str( q{} ), 'umask_str: q{} => 0000' ) ;
  2864. is( '0000', umask_str( '0000' ), 'umask_str: 0000 => 0000' ) ;
  2865. is( '0000', umask_str( '0' ), 'umask_str: 0 => 0000' ) ;
  2866. is( '0200', umask_str( '0200' ), 'umask_str: 0200 => 0200' ) ;
  2867. is( '0400', umask_str( '0400' ), 'umask_str: 0400 => 0400' ) ;
  2868. is( '0600', umask_str( '0600' ), 'umask_str: 0600 => 0600' ) ;
  2869. SKIP: {
  2870. if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests success only for Unix', 6 ) ; }
  2871. is( '0100', umask_str( '0100' ), 'umask_str: 0100 => 0100' ) ;
  2872. is( '0001', umask_str( '0001' ), 'umask_str: 0001 => 0001' ) ;
  2873. is( '0777', umask_str( '0777' ), 'umask_str: 0777 => 0777' ) ;
  2874. is( '0777', umask_str( '00777' ), 'umask_str: 00777 => 0777' ) ;
  2875. is( '0777', umask_str( ' 777 ' ), 'umask_str: 777 => 0777' ) ;
  2876. is( "$UMASK_PARANO", umask_str( $UMASK_PARANO ), "umask_str: UMASK_PARANO $UMASK_PARANO => $UMASK_PARANO" ) ;
  2877. }
  2878. is( $save_umask_str, umask_str( $save_umask_str ), 'umask_str: restore with str' ) ;
  2879. is( $save_umask, umask, 'umask_str: umask is restored, controlled by direct umask' ) ;
  2880. is( $save_umask, umask $save_umask, 'umask_str: umask is restored by direct umask' ) ;
  2881. is( $save_umask, umask, 'umask_str: umask initial controlled by direct umask' ) ;
  2882. note( 'Leaving tests_umask_str()' ) ;
  2883. return ;
  2884. }
  2885. sub umask_str
  2886. {
  2887. my $value = shift ;
  2888. if ( defined $value ) {
  2889. umask oct( $value ) ;
  2890. }
  2891. my $current = umask ;
  2892. return( sprintf( '%#04o', $current ) ) ;
  2893. }
  2894. sub tests_umask
  2895. {
  2896. note( 'Entering tests_umask()' ) ;
  2897. my $save_umask ;
  2898. is( umask, umask, 'umask: umask is umask' ) ;
  2899. is( $save_umask = umask, umask, "umask: umask is umask again + save it: $save_umask" ) ;
  2900. is( $save_umask, umask oct(0000), 'umask: umask 0000' ) ;
  2901. is( oct(0000), umask, 'umask: umask is now 0000' ) ;
  2902. is( oct(0000), umask oct(777), 'umask: umask 0777 call, previous 0000' ) ;
  2903. SKIP: {
  2904. if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests success only for Unix', 2 ) ; }
  2905. is( oct(777), umask, 'umask: umask is now 0777' ) ;
  2906. is( oct(777), umask $save_umask, "umask: umask $save_umask restore inital value, previous 0777" ) ;
  2907. }
  2908. ok( defined umask $save_umask, "umask: umask $save_umask restore inital value, previous defined" ) ;
  2909. is( $save_umask, umask, 'umask: umask is umask restored' ) ;
  2910. note( 'Leaving tests_umask()' ) ;
  2911. return ;
  2912. }
  2913. sub buggyflagsregex
  2914. {
  2915. # From /X analyse
  2916. # cut -d: -f1 Error_112_all_syncs.txt | xargs egrep -oih 'Invalid system flag [^( ]+' | sort | uniq -c | sort -g
  2917. my @buggyflagsregex = ( 's/\\\\RECEIPTCHECKED|\\\\Indexed|\\\\X-EON-HAS-ATTACHMENT|\\\\UNSEEN|\\\\ATTACHED|\\\\X-HAS-ATTACH|\\\\FORWARDED|\\\\FORWARD|\\\\X-FORWARDED|\\\\\$FORWARDED|\\\\PRIORITY|\\\\READRCPT//g' ) ;
  2918. return( @buggyflagsregex ) ;
  2919. }
  2920. sub cgisetcontext
  2921. {
  2922. my $mysync = shift ;
  2923. if ( ! under_cgi_context( $mysync ) ) { return ; }
  2924. output( $mysync, "Under cgi context\n" ) ;
  2925. set_umask( $mysync ) ;
  2926. # Remove all content in unsafe evaled options
  2927. @{ $mysync->{ regextrans2 } } = ( ) ;
  2928. @{ $mysync->{ regexflag } } = buggyflagsregex( ) ;
  2929. @regexmess = ( ) ;
  2930. @skipmess = ( ) ;
  2931. @pipemess = ( ) ;
  2932. $delete2foldersonly = undef ;
  2933. $delete2foldersbutnot = undef ;
  2934. $maxlinelengthcmd = undef ;
  2935. # Set safe default values (I hope...)
  2936. #$mysync->{pidfile} = 'imapsync.pid' ;
  2937. $mysync->{ pidfilelocking } = 1 ;
  2938. $mysync->{ errorsmax } = $ERRORS_MAX_CGI ;
  2939. $modulesversion = 0 ;
  2940. $mysync->{ releasecheck } = defined $mysync->{ releasecheck } ? $mysync->{ releasecheck } : 1 ;
  2941. $usecache = 0 ;
  2942. $mysync->{ showpasswords } = 0 ;
  2943. $mysync->{ acc1 }->{ debugimap } = 0 ;
  2944. $mysync->{ acc2 }->{ debugimap } = 0 ;
  2945. $mysync->{ acc1 }->{ reconnectretry } = $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ;
  2946. $mysync->{ acc2 }->{ reconnectretry } = $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ;
  2947. $pipemesscheck = 0 ;
  2948. $mysync->{ hashfile } = $CGI_HASHFILE ;
  2949. my $hashsynclocal = hashsynclocal( $mysync ) || die "Can not get hashsynclocal. Exiting\n" ;
  2950. if ( $ENV{ 'NET_SERVER_SOFTWARE' } and ( $ENV{ 'NET_SERVER_SOFTWARE' } =~ /Net::Server::HTTP/ ) )
  2951. {
  2952. # under local webserver
  2953. $cgidir = q{.} ;
  2954. }
  2955. else
  2956. {
  2957. $cgidir = $CGI_TMPDIR_TOP . '/' . $hashsynclocal ;
  2958. }
  2959. -d $cgidir or mkpath $cgidir or die "Can not create $cgidir: $OS_ERROR\n" ;
  2960. $mysync->{ tmpdir } = $cgidir ;
  2961. $mysync->{ logdir } = '' ;
  2962. chdir $cgidir or die "Can not cd to $cgidir: $OS_ERROR\n" ;
  2963. cgioutputenvcontext( $mysync ) ;
  2964. $mysync->{ debug } and output( $mysync, 'Current directory is ' . getcwd( ) . "\n" ) ;
  2965. $mysync->{ debug } and output( $mysync, 'Real user id is ' . getpwuid_any_os( $REAL_USER_ID ) . " (uid $REAL_USER_ID)\n" ) ;
  2966. $mysync->{ debug } and output( $mysync, 'Effective user id is ' . getpwuid_any_os( $EFFECTIVE_USER_ID ). " (euid $EFFECTIVE_USER_ID)\n" ) ;
  2967. $mysync->{ skipemptyfolders } = defined $mysync->{ skipemptyfolders } ? $mysync->{ skipemptyfolders } : 1 ;
  2968. # Out of memory with messages over 1 GB ?
  2969. $mysync->{ maxsize } = defined $mysync->{ maxsize } ? $mysync->{ maxsize } : 1_000_000_000 ;
  2970. # tail -f behaviour on by default
  2971. $mysync->{ tail } = defined $mysync->{ tail } ? $mysync->{ tail } : 1 ;
  2972. # not sure it's for good
  2973. @useheader = qw( Message-Id Received ) ;
  2974. # addheader on by default
  2975. $mysync->{ addheader } = defined $mysync->{ addheader } ? $mysync->{ addheader } : 1 ;
  2976. # sync duplicates by default in cgi context
  2977. $mysync->{ syncduplicates } = defined $mysync->{ syncduplicates } ? $mysync->{ syncduplicates } : 1 ;
  2978. # log the logfile name by default in cgi context
  2979. $mysync->{ loglogfile } = defined $mysync->{ loglogfile } ? $mysync->{ loglogfile } : 1 ;
  2980. return ;
  2981. }
  2982. sub cgioutputenvcontext
  2983. {
  2984. my $mysync = shift @ARG ;
  2985. for my $envvar ( qw( REMOTE_ADDR REMOTE_HOST HTTP_REFERER HTTP_USER_AGENT SERVER_SOFTWARE SERVER_PORT HTTP_COOKIE ) ) {
  2986. my $envval = $ENV{ $envvar } || q{} ;
  2987. if ( $envval ) { output( $mysync, "$envvar is $envval\n" ) } ;
  2988. }
  2989. return ;
  2990. }
  2991. sub announcelogfile
  2992. {
  2993. my $mysync = shift ;
  2994. if ( $mysync->{ log } )
  2995. {
  2996. myprint( "Log file is $mysync->{ logfile } ( to change it, use --logfile path ; or use --nolog to turn off logging )\n" ) ;
  2997. loglogfile( $mysync ) ;
  2998. }
  2999. else
  3000. {
  3001. myprint( "No log file because of option --nolog\n" ) ;
  3002. }
  3003. return ;
  3004. }
  3005. sub loglogfile
  3006. {
  3007. my $mysync = shift ;
  3008. if ( ! $mysync->{ loglogfile } ) { return ; }
  3009. if ( ! $mysync->{ log } ) { return ; }
  3010. my $cwd = getcwd( ) ;
  3011. my $absolutelogfilepath ;
  3012. # Fixme: add case when the logfile name is already absolute
  3013. $absolutelogfilepath = "$cwd/$mysync->{ logfile }" ;
  3014. my $loglogfilename = '../list_all_logs_auto.txt' ;
  3015. myprint( "Writing log file name $absolutelogfilepath to $loglogfilename\n" ) ;
  3016. if ( open( my $fh, '>>', $loglogfilename ) )
  3017. {
  3018. print $fh "$absolutelogfilepath\n" ;
  3019. close $fh ;
  3020. }
  3021. else
  3022. {
  3023. myprint( "Could not open loglogfile $loglogfilename $!\n" ) ;
  3024. }
  3025. return ;
  3026. }
  3027. sub checkselectable
  3028. {
  3029. my $mysync = shift ;
  3030. if ( $mysync->{ checkselectable } ) {
  3031. my @h1_folders_wanted_selectable ;
  3032. myprint( "Host1: Checking wanted folders are selectable. Use --nocheckselectable to avoid this check.\n" ) ;
  3033. foreach my $folder ( @{ $mysync->{ h1_folders_wanted } } )
  3034. {
  3035. ( $mysync->{ debug } or $mysync->{ debugfolders } ) and myprint( "Checking $folder is selectable on host1\n" ) ;
  3036. # It does an imap command LIST "" $folder and then search for no \Noselect
  3037. if ( ! $mysync->{ imap1 }->selectable( $folder ) )
  3038. {
  3039. myprint( "Host1: warning! ignoring folder $folder because it is not selectable\n" ) ;
  3040. }else
  3041. {
  3042. push @h1_folders_wanted_selectable, $folder ;
  3043. }
  3044. }
  3045. @{ $mysync->{ h1_folders_wanted } } = @h1_folders_wanted_selectable ;
  3046. ( $mysync->{ debug } or $mysync->{ debugfolders } )
  3047. and myprint( 'Host1: checking folders took ', timenext( $mysync ), " s\n" ) ;
  3048. }
  3049. else
  3050. {
  3051. myprint( "Host1: Not checking that wanted folders are selectable. Use --checkselectable to force this check.\n" ) ;
  3052. }
  3053. return ;
  3054. }
  3055. sub setcheckselectable
  3056. {
  3057. my $mysync = shift ;
  3058. my $h1_folders_wanted_nb = scalar @{ $mysync->{ h1_folders_wanted } } ;
  3059. # 152 because 98% of host1 accounts have less than 152 folders on /X service.
  3060. # command to get this value:
  3061. # datamash_file_op_index G_Host1_Nb_folders.txt perc:98 4 %16.1f
  3062. if ( ! defined $mysync->{ checkselectable } )
  3063. {
  3064. if ( 152 >= $h1_folders_wanted_nb )
  3065. {
  3066. $mysync->{ checkselectable } = 1 ;
  3067. }else{
  3068. myprint( "Host1: Not checking that $h1_folders_wanted_nb wanted folders are selectable. Use --checkselectable to force this check.\n" ) ;
  3069. $mysync->{ checkselectable } = 0 ;
  3070. }
  3071. }
  3072. return ;
  3073. }
  3074. sub debugsleep
  3075. {
  3076. my $mysync = shift @ARG ;
  3077. if ( defined $mysync->{debugsleep} ) {
  3078. myprint( "Info: sleeping $mysync->{debugsleep}s\n" ) ;
  3079. sleep $mysync->{debugsleep} ;
  3080. }
  3081. return ;
  3082. }
  3083. sub tests_foldersize
  3084. {
  3085. note( 'Entering tests_foldersize()' ) ;
  3086. is( undef, foldersize( ), 'foldersize: no args => undef' ) ;
  3087. #is_deeply( {}, {}, 'foldersize: a hash is a hash' ) ;
  3088. #is_deeply( [], [], 'foldersize: an array is an array' ) ;
  3089. note( 'Leaving tests_foldersize()' ) ;
  3090. return ;
  3091. }
  3092. # Globals:
  3093. # $fetch_hash_set
  3094. #
  3095. sub foldersize
  3096. {
  3097. # size of one folder
  3098. my ( $mysync, $side, $imap, $search_cmd, $abletosearch, $folder ) = @ARG ;
  3099. if ( ! all_defined( $mysync, $side, $imap, $folder ) )
  3100. {
  3101. return ;
  3102. }
  3103. # FTGate is RFC buggy with EXAMINE it does not act as SELECT
  3104. #if ( ! $imap->examine( $folder ) ) {
  3105. if ( ! $imap->select( $folder ) ) {
  3106. my $error = join q{},
  3107. "$side Folder $folder: Could not select: ",
  3108. $imap->LastError, "\n" ;
  3109. errors_incr( $mysync, $error ) ;
  3110. return ;
  3111. }
  3112. if ( $imap->IsUnconnected( ) )
  3113. {
  3114. return ;
  3115. }
  3116. my $hash_ref = { } ;
  3117. my @msgs = select_msgs( $imap, undef, $search_cmd, $abletosearch, $folder ) ;
  3118. my $nb_msgs = scalar @msgs ;
  3119. my $biggest_in_folder = 0 ;
  3120. @{ $hash_ref }{ @msgs } = ( undef ) if @msgs ;
  3121. my $stot = 0 ;
  3122. if ( $imap->IsUnconnected( ) )
  3123. {
  3124. return ;
  3125. }
  3126. if ( $nb_msgs > 0 and @msgs ) {
  3127. if ( $abletosearch ) {
  3128. if ( ! $imap->fetch_hash( \@msgs, 'RFC822.SIZE', $hash_ref) ) {
  3129. my $error = "$side failure with fetch_hash: $EVAL_ERROR\n" ;
  3130. errors_incr( $mysync, $error ) ;
  3131. return ;
  3132. }
  3133. }
  3134. else
  3135. {
  3136. my $fetch_hash_uids = $fetch_hash_set || "1:*" ;
  3137. if ( ! $imap->fetch_hash( $fetch_hash_uids, 'RFC822.SIZE', $hash_ref ) ) {
  3138. my $error = "$side failure with fetch_hash: $EVAL_ERROR\n" ;
  3139. errors_incr( $mysync, $error ) ;
  3140. return ;
  3141. }
  3142. }
  3143. for ( keys %{ $hash_ref } ) {
  3144. my $size = $hash_ref->{ $_ }->{ 'RFC822.SIZE' } ;
  3145. if ( defined $size )
  3146. {
  3147. $stot += $size ;
  3148. $biggest_in_folder = max( $biggest_in_folder, $size ) ;
  3149. }
  3150. }
  3151. }
  3152. return( $stot, $nb_msgs, $biggest_in_folder ) ;
  3153. }
  3154. # The old subroutine that performed just one side at a time.
  3155. # Still here for a while, until confident with sub foldersize_diff_compute()
  3156. sub foldersizes
  3157. {
  3158. my ( $mysync, $side, $imap, $search_cmd, $abletosearch, @folders ) = @_ ;
  3159. my $total_size = 0 ;
  3160. my $total_nb = 0 ;
  3161. my $biggest_in_all = 0 ;
  3162. my $nb_folders = scalar @folders ;
  3163. my $ct_folders = 0 ; # folder counter.
  3164. myprint( "++++ Calculating sizes of $nb_folders folders on $side\n" ) ;
  3165. foreach my $folder ( @folders ) {
  3166. my $stot = 0 ;
  3167. my $nb_msgs = 0 ;
  3168. my $biggest_in_folder = 0 ;
  3169. $ct_folders++ ;
  3170. myprintf( "$side folder %7s %-35s", "$ct_folders/$nb_folders", jux_utf8( $folder ) ) ;
  3171. if ( 'Host2' eq $side and not exists $mysync->{h2_folders_all_UPPER}{ uc $folder } ) {
  3172. myprint( " does not exist yet\n") ;
  3173. next ;
  3174. }
  3175. if ( 'Host1' eq $side and not exists $h1_folders_all{ $folder } ) {
  3176. myprint( " does not exist\n" ) ;
  3177. next ;
  3178. }
  3179. last if $imap->IsUnconnected( ) ;
  3180. ( $stot, $nb_msgs, $biggest_in_folder ) = foldersize( $mysync, $side, $imap, $search_cmd, $abletosearch, $folder ) ;
  3181. myprintf( ' Size: %9s', $stot ) ;
  3182. myprintf( ' Messages: %5s', $nb_msgs ) ;
  3183. myprintf( " Biggest: %9s\n", $biggest_in_folder ) ;
  3184. $total_size += $stot ;
  3185. $total_nb += $nb_msgs ;
  3186. $biggest_in_all = max( $biggest_in_all, $biggest_in_folder ) ;
  3187. }
  3188. myprintf( "%s Nb folders: %11s folders\n", $side, $nb_folders ) ;
  3189. myprintf( "%s Nb messages: %11s messages\n", $side, $total_nb ) ;
  3190. myprintf( "%s Total size: %11s bytes (%s)\n", $side, $total_size, bytes_display_string_bin( $total_size ) ) ;
  3191. myprintf( "%s Biggest message: %11s bytes (%s)\n", $side, $biggest_in_all, bytes_display_string_bin( $biggest_in_all ) ) ;
  3192. myprintf( "%s Time spent on sizing: %11.1f seconds\n", $side, timenext( $mysync ) ) ;
  3193. return( $total_nb, $total_size ) ;
  3194. }
  3195. sub foldersize_diff_present
  3196. {
  3197. my $mysync = shift ;
  3198. my $folder1 = shift ;
  3199. my $folder2 = shift ;
  3200. my $counter_str = shift ;
  3201. my $force = shift ;
  3202. my $values1_str ;
  3203. my $values2_str ;
  3204. if ( ! defined $mysync->{ folder1 }->{ $folder1 }->{ size } || $force )
  3205. {
  3206. foldersize_diff_compute( $mysync, $folder1, $folder2, $force ) ;
  3207. }
  3208. # again, but this time it means no availaible data.
  3209. if ( defined $mysync->{ folder1 }->{ $folder1 }->{ size } )
  3210. {
  3211. $values1_str = sprintf( "Size: %9s Messages: %5s Biggest: %9s\n",
  3212. $mysync->{ folder1 }->{ $folder1 }->{ size },
  3213. $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs },
  3214. $mysync->{ folder1 }->{ $folder1 }->{ biggest },
  3215. ) ;
  3216. }
  3217. else
  3218. {
  3219. $values1_str = " does not exist\n" ;
  3220. }
  3221. if ( defined $mysync->{ folder2 }->{ $folder2 }->{ size } )
  3222. {
  3223. $values2_str = sprintf( "Size: %9s Messages: %5s Biggest: %9s\n",
  3224. $mysync->{ folder2 }->{ $folder2 }->{ size },
  3225. $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs },
  3226. $mysync->{ folder2 }->{ $folder2 }->{ biggest },
  3227. ) ;
  3228. }
  3229. else
  3230. {
  3231. $values2_str = " does not exist yet\n" ;
  3232. }
  3233. myprintf( "Host1 folder %7s %-35s %s",
  3234. "$counter_str",
  3235. jux_utf8( $folder1 ),
  3236. $values1_str
  3237. ) ;
  3238. myprintf( "Host2 folder %7s %-35s %s",
  3239. "$counter_str",
  3240. jux_utf8( $folder2 ),
  3241. $values2_str
  3242. ) ;
  3243. myprintf( "Host2-Host1 %7s %-35s %9s %5s %9s\n\n",
  3244. "",
  3245. "",
  3246. $mysync->{ folder1 }->{ $folder1 }->{ size_diff },
  3247. $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs_diff },
  3248. $mysync->{ folder1 }->{ $folder1 }->{ biggest_diff },
  3249. ) ;
  3250. return ;
  3251. }
  3252. sub foldersize_diff_compute
  3253. {
  3254. my $mysync = shift ;
  3255. my $folder1 = shift ;
  3256. my $folder2 = shift ;
  3257. my $force = shift ;
  3258. my ( $size_1, $nb_msgs_1, $biggest_1 ) ;
  3259. # memoization
  3260. if (
  3261. exists $h1_folders_all{ $folder1 }
  3262. &&
  3263. (
  3264. ! defined $mysync->{ folder1 }->{ $folder1 }->{ size }
  3265. || $force
  3266. )
  3267. )
  3268. {
  3269. #myprint( "foldersize folder1 $h1_folders_all{ $folder1 }\n" ) ;
  3270. ( $size_1, $nb_msgs_1, $biggest_1 ) =
  3271. foldersize( $mysync,
  3272. 'Host1',
  3273. $mysync->{ imap1 },
  3274. $mysync->{ search1 },
  3275. $mysync->{ abletosearch1 },
  3276. $folder1
  3277. ) ;
  3278. $mysync->{ folder1 }->{ $folder1 }->{ size } = $size_1 ;
  3279. $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs } = $nb_msgs_1 ;
  3280. $mysync->{ folder1 }->{ $folder1 }->{ biggest } = $biggest_1 ;
  3281. }
  3282. else
  3283. {
  3284. $size_1 = $mysync->{ folder1 }->{ $folder1 }->{ size } ;
  3285. $nb_msgs_1 = $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs } ;
  3286. $biggest_1 = $mysync->{ folder1 }->{ $folder1 }->{ biggest } ;
  3287. }
  3288. my ( $size_2, $nb_msgs_2, $biggest_2 ) ;
  3289. if (
  3290. exists $mysync->{ h2_folders_all_UPPER }{ uc $folder2 }
  3291. &&
  3292. (
  3293. ! defined $mysync->{ folder2 }->{ $folder2 }->{ size }
  3294. || $force
  3295. )
  3296. )
  3297. {
  3298. #myprint( "foldersize folder2\n" ) ;
  3299. ( $size_2, $nb_msgs_2, $biggest_2 ) =
  3300. foldersize( $mysync,
  3301. 'Host2',
  3302. $mysync->{ imap2 },
  3303. $mysync->{ search2 },
  3304. $mysync->{ abletosearch2 },
  3305. $folder2
  3306. ) ;
  3307. $mysync->{ folder2 }->{ $folder2 }->{ size } = $size_2 ;
  3308. $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs } = $nb_msgs_2 ;
  3309. $mysync->{ folder2 }->{ $folder2 }->{ biggest } = $biggest_2 ;
  3310. }
  3311. else
  3312. {
  3313. $size_2 = $mysync->{ folder2 }->{ $folder2 }->{ size } ;
  3314. $nb_msgs_2 = $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs } ;
  3315. $biggest_2 = $mysync->{ folder2 }->{ $folder2 }->{ biggest } ;
  3316. }
  3317. my $size_diff = diff( $size_2, $size_1 ) ;
  3318. my $nb_msgs_diff = diff( $nb_msgs_2, $nb_msgs_1 ) ;
  3319. my $biggest_diff = diff( $biggest_2, $biggest_1 ) ;
  3320. $mysync->{ folder1 }->{ $folder1 }->{ size_diff } = $size_diff ;
  3321. $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs_diff } = $nb_msgs_diff ;
  3322. $mysync->{ folder1 }->{ $folder1 }->{ biggest_diff } = $biggest_diff ;
  3323. # It's redundant but easier to access later
  3324. $mysync->{ folder2 }->{ $folder2 }->{ size_diff } = $size_diff ;
  3325. $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs_diff } = $nb_msgs_diff ;
  3326. $mysync->{ folder2 }->{ $folder2 }->{ biggest_diff } = $biggest_diff ;
  3327. return ;
  3328. }
  3329. sub diff
  3330. {
  3331. my $x = shift ;
  3332. my $y = shift ;
  3333. $x ||= 0 ;
  3334. $y ||= 0 ;
  3335. return $x - $y ;
  3336. }
  3337. sub add
  3338. {
  3339. my $x = shift ;
  3340. my $y = shift ;
  3341. $x ||= 0 ;
  3342. $y ||= 0 ;
  3343. return $x + $y ;
  3344. }
  3345. sub tests_checknoabletosearch
  3346. {
  3347. note( 'Entering checknoabletosearch()' ) ;
  3348. is( undef, checknoabletosearch( ), 'checknoabletosearch: no args => undef' ) ;
  3349. note( 'Leaving checknoabletosearch()' ) ;
  3350. return ;
  3351. }
  3352. sub checknoabletosearch
  3353. {
  3354. # call example: checknoabletosearch( $sync, $sync->{ imap1 }, 'INBOX', 'Host1' ) ;
  3355. # output:
  3356. # * undef if something is not ok to decide
  3357. # * 1 if SEARCH ALL failed
  3358. my( $mysync, $imap, $folder, $HostX ) = @ARG ;
  3359. if ( ! all_defined( $mysync, $imap, $folder, $HostX ) )
  3360. {
  3361. return ;
  3362. }
  3363. myprint( "$HostX: checking if SEARCH ALL works on $folder\n" ) ;
  3364. if ( ! select_folder( $mysync, $imap, $folder, $HostX ) )
  3365. {
  3366. myprint( "$HostX: can not SELECT folder [$folder]\n" ) ;
  3367. return ;
  3368. }
  3369. my $count_from_select = count_from_select( $imap->History ) ;
  3370. myprint( "$HostX: folder [$folder] has $count_from_select messages mentioned by SELECT\n" ) ;
  3371. my $msgs_all = $imap->messages( ) ;
  3372. if ( ! $msgs_all )
  3373. {
  3374. myprint( "$HostX: can not SEARCH ALL folder [$folder]\n" ) ;
  3375. myprint( "$HostX: ", $imap->LastError(), "\n" ) ;
  3376. return 1 ;
  3377. }
  3378. my $count_from_search_all = scalar( @{ $msgs_all } ) ;
  3379. myprint( "$HostX: folder [$folder] has $count_from_search_all messages found by SEARCH ALL\n" ) ;
  3380. if ( $count_from_select == $count_from_search_all )
  3381. {
  3382. myprint( "$HostX: folder [$folder] has the same messages count ($count_from_select) by SELECT and SEARCH ALL\n" ) ;
  3383. }
  3384. else
  3385. {
  3386. myprint( "$HostX: Warning, folder [$folder] has not the same count by SELECT ($count_from_select) and SEARCH ALL ($count_from_search_all)\n" ) ;
  3387. return 1 ;
  3388. }
  3389. return ;
  3390. }
  3391. sub foldersizes_diff_list
  3392. {
  3393. my $mysync = shift ;
  3394. my $force = shift ;
  3395. my @folders = @{ $mysync->{h1_folders_wanted} } ;
  3396. my $nb_folders = scalar @folders ;
  3397. my $ct_folders = 0 ; # folder counter.
  3398. foreach my $folder1 ( @folders )
  3399. {
  3400. $ct_folders++ ;
  3401. my $counter_str = "$ct_folders/$nb_folders" ;
  3402. my $folder2 = imap2_folder_name( $mysync, $folder1 ) ;
  3403. foldersize_diff_present( $mysync, $folder1, $folder2, $counter_str, $force ) ;
  3404. }
  3405. return ;
  3406. }
  3407. sub foldersizes_total
  3408. {
  3409. my $mysync = shift ;
  3410. my @folders_1 = @{ $mysync->{h1_folders_wanted} } ;
  3411. my @folders_2 = @h2_folders_from_1_wanted ;
  3412. my $nb_folders_1 = scalar( @folders_1 ) ;
  3413. my $nb_folders_2 = scalar( @folders_2 ) ;
  3414. my ( $total_size_1, $total_nb_1, $biggest_in_all_1 ) = ( 0, 0, 0 ) ;
  3415. my ( $total_size_2, $total_nb_2, $biggest_in_all_2 ) = ( 0, 0, 0 ) ;
  3416. foreach my $folder1 ( @folders_1 )
  3417. {
  3418. $total_size_1 = add( $total_size_1, $mysync->{ folder1 }->{ $folder1 }->{ size } ) ;
  3419. $total_nb_1 = add( $total_nb_1, $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs } ) ;
  3420. $biggest_in_all_1 = max( $biggest_in_all_1 , $mysync->{ folder1 }->{ $folder1 }->{ biggest } ) ;
  3421. }
  3422. foreach my $folder2 ( @folders_2 )
  3423. {
  3424. $total_size_2 = add( $total_size_2, $mysync->{ folder2 }->{ $folder2 }->{ size } ) ;
  3425. $total_nb_2 = add( $total_nb_2, $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs } ) ;
  3426. $biggest_in_all_2 = max( $biggest_in_all_2 , $mysync->{ folder2 }->{ $folder2 }->{ biggest } ) ;
  3427. }
  3428. myprintf( "Host1 Nb folders: %11s folders\n", $nb_folders_1 ) ;
  3429. myprintf( "Host2 Nb folders: %11s folders\n", $nb_folders_2 ) ;
  3430. myprint( "\n" ) ;
  3431. myprintf( "Host1 Nb messages: %11s messages\n", $total_nb_1 ) ;
  3432. myprintf( "Host2 Nb messages: %11s messages\n", $total_nb_2 ) ;
  3433. myprint( "\n" ) ;
  3434. myprintf( "Host1 Total size: %11s bytes (%s)\n", $total_size_1, bytes_display_string_bin( $total_size_1 ) ) ;
  3435. myprintf( "Host2 Total size: %11s bytes (%s)\n", $total_size_2, bytes_display_string_bin( $total_size_2 ) ) ;
  3436. myprint( "\n" ) ;
  3437. myprintf( "Host1 Biggest message: %11s bytes (%s)\n", $biggest_in_all_1, bytes_display_string_bin( $biggest_in_all_1 ) ) ;
  3438. myprintf( "Host2 Biggest message: %11s bytes (%s)\n", $biggest_in_all_2, bytes_display_string_bin( $biggest_in_all_2 ) ) ;
  3439. myprint( "\n" ) ;
  3440. myprintf( "Time spent on sizing: %11.1f seconds\n", timenext( $mysync ) ) ;
  3441. my @total_1_2 = ( $total_nb_1, $total_size_1, $total_nb_2, $total_size_2 ) ;
  3442. return @total_1_2 ;
  3443. }
  3444. sub foldersizesatend_old
  3445. {
  3446. my $mysync = shift ;
  3447. timenext( $mysync ) ;
  3448. return if ( $mysync->{imap1}->IsUnconnected( ) ) ;
  3449. return if ( $mysync->{imap2}->IsUnconnected( ) ) ;
  3450. # Get all folders on host2 again since new were created
  3451. @h2_folders_all = sort $mysync->{imap2}->folders();
  3452. for ( @h2_folders_all ) {
  3453. $h2_folders_all{ $_ } = 1 ;
  3454. $mysync->{h2_folders_all_UPPER}{ uc $_ } = 1 ;
  3455. } ;
  3456. ( $h1_nb_msg_end, $h1_bytes_end ) = foldersizes( $mysync, 'Host1', $mysync->{imap1}, $mysync->{ search1 }, $mysync->{abletosearch1}, @{ $mysync->{h1_folders_wanted} } ) ;
  3457. ( $h2_nb_msg_end, $h2_bytes_end ) = foldersizes( $mysync, 'Host2', $mysync->{imap2}, $mysync->{ search2 }, $mysync->{abletosearch2}, @h2_folders_from_1_wanted ) ;
  3458. if ( not all_defined( $h1_nb_msg_end, $h1_bytes_end, $h2_nb_msg_end, $h2_bytes_end ) ) {
  3459. my $error = "Failure getting foldersizes, final differences will not be calculated\n" ;
  3460. errors_incr( $mysync, $error ) ;
  3461. }
  3462. return ;
  3463. }
  3464. sub foldersizesatend
  3465. {
  3466. my $mysync = shift ;
  3467. timenext( $mysync ) ;
  3468. return if ( $mysync->{imap1}->IsUnconnected( ) ) ;
  3469. return if ( $mysync->{imap2}->IsUnconnected( ) ) ;
  3470. # Get all folders on host2 again since new were created
  3471. @h2_folders_all = sort $mysync->{imap2}->folders();
  3472. for ( @h2_folders_all ) {
  3473. $h2_folders_all{ $_ } = 1 ;
  3474. $mysync->{h2_folders_all_UPPER}{ uc $_ } = 1 ;
  3475. } ;
  3476. foldersizes_diff_list( $mysync, $FORCE ) ;
  3477. ( $h1_nb_msg_end, $h1_bytes_end, $h2_nb_msg_end, $h2_bytes_end )
  3478. = foldersizes_total( $mysync ) ;
  3479. if ( not all_defined( $h1_nb_msg_end, $h1_bytes_end, $h2_nb_msg_end, $h2_bytes_end ) ) {
  3480. my $error = "Failure getting foldersizes, final differences will not be calculated\n" ;
  3481. errors_incr( $mysync, $error ) ;
  3482. }
  3483. return ;
  3484. }
  3485. sub foldersizes_at_the_beggining
  3486. {
  3487. my $mysync = shift ;
  3488. myprint( << 'END_SIZE' ) ;
  3489. Folders sizes before the synchronization.
  3490. You can remove foldersizes listings by using "--nofoldersizes" and "--nofoldersizesatend"
  3491. but then you will also lose the ETA (Estimation Time of Arrival) given after each message copy.
  3492. END_SIZE
  3493. foldersizes_diff_list( $mysync ) ;
  3494. ( $mysync->{ h1_nb_msg_start }, $mysync->{ h1_bytes_start },
  3495. $mysync->{ h2_nb_msg_start }, $mysync->{ h2_bytes_start } )
  3496. = foldersizes_total( $mysync ) ;
  3497. if ( not all_defined(
  3498. $mysync->{ h1_nb_msg_start },
  3499. $mysync->{ h1_bytes_start },
  3500. $mysync->{ h2_nb_msg_start },
  3501. $mysync->{ h2_bytes_start } ) )
  3502. {
  3503. my $error = "Failure getting foldersizes, ETA and final diff will not be displayed\n" ;
  3504. errors_incr( $mysync, $error ) ;
  3505. $mysync->{ foldersizes } = 0 ;
  3506. $mysync->{ foldersizesatend } = 0 ;
  3507. return ;
  3508. }
  3509. my $h2_bytes_limit = $mysync->{ acc2 }->{quota_limit_bytes} || 0 ;
  3510. if ( $h2_bytes_limit and ( $h2_bytes_limit < $mysync->{ h1_bytes_start } ) )
  3511. {
  3512. my $quota_percent = mysprintf( '%.0f', $NUMBER_100 * $mysync->{ h1_bytes_start } / $h2_bytes_limit ) ;
  3513. my $error = "Host2: Quota limit will be exceeded! Over $quota_percent % ( $mysync->{ h1_bytes_start } bytes / $h2_bytes_limit bytes )\n" ;
  3514. errors_incr( $mysync, $error ) ;
  3515. }
  3516. return ;
  3517. }
  3518. # Globals:
  3519. # @h2_folders_from_1_wanted
  3520. sub foldersizes_at_the_beggining_old
  3521. {
  3522. my $mysync = shift ;
  3523. myprint( << 'END_SIZE' ) ;
  3524. Folders sizes before the synchronization.
  3525. You can remove foldersizes listings by using "--nofoldersizes" and "--nofoldersizesatend"
  3526. but then you will also lose the ETA (Estimation Time of Arrival) given after each message copy.
  3527. END_SIZE
  3528. ( $mysync->{ h1_nb_msg_start }, $mysync->{ h1_bytes_start } ) =
  3529. foldersizes( $mysync, 'Host1', $mysync->{imap1}, $mysync->{ search1 },
  3530. $mysync->{abletosearch1}, @{ $mysync->{h1_folders_wanted} } ) ;
  3531. ( $mysync->{ h2_nb_msg_start }, $mysync->{ h2_bytes_start } ) =
  3532. foldersizes( $mysync, 'Host2', $mysync->{imap2}, $mysync->{ search2 },
  3533. $mysync->{abletosearch2}, @h2_folders_from_1_wanted ) ;
  3534. if ( not all_defined( $mysync->{ h1_nb_msg_start },
  3535. $mysync->{ h1_bytes_start }, $mysync->{ h2_nb_msg_start }, $mysync->{ h2_bytes_start } ) )
  3536. {
  3537. my $error = "Failure getting foldersizes, ETA and final diff will not be displayed\n" ;
  3538. errors_incr( $mysync, $error ) ;
  3539. $mysync->{ foldersizes } = 0 ;
  3540. $mysync->{ foldersizesatend } = 0 ;
  3541. return ;
  3542. }
  3543. my $h2_bytes_limit = $mysync->{ acc2 }->{quota_limit_bytes} || 0 ;
  3544. if ( $h2_bytes_limit and ( $h2_bytes_limit < $mysync->{ h1_bytes_start } ) )
  3545. {
  3546. my $quota_percent = mysprintf( '%.0f', $NUMBER_100 * $mysync->{ h1_bytes_start } / $h2_bytes_limit ) ;
  3547. my $error = "Host2: Quota limit will be exceeded! Over $quota_percent % ( $mysync->{ h1_bytes_start } bytes / $h2_bytes_limit bytes )\n" ;
  3548. errors_incr( $mysync, $error ) ;
  3549. }
  3550. return ;
  3551. }
  3552. sub tests_total_bytes_max_reached
  3553. {
  3554. note( 'Entering tests_total_bytes_max_reached()' ) ;
  3555. is( undef, total_bytes_max_reached( ), 'total_bytes_max_reached: no args => undef' ) ;
  3556. my $mysync = {} ;
  3557. is( undef, total_bytes_max_reached( $mysync ), 'total_bytes_max_reached: no exitwhenover => undef' ) ;
  3558. $mysync->{ exitwhenover } = 300 ;
  3559. is( undef, total_bytes_max_reached( $mysync ), 'total_bytes_max_reached: exitwhenover 300 but no total_bytes_transferred => undef' ) ;
  3560. $mysync->{ total_bytes_transferred } = 200 ;
  3561. is( undef, total_bytes_max_reached( $mysync ), 'total_bytes_max_reached: exitwhenover 300 but total_bytes_transferred 200 => undef' ) ;
  3562. $mysync->{ total_bytes_transferred } = 400 ;
  3563. is( 1, total_bytes_max_reached( $mysync ), 'total_bytes_max_reached: exitwhenover 300 but total_bytes_transferred 400 => 1' ) ;
  3564. note( 'Leaving tests_total_bytes_max_reached()' ) ;
  3565. return ;
  3566. }
  3567. sub total_bytes_max_reached
  3568. {
  3569. my $mysync = shift ;
  3570. if ( ! defined $mysync ) { return ; }
  3571. if ( ! $mysync->{ exitwhenover } )
  3572. {
  3573. return ;
  3574. }
  3575. if ( ! $mysync->{ total_bytes_transferred } )
  3576. {
  3577. return ;
  3578. }
  3579. if ( $mysync->{ total_bytes_transferred } >= $mysync->{ exitwhenover } )
  3580. {
  3581. my $error = "Maximum bytes transferred reached, $mysync->{total_bytes_transferred} >= $mysync->{ exitwhenover }, ending sync\n" ;
  3582. errors_incr( $mysync, $error ) ;
  3583. return( 1 ) ;
  3584. }
  3585. return ;
  3586. }
  3587. sub tests_mock_capability
  3588. {
  3589. note( 'Entering tests_mock_capability()' ) ;
  3590. my $myimap ;
  3591. ok( $myimap = mock_capability( ),
  3592. 'mock_capability: (1) no args => a Test::MockObject'
  3593. ) ;
  3594. ok( $myimap->isa( 'Test::MockObject' ),
  3595. 'mock_capability: (2) no args => a Test::MockObject'
  3596. ) ;
  3597. is( undef, $myimap->capability( ),
  3598. 'mock_capability: (3) no args => capability undef'
  3599. ) ;
  3600. ok( mock_capability( $myimap ),
  3601. 'mock_capability: (1) one arg => MockObject'
  3602. ) ;
  3603. is( undef, $myimap->capability( ),
  3604. 'mock_capability: (2) one arg OO style => capability undef'
  3605. ) ;
  3606. ok( mock_capability( $myimap, $NUMBER_123456 ),
  3607. 'mock_capability: (1) two args 123456 => capability 123456'
  3608. ) ;
  3609. is( $NUMBER_123456, $myimap->capability( ),
  3610. 'mock_capability: (2) two args 123456 => capability 123456'
  3611. ) ;
  3612. ok( mock_capability( $myimap, 'ABCD' ),
  3613. 'mock_capability: (1) two args ABCD => capability ABCD'
  3614. ) ;
  3615. is( 'ABCD', $myimap->capability( ),
  3616. 'mock_capability: (2) two args ABCD => capability ABCD'
  3617. ) ;
  3618. ok( mock_capability( $myimap, [ 'ABCD' ] ),
  3619. 'mock_capability: (1) two args [ ABCD ] => capability [ ABCD ]'
  3620. ) ;
  3621. is_deeply( [ 'ABCD' ], $myimap->capability( ),
  3622. 'mock_capability: (2) two args [ ABCD ] => capability [ ABCD ]'
  3623. ) ;
  3624. ok( mock_capability( $myimap, [ 'ABC', 'DEF' ] ),
  3625. 'mock_capability: (1) two args [ ABC, DEF ] => capability [ ABC, DEF ]'
  3626. ) ;
  3627. is_deeply( [ 'ABC', 'DEF' ], $myimap->capability( ),
  3628. 'mock_capability: (2) two args [ ABC, DEF ] => capability capability [ ABC, DEF ]'
  3629. ) ;
  3630. ok( mock_capability( $myimap, 'ABC', 'DEF' ),
  3631. 'mock_capability: (1) two args ABC, DEF => capability [ ABC, DEF ]'
  3632. ) ;
  3633. is_deeply( [ 'ABC', 'DEF' ], [ $myimap->capability( ) ],
  3634. 'mock_capability: (2) two args ABC, DEF => capability capability [ ABC, DEF ]'
  3635. ) ;
  3636. ok( mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ),
  3637. 'mock_capability: (1) two args IMAP4rev1, APPENDLIMIT=123456 => capability [ IMAP4rev1, APPENDLIMIT=123456 ]'
  3638. ) ;
  3639. is_deeply( [ 'IMAP4rev1', 'APPENDLIMIT=123456' ], [ $myimap->capability( ) ],
  3640. 'mock_capability: (2) two args IMAP4rev1, APPENDLIMIT=123456 => capability capability [ IMAP4rev1, APPENDLIMIT=123456 ]'
  3641. ) ;
  3642. note( 'Leaving tests_mock_capability()' ) ;
  3643. return ;
  3644. }
  3645. sub sig_install_toggle_sleep
  3646. {
  3647. my $mysync = shift ;
  3648. if ( 'MSWin32' ne $OSNAME ) {
  3649. #myprint( "sig_install( $mysync, \&toggle_sleep, 'USR1' )\n" ) ;
  3650. sig_install( $mysync, 'toggle_sleep', 'USR1' ) ;
  3651. }
  3652. #myprint( "Leaving sig_install_toggle_sleep\n" ) ;
  3653. return ;
  3654. }
  3655. sub mock_capability
  3656. {
  3657. my $myimap = shift ;
  3658. my @has_capability_value = @ARG ;
  3659. my ( $has_capability_value ) = @has_capability_value ;
  3660. if ( ! $myimap )
  3661. {
  3662. require_ok( "Test::MockObject" ) ;
  3663. $myimap = Test::MockObject->new( ) ;
  3664. }
  3665. $myimap->mock(
  3666. 'capability',
  3667. sub { return wantarray ?
  3668. @has_capability_value
  3669. : $has_capability_value ;
  3670. }
  3671. ) ;
  3672. return $myimap ;
  3673. }
  3674. sub tests_capability_of
  3675. {
  3676. note( 'Entering tests_capability_of()' ) ;
  3677. is( undef, capability_of( ),
  3678. 'capability_of: no args => undef' ) ;
  3679. my $myimap ;
  3680. is( undef, capability_of( $myimap ),
  3681. 'capability_of: undef => undef' ) ;
  3682. $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ;
  3683. is( undef, capability_of( $myimap, 'CACA' ),
  3684. 'capability_of: two args unknown capability => undef' ) ;
  3685. is( $NUMBER_123456, capability_of( $myimap, 'APPENDLIMIT' ),
  3686. 'capability_of: two args APPENDLIMIT 123456 => 123456 yeah!' ) ;
  3687. note( 'Leaving tests_capability_of()' ) ;
  3688. return ;
  3689. }
  3690. sub capability_of
  3691. {
  3692. my $imap = shift || return ;
  3693. my $capability_keyword = shift || return ;
  3694. my @capability = $imap->capability ;
  3695. if ( ! @capability ) { return ; }
  3696. my $capability_value = search_in_array( $capability_keyword, @capability ) ;
  3697. return $capability_value ;
  3698. }
  3699. sub tests_search_in_array
  3700. {
  3701. note( 'Entering tests_search_in_array()' ) ;
  3702. is( undef, search_in_array( 'KA' ),
  3703. 'search_in_array: no array => undef ' ) ;
  3704. is( 'VA', search_in_array( 'KA', ( 'KA=VA' ) ),
  3705. 'search_in_array: KA KA=VA => VA ' ) ;
  3706. is( 'VA', search_in_array( 'KA', ( 'KA=VA', 'KB=VB' ) ),
  3707. 'search_in_array: KA KA=VA KB=VB => VA ' ) ;
  3708. is( 'VB', search_in_array( 'KB', ( 'KA=VA', 'KB=VB' ) ),
  3709. 'search_in_array: KA=VA KB=VB => VB ' ) ;
  3710. note( 'Leaving tests_search_in_array()' ) ;
  3711. return ;
  3712. }
  3713. sub search_in_array
  3714. {
  3715. my ( $key, @array ) = @ARG ;
  3716. foreach my $item ( @array )
  3717. {
  3718. if ( $item =~ /([^=]+)=(.*)/ )
  3719. {
  3720. if ( $1 eq $key )
  3721. {
  3722. return $2 ;
  3723. }
  3724. }
  3725. }
  3726. return ;
  3727. }
  3728. sub tests_appendlimit_from_capability
  3729. {
  3730. note( 'Entering tests_appendlimit_from_capability()' ) ;
  3731. is( undef, appendlimit_from_capability( ),
  3732. 'appendlimit_from_capability: no args => undef'
  3733. ) ;
  3734. my $myimap ;
  3735. is( undef, appendlimit_from_capability( $myimap ),
  3736. 'appendlimit_from_capability: undef arg => undef'
  3737. ) ;
  3738. $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ;
  3739. # Normal behavior
  3740. is( $NUMBER_123456, appendlimit_from_capability( $myimap ),
  3741. 'appendlimit_from_capability: APPENDLIMIT=123456 => 123456'
  3742. ) ;
  3743. # Not a number
  3744. $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=ABC' ) ;
  3745. is( undef, appendlimit_from_capability( $myimap ),
  3746. 'appendlimit_from_capability: not a number => undef'
  3747. ) ;
  3748. note( 'Leaving tests_appendlimit_from_capability()' ) ;
  3749. return ;
  3750. }
  3751. sub appendlimit_from_capability
  3752. {
  3753. my $myimap = shift ;
  3754. if ( ! $myimap )
  3755. {
  3756. myprint( "Warn: no imap with call to appendlimit_from_capability\n" ) ;
  3757. return ;
  3758. }
  3759. #myprint( Data::Dumper->Dump( [ \$myimap ] ) ) ;
  3760. my $appendlimit = capability_of( $myimap, 'APPENDLIMIT' ) ;
  3761. #myprint( "has_capability APPENDLIMIT $appendlimit\n" ) ;
  3762. if ( is_integer( $appendlimit ) )
  3763. {
  3764. return $appendlimit ;
  3765. }
  3766. return ;
  3767. }
  3768. sub tests_appendlimit
  3769. {
  3770. note( 'Entering tests_appendlimit()' ) ;
  3771. is( undef, appendlimit( ),
  3772. 'appendlimit: no args => undef'
  3773. ) ;
  3774. my $mysync = { } ;
  3775. is( undef, appendlimit( $mysync ),
  3776. 'appendlimit: no imap2 => undef'
  3777. ) ;
  3778. my $myimap ;
  3779. $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ;
  3780. $mysync->{ imap2 } = $myimap ;
  3781. is( 123456, appendlimit( $mysync ),
  3782. 'appendlimit: imap2 with APPENDLIMIT=123456 => 123456'
  3783. ) ;
  3784. note( 'Leaving tests_appendlimit()' ) ;
  3785. return ;
  3786. }
  3787. sub appendlimit
  3788. {
  3789. my $mysync = shift || return ;
  3790. my $myimap = $mysync->{ imap2 } ;
  3791. my $appendlimit = appendlimit_from_capability( $myimap ) ;
  3792. if ( defined $appendlimit )
  3793. {
  3794. myprint( "Host2: found APPENDLIMIT=$appendlimit in CAPABILITY (use --appendlimit xxxx to override this automatic setting)\n" ) ;
  3795. return $appendlimit ;
  3796. }
  3797. return ;
  3798. }
  3799. sub tests_maxsize_setting
  3800. {
  3801. note( 'Entering tests_maxsize_setting()' ) ;
  3802. is( undef, maxsize_setting( ),
  3803. 'maxsize_setting: no args => undef'
  3804. ) ;
  3805. my $mysync ;
  3806. is( undef, maxsize_setting( $mysync ),
  3807. 'maxsize_setting: undef arg => undef'
  3808. ) ;
  3809. $mysync = { } ;
  3810. $mysync->{ maxsize } = $NUMBER_123456 ;
  3811. # --maxsize alone
  3812. is( $NUMBER_123456, maxsize_setting( $mysync ),
  3813. 'maxsize_setting: --maxsize 123456 alone => 123456'
  3814. ) ;
  3815. $mysync = { } ;
  3816. my $myimap ;
  3817. $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=654321' ) ;
  3818. $mysync->{ imap2 } = $myimap ;
  3819. # APPENDLIMIT alone
  3820. is( $NUMBER_654321, maxsize_setting( $mysync ),
  3821. 'maxsize_setting: APPENDLIMIT 654321 alone => 654321'
  3822. ) ;
  3823. is( $NUMBER_654321, $mysync->{ maxsize },
  3824. 'maxsize_setting: APPENDLIMIT 654321 alone => maxsize 654321'
  3825. ) ;
  3826. # APPENDLIMIT with --appendlimit => --appendlimit wins
  3827. $mysync->{ appendlimit } = $NUMBER_123456 ;
  3828. is( $NUMBER_123456, maxsize_setting( $mysync ),
  3829. 'maxsize_setting: APPENDLIMIT 654321 + --appendlimit 123456 => 123456'
  3830. ) ;
  3831. is( $NUMBER_123456, $mysync->{ maxsize },
  3832. 'maxsize_setting: APPENDLIMIT 654321 + --appendlimit 123456 => maxsize 123456'
  3833. ) ;
  3834. # Fresh
  3835. $mysync = { } ;
  3836. $mysync->{ imap2 } = $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=654321' ) ;
  3837. # Case: "APPENDLIMIT >= --maxsize" => maxsize.
  3838. $mysync->{ maxsize } = $NUMBER_123456 ;
  3839. is( $NUMBER_123456, maxsize_setting( $mysync ),
  3840. 'maxsize_setting: APPENDLIMIT 654321 --maxsize 123456 => 123456'
  3841. ) ;
  3842. # Case: "APPENDLIMIT < --maxsize" => APPENDLIMIT.
  3843. # Fresh
  3844. $mysync = { } ;
  3845. $mysync->{ imap2 } = $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ;
  3846. $mysync->{ maxsize } = $NUMBER_654321 ;
  3847. is( $NUMBER_123456, maxsize_setting( $mysync ),
  3848. 'maxsize_setting: APPENDLIMIT 123456 --maxsize 654321 => 123456 '
  3849. ) ;
  3850. # Now --truncmess stuff
  3851. note( 'Leaving tests_maxsize_setting()' ) ;
  3852. return ;
  3853. }
  3854. # Three variables to take account of
  3855. # appendlimit (given by --appendlimit or CAPABILITY...)
  3856. # maxsize
  3857. # truncmess
  3858. sub maxsize_setting
  3859. {
  3860. my $mysync = shift || return ;
  3861. if ( defined $mysync->{ appendlimit } )
  3862. {
  3863. myprint( "Host2: Getting appendlimit from --appendlimit $mysync->{ appendlimit }\n" ) ;
  3864. }
  3865. else
  3866. {
  3867. $mysync->{ appendlimit } = appendlimit( $mysync ) ;
  3868. }
  3869. if ( all_defined( $mysync->{ appendlimit }, $mysync->{ maxsize } ) )
  3870. {
  3871. my $min_maxsize_appendlimit = min( $mysync->{ maxsize }, $mysync->{ appendlimit } ) ;
  3872. myprint( "Host2: Setting maxsize to $min_maxsize_appendlimit (min of --maxsize $mysync->{ maxsize } and appendlimit $mysync->{ appendlimit }\n" ) ;
  3873. $mysync->{ maxsize } = $min_maxsize_appendlimit ;
  3874. return $mysync->{ maxsize } ;
  3875. }
  3876. elsif ( defined $mysync->{ appendlimit } )
  3877. {
  3878. myprint( "Host2: Setting maxsize to appendlimit $mysync->{ appendlimit }\n" ) ;
  3879. $mysync->{ maxsize } = $mysync->{ appendlimit } ;
  3880. return $mysync->{ maxsize } ;
  3881. }elsif ( defined $mysync->{ maxsize } )
  3882. {
  3883. return $mysync->{ maxsize } ;
  3884. }else
  3885. {
  3886. return ;
  3887. }
  3888. }
  3889. sub all_defined
  3890. {
  3891. if ( not @ARG ) {
  3892. return 0 ;
  3893. }
  3894. foreach my $elem ( @ARG ) {
  3895. if ( not defined $elem ) {
  3896. return 0 ;
  3897. }
  3898. }
  3899. return 1 ;
  3900. }
  3901. sub tests_all_defined
  3902. {
  3903. note( 'Entering tests_all_defined()' ) ;
  3904. is( 0, all_defined( ), 'all_defined: no param => 0' ) ;
  3905. is( 0, all_defined( () ), 'all_defined: void list => 0' ) ;
  3906. is( 0, all_defined( undef ), 'all_defined: undef => 0' ) ;
  3907. is( 0, all_defined( undef, undef ), 'all_defined: undef => 0' ) ;
  3908. is( 0, all_defined( 1, undef ), 'all_defined: 1 undef => 0' ) ;
  3909. is( 0, all_defined( undef, 1 ), 'all_defined: undef 1 => 0' ) ;
  3910. is( 1, all_defined( 1, 1 ), 'all_defined: 1 1 => 1' ) ;
  3911. is( 1, all_defined( (1, 1) ), 'all_defined: (1 1) => 1' ) ;
  3912. note( 'Leaving tests_all_defined()' ) ;
  3913. return ;
  3914. }
  3915. sub tests_hashsynclocal
  3916. {
  3917. note( 'Entering tests_hashsynclocal()' ) ;
  3918. my $mysync = {
  3919. host1 => q{},
  3920. user1 => q{},
  3921. password1 => q{},
  3922. host2 => q{},
  3923. user2 => q{},
  3924. password2 => q{},
  3925. } ;
  3926. is( undef, hashsynclocal( $mysync ), 'hashsynclocal: no hashfile name' ) ;
  3927. $mysync->{ hashfile } = q{} ;
  3928. is( undef, hashsynclocal( $mysync ), 'hashsynclocal: empty hashfile name' ) ;
  3929. $mysync->{ hashfile } = './noexist/rrr' ;
  3930. is( undef, hashsynclocal( $mysync ), 'hashsynclocal: no exists hashfile dir' ) ;
  3931. SKIP: {
  3932. if ( 'MSWin32' eq $OSNAME or '0' eq $EFFECTIVE_USER_ID ) { skip( 'Tests only for non-root Unix', 1 ) ; }
  3933. $mysync->{ hashfile } = '/rrr' ;
  3934. is( undef, hashsynclocal( $mysync ), 'hashsynclocal: permission denied' ) ;
  3935. }
  3936. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'hashsynclocal: mkpath W/tmp/tests/' ) ;
  3937. $mysync->{ hashfile } = 'W/tmp/tests/imapsync_hash' ;
  3938. ok( ! -e 'W/tmp/tests/imapsync_hash' || unlink 'W/tmp/tests/imapsync_hash', 'hashsynclocal: unlink W/tmp/tests/imapsync_hash' ) ;
  3939. ok( ! -e 'W/tmp/tests/imapsync_hash', 'hashsynclocal: verify there is no W/tmp/tests/imapsync_hash' ) ;
  3940. is( 'ecdeb4ede672794d173da4e08c52b8ee19b7d252', hashsynclocal( $mysync, 'mukksyhpmbixkxkpjlqivmlqsulpictj' ), 'hashsynclocal: creating/reading W/tmp/tests/imapsync_hash' ) ;
  3941. # A second time now
  3942. is( 'ecdeb4ede672794d173da4e08c52b8ee19b7d252', hashsynclocal( $mysync ), 'hashsynclocal: reading W/tmp/tests/imapsync_hash second time => same' ) ;
  3943. note( 'Leaving tests_hashsynclocal()' ) ;
  3944. return ;
  3945. }
  3946. sub hashsynclocal
  3947. {
  3948. my $mysync = shift ;
  3949. my $hashkey = shift ; # Optional, only there for tests
  3950. my $hashfile = $mysync->{ hashfile } ;
  3951. $hashfile = createhashfileifneeded( $hashfile, $hashkey ) ;
  3952. if ( ! $hashfile ) {
  3953. return ;
  3954. }
  3955. $hashkey = firstline( $hashfile ) ;
  3956. if ( ! $hashkey ) {
  3957. myprint( "No hashkey!\n" ) ;
  3958. return ;
  3959. }
  3960. my $hashsynclocal = hashsync( $mysync, $hashkey ) ;
  3961. return( $hashsynclocal ) ;
  3962. }
  3963. sub tests_hashsync
  3964. {
  3965. note( 'Entering tests_hashsync()' ) ;
  3966. is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hashsync( ), 'hashsync: no args' ) ;
  3967. is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hashsync( {}, q{} ), 'hashsync: empty args' ) ;
  3968. my $mysync ;
  3969. $mysync->{ host1 } = 'zzz' ;
  3970. is( 'e86a28a3611c1e7bbaf8057cd00ae122781a11fe', hashsync( $mysync, q{} ), 'hashsync: host1 zzz => ' ) ;
  3971. is( '6a7b451ac99eab1531ad8e6cd544b32420c552ac', hashsync( $mysync, q{A} ), 'hashsync: host1 zzz => ' ) ;
  3972. $mysync->{ host2 } = 'zzz' ;
  3973. is( '15959573e4a86763253a7aedb1a2b0c60d133dc2', hashsync( $mysync, q{} ), 'hashsync: + host2 zzz => ' ) ;
  3974. is( 'b8d4ab541b209c75928528020ca28ee43488bd8f', hashsync( $mysync, 'A' ), 'hashsync: + hashkey A => ' ) ;
  3975. $mysync = undef ;
  3976. is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hashsync( $mysync, q{} ), 'hashsync: undef $mysync' ) ;
  3977. $mysync->{ password1 } = 'abcd' ;
  3978. is( 'afa29ab8534495251ac8346a985717c54bc49c26', hashsync( $mysync, q{} ), 'hashsync: password1: abcd' ) ;
  3979. # A user reported a massive failure on /X (Thomas V. 21/04/2020 Ã 21:41 Subject: Error)
  3980. # "Wide character in subroutine entry at /usr/local/lib/perl5/site_perl/Digest/HMAC.pm"
  3981. # I can reproduce it now
  3982. # The eval is there to avoid a complete crash
  3983. # this one is fatal so it is commented
  3984. # is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', 1 / 0 , 'hashsync: 1 / 0 fatal' ) ;
  3985. my $eval ;
  3986. # this one is not fatal
  3987. is( undef, $eval = eval { 1 / 0 } , 'hashsync: 1/0 not fatal' ) ;
  3988. # this one neither
  3989. $mysync->{ password1 } = 'Ö' ;
  3990. is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', $eval = eval { hashsync( $mysync, q{} ) } , 'hashsync: password1: Ö with eval' ) ;
  3991. $mysync->{ password1 } = 'Ö' ;
  3992. is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hashsync( $mysync, q{} ), 'hashsync: password1: Ö without eval' ) ;
  3993. $mysync->{ password1 } = qq{\x{00D6}} ;
  3994. is( 'bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a', $eval = eval { hashsync( $mysync, q{} ) }, 'hashsync: password1: \x{00D6}' ) ; #
  3995. print qq{1 00D6:Ö\n} ;
  3996. print encode_utf8( qq{2 00D6:Ö\n} ) ;
  3997. print qq{3 00D6:\x{00D6}\n} ;
  3998. print encode_utf8( qq{4 00D6:\x{00D6}\n} ) ;
  3999. print qq{5 6536:æ”¶\n} ;
  4000. print encode_utf8( qq{6 6536:æ”¶\n} ) ;
  4001. # the next one prints "Wide character in print at ./imapsync line xxxx"
  4002. print qq{7 6536:\x{6536}\n} ;
  4003. print encode_utf8( qq{8 6536:\x{6536}\n} ) ;
  4004. $mysync->{ password1 } = qq{æ”¶} ;
  4005. is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hashsync( $mysync, q{} ), 'hashsync: password1: æ”¶' ) ;
  4006. $mysync->{ password1 } = qq{\x{6536}} ;
  4007. is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', $eval = eval{ hashsync( $mysync, q{} ) }, 'hashsync: password1: \x{6536} with eval' ) ;
  4008. # No side effect.
  4009. $mysync->{ password1 } = 'abcd' ;
  4010. is( 'afa29ab8534495251ac8346a985717c54bc49c26', hashsync( $mysync, q{} ), 'hashsync: password1: abcd again' ) ;
  4011. note( 'Leaving tests_hashsync()' ) ;
  4012. return ;
  4013. }
  4014. sub hashsync
  4015. {
  4016. my $mysync = shift ;
  4017. my $hashkey = shift ;
  4018. my $mystring = join( q{},
  4019. $mysync->{ host1 } || q{},
  4020. $mysync->{ user1 } || q{},
  4021. $mysync->{ password1 } || q{},
  4022. $mysync->{ host2 } || q{},
  4023. $mysync->{ user2 } || q{},
  4024. $mysync->{ password2 } || q{},
  4025. ) ;
  4026. #my $hashsync = hmac_sha1_hex( $mystring, $hashkey ) ;
  4027. my $hashsync = hmac_sha1_hex_robust( $mystring, $hashkey ) ;
  4028. #myprint( "$hashsync\n" ) ;
  4029. return( $hashsync ) ;
  4030. }
  4031. sub tests_hmac_sha1_hex
  4032. {
  4033. note( 'Entering tests_hmac_sha1_hex()' ) ;
  4034. is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex( ), 'hmac_sha1_hex: no args => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
  4035. is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex( '' ), 'hmac_sha1_hex: empty string => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
  4036. is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex( '', '' ), 'hmac_sha1_hex: empty strings => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
  4037. is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex( '', '', 'caca' ), 'hmac_sha1_hex: empty strings + caca => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
  4038. # Good
  4039. is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hmac_sha1_hex( 'Ö' ), 'hmac_sha1_hex: Ö => f1a3f3dac3f137fd658027c11678b895f773ce55' ) ;
  4040. is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hmac_sha1_hex( encode_utf8(qq{\x{00D6}}) ), 'hmac_sha1_hex: encode_utf8 \x{00D6} => f1a3f3dac3f137fd658027c11678b895f773ce55' ) ;
  4041. # Bad
  4042. is( 'fe8dc3b9ba3e8850bb4a7b070b2279e911003af2', hmac_sha1_hex( encode_utf8( 'Ö' ) ), 'hmac_sha1_hex: encode_utf8 Ö => fe8dc3b9ba3e8850bb4a7b070b2279e911003af2' ) ;
  4043. is( 'bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a', hmac_sha1_hex( qq{\x{00D6}} ), 'hmac_sha1_hex: qq{\x{00D6}} => bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a' ) ;
  4044. # Good
  4045. is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex( 'A' ), 'hmac_sha1_hex: A => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
  4046. is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex( encode_utf8(qq{\x{0041}}) ), 'hmac_sha1_hex: encode_utf8 \x{0041} => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
  4047. is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex( encode_utf8( 'A' ) ), 'hmac_sha1_hex: encode_utf8 A => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
  4048. is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex( qq{\x{0041}} ), 'hmac_sha1_hex: \x{0041} => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
  4049. # Good
  4050. is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex( 'A', 'B' ), 'hmac_sha1_hex: A B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
  4051. is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex( encode_utf8(qq{\x{0041}}), 'B' ), 'hmac_sha1_hex: encode_utf8 \x{0041} B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
  4052. is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex( encode_utf8( 'A' ), 'B' ), 'hmac_sha1_hex: encode_utf8 A B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
  4053. is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex( qq{\x{0041}}, 'B' ), 'hmac_sha1_hex: \x{0041} B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
  4054. # http://unicode.scarfboy.com/?s=U%2B6536
  4055. # Good
  4056. is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex( 'æ”¶' ), 'hmac_sha1_hex: æ”¶ => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ;
  4057. is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex( encode_utf8(qq{\x{6536}}) ), 'hmac_sha1_hex: encode_utf8 \x{6536} => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ;
  4058. # Bad
  4059. is( 'e82217119628ad03e659cc89671d05ea4cee7238', hmac_sha1_hex( encode_utf8( 'æ”¶' ) ), 'hmac_sha1_hex: encode_utf8 æ”¶ => e82217119628ad03e659cc89671d05ea4cee7238' ) ;
  4060. # Very very bad, perl dies...
  4061. #is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex( qq{\x{6536}} ), 'hmac_sha1_hex: \x{6536} => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ;
  4062. # Ok but well, bad indeed
  4063. is( undef, my $eval = eval{ hmac_sha1_hex( qq{\x{6536}} ) }, 'hmac_sha1_hex: \x{6536} => undef' ) ;
  4064. note( 'Leaving tests_hmac_sha1_hex()' ) ;
  4065. return ;
  4066. }
  4067. sub tests_hmac_sha1_hex_robust
  4068. {
  4069. note( 'Entering tests_hmac_sha1_hex_robust()' ) ;
  4070. is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex_robust( ), 'hmac_sha1_hex_robust: no args => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
  4071. is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex_robust( '' ), 'hmac_sha1_hex_robust: empty string => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
  4072. is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex_robust( '', '' ), 'hmac_sha1_hex_robust: empty strings => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
  4073. is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex_robust( '', '', 'caca' ), 'hmac_sha1_hex_robust: empty strings + caca => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
  4074. # Good
  4075. is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hmac_sha1_hex_robust( 'Ö' ), 'hmac_sha1_hex_robust: Ö => f1a3f3dac3f137fd658027c11678b895f773ce55' ) ;
  4076. is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hmac_sha1_hex_robust( encode_utf8(qq{\x{00D6}}) ), 'hmac_sha1_hex_robust: encode_utf8 \x{00D6} => f1a3f3dac3f137fd658027c11678b895f773ce55' ) ;
  4077. # Bad
  4078. is( 'fe8dc3b9ba3e8850bb4a7b070b2279e911003af2', hmac_sha1_hex_robust( encode_utf8( 'Ö' ) ), 'hmac_sha1_hex_robust: encode_utf8 Ö => fe8dc3b9ba3e8850bb4a7b070b2279e911003af2' ) ;
  4079. is( 'bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a', hmac_sha1_hex_robust( qq{\x{00D6}} ), 'hmac_sha1_hex_robust: qq{\x{00D6}} => bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a' ) ;
  4080. # Good
  4081. is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex_robust( 'A' ), 'hmac_sha1_hex_robust: A => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
  4082. is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex_robust( encode_utf8(qq{\x{0041}}) ), 'hmac_sha1_hex_robust: encode_utf8 \x{0041} => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
  4083. is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex_robust( encode_utf8( 'A' ) ), 'hmac_sha1_hex_robust: encode_utf8 A => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
  4084. is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex_robust( qq{\x{0041}} ), 'hmac_sha1_hex_robust: \x{0041} => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
  4085. # Good
  4086. is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex_robust( 'A', 'B' ), 'hmac_sha1_hex_robust: A B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
  4087. is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex_robust( encode_utf8(qq{\x{0041}}), 'B' ), 'hmac_sha1_hex_robust: encode_utf8 \x{0041} B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
  4088. is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex_robust( encode_utf8( 'A' ), 'B' ), 'hmac_sha1_hex_robust: encode_utf8 A B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
  4089. is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex_robust( qq{\x{0041}}, 'B' ), 'hmac_sha1_hex_robust: \x{0041} B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
  4090. # http://unicode.scarfboy.com/?s=U%2B6536
  4091. # Good
  4092. is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex_robust( 'æ”¶' ), 'hmac_sha1_hex_robust: æ”¶ => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ;
  4093. is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex_robust( encode_utf8(qq{\x{6536}}) ), 'hmac_sha1_hex_robust: encode_utf8 \x{6536} => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ;
  4094. # Bad
  4095. is( 'e82217119628ad03e659cc89671d05ea4cee7238', hmac_sha1_hex_robust( encode_utf8( 'æ”¶' ) ), 'hmac_sha1_hex_robust: encode_utf8 æ”¶ => e82217119628ad03e659cc89671d05ea4cee7238' ) ;
  4096. # Good
  4097. is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex_robust( qq{\x{6536}} ), 'hmac_sha1_hex_robust: \x{6536} => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ;
  4098. # Good again
  4099. is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', my $eval = eval{ hmac_sha1_hex_robust( qq{\x{6536}} ) }, 'hmac_sha1_hex_robust: \x{6536} => undef' ) ;
  4100. note( 'Leaving tests_hmac_sha1_hex_robust()' ) ;
  4101. return ;
  4102. }
  4103. sub hmac_sha1_hex_robust
  4104. {
  4105. my $string = shift ;
  4106. my $val ;
  4107. if ( defined( $val = eval{ hmac_sha1_hex( $string, @ARG ) } ) )
  4108. {
  4109. return $val ;
  4110. }
  4111. elsif( defined( $val = eval{ hmac_sha1_hex( encode_utf8( $string ), @ARG ) } ) )
  4112. {
  4113. return $val ;
  4114. }
  4115. else
  4116. {
  4117. return ;
  4118. }
  4119. }
  4120. sub tests_createhashfileifneeded
  4121. {
  4122. note( 'Entering tests_createhashfileifneeded()' ) ;
  4123. is( undef, createhashfileifneeded( ), 'createhashfileifneeded: no parameters => undef' ) ;
  4124. note( 'Leaving tests_createhashfileifneeded()' ) ;
  4125. return ;
  4126. }
  4127. sub createhashfileifneeded
  4128. {
  4129. my $hashfile = shift ;
  4130. my $hashkey = shift || rand32( ) ;
  4131. # no name
  4132. if ( ! $hashfile ) {
  4133. return ;
  4134. }
  4135. # already there
  4136. if ( -e -r $hashfile ) {
  4137. return $hashfile ;
  4138. }
  4139. # not creatable
  4140. if ( ! -w dirname( $hashfile ) ) {
  4141. return ;
  4142. }
  4143. # creatable
  4144. open my $FILE_HANDLE, '>', $hashfile
  4145. or do {
  4146. myprint( "Could not open $hashfile for writing. Check permissions or disk space." ) ;
  4147. return ;
  4148. } ;
  4149. myprint( "Writing random hashkey in $hashfile, once for all times\n" ) ;
  4150. print $FILE_HANDLE $hashkey ;
  4151. close $FILE_HANDLE ;
  4152. # Should be there now
  4153. if ( -e -r $hashfile ) {
  4154. return $hashfile ;
  4155. }
  4156. # unknown failure
  4157. return ;
  4158. }
  4159. sub tests_rand32
  4160. {
  4161. note( 'Entering tests_rand32()' ) ;
  4162. my $string = rand32( ) ;
  4163. myprint( "$string\n" ) ;
  4164. is( 32, length( $string ), 'rand32: 32 characters long' ) ;
  4165. is( 32, length( rand32( ) ), 'rand32: 32 characters long, another one' ) ;
  4166. note( 'Leaving tests_rand32()' ) ;
  4167. return ;
  4168. }
  4169. sub rand32
  4170. {
  4171. my @chars = ( "a".."z" ) ;
  4172. my $string;
  4173. $string .= $chars[rand @chars] for 1..32 ;
  4174. return $string ;
  4175. }
  4176. sub imap_id_stuff
  4177. {
  4178. my $mysync = shift ;
  4179. if ( not $mysync->{id} ) { return ; } ;
  4180. $mysync->{h1_imap_id} = imap_id( $mysync, $mysync->{imap1}, 'Host1' ) ;
  4181. #myprint( 'Host1: ' . $mysync->{h1_imap_id} ) ;
  4182. $mysync->{h2_imap_id} = imap_id( $mysync, $mysync->{imap2}, 'Host2' ) ;
  4183. #myprint( 'Host2: ' . $mysync->{h2_imap_id} ) ;
  4184. return ;
  4185. }
  4186. sub imap_id
  4187. {
  4188. my ( $mysync, $imap, $Side ) = @_ ;
  4189. if ( not $mysync->{id} ) { return q{} ; } ;
  4190. $Side ||= q{} ;
  4191. my $imap_id_response = q{} ;
  4192. if ( not $imap->has_capability( 'ID' ) ) {
  4193. $imap_id_response = 'No ID capability' ;
  4194. myprint( "$Side: No ID capability\n" ) ;
  4195. }else{
  4196. my $id_inp = imapsync_id( $mysync, { side => lc $Side } ) ;
  4197. myprint( "\n$Side: found ID capability. Sending/receiving ID, presented in raw IMAP for now.\n"
  4198. . "In order to avoid sending/receiving ID, use option --noid\n" ) ;
  4199. my $debug_before = $imap->Debug( ) ;
  4200. $imap->Debug( 1 ) ;
  4201. my $id_out = $imap->tag_and_run( 'ID ' . $id_inp ) ;
  4202. #my $id_out = $imap->tag_and_run( 'ID NIL' ) ;
  4203. myprint( "\n" ) ;
  4204. $imap->Debug( $debug_before ) ;
  4205. #$imap_id_response = Data::Dumper->Dump( [ $id_out ], [ 'IMAP_ID' ] ) ;
  4206. }
  4207. return( $imap_id_response ) ;
  4208. }
  4209. sub imapsync_id
  4210. {
  4211. my $mysync = shift ;
  4212. my $overhashref = shift ;
  4213. # See http://tools.ietf.org/html/rfc2971.html
  4214. my $imapsync_id = { } ;
  4215. my $imapsync_id_lamiral = {
  4216. name => 'imapsync',
  4217. version => imapsync_version( $mysync ),
  4218. os => $OSNAME,
  4219. vendor => 'Gilles LAMIRAL',
  4220. 'support-url' => 'https://imapsync.lamiral.info/',
  4221. # Example of date-time: 19-Sep-2015 08:56:07
  4222. date => date_from_rcs( q{$Date: 2022/01/12 21:28:37 $ } ),
  4223. } ;
  4224. my $imapsync_id_github = {
  4225. name => 'imapsync',
  4226. version => imapsync_version( $mysync ),
  4227. os => $OSNAME,
  4228. vendor => 'github',
  4229. 'support-url' => 'https://github.com/imapsync/imapsync',
  4230. date => date_from_rcs( q{$Date: 2022/01/12 21:28:37 $ } ),
  4231. } ;
  4232. $imapsync_id = $imapsync_id_lamiral ;
  4233. #$imapsync_id = $imapsync_id_github ;
  4234. my %mix = ( %{ $imapsync_id }, %{ $overhashref } ) ;
  4235. my $imapsync_id_str = format_for_imap_arg( \%mix ) ;
  4236. #myprint( "$imapsync_id_str\n" ) ;
  4237. return( $imapsync_id_str ) ;
  4238. }
  4239. sub tests_imapsync_id
  4240. {
  4241. note( 'Entering tests_imapsync_id()' ) ;
  4242. my $mysync ;
  4243. ok( '("name" "imapsync" "version" "111" "os" "beurk" "vendor" "Gilles LAMIRAL" "support-url" "https://imapsync.lamiral.info/" "date" "22-12-1968" "side" "host1")'
  4244. eq imapsync_id( $mysync,
  4245. {
  4246. version => 111,
  4247. os => 'beurk',
  4248. date => '22-12-1968',
  4249. side => 'host1'
  4250. }
  4251. ),
  4252. 'tests_imapsync_id override'
  4253. ) ;
  4254. note( 'Leaving tests_imapsync_id()' ) ;
  4255. return ;
  4256. }
  4257. sub format_for_imap_arg
  4258. {
  4259. my $ref = shift ;
  4260. my $string = q{} ;
  4261. my %terms = %{ $ref } ;
  4262. my @terms = ( ) ;
  4263. if ( not ( %terms ) ) { return( 'NIL' ) } ;
  4264. # sort like in RFC then add extra key/values
  4265. foreach my $key ( qw( name version os os-version vendor support-url address date command arguments environment) ) {
  4266. if ( $terms{ $key } ) {
  4267. push @terms, $key, $terms{ $key } ;
  4268. delete $terms{ $key } ;
  4269. }
  4270. }
  4271. push @terms, %terms ;
  4272. $string = '(' . ( join q{ }, map { '"' . $_ . '"' } @terms ) . ')' ;
  4273. return( $string ) ;
  4274. }
  4275. sub tests_format_for_imap_arg
  4276. {
  4277. note( 'Entering tests_format_for_imap_arg()' ) ;
  4278. ok( 'NIL' eq format_for_imap_arg( { } ), 'format_for_imap_arg empty hash ref' ) ;
  4279. ok( '("name" "toto")' eq format_for_imap_arg( { name => 'toto' } ), 'format_for_imap_arg { name => toto }' ) ;
  4280. ok( '("name" "toto" "key" "val")' eq format_for_imap_arg( { name => 'toto', key => 'val' } ), 'format_for_imap_arg 2 x key val' ) ;
  4281. note( 'Leaving tests_format_for_imap_arg()' ) ;
  4282. return ;
  4283. }
  4284. sub quota
  4285. {
  4286. my ( $mysync, $imap, $side ) = @_ ;
  4287. my %side = (
  4288. h1 => 'Host1',
  4289. h2 => 'Host2',
  4290. ) ;
  4291. my $Side = $side{ $side } ;
  4292. my $debug_before = $imap->Debug( ) ;
  4293. $imap->Debug( 1 ) ;
  4294. if ( not $imap->has_capability( 'QUOTA' ) )
  4295. {
  4296. myprint( "$Side: No QUOTA capability found, skipping it.\n" ) ;
  4297. $imap->Debug( $debug_before ) ;
  4298. return ;
  4299. } ;
  4300. myprint( "\n$Side: QUOTA capability found, presented in raw IMAP on next lines\n" ) ;
  4301. my $getquotaroot = $imap->getquotaroot( 'INBOX' ) ;
  4302. # Gmail INBOX quotaroot is "" but with it Mail::IMAPClient does a literal GETQUOTA {2} \n ""
  4303. #$imap->quota( 'ROOT' ) ;
  4304. #$imap->quota( '""' ) ;
  4305. myprint( "\n" ) ;
  4306. $imap->Debug( $debug_before ) ;
  4307. my $quota_limit_bytes = quota_extract_storage_limit_in_bytes( $mysync, $getquotaroot ) ;
  4308. my $quota_current_bytes = quota_extract_storage_current_in_bytes( $mysync, $getquotaroot ) ;
  4309. $mysync->{$side}->{quota_limit_bytes} = $quota_limit_bytes ;
  4310. $mysync->{$side}->{quota_current_bytes} = $quota_current_bytes ;
  4311. my $quota_percent ;
  4312. if ( $quota_limit_bytes > 0 ) {
  4313. $quota_percent = mysprintf( '%.2f', $NUMBER_100 * $quota_current_bytes / $quota_limit_bytes ) ;
  4314. }else{
  4315. $quota_percent = 0 ;
  4316. }
  4317. myprint( "$Side: Quota current storage is $quota_current_bytes bytes. Limit is $quota_limit_bytes bytes. So $quota_percent % full\n" ) ;
  4318. if ( $QUOTA_PERCENT_LIMIT < $quota_percent ) {
  4319. my $error = "$Side: $quota_percent % full: it is time to find a bigger place! ( $quota_current_bytes bytes / $quota_limit_bytes bytes )\n" ;
  4320. errors_incr( $mysync, $error ) ;
  4321. }
  4322. return ;
  4323. }
  4324. sub tests_quota_extract_storage_limit_in_bytes
  4325. {
  4326. note( 'Entering tests_quota_extract_storage_limit_in_bytes()' ) ;
  4327. my $mysync = {} ;
  4328. my $imap_output = [
  4329. '* QUOTAROOT "INBOX" "Storage quota" "Messages quota"',
  4330. '* QUOTA "Storage quota" (STORAGE 1 104857600)',
  4331. '* QUOTA "Messages quota" (MESSAGE 2 100000)',
  4332. '5 OK Getquotaroot completed.'
  4333. ] ;
  4334. ok( $NUMBER_104_857_600 * $KIBI == quota_extract_storage_limit_in_bytes( $mysync, $imap_output ), 'quota_extract_storage_limit_in_bytes ') ;
  4335. note( 'Leaving tests_quota_extract_storage_limit_in_bytes()' ) ;
  4336. return ;
  4337. }
  4338. sub quota_extract_storage_limit_in_bytes
  4339. {
  4340. my $mysync = shift ;
  4341. my $imap_output = shift ;
  4342. my $limit_kb ;
  4343. $limit_kb = ( map { /.*\(\s*STORAGE\s+\d+\s+(\d+)\s*\)/x ? $1 : () } @{ $imap_output } )[0] ;
  4344. $limit_kb ||= 0 ;
  4345. $mysync->{ debug } and myprint( "storage_limit_kb = $limit_kb\n" ) ;
  4346. return( $KIBI * $limit_kb ) ;
  4347. }
  4348. sub tests_quota_extract_storage_current_in_bytes
  4349. {
  4350. note( 'Entering tests_quota_extract_storage_current_in_bytes()' ) ;
  4351. my $mysync = {} ;
  4352. my $imap_output = [
  4353. '* QUOTAROOT "INBOX" "Storage quota" "Messages quota"',
  4354. '* QUOTA "Storage quota" (STORAGE 1 104857600)',
  4355. '* QUOTA "Messages quota" (MESSAGE 2 100000)',
  4356. '5 OK Getquotaroot completed.'
  4357. ] ;
  4358. ok( 1*$KIBI == quota_extract_storage_current_in_bytes( $mysync, $imap_output ), 'quota_extract_storage_current_in_bytes: 1 => 1024 ') ;
  4359. note( 'Leaving tests_quota_extract_storage_current_in_bytes()' ) ;
  4360. return ;
  4361. }
  4362. sub quota_extract_storage_current_in_bytes
  4363. {
  4364. my $mysync = shift ;
  4365. my $imap_output = shift ;
  4366. my $current_kb ;
  4367. $current_kb = ( map { /.*\(\s*STORAGE\s+(\d+)\s+\d+\s*\)/x ? $1 : () } @{ $imap_output } )[0] ;
  4368. $current_kb ||= 0 ;
  4369. $mysync->{ debug } and myprint( "storage_current_kb = $current_kb\n" ) ;
  4370. return( $KIBI * $current_kb ) ;
  4371. }
  4372. sub automap
  4373. {
  4374. my ( $mysync ) = @_ ;
  4375. if ( $mysync->{automap} ) {
  4376. myprint( "Turned on automapping folders ( use --noautomap to turn off automapping )\n" ) ;
  4377. }else{
  4378. myprint( "Turned off automapping folders ( use --automap to turn on automapping )\n" ) ;
  4379. return ;
  4380. }
  4381. $mysync->{h1_special} = special_from_folders_hash( $mysync, $mysync->{imap1}, 'Host1' ) ;
  4382. $mysync->{h2_special} = special_from_folders_hash( $mysync, $mysync->{imap2}, 'Host2' ) ;
  4383. build_possible_special( $mysync ) ;
  4384. build_guess_special( $mysync ) ;
  4385. build_automap( $mysync ) ;
  4386. return ;
  4387. }
  4388. sub build_guess_special
  4389. {
  4390. my ( $mysync ) = shift ;
  4391. foreach my $h1_fold ( sort keys %{ $mysync->{h1_folders_all} } ) {
  4392. my $special = guess_special( $h1_fold, $mysync->{possible_special}, $mysync->{h1_prefix} ) ;
  4393. if ( $special ) {
  4394. $mysync->{h1_special_guessed}{$h1_fold} = $special ;
  4395. my $already_guessed = $mysync->{h1_special_guessed}{$special} ;
  4396. if ( $already_guessed ) {
  4397. myprint( "Host1: $h1_fold not $special because set to $already_guessed\n" ) ;
  4398. }else{
  4399. $mysync->{h1_special_guessed}{$special} = $h1_fold ;
  4400. }
  4401. }
  4402. }
  4403. foreach my $h2_fold ( sort keys %{ $mysync->{h2_folders_all} } ) {
  4404. my $special = guess_special( $h2_fold, $mysync->{possible_special}, $mysync->{h2_prefix} ) ;
  4405. if ( $special ) {
  4406. $mysync->{h2_special_guessed}{$h2_fold} = $special ;
  4407. my $already_guessed = $mysync->{h2_special_guessed}{$special} ;
  4408. if ( $already_guessed ) {
  4409. myprint( "Host2: $h2_fold not $special because set to $already_guessed\n" ) ;
  4410. }else{
  4411. $mysync->{h2_special_guessed}{$special} = $h2_fold ;
  4412. }
  4413. }
  4414. }
  4415. return ;
  4416. }
  4417. sub guess_special
  4418. {
  4419. my( $folder, $possible_special_ref, $prefix ) = @_ ;
  4420. my $folder_no_prefix = $folder ;
  4421. $folder_no_prefix =~ s/\Q${prefix}\E//xms ;
  4422. #$debug and myprint( "folder_no_prefix: $folder_no_prefix\n" ) ;
  4423. my $guess_special = $possible_special_ref->{ $folder }
  4424. || $possible_special_ref->{ $folder_no_prefix }
  4425. || q{} ;
  4426. return( $guess_special ) ;
  4427. }
  4428. sub tests_guess_special
  4429. {
  4430. note( 'Entering tests_guess_special()' ) ;
  4431. my $possible_special_ref = build_possible_special( my $mysync ) ;
  4432. ok( '\Sent' eq guess_special( 'Sent', $possible_special_ref, q{} ) ,'guess_special: Sent => \Sent' ) ;
  4433. ok( q{} eq guess_special( 'Blabla', $possible_special_ref, q{} ) ,'guess_special: Blabla => q{}' ) ;
  4434. ok( '\Sent' eq guess_special( 'INBOX.Sent', $possible_special_ref, 'INBOX.' ) ,'guess_special: INBOX.Sent => \Sent' ) ;
  4435. ok( '\Sent' eq guess_special( 'IN BOX.Sent', $possible_special_ref, 'IN BOX.' ) ,'guess_special: IN BOX.Sent => \Sent' ) ;
  4436. note( 'Leaving tests_guess_special()' ) ;
  4437. return ;
  4438. }
  4439. sub build_automap
  4440. {
  4441. my $mysync = shift ;
  4442. $mysync->{ debug } and myprint( "Entering build_automap\n" ) ;
  4443. foreach my $h1_fold ( @{ $mysync->{h1_folders_wanted} } ) {
  4444. my $h2_fold ;
  4445. my $h1_special = $mysync->{h1_special}{$h1_fold} ;
  4446. my $h1_special_guessed = $mysync->{h1_special_guessed}{$h1_fold} ;
  4447. # Case 1: special on both sides.
  4448. if ( $h1_special
  4449. and exists $mysync->{h2_special}{$h1_special} ) {
  4450. $h2_fold = $mysync->{h2_special}{$h1_special} ;
  4451. $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ;
  4452. next ;
  4453. }
  4454. # Case 2: special on host1, not on host2
  4455. if ( $h1_special
  4456. and ( not exists $mysync->{h2_special}{$h1_special} )
  4457. and ( exists $mysync->{h2_special_guessed}{$h1_special} )
  4458. ) {
  4459. # special_guessed on host2
  4460. $h2_fold = $mysync->{h2_special_guessed}{$h1_special} ;
  4461. $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ;
  4462. next ;
  4463. }
  4464. # Case 3: no special on host1, special on host2
  4465. if ( ( not $h1_special )
  4466. and ( $h1_special_guessed )
  4467. and ( exists $mysync->{h2_special}{$h1_special_guessed} )
  4468. ) {
  4469. $h2_fold = $mysync->{h2_special}{$h1_special_guessed} ;
  4470. $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ;
  4471. next ;
  4472. }
  4473. # Case 4: no special on both sides.
  4474. if ( ( not $h1_special )
  4475. and ( $h1_special_guessed )
  4476. and ( not exists $mysync->{h2_special}{$h1_special_guessed} )
  4477. and ( exists $mysync->{h2_special_guessed}{$h1_special_guessed} )
  4478. ) {
  4479. $h2_fold = $mysync->{h2_special_guessed}{$h1_special_guessed} ;
  4480. $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ;
  4481. next ;
  4482. }
  4483. }
  4484. return( $mysync->{f1f2auto} ) ;
  4485. }
  4486. # I will not add what there is at:
  4487. # http://stackoverflow.com/questions/2185391/localized-gmail-imap-folders/2185548#2185548
  4488. # because it works well without
  4489. sub build_possible_special
  4490. {
  4491. my $mysync = shift ;
  4492. my $possible_special = { } ;
  4493. # All|Archive|Drafts|Flagged|Junk|Sent|Trash
  4494. $possible_special->{'\All'} = [ 'All', 'All Messages', '&BBIEQQQ1-' ] ;
  4495. $possible_special->{'\Archive'} = [ 'Archive', 'Archives', '&BBAEQARFBDgEMg-' ] ;
  4496. $possible_special->{'\Drafts'} = [ 'Drafts', 'DRAFTS', '&BCcENQRABD0EPgQyBDgEOgQ4-', 'Szkice', 'Wersje robocze' ] ;
  4497. $possible_special->{'\Flagged'} = [ 'Flagged', 'Starred', '&BB8EPgQ8BDUERwQ1BD0EPQRLBDU-' ] ;
  4498. $possible_special->{'\Junk'} = [ 'Junk', 'junk', 'Spam', 'SPAM', '&BCEEPwQwBDw-',
  4499. 'Potwierdzony spam', 'Wiadomo&AVs-ci-&AVs-mieci',
  4500. 'Junk E-Mail', 'Junk Email'] ;
  4501. $possible_special->{'\Sent'} = [ 'Sent', 'Sent Messages', 'Sent Items',
  4502. 'Gesendete Elemente', 'Gesendete Objekte',
  4503. '&AMk-l&AOk-ments envoy&AOk-s', 'E&AwE-le&AwE-ments envoye&AwE-s', 'Envoy&AOk-', 'Objets envoy&AOk-s',
  4504. 'Elementos enviados',
  4505. '&kAFP4W4IMH8wojCkMMYw4A-',
  4506. '&BB4EQgQ,BEAEMAQyBDsENQQ9BD0ESwQ1-',
  4507. 'Elementy wys&AUI-ane'] ;
  4508. $possible_special->{'\Trash'} = [ 'Trash', 'TRASH',
  4509. '&BCMENAQwBDsENQQ9BD0ESwQ1-', '&BBoEPgRABDcEOAQ9BDA-',
  4510. 'Kosz',
  4511. 'Deleted Items', 'Deleted Messages' ] ;
  4512. foreach my $special ( qw( \All \Archive \Drafts \Flagged \Junk \Sent \Trash ) ){
  4513. foreach my $possible_folder ( @{ $possible_special->{$special} } ) {
  4514. $possible_special->{ $possible_folder } = $special ;
  4515. } ;
  4516. }
  4517. $mysync->{possible_special} = $possible_special ;
  4518. $mysync->{ debug } and myprint( Data::Dumper->Dump( [ $possible_special ], [ 'possible_special' ] ) ) ;
  4519. return( $possible_special ) ;
  4520. }
  4521. sub tests_special_from_folders_hash
  4522. {
  4523. note( 'Entering tests_special_from_folders_hash()' ) ;
  4524. my $mysync = {} ;
  4525. require_ok( "Test::MockObject" ) ;
  4526. my $imapT = Test::MockObject->new( ) ;
  4527. is( undef, special_from_folders_hash( ), 'special_from_folders_hash: no args' ) ;
  4528. is( undef, special_from_folders_hash( $mysync ), 'special_from_folders_hash: undef args' ) ;
  4529. is_deeply( {}, special_from_folders_hash( $mysync, $imapT ), 'special_from_folders_hash: $imap void' ) ;
  4530. $imapT->mock( 'folders_hash', sub { return( [ { name => 'Sent', attrs => [ '\Sent' ] } ] ) } ) ;
  4531. is_deeply( { Sent => '\Sent', '\Sent' => 'Sent' },
  4532. special_from_folders_hash( $mysync, $imapT ), 'special_from_folders_hash: $imap \Sent' ) ;
  4533. note( 'Leaving tests_special_from_folders_hash()' ) ;
  4534. return( ) ;
  4535. }
  4536. sub special_from_folders_hash
  4537. {
  4538. my ( $mysync, $imap, $side ) = @_ ;
  4539. my %special = ( ) ;
  4540. if ( ! defined $imap ) { return ; }
  4541. $side = defined $side ? $side : 'Host?' ;
  4542. if ( ! $imap->can( 'folders_hash' ) ) {
  4543. my $error = "$side: To have automagic rfc6154 folder mapping, upgrade Mail::IMAPClient >= 3.34\n" ;
  4544. errors_incr( $mysync, $error ) ;
  4545. return( \%special ) ; # empty hash ref
  4546. }
  4547. my $folders_hash = $imap->folders_hash( ) ;
  4548. foreach my $fhash (@{ $folders_hash } ) {
  4549. my @special = grep { /\\(?:All|Archive|Drafts|Flagged|Junk|Sent|Trash)/x } @{ $fhash->{attrs} } ;
  4550. if ( @special ) {
  4551. my $special = $special[0] ; # keep first one. Could be not very good.
  4552. if ( exists $special{ $special } ) {
  4553. myprintf( "%s: special %-20s = %s already assigned to %s\n",
  4554. $side, $fhash->{name}, join( q{ }, @special ), $special{ $special } ) ;
  4555. }else{
  4556. myprintf( "%s: special %-20s = %s\n",
  4557. $side, $fhash->{name}, join( q{ }, @special ) ) ;
  4558. $special{ $special } = $fhash->{name} ;
  4559. $special{ $fhash->{name} } = $special ; # double entry value => key
  4560. }
  4561. }
  4562. }
  4563. myprint( "\n" ) if ( %special ) ;
  4564. return( \%special ) ;
  4565. }
  4566. sub tests_errors_log
  4567. {
  4568. note( 'Entering tests_errors_log()' ) ;
  4569. is( undef, errors_log( ), 'errors_log: no args => undef' ) ;
  4570. my $mysync = {} ;
  4571. is( undef, errors_log( $mysync ), 'errors_log: empty => undef' ) ;
  4572. is_deeply( [ 'aieaie' ], [ errors_log( $mysync, 'aieaie' ) ], 'errors_log: aieaie => aieaie' ) ;
  4573. # cumulative
  4574. is_deeply( [ 'aieaie' ], [ errors_log( $mysync ) ], 'errors_log: nothing more => aieaie' ) ;
  4575. is_deeply( [ 'aieaie', 'ouille' ], [ errors_log( $mysync, 'ouille' ) ], 'errors_log: ouille => aieaie ouille' ) ;
  4576. is_deeply( [ 'aieaie', 'ouille' ], [ errors_log( $mysync ) ], 'errors_log: nothing more => aieaie ouille' ) ;
  4577. note( 'Leaving tests_errors_log()' ) ;
  4578. return ;
  4579. }
  4580. sub errors_log
  4581. {
  4582. my ( $mysync, @error ) = @ARG ;
  4583. if ( ! $mysync->{errors_log} ) {
  4584. $mysync->{errors_log} = [] ;
  4585. }
  4586. if ( @error ) {
  4587. push @{ $mysync->{errors_log} }, join( q{}, @error ) ;
  4588. }
  4589. if ( @{ $mysync->{errors_log} } ) {
  4590. return @{ $mysync->{errors_log} } ;
  4591. }
  4592. else {
  4593. return ;
  4594. }
  4595. }
  4596. sub tests_comment_of_error_type
  4597. {
  4598. note( 'Entering tests_comment_of_error_type()' ) ;
  4599. is( undef, comment_of_error_type( ), 'comment_of_error_type: no args => undef' ) ;
  4600. my $mysync = { } ;
  4601. is( undef, comment_of_error_type( $mysync ), 'comment_of_error_type: undef => undef' ) ;
  4602. is( "", comment_of_error_type( $mysync, '' ), 'comment_of_error_type: "" => ""' ) ;
  4603. is( "", comment_of_error_type( $mysync, 'blabla' ), 'comment_of_error_type: blabla => ""' ) ;
  4604. is( "", comment_of_error_type( $mysync, 'ERR_UNCLASSIFIED' ), 'comment_of_error_type: ERR_UNCLASSIFIED => ""' ) ;
  4605. like( comment_of_error_type( $mysync, 'ERR_OVERQUOTA' ), qr{100% full}, 'comment_of_error_type: ERR_OVERQUOTA => matches 100% full' ) ;
  4606. note( 'Leaving tests_comment_of_error_type()' ) ;
  4607. return ;
  4608. }
  4609. sub comment_of_error_type
  4610. {
  4611. my $mysync = shift @ARG ;
  4612. my $error_type = shift @ARG ;
  4613. if ( ! defined $mysync ) { return ; }
  4614. if ( ! defined $error_type ) { return ; }
  4615. my $comment ;
  4616. if ( exists( $COMMENT_OF_ERR_TYPE{ $error_type } ) )
  4617. {
  4618. $comment = $COMMENT_OF_ERR_TYPE{ $error_type }->( $mysync ) ;
  4619. }
  4620. else
  4621. {
  4622. $comment = "" ;
  4623. }
  4624. return $comment ;
  4625. }
  4626. sub tests_error_type
  4627. {
  4628. note( 'Entering tests_error_type()' ) ;
  4629. is( 'ERR_NOTHING_REPORTED', error_type( ), 'error_type: no args => ERR_NOTHING_REPORTED' ) ;
  4630. is( 'ERR_NOTHING_REPORTED', error_type( '' ), 'error_type: empty string => ERR_NOTHING_REPORTED' ) ;
  4631. is( 'ERR_UNCLASSIFIED', error_type( 'ERR_UNCLASSIFIED' ), 'error_type: ERR_UNCLASSIFIED => ERR_UNCLASSIFIED' ) ;
  4632. is( 'ERR_UNCLASSIFIED', error_type( 'aie' ), 'error_type: aie => ERR_UNCLASSIFIED' ) ;
  4633. is( 'ERR_UNCLASSIFIED', error_type( 'ouille' ), 'error_type: ouille => ERR_UNCLASSIFIED' ) ;
  4634. is( 'ERR_Host1_FETCH', error_type( 'Message xxx could not be fetched: blabla' ),
  4635. 'error_type: could not be fetched => ERR_Host1_FETCH'
  4636. ) ;
  4637. is( 'ERR_APPEND_SIZE',
  4638. error_type( 'could not append message xxx: BAD maximum message size exceeded' ),
  4639. 'error_type: could not append message xxx: BAD maximum message size exceeded => ERR_APPEND_SIZE'
  4640. ) ;
  4641. is( 'ERR_OVERQUOTA',
  4642. error_type( 'Quota limit will be exceeded' ),
  4643. 'error_type: Quota limit will be exceeded => ERR_OVERQUOTA'
  4644. ) ;
  4645. is( 'ERR_APPEND', error_type( 'could not append' ), 'error_type: could not append => ERR_APPEND' ) ;
  4646. is( 'ERR_CREATE',
  4647. error_type( 'Could not create folder' ),
  4648. 'error_type: Could not create folder => ERR_CREATE'
  4649. ) ;
  4650. is( 'ERR_SELECT',
  4651. error_type( 'Could not select: blabla' ),
  4652. 'error_type: Could not select: blabla => ERR_SELECT'
  4653. ) ;
  4654. #
  4655. #Maximum bytes transferred reached, 423 >= 100, ending sync
  4656. is( 'ERR_TRANSFER_EXCEEDED',
  4657. error_type( 'Maximum bytes transferred reached, blabla' ),
  4658. 'error_type: Maximum bytes transferred reached, blabla => ERR_TRANSFER_EXCEEDED'
  4659. ) ;
  4660. #
  4661. is( 'ERR_CONNECTION_FAILURE_HOST1',
  4662. error_type( 'Host1 failure: can not open imap connection on host1 [badhostkaka] with user [tata]: Unable to connect to badhostkaka: Invalid argument' ),
  4663. 'error_type: can not open imap connection on host1 => ERR_CONNECTION_FAILURE_HOST1'
  4664. ) ;
  4665. is( 'ERR_CONNECTION_FAILURE_HOST2',
  4666. error_type( 'Host2 failure: can not open imap connection on host2 [badhostkiki] with user [titi]: Unable to connect to badhostkiki: Invalid argument' ),
  4667. 'error_type: can not open imap connection on host2 => ERR_CONNECTION_FAILURE_HOST2'
  4668. ) ;
  4669. is( 'ERR_APPEND_VIRUS',
  4670. error_type( 'could not append ( Subject:[For Your Consideration], Date:["29-Nov-2016 03:21:10 -0800"], Size:[5505], Flags:[\Seen] ) to folder INBOX: 275 NO Message refused because it contains a virus' ),
  4671. 'error_type: could not append ... virus => ERR_APPEND_VIRUS'
  4672. ) ;
  4673. is( 'ERR_FLAGS',
  4674. error_type( 'Host2: flags msg INBOX/957910 could not add flags [PasGlop \PasGlopRe]: 33 NO Error in IMAP command received by server.' ),
  4675. 'error_type: could not add flags => ERR_FLAGS'
  4676. ) ;
  4677. note( 'Leaving tests_error_type()' ) ;
  4678. return ;
  4679. }
  4680. # Could be implemented with https://metacpan.org/pod/Tie::RegexpHash
  4681. # with just a hash of error regexes as keys and types as values.
  4682. sub error_type
  4683. {
  4684. my $error = shift ;
  4685. if ( ! defined $error ) { return 'ERR_NOTHING_REPORTED' ; }
  4686. if ( ! $error ) { return 'ERR_NOTHING_REPORTED' ; }
  4687. #
  4688. if ( $error =~ m{Host1 failure: Error login on} ) { return 'ERR_AUTHENTICATION_FAILURE_USER1' } ;
  4689. if ( $error =~ m{Host2 failure: Error login on} ) { return 'ERR_AUTHENTICATION_FAILURE_USER2' } ;
  4690. if ( $error =~ m{Host. failure: Can not go to tls encryption on host.} ) { return 'ERR_EXIT_TLS_FAILURE' } ;
  4691. #
  4692. if ( $error =~ m{could not be fetched:} ) { return 'ERR_Host1_FETCH' } ;
  4693. # could not append .*BAD maximum message size exceeded
  4694. # could not append.*Maximum size of appendable message has been exceeded
  4695. if ( $error =~ m{could not append .*BAD maximum message size exceeded} )
  4696. { return 'ERR_APPEND_SIZE' ; } ;
  4697. if ( $error =~ m{could not append.*Maximum size of appendable message has been exceeded} )
  4698. { return 'ERR_APPEND_SIZE' ; } ;
  4699. # Could not create folder *[OVERQUOTA] Not enough disk quota
  4700. # could not append .*[OVERQUOTA] Not enough disk quota
  4701. # could not append .*[OVERQUOTA] Mailbox is full / Blocks limit exceeded / Inode limit exceeded
  4702. if ( $error =~ m{OVERQUOTA} ) { return 'ERR_OVERQUOTA' ; } ;
  4703. if ( $error =~ m{Quota limit will be exceeded} ) { return 'ERR_OVERQUOTA' ; } ;
  4704. if ( $error =~ m{full: it is time to find a bigger place} ) { return 'ERR_OVERQUOTA' ; } ;
  4705. # could not append ... to folder INBOX: 276 NO Message refused because it contains a virus
  4706. if ( $error =~ m{could not append.*virus} )
  4707. { return 'ERR_APPEND_VIRUS' ; } ;
  4708. # could not append .*Write failed 'Broken pipe'
  4709. # could not append .*timeout waiting .* for data from server
  4710. # could not append .*BAD Invalid Arguments: Unable to parse message
  4711. # could not append .*BAD Command Argument Error. 11
  4712. # could not append .*NO header limit reached
  4713. if ( $error =~ m{could not append} ) { return 'ERR_APPEND' ; } ;
  4714. # could not add flags
  4715. if ( $error =~ m{could not add flags} ) { return 'ERR_FLAGS' ; } ;
  4716. # Could not create folder .*Invalid mailbox name
  4717. if ( $error =~ m{Could not create folder} ) { return 'ERR_CREATE' ; } ;
  4718. # Could not select:.*NO [NOPERM] Permission denied
  4719. # Could not select:.*NO Mailbox doesn't exist
  4720. # Could not select:.*NO [SERVERBUG] Internal error occurred.
  4721. # Could not select:.*[CANNOT] Mailbox isn't a valid mbox file
  4722. if ( $error =~ m{Could not select:} ) { return 'ERR_SELECT' ; } ;
  4723. #Maximum bytes transferred reached, 423 >= 100, ending sync
  4724. if ( $error =~ m{Maximum bytes transferred reached} ) { return 'ERR_TRANSFER_EXCEEDED' ; } ;
  4725. if ( $error =~ m{can not open imap connection on host1} ) { return 'ERR_CONNECTION_FAILURE_HOST1' ; } ;
  4726. if ( $error =~ m{can not open imap connection on host2} ) { return 'ERR_CONNECTION_FAILURE_HOST2' ; } ;
  4727. # Default is ERR_UNCLASSIFIED
  4728. return 'ERR_UNCLASSIFIED' ;
  4729. }
  4730. sub tests_errorclassify
  4731. {
  4732. note( 'Entering tests_errorclassify()' ) ;
  4733. is( undef, errorclassify( ), 'errorclassify: no args => undef' ) ;
  4734. is_deeply( { 'ERR_UNCLASSIFIED' => 1 }, errorclassify( 'aie' ), 'errorclassify: aie => { ERR_UNCLASSIFIED => 1 }' ) ;
  4735. is_deeply( { 'ERR_UNCLASSIFIED' => 2 }, errorclassify( 'aie', 'ouille' ), 'errorclassify: aie ouille => { ERR_UNCLASSIFIED => 2 }' ) ;
  4736. is_deeply( { 'ERR_UNCLASSIFIED' => 2, 'ERR_NOTHING_REPORTED' => 1 }, errorclassify( 'aie', 'ouille', '' ), 'errorclassify: aie ouille "" => { ERR_UNCLASSIFIED => 2 }' ) ;
  4737. is_deeply( { 'ERR_UNCLASSIFIED' => 3 }, errorclassify( 'aie', 'ouille', 'aie' ), 'errorclassify: aie ouille aie => { ERR_UNCLASSIFIED => 3 }' ) ;
  4738. is_deeply( { 'ERR_UNCLASSIFIED' => 1, 'ERR_OVERQUOTA' => 2 }, errorclassify( 'aie', 'OVERQUOTA pipi', 'OVERQUOTA caca' ), 'errorclassify: aie OVERQUOTA OVERQUOTA' ) ;
  4739. is_deeply( { 'ERR_NOTHING_REPORTED' => 1 }, errorclassify( '' ), 'errorclassify: "" => { ERR_NOTHING_REPORTED => 1 }' ) ;
  4740. is_deeply( { 'ERR_NOTHING_REPORTED' => 2 }, errorclassify( '', '' ), 'errorclassify: "", "" => { ERR_NOTHING_REPORTED => 1 }' ) ;
  4741. note( 'Leaving tests_errorclassify()' ) ;
  4742. return ;
  4743. }
  4744. sub errorclassify
  4745. {
  4746. my @errors = @ARG ;
  4747. if ( ! @errors ) { return ; } ;
  4748. my $error_type_count = { } ;
  4749. foreach my $error ( @errors )
  4750. {
  4751. my $error_type = error_type( $error ) ;
  4752. $error_type_count->{ $error_type }++ ;
  4753. }
  4754. return $error_type_count ;
  4755. }
  4756. sub tests_most_common_error
  4757. {
  4758. note( 'Entering tests_most_common_error()' ) ;
  4759. is( 'ERR_NOTHING_REPORTED', most_common_error( ), 'most_common_error: no args => ERR_NOTHING_REPORTED' ) ;
  4760. is( 'ERR_NOTHING_REPORTED', most_common_error( {} ), 'most_common_error: empty hash ref => ERR_NOTHING_REPORTED' ) ;
  4761. is( 'ERR_NOTHING_REPORTED', most_common_error( 'blabla' ), 'most_common_error: not a hash ref => ERR_NOTHING_REPORTED' ) ;
  4762. is( 'ERR_FOO', most_common_error( { ERR_FOO => 1 } ), 'most_common_error: { ERR_FOO => 1 } => ERR_FOO' ) ;
  4763. is( 'ERR_BAR', most_common_error( { ERR_FOO => 1, ERR_BAR => 2 } ), 'most_common_error: { ERR_FOO => 1, ERR_BAR => 2 } => ERR_BAR' ) ;
  4764. is( 'ERR_FOO', most_common_error( { ERR_FOO => 2, ERR_BAR => 1 } ), 'most_common_error: { ERR_FOO => 2, ERR_BAR => 1 } => ERR_FOO' ) ;
  4765. # exaequo => first lexical wins. ERR_BAR <= ERR_FOO
  4766. is( 'ERR_BAR', most_common_error( { ERR_FOO => 2, ERR_BAR => 2 } ), 'most_common_error: { ERR_FOO => 2, ERR_BAR => 2 } => ERR_BAR' ) ;
  4767. is( 'A', most_common_error( { A => 5, B => 5, C => 5 } ), 'most_common_error: { A => 5, B => 5, C => 5 } => A' ) ;
  4768. is( 'B', most_common_error( { A => 5, B => 6, C => 6 } ), 'most_common_error: { A => 5, B => 6, C => 6 } => B' ) ;
  4769. is( 'C', most_common_error( { A => 5, B => 5, C => 7 } ), 'most_common_error: { A => 5, B => 5, C => 7 } => C' ) ;
  4770. is( 'C', most_common_error( { A => 5, B => 6, C => 7 } ), 'most_common_error: { A => 5, B => 5, C => 7 } => C' ) ;
  4771. note( 'Leaving tests_most_common_error()' ) ;
  4772. return ;
  4773. }
  4774. sub most_common_error
  4775. {
  4776. my $errors_counted_ref = shift ;
  4777. if ( ! defined $errors_counted_ref ) { return 'ERR_NOTHING_REPORTED' ; }
  4778. if ( 'HASH' ne ref $errors_counted_ref ) { return 'ERR_NOTHING_REPORTED' ; }
  4779. # empty hash
  4780. if ( !%{ $errors_counted_ref } ) { return 'ERR_NOTHING_REPORTED' ; }
  4781. # non empty hash
  4782. # in case of equality the winner error is the first in alphabetic order
  4783. my $most_common_error = ( sort
  4784. {
  4785. $errors_counted_ref->{$b} <=> $errors_counted_ref->{$a}
  4786. || $a cmp $b
  4787. } keys %{$errors_counted_ref} )[0] ;
  4788. return $most_common_error ;
  4789. }
  4790. sub tests_errorsanalyse
  4791. {
  4792. note( 'Entering tests_errorsanalyse()' ) ;
  4793. is( 'ERR_NOTHING_REPORTED', errorsanalyse( ), 'errorsanalyse: no args => ERR_NOTHING_REPORTED' ) ;
  4794. is( 'ERR_NOTHING_REPORTED', errorsanalyse( ( ) ), 'errorsanalyse: empty list => ERR_NOTHING_REPORTED' ) ;
  4795. is( 'ERR_UNCLASSIFIED', errorsanalyse( 'aie' ), 'errorsanalyse: aie => ERR_UNCLASSIFIED' ) ;
  4796. # in case of equality, empty wins
  4797. is( 'ERR_NOTHING_REPORTED', errorsanalyse( 'aie', '' ), 'errorsanalyse: aie => ERR_UNCLASSIFIED' ) ;
  4798. is( 'ERR_NOTHING_REPORTED', errorsanalyse( '', 'aie' ), 'errorsanalyse: aie => ERR_UNCLASSIFIED' ) ;
  4799. is( 'ERR_UNCLASSIFIED', errorsanalyse( 'aie', 'ouille' ), 'errorsanalyse: aie, ouille => ERR_UNCLASSIFIED' ) ;
  4800. is( 'ERR_UNCLASSIFIED', errorsanalyse( 'aie', 'ouille', '' ), 'errorsanalyse: aie, ouille, "" => ERR_UNCLASSIFIED' ) ;
  4801. is( 'ERR_UNCLASSIFIED', errorsanalyse( '', 'aie', 'ouille' ), 'errorsanalyse: aie, ouille, "" => ERR_UNCLASSIFIED' ) ;
  4802. is( 'ERR_NOTHING_REPORTED', errorsanalyse( '' ), 'errorsanalyse: "" => ERR_NOTHING_REPORTED' ) ;
  4803. is( 'ERR_NOTHING_REPORTED', errorsanalyse( ( '' ) ), 'errorsanalyse: ( "" ) => ERR_NOTHING_REPORTED' ) ;
  4804. is( 'ERR_NOTHING_REPORTED', errorsanalyse( ( '', '' ) ), 'errorsanalyse: ( "", "" ) => ERR_NOTHING_REPORTED' ) ;
  4805. note( 'Leaving tests_errorsanalyse()' ) ;
  4806. return ;
  4807. }
  4808. sub errorsanalyse
  4809. {
  4810. my @errors = @ARG ;
  4811. my $errors_types_counted = errorclassify( @errors ) ;
  4812. my $most_common_error = most_common_error( $errors_types_counted ) ;
  4813. return $most_common_error ;
  4814. }
  4815. sub tests_errorsdump
  4816. {
  4817. note( 'Entering tests_errorsdump()' ) ;
  4818. is( undef, errorsdump( ), 'errorsdump: no args => undef' ) ;
  4819. is( undef, errorsdump( ( ) ), 'errorsdump: empty list => undef' ) ;
  4820. is( "Err 1/1: ", errorsdump( '' ), 'errorsdump: one empty string => "Err 1/1: "' ) ;
  4821. is( "Err 1/1: aieaieaie", errorsdump( 'aieaieaie' ), 'errorsdump: aieaieaie => "Err 1/1: aieaieaie"' ) ;
  4822. is( "Err 1/2: Aie Err 2/2: Ouille", errorsdump( 'Aie ', 'Ouille' ), 'errorsdump: Aie Ouille => "Err 1/2: Aie Err 2/2: Ouille"' ) ;
  4823. note( 'Leaving tests_errorsdump()' ) ;
  4824. return ;
  4825. }
  4826. sub errorsdump
  4827. {
  4828. if ( ! @ARG ) { return ; }
  4829. my @errors_log = @ARG ;
  4830. my $nb_errors = @errors_log ;
  4831. my $error_num = 0 ;
  4832. my $errors_list = q{} ;
  4833. if ( @errors_log ) {
  4834. foreach my $error ( @errors_log )
  4835. {
  4836. $error_num++ ;
  4837. $errors_list .= "Err $error_num/$nb_errors: $error" ;
  4838. }
  4839. }
  4840. return( $errors_list ) ;
  4841. }
  4842. sub errors_listing
  4843. {
  4844. my $mysync = shift ;
  4845. $mysync->{ most_common_error } = errorsanalyse( errors_log( $mysync ) ) ;
  4846. my $errors_listing = '' ;
  4847. if ( $mysync->{ errorsdump } )
  4848. {
  4849. $errors_listing = join( '',
  4850. "++++ Listing $mysync->{nb_errors} errors encountered during the sync ( avoid this listing with --noerrorsdump ).\n",
  4851. errorsdump( errors_log( $mysync ) ),
  4852. ) ;
  4853. }
  4854. $errors_listing .= join( '',
  4855. "The most frequent error is $mysync->{ most_common_error }. ",
  4856. comment_of_error_type( $mysync, $mysync->{ most_common_error } ),
  4857. "\n",
  4858. ) ;
  4859. return $errors_listing ;
  4860. }
  4861. sub errors_incr
  4862. {
  4863. my ( $mysync, @error ) = @ARG ;
  4864. $mysync->{ nb_errors }++ ;
  4865. if ( @error ) {
  4866. errors_log( $mysync, @error ) ;
  4867. myprint( @error ) ;
  4868. }
  4869. $mysync->{ errorsmax } ||= $ERRORS_MAX ;
  4870. if ( $mysync->{ nb_errors } >= $mysync->{ errorsmax } )
  4871. {
  4872. myprint( errorsmax_msg( $mysync ) ) ;
  4873. myprint( errors_listing( $mysync ) ) ;
  4874. if ( $mysync->{ errorsdump } )
  4875. {
  4876. # again since errorsdump( ) can be very verbose and masquerade previous warning
  4877. myprint( errorsmax_msg( $mysync ) ) ;
  4878. }
  4879. my $exit_value = exit_value( $mysync, $mysync->{ most_common_error } ) ;
  4880. exit_clean( $mysync, $exit_value ) ;
  4881. }
  4882. return ;
  4883. }
  4884. sub errorsmax_msg
  4885. {
  4886. my $mysync = shift @ARG ;
  4887. my $msg = "Maximum number of errors $mysync->{errorsmax} reached "
  4888. . "( you can change $mysync->{errorsmax} to any value, for example 100 with --errorsmax 100 ). "
  4889. . "Exiting.\n" ;
  4890. return $msg ;
  4891. }
  4892. sub tests_live_result
  4893. {
  4894. note( 'Entering tests_live_result()' ) ;
  4895. my $nb_errors = shift ;
  4896. if ( $nb_errors ) {
  4897. myprint( "Live tests failed with $nb_errors errors\n" ) ;
  4898. } else {
  4899. myprint( "Live tests ended successfully\n" ) ;
  4900. }
  4901. note( 'Leaving tests_live_result()' ) ;
  4902. return ;
  4903. }
  4904. sub size_filtered_flag
  4905. {
  4906. my $mysync = shift ;
  4907. my $h1_size = shift ;
  4908. if ( defined $mysync->{ maxsize } and $h1_size >= $mysync->{ maxsize } ) {
  4909. return( 1 ) ;
  4910. }
  4911. if ( defined $minsize and $h1_size <= $minsize ) {
  4912. return( 1 ) ;
  4913. }
  4914. return( 0 ) ;
  4915. }
  4916. sub sync_flags_fir
  4917. {
  4918. my ( $mysync, $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) = @_ ;
  4919. if ( not defined $h1_msg ) { return } ;
  4920. if ( not defined $h2_msg ) { return } ;
  4921. my $h1_size = $h1_fir_ref->{$h1_msg}->{'RFC822.SIZE'} ;
  4922. return if size_filtered_flag( $mysync, $h1_size ) ;
  4923. # used cached flag values for efficiency
  4924. my $h1_flags = $h1_fir_ref->{ $h1_msg }->{ 'FLAGS' } || q{} ;
  4925. my $h2_flags = $h2_fir_ref->{ $h2_msg }->{ 'FLAGS' } || q{} ;
  4926. sync_flags( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) ;
  4927. return ;
  4928. }
  4929. sub sync_flags_after_copy
  4930. {
  4931. # Activated with option --syncflagsaftercopy
  4932. my( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $permanentflags2 ) = @_ ;
  4933. if ( my @h2_flags = $mysync->{imap2}->flags( $h2_msg ) ) {
  4934. my $h2_flags = "@h2_flags" ;
  4935. ( $mysync->{ debug } or $debugflags ) and myprint( "Host2: msg $h2_fold/$h2_msg flags before sync flags after copy ( $h2_flags )\n" ) ;
  4936. sync_flags( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) ;
  4937. }else{
  4938. myprint( "Host2: msg $h2_fold/$h2_msg could not get its flags for sync flags after copy\n" ) ;
  4939. }
  4940. return ;
  4941. }
  4942. # Globals
  4943. # $debug
  4944. # $debugflags
  4945. # $permanentflags2
  4946. sub sync_flags
  4947. {
  4948. my( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) = @_ ;
  4949. ( $mysync->{ debug } or $debugflags ) and
  4950. myprint( "Host1: flags init msg $h1_fold/$h1_msg flags( $h1_flags ) Host2 msg $h2_fold/$h2_msg flags( $h2_flags )\n" ) ;
  4951. $h1_flags = flags_for_host2( $mysync, $h1_flags, $permanentflags2 ) ;
  4952. $h2_flags = flagscase( $h2_flags ) ;
  4953. ( $mysync->{ debug } or $debugflags ) and
  4954. myprint( "Host1: flags filt msg $h1_fold/$h1_msg flags( $h1_flags ) Host2 msg $h2_fold/$h2_msg flags( $h2_flags )\n" ) ;
  4955. # compare flags - set flags if there a difference
  4956. my @h1_flags = sort split(q{ }, $h1_flags );
  4957. my @h2_flags = sort split(q{ }, $h2_flags );
  4958. my $diff = compare_lists( \@h1_flags, \@h2_flags );
  4959. $diff and ( $mysync->{ debug } or $debugflags )
  4960. and myprint( "Host2: flags msg $h2_fold/$h2_msg replacing h2 flags( $h2_flags ) with h1 flags( $h1_flags )\n" ) ;
  4961. # This sets flags exactly. So flags can be removed with this.
  4962. # When you remove a \Seen flag on host1 you want it
  4963. # to be removed on host2. Just add flags is not what
  4964. # we need most of the time, so no + like in "+FLAGS.SILENT".
  4965. if ( not $mysync->{dry} and $diff and not $mysync->{imap2}->store( $h2_msg, "FLAGS.SILENT (@h1_flags)" ) ) {
  4966. my $error_msg = join q{}, "Host2: flags msg $h2_fold/$h2_msg could not add flags [@h1_flags]: ",
  4967. $mysync->{imap2}->LastError || q{}, "\n" ;
  4968. errors_incr( $mysync, $error_msg ) ;
  4969. }
  4970. return ;
  4971. }
  4972. sub _filter
  4973. {
  4974. my $mysync = shift ;
  4975. my $str = shift or return q{} ;
  4976. my $sz = $SIZE_MAX_STR ;
  4977. my $len = length $str ;
  4978. if ( not $mysync->{ debug } and $len > $sz*2 ) {
  4979. my $beg = substr $str, 0, $sz ;
  4980. my $end = substr $str, -$sz, $sz ;
  4981. $str = $beg . '...' . $end ;
  4982. }
  4983. $str =~ s/\012?\015$//x ;
  4984. return "(len=$len) " . $str ;
  4985. }
  4986. sub lost_connection
  4987. {
  4988. my( $mysync, $imap, $error_message ) = @_;
  4989. if ( $imap->IsUnconnected( ) ) {
  4990. $mysync->{nb_errors}++ ;
  4991. my $lcomm = $imap->LastIMAPCommand || q{} ;
  4992. my $einfo = imap_last_error( $imap ) ;
  4993. # if string is long try reduce to a more reasonable size
  4994. $lcomm = _filter( $mysync, $lcomm ) ;
  4995. $einfo = _filter( $mysync, $einfo ) ;
  4996. myprint( "Failure: last command: $lcomm\n") if ( $mysync->{ debug } && $lcomm) ;
  4997. myprint( "Failure: lost connection $error_message: ", $einfo, "\n") ;
  4998. return( 1 ) ;
  4999. }
  5000. else{
  5001. return( 0 ) ;
  5002. }
  5003. }
  5004. sub imap_last_error
  5005. {
  5006. my $imap = shift ;
  5007. my $einfo = $imap->LastError || @{$imap->History}[$LAST] || q{} ;
  5008. chomp( $einfo ) ;
  5009. return( $einfo ) ;
  5010. }
  5011. sub tests_max
  5012. {
  5013. note( 'Entering tests_max()' ) ;
  5014. is( 0, max( 0 ), 'max 0 => 0' ) ;
  5015. is( 1, max( 1 ), 'max 1 => 1' ) ;
  5016. is( $MINUS_ONE, max( $MINUS_ONE ), 'max -1 => -1') ;
  5017. is( undef, max( ), 'max no arg => undef' ) ;
  5018. is( undef, max( undef ), 'undef => undef' ) ;
  5019. is( undef, max( undef, undef ), 'undef, undef => undef' ) ;
  5020. is( $NUMBER_100, max( 1, $NUMBER_100 ), 'max 1 100 => 100' ) ;
  5021. is( $NUMBER_100, max( $NUMBER_100, 1 ), 'max 100 1 => 100' ) ;
  5022. is( $NUMBER_100, max( $NUMBER_100, $NUMBER_42, 1 ), 'max 100 42 1 => 100' ) ;
  5023. is( $NUMBER_100, max( $NUMBER_100, '42', 1 ), 'max 100 42 1 => 100' ) ;
  5024. is( $NUMBER_100, max( '100', '42', 1 ), 'max 100 42 1 => 100' ) ;
  5025. is( $NUMBER_100, max( $NUMBER_100, 'haha', 1 ), 'max 100 haha 1 => 100') ;
  5026. is( $NUMBER_100, max( 'bb', $NUMBER_100, 'haha' ), 'max bb 100 haha => 100') ;
  5027. is( $MINUS_ONE, max( q{}, $MINUS_ONE, 'haha' ), 'max "" -1 haha => -1') ;
  5028. is( $MINUS_ONE, max( q{}, $MINUS_ONE, $MINUS_TWO ), 'max "" -1 -2 => -1') ;
  5029. is( $MINUS_ONE, max( 'haha', $MINUS_ONE, $MINUS_TWO ), 'max haha -1 -2 => -1') ;
  5030. is( 1, max( $MINUS_ONE, 1 ), 'max -1 1 => 1') ;
  5031. is( 1, max( undef, 1 ), 'max undef 1 => 1' ) ;
  5032. is( 0, max( undef, 0 ), 'max undef 0 => 0' ) ;
  5033. is( 'haha', max( 'haha' ), 'max haha => haha') ;
  5034. is( 'bb', max( 'aa', 'bb' ), 'max aa bb => bb') ;
  5035. is( 'bb', max( 'bb', 'aa' ), 'max bb aa => bb') ;
  5036. is( 'bb', max( 'bb', 'aa', 'bb' ), 'max bb aa bb => bb') ;
  5037. note( 'Leaving tests_max()' ) ;
  5038. return ;
  5039. }
  5040. sub max
  5041. {
  5042. my @list = @_ ;
  5043. return( undef ) if ( 0 == scalar @list ) ;
  5044. my( @numbers, @notnumbers ) ;
  5045. foreach my $item ( @list )
  5046. {
  5047. if ( is_number( $item ) )
  5048. {
  5049. push @numbers, $item ;
  5050. }
  5051. elsif ( defined $item )
  5052. {
  5053. push @notnumbers, $item ;
  5054. }
  5055. }
  5056. my @sorted ;
  5057. if ( @numbers )
  5058. {
  5059. @sorted = sort { $a <=> $b } @numbers ;
  5060. }
  5061. elsif ( @notnumbers )
  5062. {
  5063. @sorted = sort { $a cmp $b } @notnumbers ;
  5064. }
  5065. else
  5066. {
  5067. return ;
  5068. }
  5069. return( pop @sorted ) ;
  5070. }
  5071. sub tests_is_number
  5072. {
  5073. note( 'Entering tests_is_number()' ) ;
  5074. is( undef, is_number( ), 'is_number: no args => undef ' ) ;
  5075. is( undef, is_number( undef ), 'is_number: undef => undef ' ) ;
  5076. ok( is_number( 1 ), 'is_number: 1 => 1' ) ;
  5077. ok( is_number( 1.1 ), 'is_number: 1.1 => 1' ) ;
  5078. ok( is_number( 0 ), 'is_number: 0 => 1' ) ;
  5079. ok( is_number( -1 ), 'is_number: -1 => 1' ) ;
  5080. ok( ! is_number( 1.1.1 ), 'is_number: 1.1.1 => no' ) ;
  5081. ok( ! is_number( q{} ), 'is_number: q{} => no' ) ;
  5082. ok( ! is_number( 'haha' ), 'is_number: haha => no' ) ;
  5083. ok( ! is_number( '0haha' ), 'is_number: 0haha => no' ) ;
  5084. ok( ! is_number( '2haha' ), 'is_number: 2haha => no' ) ;
  5085. ok( ! is_number( 'haha2' ), 'is_number: haha2 => no' ) ;
  5086. note( 'Leaving tests_is_number()' ) ;
  5087. return ;
  5088. }
  5089. sub is_number
  5090. {
  5091. my $item = shift ;
  5092. if ( ! defined $item ) { return ; }
  5093. if ( $item =~ /\A$RE{num}{real}\Z/ ) {
  5094. return 1 ;
  5095. }
  5096. return ;
  5097. }
  5098. sub tests_min
  5099. {
  5100. note( 'Entering tests_min()' ) ;
  5101. is( 0, min( 0 ), 'min 0 => 0' ) ;
  5102. is( 1, min( 1 ), 'min 1 => 1' ) ;
  5103. is( $MINUS_ONE, min( $MINUS_ONE ), 'min -1 => -1' ) ;
  5104. is( undef, min( ), 'min no arg => undef' ) ;
  5105. is( 1, min( 1, $NUMBER_100 ), 'min 1 100 => 1' ) ;
  5106. is( 1, min( $NUMBER_100, 1 ), 'min 100 1 => 1' ) ;
  5107. is( 1, min( $NUMBER_100, $NUMBER_42, 1 ), 'min 100 42 1 => 1' ) ;
  5108. is( 1, min( $NUMBER_100, '42', 1 ), 'min 100 42 1 => 1' ) ;
  5109. is( 1, min( '100', '42', 1 ), 'min 100 42 1 => 1' ) ;
  5110. is( 1, min( $NUMBER_100, 'haha', 1 ), 'min 100 haha 1 => 1') ;
  5111. is( $MINUS_ONE, min( $MINUS_ONE, 1 ), 'min -1 1 => -1') ;
  5112. is( 1, min( undef, 1 ), 'min undef 1 => 1' ) ;
  5113. is( 0, min( undef, 0 ), 'min undef 0 => 0' ) ;
  5114. is( 1, min( undef, 1 ), 'min undef 1 => 1' ) ;
  5115. is( 0, min( undef, 2, 0, 1 ), 'min undef, 2, 0, 1 => 0' ) ;
  5116. is( 'haha', min( 'haha' ), 'min haha => haha') ;
  5117. is( 'aa', min( 'aa', 'bb' ), 'min aa bb => aa') ;
  5118. is( 'aa', min( 'bb', 'aa' ), 'min bb aa bb => aa') ;
  5119. is( 'aa', min( 'bb', 'aa', 'bb' ), 'min bb aa bb => aa') ;
  5120. note( 'Leaving tests_min()' ) ;
  5121. return ;
  5122. }
  5123. sub min
  5124. {
  5125. my @list = @_ ;
  5126. return( undef ) if ( 0 == scalar @list ) ;
  5127. my( @numbers, @notnumbers ) ;
  5128. foreach my $item ( @list ) {
  5129. if ( is_number( $item ) ) {
  5130. push @numbers, $item ;
  5131. }else{
  5132. push @notnumbers, $item ;
  5133. }
  5134. }
  5135. my @sorted ;
  5136. if ( @numbers ) {
  5137. @sorted = sort { $a <=> $b } @numbers ;
  5138. }elsif( @notnumbers ) {
  5139. @sorted = sort { $a cmp $b } @notnumbers ;
  5140. }else{
  5141. return ;
  5142. }
  5143. return( shift @sorted ) ;
  5144. }
  5145. sub check_lib_version
  5146. {
  5147. my $mysync = shift ;
  5148. $mysync->{ debug } and myprint( "IMAPClient $Mail::IMAPClient::VERSION\n" ) ;
  5149. if ( '2.2.9' eq $Mail::IMAPClient::VERSION ) {
  5150. myprint( "imapsync no longer supports Mail::IMAPClient 2.2.9, upgrade it\n" ) ;
  5151. return 0 ;
  5152. }
  5153. else{
  5154. # 3.x.x is no longer buggy with imapsync.
  5155. # 3.30 or currently superior is imposed in the Perl "use Mail::IMAPClient line".
  5156. return 1 ;
  5157. }
  5158. return ;
  5159. }
  5160. sub module_version_str
  5161. {
  5162. my( $module_name, $module_version ) = @_ ;
  5163. my $str = mysprintf( "%-20s %s\n", $module_name, $module_version ) ;
  5164. return( $str ) ;
  5165. }
  5166. sub modulesversion
  5167. {
  5168. my @list_version;
  5169. my %modulesversion = (
  5170. 'Authen::NTLM' => sub { $Authen::NTLM::VERSION },
  5171. 'CGI' => sub { $CGI::VERSION },
  5172. 'Compress::Zlib' => sub { $Compress::Zlib::VERSION },
  5173. 'Crypt::OpenSSL::RSA' => sub { $Crypt::OpenSSL::RSA::VERSION },
  5174. 'Data::Uniqid' => sub { $Data::Uniqid::VERSION },
  5175. 'Digest::HMAC_MD5' => sub { $Digest::HMAC_MD5::VERSION },
  5176. 'Digest::HMAC_SHA1' => sub { $Digest::HMAC_SHA1::VERSION },
  5177. 'Digest::MD5' => sub { $Digest::MD5::VERSION },
  5178. 'Encode' => sub { $Encode::VERSION },
  5179. 'Encode::IMAPUTF7' => sub { $Encode::IMAPUTF7::VERSION },
  5180. 'File::Copy::Recursive' => sub { $File::Copy::Recursive::VERSION },
  5181. 'File::Spec' => sub { $File::Spec::VERSION },
  5182. 'Getopt::Long' => sub { $Getopt::Long::VERSION },
  5183. 'HTML::Entities' => sub { $HTML::Entities::VERSION },
  5184. 'IO::Socket' => sub { $IO::Socket::VERSION },
  5185. 'IO::Socket::INET' => sub { $IO::Socket::INET::VERSION },
  5186. 'IO::Socket::INET6' => sub { $IO::Socket::INET6::VERSION },
  5187. 'IO::Socket::IP' => sub { $IO::Socket::IP::VERSION },
  5188. 'IO::Socket::SSL' => sub { $IO::Socket::SSL::VERSION },
  5189. 'IO::Tee' => sub { $IO::Tee::VERSION },
  5190. 'JSON' => sub { $JSON::VERSION },
  5191. 'JSON::WebToken' => sub { $JSON::WebToken::VERSION },
  5192. 'LWP' => sub { $LWP::VERSION },
  5193. 'Mail::IMAPClient' => sub { $Mail::IMAPClient::VERSION },
  5194. 'MIME::Base64' => sub { $MIME::Base64::VERSION },
  5195. 'Net::Ping' => sub { $Net::Ping::VERSION },
  5196. 'Net::SSLeay' => sub { $Net::SSLeay::VERSION },
  5197. 'Term::ReadKey' => sub { $Term::ReadKey::VERSION },
  5198. 'Test::MockObject' => sub { $Test::MockObject::VERSION },
  5199. 'Time::HiRes' => sub { $Time::HiRes::VERSION },
  5200. 'Unicode::String' => sub { $Unicode::String::VERSION },
  5201. 'URI::Escape' => sub { $URI::Escape::VERSION },
  5202. #'Lalala' => sub { $Lalala::VERSION },
  5203. ) ;
  5204. foreach my $module_name ( sort keys %modulesversion ) {
  5205. # trick from http://www.perlmonks.org/?node_id=152122
  5206. my $file_name = $module_name . '.pm' ;
  5207. $file_name =~s,::,/,xmgs; # Foo::Bar::Baz => Foo/Bar/Baz.pm
  5208. my $v ;
  5209. eval {
  5210. require $file_name ;
  5211. $v = defined $modulesversion{ $module_name } ? $modulesversion{ $module_name }->() : q{?} ;
  5212. } or $v = q{Not installed} ;
  5213. push @list_version, module_version_str( $module_name, $v ) ;
  5214. }
  5215. return( @list_version ) ;
  5216. }
  5217. sub tests_command_line_nopassword
  5218. {
  5219. note( 'Entering tests_command_line_nopassword()' ) ;
  5220. ok( q{} eq command_line_nopassword(), 'command_line_nopassword void' );
  5221. my $mysync = {} ;
  5222. ok( '--blabla' eq command_line_nopassword( $mysync, '--blabla' ), 'command_line_nopassword --blabla' );
  5223. #myprint( command_line_nopassword((qw{ --password1 secret1 })), "\n" ) ;
  5224. ok( '--password1 MASKED' eq command_line_nopassword( $mysync, qw{ --password1 secret1}), 'command_line_nopassword --password1' );
  5225. ok( '--blabla --password1 MASKED --blibli'
  5226. eq command_line_nopassword( $mysync, qw{ --blabla --password1 secret1 --blibli } ), 'command_line_nopassword --password1 --blibli' );
  5227. $mysync->{showpasswords} = 1 ;
  5228. ok( q{} eq command_line_nopassword(), 'command_line_nopassword void' );
  5229. ok( '--blabla' eq command_line_nopassword( $mysync, '--blabla'), 'command_line_nopassword --blabla' );
  5230. #myprint( command_line_nopassword((qw{ --password1 secret1 })), "\n" ) ;
  5231. ok( '--password1 secret1' eq command_line_nopassword( $mysync, qw{ --password1 secret1} ), 'command_line_nopassword --password1' );
  5232. ok( '--blabla --password1 secret1 --blibli'
  5233. eq command_line_nopassword( $mysync, qw{ --blabla --password1 secret1 --blibli } ), 'command_line_nopassword --password1 --blibli' );
  5234. note( 'Leaving tests_command_line_nopassword()' ) ;
  5235. return ;
  5236. }
  5237. # Construct a command line copy with passwords replaced by MASKED.
  5238. sub command_line_nopassword
  5239. {
  5240. my $mysync = shift @ARG ;
  5241. my @argv = @ARG ;
  5242. my @argv_nopassword ;
  5243. if ( $mysync->{ cmdcgi } ) {
  5244. @argv_nopassword = mask_password_value( @{ $mysync->{ cmdcgi } } ) ;
  5245. return( "@argv_nopassword" ) ;
  5246. }
  5247. if ( $mysync->{showpasswords} )
  5248. {
  5249. return( "@argv" ) ;
  5250. }
  5251. @argv_nopassword = mask_password_value( @argv ) ;
  5252. return("@argv_nopassword") ;
  5253. }
  5254. sub mask_password_value
  5255. {
  5256. my @argv = @ARG ;
  5257. my @argv_nopassword ;
  5258. while ( @argv ) {
  5259. my $arg = shift @argv ; # option name or value
  5260. if ( $arg =~ m/-password[12]/x ) {
  5261. shift @argv ; # password value
  5262. push @argv_nopassword, $arg, 'MASKED' ; # option name and fake value
  5263. }else{
  5264. push @argv_nopassword, $arg ; # same option or value
  5265. }
  5266. }
  5267. return @argv_nopassword ;
  5268. }
  5269. sub tests_get_stdin_masked
  5270. {
  5271. note( 'Entering tests_get_stdin_masked()' ) ;
  5272. is( q{}, get_stdin_masked( ), 'get_stdin_masked: no args' ) ;
  5273. is( q{}, get_stdin_masked( 'Please ENTER: ' ), 'get_stdin_masked: ENTER' ) ;
  5274. note( 'Leaving tests_get_stdin_masked()' ) ;
  5275. return ;
  5276. }
  5277. #######################################################
  5278. # The issue is that prompt() does not prompt the prompt
  5279. # when the program is used like
  5280. # { sleep 2 ; echo blablabla ; } | ./imapsync ...--host1 lo --user1 tata --host2 lo --user2 titi
  5281. # use IO::Prompter ;
  5282. sub get_stdin_masked
  5283. {
  5284. my $prompt = shift || 'Say something: ' ;
  5285. local @ARGV = () ;
  5286. my $input = prompt(
  5287. -prompt => $prompt,
  5288. -echo => '*',
  5289. ) ;
  5290. #myprint( "You said: $input\n" ) ;
  5291. return $input ;
  5292. }
  5293. sub ask_for_password_new
  5294. {
  5295. my $prompt = shift ;
  5296. my $password = get_stdin_masked( $prompt ) ;
  5297. return $password ;
  5298. }
  5299. #########################################################
  5300. sub ask_for_password
  5301. {
  5302. my $prompt = shift ;
  5303. myprint( $prompt ) ;
  5304. Term::ReadKey::ReadMode( 2 ) ;
  5305. ## no critic (InputOutput::ProhibitExplicitStdin)
  5306. my $password = <STDIN> ;
  5307. chomp $password ;
  5308. myprint( "\nGot it\n" ) ;
  5309. Term::ReadKey::ReadMode( 0 ) ;
  5310. return $password ;
  5311. }
  5312. # Have to refactor get_password1() get_password2()
  5313. # to have only get_password() and two calls
  5314. sub get_password1
  5315. {
  5316. my $mysync = shift ;
  5317. $mysync->{ password1 }
  5318. || $mysync->{ passfile1 }
  5319. || 'PREAUTH' eq $mysync->{ acc1 }->{ authmech }
  5320. || 'EXTERNAL' eq $mysync->{ acc1 }->{ authmech }
  5321. || $ENV{IMAPSYNC_PASSWORD1}
  5322. || do
  5323. {
  5324. myprint( << 'FIN_PASSFILE' ) ;
  5325. If you are afraid of giving password on the command line arguments, you can put the
  5326. password of user1 in a file named file1 and use "--passfile1 file1" instead of typing it.
  5327. Then give this file restrictive permissions with the command "chmod 600 file1".
  5328. An other solution is to set the environment variable IMAPSYNC_PASSWORD1
  5329. FIN_PASSFILE
  5330. my $user = $mysync->{ acc1 }->{ authuser } || $mysync->{ user1 } ;
  5331. my $host = $mysync->{ host1 } ;
  5332. my $prompt = "What's the password for $user" . ' at ' . "$host? (not visible while you type, then enter RETURN) " ;
  5333. $mysync->{password1} = ask_for_password( $prompt ) ;
  5334. } ;
  5335. if ( defined $mysync->{ passfile1 } ) {
  5336. if ( ! -e -r $mysync->{ passfile1 } ) {
  5337. myprint( "Failure: file from parameter --passfile1 $mysync->{ passfile1 } does not exist or is not readable\n" ) ;
  5338. $mysync->{nb_errors}++ ;
  5339. exit_clean( $mysync, $EX_NOINPUT ) ;
  5340. }
  5341. # passfile1 readable
  5342. $mysync->{password1} = firstline ( $mysync->{ passfile1 } ) ;
  5343. return ;
  5344. }
  5345. if ( $ENV{IMAPSYNC_PASSWORD1} ) {
  5346. $mysync->{password1} = $ENV{IMAPSYNC_PASSWORD1} ;
  5347. return ;
  5348. }
  5349. return ;
  5350. }
  5351. sub get_password2
  5352. {
  5353. my $mysync = shift ;
  5354. $mysync->{password2}
  5355. || $mysync->{ passfile2 }
  5356. || 'PREAUTH' eq $mysync->{ acc2 }->{ authmech }
  5357. || 'EXTERNAL' eq $mysync->{ acc2 }->{ authmech }
  5358. || $ENV{IMAPSYNC_PASSWORD2}
  5359. || do
  5360. {
  5361. myprint( << 'FIN_PASSFILE' ) ;
  5362. If you are afraid of giving password on the command line arguments, you can put the
  5363. password of user2 in a file named file2 and use "--passfile2 file2" instead of typing it.
  5364. Then give this file restrictive permissions with the command "chmod 600 file2".
  5365. An other solution is to set the environment variable IMAPSYNC_PASSWORD2
  5366. FIN_PASSFILE
  5367. my $user = $mysync->{ acc2 }->{ authuser } || $mysync->{ user2 } ;
  5368. my $host = $mysync->{ host2 } ;
  5369. my $prompt = "What's the password for $user" . ' at ' . "$host? (not visible while you type, then enter RETURN) " ;
  5370. $mysync->{password2} = ask_for_password( $prompt ) ;
  5371. } ;
  5372. if ( defined $mysync->{ passfile2 } ) {
  5373. if ( ! -e -r $mysync->{ passfile2 } ) {
  5374. myprint( "Failure: file from parameter --passfile2 $mysync->{ passfile2 } does not exist or is not readable\n" ) ;
  5375. $mysync->{nb_errors}++ ;
  5376. exit_clean( $mysync, $EX_NOINPUT ) ;
  5377. }
  5378. # passfile2 readable
  5379. $mysync->{password2} = firstline ( $mysync->{ passfile2 } ) ;
  5380. return ;
  5381. }
  5382. if ( $ENV{IMAPSYNC_PASSWORD2} ) {
  5383. $mysync->{password2} = $ENV{IMAPSYNC_PASSWORD2} ;
  5384. return ;
  5385. }
  5386. return ;
  5387. }
  5388. sub remove_tmp_files
  5389. {
  5390. my $mysync = shift or return ;
  5391. $mysync->{pidfile} or return ;
  5392. if ( -e $mysync->{pidfile} ) {
  5393. myprint( "Removing pidfile $mysync->{pidfile}\n" ) ;
  5394. unlink $mysync->{pidfile} ;
  5395. }
  5396. if ( -e $mysync->{abortfile} ) {
  5397. myprint( "Removing pidfile $mysync->{abortfile}\n" ) ;
  5398. unlink $mysync->{abortfile} ;
  5399. }
  5400. return ;
  5401. }
  5402. sub cleanup_before_exit
  5403. {
  5404. my $mysync = shift ;
  5405. remove_tmp_files( $mysync ) ;
  5406. if ( $mysync->{imap1} and $mysync->{imap1}->IsConnected() )
  5407. {
  5408. myprint( "Disconnecting from host1 $mysync->{ host1 } user1 $mysync->{ user1 }\n" ) ;
  5409. $mysync->{imap1}->logout( ) ;
  5410. }
  5411. if ( $mysync->{imap2} and $mysync->{imap2}->IsConnected() )
  5412. {
  5413. myprint( "Disconnecting from host2 $mysync->{ host2 } user2 $mysync->{ user2 }\n" ) ;
  5414. $mysync->{imap2}->logout( ) ;
  5415. }
  5416. if ( $mysync->{log} ) {
  5417. myprint( "Log file is $mysync->{logfile} ( to change it, use --logfile filepath ; or use --nolog to turn off logging )\n" ) ;
  5418. }
  5419. else
  5420. {
  5421. myprint( "No log file because of option --nolog\n" ) ;
  5422. }
  5423. if ( $mysync->{log} and $mysync->{logfile_handle} ) {
  5424. #print( "Closing $mysync->{ logfile }\n" ) ;
  5425. teefinish( $mysync ) ;
  5426. }
  5427. return ;
  5428. }
  5429. sub tests_exit_value
  5430. {
  5431. note( 'Entering tests_exit_value()' ) ;
  5432. is( $EXIT_CATCH_ALL, exit_value( ), 'exit_value: no args => EXIT_CATCH_ALL' ) ;
  5433. my $mysync = { } ;
  5434. is( $EXIT_CATCH_ALL, exit_value( $mysync ), 'exit_value: undef => EXIT_CATCH_ALL' ) ;
  5435. is( $EXIT_CATCH_ALL, exit_value( $mysync, 'Blabla_unknown' ), 'exit_value: Blabla => EXIT_CATCH_ALL' ) ;
  5436. is( $EXIT_CATCH_ALL, exit_value( $mysync, '' ), 'exit_value: empty => EXIT_CATCH_ALL' ) ;
  5437. is( $EXIT_OVERQUOTA, exit_value( $mysync, 'ERR_OVERQUOTA' ), 'exit_value: ERR_OVERQUOTA => EXIT_OVERQUOTA' ) ;
  5438. is( $EXIT_TRANSFER_EXCEEDED, exit_value( $mysync, 'ERR_TRANSFER_EXCEEDED' ), 'exit_value: ERR_TRANSFER_EXCEEDED => EXIT_TRANSFER_EXCEEDED' ) ;
  5439. note( 'Leaving tests_exit_value()' ) ;
  5440. return ;
  5441. }
  5442. sub exit_value
  5443. {
  5444. my $mysync = shift @ARG ;
  5445. my $most_common_error = shift @ARG ;
  5446. if ( ! defined $most_common_error ) { return $EXIT_CATCH_ALL ; }
  5447. my $exit_value = $EXIT_VALUE_OF_ERR_TYPE{ $most_common_error } || $EXIT_CATCH_ALL ;
  5448. return $exit_value ;
  5449. }
  5450. sub exit_most_errors
  5451. {
  5452. my $mysync = shift @ARG ;
  5453. myprint( errors_listing( $mysync ) ) ;
  5454. my $exit_value = exit_value( $mysync, $mysync->{ most_common_error } ) ;
  5455. exit_clean( $mysync, $exit_value ) ;
  5456. return ;
  5457. }
  5458. sub exit_clean
  5459. {
  5460. my $mysync = shift @ARG ;
  5461. my $status = shift @ARG ;
  5462. my @messages = @ARG ;
  5463. if ( @messages )
  5464. {
  5465. myprint( @messages ) ;
  5466. }
  5467. myprint( "Exiting with return value $status ($EXIT_TXT{$status}) $mysync->{nb_errors}/$mysync->{errorsmax} nb_errors/max_errors PID $PROCESS_ID\n" ) ;
  5468. cleanup_before_exit( $mysync ) ;
  5469. exit $status ;
  5470. }
  5471. sub missing_option
  5472. {
  5473. my $mysync = shift ;
  5474. my $option = shift ;
  5475. $mysync->{nb_errors}++ ;
  5476. exit_clean( $mysync, $EX_USAGE, "$option option is mandatory, for help run $PROGRAM_NAME --help\n" ) ;
  5477. return ;
  5478. }
  5479. sub catch_ignore
  5480. {
  5481. my $mysync = shift ;
  5482. my $signame = shift ;
  5483. my $sigcounter = ++$mysync->{ sigcounter }{ $signame } ;
  5484. myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ),
  5485. "). Received $sigcounter $signame signals so far. Thanks!\n" ) ;
  5486. do_and_print_stats( $mysync ) ;
  5487. return ;
  5488. }
  5489. sub catch_exit
  5490. {
  5491. my $mysync = shift ;
  5492. my $signame = shift || q{} ;
  5493. if ( $signame ) {
  5494. myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ),
  5495. "). Asked to terminate\n" ) ;
  5496. if ( $mysync->{can_do_stats} ) {
  5497. do_and_print_stats( $mysync ) ;
  5498. myprint( "Ended by a signal $signame (my PID is $PROCESS_ID my PPID is ",
  5499. getppid( ), "). I am asked to terminate immediately.\n" ) ;
  5500. }
  5501. myprint( "You should resynchronize those accounts by running a sync again,\n",
  5502. "since some messages and entire folders might still be missing on host2.\n"
  5503. ) ;
  5504. ## no critic (RequireLocalizedPunctuationVars)
  5505. # Well, restore default action does not work well
  5506. $SIG{ $signame } = 'DEFAULT'; # restore default action
  5507. #$SIG{ 'TERM' } = 'DEFAULT'; # restore default action
  5508. # kill myself with $signame
  5509. # https://www.cons.org/cracauer/sigint.html
  5510. myprint( "Killing myself with signal $signame\n" ) ;
  5511. #cleanup_before_exit( $mysync ) ;
  5512. kill( $signame, $PROCESS_ID ) ;
  5513. #kill( 'TERM', $PROCESS_ID ) ;
  5514. #sleep 1 ;
  5515. #while ( 1 ) { } ;
  5516. $mysync->{nb_errors}++ ;
  5517. exit_clean( $mysync, $EXIT_BY_SIGNAL,
  5518. "Still there after killing myself with signal $signame...\n"
  5519. ) ;
  5520. }
  5521. else
  5522. {
  5523. $mysync->{nb_errors}++ ;
  5524. exit_clean( $mysync, $EXIT_BY_SIGNAL, "Exiting in catch_exit with no signal...\n" ) ;
  5525. }
  5526. return ;
  5527. }
  5528. sub catch_print
  5529. {
  5530. my $mysync = shift ;
  5531. my $signame = shift ;
  5532. my $sigcounter = ++$mysync->{ sigcounter }{ $signame } ;
  5533. myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ),
  5534. "). Received $sigcounter $signame signals so far. Thanks!\n" ) ;
  5535. return ;
  5536. }
  5537. sub here_twice
  5538. {
  5539. my $mysync = shift ;
  5540. my $now = time ;
  5541. my $previous = $mysync->{lastcatch} || 0 ;
  5542. $mysync->{lastcatch} = $now ;
  5543. if ( $INTERVAL_TO_EXIT >= $now - $previous ) {
  5544. return $TRUE ;
  5545. }else{
  5546. return $FALSE ;
  5547. }
  5548. }
  5549. sub catch_reconnect
  5550. {
  5551. my $mysync = shift ;
  5552. my $signame = shift ;
  5553. if ( here_twice( $mysync ) ) {
  5554. myprint( "Got two signals $signame within $INTERVAL_TO_EXIT seconds. Exiting...\n" ) ;
  5555. catch_exit( $mysync, $signame ) ;
  5556. }else{
  5557. myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ), ")\n",
  5558. "Hit 2 ctr-c within 2 seconds to exit the program\n",
  5559. "Hit only 1 ctr-c to reconnect to both imap servers\n",
  5560. ) ;
  5561. myprint( "For now only one signal $signame within $INTERVAL_TO_EXIT seconds.\n" ) ;
  5562. if ( ! defined $mysync->{imap1} ) { return ; }
  5563. if ( ! defined $mysync->{imap2} ) { return ; }
  5564. myprint( "Info: reconnecting to host1 imap server $mysync->{host1}\n" ) ;
  5565. $mysync->{imap1}->State( Mail::IMAPClient::Unconnected ) ;
  5566. $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT} += 1 ;
  5567. if ( $mysync->{imap1}->reconnect( ) )
  5568. {
  5569. myprint( "Info: reconnected to host1 imap server $mysync->{host1}\n" ) ;
  5570. }
  5571. else
  5572. {
  5573. $mysync->{nb_errors}++ ;
  5574. exit_clean( $mysync, $EXIT_CONNECTION_FAILURE ) ;
  5575. }
  5576. myprint( "Info: reconnecting to host2 imap server\n" ) ;
  5577. $mysync->{imap2}->State( Mail::IMAPClient::Unconnected ) ;
  5578. $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT} += 1 ;
  5579. if ( $mysync->{imap2}->reconnect( ) )
  5580. {
  5581. myprint( "Info: reconnected to host2 imap server $mysync->{host2}\n" ) ;
  5582. }
  5583. else
  5584. {
  5585. $mysync->{nb_errors}++ ;
  5586. exit_clean( $mysync, $EXIT_CONNECTION_FAILURE ) ;
  5587. }
  5588. myprint( "Info: reconnected to both imap servers\n" ) ;
  5589. }
  5590. return ;
  5591. }
  5592. sub install_signals
  5593. {
  5594. my $mysync = shift ;
  5595. if ( under_docker_context( $mysync ) )
  5596. {
  5597. # output( $mysync, "Under docker context so leaving signals as they are\n" ) ;
  5598. output( $mysync, "Under docker context so installing only signals to exit\n" ) ;
  5599. @{ $mysync->{ sigexit } } = ( defined( $mysync->{ sigexit } ) ) ? @{ $mysync->{ sigexit } } : ( 'INT', 'QUIT', 'TERM' ) ;
  5600. sig_install( $mysync, 'catch_exit', @{ $mysync->{ sigexit } } ) ;
  5601. }
  5602. else
  5603. {
  5604. # Unix signals
  5605. @{ $mysync->{ sigexit } } = ( defined( $mysync->{ sigexit } ) ) ? @{ $mysync->{ sigexit } } : ( 'QUIT', 'TERM' ) ;
  5606. @{ $mysync->{ sigreconnect } } = ( defined( $mysync->{ sigreconnect } ) ) ? @{ $mysync->{ sigreconnect } } : ( 'INT' ) ;
  5607. @{ $mysync->{ sigprint } } = ( defined( $mysync->{ sigprint } ) ) ? @{ $mysync->{ sigprint } } : ( 'HUP' ) ;
  5608. @{ $mysync->{ sigignore } } = ( defined( $mysync->{ sigignore } ) ) ? @{ $mysync->{ sigignore } } : ( ) ;
  5609. #local %SIG = %SIG ;
  5610. sig_install( $mysync, 'catch_exit', @{ $mysync->{ sigexit } } ) ;
  5611. sig_install( $mysync, 'catch_reconnect', @{ $mysync->{ sigreconnect } } ) ;
  5612. sig_install( $mysync, 'catch_print', @{ $mysync->{ sigprint } } ) ;
  5613. # --sigignore can override sigexit, sigreconnect and sigprint (for the same signals only)
  5614. sig_install( $mysync, 'catch_ignore', @{ $mysync->{ sigignore } } ) ;
  5615. # remove/add sleeping mechanism when receiving USR1 signal (except on Win32)
  5616. sig_install_toggle_sleep( $mysync ) ;
  5617. }
  5618. return ;
  5619. }
  5620. sub tests_reconnect_12_if_needed
  5621. {
  5622. note( 'Entering tests_reconnect_12_if_needed()' ) ;
  5623. my $mysync ;
  5624. $mysync->{imap1} = Mail::IMAPClient->new( ) ;
  5625. $mysync->{imap2} = Mail::IMAPClient->new( ) ;
  5626. $mysync->{imap1}->Server( 'test1.lamiral.info' ) ;
  5627. $mysync->{imap2}->Server( 'test2.lamiral.info' ) ;
  5628. is( 2, reconnect_12_if_needed( $mysync ), 'reconnect_12_if_needed: test1&test2 .lamiral.info => 1' ) ;
  5629. is( 1, $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_12_if_needed: test1.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ;
  5630. is( 1, $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_12_if_needed: test2.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ;
  5631. note( 'Leaving tests_reconnect_12_if_needed()' ) ;
  5632. return ;
  5633. }
  5634. sub reconnect_12_if_needed
  5635. {
  5636. my $mysync = shift ;
  5637. #return 2 ;
  5638. if ( ! reconnect_if_needed( $mysync->{imap1} ) ) {
  5639. return ;
  5640. }
  5641. if ( ! reconnect_if_needed( $mysync->{imap2} ) ) {
  5642. return ;
  5643. }
  5644. # both were good
  5645. return 2 ;
  5646. }
  5647. sub tests_reconnect_if_needed
  5648. {
  5649. note( 'Entering tests_reconnect_if_needed()' ) ;
  5650. my $myimap ;
  5651. is( undef, reconnect_if_needed( ), 'reconnect_if_needed: no args => undef' ) ;
  5652. is( undef, reconnect_if_needed( $myimap ), 'reconnect_if_needed: undef arg => undef' ) ;
  5653. $myimap = Mail::IMAPClient->new( ) ;
  5654. $myimap->Debug( 1 ) ;
  5655. is( undef, reconnect_if_needed( $myimap ), 'reconnect_if_needed: empty new Mail::IMAPClient => undef' ) ;
  5656. $myimap->Server( 'test.lamiral.info' ) ;
  5657. is( 1, reconnect_if_needed( $myimap ), 'reconnect_if_needed: test.lamiral.info => 1' ) ;
  5658. is( 1, $myimap->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_if_needed: test.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ;
  5659. note( 'Leaving tests_reconnect_if_needed()' ) ;
  5660. return ;
  5661. }
  5662. sub reconnect_if_needed
  5663. {
  5664. # return undef upon failure.
  5665. # return 1 upon connection success, with or without reconnection.
  5666. my $imap = shift ;
  5667. if ( ! defined $imap ) { return ; }
  5668. if ( ! $imap->Server( ) ) { return ; }
  5669. if ( $imap->IsUnconnected( ) ) {
  5670. $imap->{IMAPSYNC_RECONNECT_COUNT} += 1 ;
  5671. if ( $imap->reconnect( ) ) {
  5672. return 1 ;
  5673. }
  5674. }else{
  5675. return 1 ;
  5676. }
  5677. # A last forced one
  5678. $imap->State( Mail::IMAPClient::Unconnected ) ;
  5679. $imap->reconnect( ) ;
  5680. $imap->{IMAPSYNC_RECONNECT_COUNT} += 1 ;
  5681. if ( $imap->noop ) {
  5682. # NOOP is ok
  5683. return 1 ;
  5684. }
  5685. return ;
  5686. }
  5687. sub justconnect
  5688. {
  5689. my $mysync = shift ;
  5690. my $justconnect1 = justconnect1( $sync ) ;
  5691. my $justconnect2 = justconnect2( $sync ) ;
  5692. return "$justconnect1 $justconnect2";
  5693. }
  5694. sub justconnect1
  5695. {
  5696. my $mysync = shift ;
  5697. if ( $mysync->{host1} )
  5698. {
  5699. myprint( "Host1: Will just connect to $mysync->{host1} without login\n" ) ;
  5700. $mysync->{imap1} = connect_imap(
  5701. $mysync->{host1}, $mysync->{port1},
  5702. $mysync->{ssl1}, $mysync->{tls1},
  5703. $mysync->{ acc1 } ) ;
  5704. imap_id( $mysync, $mysync->{imap1}, $mysync->{ acc1 }->{ Side } ) ;
  5705. $mysync->{imap1}->logout( ) ;
  5706. return $mysync->{host1} ;
  5707. }
  5708. return q{} ;
  5709. }
  5710. sub justconnect2
  5711. {
  5712. my $mysync = shift ;
  5713. if ( $mysync->{host2} )
  5714. {
  5715. myprint( "Host2: Will just connect to $mysync->{host2} without login\n" ) ;
  5716. $mysync->{imap2} = connect_imap(
  5717. $mysync->{host2}, $mysync->{port2},
  5718. $mysync->{ssl2}, $mysync->{tls2},
  5719. $mysync->{ acc2 } ) ;
  5720. imap_id( $mysync, $mysync->{imap2}, $mysync->{ acc2 }->{ Side } ) ;
  5721. $mysync->{imap2}->logout( ) ;
  5722. return $mysync->{host2} ;
  5723. }
  5724. return q{} ;
  5725. }
  5726. sub skip_macosx
  5727. {
  5728. #return ;
  5729. # hostname is sometimes "macosx.polarhome.com" sometimes "macosx"
  5730. return( ( ( 'macosx.polarhome.com' eq hostname( ) ) || ( 'macosx' eq hostname( ) ) )
  5731. && ( 'darwin' eq $OSNAME ) ) ;
  5732. }
  5733. sub skip_macosx_binary
  5734. {
  5735. #return ;
  5736. return( skip_macosx( ) && ( $PROGRAM_NAME =~ m{imapsync_bin_Darwin} ) ) ;
  5737. }
  5738. sub tests_mailimapclient_connect
  5739. {
  5740. note( 'Entering tests_mailimapclient_connect()' ) ;
  5741. my $imap ;
  5742. # ipv4
  5743. ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect ipv4: new' ) ;
  5744. is( 'Mail::IMAPClient', ref( $imap ), 'mailimapclient_connect ipv4: ref is Mail::IMAPClient' ) ;
  5745. # Mail::IMAPClient 3.40 die on this... So we skip it, thanks to "mature" IO::Socket::IP
  5746. # Mail::IMAPClient 3.42 is ok so this test is back.
  5747. is( undef, $imap->connect( ), 'mailimapclient_connect ipv4: connect with no server => failure' ) ;
  5748. is( 'test.lamiral.info', $imap->Server( 'test.lamiral.info' ), 'mailimapclient_connect ipv4: setting Server(test.lamiral.info)' ) ;
  5749. is( 1, $imap->Debug( 1 ), 'mailimapclient_connect ipv4: setting Debug( 1 )' ) ;
  5750. is( 143, $imap->Port( 143 ), 'mailimapclient_connect ipv4: setting Port( 143 )' ) ;
  5751. is( 10, $imap->Timeout( 10 ), 'mailimapclient_connect ipv4: setting Timeout( 10 )' ) ;
  5752. like( ref( $imap->connect( ) ), qr/IO::Socket::INET|IO::Socket::IP/, 'mailimapclient_connect ipv4: connect to test.lamiral.info' ) ;
  5753. like( $imap->logout( ), qr/Mail::IMAPClient/, 'mailimapclient_connect ipv4: logout' ) ;
  5754. is( undef, undef $imap, 'mailimapclient_connect ipv4: free variable' ) ;
  5755. # ipv4 + ssl
  5756. ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect ipv4 + ssl: new' ) ;
  5757. is( 'test.lamiral.info', $imap->Server( 'test.lamiral.info' ), 'mailimapclient_connect ipv4 + ssl: setting Server(test.lamiral.info)' ) ;
  5758. is( 1, $imap->Debug( 1 ), 'mailimapclient_connect ipv4 + ssl: setting Debug( 1 )' ) ;
  5759. ok( $imap->Ssl( [ SSL_verify_mode => SSL_VERIFY_NONE, SSL_cipher_list => 'DEFAULT:!DH' ] ), 'mailimapclient_connect ipv4 + ssl: setting Ssl( SSL_VERIFY_NONE )' ) ;
  5760. is( 993, $imap->Port( 993 ), 'mailimapclient_connect ipv4 + ssl: setting Port( 993 )' ) ;
  5761. like( ref( $imap->connect( ) ), qr/IO::Socket::SSL/, 'mailimapclient_connect ipv4 + ssl: connect to test.lamiral.info' ) ;
  5762. like( $imap->logout( ), qr/Mail::IMAPClient/, 'mailimapclient_connect ipv4 + ssl: logout in ssl does not cause failure' ) ;
  5763. is( undef, undef $imap, 'mailimapclient_connect ipv4 + ssl: free variable' ) ;
  5764. # ipv6 + ssl
  5765. ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect ipv6 + ssl: new' ) ;
  5766. is( 'petiteipv6.lamiral.info', $imap->Server( 'petiteipv6.lamiral.info' ), 'mailimapclient_connect ipv6 + ssl: setting Server petiteipv6.lamiral.info' ) ;
  5767. is( 10, $imap->Timeout( 10 ), 'mailimapclient_connect ipv6: setting Timeout( 10 )' ) ;
  5768. ok( $imap->Ssl( [ SSL_verify_mode => SSL_VERIFY_NONE, SSL_cipher_list => 'DEFAULT:!DH' ] ), 'mailimapclient_connect ipv6 + ssl: setting Ssl( SSL_VERIFY_NONE )' ) ;
  5769. is( 993, $imap->Port( 993 ), 'mailimapclient_connect ipv6 + ssl: setting Port( 993 )' ) ;
  5770. SKIP: {
  5771. if (
  5772. 'CUILLERE' eq hostname()
  5773. or
  5774. skip_macosx()
  5775. or
  5776. -e '/.dockerenv'
  5777. or
  5778. 'pcHPDV7-HP' eq hostname()
  5779. )
  5780. {
  5781. skip( 'Tests avoided on CUILLERE/pcHPDV7-HP/macosx.polarhome.com/docker cannot do ipv6', 4 ) ;
  5782. }
  5783. is( 1, $imap->Debug( 1 ), 'mailimapclient_connect ipv4 + ssl: setting Debug( 1 )' ) ;
  5784. # It sounds stupid but it avoids failures on the next test about $imap->connect
  5785. is( '2a01:e34:ecde:70d0:223:54ff:fec2:36d7', resolv( 'petiteipv6.lamiral.info' ), 'resolv: petiteipv6.lamiral.info => 2a01:e34:ecde:70d0:223:54ff:fec2:36d7' ) ;
  5786. like( ref( $imap->connect( ) ), qr/IO::Socket::SSL/, 'mailimapclient_connect ipv6 + ssl: connect to petiteipv6.lamiral.info' ) ;
  5787. # This one is ok on petite, not on ks2, do not know why, so commented.
  5788. like( ref( $imap->logout( ) ), qr/Mail::IMAPClient/, 'mailimapclient_connect ipv6 + ssl: logout in ssl is ok on petiteipv6.lamiral.info' ) ;
  5789. }
  5790. is( undef, undef $imap, 'mailimapclient_connect ipv6 + ssl: free variable' ) ;
  5791. note( 'Leaving tests_mailimapclient_connect()' ) ;
  5792. return ;
  5793. }
  5794. sub tests_mailimapclient_connect_bug
  5795. {
  5796. note( 'Entering tests_mailimapclient_connect_bug()' ) ;
  5797. my $imap ;
  5798. # ipv6
  5799. ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect_bug ipv6: new' ) ;
  5800. is( 'ks6ipv6.lamiral.info', $imap->Server( 'ks6ipv6.lamiral.info' ), 'mailimapclient_connect_bug ipv6: setting Server(ks6ipv6.lamiral.info)' ) ;
  5801. is( 143, $imap->Port( 143 ), 'mailimapclient_connect_bug ipv6: setting Port( 993 )' ) ;
  5802. SKIP: {
  5803. if (
  5804. 'CUILLERE' eq hostname()
  5805. or
  5806. skip_macosx()
  5807. or
  5808. -e '/.dockerenv'
  5809. or
  5810. 'pcHPDV7-HP' eq hostname()
  5811. )
  5812. {
  5813. skip( 'Tests avoided on CUILLERE/pcHPDV7-HP/macosx.polarhome.com/docker cannot do ipv6', 1 ) ;
  5814. }
  5815. like( ref( $imap->connect( ) ), qr/IO::Socket::INET/, 'mailimapclient_connect_bug ipv6: connect to ks6ipv6.lamiral.info' )
  5816. or diag( 'mailimapclient_connect_bug ipv6: ', $imap->LastError( ), $!, ) ;
  5817. }
  5818. #is( $imap->logout( ), undef, 'mailimapclient_connect_bug ipv6: logout in ssl causes failure' ) ;
  5819. is( undef, undef $imap, 'mailimapclient_connect_bug ipv6: free variable' ) ;
  5820. note( 'Leaving tests_mailimapclient_connect_bug()' ) ;
  5821. return ;
  5822. }
  5823. sub tests_connect_socket
  5824. {
  5825. note( 'Entering tests_connect_socket()' ) ;
  5826. is( undef, connect_socket( ), 'connect_socket: no args' ) ;
  5827. my $socket ;
  5828. my $imap ;
  5829. SKIP: {
  5830. if (
  5831. 'CUILLERE' eq hostname()
  5832. or
  5833. skip_macosx()
  5834. or
  5835. -e '/.dockerenv'
  5836. or
  5837. 'pcHPDV7-HP' eq hostname()
  5838. )
  5839. {
  5840. skip( 'Tests avoided on CUILLERE/pcHPDV7-HP/macosx.polarhome.com/docker cannot do ipv6', 2 ) ;
  5841. }
  5842. $socket = IO::Socket::INET6->new(
  5843. PeerAddr => 'ks6ipv6.lamiral.info',
  5844. PeerPort => 143,
  5845. ) ;
  5846. ok( $imap = connect_socket( $socket ), 'connect_socket: ks6ipv6.lamiral.info port 143 IO::Socket::INET6' ) ;
  5847. #$imap->Debug( 1 ) ;
  5848. # myprint( $imap->capability( ) ) ;
  5849. if ( $imap ) {
  5850. $imap->logout( ) ;
  5851. }
  5852. $IO::Socket::SSL::DEBUG = 4 ;
  5853. $socket = IO::Socket::SSL->new(
  5854. PeerHost => 'ks6ipv6.lamiral.info',
  5855. PeerPort => 993,
  5856. SSL_verify_mode => SSL_VERIFY_NONE,
  5857. SSL_cipher_list => 'DEFAULT:!DH',
  5858. ) ;
  5859. # myprint( $socket ) ;
  5860. ok( $imap = connect_socket( $socket ), 'connect_socket: ks6ipv6.lamiral.info port 993 IO::Socket::SSL' ) ;
  5861. #$imap->Debug( 1 ) ;
  5862. # myprint( $imap->capability( ) ) ;
  5863. # $socket->close( ) ;
  5864. if ( $imap ) {
  5865. $socket->close( ) ;
  5866. }
  5867. #$socket->close(SSL_no_shutdown => 1) ;
  5868. #$imap->logout( ) ;
  5869. #myprint( "\n" ) ;
  5870. #$imap->logout( ) ;
  5871. }
  5872. note( 'Leaving tests_connect_socket()' ) ;
  5873. return ;
  5874. }
  5875. sub connect_socket
  5876. {
  5877. my( $socket ) = @ARG ;
  5878. if ( ! defined $socket ) { return ; }
  5879. my $host = $socket->peerhost( ) ;
  5880. my $port = $socket->peerport( ) ;
  5881. #print "socket->peerhost: ", $socket->peerhost( ), "\n" ;
  5882. #print "socket->peerport: ", $socket->peerport( ), "\n" ;
  5883. my $imap = Mail::IMAPClient->new( ) ;
  5884. $imap->Socket( $socket ) ;
  5885. my $banner = $imap->Results()->[0] ;
  5886. #myprint( "banner: $banner" ) ;
  5887. return $imap ;
  5888. }
  5889. sub tests_probe_imapssl
  5890. {
  5891. note( 'Entering tests_probe_imapssl()' ) ;
  5892. is( undef, probe_imapssl( ), 'probe_imapssl: no args => undef' ) ;
  5893. is( undef, probe_imapssl( 'unknown' ), 'probe_imapssl: unknown => undef' ) ;
  5894. note( "hostname is: ", hostname() ) ;
  5895. SKIP: {
  5896. if (
  5897. 'CUILLERE' eq hostname()
  5898. or
  5899. skip_macosx()
  5900. or
  5901. -e '/.dockerenv'
  5902. or
  5903. 'pcHPDV7-HP' eq hostname()
  5904. )
  5905. {
  5906. skip( 'Tests avoided on CUILLERE or pcHPDV7-HP or Mac or docker: cannot do ipv6', 0 ) ;
  5907. }
  5908. # fed up with this one
  5909. #like( probe_imapssl( 'ks6ipv6.lamiral.info' ), qr/^\* OK/, 'probe_imapssl: ks6ipv6.lamiral.info matches "* OK"' ) ;
  5910. } ;
  5911. # It sounds stupid but it avoids failures on the next test about $imap->connect
  5912. ok( resolv( 'imap.gmail.com' ), 'resolv: imap.gmail.com => something' ) ;
  5913. like( probe_imapssl( 'imap.gmail.com' ), qr/^\* OK/, 'probe_imapssl: imap.gmail.com matches "* OK"' ) ;
  5914. like( probe_imapssl( 'test1.lamiral.info' ), qr/^\* OK/, 'probe_imapssl: test1.lamiral.info matches "* OK"' ) ;
  5915. note( 'Leaving tests_probe_imapssl()' ) ;
  5916. return ;
  5917. }
  5918. sub probe_imapssl
  5919. {
  5920. my $host = shift ;
  5921. if ( ! $host ) { return ; }
  5922. $sync->{ debug } and $IO::Socket::SSL::DEBUG = 4 ;
  5923. my $socket = IO::Socket::SSL->new(
  5924. PeerHost => $host,
  5925. PeerPort => $IMAP_SSL_PORT,
  5926. SSL_verifycn_scheme => 'imap',
  5927. SSL_verify_mode => $SSL_VERIFY_POLICY,
  5928. SSL_cipher_list => 'DEFAULT:!DH',
  5929. ) ;
  5930. if ( ! $socket ) { return ; }
  5931. $sync->{ debug } and print "socket: $socket\n" ;
  5932. my $banner ;
  5933. $socket->sysread( $banner, 65_536 ) ;
  5934. $sync->{ debug } and print "banner: $banner" ;
  5935. $socket->close( ) ;
  5936. return $banner ;
  5937. }
  5938. sub connect_imap
  5939. {
  5940. my( $host, $port, $ssl, $tls, $acc ) = @_ ;
  5941. my $imap = Mail::IMAPClient->new( ) ;
  5942. if ( $ssl ) { set_ssl( $imap, $acc ) }
  5943. $imap->Server( $host ) ;
  5944. $imap->Port( $port ) ;
  5945. $imap->Debug( $acc->{ debugimap } ) ;
  5946. $imap->Timeout( $acc->{ timeout } ) ;
  5947. #$imap->Keepalive( $acc->{ keepalive } ) ;
  5948. my $side = lc $acc->{ Side } ;
  5949. myprint( "$acc->{ Side }: connecting on $side [$host] port [$port]\n" ) ;
  5950. if ( ! $imap->connect( ) )
  5951. {
  5952. $sync->{nb_errors}++ ;
  5953. exit_clean( $sync, $EXIT_CONNECTION_FAILURE,
  5954. "$acc->{ Side }: Can not open imap connection on [$host]: ",
  5955. $imap->LastError,
  5956. " $OS_ERROR\n"
  5957. ) ;
  5958. }
  5959. myprint( "$acc->{ Side } IP address: ", $imap->Socket->peerhost(), "\n" ) ;
  5960. my $banner = $imap->Results()->[0] ;
  5961. myprint( "$acc->{ Side } banner: $banner" ) ;
  5962. myprint( "$acc->{ Side } capability: ", join(q{ }, @{ $imap->capability() || [] }), "\n" ) ;
  5963. if ( $tls ) {
  5964. set_tls( $imap, $acc ) ;
  5965. if ( ! $imap->starttls( ) )
  5966. {
  5967. $sync->{nb_errors}++ ;
  5968. exit_clean( $sync, $EXIT_TLS_FAILURE,
  5969. "$acc->{ Side }: Can not go to tls encryption on $side [$host]:",
  5970. $imap->LastError, "\n"
  5971. ) ;
  5972. }
  5973. myprint( "$acc->{ Side }: Socket successfully converted to SSL\n" ) ;
  5974. }
  5975. return( $imap ) ;
  5976. }
  5977. sub tests_compress_ssl
  5978. {
  5979. note( 'Entering tests_compress_ssl()' ) ;
  5980. SKIP: {
  5981. if ( skip_macosx( ) )
  5982. {
  5983. skip( 'Tests avoided on host polarhome macosx, no clue "ssl3_get_server_certificate:certificate verify failed"', 12 ) ;
  5984. }
  5985. else
  5986. {
  5987. my $myimap ;
  5988. my $acc = {} ;
  5989. $acc->{ Side } = 'HostK' ;
  5990. $acc->{ authmech } = 'LOGIN' ;
  5991. $acc->{ debugimap } = 1 ;
  5992. $acc->{ compress } = 1 ;
  5993. $acc->{ N } = 'K' ;
  5994. ok(
  5995. $myimap = login_imap( 'test1.lamiral.info', 993, 'test1', 'secret1',
  5996. 1, undef,
  5997. 1, 100, $acc, {},
  5998. ), 'acc_compress_imap: test1.lamiral.info test1 ssl' ) ;
  5999. ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 ssl IsAuthenticated' ) ;
  6000. is( $acc->{ imap }, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info ok" ) ;
  6001. is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info 2nd nok" ) ;
  6002. ok(
  6003. $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1',
  6004. 0, undef,
  6005. 1, 100, $acc, {},
  6006. ), 'acc_compress_imap: test1.lamiral.info test1 tls' ) ;
  6007. ok( $myimap && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 tls IsAuthenticated' ) ;
  6008. is( $acc->{ imap }, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info tls ok" ) ;
  6009. is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info tls 2nd nok" ) ;
  6010. # Third, no compression
  6011. $acc->{ compress } = 0 ;
  6012. ok(
  6013. $myimap = login_imap( 'test1.lamiral.info', 993, 'test1', 'secret1',
  6014. 1, undef,
  6015. 1, 100, $acc, {},
  6016. ), 'acc_compress_imap: test1.lamiral.info test1 ssl' ) ;
  6017. ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 ssl IsAuthenticated' ) ;
  6018. is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info off ok" ) ;
  6019. is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info 2nd off ok" ) ;
  6020. }
  6021. }
  6022. note( 'Leaving tests_compress_ssl()' ) ;
  6023. return ;
  6024. }
  6025. sub tests_compress
  6026. {
  6027. note( 'Entering tests_compress()' ) ;
  6028. my $myimap ;
  6029. my $acc = {} ;
  6030. $acc->{ Side } = 'HostK' ;
  6031. $acc->{ authmech } = 'LOGIN' ;
  6032. $acc->{ debugimap } = 1 ;
  6033. $acc->{ compress } = 1 ;
  6034. $acc->{ N } = 'K' ;
  6035. ok(
  6036. $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1',
  6037. 0, 0,
  6038. 1, 100, $acc, {},
  6039. ), 'acc_compress_imap: test1.lamiral.info test1' ) ;
  6040. ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 IsAuthenticated' ) ;
  6041. is( $acc->{ imap }, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info ok" ) ;
  6042. is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info 2nd nok" ) ;
  6043. ok(
  6044. $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1',
  6045. 0, 0,
  6046. 1, 100, $acc, {},
  6047. ), 'acc_compress_imap: test1.lamiral.info test1 tls' ) ;
  6048. ok( $myimap && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 tls IsAuthenticated' ) ;
  6049. is( $acc->{ imap }, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info tls ok" ) ;
  6050. is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info tls 2nd nok" ) ;
  6051. # Third, no compression
  6052. $acc->{ compress } = 0 ;
  6053. ok(
  6054. $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1',
  6055. 0, 0,
  6056. 1, 100, $acc, {},
  6057. ), 'acc_compress_imap: test1.lamiral.info test1 ssl' ) ;
  6058. ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 ssl IsAuthenticated' ) ;
  6059. is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info off ok" ) ;
  6060. is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info 2nd off ok" ) ;
  6061. note( 'Leaving tests_compress()' ) ;
  6062. return ;
  6063. }
  6064. sub acc_compress_imap
  6065. {
  6066. my $acc = shift ;
  6067. if ( ! defined( $acc ) ) { return ; }
  6068. my $ret ;
  6069. my $imap = $acc->{ imap } ;
  6070. if ( ! defined $imap ) { return ; }
  6071. if ( $imap && $acc->{ compress } )
  6072. {
  6073. myprint( "$acc->{ Side }: Trying to turn imap compression on. Use --nocompress" . $acc->{ N } . " to avoid compression on " . lc( $acc->{ Side } ) . "\n" ) ;
  6074. if ( $ret = $imap->compress() )
  6075. {
  6076. myprint( "$acc->{ Side }: Compression is on now\n" ) ;
  6077. }
  6078. else
  6079. {
  6080. myprint( "$acc->{ Side }: Failed to turn compression on\n" ) ;
  6081. }
  6082. }
  6083. else
  6084. {
  6085. myprint( "$acc->{ Side }: Compression is off. Use --compress" . $acc->{ N } . " to allow compression on " . lc( $acc->{ Side } ) . "\n" ) ;
  6086. }
  6087. # $ret is $acc->{ imap } on success, undef on failure or when there is nothing to do.
  6088. return $ret ;
  6089. }
  6090. sub tests_login_imap
  6091. {
  6092. note( 'Entering tests_login_imap()' ) ;
  6093. is( undef, login_imap( ), 'login_imap: no args => undef' ) ;
  6094. SKIP: {
  6095. if ( skip_macosx( ) )
  6096. {
  6097. skip( 'Tests avoided only on binary on host polarhome macosx, no clue "ssl3_get_server_certificate:certificate verify failed"', 15 ) ;
  6098. }
  6099. else{
  6100. my $myimap ;
  6101. my $acc = {} ;
  6102. $acc->{ Side } = 'HostK' ;
  6103. $acc->{ authmech } = 'LOGIN' ;
  6104. #$IO::Socket::SSL::DEBUG = 4 ;
  6105. # Each month (trimester?):
  6106. # echo | openssl s_client -crlf -connect test1.lamiral.info:993
  6107. # ...
  6108. # certificate has expired
  6109. # Fix: ssh root@test1.lamiral.info 'apt update && apt upgrade && /etc/init.d/dovecot restart'
  6110. #
  6111. # or
  6112. # echo | openssl s_client -crlf -connect test1.lamiral.info:993
  6113. # ...
  6114. # Verify return code: 9 (certificate is not yet valid)
  6115. # Fix: /etc/init.d/openntpd restart
  6116. # 2021_09_04 done
  6117. ok(
  6118. $myimap = login_imap( 'test1.lamiral.info', 993, 'test1', 'secret1',
  6119. 1, undef,
  6120. 1, 100, $acc, {},
  6121. ), 'login_imap: test1.lamiral.info test1 ssl' ) ;
  6122. ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: test1.lamiral.info test1 ssl IsAuthenticated' ) ;
  6123. is( $myimap, $acc->{ imap }, "login_imap: acc->{ imap } ok test1 ssl") ;
  6124. ok(
  6125. $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1',
  6126. 0, undef,
  6127. 1, 100, $acc, {},
  6128. ), 'login_imap: test1.lamiral.info test1 tls' ) ;
  6129. ok( $myimap && $myimap->IsAuthenticated( ), 'login_imap: test1.lamiral.info test1 tls IsAuthenticated' ) ;
  6130. is( $myimap, $acc->{ imap }, "login_imap: acc->{ imap } ok test1 tls") ;
  6131. #$IO::Socket::SSL::DEBUG = 4 ;
  6132. $acc->{sslargs} = { SSL_version => 'SSLv2' } ;
  6133. # SSLv2 not supported
  6134. is(
  6135. undef, $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1',
  6136. 0, undef,
  6137. 1, 100, $acc, {},
  6138. ), 'login_imap: test1.lamiral.info test1 tls SSLv2 not supported' ) ;
  6139. #SSL_verify_mode => 1
  6140. #SSL_version => 'TLSv1_1'
  6141. is( undef, $acc->{ imap }, "login_imap: acc->{ imap } test1 tls error => undef") ;
  6142. # I have left ? exit_clean to be replaced by errors_incr( $mysync, 'error message' )
  6143. # 1 in login_imap()
  6144. my $mysync = {} ;
  6145. $acc = {} ;
  6146. $acc->{ Side } = 'Host2' ;
  6147. $acc->{ authmech } = 'LOGIN' ;
  6148. is(
  6149. undef, login_imap( 'noresol.lamiral.info', 143, 'test1', 'secret1',
  6150. 0, undef,
  6151. 1, 100, $acc, $mysync,
  6152. ), 'login_imap: noresol.lamiral.info undef' ) ;
  6153. is( 'ERR_CONNECTION_FAILURE_HOST2', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host2 noresol.lamiral.info => ERR_CONNECTION_FAILURE_HOST2' ) ;
  6154. is( undef, $acc->{ imap }, "login_imap: acc->{ imap } noresol error => undef") ;
  6155. # authentication failure for user2
  6156. $mysync = {} ;
  6157. is(
  6158. undef, login_imap( 'test1.lamiral.info', 143, 'test1', 'Ce crétin',
  6159. 0, undef,
  6160. 1, 100, $acc, $mysync,
  6161. ), 'login_imap: user2 bad passord => undef' ) ;
  6162. is( 'ERR_AUTHENTICATION_FAILURE_USER2', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host2 bad password => ERR_AUTHENTICATION_FAILURE_USER2' ) ;
  6163. # authentication failure for user1
  6164. $mysync = {} ;
  6165. $acc = {} ;
  6166. $acc->{ Side } = 'Host1' ;
  6167. $acc->{ authmech } = 'LOGIN' ;
  6168. is(
  6169. undef, login_imap( 'test1.lamiral.info', 143, 'test1', 'Ce crétin',
  6170. 0, undef,
  6171. 1, 100, $acc, $mysync,
  6172. ), 'login_imap: user1 bad passord => undef' ) ;
  6173. is( 'ERR_AUTHENTICATION_FAILURE_USER1', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host1 bad password => ERR_AUTHENTICATION_FAILURE_USER1' ) ;
  6174. }
  6175. }
  6176. note( 'Leaving tests_login_imap()' ) ;
  6177. return ;
  6178. }
  6179. sub oauthgenerateaccess
  6180. {
  6181. if ( "petite" eq hostname() )
  6182. {
  6183. myprint( "oauthgenerateaccess\n" ) ;
  6184. my @output = backtick( 'cd oauth2 && pwd && ./generate_gmail_token imapsync.gl0@gmail.com' ) ;
  6185. myprint( @output ) ;
  6186. }
  6187. return ;
  6188. }
  6189. sub tests_login_imap_oauth
  6190. {
  6191. note( 'Entering tests_login_imap_oauth()' ) ;
  6192. oauthgenerateaccess() ;
  6193. SKIP: {
  6194. if ( skip_macosx_binary( ) )
  6195. {
  6196. skip( 'Tests avoided only on binary on host polarhome macosx, no clue "ssl3_get_server_certificate:certificate verify failed"', 6 ) ;
  6197. }
  6198. else
  6199. {
  6200. my $mysync ;
  6201. my $acc ;
  6202. # oauthdirect authentication failure for user2
  6203. $mysync = {} ;
  6204. $acc = {} ;
  6205. $acc->{ oauthdirect } = 'caca2' ;
  6206. $acc->{ debugimap } = 1 ;
  6207. $mysync->{ showpasswords } = 1 ;
  6208. $acc->{ Side } = 'Host2' ;
  6209. $acc->{ authmech } = 'QQQ' ;
  6210. is(
  6211. undef, login_imap( 'imap.gmail.com', 993, 'test1', 'Ce crétin',
  6212. 1, undef,
  6213. 1, 100, $acc, $mysync,
  6214. ), 'login_imap: user2 bad oauthdirect => undef' ) ;
  6215. is( 'ERR_AUTHENTICATION_FAILURE_USER2', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host2 bad oauthdirect => ERR_AUTHENTICATION_FAILURE_USER2' ) ;
  6216. # oauthdirect authentication failure for user1
  6217. $mysync = {} ;
  6218. $acc = {} ;
  6219. $acc->{ Side } = 'Host1' ;
  6220. $acc->{ oauthdirect } = 'caca1' ;
  6221. $acc->{ debugimap } = 1 ;
  6222. $mysync->{ showpasswords } = 1 ;
  6223. $acc->{ authmech } = 'QQQ' ;
  6224. is(
  6225. undef, login_imap( 'imap.gmail.com', 993, 'test1', 'Ce crétin',
  6226. 1, undef,
  6227. 1, 100, $acc, $mysync,
  6228. ), 'login_imap: user1 bad oauthdirect => undef' ) ;
  6229. is( 'ERR_AUTHENTICATION_FAILURE_USER1', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host1 bad oauthdirect => ERR_AUTHENTICATION_FAILURE_USER1' ) ;
  6230. # oauthdirect authentication failure for user1
  6231. $mysync = {} ;
  6232. $acc = {} ;
  6233. $acc->{ Side } = 'Host1' ;
  6234. $acc->{ oauthdirect } = '' ;
  6235. $acc->{ debugimap } = 1 ;
  6236. $mysync->{ showpasswords } = 1 ;
  6237. $acc->{ authmech } = 'QQQ' ;
  6238. is(
  6239. undef, login_imap( 'imap.gmail.com', 993, 'test1', 'Ce crétin',
  6240. 1, undef,
  6241. 1, 100, $acc, $mysync,
  6242. ), 'login_imap: user1 bad oauthdirect => undef' ) ;
  6243. is( 'ERR_AUTHENTICATION_FAILURE_USER1', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host1 no oauthdirect value => ERR_AUTHENTICATION_FAILURE_USER1' ) ;
  6244. }
  6245. }
  6246. # oauthdirect authentication success for user1
  6247. SKIP: {
  6248. if ( ! -r 'oauth2/D_oauth2_oauthdirect_imapsync.gl0@gmail.com.txt' )
  6249. {
  6250. skip( 'oauthdirect: no oauthdirect file', 6 ) ;
  6251. }
  6252. my $myimap ;
  6253. my $mysync = {} ;
  6254. my $acc = {} ;
  6255. $acc->{ Side } = 'Host1' ;
  6256. $acc->{ oauthdirect } = 'oauth2/D_oauth2_oauthdirect_imapsync.gl0@gmail.com.txt' ;
  6257. $acc->{ debugimap } = 1 ;
  6258. $mysync->{ showpasswords } = 1 ;
  6259. $acc->{ authmech } = 'QQQ' ;
  6260. isa_ok(
  6261. $myimap = login_imap( 'imap.gmail.com', 993, 'user_useless', 'password_useless',
  6262. 1, undef,
  6263. 1, 100, $acc, $mysync,
  6264. ), 'Mail::IMAPClient', 'login_imap: user1 good oauthdirect => Mail::IMAPClient' ) ;
  6265. ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthdirect IsAuthenticated' ) ;
  6266. ok( defined( $myimap ) && $myimap->logout( ), 'login_imap: gmail oauth2 oauthdirect logout' ) ;
  6267. ok( defined( $myimap ) && ! $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthdirect not IsAuthenticated after logout' ) ;
  6268. ok( defined( $myimap ) && $myimap->reconnect( ), 'login_imap: gmail oauth2 oauthdirect reconnect ok' ) ;
  6269. ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthdirect IsAuthenticated after reconnect' ) ;
  6270. }
  6271. # oauthaccesstoken authentication success for user1
  6272. SKIP: {
  6273. if ( ! -r 'oauth2/D_oauth2_access_token_imapsync.gl0@gmail.com.txt' )
  6274. {
  6275. skip( 'oauthaccesstoken: no access_token file', 6 ) ;
  6276. }
  6277. my $myimap ;
  6278. my $mysync = {} ;
  6279. my $acc = {} ;
  6280. $acc->{ Side } = 'Host1' ;
  6281. $acc->{ oauthaccesstoken } = 'oauth2/D_oauth2_access_token_imapsync.gl0@gmail.com.txt' ;
  6282. $acc->{ debugimap } = 1 ;
  6283. $mysync->{ showpasswords } = 1 ;
  6284. $acc->{ authmech } = 'QQQ' ;
  6285. isa_ok(
  6286. $myimap = login_imap( 'imap.gmail.com', 993, 'imapsync.gl0@gmail.com', 'password_useless',
  6287. 1, undef,
  6288. 1, 100, $acc, $mysync,
  6289. ), 'Mail::IMAPClient', 'login_imap: user1 good oauthaccesstoken => Mail::IMAPClient' ) ;
  6290. ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthaccesstoken IsAuthenticated' ) ;
  6291. ok( defined( $myimap ) && $myimap->logout( ), 'login_imap: gmail oauth2 oauthaccesstoken logout' ) ;
  6292. ok( defined( $myimap ) && ! $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthaccesstoken not IsAuthenticated after logout' ) ;
  6293. ok( defined( $myimap ) && $myimap->reconnect( ), 'login_imap: gmail oauth2 oauthaccesstoken reconnect ok' ) ;
  6294. ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthaccesstoken IsAuthenticated after reconnect' ) ;
  6295. }
  6296. note( 'Leaving tests_login_imap_oauth()' ) ;
  6297. return ;
  6298. }
  6299. sub login_imap
  6300. {
  6301. my @allargs = @_ ;
  6302. my(
  6303. $host, $port, $user, $password,
  6304. $ssl, $tls,
  6305. $uid, $split, $acc, $mysync ) = @allargs ;
  6306. $acc->{ imap } = undef ;
  6307. if ( ! all_defined( $host, $port, $user, $acc->{ Side } ) )
  6308. {
  6309. return ;
  6310. }
  6311. my $side = lc $acc->{ Side } ;
  6312. myprint( "$acc->{ Side }: connecting and login on $side [$host] port [$port] with user [$user]\n" ) ;
  6313. my $imap = init_imap( @allargs ) ;
  6314. if ( ! $imap->connect() )
  6315. {
  6316. my $error = "$acc->{ Side } failure: can not open imap connection on $side [$host] with user [$user]: "
  6317. . $imap->LastError . " $OS_ERROR\n" ;
  6318. errors_incr( $mysync, $error ) ;
  6319. return ;
  6320. }
  6321. myprint( "$acc->{ Side } IP address: ", $imap->Socket->peerhost(), "\n" ) ;
  6322. my $banner = $imap->Results()->[0] ;
  6323. myprint( "$acc->{ Side } banner: $banner" ) ;
  6324. myprint( "$acc->{ Side } capability before authentication: ", join(q{ }, @{ $imap->capability() || [] }), "\n" ) ;
  6325. if ( (! $ssl) and (! defined $tls ) and $imap->has_capability( 'STARTTLS' ) ) {
  6326. myprint( "$acc->{ Side }: going to ssl because STARTTLS is in CAPABILITY. Use --notls1 or --notls2 to avoid that behavior\n" ) ;
  6327. $tls = 1 ;
  6328. }
  6329. #myprint( Data::Dumper->Dump( [ @allargs ] ) ) ;
  6330. if ( $tls ) {
  6331. set_tls( $imap, $acc ) ;
  6332. if ( ! $imap->starttls( ) )
  6333. {
  6334. my $error = "$acc->{ Side } failure: Can not go to tls encryption on $side [$host]: "
  6335. . $imap->LastError . "\n" ;
  6336. errors_incr( $mysync, $error ) ;
  6337. return ;
  6338. }
  6339. myprint( "$acc->{ Side }: Socket successfully converted to SSL\n" ) ;
  6340. }
  6341. if ( $acc->{ authmech } eq 'PREAUTH' ) {
  6342. if ( $imap->IsAuthenticated( ) ) {
  6343. $imap->Socket ;
  6344. myprintf("%s: Assuming PREAUTH for %s\n", $acc->{ Side }, $imap->Server ) ;
  6345. }else{
  6346. $mysync->{nb_errors}++ ;
  6347. exit_clean(
  6348. $mysync, $EXIT_AUTHENTICATION_FAILURE,
  6349. "$acc->{ Side } failure: error login on $side [$host] with user [$user] auth [PREAUTH]\n"
  6350. ) ;
  6351. }
  6352. }
  6353. if ( authenticate_imap( $imap, @allargs ) )
  6354. {
  6355. myprint( "$acc->{ Side }: success login on [$host] with user [$user] auth [$acc->{ authmech }] or [LOGIN]\n" ) ;
  6356. $acc->{ imap } = $imap ;
  6357. return( $imap ) ;
  6358. }
  6359. else
  6360. {
  6361. # The errors are already printed
  6362. myprint( "$acc->{ Side }: failed login on [$host] with user [$user] auth [$acc->{ authmech }]\n" ) ;
  6363. return ;
  6364. }
  6365. }
  6366. sub init_imap
  6367. {
  6368. my(
  6369. $host, $port, $user, $password,
  6370. $ssl, $tls,
  6371. $uid, $split, $acc, $mysync ) = @_ ;
  6372. my ( $imap ) ;
  6373. $imap = Mail::IMAPClient->new() ;
  6374. if ( $mysync->{ tee } )
  6375. {
  6376. # Well, it does not change anything, does it?
  6377. # It does when suppressing the hack with *STDERR
  6378. $imap->Debug_fh( $mysync->{ tee } ) ;
  6379. }
  6380. if ( $ssl ) { set_ssl( $imap, $acc ) }
  6381. if ( $tls ) { } # can not do set_tls() here because connect() will directly do a STARTTLS
  6382. $imap->Clear( 1 ) ;
  6383. $imap->Server( $host ) ;
  6384. $imap->Port( $port ) ;
  6385. $imap->Fast_io( $acc->{ fastio } ) ;
  6386. $imap->Buffer( $buffersize || $DEFAULT_BUFFER_SIZE ) ;
  6387. $imap->Uid( $uid ) ;
  6388. $imap->Peek( 1 ) ;
  6389. $imap->Debug( $acc->{ debugimap } ) ;
  6390. if ( $mysync->{ showpasswords } ) {
  6391. $imap->Showcredentials( 1 ) ;
  6392. }
  6393. if ( defined( $acc->{ timeout } ) )
  6394. {
  6395. $imap->Timeout( $acc->{ timeout } ) ;
  6396. }
  6397. if ( defined $acc->{ keepalive } )
  6398. {
  6399. $imap->Keepalive( $acc->{ keepalive } ) ;
  6400. }
  6401. if ( defined $acc->{ reconnectretry } )
  6402. {
  6403. $imap->Reconnectretry( $acc->{ reconnectretry } ) ;
  6404. }
  6405. $imap->{IMAPSYNC_RECONNECT_COUNT} = 0 ;
  6406. $imap->Ignoresizeerrors( $allowsizemismatch ) ;
  6407. $split and $imap->Maxcommandlength( $SPLIT_FACTOR * $split ) ;
  6408. return( $imap ) ;
  6409. }
  6410. sub authenticate_imap
  6411. {
  6412. my( $imap,
  6413. $host, $port, $user, $password,
  6414. $ssl, $tls,
  6415. $uid, $split, $acc, $mysync ) = @_ ;
  6416. check_capability( $imap, $acc->{ authmech }, $acc->{ Side } ) ;
  6417. $imap->User( $user ) ;
  6418. if ( defined $acc->{ domain } )
  6419. {
  6420. $imap->Domain( $acc->{ domain } ) ;
  6421. $mysync->{ debug } and myprint( "Domain: $acc->{ domain }\n" ) ;
  6422. }
  6423. $imap->Authuser( $acc->{ authuser } ) ;
  6424. $imap->Password( $password ) ;
  6425. if ( 'X-MASTERAUTH' eq $acc->{ authmech } )
  6426. {
  6427. xmasterauth( $imap ) ;
  6428. return 1 ;
  6429. }
  6430. if ( defined $acc->{ oauthdirect } )
  6431. {
  6432. $acc->{ authmech } = 'XOAUTH2 direct' ;
  6433. return( oauthdirect( $mysync, $acc, $imap, $host, $user ) ) ;
  6434. }
  6435. if ( defined $acc->{ oauthaccesstoken } )
  6436. {
  6437. $acc->{ authmech } = 'XOAUTH2 accesstoken' ;
  6438. return( oauthaccesstoken( $mysync, $acc, $imap, $host, $user ) ) ;
  6439. }
  6440. if ( $acc->{ proxyauth } ) {
  6441. $imap->Authmechanism(q{}) ;
  6442. $imap->User( $acc->{ authuser } ) ;
  6443. } else {
  6444. $imap->Authmechanism( $acc->{ authmech } ) unless ( $acc->{ authmech } eq 'LOGIN' or $acc->{ authmech } eq 'PREAUTH' ) ;
  6445. }
  6446. $imap->Authcallback(\&xoauth2) if ( 'XOAUTH2' eq $acc->{ authmech } ) ;
  6447. $imap->Authcallback(\&plainauth) if ( ( 'PLAIN' eq $acc->{ authmech } ) or ( 'EXTERNAL' eq $acc->{ authmech } ) ) ;
  6448. unless ( $acc->{ authmech } eq 'PREAUTH' or $imap->login( ) ) {
  6449. my $info = "$acc->{ Side } failure: Error login on [$host] with user [$user] auth" ;
  6450. my $einfo = imap_last_error( $imap ) ;
  6451. my $error = "$info [$acc->{ authmech }]: $einfo\n" ;
  6452. if ( ( $acc->{ authmech } eq 'LOGIN' ) or $imap->IsUnconnected( ) or $acc->{ authuser } ) {
  6453. $acc->{ authuser } ||= "" ;
  6454. myprint( "$acc->{ Side } info: authmech [$acc->{ authmech }] user [$user] authuser [$acc->{ authuser }] IsUnconnected [", $imap->IsUnconnected( ), "]\n" ) ;
  6455. errors_incr( $mysync, $error ) ;
  6456. return ;
  6457. }else{
  6458. errors_incr( $mysync, $error ) ;
  6459. }
  6460. # It is not secure to try plain text LOGIN when another authmech failed
  6461. # but I do it anyway. This behavior is optional as option --notrylogin will skip it.
  6462. if ( $mysync->{ trylogin } )
  6463. {
  6464. myprint( "$acc->{ Side } info: trying LOGIN Auth mechanism on [$host] with user [$user]. Use option --notrylogin to avoid this second chance to login via LOGIN auth\n" ) ;
  6465. $imap->Authmechanism(q{}) ;
  6466. if ( ! $imap->login( ) )
  6467. {
  6468. failure_login( $mysync, $acc, 'LOGIN', $imap, $host, $user ) ;
  6469. return ;
  6470. }
  6471. else
  6472. {
  6473. myprint( "$acc->{ Side }: success login on [$host] with user [$user] auth [LOGIN] after [$acc->{ authmech }] failure\n" ) ;
  6474. }
  6475. }
  6476. else
  6477. {
  6478. myprint( "$acc->{ Side } info: not trying LOGIN Auth mechanism on [$host] with user [$user]. Use option --trylogin to have this second chance to login via LOGIN auth\n" ) ;
  6479. return ;
  6480. }
  6481. }
  6482. if ( $acc->{ proxyauth } ) {
  6483. if ( ! $imap->proxyauth( $user ) ) {
  6484. failure_proxyauth( $mysync, $acc, $acc->{ authmech }, $imap, $host, $user ) ;
  6485. return ;
  6486. }
  6487. }
  6488. return 1;
  6489. }
  6490. sub failure_login
  6491. {
  6492. my( $mysync, $acc, $authmech, $imap, $host, $user ) = @ARG ;
  6493. my $info = "$acc->{ Side } failure: Error login on [$host] with user [$user] auth" ;
  6494. my $einfo = imap_last_error( $imap ) ;
  6495. my $error = "$info [$authmech]: $einfo\n" ;
  6496. errors_incr( $mysync, $error ) ;
  6497. return ;
  6498. }
  6499. # failure_login and failure_proxyauth function are similar but
  6500. # variable $error so no factoring
  6501. sub failure_proxyauth
  6502. {
  6503. my( $mysync, $acc, $authmech, $imap, $host, $user ) = @ARG ;
  6504. my $info = "$acc->{ Side } failure: Error login on [$host] with user [$user] auth" ;
  6505. my $einfo = imap_last_error( $imap ) ;
  6506. my $error = "$info [$authmech] using proxy-login as [$acc->{ authuser }]: $einfo\n" ;
  6507. errors_incr( $mysync, $error ) ;
  6508. return ;
  6509. }
  6510. sub oauthdirect
  6511. {
  6512. my( $mysync, $acc, $imap, $host, $user ) = @_ ;
  6513. my $oauthdirect_str ;
  6514. if ( -f -r $acc->{ oauthdirect } )
  6515. {
  6516. $oauthdirect_str = firstline( $acc->{ oauthdirect } ) ;
  6517. }
  6518. else
  6519. {
  6520. $oauthdirect_str = $acc->{ oauthdirect } || 'Please define oauthdirect value' ;
  6521. }
  6522. $imap->Authmechanism( 'XOAUTH2' ) ;
  6523. $imap->Authcallback( sub { return $oauthdirect_str } ) ;
  6524. #if ( $imap->authenticate('XOAUTH2', sub { return $oauthdirect_str } ) )
  6525. if ( $imap->login( ) )
  6526. {
  6527. return 1 ;
  6528. }
  6529. else
  6530. {
  6531. failure_login( $mysync, $acc, $acc->{ authmech }, $imap, $host, $user ) ;
  6532. return ;
  6533. }
  6534. return ;
  6535. }
  6536. sub oauthaccesstoken
  6537. {
  6538. my( $mysync, $acc, $imap, $host, $user ) = @_ ;
  6539. my $oauthaccesstoken_str ;
  6540. if ( -f -r $acc->{ oauthaccesstoken } )
  6541. {
  6542. $oauthaccesstoken_str = firstline( $acc->{ oauthaccesstoken } ) ;
  6543. }
  6544. else
  6545. {
  6546. $oauthaccesstoken_str = $acc->{ oauthaccesstoken } || 'Please define oauthaccesstoken value' ;
  6547. }
  6548. my $oauth_string = "user=" . $user . "\x01auth=Bearer ". $oauthaccesstoken_str . "\x01\x01" ;
  6549. #myprint "oauth_string: $oauth_string\n" ;
  6550. my $oauth_string_base64 = encode_base64( $oauth_string , '' ) ;
  6551. #myprint "oauth_string_base64: $oauth_string_base64\n" ;
  6552. my $oauthdirect_str = $oauth_string_base64 ;
  6553. $imap->Authmechanism( 'XOAUTH2' ) ;
  6554. $imap->Authcallback( sub { return $oauthdirect_str } ) ;
  6555. #if ( $imap->authenticate('XOAUTH2', sub { return $oauthdirect_str } ) )
  6556. if ( $imap->login( ) )
  6557. {
  6558. return 1 ;
  6559. }
  6560. else
  6561. {
  6562. failure_login( $mysync, $acc, $acc->{ authmech }, $imap, $host, $user ) ;
  6563. return ;
  6564. }
  6565. return ;
  6566. }
  6567. sub check_capability
  6568. {
  6569. my( $imap, $authmech, $Side ) = @_ ;
  6570. if ( $imap->has_capability( "AUTH=$authmech" )
  6571. or $imap->has_capability( $authmech ) )
  6572. {
  6573. myprintf("%s: %s says it has CAPABILITY for AUTHENTICATE %s\n",
  6574. $Side, $imap->Server, $authmech) ;
  6575. return ;
  6576. }
  6577. if ( $authmech eq 'LOGIN' )
  6578. {
  6579. # Well, the warning is so common and useless that I prefer to remove it
  6580. # No more "... says it has NO CAPABILITY for AUTHENTICATE LOGIN"
  6581. return ;
  6582. }
  6583. myprintf( "%s: %s says it has NO CAPABILITY for AUTHENTICATE %s\n",
  6584. $Side, $imap->Server, $authmech ) ;
  6585. if ( $authmech eq 'PLAIN' )
  6586. {
  6587. myprint( "$Side: frequently PLAIN is only supported with SSL, try --ssl or --tls options\n" ) ;
  6588. }
  6589. return ;
  6590. }
  6591. sub set_ssl
  6592. {
  6593. my ( $imap, $acc ) = @_ ;
  6594. # SSL_version can be
  6595. # SSLv3 SSLv2 SSLv23 SSLv23:!SSLv2 (last one is the default in IO-Socket-SSL-1.953)
  6596. #
  6597. my $sslargs_hash = $acc->{sslargs} ;
  6598. my $sslargs_default = {
  6599. SSL_verify_mode => $SSL_VERIFY_POLICY,
  6600. SSL_verifycn_scheme => 'imap',
  6601. SSL_cipher_list => 'DEFAULT:!DH',
  6602. } ;
  6603. # initiate with default values
  6604. my %sslargs_mix = %{ $sslargs_default } ;
  6605. # now override with passed values
  6606. @sslargs_mix{ keys %{ $sslargs_hash } } = values %{ $sslargs_hash } ;
  6607. # remove keys with undef values
  6608. foreach my $key ( keys %sslargs_mix ) {
  6609. delete $sslargs_mix{ $key } if ( not defined $sslargs_mix{ $key } ) ;
  6610. }
  6611. # back to an ARRAY
  6612. my @sslargs_mix = %sslargs_mix ;
  6613. #myprint( Data::Dumper->Dump( [ $sslargs_hash, $sslargs_default, \%sslargs_mix, \@sslargs_mix ] ) ) ;
  6614. $imap->Ssl( \@sslargs_mix ) ;
  6615. return ;
  6616. }
  6617. sub set_tls
  6618. {
  6619. my ( $imap, $acc ) = @_ ;
  6620. my $sslargs_hash = $acc->{sslargs} ;
  6621. my $sslargs_default = {
  6622. SSL_verify_mode => $SSL_VERIFY_POLICY,
  6623. SSL_cipher_list => 'DEFAULT:!DH',
  6624. } ;
  6625. #myprint( Data::Dumper->Dump( [ $acc, $sslargs_hash, $sslargs_default ] ) ) ;
  6626. # initiate with default values
  6627. my %sslargs_mix = %{ $sslargs_default } ;
  6628. # now override with passed values
  6629. @sslargs_mix{ keys %{ $sslargs_hash } } = values %{ $sslargs_hash } ;
  6630. # remove keys with undef values
  6631. foreach my $key ( keys %sslargs_mix ) {
  6632. delete $sslargs_mix{ $key } if ( not defined $sslargs_mix{ $key } ) ;
  6633. }
  6634. # back to an ARRAY
  6635. my @sslargs_mix = %sslargs_mix ;
  6636. $imap->Starttls( \@sslargs_mix ) ;
  6637. return ;
  6638. }
  6639. sub plainauth
  6640. {
  6641. my $code = shift;
  6642. my $imap = shift;
  6643. my $string = mysprintf("%s\x00%s\x00%s", $imap->User,
  6644. defined $imap->Authuser ? $imap->Authuser : "", $imap->Password);
  6645. return encode_base64("$string", q{});
  6646. }
  6647. # Copy from https://github.com/imapsync/imapsync/pull/25/files
  6648. # Changes "use" pragmas to "require".
  6649. # The openssl system call shall be replaced by pure Perl and
  6650. # https://metacpan.org/pod/Crypt::OpenSSL::PKCS12
  6651. # Now the Joaquin Lopez code:
  6652. #
  6653. # Used this as an example: https://gist.github.com/gsainio/6322375
  6654. #
  6655. # And this as a reference: https://developers.google.com/accounts/docs/OAuth2ServiceAccount
  6656. # (note there is an http/rest tab, where the real info is hidden away... went on a witch hunt
  6657. # until I noticed that...)
  6658. #
  6659. # This is targeted at gmail to maintain compatibility after google's oauth1 service is deactivated
  6660. # on May 5th, 2015: https://developers.google.com/gmail/oauth_protocol
  6661. # If there are other oauth2 implementations out there, this would need to be modified to be
  6662. # compatible
  6663. #
  6664. # This is a good guide on setting up the google api/apps side of the equation:
  6665. # http://www.limilabs.com/blog/oauth2-gmail-imap-service-account
  6666. #
  6667. # 2016/05/27: Updated to support oauth/key data in the .json files Google now defaults to
  6668. # when creating gmail service accounts. They're easier to work with since they neither
  6669. # requiring decrypting nor specifying the oauth2 client id separately.
  6670. #
  6671. # If the password arg ends in .json, it will assume this new json method, otherwise it
  6672. # will fallback to the "oauth client id;.p12" format it was previously using.
  6673. sub xoauth2
  6674. {
  6675. require JSON::WebToken ;
  6676. require LWP::UserAgent ;
  6677. require HTML::Entities ;
  6678. require JSON ;
  6679. require JSON::WebToken::Crypt::RSA ;
  6680. require Crypt::OpenSSL::PKCS12;
  6681. require Crypt::OpenSSL::RSA ;
  6682. require Encode::Byte ;
  6683. require IO::Socket::SSL ;
  6684. my $code = shift;
  6685. my $imap = shift;
  6686. my ($iss,$key);
  6687. if( $imap->Password =~ /^(.*\.json)$/x )
  6688. {
  6689. my $json = JSON->new( ) ;
  6690. my $filename = $1;
  6691. $sync->{ debug } and myprint( "XOAUTH2 json file: $filename\n" ) ;
  6692. my $FILE ;
  6693. if ( ! open( $FILE, '<', $filename ) )
  6694. {
  6695. $sync->{nb_errors}++ ;
  6696. exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE,
  6697. "error [$filename]: $OS_ERROR\n"
  6698. ) ;
  6699. }
  6700. my $jsonfile = $json->decode( join q{}, <$FILE> ) ;
  6701. close $FILE ;
  6702. $iss = $jsonfile->{client_id};
  6703. $key = $jsonfile->{private_key};
  6704. $sync->{ debug } and myprint( "Service account: $iss\n");
  6705. $sync->{ debug } and myprint( "Private key:\n$key\n");
  6706. }
  6707. else
  6708. {
  6709. # Get iss (service account address), keyfile name, and keypassword if necessary
  6710. ( $iss, my $keyfile, my $keypass ) = $imap->Password =~ /([\-\d\w\@\.]+);([a-zA-Z0-9 \_\-\.\/]+);?(.*)?/x ;
  6711. # Assume key password is google default if not provided
  6712. $keypass = 'notasecret' if not $keypass;
  6713. $sync->{ debug } and myprint( "Service account: $iss\nKey file: $keyfile\nKey password: $keypass\n");
  6714. # Get private key from p12 file
  6715. my $pkcs12 = Crypt::OpenSSL::PKCS12->new_from_file($keyfile);
  6716. $key = $pkcs12->private_key($keypass);
  6717. $sync->{ debug } and myprint( "Private key:\n$key\n");
  6718. }
  6719. # Create jwt of oauth2 request
  6720. my $time = time ;
  6721. my $jwt = JSON::WebToken->encode( {
  6722. 'iss' => $iss, # service account
  6723. 'scope' => 'https://mail.google.com/',
  6724. 'aud' => 'https://www.googleapis.com/oauth2/v3/token',
  6725. 'exp' => $time + $DEFAULT_EXPIRATION_TIME_OAUTH2_PK12,
  6726. 'iat' => $time,
  6727. 'prn' => $imap->User # user to auth as
  6728. },
  6729. $key, 'RS256', {'typ' => 'JWT'} ); # Crypt::OpenSSL::RSA needed here.
  6730. # Post oauth2 request
  6731. my $ua = LWP::UserAgent->new( ) ;
  6732. $ua->env_proxy( ) ;
  6733. my $response = $ua->post('https://www.googleapis.com/oauth2/v3/token',
  6734. { grant_type => HTML::Entities::encode_entities('urn:ietf:params:oauth:grant-type:jwt-bearer'),
  6735. assertion => $jwt } ) ;
  6736. unless( $response->is_success( ) ) {
  6737. $sync->{nb_errors}++ ;
  6738. exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE,
  6739. $response->code, "\n", $response->content, "\n"
  6740. ) ;
  6741. }else{
  6742. $sync->{ debug } and myprint( $response->content ) ;
  6743. }
  6744. # access_token in response is what we need
  6745. my $data = JSON::decode_json( $response->content ) ;
  6746. # format as oauth2 auth data
  6747. my $xoauth2_string = encode_base64( 'user=' . $imap->User . "\1auth=Bearer " . $data->{access_token} . "\1\1", q{} ) ;
  6748. $sync->{ debug } and myprint( "XOAUTH2 String: $xoauth2_string\n");
  6749. return($xoauth2_string);
  6750. }
  6751. sub xmasterauth
  6752. {
  6753. # This is Kerio auth admin
  6754. # This code comes from
  6755. # https://github.com/imapsync/imapsync/pull/53/files
  6756. my $imap = shift ;
  6757. my $user = $imap->User( ) ;
  6758. my $password = $imap->Password( ) ;
  6759. my $authmech = 'X-MASTERAUTH' ;
  6760. my @challenge = $imap->tag_and_run( $authmech, "+" ) ;
  6761. if ( not defined $challenge[0] )
  6762. {
  6763. $sync->{nb_errors}++ ;
  6764. exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE,
  6765. "Failure authenticate with $authmech: ",
  6766. $imap->LastError, "\n"
  6767. ) ;
  6768. return ; # hahaha!
  6769. }
  6770. $sync->{ debug } and myprint( "X-MASTERAUTH challenge: [@challenge]\n" ) ;
  6771. $challenge[1] =~ s/^\+ |^\s+|\s+$//g ;
  6772. if ( ! $imap->_imap_command( { addcrlf => 1, addtag => 0, tag => $imap->Count }, md5_hex( $challenge[1] . $password ) ) )
  6773. {
  6774. $sync->{nb_errors}++ ;
  6775. exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE,
  6776. "Failure authenticate with $authmech: ",
  6777. $imap->LastError, "\n"
  6778. ) ;
  6779. }
  6780. if ( ! $imap->tag_and_run( 'X-SETUSER ' . $user ) )
  6781. {
  6782. $sync->{nb_errors}++ ;
  6783. exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE,
  6784. "Failure authenticate with $authmech: ",
  6785. "X-SETUSER ", $imap->LastError, "\n"
  6786. ) ;
  6787. }
  6788. $imap->State( Mail::IMAPClient::Authenticated ) ;
  6789. # I comment this state because "Selected" state is usually done by SELECT or EXAMINE imap commands
  6790. # $imap->State( Mail::IMAPClient::Selected ) ;
  6791. return ;
  6792. }
  6793. sub keepalive1
  6794. {
  6795. my $mysync = shift ;
  6796. $mysync->{ acc1 }->{ keepalive } = defined $mysync->{ acc1 }->{ keepalive } ? $mysync->{ acc1 }->{ keepalive } : 1 ;
  6797. if ( $mysync->{ acc1 }->{ keepalive } )
  6798. {
  6799. myprint( "Host1: imap connection keepalive is on on host1. Use --nokeepalive1 to disable it.\n" ) ;
  6800. }
  6801. else
  6802. {
  6803. myprint( "Host1: imap connection keepalive is off on host1. Use --keepalive1 to enable it.\n" ) ;
  6804. }
  6805. }
  6806. sub keepalive2
  6807. {
  6808. my $mysync = shift ;
  6809. $mysync->{ acc2 }->{ keepalive } = defined $mysync->{ acc2 }->{ keepalive } ? $mysync->{ acc2 }->{ keepalive } : 1 ;
  6810. if ( $mysync->{ acc2 }->{ keepalive } )
  6811. {
  6812. myprint( "Host2: imap connection keepalive is on on host2. Use --nokeepalive2 to disable it.\n" ) ;
  6813. }
  6814. else
  6815. {
  6816. myprint( "Host2: imap connection keepalive is off on host2. Use --keepalive2 to enable it.\n" ) ;
  6817. }
  6818. }
  6819. sub banner_imapsync
  6820. {
  6821. my $mysync = shift @ARG ;
  6822. my @argv = @ARG ;
  6823. my $banner_imapsync = join q{},
  6824. q{$RCSfile: imapsync,v $ },
  6825. q{$Revision: 2.178 $ },
  6826. q{$Date: 2022/01/12 21:28:37 $ },
  6827. "\n",
  6828. "Command line used, run by $EXECUTABLE_NAME:\n",
  6829. "$PROGRAM_NAME ", command_line_nopassword( $mysync, @argv ), "\n" ;
  6830. return( $banner_imapsync ) ;
  6831. }
  6832. sub tests_do_valid_directory
  6833. {
  6834. note( 'Entering tests_do_valid_directory()' ) ;
  6835. is( 1, do_valid_directory( '.'), 'do_valid_directory: . good' ) ;
  6836. is( 1, do_valid_directory( './W/tmp/tests/valid/sub'), 'do_valid_directory: ./W/tmp/tests/valid/sub good' ) ;
  6837. Readonly my $NB_UNIX_tests_do_valid_directory_non_root => 2 ;
  6838. diag( "OSNAME=$OSNAME EFFECTIVE_USER_ID=$EFFECTIVE_USER_ID" ) ;
  6839. SKIP: {
  6840. skip( 'Tests only for non roor user', $NB_UNIX_tests_do_valid_directory_non_root ) if ( '0' eq $EFFECTIVE_USER_ID ) ;
  6841. diag( 'The "Error / is not writable" is on purpose' ) ;
  6842. ok( 0 == do_valid_directory( '/'), 'do_valid_directory: / bad' ) ;
  6843. diag( 'The "Error permission denied" on /noway is on purpose' ) ;
  6844. ok( 0 == do_valid_directory( '/noway'), 'do_valid_directory: /noway bad' ) ;
  6845. }
  6846. note( 'Leaving tests_do_valid_directory()' ) ;
  6847. return ;
  6848. }
  6849. sub do_valid_directory
  6850. {
  6851. my $dir = shift @ARG ;
  6852. # all good => return ok.
  6853. return( 1 ) if ( -d $dir and -r _ and -w _ ) ;
  6854. # exist but bad
  6855. if ( -e $dir and not -d _ ) {
  6856. myprint( "Error: $dir exists but is not a directory\n" ) ;
  6857. return( 0 ) ;
  6858. }
  6859. if ( -e $dir and not -w _ ) {
  6860. my $sb = stat $dir ;
  6861. myprintf( "Error: directory %s is not writable for user %s, permissions are %04o and owner is %s ( uid %s )\n",
  6862. $dir, getpwuid_any_os( $EFFECTIVE_USER_ID ), ($sb->mode & oct($PERMISSION_FILTER) ), getpwuid_any_os( $sb->uid ), $sb->uid( ) ) ;
  6863. return( 0 ) ;
  6864. }
  6865. # Trying to create it
  6866. myprint( "Creating directory $dir (current directory is " . getcwd( ) . ")\n" ) ;
  6867. if ( ! eval { mkpath( $dir ) } ) {
  6868. myprint( "$EVAL_ERROR" ) if ( $EVAL_ERROR ) ;
  6869. }
  6870. return( 1 ) if ( -d $dir and -r _ and -w _ ) ;
  6871. return( 0 ) ;
  6872. }
  6873. sub tests_match_a_pid_number
  6874. {
  6875. note( 'Entering tests_match_a_pid_number()' ) ;
  6876. is( undef, match_a_pid_number( ), 'match_a_pid_number: no args => undef' ) ;
  6877. is( undef, match_a_pid_number( q{} ), 'match_a_pid_number: "" => undef' ) ;
  6878. is( undef, match_a_pid_number( 'lalala' ), 'match_a_pid_number: lalala => undef' ) ;
  6879. is( 1, match_a_pid_number( 1 ), 'match_a_pid_number: 1 => 1' ) ;
  6880. is( 1, match_a_pid_number( 123 ), 'match_a_pid_number: 123 => 1' ) ;
  6881. is( 1, match_a_pid_number( -123 ), 'match_a_pid_number: -123 => 1' ) ;
  6882. is( 1, match_a_pid_number( '123' ), 'match_a_pid_number: "123" => 1' ) ;
  6883. is( 1, match_a_pid_number( '-123' ), 'match_a_pid_number: "-123" => 1' ) ;
  6884. is( undef, match_a_pid_number( 'a123' ), 'match_a_pid_number: a123 => undef' ) ;
  6885. is( undef, match_a_pid_number( '-a123' ), 'match_a_pid_number: -a123 => undef' ) ;
  6886. is( 1, match_a_pid_number( 99999 ), 'match_a_pid_number: 99999 => 1' ) ;
  6887. is( 1, match_a_pid_number( -99999 ), 'match_a_pid_number: -99999 => 1' ) ;
  6888. is( undef, match_a_pid_number( 0 ), 'match_a_pid_number: 0 => undef' ) ;
  6889. is( 1, match_a_pid_number( 100000 ), 'match_a_pid_number: 100000 => 1' ) ;
  6890. is( 1, match_a_pid_number( 123456 ), 'match_a_pid_number: 123456 => 1' ) ;
  6891. is( undef, match_a_pid_number( '-0' ), 'match_a_pid_number: "-0" => undef' ) ;
  6892. is( 1, match_a_pid_number( -100000 ), 'match_a_pid_number: -100000 => 1' ) ;
  6893. is( 1, match_a_pid_number( -123456 ), 'match_a_pid_number: -123456 => 1' ) ;
  6894. is( 1, match_a_pid_number( 2**22 ), 'match_a_pid_number: 2**22 => 1' ) ;
  6895. is( undef, match_a_pid_number( 2**22 + 1 ), 'match_a_pid_number: 2**22 + 1 => undef' ) ;
  6896. is( undef, match_a_pid_number( 4194304 + 1 ), 'match_a_pid_number: 2**22 + 1 = 4194305 => undef' ) ;
  6897. note( 'Leaving tests_match_a_pid_number()' ) ;
  6898. return ;
  6899. }
  6900. sub match_a_pid_number
  6901. {
  6902. my $pid = shift @ARG ;
  6903. if ( ! defined $pid ) { return ; }
  6904. #print "$pid\n" ;
  6905. if ( ! match( $pid, '^-?\d+$' ) ) { return ; }
  6906. #print "$pid\n" ;
  6907. # can be negative on Windows
  6908. #if ( 0 > $pid ) { return ; }
  6909. #if ( 65535 < $pid ) { return ; }
  6910. if ( 2**22 < abs( $pid ) ) { return ; }
  6911. if ( 0 == abs( $pid ) ) { return ; }
  6912. return 1 ;
  6913. }
  6914. sub tests_remove_pidfile_not_running
  6915. {
  6916. note( 'Entering tests_remove_pidfile_not_running()' ) ;
  6917. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'remove_pidfile_not_running: mkpath W/tmp/tests/' ) ;
  6918. is( undef, remove_pidfile_not_running( ), 'remove_pidfile_not_running: no args => undef' ) ;
  6919. is( undef, remove_pidfile_not_running( './W' ), 'remove_pidfile_not_running: a dir => undef' ) ;
  6920. is( undef, remove_pidfile_not_running( 'noexists' ), 'remove_pidfile_not_running: noexists => undef' ) ;
  6921. is( 1, touch( 'W/tmp/tests/empty.pid' ), 'remove_pidfile_not_running: prepa empty W/tmp/tests/empty.pid' ) ;
  6922. is( undef, remove_pidfile_not_running( 'W/tmp/tests/empty.pid' ), 'remove_pidfile_not_running: W/tmp/tests/empty.pid => undef' ) ;
  6923. is( 'lalala', string_to_file( 'lalala', 'W/tmp/tests/lalala.pid' ), 'remove_pidfile_not_running: prepa W/tmp/tests/lalala.pid' ) ;
  6924. is( undef, remove_pidfile_not_running( 'W/tmp/tests/lalala.pid' ), 'remove_pidfile_not_running: W/tmp/tests/lalala.pid => undef' ) ;
  6925. is( '55555', string_to_file( '55555', 'W/tmp/tests/notrunning.pid' ), 'remove_pidfile_not_running: prepa W/tmp/tests/notrunning.pid' ) ;
  6926. is( 1, remove_pidfile_not_running( 'W/tmp/tests/notrunning.pid' ), 'remove_pidfile_not_running: W/tmp/tests/notrunning.pid => 1' ) ;
  6927. is( $PROCESS_ID, string_to_file( $PROCESS_ID, 'W/tmp/tests/running.pid' ), 'remove_pidfile_not_running: prepa W/tmp/tests/running.pid' ) ;
  6928. is( undef, remove_pidfile_not_running( 'W/tmp/tests/running.pid' ), 'remove_pidfile_not_running: W/tmp/tests/running.pid => undef' ) ;
  6929. note( 'Leaving tests_remove_pidfile_not_running()' ) ;
  6930. return ;
  6931. }
  6932. sub remove_pidfile_not_running
  6933. {
  6934. #
  6935. my $pid_filename = shift @ARG ;
  6936. #myprint( "In remove_pidfile_not_running $pid_filename\n" ) ;
  6937. if ( ! $pid_filename ) { myprint( "No variable pid_filename\n" ) ; return } ;
  6938. if ( ! -e $pid_filename )
  6939. {
  6940. myprint( "File $pid_filename does not exist\n" ) ;
  6941. return ;
  6942. }
  6943. #myprint( "Still In remove_pidfile_not_running $pid_filename\n" ) ;
  6944. if ( ! -f $pid_filename ) { myprint( "File $pid_filename is not a file\n" ) ; return } ;
  6945. my $pid = firstline( $pid_filename ) ;
  6946. if ( ! match_a_pid_number( $pid ) ) { myprint( "In remove_pidfile_not_running: pid $pid in $pid_filename is not a pid number\n" ) ; return } ;
  6947. # can't kill myself => do nothing
  6948. if ( ! kill 'ZERO', $PROCESS_ID ) { myprint( "Can not kill ZERO myself $PROCESS_ID\n" ) ; return } ;
  6949. # can't kill ZERO the pid => it is gone or own by another user => remove pidfile
  6950. if ( ! kill 'ZERO', $pid ) {
  6951. myprint( "Removing old $pid_filename since its PID $pid is not running anymore (oo-killed?)\n" ) ;
  6952. if ( unlink $pid_filename ) {
  6953. myprint( "Removed old $pid_filename\n" ) ;
  6954. return 1 ;
  6955. }else{
  6956. myprint( "Could not remove old $pid_filename because $!\n" ) ;
  6957. return ;
  6958. }
  6959. }
  6960. myprint( "Another imapsync process $pid is running as says pidfile $pid_filename\n" ) ;
  6961. return ;
  6962. }
  6963. sub tests_tail
  6964. {
  6965. note( 'Entering tests_tail()' ) ;
  6966. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'tail: mkpath W/tmp/tests/' ) ;
  6967. ok( ( ! -e 'W/tmp/tests/tail.pid' || unlink 'W/tmp/tests/tail.pid' ), 'tail: unlink W/tmp/tests/tail.pid' ) ;
  6968. ok( ( ! -e 'W/tmp/tests/tail.txt' || unlink 'W/tmp/tests/tail.txt' ), 'tail: unlink W/tmp/tests/tail.txt' ) ;
  6969. is( undef, tail( ), 'tail: no args => undef' ) ;
  6970. my $mysync ;
  6971. is( undef, tail( $mysync ), 'tail: no pidfile => undef' ) ;
  6972. $mysync->{pidfile} = 'W/tmp/tests/tail.pid' ;
  6973. is( undef, tail( $mysync ), 'tail: no pidfilelocking => undef' ) ;
  6974. $mysync->{pidfilelocking} = 1 ;
  6975. is( undef, tail( $mysync ), 'tail: pidfile no exists => undef' ) ;
  6976. my $pidandlog = "33333\nW/tmp/tests/tail.txt\n" ;
  6977. is( $pidandlog, string_to_file( $pidandlog, $mysync->{pidfile} ), 'tail: put pid 33333 and tail.txt in pidfile' ) ;
  6978. is( undef, tail( $mysync ), 'tail: logfile to tail no exists => undef' ) ;
  6979. my $tailcontent = "L1\nL2\nL3\nL4\nL5\n" ;
  6980. is( $tailcontent, string_to_file( $tailcontent, 'W/tmp/tests/tail.txt' ),
  6981. 'tail: put L1\nL2\nL3\nL4\nL5\n in W/tmp/tests/tail.txt' ) ;
  6982. is( undef, tail( $mysync ), 'tail: fake pid in pidfile + tail off => 1' ) ;
  6983. $mysync->{ tail } = 1 ;
  6984. is( 1, tail( $mysync ), 'tail: fake pid in pidfile + tail on=> 1' ) ;
  6985. # put my own pid, won't do tail
  6986. $pidandlog = "$PROCESS_ID\nW/tmp/tests/tail.txt\n" ;
  6987. is( $pidandlog, string_to_file( $pidandlog, $mysync->{pidfile} ), 'tail: put my own PID in pidfile' ) ;
  6988. is( undef, tail( $mysync ), 'tail: my own pid in pidfile => undef' ) ;
  6989. note( 'Leaving tests_tail()' ) ;
  6990. return ;
  6991. }
  6992. sub tail
  6993. {
  6994. # return undef on failures
  6995. # return 1 on success
  6996. my $mysync = shift ;
  6997. # no tail when aborting!
  6998. if ( $mysync->{ abort } ) { return ; }
  6999. my $pidfile = $mysync->{pidfile} ;
  7000. my $lock = $mysync->{pidfilelocking} ;
  7001. my $tail = $mysync->{tail} ;
  7002. if ( ! $pidfile ) { return ; }
  7003. if ( ! $lock ) { return ; }
  7004. if ( ! $tail ) { return ; }
  7005. if ( ! -e $pidfile ) { return ; }
  7006. my $pidtotail = firstline( $pidfile ) ;
  7007. if ( ! $pidtotail ) { return ; }
  7008. # It should not happen but who knows...
  7009. if ( $pidtotail eq $PROCESS_ID ) { return ; }
  7010. my $filetotail = secondline( $pidfile ) ;
  7011. if ( ! $filetotail ) { return ; }
  7012. if ( ! -r $filetotail )
  7013. {
  7014. #myprint( "Error: can not read $filetotail\n" ) ;
  7015. return ;
  7016. }
  7017. myprint( "Doing a tail -f on $filetotail for processus pid $pidtotail until it is finished.\n" ) ;
  7018. my $file = File::Tail->new(
  7019. name => $filetotail,
  7020. nowait => 1,
  7021. interval => 1,
  7022. tail => 1,
  7023. adjustafter => 2
  7024. );
  7025. my $moretimes = 200 ;
  7026. # print one line at least
  7027. my $line = $file->read ;
  7028. myprint( $line ) ;
  7029. while ( isrunning( $pidtotail, \$moretimes ) and defined( $line = $file->read ) )
  7030. {
  7031. myprint( $line );
  7032. sleep( 0.02 ) ;
  7033. }
  7034. return 1 ;
  7035. }
  7036. sub isrunning
  7037. {
  7038. my $pidtocheck = shift ;
  7039. my $moretimes_ref = shift ;
  7040. if ( kill 'ZERO', $pidtocheck )
  7041. {
  7042. #myprint( "$pidtocheck running\n" ) ;
  7043. return 1 ;
  7044. }
  7045. elsif ( $$moretimes_ref >= 0 )
  7046. {
  7047. # continue to consider it running
  7048. $$moretimes_ref-- ;
  7049. return 1 ;
  7050. }
  7051. else
  7052. {
  7053. myprint( "Tailed processus $pidtocheck ended\n" ) ;
  7054. return ;
  7055. }
  7056. }
  7057. sub tests_write_pidfile
  7058. {
  7059. note( 'Entering tests_write_pidfile()' ) ;
  7060. my $mysync ;
  7061. is( 1, write_pidfile( ), 'write_pidfile: no args => 1' ) ;
  7062. # no pidfile => ok
  7063. $mysync->{pidfile} = q{} ;
  7064. is( 1, write_pidfile( $mysync ), 'write_pidfile: no pidfile => undef' ) ;
  7065. # The pidfile path is bad => failure
  7066. $mysync->{pidfile} = '/no/no/no.pid' ;
  7067. is( undef, write_pidfile( $mysync ), 'write_pidfile: no permission for /no/no/no.pid, no lock => undef' ) ;
  7068. $mysync->{pidfilelocking} = 1 ;
  7069. is( undef, write_pidfile( $mysync ), 'write_pidfile: no permission for /no/no/no.pid + lock => undef' ) ;
  7070. $mysync->{pidfile} = 'W/tmp/tests/test.pid' ;
  7071. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'write_pidfile: mkpath W/tmp/tests/' ) ;
  7072. is( 1, touch( $mysync->{pidfile} ), 'write_pidfile: lock prepa' ) ;
  7073. $mysync->{pidfilelocking} = 0 ;
  7074. is( 1, write_pidfile( $mysync ), 'write_pidfile: W/tmp/tests/test.pid + no lock => 1' ) ;
  7075. is( $PROCESS_ID, firstline( 'W/tmp/tests/test.pid' ), "write_pidfile: W/tmp/tests/test.pid contains $PROCESS_ID" ) ;
  7076. is( q{}, secondline( 'W/tmp/tests/test.pid' ), "write_pidfile: W/tmp/tests/test.pid contains no second line" ) ;
  7077. $mysync->{pidfilelocking} = 1 ;
  7078. is( undef, write_pidfile( $mysync ), 'write_pidfile: W/tmp/tests/test.pid + lock => undef' ) ;
  7079. $mysync->{pidfilelocking} = 0 ;
  7080. $mysync->{ logfile } = 'rrrr.txt' ;
  7081. is( 1, write_pidfile( $mysync ), 'write_pidfile: W/tmp/tests/test.pid + no lock + logfile => 1' ) ;
  7082. is( $PROCESS_ID, firstline( 'W/tmp/tests/test.pid' ), "write_pidfile: + no lock + logfile W/tmp/tests/test.pid contains $PROCESS_ID" ) ;
  7083. is( q{rrrr.txt}, secondline( 'W/tmp/tests/test.pid' ), "write_pidfile: + no lock + logfile W/tmp/tests/test.pid contains rrrr.txt" ) ;
  7084. note( 'Leaving tests_write_pidfile()' ) ;
  7085. return ;
  7086. }
  7087. sub write_pidfile
  7088. {
  7089. # returns undef if something is considered fatal
  7090. # returns 1 otherwise
  7091. #myprint( "In write_pidfile\n" ) ;
  7092. if ( ! @ARG ) { return 1 ; }
  7093. my $mysync = shift @ARG ;
  7094. # Do not write the pid file if the current process goal is to abort the process designed by the pid file
  7095. if ( $mysync->{ abort } ) { return 1 ; }
  7096. #
  7097. my $pid_filename = $mysync->{ pidfile } ;
  7098. my $lock = $mysync->{ pidfilelocking } ;
  7099. if ( ! $pid_filename )
  7100. {
  7101. myprint( "PID file is unset ( to set it, use --pidfile filepath ; to avoid it use --pidfile \"\" )\n" ) ;
  7102. return( 1 ) ;
  7103. }
  7104. myprint( "PID file is $pid_filename ( to change it, use --pidfile filepath ; to avoid it use --pidfile \"\" )\n" ) ;
  7105. if ( -e $pid_filename and $lock ) {
  7106. myprint( "$pid_filename already exists, another imapsync may be curently running. Aborting imapsync.\n" ) ;
  7107. return ;
  7108. }
  7109. if ( -e $pid_filename ) {
  7110. myprint( "$pid_filename already exists, overwriting it ( use --pidfilelocking to avoid concurrent runs )\n" ) ;
  7111. }
  7112. my $pid_string = "$PROCESS_ID\n" ;
  7113. my $pid_message = "Writing my PID $PROCESS_ID in $pid_filename\n" ;
  7114. if ( $mysync->{ logfile } )
  7115. {
  7116. $pid_string .= "$mysync->{ logfile }\n" ;
  7117. $pid_message .= "Writing also my logfile name in $pid_filename : $mysync->{ logfile }\n" ;
  7118. }
  7119. if ( open my $FILE_HANDLE, '>', $pid_filename ) {
  7120. myprint( $pid_message ) ;
  7121. print $FILE_HANDLE $pid_string ;
  7122. close $FILE_HANDLE ;
  7123. return( 1 ) ;
  7124. }
  7125. else
  7126. {
  7127. myprint( "Could not open $pid_filename for writing. Check permissions or disk space: $OS_ERROR\n" ) ;
  7128. return ;
  7129. }
  7130. }
  7131. sub fix_Inbox_INBOX_mapping
  7132. {
  7133. my( $h1_all, $h2_all ) = @_ ;
  7134. my $regex = q{} ;
  7135. SWITCH: {
  7136. if ( exists $h1_all->{INBOX} and exists $h2_all->{INBOX} ) { $regex = q{} ; last SWITCH ; } ;
  7137. if ( exists $h1_all->{Inbox} and exists $h2_all->{Inbox} ) { $regex = q{} ; last SWITCH ; } ;
  7138. if ( exists $h1_all->{INBOX} and exists $h2_all->{Inbox} ) { $regex = q{s/^INBOX$/Inbox/x} ; last SWITCH ; } ;
  7139. if ( exists $h1_all->{Inbox} and exists $h2_all->{INBOX} ) { $regex = q{s/^Inbox$/INBOX/x} ; last SWITCH ; } ;
  7140. } ;
  7141. return( $regex ) ;
  7142. }
  7143. sub tests_fix_Inbox_INBOX_mapping
  7144. {
  7145. note( 'Entering tests_fix_Inbox_INBOX_mapping()' ) ;
  7146. my( $h1_all, $h2_all ) ;
  7147. $h1_all = { 'INBOX' => q{} } ;
  7148. $h2_all = { 'INBOX' => q{} } ;
  7149. ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX INBOX' ) ;
  7150. $h1_all = { 'Inbox' => q{} } ;
  7151. $h2_all = { 'Inbox' => q{} } ;
  7152. ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: Inbox Inbox' ) ;
  7153. $h1_all = { 'INBOX' => q{} } ;
  7154. $h2_all = { 'Inbox' => q{} } ;
  7155. ok( q{s/^INBOX$/Inbox/x} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX Inbox' ) ;
  7156. $h1_all = { 'Inbox' => q{} } ;
  7157. $h2_all = { 'INBOX' => q{} } ;
  7158. ok( q{s/^Inbox$/INBOX/x} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: Inbox INBOX' ) ;
  7159. $h1_all = { 'INBOX' => q{} } ;
  7160. $h2_all = { 'rrrrr' => q{} } ;
  7161. ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX rrrrrr' ) ;
  7162. $h1_all = { 'rrrrr' => q{} } ;
  7163. $h2_all = { 'Inbox' => q{} } ;
  7164. ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: rrrrr Inbox' ) ;
  7165. note( 'Leaving tests_fix_Inbox_INBOX_mapping()' ) ;
  7166. return ;
  7167. }
  7168. sub jux_utf8_list
  7169. {
  7170. my @s_inp = @_ ;
  7171. my $s_out = q{} ;
  7172. foreach my $s ( @s_inp ) {
  7173. $s_out .= jux_utf8( $s ) . "\n" ;
  7174. }
  7175. return( $s_out ) ;
  7176. }
  7177. sub tests_jux_utf8_list
  7178. {
  7179. note( 'Entering tests_jux_utf8_list()' ) ;
  7180. use utf8 ;
  7181. is( q{}, jux_utf8_list( ), 'jux_utf8_list: void' ) ;
  7182. is( "[]\n", jux_utf8_list( q{} ), 'jux_utf8_list: empty string' ) ;
  7183. is( "[INBOX]\n", jux_utf8_list( 'INBOX' ), 'jux_utf8_list: INBOX' ) ;
  7184. is( "[&ANY-] = [Ö]\n", jux_utf8_list( '&ANY-' ), 'jux_utf8_list: [&ANY-] = [Ö]' ) ;
  7185. note( 'Leaving tests_jux_utf8_list()' ) ;
  7186. return( 0 ) ;
  7187. }
  7188. # editing utf8 can be tricky without an utf8 editor
  7189. sub tests_jux_utf8_old
  7190. {
  7191. note( 'Entering tests_jux_utf8_old()' ) ;
  7192. no utf8 ;
  7193. is( '[]', jux_utf8_old( q{} ), 'jux_utf8_old: void => []' ) ;
  7194. is( '[INBOX]', jux_utf8_old( 'INBOX'), 'jux_utf8_old: INBOX => [INBOX]' ) ;
  7195. is( '[&ZTZO9nux-] = [æ”¶ä»¶ç®±]', jux_utf8_old( '&ZTZO9nux-'), 'jux_utf8_old: => [&ZTZO9nux-] = [æ”¶ä»¶ç®±]' ) ;
  7196. is( '[&ANY-] = [Ö]', jux_utf8_old( '&ANY-'), 'jux_utf8_old: &ANY- => [&ANY-] = [Ö]' ) ;
  7197. # +BD8EQAQ1BDQEOwQ+BDM- SHOULD stay as is!
  7198. is( '[+BD8EQAQ1BDQEOwQ+BDM-] = [предлог]', jux_utf8_old( '+BD8EQAQ1BDQEOwQ+BDM-' ), 'jux_utf8_old: => [+BD8EQAQ1BDQEOwQ+BDM-] = [предлог]' ) ;
  7199. is( '[&BB8EQAQ+BDUEOgRC-] = [Проект]', jux_utf8_old( '&BB8EQAQ+BDUEOgRC-' ), 'jux_utf8_old: => [&BB8EQAQ+BDUEOgRC-] = [Проект]' ) ;
  7200. note( 'Leaving tests_jux_utf8_old()' ) ;
  7201. return ;
  7202. }
  7203. sub jux_utf8_old
  7204. {
  7205. # juxtapose utf8 at the right if different
  7206. my ( $s_utf7 ) = shift ;
  7207. my ( $s_utf8 ) = imap_utf7_decode_old( $s_utf7 ) ;
  7208. if ( $s_utf7 eq $s_utf8 ) {
  7209. #myprint( "[$s_utf7]\n" ) ;
  7210. return( "[$s_utf7]" ) ;
  7211. }else{
  7212. #myprint( "[$s_utf7] = [$s_utf8]\n" ) ;
  7213. return( "[$s_utf7] = [$s_utf8]" ) ;
  7214. }
  7215. }
  7216. # Copied from http://cpansearch.perl.org/src/FABPOT/Unicode-IMAPUtf7-2.01/lib/Unicode/IMAPUtf7.pm
  7217. # and then fixed with
  7218. # https://rt.cpan.org/Public/Bug/Display.html?id=11172
  7219. sub imap_utf7_decode_old
  7220. {
  7221. my ( $s ) = shift ;
  7222. # Algorithm
  7223. # On remplace , par / dans les BASE 64 (, entre & et -)
  7224. # On remplace les &, non suivi d'un - par +
  7225. # On remplace les &- par &
  7226. $s =~ s/&([^,&\-]*),([^,\-&]*)\-/&$1\/$2\-/xg ;
  7227. $s =~ s/&(?!\-)/\+/xg ;
  7228. $s =~ s/&\-/&/xg ;
  7229. return( Unicode::String::utf7( $s )->utf8 ) ;
  7230. }
  7231. sub tests_jux_utf8
  7232. {
  7233. note( 'Entering tests_jux_utf8()' ) ;
  7234. #no utf8 ;
  7235. use utf8 ;
  7236. #binmode STDOUT, ":encoding(UTF-8)" ;
  7237. binmode STDERR, ":encoding(UTF-8)" ;
  7238. # This test is because the binary can fail on it, a PAR.pm issue.
  7239. # The failure was with the underlying Encode::IMAPUTF7 module line 66 release 1.05
  7240. # Was solved by including Encode in imapsync and using "pp -x".
  7241. ok( find_encoding( "UTF-16BE"), 'jux_utf8: Encode::find_encoding: UTF-16BE' ) ;
  7242. #
  7243. is( '[]', jux_utf8( q{} ), 'jux_utf8: void => []' ) ;
  7244. is( '[INBOX]', jux_utf8( 'INBOX'), 'jux_utf8: INBOX => [INBOX]' ) ;
  7245. is( '[&ANY-] = [Ö]', jux_utf8( '&ANY-'), 'jux_utf8: &ANY- => [&ANY-] = [Ö]' ) ;
  7246. # +BD8EQAQ1BDQEOwQ+BDM- must stay as is
  7247. is( '[+BD8EQAQ1BDQEOwQ+BDM-]', jux_utf8( '+BD8EQAQ1BDQEOwQ+BDM-' ), 'jux_utf8: => [+BD8EQAQ1BDQEOwQ+BDM-] = [+BD8EQAQ1BDQEOwQ+BDM-]' ) ;
  7248. is( '[&BB8EQAQ+BDUEOgRC-] = [Проект]', jux_utf8( '&BB8EQAQ+BDUEOgRC-' ), 'jux_utf8: => [&BB8EQAQ+BDUEOgRC-] = [Проект]' ) ;
  7249. is( '[R&AOk-ponses 1200+1201+1202] = [Réponses 1200+1201+1202]', jux_utf8( q{R&AOk-ponses 1200+1201+1202} ), 'jux_utf8: [R&AOk-ponses 1200+1201+1202] = [Réponses 1200+1201+1202]' ) ;
  7250. my $str = Encode::IMAPUTF7::encode("IMAP-UTF-7", 'Réponses 1200+1201+1202' ) ;
  7251. is( '[R&AOk-ponses 1200+1201+1202] = [Réponses 1200+1201+1202]', jux_utf8( $str ), "jux_utf8: [$str] = [Réponses 1200+1201+1202]" ) ;
  7252. is( '[INBOX.&AOkA4ADnAPk-&-*] = [INBOX.éà çù&*]', jux_utf8( 'INBOX.&AOkA4ADnAPk-&-*' ), "jux_utf8: [INBOX.&AOkA4ADnAPk-&-*] = [INBOX.éà çù&*]" ) ;
  7253. is( '[&ZTZO9nux-] = [æ”¶ä»¶ç®±]', jux_utf8( '&ZTZO9nux-'), 'jux_utf8: => [&ZTZO9nux-] = [æ”¶ä»¶ç®±]' ) ;
  7254. #
  7255. #
  7256. is( '[!Old Emails]', jux_utf8( '!Old Emails'), 'jux_utf8: !Old Emails => [!Old Emails]' ) ;
  7257. is( '[2006 Budget & Fcst]', jux_utf8( '2006 Budget & Fcst'), 'jux_utf8: 2006 Budget & Fcst => [2006 Budget & Fcst]' ) ;
  7258. note( 'Leaving tests_jux_utf8()' ) ;
  7259. return ;
  7260. }
  7261. sub jux_utf8
  7262. {
  7263. #use utf8 ;
  7264. # juxtapose utf8 at the right if different
  7265. my ( $s_utf7 ) = shift ;
  7266. my ( $s_utf8 ) = imap_utf7_decode( $s_utf7 ) ;
  7267. if ( $s_utf7 eq $s_utf8 ) {
  7268. #myprint( "[$s_utf7]\n" ) ;
  7269. return( "[$s_utf7]" ) ;
  7270. }else{
  7271. #myprint( "[$s_utf7] = [$s_utf8]\n" ) ;
  7272. return( "[$s_utf7] = [$s_utf8]" ) ;
  7273. }
  7274. }
  7275. sub imap_utf7_decode
  7276. {
  7277. #use utf8 ;
  7278. my ( $s ) = shift ;
  7279. return( Encode::IMAPUTF7::decode("IMAP-UTF-7", $s ) ) ;
  7280. }
  7281. sub imap_utf7_encode
  7282. {
  7283. #use utf8 ;
  7284. my ( $s ) = shift ;
  7285. return( Encode::IMAPUTF7::encode("IMAP-UTF-7", $s ) ) ;
  7286. }
  7287. sub imap_utf7_encode_old
  7288. {
  7289. my ( $s ) = @_ ;
  7290. $s = Unicode::String::utf8( $s )->utf7 ;
  7291. $s =~ s/\+([^\/&\-]*)\/([^\/\-&]*)\-/\+$1,$2\-/xg ;
  7292. $s =~ s/&/&\-/xg ;
  7293. $s =~ s/\+([^+\-]+)?\-/&$1\-/xg ;
  7294. return( $s ) ;
  7295. }
  7296. sub select_folder
  7297. {
  7298. my ( $mysync, $imap, $folder, $hostside ) = @_ ;
  7299. if ( ! $imap->select( $folder ) ) {
  7300. my $error = join q{},
  7301. "$hostside folder $folder: Could not select: ",
  7302. $imap->LastError, "\n" ;
  7303. errors_incr( $mysync, $error ) ;
  7304. return( 0 ) ;
  7305. }else{
  7306. # ok select succeeded
  7307. return( 1 ) ;
  7308. }
  7309. }
  7310. sub examine_folder
  7311. {
  7312. my ( $mysync, $imap, $folder, $hostside ) = @_ ;
  7313. if ( ! $imap->examine( $folder ) ) {
  7314. my $error = join q{},
  7315. "$hostside folder $folder: Could not examine: ",
  7316. $imap->LastError, "\n" ;
  7317. errors_incr( $mysync, $error ) ;
  7318. return( 0 ) ;
  7319. }else{
  7320. # ok select succeeded
  7321. return( 1 ) ;
  7322. }
  7323. }
  7324. sub count_from_select
  7325. {
  7326. my @lines = @ARG ;
  7327. my $count ;
  7328. foreach my $line ( @lines ) {
  7329. #myprint( "line = [$line]\n" ) ;
  7330. if ( $line =~ m/^\*\s+(\d+)\s+EXISTS/x ) {
  7331. $count = $1 ;
  7332. return( $count ) ;
  7333. }
  7334. }
  7335. return( undef ) ;
  7336. }
  7337. sub create_folder_old
  7338. {
  7339. my $mysync = shift @ARG ;
  7340. my( $imap, $h2_fold, $h1_fold ) = @ARG ;
  7341. myprint( "Creating (old way) folder [$h2_fold] on host2\n" ) ;
  7342. if ( ( 'INBOX' eq uc $h2_fold )
  7343. and ( $imap->exists( $h2_fold ) ) ) {
  7344. myprint( "Folder [$h2_fold] already exists\n" ) ;
  7345. return( 1 ) ;
  7346. }
  7347. if ( ! $mysync->{dry} ){
  7348. if ( ! $imap->create( $h2_fold ) ) {
  7349. my $error = join q{},
  7350. "Could not create folder [$h2_fold] from [$h1_fold]: ",
  7351. $imap->LastError( ), "\n" ;
  7352. errors_incr( $mysync, $error ) ;
  7353. # success if folder exists ("already exists" error)
  7354. return( 1 ) if $imap->exists( $h2_fold ) ;
  7355. # failure since create failed
  7356. return( 0 ) ;
  7357. }else{
  7358. #create succeeded
  7359. myprint( "Created ( the old way ) folder [$h2_fold] on host2\n" ) ;
  7360. return( 1 ) ;
  7361. }
  7362. }else{
  7363. # dry mode, no folder so many imap will fail, assuming failure
  7364. myprint( "Created ( the old way ) folder [$h2_fold] on host2 $mysync->{dry_message}\n" ) ;
  7365. return( 0 ) ;
  7366. }
  7367. }
  7368. sub create_folder
  7369. {
  7370. my $mysync = shift @ARG ;
  7371. my( $myimap2 , $h2_fold , $h1_fold ) = @ARG ;
  7372. my( @parts , $parent ) ;
  7373. if ( $myimap2->IsUnconnected( ) ) {
  7374. myprint( "Host2: Unconnected state\n" ) ;
  7375. return( 0 ) ;
  7376. }
  7377. if ( $create_folder_old ) {
  7378. return( create_folder_old( $mysync, $myimap2 , $h2_fold , $h1_fold ) ) ;
  7379. }
  7380. # $imap->exists() calls $imap->status() that does an IMAP STATUS folder
  7381. myprint( "Creating folder [$h2_fold] on host2\n" ) ;
  7382. if ( ( 'INBOX' eq uc $h2_fold )
  7383. and ( $myimap2->exists( $h2_fold ) ) ) {
  7384. myprint( "Folder [$h2_fold] already exists\n" ) ;
  7385. return( 1 ) ;
  7386. }
  7387. if ( $mixfolders and $myimap2->exists( $h2_fold ) ) {
  7388. myprint( "Folder [$h2_fold] already exists (--nomixfolders is not set)\n" ) ;
  7389. return( 1 ) ;
  7390. }
  7391. if ( ( not $mixfolders ) and ( $myimap2->exists( $h2_fold ) ) ) {
  7392. myprint( "Folder [$h2_fold] already exists and --nomixfolders is set\n" ) ;
  7393. return( 0 ) ;
  7394. }
  7395. @parts = split /\Q$mysync->{ h2_sep }\E/x, $h2_fold ;
  7396. pop @parts ;
  7397. $parent = join $mysync->{ h2_sep }, @parts ;
  7398. $parent =~ s/^\s+|\s+$//xg ;
  7399. if ( ( $parent ne q{} ) and ( ! $myimap2->exists( $parent ) ) ) {
  7400. create_folder( $mysync, $myimap2 , $parent , $h1_fold ) ;
  7401. }
  7402. if ( ! $mysync->{dry} ) {
  7403. if ( ! $myimap2->create( $h2_fold ) ) {
  7404. my $error = join q{},
  7405. "Could not create folder [$h2_fold] from [$h1_fold]: " ,
  7406. $myimap2->LastError( ), "\n" ;
  7407. errors_incr( $mysync, $error ) ;
  7408. # success if folder exists ("already exists" error) or selectable
  7409. if ( $myimap2->exists( $h2_fold ) or select_folder( $mysync, $myimap2, $h2_fold, 'Host2' ) )
  7410. {
  7411. return( 1 ) ;
  7412. }
  7413. # failure since create failed + not exist + not selectable
  7414. return( 0 ) ;
  7415. }else{
  7416. #create succeeded
  7417. myprint( "Created folder [$h2_fold] on host2\n" ) ;
  7418. return( 1 ) ;
  7419. }
  7420. }else{
  7421. # dry mode, no folder so many imap will fail, assuming failure
  7422. myprint( "Created folder [$h2_fold] on host2 $mysync->{dry_message}\n" ) ;
  7423. if ( ! $mysync->{ justfolders } ) {
  7424. myprint( "Since --dry mode is on and folder [$h2_fold] on host2 does not exist yet, syncing messages will not be simulated.\n"
  7425. . "To simulate message syncing, use --justfolders without --dry to first create the missing folders then rerun the --dry sync.\n" ) ;
  7426. # The messages that could be transferred are counted and the number is given at the end.
  7427. }
  7428. return( 0 ) ;
  7429. }
  7430. }
  7431. sub tests_folder_routines
  7432. {
  7433. note( 'Entering tests_folder_routines()' ) ;
  7434. ok( !is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 1' );
  7435. ok( add_to_requested_folders('folder_foo'), 'add_to_requested_folders folder_foo' );
  7436. ok( is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 2' );
  7437. ok( !is_requested_folder('folder_NO_EXIST'), 'is_requested_folder folder_NO_EXIST' );
  7438. is_deeply( [ 'folder_foo' ], [ remove_from_requested_folders( 'folder_foo' ) ], 'removed folder_foo => folder_foo' ) ;
  7439. ok( !is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 3' );
  7440. my @f ;
  7441. ok( @f = add_to_requested_folders('folder_bar', 'folder_toto'), "add result: @f" );
  7442. ok( is_requested_folder('folder_bar'), 'is_requested_folder 4' );
  7443. ok( is_requested_folder('folder_toto'), 'is_requested_folder 5' );
  7444. ok( remove_from_requested_folders('folder_toto'), 'remove_from_requested_folders: ' );
  7445. ok( !is_requested_folder('folder_toto'), 'is_requested_folder 6' );
  7446. is_deeply( [ 'folder_bar' ], [ remove_from_requested_folders('folder_bar') ], 'remove_from_requested_folders: empty' ) ;
  7447. ok( 0 == compare_lists( [ sort_requested_folders( ) ], [] ), 'sort_requested_folders: all empty' ) ;
  7448. ok( add_to_requested_folders( 'A_99', 'M_55', 'Z_11' ), 'add_to_requested_folders M_55 Z_11' );
  7449. ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'A_99', 'M_55', 'Z_11' ] ), 'sort_requested_folders: middle' ) ;
  7450. @folderfirst = ( 'Z_11' ) ;
  7451. ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'A_99', 'M_55' ] ), 'sort_requested_folders: first+middle' ) ;
  7452. is_deeply( [ 'Z_11', 'A_99', 'M_55' ], [ sort_requested_folders( ) ], 'sort_requested_folders: first+middle is_deeply' ) ;
  7453. @folderlast = ( 'A_99' ) ;
  7454. ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'M_55', 'A_99' ] ), 'sort_requested_folders: first+middle+last 1' ) ;
  7455. ok( add_to_requested_folders('M_55', 'M_44',), 'add_to_requested_folders M_55 M_44' ) ;
  7456. ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'M_44', 'M_55', 'A_99'] ), 'sort_requested_folders: first+middle+last 2' ) ;
  7457. ok( add_to_requested_folders('A_88', 'Z_22',), 'add_to_requested_folders A_88 Z_22' ) ;
  7458. @folderfirst = qw( Z_22 Z_11 ) ;
  7459. @folderlast = qw( A_99 A_88 ) ;
  7460. 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' ) ;
  7461. undef @folderfirst ;
  7462. undef @folderlast ;
  7463. note( 'Leaving tests_folder_routines()' ) ;
  7464. return ;
  7465. }
  7466. sub sort_requested_folders
  7467. {
  7468. my @requested_folders_sorted = () ;
  7469. $sync->{ debug } and myprint "folderfirst: @folderfirst\n" ;
  7470. my @folderfirst_requested = remove_from_requested_folders( @folderfirst ) ;
  7471. #myprint "folderfirst_requested: @folderfirst_requested\n" ;
  7472. my @folderlast_requested = remove_from_requested_folders( @folderlast ) ;
  7473. my @middle = sort keys %requested_folder ;
  7474. @requested_folders_sorted = ( @folderfirst_requested, @middle, @folderlast_requested ) ;
  7475. $sync->{ debug } and myprint "requested_folders_sorted: @requested_folders_sorted\n" ;
  7476. add_to_requested_folders( @requested_folders_sorted ) ;
  7477. return( @requested_folders_sorted ) ;
  7478. }
  7479. sub is_requested_folder
  7480. {
  7481. my ( $folder ) = @_;
  7482. return( defined $requested_folder{ $folder } ) ;
  7483. }
  7484. sub add_to_requested_folders
  7485. {
  7486. my @wanted_folders = @_ ;
  7487. foreach my $folder ( @wanted_folders ) {
  7488. ++$requested_folder{ $folder } ;
  7489. }
  7490. return( keys %requested_folder ) ;
  7491. }
  7492. sub tests_remove_from_requested_folders
  7493. {
  7494. note( 'Entering tests_remove_from_requested_folders()' ) ;
  7495. is( undef, undef, 'remove_from_requested_folders: undef is undef' ) ;
  7496. is_deeply( [], [ remove_from_requested_folders( ) ], 'remove_from_requested_folders: no args' ) ;
  7497. %requested_folder = (
  7498. 'F1' => 1,
  7499. ) ;
  7500. is_deeply( [], [ remove_from_requested_folders( ) ], 'remove_from_requested_folders: remove nothing among F1 => nothing' ) ;
  7501. is_deeply( [], [ remove_from_requested_folders( 'Fno' ) ], 'remove_from_requested_folders: remove Fno among F1 => nothing' ) ;
  7502. is_deeply( [ 'F1' ], [ remove_from_requested_folders( 'F1' ) ], 'remove_from_requested_folders: remove F1 among F1 => F1' ) ;
  7503. is_deeply( { }, { %requested_folder }, 'remove_from_requested_folders: remove F1 among F1 => %requested_folder emptied' ) ;
  7504. %requested_folder = (
  7505. 'F1' => 1,
  7506. 'F2' => 1,
  7507. ) ;
  7508. is_deeply( [], [ remove_from_requested_folders( ) ], 'remove_from_requested_folders: remove nothing among F1 F2 => nothing' ) ;
  7509. is_deeply( [], [ remove_from_requested_folders( 'Fno' ) ], 'remove_from_requested_folders: remove Fno among F1 F2 => nothing' ) ;
  7510. is_deeply( [ 'F1' ], [ remove_from_requested_folders( 'F1' ) ], 'remove_from_requested_folders: remove F1 among F1 F2 => F1' ) ;
  7511. is_deeply( { 'F2' => 1 }, { %requested_folder }, 'remove_from_requested_folders: remove F1 among F1 F2 => %requested_folder F2' ) ;
  7512. is_deeply( [], [ remove_from_requested_folders( 'F1' ) ], 'remove_from_requested_folders: remove F1 among F2 => nothing' ) ;
  7513. is_deeply( [ 'F2' ], [ remove_from_requested_folders( 'F1', 'F2' ) ], 'remove_from_requested_folders: remove F1 F2 among F2 => F2' ) ;
  7514. is_deeply( {}, { %requested_folder }, 'remove_from_requested_folders: remove F1 among F1 F2 => %requested_folder F2' ) ;
  7515. %requested_folder = (
  7516. 'F1' => 1,
  7517. 'F2' => 1,
  7518. 'F3' => 1,
  7519. ) ;
  7520. is_deeply( [ 'F1', 'F2' ], [ remove_from_requested_folders( 'F1', 'F2' ) ], 'remove_from_requested_folders: remove F1 F2 among F1 F2 F3 => F1 F2' ) ;
  7521. is_deeply( { 'F3' => 1 }, { %requested_folder }, 'remove_from_requested_folders: remove F1 F2 among F1 F2 F3 => %requested_folder F3' ) ;
  7522. undef %requested_folder ;
  7523. note( 'Leaving tests_remove_from_requested_folders()' ) ;
  7524. return ;
  7525. }
  7526. sub remove_from_requested_folders
  7527. {
  7528. my @unwanted_folders = @_ ;
  7529. my @removed_folders = () ;
  7530. foreach my $folder ( @unwanted_folders ) {
  7531. if ( exists $requested_folder{ $folder } )
  7532. {
  7533. delete $requested_folder{ $folder } ;
  7534. push @removed_folders, $folder ;
  7535. }
  7536. }
  7537. return( @removed_folders ) ;
  7538. }
  7539. sub compare_lists
  7540. {
  7541. my ($list_1_ref, $list_2_ref) = @_;
  7542. return($MINUS_ONE) if ((not defined $list_1_ref) and defined $list_2_ref);
  7543. return(0) if ((not defined $list_1_ref) and not defined $list_2_ref); # end if no list
  7544. return(1) if (not defined $list_2_ref); # end if only one list
  7545. if (not ref $list_1_ref ) {$list_1_ref = [$list_1_ref]};
  7546. if (not ref $list_2_ref ) {$list_2_ref = [$list_2_ref]};
  7547. my $last_used_indice = $MINUS_ONE;
  7548. ELEMENT:
  7549. foreach my $indice ( 0 .. $#{ $list_1_ref } ) {
  7550. $last_used_indice = $indice ;
  7551. # End of list_2
  7552. return 1 if ($indice > $#{ $list_2_ref } ) ;
  7553. my $element_list_1 = $list_1_ref->[$indice] ;
  7554. my $element_list_2 = $list_2_ref->[$indice] ;
  7555. my $balance = $element_list_1 cmp $element_list_2 ;
  7556. next ELEMENT if ($balance == 0) ;
  7557. return $balance ;
  7558. }
  7559. # each element equal until last indice of list_1
  7560. return $MINUS_ONE if ($last_used_indice < $#{ $list_2_ref } ) ;
  7561. # same size, each element equal
  7562. return 0 ;
  7563. }
  7564. sub tests_compare_lists
  7565. {
  7566. note( 'Entering tests_compare_lists()' ) ;
  7567. my $empty_list_ref = [];
  7568. ok( 0 == compare_lists() , 'compare_lists, no args');
  7569. ok( 0 == compare_lists(undef) , 'compare_lists, undef = nothing');
  7570. ok( 0 == compare_lists(undef, undef) , 'compare_lists, undef = undef');
  7571. ok($MINUS_ONE == compare_lists(undef , []) , 'compare_lists, undef < []');
  7572. ok($MINUS_ONE == compare_lists(undef , [1]) , 'compare_lists, undef < [1]');
  7573. ok($MINUS_ONE == compare_lists(undef , [0]) , 'compare_lists, undef < [0]');
  7574. ok(+1 == compare_lists([]) , 'compare_lists, [] > nothing');
  7575. ok(+1 == compare_lists([], undef) , 'compare_lists, [] > undef');
  7576. ok( 0 == compare_lists([] , []) , 'compare_lists, [] = []');
  7577. ok($MINUS_ONE == compare_lists([] , [1]) , 'compare_lists, [] < [1]');
  7578. ok(+1 == compare_lists([1] , []) , 'compare_lists, [1] > []');
  7579. ok( 0 == compare_lists( [1], 1 ) , 'compare_lists, [1] = 1 ') ;
  7580. ok( 0 == compare_lists( 1 , [1] ) , 'compare_lists, 1 = [1]') ;
  7581. ok( 0 == compare_lists( 1 , 1 ) , 'compare_lists, 1 = 1 ') ;
  7582. ok( $MINUS_ONE == compare_lists( 0 , 1 ) , 'compare_lists, 0 < 1 ') ;
  7583. ok( $MINUS_ONE == compare_lists( $MINUS_ONE , 0 ) , 'compare_lists, -1 < 0 ') ;
  7584. ok( $MINUS_ONE == compare_lists( 1 , 2 ) , 'compare_lists, 1 < 2 ') ;
  7585. ok( +1 == compare_lists( 2 , 1 ) , 'compare_lists, 2 > 1 ') ;
  7586. ok( 0 == compare_lists([1,2], [1,2]) , 'compare_lists, [1,2] = [1,2]' ) ;
  7587. ok($MINUS_ONE == compare_lists([1], [1,2]) , 'compare_lists, [1] < [1,2]' ) ;
  7588. ok(+1 == compare_lists([2], [1,2]) , 'compare_lists, [2] > [1,2]' ) ;
  7589. ok($MINUS_ONE == compare_lists([1], [1,1]) , 'compare_lists, [1] < [1,1]' ) ;
  7590. ok(+1 == compare_lists([1, 1], [1]) , 'compare_lists, [1, 1] > [1]' ) ;
  7591. ok( 0 == compare_lists([1 .. $NUMBER_20_000] , [1 .. $NUMBER_20_000])
  7592. , 'compare_lists, [1..20_000] = [1..20_000]' ) ;
  7593. ok($MINUS_ONE == compare_lists([1], [2]) , 'compare_lists, [1] < [2]') ;
  7594. ok( 0 == compare_lists([2], [2]) , 'compare_lists, [0] = [2]') ;
  7595. ok(+1 == compare_lists([2], [1]) , 'compare_lists, [2] > [1]') ;
  7596. ok($MINUS_ONE == compare_lists(['a'], ['b']) , 'compare_lists, ["a"] < ["b"]') ;
  7597. ok( 0 == compare_lists(['a'], ['a']) , 'compare_lists, ["a"] = ["a"]') ;
  7598. ok( 0 == compare_lists(['ab'], ['ab']) , 'compare_lists, ["ab"] = ["ab"]') ;
  7599. ok(+1 == compare_lists(['b'], ['a']) , 'compare_lists, ["b"] > ["a"]') ;
  7600. ok($MINUS_ONE == compare_lists(['a'], ['aa']) , 'compare_lists, ["a"] < ["aa"]') ;
  7601. ok($MINUS_ONE == compare_lists(['a'], ['a', 'a']), 'compare_lists, ["a"] < ["a", "a"]') ;
  7602. ok( 0 == compare_lists([split q{ }, 'a b' ], ['a', 'b']), 'compare_lists, split') ;
  7603. ok( 0 == compare_lists([sort split q{ }, 'b a' ], ['a', 'b']), 'compare_lists, sort split') ;
  7604. note( 'Leaving tests_compare_lists()' ) ;
  7605. return ;
  7606. }
  7607. sub guess_prefix
  7608. {
  7609. my @foldernames = @_ ;
  7610. my $prefix_guessed = q{} ;
  7611. foreach my $folder ( @foldernames ) {
  7612. next if ( $folder =~ m{^INBOX$}xi ) ; # no guessing from INBOX
  7613. if ( $folder !~ m{^INBOX}xi ) {
  7614. $prefix_guessed = q{} ; # prefix empty guessed
  7615. last ;
  7616. }
  7617. if ( $folder =~ m{^(INBOX(?:\.|\/))}xi ) {
  7618. $prefix_guessed = $1 ; # prefix Inbox/ or INBOX. guessed
  7619. }
  7620. }
  7621. return( $prefix_guessed ) ;
  7622. }
  7623. sub tests_guess_prefix
  7624. {
  7625. note( 'Entering tests_guess_prefix()' ) ;
  7626. is( guess_prefix( ), q{}, 'guess_prefix: no args => empty string' ) ;
  7627. is( q{} , guess_prefix( 'INBOX' ), 'guess_prefix: INBOX alone' ) ;
  7628. is( q{} , guess_prefix( 'Inbox' ), 'guess_prefix: Inbox alone' ) ;
  7629. is( q{} , guess_prefix( 'INBOX' ), 'guess_prefix: INBOX alone' ) ;
  7630. is( 'INBOX/' , guess_prefix( 'INBOX', 'INBOX/Junk' ), 'guess_prefix: INBOX INBOX/Junk' ) ;
  7631. is( 'INBOX.' , guess_prefix( 'INBOX', 'INBOX.Junk' ), 'guess_prefix: INBOX INBOX.Junk' ) ;
  7632. is( 'Inbox/' , guess_prefix( 'Inbox', 'Inbox/Junk' ), 'guess_prefix: Inbox Inbox/Junk' ) ;
  7633. is( 'Inbox.' , guess_prefix( 'Inbox', 'Inbox.Junk' ), 'guess_prefix: Inbox Inbox.Junk' ) ;
  7634. is( 'INBOX/' , guess_prefix( 'INBOX', 'INBOX/Junk', 'INBOX/rrr' ), 'guess_prefix: INBOX INBOX/Junk INBOX/rrr' ) ;
  7635. is( q{} , guess_prefix( 'INBOX', 'INBOX/Junk', 'INBOX/rrr', 'zzz' ), 'guess_prefix: INBOX INBOX/Junk INBOX/rrr zzz' ) ;
  7636. is( q{} , guess_prefix( 'INBOX', 'Junk' ), 'guess_prefix: INBOX Junk' ) ;
  7637. is( q{} , guess_prefix( 'INBOX', 'Junk' ), 'guess_prefix: INBOX Junk' ) ;
  7638. note( 'Leaving tests_guess_prefix()' ) ;
  7639. return ;
  7640. }
  7641. sub get_prefix
  7642. {
  7643. my( $imap, $prefix_in, $prefix_opt, $Side, $folders_ref ) = @_ ;
  7644. my( $prefix_out, $prefix_guessed ) ;
  7645. ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "$Side: Getting prefix\n" ) ;
  7646. $prefix_guessed = guess_prefix( @{ $folders_ref } ) ;
  7647. myprint( "$Side: guessing prefix from folder listing: [$prefix_guessed]\n" ) ;
  7648. ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "$Side: Calling namespace capability\n" ) ;
  7649. if ( $imap->has_capability( 'namespace' ) ) {
  7650. my $r_namespace = $imap->namespace( ) ;
  7651. $prefix_out = $r_namespace->[0][0][0] ;
  7652. myprint( "$Side: prefix given by NAMESPACE: [$prefix_out]\n" ) ;
  7653. if ( defined $prefix_in ) {
  7654. myprint( "$Side: but using [$prefix_in] given by $prefix_opt\n" ) ;
  7655. $prefix_out = $prefix_in ;
  7656. return( $prefix_out ) ;
  7657. }else{
  7658. # all good
  7659. return( $prefix_out ) ;
  7660. }
  7661. }
  7662. else{
  7663. if ( defined $prefix_in ) {
  7664. myprint( "$Side: using [$prefix_in] given by $prefix_opt\n" ) ;
  7665. $prefix_out = $prefix_in ;
  7666. return( $prefix_out ) ;
  7667. }else{
  7668. myprint(
  7669. "$Side: No NAMESPACE capability so using guessed prefix [$prefix_guessed]\n",
  7670. help_to_guess_prefix( $imap, $prefix_opt ) ) ;
  7671. return( $prefix_guessed ) ;
  7672. }
  7673. }
  7674. return ;
  7675. }
  7676. sub guess_separator
  7677. {
  7678. my @foldernames = @_ ;
  7679. #return( undef ) unless ( @foldernames ) ;
  7680. my $sep_guessed ;
  7681. my %counter ;
  7682. foreach my $folder ( @foldernames ) {
  7683. $counter{'/'}++ while ( $folder =~ m{/}xg ) ; # count /
  7684. $counter{'.'}++ while ( $folder =~ m{\.}xg ) ; # count .
  7685. $counter{'\\\\'}++ while ( $folder =~ m{(\\){2}}xg ) ; # count \\
  7686. $counter{'\\'}++ while ( $folder =~ m{[^\\](\\){1}(?=[^\\])}xg ) ; # count \
  7687. }
  7688. my @race_sorted = sort { $counter{ $b } <=> $counter{ $a } } keys %counter ;
  7689. $sync->{ debug } and myprint( "@foldernames\n@race_sorted\n", %counter, "\n" ) ;
  7690. $sep_guessed = shift @race_sorted || $LAST_RESSORT_SEPARATOR ; # / when nothing found.
  7691. return( $sep_guessed ) ;
  7692. }
  7693. sub tests_guess_separator
  7694. {
  7695. note( 'Entering tests_guess_separator()' ) ;
  7696. ok( '/' eq guess_separator( ), 'guess_separator: no args' ) ;
  7697. ok( '/' eq guess_separator( 'abcd' ), 'guess_separator: abcd' ) ;
  7698. ok( '/' eq guess_separator( 'a/b/c.d' ), 'guess_separator: a/b/c.d' ) ;
  7699. ok( '.' eq guess_separator( 'a.b/c.d' ), 'guess_separator: a.b/c.d' ) ;
  7700. ok( '\\\\' eq guess_separator( 'a\\\\b\\\\c.c\\\\d/e/f' ), 'guess_separator: a\\\\b\\\\c.c\\\\d/e/f' ) ;
  7701. ok( '\\' eq guess_separator( 'a\\b\\c.c\\d/e/f' ), 'guess_separator: a\\b\\c.c\\d/e/f' ) ;
  7702. ok( '\\' eq guess_separator( 'a\\b' ), 'guess_separator: a\\b' ) ;
  7703. ok( '\\' eq guess_separator( 'a\\b\\c' ), 'guess_separator: a\\b\\c' ) ;
  7704. note( 'Leaving tests_guess_separator()' ) ;
  7705. return ;
  7706. }
  7707. sub get_separator
  7708. {
  7709. my( $imap, $sep_in, $sep_opt, $Side, $folders_ref ) = @_ ;
  7710. my( $sep_out, $sep_guessed ) ;
  7711. ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "$Side: Getting separator\n" ) ;
  7712. $sep_guessed = guess_separator( @{ $folders_ref } ) ;
  7713. myprint( "$Side: guessing separator from folder listing: [$sep_guessed]\n" ) ;
  7714. ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "$Side: calling namespace capability\n" ) ;
  7715. if ( $imap->has_capability( 'namespace' ) )
  7716. {
  7717. $sep_out = $imap->separator( ) ;
  7718. if ( defined $sep_out ) {
  7719. myprint( "$Side: separator given by NAMESPACE: [$sep_out]\n" ) ;
  7720. if ( defined $sep_in ) {
  7721. myprint( "$Side: but using [$sep_in] given by $sep_opt\n" ) ;
  7722. $sep_out = $sep_in ;
  7723. return( $sep_out ) ;
  7724. }else{
  7725. return( $sep_out ) ;
  7726. }
  7727. }else{
  7728. if ( defined $sep_in ) {
  7729. myprint( "$Side: NAMESPACE request failed but using [$sep_in] given by $sep_opt\n" ) ;
  7730. $sep_out = $sep_in ;
  7731. return( $sep_out ) ;
  7732. }else{
  7733. myprint(
  7734. "$Side: NAMESPACE request failed so using guessed separator [$sep_guessed]\n",
  7735. help_to_guess_sep( $imap, $sep_opt ) ) ;
  7736. return( $sep_guessed ) ;
  7737. }
  7738. }
  7739. }
  7740. else
  7741. {
  7742. if ( defined $sep_in ) {
  7743. myprint( "$Side: No NAMESPACE capability but using [$sep_in] given by $sep_opt\n" ) ;
  7744. $sep_out = $sep_in ;
  7745. return( $sep_out ) ;
  7746. }else{
  7747. myprint(
  7748. "$Side: No NAMESPACE capability, so using guessed separator [$sep_guessed]\n",
  7749. help_to_guess_sep( $imap, $sep_opt ) ) ;
  7750. return( $sep_guessed ) ;
  7751. }
  7752. }
  7753. return ;
  7754. }
  7755. sub help_to_guess_sep
  7756. {
  7757. my( $imap, $sep_opt ) = @_ ;
  7758. my $help_to_guess_sep = "You can set the separator character with the $sep_opt option,\n"
  7759. . "the complete listing of folders may help you to find it\n"
  7760. . folders_list_to_help( $imap ) ;
  7761. return( $help_to_guess_sep ) ;
  7762. }
  7763. sub help_to_guess_prefix
  7764. {
  7765. my( $imap, $prefix_opt ) = @_ ;
  7766. my $help_to_guess_prefix = "You can set the prefix namespace with the $prefix_opt option,\n"
  7767. . "the folowing listing of folders may help you to find it:\n"
  7768. . folders_list_to_help( $imap ) ;
  7769. return( $help_to_guess_prefix ) ;
  7770. }
  7771. sub folders_list_to_help
  7772. {
  7773. my( $imap ) = shift ;
  7774. my @folders = $imap->folders ;
  7775. my $listing = join q{}, map { "[$_]\n" } @folders ;
  7776. return( $listing ) ;
  7777. }
  7778. # Globals are $sync @h1_folders_all @h2_folders_all $prefix1 $prefix2
  7779. sub private_folders_separators_and_prefixes
  7780. {
  7781. # what are the private folders separators and prefixes for each server ?
  7782. ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "Getting separators\n" ) ;
  7783. $sync->{ h1_sep } = get_separator( $sync->{imap1}, $sync->{ sep1 }, '--sep1', 'Host1', \@h1_folders_all ) ;
  7784. $sync->{ h2_sep } = get_separator( $sync->{imap2}, $sync->{ sep2 }, '--sep2', 'Host2', \@h2_folders_all ) ;
  7785. $sync->{ h1_prefix } = get_prefix( $sync->{imap1}, $prefix1, '--prefix1', 'Host1', \@h1_folders_all ) ;
  7786. $sync->{ h2_prefix } = get_prefix( $sync->{imap2}, $prefix2, '--prefix2', 'Host2', \@h2_folders_all ) ;
  7787. myprint( "Host1: separator and prefix: [$sync->{ h1_sep }][$sync->{ h1_prefix }]\n" ) ;
  7788. myprint( "Host2: separator and prefix: [$sync->{ h2_sep }][$sync->{ h2_prefix }]\n" ) ;
  7789. return ;
  7790. }
  7791. sub subfolder1
  7792. {
  7793. my $mysync = shift ;
  7794. my $subfolder1 = sanitize_subfolder( $mysync->{ subfolder1 } ) ;
  7795. if ( $subfolder1 )
  7796. {
  7797. # turns off automap
  7798. myprint( "Turning off automapping folders because of --subfolder1\n" ) ;
  7799. $mysync->{ automap } = undef ;
  7800. myprint( "Sanitizing subfolder1: [$mysync->{ subfolder1 }] => [$subfolder1]\n" ) ;
  7801. $mysync->{ subfolder1 } = $subfolder1 ;
  7802. if ( ! add_subfolder1_to_folderrec( $mysync ) )
  7803. {
  7804. $mysync->{nb_errors}++ ;
  7805. exit_clean( $mysync, $EXIT_SUBFOLDER1_NO_EXISTS, "subfolder1 $subfolder1 does not exist\n" ) ;
  7806. }
  7807. }
  7808. else
  7809. {
  7810. $mysync->{ subfolder1 } = undef ;
  7811. }
  7812. return ;
  7813. }
  7814. sub subfolder2
  7815. {
  7816. my $mysync = shift ;
  7817. my $subfolder2 = sanitize_subfolder( $mysync->{ subfolder2 } ) ;
  7818. if ( $subfolder2 )
  7819. {
  7820. # turns off automap
  7821. myprint( "Turning off automapping folders because of --subfolder2\n" ) ;
  7822. $mysync->{ automap } = undef ;
  7823. myprint( "Sanitizing subfolder2: [$mysync->{ subfolder2 }] => [$subfolder2]\n" ) ;
  7824. $mysync->{ subfolder2 } = $subfolder2 ;
  7825. set_regextrans2_for_subfolder2( $mysync ) ;
  7826. }
  7827. else
  7828. {
  7829. $mysync->{ subfolder2 } = undef ;
  7830. }
  7831. return ;
  7832. }
  7833. sub tests_sanitize_subfolder
  7834. {
  7835. note( 'Entering tests_sanitize_subfolder()' ) ;
  7836. is( undef, sanitize_subfolder( ), 'sanitize_subfolder: no args => undef' ) ;
  7837. is( undef, sanitize_subfolder( q{} ), 'sanitize_subfolder: empty => undef' ) ;
  7838. is( undef, sanitize_subfolder( ' ' ), 'sanitize_subfolder: blank => undef' ) ;
  7839. is( undef, sanitize_subfolder( ' ' ), 'sanitize_subfolder: blanks => undef' ) ;
  7840. is( 'abcd', sanitize_subfolder( 'abcd' ), 'sanitize_subfolder: abcd => abcd' ) ;
  7841. is( 'ab cd', sanitize_subfolder( ' ab cd ' ), 'sanitize_subfolder: " ab cd " => "ab cd"' ) ;
  7842. is( 'abcd', sanitize_subfolder( q{a&~b#\\c[]=d;} ), 'sanitize_subfolder: "a&~b#\\c[]=d;" => "abcd"' ) ;
  7843. is( 'aA.b-_ 8c/dD', sanitize_subfolder( 'aA.b-_ 8c/dD' ), 'sanitize_subfolder: aA.b-_ 8c/dD => aA.b-_ 8c/dD' ) ;
  7844. note( 'Leaving tests_sanitize_subfolder()' ) ;
  7845. return ;
  7846. }
  7847. sub sanitize_subfolder
  7848. {
  7849. my $subfolder = shift ;
  7850. if ( ! $subfolder )
  7851. {
  7852. return ;
  7853. }
  7854. # Remove edging blanks
  7855. $subfolder =~ s,^ +| +$,,g ;
  7856. # Keep only abcd...ABCD...0123... and -_./
  7857. $subfolder =~ tr,-_a-zA-Z0-9./ ,,cd ;
  7858. # A blank subfolder is not a subfolder
  7859. if ( ! $subfolder )
  7860. {
  7861. return ;
  7862. }
  7863. else
  7864. {
  7865. return $subfolder ;
  7866. }
  7867. }
  7868. sub tests_sanitize_host
  7869. {
  7870. note( 'Entering tests_sanitize_host()' ) ;
  7871. is( undef, sanitize_host( ), 'sanitize_host: no args => undef' ) ;
  7872. is( '', sanitize_host( '' ), 'sanitize_host: empty => empty' ) ;
  7873. is( 'imap.example.org', sanitize_host( 'imap.example.org' ), 'sanitize_host: imap.example.org => imap.example.org' ) ;
  7874. is( 'imap.example.org', sanitize_host( ' imap.example.org' ), 'sanitize_host: imap.example.org 1 => imap.example.org' ) ;
  7875. is( 'imap.example.org', sanitize_host( 'imap.example.org ' ), 'sanitize_host: imap.example.org 2 => imap.example.org' ) ;
  7876. is( 'imap.example.org', sanitize_host( 'imap.exam ple.org' ), 'sanitize_host: imap.example.org 3 => imap.example.org' ) ;
  7877. is( 'imap.example.org', sanitize_host( ' imap.exam ple.org ' ), 'sanitize_host: imap.example.org 4 => imap.example.org' ) ;
  7878. is( 'imap.example.org', sanitize_host( 'imap.exa/mple.org/' ), 'sanitize_host: imap.example.org/ => imap.example.org' ) ;
  7879. note( 'Leaving tests_sanitize_host()' ) ;
  7880. return ;
  7881. }
  7882. sub sanitize_host
  7883. {
  7884. my $host = shift ;
  7885. if ( ! defined $host ) { return ; }
  7886. $host =~ tr{ /}{}d ;
  7887. return $host ;
  7888. }
  7889. sub tests_add_subfolder1_to_folderrec
  7890. {
  7891. note( 'Entering tests_add_subfolder1_to_folderrec()' ) ;
  7892. is( undef, add_subfolder1_to_folderrec( ), 'add_subfolder1_to_folderrec: undef => undef' ) ;
  7893. is_deeply( [], [ add_subfolder1_to_folderrec( ) ], 'add_subfolder1_to_folderrec: no args => empty array' ) ;
  7894. @folderrec = () ;
  7895. my $mysync = {} ;
  7896. is_deeply( [ ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: empty => empty array' ) ;
  7897. is_deeply( [ ], [ @folderrec ], 'add_subfolder1_to_folderrec: empty => empty folderrec' ) ;
  7898. $mysync->{ subfolder1 } = 'SUBI' ;
  7899. $h1_folders_all{ 'SUBI' } = 1 ;
  7900. $mysync->{ h1_prefix } = 'INBOX/' ;
  7901. is_deeply( [ 'SUBI' ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: SUBI => SUBI' ) ;
  7902. is_deeply( [ 'SUBI' ], [ @folderrec ], 'add_subfolder1_to_folderrec: SUBI => folderrec SUBI ' ) ;
  7903. @folderrec = () ;
  7904. $mysync->{ subfolder1 } = 'SUBO' ;
  7905. is_deeply( [ ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: SUBO no exists => empty array' ) ;
  7906. is_deeply( [ ], [ @folderrec ], 'add_subfolder1_to_folderrec: SUBO no exists => empty folderrec' ) ;
  7907. $h1_folders_all{ 'INBOX/SUBO' } = 1 ;
  7908. is_deeply( [ 'INBOX/SUBO' ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: SUBO + INBOX/SUBO exists => INBOX/SUBO' ) ;
  7909. is_deeply( [ 'INBOX/SUBO' ], [ @folderrec ], 'add_subfolder1_to_folderrec: SUBO + INBOX/SUBO exists => INBOX/SUBO folderrec' ) ;
  7910. note( 'Leaving tests_add_subfolder1_to_folderrec()' ) ;
  7911. return ;
  7912. }
  7913. sub add_subfolder1_to_folderrec
  7914. {
  7915. my $mysync = shift ;
  7916. if ( ! $mysync || ! $mysync->{ subfolder1 } )
  7917. {
  7918. return ;
  7919. }
  7920. my $subfolder1 = $mysync->{ subfolder1 } ;
  7921. my $subfolder1_extended = $mysync->{ h1_prefix } . $subfolder1 ;
  7922. if ( exists $h1_folders_all{ $subfolder1 } )
  7923. {
  7924. myprint( qq{Acting like --folderrec "$subfolder1"\n} ) ;
  7925. push @folderrec, $subfolder1 ;
  7926. }
  7927. elsif ( exists $h1_folders_all{ $subfolder1_extended } )
  7928. {
  7929. myprint( qq{Acting like --folderrec "$subfolder1_extended"\n} ) ;
  7930. push @folderrec, $subfolder1_extended ;
  7931. }
  7932. else
  7933. {
  7934. myprint( qq{Nor folder "$subfolder1" nor "$subfolder1_extended" exists on host1\n} ) ;
  7935. }
  7936. return @folderrec ;
  7937. }
  7938. sub set_regextrans2_for_subfolder2
  7939. {
  7940. my $mysync = shift ;
  7941. unshift @{ $mysync->{ regextrans2 } },
  7942. q(s,^$mysync->{ h2_prefix }(.*),$mysync->{ h2_prefix }$mysync->{ subfolder2 }$mysync->{ h2_sep }$1,),
  7943. q(s,^INBOX$,$mysync->{ h2_prefix }$mysync->{ subfolder2 }$mysync->{ h2_sep }INBOX,),
  7944. q(s,^($mysync->{ h2_prefix }){2},$mysync->{ h2_prefix },);
  7945. #myprint( "@{ $mysync->{ regextrans2 } }\n" ) ;
  7946. return ;
  7947. }
  7948. # Looks like no globals here
  7949. sub tests_imap2_folder_name
  7950. {
  7951. note( 'Entering tests_imap2_folder_name()' ) ;
  7952. my $mysync = {} ;
  7953. $mysync->{ h1_prefix } = q{} ;
  7954. $mysync->{ h2_prefix } = q{} ;
  7955. $mysync->{ h1_sep } = '/';
  7956. $mysync->{ h2_sep } = '.';
  7957. $mysync->{ debug } and myprint( <<"EOS"
  7958. prefix1: [$mysync->{ h1_prefix }]
  7959. prefix2: [$mysync->{ h2_prefix }]
  7960. sep1: [$sync->{ h1_sep }]
  7961. sep2: [$sync->{ h2_sep }]
  7962. EOS
  7963. ) ;
  7964. $mysync->{ fixslash2 } = 0 ;
  7965. is( q{INBOX}, imap2_folder_name( $mysync, q{} ), 'imap2_folder_name: empty string' ) ;
  7966. is( 'blabla', imap2_folder_name( $mysync, 'blabla' ), 'imap2_folder_name: blabla' ) ;
  7967. is('spam.spam', imap2_folder_name( $mysync, 'spam/spam' ), 'imap2_folder_name: spam/spam' ) ;
  7968. is( 'spam/spam', imap2_folder_name( $mysync, 'spam.spam' ), 'imap2_folder_name: spam.spam') ;
  7969. is( 'spam.spam/spam', imap2_folder_name( $mysync, 'spam/spam.spam' ), 'imap2_folder_name: spam/spam.spam' ) ;
  7970. is( 's pam.spam/sp am', imap2_folder_name( $mysync, 's pam/spam.sp am' ), 'imap2_folder_name: s pam/spam.sp am' ) ;
  7971. $mysync->{f1f2h}{ 'auto' } = 'moto' ;
  7972. is( 'moto', imap2_folder_name( $mysync, 'auto' ), 'imap2_folder_name: auto' ) ;
  7973. $mysync->{f1f2h}{ 'auto/auto' } = 'moto x 2' ;
  7974. is( 'moto x 2', imap2_folder_name( $mysync, 'auto/auto' ), 'imap2_folder_name: auto/auto' ) ;
  7975. @{ $mysync->{ regextrans2 } } = ( 's,/,X,g' ) ;
  7976. is( q{INBOX}, imap2_folder_name( $mysync, q{} ), 'imap2_folder_name: empty string [s,/,X,g]' ) ;
  7977. is( 'blabla', imap2_folder_name( $mysync, 'blabla' ), 'imap2_folder_name: blabla [s,/,X,g]' ) ;
  7978. is('spam.spam', imap2_folder_name( $mysync, 'spam/spam'), 'imap2_folder_name: spam/spam [s,/,X,g]');
  7979. is('spamXspam', imap2_folder_name( $mysync, 'spam.spam'), 'imap2_folder_name: spam.spam [s,/,X,g]');
  7980. is('spam.spamXspam', imap2_folder_name( $mysync, 'spam/spam.spam'), 'imap2_folder_name: spam/spam.spam [s,/,X,g]');
  7981. @{ $mysync->{ regextrans2 } } = ( 's, ,_,g' ) ;
  7982. is('blabla', imap2_folder_name( $mysync, 'blabla'), 'imap2_folder_name: blabla [s, ,_,g]');
  7983. is('bla_bla', imap2_folder_name( $mysync, 'bla bla'), 'imap2_folder_name: blabla [s, ,_,g]');
  7984. @{ $mysync->{ regextrans2 } } = ( q{s,(.*),\U$1,} ) ;
  7985. is( 'BLABLA', imap2_folder_name( $mysync, 'blabla' ), q{imap2_folder_name: blabla [s,\U(.*)\E,$1,]} ) ;
  7986. $mysync->{ fixslash2 } = 1 ;
  7987. @{ $mysync->{ regextrans2 } } = ( ) ;
  7988. is(q{INBOX}, imap2_folder_name( $mysync, q{}), 'imap2_folder_name: empty string');
  7989. is('blabla', imap2_folder_name( $mysync, 'blabla'), 'imap2_folder_name: blabla');
  7990. is('spam.spam', imap2_folder_name( $mysync, 'spam/spam'), 'imap2_folder_name: spam/spam -> spam.spam');
  7991. is('spam_spam', imap2_folder_name( $mysync, 'spam.spam'), 'imap2_folder_name: spam.spam -> spam_spam');
  7992. is('spam.spam_spam', imap2_folder_name( $mysync, 'spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam_spam');
  7993. 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');
  7994. $mysync->{ h1_sep } = '.';
  7995. $mysync->{ h2_sep } = '/';
  7996. is( q{INBOX}, imap2_folder_name( $mysync, q{}), 'imap2_folder_name: empty string');
  7997. is('blabla', imap2_folder_name( $mysync, 'blabla'), 'imap2_folder_name: blabla');
  7998. is('spam.spam', imap2_folder_name( $mysync, 'spam/spam'), 'imap2_folder_name: spam/spam -> spam.spam');
  7999. is('spam/spam', imap2_folder_name( $mysync, 'spam.spam'), 'imap2_folder_name: spam.spam -> spam/spam');
  8000. is('spam.spam/spam', imap2_folder_name( $mysync, 'spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam');
  8001. $mysync->{ fixslash2 } = 0 ;
  8002. $mysync->{ h1_prefix } = q{ };
  8003. is( 'spam.spam/spam', imap2_folder_name( $mysync, 'spam/spam.spam' ), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam' ) ;
  8004. is( 'spam.spam/spam', imap2_folder_name( $mysync, ' spam/spam.spam' ), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam' ) ;
  8005. $mysync->{ h1_sep } = '.' ;
  8006. $mysync->{ h2_sep } = '/' ;
  8007. $mysync->{ h1_prefix } = 'INBOX.' ;
  8008. $mysync->{ h2_prefix } = q{} ;
  8009. @{ $mysync->{ regextrans2 } } = ( q{s,(.*),\U$1,} ) ;
  8010. is( 'BLABLA', imap2_folder_name( $mysync, 'blabla' ), 'imap2_folder_name: blabla' ) ;
  8011. is( 'TEST/TEST/TEST/TEST', imap2_folder_name( $mysync, 'INBOX.TEST.test.Test.tesT' ), 'imap2_folder_name: INBOX.TEST.test.Test.tesT' ) ;
  8012. @{ $mysync->{ regextrans2 } } = ( q{s,(.*),\L$1,} ) ;
  8013. is( 'test/test/test/test', imap2_folder_name( $mysync, 'INBOX.TEST.test.Test.tesT' ), 'imap2_folder_name: INBOX.TEST.test.Test.tesT' ) ;
  8014. # INBOX
  8015. $mysync = {} ;
  8016. $mysync->{ h1_prefix } = q{Pf1.} ;
  8017. $mysync->{ h2_prefix } = q{Pf2/} ;
  8018. $mysync->{ h1_sep } = '.';
  8019. $mysync->{ h2_sep } = '/';
  8020. #
  8021. #$mysync->{ debug } = 1 ;
  8022. is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'F1.F2.F3' ), 'imap2_folder_name: F1.F2.F3 -> Pf2/F1/F2/F3' ) ;
  8023. is( 'Pf2/F1/INBOX', imap2_folder_name( $mysync, 'F1.INBOX' ), 'imap2_folder_name: F1.INBOX -> Pf2/F1/INBOX' ) ;
  8024. is( 'INBOX', imap2_folder_name( $mysync, 'INBOX' ), 'imap2_folder_name: INBOX -> INBOX' ) ;
  8025. is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'Pf1.F1.F2.F3' ), 'imap2_folder_name: Pf1.F1.F2.F3 -> Pf2/F1/F2/F3' ) ;
  8026. is( 'Pf2/F1/INBOX', imap2_folder_name( $mysync, 'Pf1.F1.INBOX' ), 'imap2_folder_name: Pf1.F1.INBOX -> Pf2/F1/INBOX' ) ;
  8027. is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.INBOX' ), 'imap2_folder_name: Pf1.INBOX -> INBOX' ) ; # not Pf2/INBOX: Yes I can!
  8028. # subfolder2
  8029. $mysync = {} ;
  8030. $mysync->{ h1_prefix } = q{} ;
  8031. $mysync->{ h2_prefix } = q{} ;
  8032. $mysync->{ h1_sep } = '/';
  8033. $mysync->{ h2_sep } = '.';
  8034. set_regextrans2_for_subfolder2( $mysync ) ;
  8035. $mysync->{ subfolder2 } = 'S1.S2' ;
  8036. is( 'S1.S2.F1.F2.F3', imap2_folder_name( $mysync, 'F1/F2/F3' ), 'imap2_folder_name: F1/F2/F3 -> S1.S2.F1.F2.F3' ) ;
  8037. is( 'S1.S2.INBOX', imap2_folder_name( $mysync, 'INBOX' ), 'imap2_folder_name: F1/F2/F3 -> S1.S2.INBOX' ) ;
  8038. $mysync = {} ;
  8039. $mysync->{ h1_prefix } = q{Pf1/} ;
  8040. $mysync->{ h2_prefix } = q{Pf2.} ;
  8041. $mysync->{ h1_sep } = '/';
  8042. $mysync->{ h2_sep } = '.';
  8043. #$mysync->{ debug } = 1 ;
  8044. set_regextrans2_for_subfolder2( $mysync ) ;
  8045. $mysync->{ subfolder2 } = 'Pf2.S1.S2' ;
  8046. 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' ) ;
  8047. is( 'Pf2.S1.S2.INBOX', imap2_folder_name( $mysync, 'INBOX' ), 'imap2_folder_name: INBOX -> Pf2.S1.S2.INBOX' ) ;
  8048. 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' ) ;
  8049. is( 'Pf2.S1.S2.INBOX', imap2_folder_name( $mysync, 'Pf1/INBOX' ), 'imap2_folder_name: INBOX -> Pf2.S1.S2.INBOX' ) ;
  8050. # subfolder1
  8051. # scenario as the reverse of the previous tests, separators point of vue
  8052. $mysync = {} ;
  8053. $mysync->{ h1_prefix } = q{Pf1.} ;
  8054. $mysync->{ h2_prefix } = q{Pf2/} ;
  8055. $mysync->{ h1_sep } = '.';
  8056. $mysync->{ h2_sep } = '/';
  8057. #$mysync->{ debug } = 1 ;
  8058. $mysync->{ subfolder1 } = 'S1.S2' ;
  8059. 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' ) ;
  8060. 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' ) ;
  8061. is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.INBOX' ), 'imap2_folder_name: S1.S2.INBOX -> INBOX' ) ;
  8062. is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2' ), 'imap2_folder_name: S1.S2 -> INBOX' ) ;
  8063. is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.' ), 'imap2_folder_name: S1.S2. -> INBOX' ) ;
  8064. is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.INBOX' ), 'imap2_folder_name: Pf1.S1.S2.INBOX -> INBOX' ) ;
  8065. is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2' ), 'imap2_folder_name: Pf1.S1.S2 -> INBOX' ) ;
  8066. is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.' ), 'imap2_folder_name: Pf1.S1.S2. -> INBOX' ) ;
  8067. $mysync->{ subfolder1 } = 'S1.S2.' ;
  8068. 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' ) ;
  8069. 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' ) ;
  8070. is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.INBOX' ), 'imap2_folder_name: S1.S2.INBOX -> INBOX' ) ;
  8071. is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2' ), 'imap2_folder_name: S1.S2 -> INBOX' ) ;
  8072. is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.' ), 'imap2_folder_name: S1.S2. -> INBOX' ) ;
  8073. is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.INBOX' ), 'imap2_folder_name: Pf1.S1.S2.INBOX -> INBOX' ) ;
  8074. is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2' ), 'imap2_folder_name: Pf1.S1.S2 -> INBOX' ) ;
  8075. is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.' ), 'imap2_folder_name: Pf1.S1.S2. -> INBOX' ) ;
  8076. # subfolder1
  8077. # scenario as Gmail
  8078. $mysync = {} ;
  8079. $mysync->{ h1_prefix } = q{} ;
  8080. $mysync->{ h2_prefix } = q{} ;
  8081. $mysync->{ h1_sep } = '/';
  8082. $mysync->{ h2_sep } = '/';
  8083. #$mysync->{ debug } = 1 ;
  8084. $mysync->{ subfolder1 } = 'S1/S2' ;
  8085. is( 'F1/F2/F3', imap2_folder_name( $mysync, 'S1/S2/F1/F2/F3' ), 'imap2_folder_name: S1/S2/F1/F2/F3 -> F1/F2/F3' ) ;
  8086. is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/INBOX' ), 'imap2_folder_name: S1/S2/INBOX -> INBOX' ) ;
  8087. is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2' ), 'imap2_folder_name: S1/S2 -> INBOX' ) ;
  8088. is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/' ), 'imap2_folder_name: S1/S2/ -> INBOX' ) ;
  8089. $mysync->{ subfolder1 } = 'S1/S2/' ;
  8090. is( 'F1/F2/F3', imap2_folder_name( $mysync, 'S1/S2/F1/F2/F3' ), 'imap2_folder_name: S1/S2/F1/F2/F3 -> F1/F2/F3' ) ;
  8091. is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/INBOX' ), 'imap2_folder_name: S1/S2/INBOX -> INBOX' ) ;
  8092. is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2' ), 'imap2_folder_name: S1/S2 -> INBOX' ) ;
  8093. is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/' ), 'imap2_folder_name: S1/S2/ -> INBOX' ) ;
  8094. note( 'Leaving tests_imap2_folder_name()' ) ;
  8095. return ;
  8096. }
  8097. # Global variables to remove:
  8098. # None?
  8099. sub imap2_folder_name
  8100. {
  8101. my $mysync = shift ;
  8102. my ( $h1_fold ) = shift ;
  8103. my ( $h2_fold ) ;
  8104. if ( $mysync->{f1f2h}{ $h1_fold } ) {
  8105. $h2_fold = $mysync->{f1f2h}{ $h1_fold } ;
  8106. ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "f1f2 [$h1_fold] -> [$h2_fold]\n" ) ;
  8107. return( $h2_fold ) ;
  8108. }
  8109. if ( $mysync->{f1f2auto}{ $h1_fold } ) {
  8110. $h2_fold = $mysync->{f1f2auto}{ $h1_fold } ;
  8111. ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "automap [$h1_fold] -> [$h2_fold]\n" ) ;
  8112. return( $h2_fold ) ;
  8113. }
  8114. if ( $mysync->{ subfolder1 } )
  8115. {
  8116. my $esc_h1_sep = "\\" . $mysync->{ h1_sep } ;
  8117. # case where subfolder1 has the sep1 at the end, then remove it
  8118. my $part_to_removed = remove_last_char_if_is( $mysync->{ subfolder1 }, $mysync->{ h1_sep } ) ;
  8119. # remove the subfolder1 part and the sep1 if present after
  8120. $h1_fold =~ s{$part_to_removed($esc_h1_sep)?}{} ;
  8121. #myprint( "h1_fold=$h1_fold\n" ) ;
  8122. }
  8123. if ( ( q{} eq $h1_fold ) or ( $mysync->{ h1_prefix } eq $h1_fold ) )
  8124. {
  8125. $h1_fold = 'INBOX' ;
  8126. }
  8127. $h2_fold = prefix_seperator_invertion( $mysync, $h1_fold ) ;
  8128. $h2_fold = regextrans2( $mysync, $h2_fold ) ;
  8129. return( $h2_fold ) ;
  8130. }
  8131. sub tests_remove_last_char_if_is
  8132. {
  8133. note( 'Entering tests_remove_last_char_if_is()' ) ;
  8134. is( undef, remove_last_char_if_is( ), 'remove_last_char_if_is: no args => undef' ) ;
  8135. is( q{}, remove_last_char_if_is( q{} ), 'remove_last_char_if_is: empty => empty' ) ;
  8136. is( q{}, remove_last_char_if_is( q{}, 'Z' ), 'remove_last_char_if_is: empty Z => empty' ) ;
  8137. is( q{}, remove_last_char_if_is( 'Z', 'Z' ), 'remove_last_char_if_is: Z Z => empty' ) ;
  8138. is( 'abc', remove_last_char_if_is( 'abcZ', 'Z' ), 'remove_last_char_if_is: abcZ Z => abc' ) ;
  8139. is( 'abcY', remove_last_char_if_is( 'abcY', 'Z' ), 'remove_last_char_if_is: abcY Z => abcY' ) ;
  8140. note( 'Leaving tests_remove_last_char_if_is()' ) ;
  8141. return ;
  8142. }
  8143. sub remove_last_char_if_is
  8144. {
  8145. my $string = shift ;
  8146. my $char = shift ;
  8147. if ( ! defined $string )
  8148. {
  8149. return ;
  8150. }
  8151. if ( ! defined $char )
  8152. {
  8153. return $string ;
  8154. }
  8155. my $last_char = substr $string, -1 ;
  8156. if ( $char eq $last_char )
  8157. {
  8158. chop $string ;
  8159. return $string ;
  8160. }
  8161. else
  8162. {
  8163. return $string ;
  8164. }
  8165. }
  8166. sub tests_prefix_seperator_invertion
  8167. {
  8168. note( 'Entering tests_prefix_seperator_invertion()' ) ;
  8169. is( undef, prefix_seperator_invertion( ), 'prefix_seperator_invertion: no args => undef' ) ;
  8170. is( q{}, prefix_seperator_invertion( undef, q{} ), 'prefix_seperator_invertion: empty string => empty string' ) ;
  8171. is( 'lalala', prefix_seperator_invertion( undef, 'lalala' ), 'prefix_seperator_invertion: lalala => lalala' ) ;
  8172. is( 'lal/ala', prefix_seperator_invertion( undef, 'lal/ala' ), 'prefix_seperator_invertion: lal/ala => lal/ala' ) ;
  8173. is( 'lal.ala', prefix_seperator_invertion( undef, 'lal.ala' ), 'prefix_seperator_invertion: lal.ala => lal.ala' ) ;
  8174. is( '////', prefix_seperator_invertion( undef, '////' ), 'prefix_seperator_invertion: //// => ////' ) ;
  8175. is( '.....', prefix_seperator_invertion( undef, '.....' ), 'prefix_seperator_invertion: ..... => .....' ) ;
  8176. my $mysync = {
  8177. h1_prefix => q{},
  8178. h2_prefix => q{},
  8179. h1_sep => '/',
  8180. h2_sep => '/',
  8181. } ;
  8182. is( q{}, prefix_seperator_invertion( $mysync, q{} ), 'prefix_seperator_invertion: $mysync empty string => empty string' ) ;
  8183. is( 'lalala', prefix_seperator_invertion( $mysync, 'lalala' ), 'prefix_seperator_invertion: $mysync lalala => lalala' ) ;
  8184. is( 'lal/ala', prefix_seperator_invertion( $mysync, 'lal/ala' ), 'prefix_seperator_invertion: $mysync lal/ala => lal/ala' ) ;
  8185. is( 'lal.ala', prefix_seperator_invertion( $mysync, 'lal.ala' ), 'prefix_seperator_invertion: $mysync lal.ala => lal.ala' ) ;
  8186. is( '////', prefix_seperator_invertion( $mysync, '////' ), 'prefix_seperator_invertion: $mysync //// => ////' ) ;
  8187. is( '.....', prefix_seperator_invertion( $mysync, '.....' ), 'prefix_seperator_invertion: $mysync ..... => .....' ) ;
  8188. $mysync = {
  8189. h1_prefix => 'PPP',
  8190. h2_prefix => 'QQQ',
  8191. h1_sep => 's',
  8192. h2_sep => 't',
  8193. } ;
  8194. is( q{QQQ}, prefix_seperator_invertion( $mysync, q{} ), 'prefix_seperator_invertion: PPPQQQst empty string => QQQ' ) ;
  8195. is( 'QQQlalala', prefix_seperator_invertion( $mysync, 'lalala' ), 'prefix_seperator_invertion: PPPQQQst lalala => QQQlalala' ) ;
  8196. is( 'QQQlal/ala', prefix_seperator_invertion( $mysync, 'lal/ala' ), 'prefix_seperator_invertion: PPPQQQst lal/ala => QQQlal/ala' ) ;
  8197. is( 'QQQlal.ala', prefix_seperator_invertion( $mysync, 'lal.ala' ), 'prefix_seperator_invertion: PPPQQQst lal.ala => QQQlal.ala' ) ;
  8198. is( 'QQQ////', prefix_seperator_invertion( $mysync, '////' ), 'prefix_seperator_invertion: PPPQQQst //// => QQQ////' ) ;
  8199. is( 'QQQ.....', prefix_seperator_invertion( $mysync, '.....' ), 'prefix_seperator_invertion: PPPQQQst ..... => QQQ.....' ) ;
  8200. is( 'QQQPlalala', prefix_seperator_invertion( $mysync, 'PPPPlalala' ), 'prefix_seperator_invertion: PPPQQQst PPPPlalala => QQQPlalala' ) ;
  8201. is( 'QQQ', prefix_seperator_invertion( $mysync, 'PPP' ), 'prefix_seperator_invertion: PPPQQQst PPP => QQQ' ) ;
  8202. is( 'QQQttt', prefix_seperator_invertion( $mysync, 'sss' ), 'prefix_seperator_invertion: PPPQQQst sss => QQQttt' ) ;
  8203. is( 'QQQt', prefix_seperator_invertion( $mysync, 's' ), 'prefix_seperator_invertion: PPPQQQst s => QQQt' ) ;
  8204. is( 'QQQtAAAtBBB', prefix_seperator_invertion( $mysync, 'PPPsAAAsBBB' ), 'prefix_seperator_invertion: PPPQQQst PPPsAAAsBBB => QQQtAAAtBBB' ) ;
  8205. note( 'Leaving tests_prefix_seperator_invertion()' ) ;
  8206. return ;
  8207. }
  8208. # Global variables to remove:
  8209. sub prefix_seperator_invertion
  8210. {
  8211. my $mysync = shift ;
  8212. my $h1_fold = shift ;
  8213. my $h2_fold ;
  8214. if ( not defined $h1_fold ) { return ; }
  8215. my $my_h1_prefix = $mysync->{ h1_prefix } || q{} ;
  8216. my $my_h2_prefix = $mysync->{ h2_prefix } || q{} ;
  8217. my $my_h1_sep = $mysync->{ h1_sep } || '/' ;
  8218. my $my_h2_sep = $mysync->{ h2_sep } || '/' ;
  8219. # first we remove the prefix
  8220. $h1_fold =~ s/^\Q$my_h1_prefix\E//x ;
  8221. ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "removed host1 prefix: [$h1_fold]\n" ) ;
  8222. $h2_fold = separator_invert( $mysync, $h1_fold, $my_h1_sep, $my_h2_sep ) ;
  8223. ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "inverted separators: [$h2_fold]\n" ) ;
  8224. # Adding the prefix supplied by namespace or the --prefix2 option
  8225. # except for INBOX or Inbox
  8226. if ( $h2_fold !~ m/^INBOX$/xi )
  8227. {
  8228. $h2_fold = $my_h2_prefix . $h2_fold ;
  8229. }
  8230. ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "added host2 prefix: [$h2_fold]\n" ) ;
  8231. return( $h2_fold ) ;
  8232. }
  8233. sub tests_separator_invert
  8234. {
  8235. note( 'Entering tests_separator_invert()' ) ;
  8236. my $mysync = {} ;
  8237. $mysync->{ fixslash2 } = 0 ;
  8238. ok( not( defined separator_invert( ) ), 'separator_invert: no args' ) ;
  8239. ok( not( defined separator_invert( q{} ) ), 'separator_invert: not enough args' ) ;
  8240. ok( not( defined separator_invert( q{}, q{} ) ), 'separator_invert: not enough args' ) ;
  8241. ok( q{} eq separator_invert( $mysync, q{}, q{}, q{} ), 'separator_invert: 3 empty strings' ) ;
  8242. ok( 'lalala' eq separator_invert( $mysync, 'lalala', q{}, q{} ), 'separator_invert: empty separator' ) ;
  8243. ok( 'lalala' eq separator_invert( $mysync, 'lalala', '/', '/' ), 'separator_invert: same separator /' ) ;
  8244. ok( 'lal/ala' eq separator_invert( $mysync, 'lal/ala', '/', '/' ), 'separator_invert: same separator / 2' ) ;
  8245. ok( 'lal.ala' eq separator_invert( $mysync, 'lal/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
  8246. ok( 'lal/ala' eq separator_invert( $mysync, 'lal.ala', '.', '/' ), 'separator_invert: separators ./' ) ;
  8247. ok( 'la.l/ala' eq separator_invert( $mysync, 'la/l.ala', '.', '/' ), 'separator_invert: separators ./' ) ;
  8248. ok( 'l/al.ala' eq separator_invert( $mysync, 'l.al/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
  8249. $mysync->{ fixslash2 } = 1 ;
  8250. ok( 'l_al.ala' eq separator_invert( $mysync, 'l.al/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
  8251. note( 'Leaving tests_separator_invert()' ) ;
  8252. return ;
  8253. }
  8254. # Global variables to remove:
  8255. #
  8256. sub separator_invert
  8257. {
  8258. my( $mysync, $h1_fold, $h1_separator, $h2_separator ) = @_ ;
  8259. return( undef ) if ( not all_defined( $mysync, $h1_fold, $h1_separator, $h2_separator ) ) ;
  8260. # The separator we hope we'll never encounter: 00000000 == 0x00
  8261. my $o_sep = "\000" ;
  8262. my $h2_fold = $h1_fold ;
  8263. $h2_fold =~ s,\Q$h2_separator,$o_sep,xg ;
  8264. $h2_fold =~ s,\Q$h1_separator,$h2_separator,xg ;
  8265. $h2_fold =~ s,\Q$o_sep,$h1_separator,xg ;
  8266. $h2_fold =~ s,/,_,xg if( $mysync->{ fixslash2 } and '/' ne $h2_separator and '/' eq $h1_separator ) ;
  8267. return( $h2_fold ) ;
  8268. }
  8269. sub regextrans2
  8270. {
  8271. my( $mysync, $h2_fold ) = @_ ;
  8272. # Transforming the folder name by the --regextrans2 option(s)
  8273. foreach my $regextrans2 ( @{ $mysync->{ regextrans2 } } ) {
  8274. my $h2_fold_before = $h2_fold ;
  8275. my $ret = eval "\$h2_fold =~ $regextrans2 ; 1 " ;
  8276. ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "[$h2_fold_before] -> [$h2_fold] using regextrans2 [$regextrans2]\n" ) ;
  8277. if ( not ( defined $ret ) or $EVAL_ERROR ) {
  8278. $mysync->{nb_errors}++ ;
  8279. exit_clean( $mysync, $EX_USAGE,
  8280. "error: eval regextrans2 '$regextrans2': $EVAL_ERROR\n"
  8281. ) ;
  8282. }
  8283. }
  8284. return( $h2_fold ) ;
  8285. }
  8286. sub tests_decompose_regex
  8287. {
  8288. note( 'Entering tests_decompose_regex()' ) ;
  8289. ok( 1, 'decompose_regex 1' ) ;
  8290. ok( 0 == compare_lists( [ q{}, q{} ], [ decompose_regex( q{} ) ] ), 'decompose_regex empty string' ) ;
  8291. ok( 0 == compare_lists( [ '.*', 'lala' ], [ decompose_regex( 's/.*/lala/' ) ] ), 'decompose_regex s/.*/lala/' ) ;
  8292. note( 'Leaving tests_decompose_regex()' ) ;
  8293. return ;
  8294. }
  8295. sub decompose_regex
  8296. {
  8297. my $regex = shift ;
  8298. my( $left_part, $right_part ) ;
  8299. ( $left_part, $right_part ) = $regex =~ m{^s/((?:[^/]|\\/)+)/((?:[^/]|\\/)+)/}x;
  8300. return( q{}, q{} ) if not $left_part ;
  8301. return( $left_part, $right_part ) ;
  8302. }
  8303. sub tests_timenext
  8304. {
  8305. note( 'Entering tests_timenext()' ) ;
  8306. is( undef, timenext( ), 'timenext: no args => undef' ) ;
  8307. my $mysync ;
  8308. is( undef, timenext( $mysync ), 'timenext: undef => undef' ) ;
  8309. $mysync = {} ;
  8310. ok( time - timenext( $mysync ) <= 1e-02, 'timenext: defined first time => ~ time' ) ;
  8311. ok( timenext( $mysync ) <= 1e-02, 'timenext: second time => less than 1e-02' ) ;
  8312. ok( timenext( $mysync ) <= 1e-02, 'timenext: third time => less than 1e-02' ) ;
  8313. note( 'Leaving tests_timenext()' ) ;
  8314. return ;
  8315. }
  8316. sub timenext
  8317. {
  8318. my $mysync = shift ;
  8319. if ( ! defined $mysync )
  8320. {
  8321. return ;
  8322. }
  8323. my ( $timenow, $timediff ) ;
  8324. $mysync->{ timebefore } ||= 0; # epoch...
  8325. $timenow = time ;
  8326. $timediff = $timenow - $mysync->{ timebefore } ;
  8327. $mysync->{ timebefore } = $timenow ;
  8328. # myprint( "timenext: $timediff\n" ) ;
  8329. return( $timediff ) ;
  8330. }
  8331. sub tests_timesince
  8332. {
  8333. note( 'Entering tests_timesince()' ) ;
  8334. ok( timesince( time - 1 ) - 1 <= 1e-02, 'timesince: time - 1 => <= 1 + 1e-02' ) ;
  8335. ok( timesince( time ) <= 1e-02, 'timesince: time => <= 1e-02' ) ;
  8336. ok( timesince( ) - time <= 1e-02, 'timesince: no args => <= time + 1e-02' ) ;
  8337. note( 'Leaving tests_timesince()' ) ;
  8338. return ;
  8339. }
  8340. sub timesince
  8341. {
  8342. my $timeinit = shift || 0 ;
  8343. my ( $timenow, $timediff ) ;
  8344. $timenow = time ;
  8345. $timediff = $timenow - $timeinit ;
  8346. # Often used in a division so no 0 but a nano seconde.
  8347. return( max( $timediff, min( 1e-09, $timediff ) ) ) ;
  8348. }
  8349. sub tests_regexflags
  8350. {
  8351. note( 'Entering tests_regexflags()' ) ;
  8352. my $mysync = {} ;
  8353. ok( q{} eq regexflags( $mysync, q{} ), 'regexflags, null string q{}' ) ;
  8354. ok( q{\Seen NonJunk $Spam} eq regexflags( $mysync, q{\Seen NonJunk $Spam} ), q{regexflags, nothing to do} ) ;
  8355. @{ $mysync->{ regexflag } } = ('I am BAD' ) ;
  8356. ok( not ( defined regexflags( $mysync, q{} ) ), 'regexflags, bad regex' ) ;
  8357. @{ $mysync->{ regexflag } } = ( 's/NonJunk//g' ) ;
  8358. ok( q{\Seen $Spam} eq regexflags( $mysync, q{\Seen NonJunk $Spam} ), q{regexflags, remove NonJunk: 's/NonJunk//g'} ) ;
  8359. @{ $mysync->{ regexflag } } = ( q{s/\$Spam//g} ) ;
  8360. ok( q{\Seen NonJunk } eq regexflags( $mysync, q{\Seen NonJunk $Spam} ), q{regexflags, remove $Spam: 's/\$Spam//g'} ) ;
  8361. @{ $mysync->{ regexflag } } = ( 's/\\\\Seen//g' ) ;
  8362. ok( q{ NonJunk $Spam} eq regexflags( $mysync, q{\Seen NonJunk $Spam} ), q{regexflags, remove \Seen: 's/\\\\\\\\Seen//g'} ) ;
  8363. @{ $mysync->{ regexflag } } = ( 's/(\s|^)[^\\\\]\w+//g' ) ;
  8364. ok( q{\Seen \Middle \End} eq regexflags( $mysync, q{\Seen NonJunk \Middle $Spam \End} ), q{regexflags: only \word among \Seen NonJunk \Middle $Spam \End} ) ;
  8365. ok( q{ \Seen \Middle \End1} eq regexflags( $mysync, q{Begin \Seen NonJunk \Middle $Spam \End1 End} ),
  8366. q{regexflags: only \word among Begin \Seen NonJunk \Middle $Spam \End1 End} ) ;
  8367. @{ $mysync->{ regexflag } } = ( q{s/.*?(Keep1|Keep2|Keep3)/$1 /g} ) ;
  8368. ok( 'Keep1 Keep2 ReB' eq regexflags( $mysync, 'ReA Keep1 REM Keep2 ReB' ), 'Keep only regex' ) ;
  8369. ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM REM Keep1 Keep2' ), 'Keep only regex' ) ;
  8370. ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 REM REM Keep2' ), 'Keep only regex' ) ;
  8371. ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM Keep1 REM REM Keep2' ), 'Keep only regex' ) ;
  8372. ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 Keep2' ), 'Keep only regex' ) ;
  8373. ok( 'Keep1 ' eq regexflags( $mysync, 'REM Keep1' ), 'Keep only regex' ) ;
  8374. @{ $mysync->{ regexflag } } = ( q{s/(Keep1|Keep2|Keep3) (?!(Keep1|Keep2|Keep3)).*/$1 /g} ) ;
  8375. ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 Keep2 ReB' ), 'Keep only regex' ) ;
  8376. ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 Keep2 REM REM REM' ), 'Keep only regex' ) ;
  8377. ok( 'Keep2 ' eq regexflags( $mysync, 'Keep2 REM REM REM' ), 'Keep only regex' ) ;
  8378. @{ $mysync->{ regexflag } } = ( q{s/.*?(Keep1|Keep2|Keep3)/$1 /g},
  8379. 's/(Keep1|Keep2|Keep3) (?!(Keep1|Keep2|Keep3)).*/$1 /g' ) ;
  8380. ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM Keep1 REM Keep2 REM' ), 'Keep only regex' ) ;
  8381. ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 REM Keep2 REM' ), 'Keep only regex' ) ;
  8382. ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM Keep1 Keep2 REM' ), 'Keep only regex' ) ;
  8383. ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM Keep1 REM Keep2' ), 'Keep only regex' ) ;
  8384. ok( 'Keep1 Keep2 Keep3 ' eq regexflags( $mysync, 'REM Keep1 REM Keep2 REM REM Keep3 REM' ), 'Keep only regex' ) ;
  8385. ok( 'Keep1 ' eq regexflags( $mysync, 'REM REM Keep1 REM REM REM ' ), 'Keep only regex' ) ;
  8386. ok( 'Keep1 Keep3 ' eq regexflags( $mysync, 'RE1 Keep1 RE2 Keep3 RE3 RE4 RE5 ' ), 'Keep only regex' ) ;
  8387. @{ $mysync->{ regexflag } } = ( 's/(.*)/$1 jrdH8u/' ) ;
  8388. ok('REM REM REM REM REM jrdH8u' eq regexflags( $mysync, 'REM REM REM REM REM' ), q{Add jrdH8u 's/(.*)/\$1 jrdH8u/'} ) ;
  8389. @{ $mysync->{ regexflag } } = ('s/jrdH8u *//' );
  8390. ok('REM REM REM REM REM ' eq regexflags( $mysync, 'REM REM REM REM REM jrdH8u' ), q{Remove jrdH8u s/jrdH8u *//} ) ;
  8391. @{ $mysync->{ regexflag } } = (
  8392. 's/.*?(?:(\\\\(?:Answered|Flagged|Deleted|Seen|Draft)\s?)|$)/defined($1)?$1:q()/eg'
  8393. );
  8394. ok( '\\Deleted \\Answered '
  8395. eq regexflags( $mysync, 'Blabla \$Junk \\Deleted machin \\Answered truc' ),
  8396. 'Keep only regex: Exchange case (Phil)' ) ;
  8397. ok( q{} eq regexflags( $mysync, q{} ), 'Keep only regex: Exchange case, null string (Phil)' ) ;
  8398. ok( q{}
  8399. eq regexflags( $mysync, 'Blabla $Junk machin truc' ),
  8400. 'Keep only regex: Exchange case, no accepted flags (Phil)' ) ;
  8401. ok('\\Deleted \\Answered \\Draft \\Flagged '
  8402. eq regexflags( $mysync, '\\Deleted \\Answered \\Draft \\Flagged ' ),
  8403. 'Keep only regex: Exchange case (Phil)' ) ;
  8404. @{ $mysync->{ regexflag } } = ( 's/\\\\Flagged//g' ) ;
  8405. is('\Deleted \Answered \Draft ',
  8406. regexflags( $mysync, '\\Deleted \\Answered \\Draft \\Flagged ' ),
  8407. 'regexflags: remove \Flagged 1' ) ;
  8408. is('\\Deleted \\Answered \\Draft',
  8409. regexflags( $mysync, '\\Deleted \\Flagged \\Answered \\Draft' ),
  8410. 'regexflags: remove \Flagged 2' ) ;
  8411. # I didn't understand why it gives \F
  8412. # https://perldoc.perl.org/perlrebackslash.html
  8413. # \F Foldcase till \E. Not in [].
  8414. # https://perldoc.perl.org/functions/fc.html
  8415. # \F Not available in old Perl so I comment the test
  8416. # @{ $mysync->{ regexflag } } = ( 's/\\Flagged/X/g' ) ;
  8417. #is('\Deleted FX \Answered \FX \Draft \FX',
  8418. #regexflags( '\Deleted Flagged \Answered \Flagged \Draft \Flagged' ),
  8419. # 'regexflags: remove \Flagged 3 mistery...' ) ;
  8420. $mysync->{ regexflag } = [ ] ;
  8421. $mysync->{ filterbuggyflags } = 1 ;
  8422. filterbuggyflags( $mysync ) ;
  8423. is( '\Deleted \Answered \Draft \Flagged',
  8424. regexflags( $mysync, '\\Deleted \\Answered \\RECEIPTCHECKED \\Draft \\Indexed \\Flagged' ),
  8425. 'regexflags: remove famous /X 1' ) ;
  8426. is( '\\Deleted \\Flagged \\Answered \\Draft',
  8427. regexflags( $mysync, '\\Deleted \\RECEIPTCHECKED \\Flagged \\Answered \\Indexed \\Draft' ),
  8428. 'regexflags: remove famous /X 2' ) ;
  8429. is( '\ ', '\\ ', 'regexflags: \ is \\ ' ) ;
  8430. is( '\\ ', '\\ ', 'regexflags: \\ is \\ ' ) ;
  8431. is( '\\ \ ', '\ \\ ', 'regexflags: \\ \ is \ \\ ' ) ;
  8432. note( 'Leaving tests_regexflags()' ) ;
  8433. return ;
  8434. }
  8435. sub regexflags
  8436. {
  8437. my $mysync = shift ;
  8438. my $flags = shift ;
  8439. foreach my $regexflag ( @{ $mysync->{ regexflag } } )
  8440. {
  8441. my $flags_orig = $flags ;
  8442. $debugflags and myprint( "eval \$flags =~ $regexflag\n" ) ;
  8443. my $ret = eval "\$flags =~ $regexflag ; 1 " ;
  8444. $debugflags and myprint( "regexflag $regexflag [$flags_orig] -> [$flags]\n" ) ;
  8445. if( not ( defined $ret ) or $EVAL_ERROR ) {
  8446. myprint( "Error: eval regexflag '$regexflag': $EVAL_ERROR\n" ) ;
  8447. return( undef ) ;
  8448. }
  8449. }
  8450. return( $flags ) ;
  8451. }
  8452. sub filterbuggyflags
  8453. {
  8454. my $mysync = shift ;
  8455. if ( $mysync->{ filterbuggyflags } )
  8456. {
  8457. unshift @{ $mysync->{ regexflag } }, buggyflagsregex( ) ;
  8458. }
  8459. return ;
  8460. }
  8461. sub tests_remove_doublequotes_if_any
  8462. {
  8463. note( 'Entering tests_remove_doublequotes_if_any()' ) ;
  8464. # the number of tests is stupid here
  8465. is( undef, remove_doublequotes_if_any( ), 'remove_doublequotes_if_any: no args => undef' ) ;
  8466. is( q{}, remove_doublequotes_if_any( q{} ), 'remove_doublequotes_if_any: empty string => empty string' ) ;
  8467. is( q{}, remove_doublequotes_if_any( q{""} ), 'remove_doublequotes_if_any: double-quotes => empty string' ) ;
  8468. is( q{}, remove_doublequotes_if_any( q{"""} ), 'remove_doublequotes_if_any: double-quotes => empty string' ) ;
  8469. is( q{}, remove_doublequotes_if_any( q{"""} ), 'remove_doublequotes_if_any: double-quotes => empty string' ) ;
  8470. is( q{toto}, remove_doublequotes_if_any( q{"toto"} ), 'remove_doublequotes_if_any: "toto" => toto' ) ;
  8471. is( q{toto}, remove_doublequotes_if_any( q{toto} ), 'remove_doublequotes_if_any: toto => toto' ) ;
  8472. is( q{toto}, remove_doublequotes_if_any( q{to"to} ), 'remove_doublequotes_if_any: to"to => toto' ) ;
  8473. is( q{toto}, remove_doublequotes_if_any( q{toto"} ), 'remove_doublequotes_if_any: toto" => toto' ) ;
  8474. is( q{toto}, remove_doublequotes_if_any( q{"toto} ), 'remove_doublequotes_if_any: "toto => toto' ) ;
  8475. is( q{toto}, remove_doublequotes_if_any( q{"to"to} ), 'remove_doublequotes_if_any: "to"to => toto' ) ;
  8476. is( q{toto}, remove_doublequotes_if_any( q{to"to"} ), 'remove_doublequotes_if_any: to"to" => toto' ) ;
  8477. is( q{toto}, remove_doublequotes_if_any( q{to\"to} ), 'remove_doublequotes_if_any: to\"to => toto' ) ;
  8478. is( q{toto}, remove_doublequotes_if_any( q{toto\"} ), 'remove_doublequotes_if_any: toto\" => toto' ) ;
  8479. is( q{toto}, remove_doublequotes_if_any( q{\"toto} ), 'remove_doublequotes_if_any: \"toto => toto' ) ;
  8480. is( q{toto}, remove_doublequotes_if_any( q{\"to\"to} ), 'remove_doublequotes_if_any: \"to\"to => toto' ) ;
  8481. is( q{toto}, remove_doublequotes_if_any( q{to\"to\"} ), 'remove_doublequotes_if_any: to\"to" => toto' ) ;
  8482. note( 'Leaving tests_remove_doublequotes_if_any()' ) ;
  8483. return ;
  8484. }
  8485. sub remove_doublequotes_if_any
  8486. {
  8487. my $string = shift ;
  8488. if ( ! defined $string ) { return ; }
  8489. $string =~ s/\\\"//g ;
  8490. $string =~ tr/"//d ;
  8491. return $string ;
  8492. }
  8493. # No globals here
  8494. sub acls_sync
  8495. {
  8496. # https://tools.ietf.org/html/rfc4314
  8497. # Standard Rights:
  8498. # https://tools.ietf.org/html/rfc4314#section-2.1
  8499. my( $mysync, $h1_fold, $h2_fold ) = @_ ;
  8500. if ( $mysync->{ syncacls } ) {
  8501. my $h1_hash = $mysync->{imap1}->getacl($h1_fold)
  8502. or myprint( "Host1: Could not getacl for $h1_fold: $EVAL_ERROR\n" ) ;
  8503. my $h2_hash = $mysync->{imap2}->getacl($h2_fold)
  8504. or myprint( "Host2: Could not getacl for $h2_fold: $EVAL_ERROR\n" ) ;
  8505. my %users = map { ($_, 1) } ( keys %{ $h1_hash} , keys %{ $h2_hash } ) ;
  8506. foreach my $user (sort keys %users ) {
  8507. my $h1_acl = remove_doublequotes_if_any( $h1_hash->{$user} ) || '' ;
  8508. my $h2_acl = remove_doublequotes_if_any( $h2_hash->{$user} ) || '' ;
  8509. myprint( "Host1: user $user has acl [$h1_acl] on host1\n" ) ;
  8510. myprint( "Host2: user $user has acl [$h2_acl] on host2\n" ) ;
  8511. # removes surrounding double-quotes if any
  8512. my $user_no_quotes = remove_doublequotes_if_any( $user ) ;
  8513. if ( $h1_hash->{$user}
  8514. && $h2_hash->{$user}
  8515. && $h1_hash->{$user} eq $h2_hash->{$user} )
  8516. {
  8517. myprint( "Host2: user $user_no_quotes has already the same acl, no need to set it.\n" ) ;
  8518. next ;
  8519. }
  8520. myprint( "Host2: setting acl for folder $h2_fold user $user_no_quotes acl $h1_acl $mysync->{dry_message}\n" ) ;
  8521. unless ( $mysync->{dry} ) {
  8522. $mysync->{imap2}->setacl( $h2_fold, $user_no_quotes, $h1_acl )
  8523. or myprint( "Could not set acl for user $user_no_quotes on host2: $EVAL_ERROR\n" ) ;
  8524. }
  8525. }
  8526. }
  8527. return ;
  8528. }
  8529. sub tests_permanentflags
  8530. {
  8531. note( 'Entering tests_permanentflags()' ) ;
  8532. my $mysync = { } ;
  8533. ok( q{} eq permanentflags( $mysync, ' * OK [PERMANENTFLAGS (\* \Draft \Answered)] Limited' ),
  8534. 'permanentflags \*' ) ;
  8535. ok( '\Draft \Answered' eq permanentflags( $mysync, ' * OK [PERMANENTFLAGS (\Draft \Answered)] Limited' ),
  8536. 'permanentflags \Draft \Answered' ) ;
  8537. ok( '\Draft \Answered'
  8538. eq permanentflags( $mysync, 'Blabla',
  8539. ' * OK [PERMANENTFLAGS (\Draft \Answered)] Limited',
  8540. 'Blabla' ),
  8541. 'permanentflags \Draft \Answered'
  8542. ) ;
  8543. ok( q{} eq permanentflags( $mysync, 'Blabla' ), 'permanentflags nothing' ) ;
  8544. note( 'Leaving tests_permanentflags()' ) ;
  8545. return ;
  8546. }
  8547. sub permanentflags
  8548. {
  8549. my $mysync = shift ;
  8550. my @lines = @_ ;
  8551. foreach my $line (@lines) {
  8552. if ( $line =~ m{\[PERMANENTFLAGS\s\(([^)]+?)\)\]}x ) {
  8553. ( $debugflags or $sync->{ debug } ) and myprint( "permanentflags: $line" ) ;
  8554. my $permanentflags = $1 ;
  8555. if ( $permanentflags =~ m{\\\*}x )
  8556. {
  8557. $permanentflags = q{} ;
  8558. }
  8559. return( $permanentflags ) ;
  8560. } ;
  8561. }
  8562. return( q{} ) ;
  8563. }
  8564. sub tests_flags_filter
  8565. {
  8566. note( 'Entering tests_flags_filter()' ) ;
  8567. ok( '\Seen' eq flags_filter('\Seen', '\Draft \Seen \Answered'), 'flags_filter ' );
  8568. ok( q{} eq flags_filter('\Seen', '\Draft \Answered'), 'flags_filter ' );
  8569. ok( '\Seen' eq flags_filter('\Seen', '\Seen'), 'flags_filter ' );
  8570. ok( '\Seen' eq flags_filter('\Seen', ' \Seen '), 'flags_filter ' );
  8571. ok( '\Seen \Draft'
  8572. eq flags_filter('\Seen \Draft', '\Draft \Seen \Answered'), 'flags_filter ' );
  8573. ok( '\Seen \Draft'
  8574. eq flags_filter('\Seen \Draft', ' \Draft \Seen \Answered '), 'flags_filter ' );
  8575. note( 'Leaving tests_flags_filter()' ) ;
  8576. return ;
  8577. }
  8578. sub flags_filter
  8579. {
  8580. my( $flags, $allowed_flags ) = @_ ;
  8581. my @flags = split /\s+/x, $flags ;
  8582. my %allowed_flags = map { $_ => 1 } split q{ }, $allowed_flags ;
  8583. my @flags_out = map { exists $allowed_flags{$_} ? $_ : () } @flags ;
  8584. my $flags_out = join q{ }, @flags_out ;
  8585. return( $flags_out ) ;
  8586. }
  8587. sub tests_flagscase
  8588. {
  8589. note( 'Entering tests_flagscase()' ) ;
  8590. ok( '\Seen' eq flagscase( '\Seen' ), 'flagscase: \Seen -> \Seen' ) ;
  8591. ok( '\Seen' eq flagscase( '\SEEN' ), 'flagscase: \SEEN -> \Seen' ) ;
  8592. ok( '\Seen \Draft' eq flagscase( '\SEEN \DRAFT' ), 'flagscase: \SEEN \DRAFT -> \Seen \Draft' ) ;
  8593. ok( '\Draft \Seen' eq flagscase( '\DRAFT \SEEN' ), 'flagscase: \DRAFT \SEEN -> \Draft \Seen' ) ;
  8594. ok( '\Draft LALA \Seen' eq flagscase( '\DRAFT LALA \SEEN' ), 'flagscase: \DRAFT LALA \SEEN -> \Draft LALA \Seen' ) ;
  8595. ok( '\Draft lala \Seen' eq flagscase( '\DRAFT lala \SEEN' ), 'flagscase: \DRAFT lala \SEEN -> \Draft lala \Seen' ) ;
  8596. note( 'Leaving tests_flagscase()' ) ;
  8597. return ;
  8598. }
  8599. sub flagscase
  8600. {
  8601. my $flags = shift ;
  8602. my @flags = split /\s+/x, $flags ;
  8603. my %rfc_flags = map { $_ => 1 } split q{ }, '\Answered \Flagged \Deleted \Seen \Draft' ;
  8604. my @flags_out = map { exists $rfc_flags{ ucsecond( lc $_ ) } ? ucsecond( lc $_ ) : $_ } @flags ;
  8605. my $flags_out = join q{ }, @flags_out ;
  8606. return( $flags_out ) ;
  8607. }
  8608. sub tests_flags_for_host2
  8609. {
  8610. note( 'Entering tests_flags_for_host2()' ) ;
  8611. is( undef, flags_for_host2( ), 'flags_for_host2: no args => undef' ) ;
  8612. my $mysync ;
  8613. is( undef, flags_for_host2( $mysync ), 'flags_for_host2: undef => undef' ) ;
  8614. $mysync = { } ;
  8615. is( undef, flags_for_host2( $mysync ), 'flags_for_host2: nothing => undef' ) ;
  8616. is( q{}, flags_for_host2( $mysync, '' ), 'flags_for_host2: no flags => empty string' ) ;
  8617. is( q{}, flags_for_host2( $mysync, '\Recent' ), 'flags_for_host2: \Recent => empty string' ) ;
  8618. is( q{\Seen}, flags_for_host2( $mysync, '\Recent \Seen' ), 'flags_for_host2: \Recent \Seen => \Seen' ) ;
  8619. is( q{\Deleted \Seen}, flags_for_host2( $mysync, '\Deleted \Recent \Seen' ), 'flags_for_host2: \Deleted \Recent \Seen => \Deleted \Seen' ) ;
  8620. $mysync->{ flagscase } = 0 ;
  8621. is( q{\DELETED \Seen}, flags_for_host2( $mysync, '\DELETED \Seen' ), 'flags_for_host2: flagscase = 0 \DELETED \Seen => \DELETED \Seen' ) ;
  8622. $mysync->{ flagscase } = 1 ;
  8623. is( q{\Deleted \Seen}, flags_for_host2( $mysync, '\DELETED \Seen' ), 'flags_for_host2: flagscase = 1 \DELETED \Seen => \Deleted \Seen' ) ;
  8624. $mysync->{ filterflags } = 0 ;
  8625. is( q{\Seen \Blabla}, flags_for_host2( $mysync, '\Seen \Blabla', '\Seen \Junk' ), 'flags_for_host2: filterflags = 0 \Seen \Blabla among \Seen \Junk => \Seen \Blabla' ) ;
  8626. $mysync->{ filterflags } = 1 ;
  8627. is( q{\Seen}, flags_for_host2( $mysync, '\Seen \Blabla', '\Seen \Junk' ), 'flags_for_host2: filterflags = 1 \Seen \Blabla among \Seen \Junk => \Seen' ) ;
  8628. $mysync->{ filterflags } = 1 ;
  8629. is( q{\Seen \Blabla}, flags_for_host2( $mysync, '\Seen \Blabla', '' ), 'flags_for_host2: filterflags = 1 \Seen \Blabla among "" => \Seen \Blabla' ) ;
  8630. note( 'Leaving tests_flags_for_host2()' ) ;
  8631. return ;
  8632. }
  8633. sub flags_for_host2
  8634. {
  8635. my $mysync = shift ;
  8636. my $h1_flags = shift ;
  8637. my $permanentflags2 = shift ;
  8638. if ( ! all_defined( $mysync, $h1_flags ) ) { return ; } ;
  8639. # RFC 2060: This flag can not be altered by any client
  8640. $h1_flags =~ s@\\Recent\s?@@xgi ;
  8641. my $h1_flags_re ;
  8642. if ( $mysync->{ regexflag } and defined( $h1_flags_re = regexflags( $mysync, $h1_flags ) ) ) {
  8643. $h1_flags = $h1_flags_re ;
  8644. }
  8645. if ( $mysync->{ flagscase } )
  8646. {
  8647. $h1_flags = flagscase( $h1_flags ) ;
  8648. }
  8649. if ( $permanentflags2 and $mysync->{ filterflags } )
  8650. {
  8651. $h1_flags = flags_filter( $h1_flags, $permanentflags2 ) ;
  8652. }
  8653. return( $h1_flags ) ;
  8654. }
  8655. sub ucsecond
  8656. {
  8657. my $string = shift ;
  8658. my $output ;
  8659. return( $string ) if ( 1 >= length $string ) ;
  8660. $output = ( substr( $string, 0, 1) ) . ( uc substr $string, 1, 1 ) . ( substr $string, 2 ) ;
  8661. #myprint( "UUU $string -> $output\n" ) ;
  8662. return( $output ) ;
  8663. }
  8664. sub tests_ucsecond
  8665. {
  8666. note( 'Entering tests_ucsecond()' ) ;
  8667. ok( 'aBcde' eq ucsecond( 'abcde' ), 'ucsecond: abcde -> aBcde' ) ;
  8668. ok( 'ABCDE' eq ucsecond( 'ABCDE' ), 'ucsecond: ABCDE -> ABCDE' ) ;
  8669. ok( 'ABCDE' eq ucsecond( 'AbCDE' ), 'ucsecond: AbCDE -> ABCDE' ) ;
  8670. ok( 'ABCde' eq ucsecond( 'AbCde' ), 'ucsecond: AbCde -> ABCde' ) ;
  8671. ok( 'A' eq ucsecond( 'A' ), 'ucsecond: A -> A' ) ;
  8672. ok( 'AB' eq ucsecond( 'Ab' ), 'ucsecond: Ab -> AB' ) ;
  8673. ok( '\B' eq ucsecond( '\b' ), 'ucsecond: \b -> \B' ) ;
  8674. ok( '\Bcde' eq ucsecond( '\bcde' ), 'ucsecond: \bcde -> \Bcde' ) ;
  8675. note( 'Leaving tests_ucsecond()' ) ;
  8676. return ;
  8677. }
  8678. sub select_msgs
  8679. {
  8680. my ( $imap, $msgs_all_hash_ref, $search_cmd, $abletosearch, $folder ) = @_ ;
  8681. my ( @msgs ) ;
  8682. if ( $abletosearch ) {
  8683. @msgs = select_msgs_by_search( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) ;
  8684. }else{
  8685. @msgs = select_msgs_by_fetch( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) ;
  8686. }
  8687. return( @msgs ) ;
  8688. }
  8689. sub select_msgs_by_search
  8690. {
  8691. my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ;
  8692. my ( @msgs, @msgs_all ) ;
  8693. # Need to have the whole list in msgs_all_hash_ref
  8694. # without calling messages() several times.
  8695. # Need all messages list to avoid deleting useful cache part
  8696. # in case of --search or --minage or --maxage
  8697. if ( ( defined $msgs_all_hash_ref and $usecache )
  8698. or ( not defined $maxage and not defined $minage and not defined $search_cmd )
  8699. ) {
  8700. $debugdev and myprint( "Calling messages()\n" ) ;
  8701. @msgs_all = $imap->messages( ) ;
  8702. return if ( $#msgs_all == 0 && !defined $msgs_all[0] ) ;
  8703. if ( defined $msgs_all_hash_ref ) {
  8704. @{ $msgs_all_hash_ref }{ @msgs_all } = () ;
  8705. }
  8706. # return all messages
  8707. if ( not defined $maxage and not defined $minage and not defined $search_cmd ) {
  8708. return( @msgs_all ) ;
  8709. }
  8710. }
  8711. if ( defined $search_cmd ) {
  8712. @msgs = $imap->search( $search_cmd ) ;
  8713. return( @msgs ) ;
  8714. }
  8715. # we are here only if $maxage or $minage is defined
  8716. @msgs = select_msgs_by_age( $imap ) ;
  8717. return( @msgs );
  8718. }
  8719. sub select_msgs_by_fetch
  8720. {
  8721. my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ;
  8722. my ( @msgs, @msgs_all, %fetch ) ;
  8723. # Need to have the whole list in msgs_all_hash_ref
  8724. # without calling messages() several times.
  8725. # Need all messages list to avoid deleting useful cache part
  8726. # in case of --search or --minage or --maxage
  8727. $debugdev and myprint( "Calling fetch_hash()\n" ) ;
  8728. my $fetch_hash_uids = $fetch_hash_set || "1:*" ;
  8729. %fetch = %{$imap->fetch_hash( $fetch_hash_uids, 'INTERNALDATE' ) } ;
  8730. @msgs_all = sort { $a <=> $b } keys %fetch ;
  8731. $debugdev and myprint( "Done fetch_hash()\n" ) ;
  8732. return if ( $#msgs_all == 0 && !defined $msgs_all[0] ) ;
  8733. if ( defined $msgs_all_hash_ref ) {
  8734. @{ $msgs_all_hash_ref }{ @msgs_all } = () ;
  8735. }
  8736. # return all messages
  8737. if ( not defined $maxage and not defined $minage and not defined $search_cmd ) {
  8738. return( @msgs_all ) ;
  8739. }
  8740. if ( defined $search_cmd ) {
  8741. myprint( "Warning: strange to see --search with --noabletosearch, an error can happen\n" ) ;
  8742. @msgs = $imap->search( $search_cmd ) ;
  8743. return( @msgs ) ;
  8744. }
  8745. # we are here only if $maxage or $minage is defined
  8746. my( @max, @min, $maxage_epoch, $minage_epoch ) ;
  8747. if ( defined $maxage ) { $maxage_epoch = $timestart_int - $NB_SECONDS_IN_A_DAY * $maxage ; }
  8748. if ( defined $minage ) { $minage_epoch = $timestart_int - $NB_SECONDS_IN_A_DAY * $minage ; }
  8749. foreach my $msg ( @msgs_all ) {
  8750. my $idate = $fetch{ $msg }->{'INTERNALDATE'} ;
  8751. #myprint( "$idate\n" ) ;
  8752. if ( defined $maxage and ( epoch( $idate ) >= $maxage_epoch ) ) {
  8753. push @max, $msg ;
  8754. }
  8755. if ( defined $minage and ( epoch( $idate ) <= $minage_epoch ) ) {
  8756. push @min, $msg ;
  8757. }
  8758. }
  8759. @msgs = msgs_from_maxmin( \@max, \@min ) ;
  8760. return( @msgs ) ;
  8761. }
  8762. sub select_msgs_by_age
  8763. {
  8764. my( $imap ) = @_ ;
  8765. my( @max, @min, @msgs, @inter, @union ) ;
  8766. if ( defined $maxage ) {
  8767. @max = $imap->sentsince( $timestart_int - $NB_SECONDS_IN_A_DAY * $maxage ) ;
  8768. }
  8769. if ( defined $minage ) {
  8770. @min = $imap->sentbefore( $timestart_int - $NB_SECONDS_IN_A_DAY * $minage ) ;
  8771. }
  8772. @msgs = msgs_from_maxmin( \@max, \@min ) ;
  8773. return( @msgs ) ;
  8774. }
  8775. sub msgs_from_maxmin
  8776. {
  8777. my( $max_ref, $min_ref ) = @_ ;
  8778. my( @max, @min, @msgs, @inter, @union ) ;
  8779. @max = @{ $max_ref } ;
  8780. @min = @{ $min_ref } ;
  8781. SWITCH: {
  8782. if ( not ( defined $minage or defined $maxage ) )
  8783. {
  8784. return ;
  8785. }
  8786. unless( defined $minage ) { @msgs = @max ; last SWITCH } ;
  8787. unless( defined $maxage ) { @msgs = @min ; last SWITCH } ;
  8788. my ( %union, %inter ) ;
  8789. foreach my $m ( @min, @max ) { $union{ $m }++ && $inter{ $m }++ }
  8790. @inter = sort { $a <=> $b } keys %inter ;
  8791. @union = sort { $a <=> $b } keys %union ;
  8792. # normal case
  8793. if ( $minage <= $maxage ) { @msgs = @inter ; last SWITCH } ;
  8794. # just exclude messages between
  8795. if ( $minage > $maxage ) { @msgs = @union ; last SWITCH } ;
  8796. }
  8797. return( @msgs ) ;
  8798. }
  8799. sub tests_msgs_from_maxmin
  8800. {
  8801. note( 'Entering tests_msgs_from_maxmin()' ) ;
  8802. my @msgs ;
  8803. # no maxage nor minage
  8804. @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
  8805. is_deeply( [ ], \@msgs , 'msgs_from_maxmin: no maxage nor minage => empty result' ) ;
  8806. # maxage alone
  8807. $maxage = $NUMBER_200 ;
  8808. @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
  8809. is_deeply( [ '1', '2' ], \@msgs , 'msgs_from_maxmin: maxage++' ) ;
  8810. # maxage > minage -> intersection
  8811. $minage = $NUMBER_100 ;
  8812. @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
  8813. is_deeply( [ '2' ], \@msgs , 'msgs_from_maxmin: -maxage++minage-' ) ;
  8814. # maxage < minage -> union
  8815. $minage = $NUMBER_300 ;
  8816. @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
  8817. is_deeply( [ '1', '2', '3' ], \@msgs, 'msgs_from_maxmin: ++maxage-minage++' ) ;
  8818. # minage alone
  8819. $maxage = undef ;
  8820. @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
  8821. is_deeply( [ '2', '3' ], \@msgs, 'msgs_from_maxmin: ++minage-' ) ;
  8822. note( 'Leaving tests_msgs_from_maxmin()' ) ;
  8823. return ;
  8824. }
  8825. sub tests_info_date_from_uid
  8826. {
  8827. note( 'Entering tests_info_date_from_uid()' ) ;
  8828. note( 'Leaving tests_info_date_from_uid()' ) ;
  8829. return ;
  8830. }
  8831. sub info_date_from_uid
  8832. {
  8833. #my $first_uid = $msgs_all[ 0 ] ;
  8834. #my $first_idate = $fetch{ $first_uid }->{'INTERNALDATE'} ;
  8835. #my $first_epoch = epoch( $first_idate ) ;
  8836. #my $first_days = ( $timestart_int - $first_epoch ) / $NB_SECONDS_IN_A_DAY ;
  8837. #myprint( "\nOldest msg has UID $first_uid INTERNALDATE $first_idate EPOCH $first_epoch DAYS AGO $first_days\n" ) ;
  8838. }
  8839. sub lastuid
  8840. {
  8841. my $imap = shift ;
  8842. my $folder = shift ;
  8843. my $lastuid_guess = shift ;
  8844. my $lastuid ;
  8845. # rfc3501: The only reliable way to identify recent messages is to
  8846. # look at message flags to see which have the \Recent flag
  8847. # set, or to do a SEARCH RECENT.
  8848. # SEARCH RECENT doesn't work this way on courrier.
  8849. my @recent_messages ;
  8850. # SEARCH RECENT for each transfer can be expensive with a big folder
  8851. # Call commented for now
  8852. #@recent_messages = $imap->recent( ) ;
  8853. #myprint( "Recent: @recent_messages\n" ) ;
  8854. my $max_recent ;
  8855. $max_recent = max( @recent_messages ) ;
  8856. if ( defined $max_recent and ($lastuid_guess <= $max_recent ) ) {
  8857. $lastuid = $max_recent ;
  8858. }else{
  8859. $lastuid = $lastuid_guess
  8860. }
  8861. return( $lastuid ) ;
  8862. }
  8863. sub size_filtered
  8864. {
  8865. my( $h1_size, $h1_msg, $h1_fold, $h2_fold ) = @_ ;
  8866. $h1_size = 0 if ( ! $h1_size ) ; # null if empty or undef
  8867. if ( defined $sync->{ maxsize } and $h1_size > $sync->{ maxsize } ) {
  8868. myprint( "msg $h1_fold/$h1_msg skipped ($h1_size exceeds maxsize limit $sync->{ maxsize } bytes)\n" ) ;
  8869. $sync->{ total_bytes_skipped } += $h1_size;
  8870. $sync->{ nb_msg_skipped } += 1;
  8871. return( 1 ) ;
  8872. }
  8873. if ( defined $minsize and $h1_size <= $minsize ) {
  8874. myprint( "msg $h1_fold/$h1_msg skipped ($h1_size smaller than minsize $minsize bytes)\n" ) ;
  8875. $sync->{ total_bytes_skipped } += $h1_size;
  8876. $sync->{ nb_msg_skipped } += 1;
  8877. return( 1 ) ;
  8878. }
  8879. return( 0 ) ;
  8880. }
  8881. sub message_exists
  8882. {
  8883. my( $imap, $msg ) = @_ ;
  8884. return( 1 ) if not $imap->Uid( ) ;
  8885. my $search_uid ;
  8886. ( $search_uid ) = $imap->search( "UID $msg" ) ;
  8887. #myprint( "$search ? $msg\n" ) ;
  8888. return( 1 ) if ( $search_uid eq $msg ) ;
  8889. return( 0 ) ;
  8890. }
  8891. # Globals
  8892. # $sync->{ total_bytes_skipped }
  8893. # $sync->{ nb_msg_skipped }
  8894. # $mysync->{ h1_nb_msg_processed }
  8895. sub stats_update_skip_message
  8896. {
  8897. my $mysync = shift ; # to be used
  8898. my $h1_size = shift ;
  8899. $mysync->{ total_bytes_skipped } += $h1_size ;
  8900. $mysync->{ nb_msg_skipped } += 1 ;
  8901. $mysync->{ h1_nb_msg_processed } +=1 ;
  8902. return ;
  8903. }
  8904. sub copy_message
  8905. {
  8906. # copy
  8907. my ( $mysync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) = @_ ;
  8908. ( $mysync->{ debug } or $mysync->{dry} )
  8909. and myprint( "msg $h1_fold/$h1_msg copying to $h2_fold $mysync->{dry_message} " . eta( $mysync ) . "\n" ) ;
  8910. if ( $mysync->{dry1} )
  8911. {
  8912. $mysync->{ h1_nb_msg_processed } +=1 ;
  8913. $nb_msg_skipped_dry_mode += 1 ;
  8914. return ;
  8915. }
  8916. my $h1_size = $h1_fir_ref->{$h1_msg}->{'RFC822.SIZE'} || 0 ;
  8917. my $h1_flags = $h1_fir_ref->{$h1_msg}->{'FLAGS'} || q{} ;
  8918. my $h1_idate = $h1_fir_ref->{$h1_msg}->{'INTERNALDATE'} || q{} ;
  8919. if ( size_filtered( $h1_size, $h1_msg, $h1_fold, $h2_fold ) ) {
  8920. $mysync->{ h1_nb_msg_processed } +=1 ;
  8921. return ;
  8922. }
  8923. debugsleep( $mysync ) ;
  8924. myprint( "- msg $h1_fold/$h1_msg S[$h1_size] F[$h1_flags] I[$h1_idate] has RFC822.SIZE null!\n" ) if ( ! $h1_size ) ;
  8925. if ( $checkmessageexists and not message_exists( $mysync->{imap1}, $h1_msg ) ) {
  8926. stats_update_skip_message( $mysync, $h1_size ) ;
  8927. return ;
  8928. }
  8929. myprint( debugmemory( $mysync, " at C1" ) ) ;
  8930. my ( $string, $string_len ) ;
  8931. ( $string_len ) = message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, \$string ) ;
  8932. myprint( debugmemory( $mysync, " at C2" ) ) ;
  8933. # not defined or empty $string
  8934. if ( ( not $string ) or ( not $string_len ) ) {
  8935. myprint( "- msg $h1_fold/$h1_msg skipped.\n" ) ;
  8936. stats_update_skip_message( $mysync, $h1_size ) ;
  8937. return ;
  8938. }
  8939. # Lines too long (or not enough) => do no copy or fix
  8940. if ( ( defined $maxlinelength ) or ( defined $minmaxlinelength ) ) {
  8941. $string = linelengthstuff( $string, $h1_fold, $h1_msg, $string_len, $h1_size, $h1_flags, $h1_idate ) ;
  8942. if ( not defined $string ) {
  8943. stats_update_skip_message( $mysync, $h1_size ) ;
  8944. return ;
  8945. }
  8946. }
  8947. my $h1_date = date_for_host2( $h1_msg, $h1_idate ) ;
  8948. ( $mysync->{ debug } or $debugflags ) and
  8949. myprint( "Host1: flags init msg $h1_fold/$h1_msg date [$h1_date] flags [$h1_flags] size [$h1_size]\n" ) ;
  8950. $h1_flags = flags_for_host2( $mysync, $h1_flags, $permanentflags2 ) ;
  8951. ( $mysync->{ debug } or $debugflags ) and
  8952. myprint( "Host1: flags filt msg $h1_fold/$h1_msg date [$h1_date] flags [$h1_flags] size [$h1_size]\n" ) ;
  8953. $h1_date = undef if ( $h1_date eq q{} ) ;
  8954. 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 ) ;
  8955. if ( $new_id and $syncflagsaftercopy ) {
  8956. sync_flags_after_copy( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $new_id, $permanentflags2 ) ;
  8957. }
  8958. myprint( debugmemory( $mysync, " at C3" ) ) ;
  8959. return $new_id ;
  8960. }
  8961. sub linelengthstuff
  8962. {
  8963. my( $string, $h1_fold, $h1_msg, $string_len, $h1_size, $h1_flags, $h1_idate ) = @_ ;
  8964. my $maxlinelength_string = max_line_length( $string ) ;
  8965. $debugmaxlinelength and myprint( "msg $h1_fold/$h1_msg maxlinelength: $maxlinelength_string\n" ) ;
  8966. if ( ( defined $minmaxlinelength ) and ( $maxlinelength_string <= $minmaxlinelength ) ) {
  8967. my $subject = subject( $string ) ;
  8968. $debugdev and myprint( "- msg $h1_fold/$h1_msg skipped S[$h1_size] F[$h1_flags] I[$h1_idate] "
  8969. . "(Subject:[$subject]) (max line length under minmaxlinelength $minmaxlinelength bytes)\n" ) ;
  8970. return ;
  8971. }
  8972. if ( ( defined $maxlinelength ) and ( $maxlinelength_string > $maxlinelength ) ) {
  8973. my $subject = subject( $string ) ;
  8974. if ( $maxlinelengthcmd ) {
  8975. $string = pipemess( $string, $maxlinelengthcmd ) ;
  8976. # string undef means something was bad.
  8977. if ( not ( defined $string ) ) {
  8978. myprint( "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate] "
  8979. . "(Subject:[$subject]) could not be successfully transformed by --maxlinelengthcmd option\n" ) ;
  8980. return ;
  8981. }else{
  8982. return $string ;
  8983. }
  8984. }
  8985. myprint( "- msg $h1_fold/$h1_msg skipped S[$h1_size] F[$h1_flags] I[$h1_idate] "
  8986. . "(Subject:[$subject]) (line length exceeds maxlinelength $maxlinelength bytes)\n" ) ;
  8987. return ;
  8988. }
  8989. return $string ;
  8990. }
  8991. sub message_for_host2
  8992. {
  8993. # global variable list:
  8994. # @skipmess
  8995. # @regexmess
  8996. # @pipemess
  8997. # $debugcontent
  8998. # $debug
  8999. #
  9000. # API current
  9001. #
  9002. # at failure:
  9003. # * return nothing ( will then be undef or () )
  9004. # * $string_ref content is undef or empty
  9005. # at success:
  9006. # * return string length ($string_ref content length)
  9007. # * $string_ref content filled with message
  9008. # API future
  9009. #
  9010. #
  9011. my ( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ) = @_ ;
  9012. # abort when missing a parameter
  9013. if ( ( ! $mysync ) or ( ! $h1_msg ) or ( ! $h1_fold ) or ( ! defined $h1_size )
  9014. or ( ! defined $h1_flags) or ( ! defined $h1_idate )
  9015. or ( ! $h1_fir_ref) or ( ! $string_ref ) )
  9016. {
  9017. return ;
  9018. }
  9019. myprint( debugmemory( $mysync, " at M1" ) ) ;
  9020. my $string_ok = $mysync->{imap1}->message_to_file( $string_ref, $h1_msg ) ;
  9021. myprint( debugmemory( $mysync, " at M2" ) ) ;
  9022. my $string_len = length_ref( $string_ref ) ;
  9023. unless ( defined $string_ok and $string_len ) {
  9024. # undef or 0 length
  9025. my $error = join q{},
  9026. "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate] could not be fetched: ",
  9027. $mysync->{imap1}->LastError || q{}, "\n" ;
  9028. errors_incr( $mysync, $error ) ;
  9029. $mysync->{ h1_nb_msg_processed } +=1 ;
  9030. return ;
  9031. }
  9032. if ( @skipmess ) {
  9033. my $match = skipmess( ${ $string_ref } ) ;
  9034. # string undef means the eval regex was bad.
  9035. if ( not ( defined $match ) ) {
  9036. myprint(
  9037. "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
  9038. . " could not be skipped by --skipmess option, bad regex\n" ) ;
  9039. return ;
  9040. }
  9041. if ( $match ) {
  9042. my $subject = subject( ${ $string_ref } ) ;
  9043. myprint( "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
  9044. . " (Subject:[$subject]) skipped by --skipmess\n" ) ;
  9045. return ;
  9046. }
  9047. }
  9048. if ( @regexmess ) {
  9049. ${ $string_ref } = regexmess( ${ $string_ref } ) ;
  9050. # string undef means the eval regex was bad.
  9051. if ( not ( defined ${ $string_ref } ) ) {
  9052. myprint(
  9053. "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
  9054. . " could not be transformed by --regexmess\n" ) ;
  9055. return ;
  9056. }
  9057. }
  9058. if ( @pipemess ) {
  9059. ${ $string_ref } = pipemess( ${ $string_ref }, @pipemess ) ;
  9060. # string undef means something was bad.
  9061. if ( not ( defined ${ $string_ref } ) ) {
  9062. myprint(
  9063. "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
  9064. . " could not be successfully transformed by --pipemess option\n" ) ;
  9065. return ;
  9066. }
  9067. }
  9068. if ( $mysync->{addheader} and defined $h1_fir_ref->{$h1_msg}->{'NO_HEADER'} ) {
  9069. my $header = add_header( $h1_msg ) ;
  9070. $mysync->{ debug } and myprint( "msg $h1_fold/$h1_msg adding custom header [$header]\n" ) ;
  9071. ${ $string_ref } = $header . "\r\n" . ${ $string_ref } ;
  9072. }
  9073. if ( ( defined $mysync->{ truncmess } ) and is_integer( $mysync->{ truncmess } ) )
  9074. {
  9075. ${ $string_ref } = truncmess( ${ $string_ref }, $mysync->{ truncmess } ) ;
  9076. }
  9077. $string_len = length_ref( $string_ref ) ;
  9078. $mysync->{ debugcontent } and myprint( debugcontent( $mysync, $string_ref ) ) ;
  9079. myprint( debugmemory( $mysync, " at M3" ) ) ;
  9080. return $string_len ;
  9081. }
  9082. sub tests_debugcontent
  9083. {
  9084. note( 'Entering tests_debugcontent()' ) ;
  9085. is( undef, debugcontent( ), 'debugcontent: no args => undef' ) ;
  9086. my $mysync = { } ;
  9087. is( undef, debugcontent( $mysync ), 'debugcontent: undef => undef' ) ;
  9088. is( undef, debugcontent( $mysync, 'mm' ), 'debugcontent: undef, mm => undef' ) ;
  9089. #my $string_ref = \'zztop' ;
  9090. my $string = '================================================================================
  9091. F message content begin next line (2 characters long)
  9092. mm
  9093. F message content ended on previous line
  9094. ================================================================================
  9095. ' ;
  9096. is( $string, debugcontent( $mysync, \'mm' ), 'debugcontent: undef, mm => mm' ) ;
  9097. note( 'Leaving tests_debugcontent()' ) ;
  9098. return ;
  9099. }
  9100. sub debugcontent
  9101. {
  9102. my $mysync = shift @ARG ;
  9103. if ( ! defined $mysync ) { return ; }
  9104. my $string_ref = shift @ARG ;
  9105. if ( ! defined $string_ref ) { return ; }
  9106. if ( 'SCALAR' ne ref( $string_ref ) ) { return ; }
  9107. my $string_len = length_ref( $string_ref ) ;
  9108. my $string = join( '',
  9109. q{=} x $STD_CHAR_PER_LINE, "\n",
  9110. "F message content begin next line ($string_len characters long)\n",
  9111. ${ $string_ref },
  9112. "\nF message content ended on previous line\n", q{=} x $STD_CHAR_PER_LINE, "\n",
  9113. ) ;
  9114. return $string ;
  9115. }
  9116. sub tests_truncmess
  9117. {
  9118. note( 'Entering tests_truncmess()' ) ;
  9119. is( undef, truncmess( ), 'truncmess: no args => undef' ) ;
  9120. is( 'abc', truncmess( 'abc' ), 'truncmess: abc => abc' ) ;
  9121. is( 'ab', truncmess( 'abc', 2 ), 'truncmess: abc 2 => ab' ) ;
  9122. is( 'abc', truncmess( 'abc', 3 ), 'truncmess: abc 3 => abc' ) ;
  9123. is( 'abc', truncmess( 'abc', 4 ), 'truncmess: abc 4 => abc' ) ;
  9124. is( '12345', truncmess( "123456789\n", 5 ), 'truncmess: "123456789\n", 5 => 12345' ) ;
  9125. is( "123456789\n" x 5000, truncmess( "123456789\n" x 100000, 50000 ), 'truncmess: "123456789\n" x 100000, 50000 => "123456789\n" x 5000' ) ;
  9126. note( 'Leaving tests_truncmess()' ) ;
  9127. return ;
  9128. }
  9129. sub truncmess
  9130. {
  9131. my $string = shift ;
  9132. my $length = shift ;
  9133. if ( not defined $string ) { return ; }
  9134. if ( not defined $length ) { return $string ; }
  9135. $string = substr $string, 0, $length ;
  9136. return $string ;
  9137. }
  9138. sub tests_message_for_host2
  9139. {
  9140. note( 'Entering tests_message_for_host2()' ) ;
  9141. my ( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ) ;
  9142. is( undef, message_for_host2( ), q{message_for_host2: no args} ) ;
  9143. 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} ) ;
  9144. require_ok( "Test::MockObject" ) ;
  9145. my $imapT = Test::MockObject->new( ) ;
  9146. $mysync->{imap1} = $imapT ;
  9147. my $string ;
  9148. $h1_msg = 1 ;
  9149. $h1_fold = 'FoldFoo';
  9150. $h1_size = 9 ;
  9151. $h1_flags = q{} ;
  9152. $h1_idate = '10-Jul-2015 09:00:00 +0200' ;
  9153. $h1_fir_ref = {} ;
  9154. $string_ref = \$string ;
  9155. $imapT->mock( 'message_to_file',
  9156. sub {
  9157. my ( $imap, $mystring_ref, $msg ) = @_ ;
  9158. ${$mystring_ref} = 'blablabla' ;
  9159. return length ${$mystring_ref} ;
  9160. }
  9161. ) ;
  9162. is( 9, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
  9163. q{message_for_host2: msg 1 == "blablabla", length} ) ;
  9164. is( 'blablabla', $string, q{message_for_host2: msg 1 == "blablabla", value} ) ;
  9165. # so far so good
  9166. # now the --pipemess stuff
  9167. SKIP: {
  9168. Readonly my $NB_WIN_tests_message_for_host2 => 0 ;
  9169. skip( 'Not on MSWin32', $NB_WIN_tests_message_for_host2 ) if ('MSWin32' ne $OSNAME) ;
  9170. # Windows
  9171. # "type" command does not accept redirection of STDIN with <
  9172. # "sort" does
  9173. } ;
  9174. SKIP: {
  9175. Readonly my $NB_UNX_tests_message_for_host2 => 6 ;
  9176. skip( 'Not on Unix', $NB_UNX_tests_message_for_host2 ) if ('MSWin32' eq $OSNAME) ;
  9177. # Unix
  9178. # no change by cat
  9179. @pipemess = ( 'cat' ) ;
  9180. is( 9, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
  9181. q{message_for_host2: --pipemess 'cat', length} ) ;
  9182. is( 'blablabla', $string, q{message_for_host2: --pipemess 'cat', value} ) ;
  9183. # failure by false
  9184. @pipemess = ( 'false' ) ;
  9185. is( undef, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
  9186. q{message_for_host2: --pipemess 'false', length} ) ;
  9187. is( undef, $string, q{message_for_host2: --pipemess 'false', value} ) ;
  9188. # failure by true since no output
  9189. @pipemess = ( 'true' ) ;
  9190. is( undef, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
  9191. q{message_for_host2: --pipemess 'true', length} ) ;
  9192. is( undef, $string, q{message_for_host2: --pipemess 'true', value} ) ;
  9193. }
  9194. note( 'Leaving tests_message_for_host2()' ) ;
  9195. return ;
  9196. }
  9197. sub tests_labels_remove_subfolder1
  9198. {
  9199. note( 'Entering tests_labels_remove_subfolder1()' ) ;
  9200. is( undef, labels_remove_subfolder1( ), 'labels_remove_subfolder1: no parameters => undef' ) ;
  9201. is( 'Blabla', labels_remove_subfolder1( 'Blabla' ), 'labels_remove_subfolder1: one parameter Blabla => Blabla' ) ;
  9202. is( 'Blan blue', labels_remove_subfolder1( 'Blan blue' ), 'labels_remove_subfolder1: one parameter Blan blue => Blan blue' ) ;
  9203. is( '\Bla "Blan blan" Blabla', labels_remove_subfolder1( '\Bla "Blan blan" Blabla' ),
  9204. 'labels_remove_subfolder1: one parameter \Bla "Blan blan" Blabla => \Bla "Blan blan" Blabla' ) ;
  9205. is( 'Bla', labels_remove_subfolder1( 'Subf/Bla', 'Subf' ), 'labels_remove_subfolder1: Subf/Bla Subf => "Bla"' ) ;
  9206. is( '"\\\\Bla"', labels_remove_subfolder1( '"\\\\Bla"', 'Subf' ), 'labels_remove_subfolder1: "\\\\Bla" Subf => "\\\\Bla"' ) ;
  9207. is( 'Bla Kii', labels_remove_subfolder1( 'Subf/Bla Subf/Kii', 'Subf' ),
  9208. 'labels_remove_subfolder1: Subf/Bla Subf/Kii, Subf => "Bla" "Kii"' ) ;
  9209. is( '"\\\\Bla" Kii', labels_remove_subfolder1( '"\\\\Bla" Subf/Kii', 'Subf' ),
  9210. 'labels_remove_subfolder1: "\\\\Bla" Subf/Kii Subf => "\\\\Bla" Kii' ) ;
  9211. is( '"Blan blan"', labels_remove_subfolder1( '"Subf/Blan blan"', 'Subf' ),
  9212. 'labels_remove_subfolder1: "Subf/Blan blan" Subf => "Blan blan"' ) ;
  9213. is( '"\\\\Loo" "Blan blan" Kii', labels_remove_subfolder1( '"\\\\Loo" "Subf/Blan blan" Subf/Kii', 'Subf' ),
  9214. 'labels_remove_subfolder1: "\\\\Loo" "Subf/Blan blan" Subf/Kii + Subf => "\\\\Loo" "Blan blan" Kii' ) ;
  9215. is( '"\\\\Inbox"', labels_remove_subfolder1( 'Subf/INBOX', 'Subf' ),
  9216. 'labels_remove_subfolder1: Subf/INBOX + Subf => "\\\\Inbox"' ) ;
  9217. is( '"\\\\Loo" "Blan blan" Kii "\\\\Inbox"', labels_remove_subfolder1( '"\\\\Loo" "Subf/Blan blan" Subf/Kii Subf/INBOX', 'Subf' ),
  9218. 'labels_remove_subfolder1: "\\\\Loo" "Subf/Blan blan" Subf/Kii Subf/INBOX + Subf => "\\\\Loo" "Blan blan" Kii "\\\\Inbox"' ) ;
  9219. note( 'Leaving tests_labels_remove_subfolder1()' ) ;
  9220. return ;
  9221. }
  9222. sub labels_remove_subfolder1
  9223. {
  9224. my $labels = shift ;
  9225. my $subfolder1 = shift ;
  9226. if ( not defined $labels ) { return ; }
  9227. if ( not defined $subfolder1 ) { return $labels ; }
  9228. my @labels = quotewords('\s+', 1, $labels ) ;
  9229. #myprint( "@labels\n" ) ;
  9230. my @labels_subfolder2 ;
  9231. foreach my $label ( @labels )
  9232. {
  9233. if ( $label =~ m{zzzzzzzzzz} )
  9234. {
  9235. # \Seen \Deleted ... stay the same
  9236. push @labels_subfolder2, $label ;
  9237. }
  9238. else
  9239. {
  9240. # Remove surrounding quotes if any, to add them again in case of space
  9241. $label = join( q{}, quotewords('\s+', 0, $label ) ) ;
  9242. $label =~ s{$subfolder1/?}{} ;
  9243. if ( 'INBOX' eq $label )
  9244. {
  9245. push @labels_subfolder2, q{"\\\\Inbox"} ;
  9246. }
  9247. elsif ( $label =~ m{\\} )
  9248. {
  9249. push @labels_subfolder2, qq{"\\$label"} ;
  9250. }
  9251. elsif ( $label =~ m{ } )
  9252. {
  9253. push @labels_subfolder2, qq{"$label"} ;
  9254. }
  9255. else
  9256. {
  9257. push @labels_subfolder2, $label ;
  9258. }
  9259. }
  9260. }
  9261. my $labels_subfolder2 = join( ' ', sort uniq( @labels_subfolder2 ) ) ;
  9262. return $labels_subfolder2 ;
  9263. }
  9264. sub tests_labels_remove_special
  9265. {
  9266. note( 'Entering tests_labels_remove_special()' ) ;
  9267. is( undef, labels_remove_special( ), 'labels_remove_special: no parameters => undef' ) ;
  9268. is( q{}, labels_remove_special( q{} ), 'labels_remove_special: empty string => empty string' ) ;
  9269. is( q{}, labels_remove_special( '"\\\\Inbox"' ), 'labels_remove_special:"\\\\Inbox" => empty string' ) ;
  9270. is( q{}, labels_remove_special( '"\\\\Inbox" "\\\\Starred"' ), 'labels_remove_special:"\\\\Inbox" "\\\\Starred" => empty string' ) ;
  9271. is( 'Bar Foo', labels_remove_special( 'Foo Bar' ), 'labels_remove_special:Foo Bar => Bar Foo' ) ;
  9272. is( 'Bar Foo', labels_remove_special( 'Foo Bar "\\\\Inbox"' ), 'labels_remove_special:Foo Bar "\\\\Inbox" => Bar Foo' ) ;
  9273. note( 'Leaving tests_labels_remove_special()' ) ;
  9274. return ;
  9275. }
  9276. sub labels_remove_special
  9277. {
  9278. my $labels = shift ;
  9279. if ( not defined $labels ) { return ; }
  9280. my @labels = quotewords('\s+', 1, $labels ) ;
  9281. myprint( "labels before remove_non_folded: @labels\n" ) ;
  9282. my @labels_remove_special ;
  9283. foreach my $label ( @labels )
  9284. {
  9285. if ( $label =~ m{^\"\\\\} )
  9286. {
  9287. # not kept
  9288. }
  9289. else
  9290. {
  9291. push @labels_remove_special, $label ;
  9292. }
  9293. }
  9294. my $labels_remove_special = join( ' ', sort @labels_remove_special ) ;
  9295. return $labels_remove_special ;
  9296. }
  9297. sub tests_labels_add_subfolder2
  9298. {
  9299. note( 'Entering tests_labels_add_subfolder2()' ) ;
  9300. is( undef, labels_add_subfolder2( ), 'labels_add_subfolder2: no parameters => undef' ) ;
  9301. is( 'Blabla', labels_add_subfolder2( 'Blabla' ), 'labels_add_subfolder2: one parameter Blabla => Blabla' ) ;
  9302. is( 'Blan blue', labels_add_subfolder2( 'Blan blue' ), 'labels_add_subfolder2: one parameter Blan blue => Blan blue' ) ;
  9303. is( '\Bla "Blan blan" Blabla', labels_add_subfolder2( '\Bla "Blan blan" Blabla' ),
  9304. 'labels_add_subfolder2: one parameter \Bla "Blan blan" Blabla => \Bla "Blan blan" Blabla' ) ;
  9305. is( 'Subf/Bla', labels_add_subfolder2( 'Bla', 'Subf' ), 'labels_add_subfolder2: Bla Subf => "Subf/Bla"' ) ;
  9306. is( 'Subf/\Bla', labels_add_subfolder2( '\\\\Bla', 'Subf' ), 'labels_add_subfolder2: \Bla Subf => \Bla' ) ;
  9307. is( 'Subf/Bla Subf/Kii', labels_add_subfolder2( 'Bla Kii', 'Subf' ),
  9308. 'labels_add_subfolder2: Bla Kii Subf => "Subf/Bla" "Subf/Kii"' ) ;
  9309. is( 'Subf/Kii Subf/\Bla', labels_add_subfolder2( '\\\\Bla Kii', 'Subf' ),
  9310. 'labels_add_subfolder2: \Bla Kii Subf => \Bla Subf/Kii' ) ;
  9311. is( '"Subf/Blan blan"', labels_add_subfolder2( '"Blan blan"', 'Subf' ),
  9312. 'labels_add_subfolder2: "Blan blan" Subf => "Subf/Blan blan"' ) ;
  9313. is( '"Subf/Blan blan" Subf/Kii Subf/\Loo', labels_add_subfolder2( '\\\\Loo "Blan blan" Kii', 'Subf' ),
  9314. 'labels_add_subfolder2: \Loo "Blan blan" Kii + Subf => "Subf/Blan blan" Subf/Kii Subf/\Loo' ) ;
  9315. # "\\Inbox" is special, add to subfolder INBOX also because Gmail will but ...
  9316. is( '"Subf/\\\\Inbox" Subf/INBOX', labels_add_subfolder2( '"\\\\Inbox"', 'Subf' ),
  9317. 'labels_add_subfolder2: "\\\\Inbox" Subf => "Subf/\\\\Inbox" Subf/INBOX' ) ;
  9318. # but not with INBOX folder
  9319. is( '"Subf/\\\\Inbox"', labels_add_subfolder2( '"\\\\Inbox"', 'Subf', 'INBOX' ),
  9320. 'labels_add_subfolder2: "\\\\Inbox" Subf INBOX => "Subf/\\\\Inbox"' ) ;
  9321. # two times => one time
  9322. is( '"Subf/\\\\Inbox" Subf/INBOX', labels_add_subfolder2( '"\\\\Inbox" "\\\\Inbox"', 'Subf' ),
  9323. 'labels_add_subfolder2: "\\\\Inbox" "\\\\Inbox" Subf => "Subf/\\\\Inbox"' ) ;
  9324. is( '"Subf/\\\\Starred"', labels_add_subfolder2( '"\\\\Starred"', 'Subf' ),
  9325. 'labels_add_subfolder2: "\\\\Starred" Subf => "Subf/\\\\Starred"' ) ;
  9326. note( 'Leaving tests_labels_add_subfolder2()' ) ;
  9327. return ;
  9328. }
  9329. sub labels_add_subfolder2
  9330. {
  9331. my $labels = shift ;
  9332. my $subfolder2 = shift ;
  9333. my $h1_folder = shift || q{} ;
  9334. if ( not defined $labels ) { return ; }
  9335. if ( not defined $subfolder2 ) { return $labels ; }
  9336. # Isn't it messy?
  9337. if ( 'INBOX' eq $h1_folder )
  9338. {
  9339. $labels .= ' "\\\\Inbox"' ;
  9340. }
  9341. my @labels = uniq( quotewords('\s+', 1, $labels ) ) ;
  9342. myprint( "labels before subfolder2: @labels\n" ) ;
  9343. my @labels_subfolder2 ;
  9344. foreach my $label ( @labels )
  9345. {
  9346. # Isn't it more messy?
  9347. if ( ( q{"\\\\Inbox"} eq $label ) and ( 'INBOX' ne $h1_folder ) )
  9348. {
  9349. if ( $subfolder2 =~ m{ } )
  9350. {
  9351. push @labels_subfolder2, qq{"$subfolder2/INBOX"} ;
  9352. }
  9353. else
  9354. {
  9355. push @labels_subfolder2, "$subfolder2/INBOX" ;
  9356. }
  9357. }
  9358. if ( $label =~ m{^\"\\\\} )
  9359. {
  9360. # \Seen \Deleted ... stay the same
  9361. #push @labels_subfolder2, $label ;
  9362. # Remove surrounding quotes if any, to add them again
  9363. $label = join( q{}, quotewords('\s+', 0, $label ) ) ;
  9364. push @labels_subfolder2, qq{"$subfolder2/\\$label"} ;
  9365. }
  9366. else
  9367. {
  9368. # Remove surrounding quotes if any, to add them again in case of space
  9369. $label = join( q{}, quotewords('\s+', 0, $label ) ) ;
  9370. if ( $label =~ m{ } )
  9371. {
  9372. push @labels_subfolder2, qq{"$subfolder2/$label"} ;
  9373. }
  9374. else
  9375. {
  9376. push @labels_subfolder2, "$subfolder2/$label" ;
  9377. }
  9378. }
  9379. }
  9380. my $labels_subfolder2 = join( ' ', sort @labels_subfolder2 ) ;
  9381. return $labels_subfolder2 ;
  9382. }
  9383. sub tests_labels
  9384. {
  9385. note( 'Entering tests_labels()' ) ;
  9386. is( undef, labels( ), 'labels: no parameters => undef' ) ;
  9387. is( undef, labels( undef ), 'labels: undef => undef' ) ;
  9388. require_ok( "Test::MockObject" ) ;
  9389. my $myimap = Test::MockObject->new( ) ;
  9390. $myimap->mock( 'fetch_hash',
  9391. sub {
  9392. return(
  9393. { '1' => {
  9394. 'X-GM-LABELS' => '\Seen Blabla'
  9395. }
  9396. }
  9397. ) ;
  9398. }
  9399. ) ;
  9400. $myimap->mock( 'Debug' , sub { } ) ;
  9401. $myimap->mock( 'Unescape', sub { return Mail::IMAPClient::Unescape( @_ ) } ) ; # real one
  9402. is( undef, labels( $myimap ), 'labels: one parameter => undef' ) ;
  9403. is( '\Seen Blabla', labels( $myimap, '1' ), 'labels: $mysync UID_1 => \Seen Blabla' ) ;
  9404. note( 'Leaving tests_labels()' ) ;
  9405. return ;
  9406. }
  9407. sub labels
  9408. {
  9409. my ( $myimap, $uid ) = @ARG ;
  9410. if ( not all_defined( $myimap, $uid ) ) {
  9411. return ;
  9412. }
  9413. my $hash = $myimap->fetch_hash( [ $uid ], 'X-GM-LABELS' ) ;
  9414. my $labels = $hash->{ $uid }->{ 'X-GM-LABELS' } ;
  9415. #$labels = $myimap->Unescape( $labels ) ;
  9416. return $labels ;
  9417. }
  9418. sub tests_synclabels
  9419. {
  9420. note( 'Entering tests_synclabels()' ) ;
  9421. is( undef, synclabels( ), 'synclabels: no parameters => undef' ) ;
  9422. is( undef, synclabels( undef ), 'synclabels: undef => undef' ) ;
  9423. my $mysync ;
  9424. is( undef, synclabels( $mysync ), 'synclabels: var undef => undef' ) ;
  9425. require_ok( "Test::MockObject" ) ;
  9426. $mysync = {} ;
  9427. my $myimap1 = Test::MockObject->new( ) ;
  9428. $myimap1->mock( 'fetch_hash',
  9429. sub {
  9430. return(
  9431. { '1' => {
  9432. 'X-GM-LABELS' => '\Seen Blabla'
  9433. }
  9434. }
  9435. ) ;
  9436. }
  9437. ) ;
  9438. $myimap1->mock( 'Debug', sub { } ) ;
  9439. $myimap1->mock( 'Unescape', sub { return Mail::IMAPClient::Unescape( @_ ) } ) ; # real one
  9440. my $myimap2 = Test::MockObject->new( ) ;
  9441. $myimap2->mock( 'store',
  9442. sub {
  9443. return 1 ;
  9444. }
  9445. ) ;
  9446. $mysync->{imap1} = $myimap1 ;
  9447. $mysync->{imap2} = $myimap2 ;
  9448. is( undef, synclabels( $mysync ), 'synclabels: fresh $mysync => undef' ) ;
  9449. is( undef, synclabels( $mysync, '1' ), 'synclabels: $mysync UID_1 alone => undef' ) ;
  9450. is( 1, synclabels( $mysync, '1', '2' ), 'synclabels: $mysync UID_1 UID_2 => 1' ) ;
  9451. note( 'Leaving tests_synclabels()' ) ;
  9452. return ;
  9453. }
  9454. sub synclabels
  9455. {
  9456. my( $mysync, $uid1, $uid2 ) = @ARG ;
  9457. if ( not all_defined( $mysync, $uid1, $uid2 ) ) {
  9458. return ;
  9459. }
  9460. my $myimap1 = $mysync->{ 'imap1' } || return ;
  9461. my $myimap2 = $mysync->{ 'imap2' } || return ;
  9462. $mysync->{debuglabels} and $myimap1->Debug( 1 ) ;
  9463. my $labels1 = labels( $myimap1, $uid1 ) ;
  9464. $mysync->{debuglabels} and $myimap1->Debug( 0 ) ;
  9465. $mysync->{debuglabels} and myprint( "Host1 labels: $labels1\n" ) ;
  9466. if ( $mysync->{ subfolder1 } and $labels1 )
  9467. {
  9468. $labels1 = labels_remove_subfolder1( $labels1, $mysync->{ subfolder1 } ) ;
  9469. $mysync->{debuglabels} and myprint( "Host1 labels with subfolder1: $labels1\n" ) ;
  9470. }
  9471. if ( $mysync->{ subfolder2 } and $labels1 )
  9472. {
  9473. $labels1 = labels_add_subfolder2( $labels1, $mysync->{ subfolder2 } ) ;
  9474. $mysync->{debuglabels} and myprint( "Host1 labels with subfolder2: $labels1\n" ) ;
  9475. }
  9476. my $store ;
  9477. if ( $labels1 and not $mysync->{ dry } )
  9478. {
  9479. $mysync->{ debuglabels } and $myimap2->Debug( 1 ) ;
  9480. $store = $myimap2->store( $uid2, "X-GM-LABELS ($labels1)" ) ;
  9481. $mysync->{ debuglabels } and $myimap2->Debug( 0 ) ;
  9482. }
  9483. return $store ;
  9484. }
  9485. sub tests_resynclabels
  9486. {
  9487. note( 'Entering tests_resynclabels()' ) ;
  9488. is( undef, resynclabels( ), 'resynclabels: no parameters => undef' ) ;
  9489. is( undef, resynclabels( undef ), 'resynclabels: undef => undef' ) ;
  9490. my $mysync ;
  9491. is( undef, resynclabels( $mysync ), 'resynclabels: var undef => undef' ) ;
  9492. my ( $h1_fir_ref, $h2_fir_ref ) ;
  9493. $mysync->{ debuglabels } = 1 ;
  9494. $h1_fir_ref->{ 11 }->{ 'X-GM-LABELS' } = '\Seen Baa Kii' ;
  9495. $h2_fir_ref->{ 22 }->{ 'X-GM-LABELS' } = '\Seen Baa Kii' ;
  9496. # labels are equal
  9497. is( 1, resynclabels( $mysync, 11, 22, $h1_fir_ref, $h2_fir_ref ),
  9498. 'resynclabels: $mysync UID_1 UID_2 labels are equal => 1' ) ;
  9499. # labels are different
  9500. $h2_fir_ref->{ 22 }->{ 'X-GM-LABELS' } = '\Seen Zuu' ;
  9501. require_ok( "Test::MockObject" ) ;
  9502. my $myimap2 = Test::MockObject->new( ) ;
  9503. $myimap2->mock( 'store',
  9504. sub {
  9505. return 1 ;
  9506. }
  9507. ) ;
  9508. $myimap2->mock( 'Debug', sub { } ) ;
  9509. $mysync->{imap2} = $myimap2 ;
  9510. is( 1, resynclabels( $mysync, 11, 22, $h1_fir_ref, $h2_fir_ref ),
  9511. 'resynclabels: $mysync UID_1 UID_2 labels are not equal => store => 1' ) ;
  9512. note( 'Leaving tests_resynclabels()' ) ;
  9513. return ;
  9514. }
  9515. sub resynclabels
  9516. {
  9517. my( $mysync, $uid1, $uid2, $h1_fir_ref, $h2_fir_ref, $h1_folder ) = @ARG ;
  9518. if ( not all_defined( $mysync, $uid1, $uid2, $h1_fir_ref, $h2_fir_ref ) ) {
  9519. return ;
  9520. }
  9521. my $labels1 = $h1_fir_ref->{ $uid1 }->{ 'X-GM-LABELS' } || q{} ;
  9522. my $labels2 = $h2_fir_ref->{ $uid2 }->{ 'X-GM-LABELS' } || q{} ;
  9523. if ( $mysync->{ subfolder1 } and $labels1 )
  9524. {
  9525. $labels1 = labels_remove_subfolder1( $labels1, $mysync->{ subfolder1 } ) ;
  9526. }
  9527. if ( $mysync->{ subfolder2 } and $labels1 )
  9528. {
  9529. $labels1 = labels_add_subfolder2( $labels1, $mysync->{ subfolder2 }, $h1_folder ) ;
  9530. $labels2 = labels_remove_special( $labels2 ) ;
  9531. }
  9532. $mysync->{ debuglabels } and myprint( "Host1 labels fixed: $labels1\n" ) ;
  9533. $mysync->{ debuglabels } and myprint( "Host2 labels : $labels2\n" ) ;
  9534. my $store ;
  9535. if ( $labels1 eq $labels2 )
  9536. {
  9537. # no sync needed
  9538. $mysync->{ debuglabels } and myprint( "Labels are already equal\n" ) ;
  9539. return 1 ;
  9540. }
  9541. elsif ( not $mysync->{ dry } )
  9542. {
  9543. # sync needed
  9544. $mysync->{debuglabels} and $mysync->{imap2}->Debug( 1 ) ;
  9545. $store = $mysync->{imap2}->store( $uid2, "X-GM-LABELS ($labels1)" ) ;
  9546. $mysync->{debuglabels} and $mysync->{imap2}->Debug( 0 ) ;
  9547. }
  9548. return $store ;
  9549. }
  9550. sub tests_uniq
  9551. {
  9552. note( 'Entering tests_uniq()' ) ;
  9553. is( 0, uniq( ), 'uniq: undef => 0' ) ;
  9554. is_deeply( [ 'one' ], [ uniq( 'one' ) ], 'uniq: one => one' ) ;
  9555. is_deeply( [ 'one' ], [ uniq( 'one', 'one' ) ], 'uniq: one one => one' ) ;
  9556. is_deeply( [ 'one', 'two' ], [ uniq( 'one', 'one', 'two', 'one', 'two' ) ], 'uniq: one one two one two => one two' ) ;
  9557. note( 'Leaving tests_uniq()' ) ;
  9558. return ;
  9559. }
  9560. sub uniq
  9561. {
  9562. my @list = @ARG ;
  9563. my %seen = ( ) ;
  9564. my @uniq = ( ) ;
  9565. foreach my $item ( @list ) {
  9566. if ( ! $seen{ $item } ) {
  9567. $seen{ $item } = 1 ;
  9568. push( @uniq, $item ) ;
  9569. }
  9570. }
  9571. return @uniq ;
  9572. }
  9573. sub length_ref
  9574. {
  9575. my $string_ref = shift ;
  9576. my $string_len = defined ${ $string_ref } ? length( ${ $string_ref } ) : q{} ; # length or empty string
  9577. return $string_len ;
  9578. }
  9579. sub tests_length_ref
  9580. {
  9581. note( 'Entering tests_length_ref()' ) ;
  9582. my $notdefined ;
  9583. is( q{}, length_ref( \$notdefined ), q{length_ref: value not defined} ) ;
  9584. my $notref ;
  9585. is( q{}, length_ref( $notref ), q{length_ref: param not a ref} ) ;
  9586. my $lala = 'lala' ;
  9587. is( 4, length_ref( \$lala ), q{length_ref: lala length == 4} ) ;
  9588. is( 4, length_ref( \'lili' ), q{length_ref: lili length == 4} ) ;
  9589. note( 'Leaving tests_length_ref()' ) ;
  9590. return ;
  9591. }
  9592. sub date_for_host2
  9593. {
  9594. my( $h1_msg, $h1_idate ) = @_ ;
  9595. my $h1_date = q{} ;
  9596. if ( $syncinternaldates ) {
  9597. $h1_date = $h1_idate ;
  9598. $sync->{ debug } and myprint( "internal date from host1: [$h1_date]\n" ) ;
  9599. $h1_date = good_date( $h1_date ) ;
  9600. $sync->{ debug } and myprint( "internal date from host1: [$h1_date] (fixed)\n" ) ;
  9601. }
  9602. if ( $idatefromheader ) {
  9603. $h1_date = $sync->{imap1}->get_header( $h1_msg, 'Date' ) ;
  9604. $sync->{ debug } and myprint( "header date from host1: [$h1_date]\n" ) ;
  9605. $h1_date = good_date( $h1_date ) ;
  9606. $sync->{ debug } and myprint( "header date from host1: [$h1_date] (fixed)\n" ) ;
  9607. }
  9608. return( $h1_date ) ;
  9609. }
  9610. sub subject
  9611. {
  9612. my $string = shift ;
  9613. my $subject = q{} ;
  9614. my $header = extract_header( $string ) ;
  9615. if( $header =~ m/^Subject:[ \t]*([^\n\r]*)\r?$/msx ) {
  9616. #myprint( "MMM[$1]\n" ) ;
  9617. $subject = $1 ;
  9618. }
  9619. return( $subject ) ;
  9620. }
  9621. sub tests_subject
  9622. {
  9623. note( 'Entering tests_subject()' ) ;
  9624. ok( q{} eq subject( q{} ), 'subject: null') ;
  9625. is( '', subject( 'Subject:' ), 'Subject:') ;
  9626. is( '', subject( "Subject:\r\n" ), 'Subject:\r\n') ;
  9627. ok( 'toto le hero' eq subject( 'Subject: toto le hero' ), 'Subject: toto le hero') ;
  9628. ok( 'toto le hero' eq subject( 'Subject:toto le hero' ), 'Subject:toto le hero') ;
  9629. ok( 'toto le hero' eq subject( "Subject:toto le hero\r\n" ), 'Subject: toto le hero\r\n') ;
  9630. my $MESS ;
  9631. $MESS = <<'EOF';
  9632. From: lalala
  9633. Subject: toto le hero
  9634. Date: zzzzzz
  9635. Boogie boogie
  9636. EOF
  9637. ok( 'toto le hero' eq subject( $MESS ), 'subject: toto le hero 2') ;
  9638. $MESS = <<'EOF';
  9639. Subject: toto le hero
  9640. From: lalala
  9641. Date: zzzzzz
  9642. Boogie boogie
  9643. EOF
  9644. ok( 'toto le hero' eq subject( $MESS ), 'subject: toto le hero 3') ;
  9645. $MESS = <<'EOF';
  9646. From: lalala
  9647. Subject: cuicui
  9648. Date: zzzzzz
  9649. Subject: toto le hero
  9650. EOF
  9651. ok( 'cuicui' eq subject( $MESS ), 'subject: cuicui') ;
  9652. $MESS = <<'EOF';
  9653. From: lalala
  9654. Date: zzzzzz
  9655. Subject: toto le hero
  9656. EOF
  9657. ok( q{} eq subject( $MESS ), 'subject: null but body could') ;
  9658. $MESS = <<'EOF';
  9659. From: lalala
  9660. Subject:
  9661. Date: zzzzzz
  9662. Subject: toto le hero
  9663. EOF
  9664. is( '', subject( $MESS ), 'Subject:') ;
  9665. note( 'Leaving tests_subject()' ) ;
  9666. return ;
  9667. }
  9668. # GlobVar
  9669. # $h2_uidguess
  9670. # ...
  9671. #
  9672. #
  9673. sub append_message_on_host2
  9674. {
  9675. my( $mysync, $string_ref, $h1_fold, $h1_msg, $string_len, $h2_fold, $h1_size, $h1_flags, $h1_date, $cache_dir ) = @_ ;
  9676. myprint( debugmemory( $mysync, " at A1" ) ) ;
  9677. my $new_id ;
  9678. if ( ! $mysync->{dry} ) {
  9679. $new_id = $mysync->{imap2}->append_string( $h2_fold, ${ $string_ref }, $h1_flags, $h1_date ) ;
  9680. myprint( debugmemory( $mysync, " at A2" ) ) ;
  9681. if ( ! defined $new_id ){
  9682. my $subject = subject( ${ $string_ref } ) ;
  9683. my $error_imap = $mysync->{imap2}->LastError || q{} ;
  9684. 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" ;
  9685. errors_incr( $mysync, $error ) ;
  9686. $mysync->{ h1_nb_msg_processed } +=1 ;
  9687. return ;
  9688. }
  9689. else{
  9690. # good
  9691. # $new_id is an id if the IMAP server has the
  9692. # UIDPLUS capability else just a ref
  9693. if ( $new_id !~ m{^\d+$}x ) {
  9694. $new_id = lastuid( $mysync->{imap2}, $h2_fold, $h2_uidguess ) ;
  9695. }
  9696. if ( $mysync->{ synclabels } ) { synclabels( $mysync, $h1_msg, $new_id ) }
  9697. $h2_uidguess += 1 ;
  9698. $mysync->{ total_bytes_transferred } += $string_len ;
  9699. $mysync->{ nb_msg_transferred } += 1 ;
  9700. $mysync->{ h1_nb_msg_processed } +=1 ;
  9701. $mysync->{ biggest_message_transferred } = max( $string_len, $mysync->{ biggest_message_transferred } ) ;
  9702. my $time_spent = timesince( $mysync->{begin_transfer_time} ) ;
  9703. my $rate = bytes_display_string_bin( $mysync->{total_bytes_transferred} / $time_spent ) ;
  9704. my $eta = eta( $mysync ) ;
  9705. my $amount_transferred = bytes_display_string_bin( $mysync->{total_bytes_transferred} ) ;
  9706. myprintf( "msg %s/%-19s copied to %s/%-10s %.2f msgs/s %s/s %s copied %s\n",
  9707. $h1_fold, "$h1_msg {$string_len}", $h2_fold, $new_id, $mysync->{nb_msg_transferred}/$time_spent, $rate,
  9708. $amount_transferred,
  9709. $eta );
  9710. sleep_if_needed( $mysync ) ;
  9711. if ( $usecache and $cacheaftercopy and $new_id =~ m{^\d+$}x ) {
  9712. $debugcache and myprint( "touch $cache_dir/${h1_msg}_$new_id\n" ) ;
  9713. touch( "$cache_dir/${h1_msg}_$new_id" )
  9714. or croak( "Couldn't touch $cache_dir/${h1_msg}_$new_id" ) ;
  9715. }
  9716. if ( $mysync->{ delete1 } ) {
  9717. delete_message_on_host1( $mysync, $h1_fold, $mysync->{ expungeaftereach }, $h1_msg ) ;
  9718. }
  9719. #myprint( "PRESS ENTER" ) and my $a = <> ;
  9720. return( $new_id ) ;
  9721. }
  9722. }
  9723. else{
  9724. $nb_msg_skipped_dry_mode += 1 ;
  9725. $mysync->{ h1_nb_msg_processed } += 1 ;
  9726. }
  9727. return ;
  9728. }
  9729. sub tests_sleep_if_needed
  9730. {
  9731. note( 'Entering tests_sleep_if_needed()' ) ;
  9732. is( undef, sleep_if_needed( ), 'sleep_if_needed: no args => undef' ) ;
  9733. my $mysync ;
  9734. is( undef, sleep_if_needed( $mysync ), 'sleep_if_needed: arg undef => undef' ) ;
  9735. $mysync->{maxbytespersecond} = 1000 ;
  9736. is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: maxbytespersecond only => no sleep => 0' ) ;
  9737. $mysync->{begin_transfer_time} = time ; # now
  9738. is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: begin_transfer_time now => no sleep => 0' ) ;
  9739. $mysync->{begin_transfer_time} = time - 2 ; # 2 s before
  9740. is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 0 => no sleep => 0' ) ;
  9741. $mysync->{total_bytes_transferred} = 2200 ;
  9742. $mysync->{begin_transfer_time} = time - 2 ; # 2 s before
  9743. is( '0.20', sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 2200 since 2s => sleep 0.2s' ) ;
  9744. is( '0', sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 2200 since 2+2 == 4s => no sleep' ) ;
  9745. $mysync->{maxsleep} = 0.1 ;
  9746. $mysync->{begin_transfer_time} = time - 2 ; # 2 s before again
  9747. is( '0.10', sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 4000 since 2s but maxsleep 0.1s => sleep 0.1s' ) ;
  9748. $mysync->{maxbytesafter} = 4000 ;
  9749. $mysync->{begin_transfer_time} = time - 2 ; # 2 s before again
  9750. is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: maxbytesafter == total_bytes_transferred => no sleep => 0' ) ;
  9751. note( 'Leaving tests_sleep_if_needed()' ) ;
  9752. return ;
  9753. }
  9754. sub sleep_if_needed
  9755. {
  9756. my( $mysync ) = shift ;
  9757. if ( ! $mysync ) {
  9758. return ;
  9759. }
  9760. # No need to go further if there is no limit set
  9761. if (
  9762. not (
  9763. $mysync->{maxmessagespersecond}
  9764. or $mysync->{maxbytespersecond}
  9765. )
  9766. ) {
  9767. return ;
  9768. }
  9769. $mysync->{maxsleep} = defined $mysync->{maxsleep} ? $mysync->{maxsleep} : $MAX_SLEEP ;
  9770. # Must be positive
  9771. $mysync->{maxsleep} = max( 0, $mysync->{maxsleep} ) ;
  9772. my $time_spent = timesince( $mysync->{begin_transfer_time} ) ;
  9773. my $sleep_max_messages = sleep_max_messages( $mysync->{nb_msg_transferred}, $time_spent, $mysync->{maxmessagespersecond} ) ;
  9774. my $maxbytesafter = $mysync->{maxbytesafter} || 0 ;
  9775. my $total_bytes_transferred = $mysync->{total_bytes_transferred} || 0 ;
  9776. my $total_bytes_to_consider = $total_bytes_transferred - $maxbytesafter ;
  9777. #myprint( "maxbytesafter:$maxbytesafter\n" ) ;
  9778. #myprint( "total_bytes_to_consider:$total_bytes_to_consider\n" ) ;
  9779. my $sleep_max_bytes = sleep_max_bytes( $total_bytes_to_consider, $time_spent, $mysync->{maxbytespersecond} ) ;
  9780. my $sleep_max = min( $mysync->{maxsleep}, max( $sleep_max_messages, $sleep_max_bytes ) ) ;
  9781. $sleep_max = mysprintf( "%.2f", $sleep_max ) ; # round with 2 decimals.
  9782. if ( $sleep_max > 0 ) {
  9783. myprint( "sleeping $sleep_max s\n" ) ;
  9784. sleep $sleep_max ;
  9785. # Slept
  9786. return $sleep_max ;
  9787. }
  9788. # No sleep
  9789. return 0 ;
  9790. }
  9791. sub sleep_max_messages
  9792. {
  9793. # how long we have to sleep to go under max_messages_per_second
  9794. my( $nb_msg_transferred, $time_spent, $maxmessagespersecond ) = @_ ;
  9795. if ( ( not defined $maxmessagespersecond ) or $maxmessagespersecond <= 0 ) { return( 0 ) } ;
  9796. my $sleep = ( $nb_msg_transferred / $maxmessagespersecond ) - $time_spent ;
  9797. # the sleep must be positive
  9798. return( max( 0, $sleep ) ) ;
  9799. }
  9800. sub tests_sleep_max_messages
  9801. {
  9802. note( 'Entering tests_sleep_max_messages()' ) ;
  9803. ok( 0 == sleep_max_messages( 4, 2, undef ), 'sleep_max_messages: maxmessagespersecond = undef') ;
  9804. ok( 0 == sleep_max_messages( 4, 2, 0 ), 'sleep_max_messages: maxmessagespersecond = 0') ;
  9805. ok( 0 == sleep_max_messages( 4, 2, $MINUS_ONE ), 'sleep_max_messages: maxmessagespersecond = -1') ;
  9806. ok( 0 == sleep_max_messages( 4, 2, 2 ), 'sleep_max_messages: maxmessagespersecond = 2 max reached') ;
  9807. ok( 2 == sleep_max_messages( 8, 2, 2 ), 'sleep_max_messages: maxmessagespersecond = 2 max over') ;
  9808. ok( 0 == sleep_max_messages( 2, 2, 2 ), 'sleep_max_messages: maxmessagespersecond = 2 max not reached') ;
  9809. note( 'Leaving tests_sleep_max_messages()' ) ;
  9810. return ;
  9811. }
  9812. sub sleep_max_bytes
  9813. {
  9814. # how long we have to sleep to go under max_bytes_per_second
  9815. my( $total_bytes_to_consider, $time_spent, $maxbytespersecond ) = @_ ;
  9816. $total_bytes_to_consider ||= 0 ;
  9817. $time_spent ||= 0 ;
  9818. if ( ( not defined $maxbytespersecond ) or $maxbytespersecond <= 0 ) { return( 0 ) } ;
  9819. #myprint( "total_bytes_to_consider:$total_bytes_to_consider\n" ) ;
  9820. my $sleep = ( $total_bytes_to_consider / $maxbytespersecond ) - $time_spent ;
  9821. # the sleep must be positive
  9822. return( max( 0, $sleep ) ) ;
  9823. }
  9824. sub tests_sleep_max_bytes
  9825. {
  9826. note( 'Entering tests_sleep_max_bytes()' ) ;
  9827. ok( 0 == sleep_max_bytes( 4000, 2, undef ), 'sleep_max_bytes: maxbytespersecond == undef => sleep 0' ) ;
  9828. ok( 0 == sleep_max_bytes( 4000, 2, 0 ), 'sleep_max_bytes: maxbytespersecond = 0 => sleep 0') ;
  9829. ok( 0 == sleep_max_bytes( 4000, 2, $MINUS_ONE ), 'sleep_max_bytes: maxbytespersecond = -1 => sleep 0') ;
  9830. ok( 0 == sleep_max_bytes( 4000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max reached sharp => sleep 0') ;
  9831. ok( 2 == sleep_max_bytes( 8000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max over => sleep a little') ;
  9832. ok( 0 == sleep_max_bytes( -8000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max not reached => sleep 0') ;
  9833. ok( 0 == sleep_max_bytes( 2000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max not reached => sleep 0') ;
  9834. ok( 0 == sleep_max_bytes( -2000, 2, 1000 ), 'sleep_max_bytes: maxbytespersecond = 1k max not reached => sleep 0') ;
  9835. note( 'Leaving tests_sleep_max_bytes()' ) ;
  9836. return ;
  9837. }
  9838. sub delete_message_on_host1
  9839. {
  9840. my( $mysync, $h1_fold, $expunge, @h1_msg ) = @_ ;
  9841. if ( ! $mysync->{ delete1 } ) { return ; }
  9842. if ( ! @h1_msg ) { return ; }
  9843. delete_messages_on_any(
  9844. $mysync,
  9845. $mysync->{ acc1 },
  9846. $mysync->{ imap1 },
  9847. "Host1: $h1_fold",
  9848. $expunge,
  9849. $split1,
  9850. @h1_msg ) ;
  9851. return ;
  9852. }
  9853. sub tests_operators_and_exclam_precedence
  9854. {
  9855. note( 'Entering tests_operators_and_exclam_precedence()' ) ;
  9856. is( 1, ! 0, 'tests_operators_and_exclam_precedence: ! 0 => 1' ) ;
  9857. is( "", ! 1, 'tests_operators_and_exclam_precedence: ! 1 => ""' ) ;
  9858. is( 1, not( 0 ), 'tests_operators_and_exclam_precedence: not( 0 ) => 1' ) ;
  9859. is( "", not( 1 ), 'tests_operators_and_exclam_precedence: not( 1 ) => ""' ) ;
  9860. # I wrote those tests to avoid perlcrit "Mixed high and low-precedence booleans"
  9861. # and change sub delete_messages_on_any() but got 4 more warnings... So now commented.
  9862. #is( 0, ( ! 0 and 0 ), 'tests_operators_and_exclam_precedence: ! 0 and 0 ) => 0' ) ;
  9863. #is( 1, ( ! 0 and 1 ), 'tests_operators_and_exclam_precedence: ! 0 and 1 ) => 1' ) ;
  9864. #is( "", ( ! 1 and 0 ), 'tests_operators_and_exclam_precedence: ! 1 and 0 ) => ""' ) ;
  9865. #is( "", ( ! 1 and 1 ), 'tests_operators_and_exclam_precedence: ! 1 and 1 ) => ""' ) ;
  9866. is( 0, ( ! 0 && 0 ), 'tests_operators_and_exclam_precedence: ! 0 && 0 ) => 0' ) ;
  9867. is( 1, ( ! 0 && 1 ), 'tests_operators_and_exclam_precedence: ! 0 && 1 ) => 1' ) ;
  9868. is( "", ( ! 1 && 0 ), 'tests_operators_and_exclam_precedence: ! 1 && 0 ) => ""' ) ;
  9869. is( "", ( ! 1 && 1 ), 'tests_operators_and_exclam_precedence: ! 1 && 1 ) => ""' ) ;
  9870. is( 2, ( ! 0 && 2 ), 'tests_operators_and_exclam_precedence: ! 0 && 2 ) => 1' ) ;
  9871. note( 'Leaving tests_operators_and_exclam_precedence()' ) ;
  9872. return ;
  9873. }
  9874. sub delete_messages_on_any
  9875. {
  9876. # $acc is not used yet,
  9877. #
  9878. my( $mysync, $acc, $imap, $hostX_folder, $expunge, $split, @messages ) = @_ ;
  9879. my $expunge_message = q{} ;
  9880. my $dry_message = $mysync->{ dry_message } ;
  9881. $expunge_message = 'and expunged' if ( $expunge ) ;
  9882. # "Host1: msg "
  9883. # $imap->Debug( 1 ) ;
  9884. my @messages_to_mark_deleted = @messages ;
  9885. while ( my @messages_part = splice @messages_to_mark_deleted, 0, $split )
  9886. {
  9887. foreach my $message ( @messages_part )
  9888. {
  9889. myprint( "$hostX_folder/$message marking deleted $expunge_message $dry_message\n" ) ;
  9890. }
  9891. if ( ! $mysync->{dry} && @messages_part )
  9892. {
  9893. my $nb_deleted = $imap->delete_message( $imap->Range( @messages_part ) ) ;
  9894. if ( defined $nb_deleted )
  9895. {
  9896. # $nb_deleted is not accurate
  9897. $acc->{ nb_msg_deleted } += scalar @messages_part ;
  9898. }
  9899. else
  9900. {
  9901. my $error_imap = $imap->LastError || q{} ;
  9902. my $error = join( q{}, "$hostX_folder folder, could not delete ",
  9903. scalar @messages_part, ' messages: ', $error_imap, "\n" ) ;
  9904. errors_incr( $mysync, $error ) ;
  9905. }
  9906. }
  9907. }
  9908. if ( $expunge ) {
  9909. uidexpunge_or_expunge( $mysync, $imap, @messages ) ;
  9910. }
  9911. #$imap->Debug( 0 ) ;
  9912. return ;
  9913. }
  9914. sub tests_uidexpunge_or_expunge
  9915. {
  9916. note( 'Entering tests_uidexpunge_or_expunge()' ) ;
  9917. is( undef, uidexpunge_or_expunge( ), 'uidexpunge_or_expunge: no args => undef' ) ;
  9918. my $mysync ;
  9919. is( undef, uidexpunge_or_expunge( $mysync ), 'uidexpunge_or_expunge: undef args => undef' ) ;
  9920. $mysync = {} ;
  9921. is( undef, uidexpunge_or_expunge( $mysync ), 'uidexpunge_or_expunge: arg empty => undef' ) ;
  9922. my $imap ;
  9923. is( undef, uidexpunge_or_expunge( $mysync, $imap ), 'uidexpunge_or_expunge: undef Mail-IMAPClient instance => undef' ) ;
  9924. require_ok( "Test::MockObject" ) ;
  9925. $imap = Test::MockObject->new( ) ;
  9926. is( undef, uidexpunge_or_expunge( $mysync, $imap ), 'uidexpunge_or_expunge: no message (1) to uidexpunge => undef' ) ;
  9927. my @messages = ( ) ;
  9928. is( undef, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: no message (2) to uidexpunge => undef' ) ;
  9929. @messages = ( '2', '1' ) ;
  9930. $imap->mock( 'uidexpunge', sub { return ; } ) ;
  9931. $imap->mock( 'expunge', sub { return ; } ) ;
  9932. is( undef, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: uidexpunge failure => expunge failure => undef' ) ;
  9933. $imap->mock( 'expunge', sub { return 1 ; } ) ;
  9934. is( 1, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: uidexpunge failure => expunge ok => 1' ) ;
  9935. $imap->mock( 'uidexpunge', sub { return 1 ; } ) ;
  9936. is( 1, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: messages to uidexpunge ok => 1' ) ;
  9937. note( 'Leaving tests_uidexpunge_or_expunge()' ) ;
  9938. return ;
  9939. }
  9940. sub uidexpunge_or_expunge
  9941. {
  9942. my $mysync = shift ;
  9943. my $imap = shift ;
  9944. my @messages = @ARG ;
  9945. if ( ! $imap ) { return ; } ;
  9946. if ( ! @messages ) { return ; } ;
  9947. # Doing uidexpunge
  9948. my @uidexpunge_result = $imap->uidexpunge( @messages ) ;
  9949. if ( @uidexpunge_result ) {
  9950. return 1 ;
  9951. }
  9952. # Failure so doing expunge
  9953. my $expunge_result = $imap->expunge( ) ;
  9954. if ( $expunge_result ) {
  9955. return 1 ;
  9956. }
  9957. # bad trip
  9958. return ;
  9959. }
  9960. sub eta_print
  9961. {
  9962. my $mysync = shift ;
  9963. if ( my $eta = eta( $mysync ) )
  9964. {
  9965. myprint( "$eta\n" ) ;
  9966. }
  9967. return ;
  9968. }
  9969. sub tests_eta
  9970. {
  9971. note( 'Entering tests_eta()' ) ;
  9972. is( q{}, eta( ), 'eta: no args => ""' ) ;
  9973. is( q{}, eta( undef ), 'eta: undef => ""' ) ;
  9974. my $mysync = {} ;
  9975. # No foldersizes
  9976. is( q{}, eta( $mysync ), 'eta: No foldersizes => ""' ) ;
  9977. $mysync->{ foldersizes } = 1 ;
  9978. $mysync->{ begin_transfer_time } = time ; # Now
  9979. $mysync->{ h1_nb_msg_processed } = 0 ;
  9980. is( "ETA: " . localtimez( time ) . " 0 s 0/0 msgs left",
  9981. eta( $mysync ),
  9982. 'eta: no args => ETA: "Now" 0 s 0/0 msgs left' ) ;
  9983. $mysync->{ h1_nb_msg_processed } = 1 ;
  9984. $mysync->{ h1_nb_msg_start } = 2 ;
  9985. is( "ETA: " . localtimez( time ) . " 0 s 1/2 msgs left",
  9986. eta( $mysync ),
  9987. 'eta: 1, 1, 2 => ETA: "Now" 0 s 1/2 msgs left' ) ;
  9988. note( 'Leaving tests_eta()' ) ;
  9989. return ;
  9990. }
  9991. sub eta
  9992. {
  9993. my( $mysync ) = shift ;
  9994. if ( ! $mysync )
  9995. {
  9996. return q{} ;
  9997. }
  9998. return( q{} ) if not $mysync->{ foldersizes } ;
  9999. my $h1_nb_msg_start = $mysync->{ h1_nb_msg_start } ;
  10000. my $h1_nb_processed = $mysync->{ h1_nb_msg_processed } ;
  10001. my $nb_msg_transferred = ( $mysync->{dry} ) ? $mysync->{ h1_nb_msg_processed } : $mysync->{ nb_msg_transferred } ;
  10002. my $time_spent = timesince( $mysync->{ begin_transfer_time } ) ;
  10003. $h1_nb_processed ||= 0 ;
  10004. $h1_nb_msg_start ||= 0 ;
  10005. $time_spent ||= 0 ;
  10006. my $time_remaining = time_remaining( $time_spent, $h1_nb_processed, $h1_nb_msg_start, $nb_msg_transferred ) ;
  10007. $mysync->{ debug } and myprint( "time_spent: $time_spent time_remaining: $time_remaining\n" ) ;
  10008. my $nb_msg_remaining = $h1_nb_msg_start - $h1_nb_processed ;
  10009. my $eta_date = localtimez( time + $time_remaining ) ;
  10010. return( mysprintf( 'ETA: %s %1.0f s %s/%s msgs left',
  10011. $eta_date, $time_remaining, $nb_msg_remaining, $h1_nb_msg_start ) ) ;
  10012. }
  10013. sub time_remaining
  10014. {
  10015. my( $my_time_spent, $h1_nb_processed, $h1_nb_msg_start, $nb_transferred ) = @_ ;
  10016. $nb_transferred ||= 1 ; # At least one is done (no division by zero)
  10017. $h1_nb_processed ||= 0 ;
  10018. $h1_nb_msg_start ||= $h1_nb_processed ;
  10019. $my_time_spent ||= 0 ;
  10020. my $time_remaining = ( $my_time_spent / $nb_transferred ) * ( $h1_nb_msg_start - $h1_nb_processed ) ;
  10021. return( $time_remaining ) ;
  10022. }
  10023. sub tests_time_remaining
  10024. {
  10025. note( 'Entering tests_time_remaining()' ) ;
  10026. # time_spent, nb_processed, nb_to_do_total, nb_transferred
  10027. is( 0, time_remaining( ), 'time_remaining: no args -> 0' ) ;
  10028. is( 0, time_remaining( 0, 0, 0, 0 ), 'time_remaining: 0, 0, 0, 0 -> 0' ) ;
  10029. is( 1, time_remaining( 1, 1, 2, 1 ), 'time_remaining: 1, 1, 2, 1 -> 1' ) ;
  10030. is( 1, time_remaining( 9, 9, 10, 9 ), 'time_remaining: 9, 9, 10, 9 -> 1' ) ;
  10031. is( 9, time_remaining( 1, 1, 10, 1 ), 'time_remaining: 1, 1, 10, 1 -> 9' ) ;
  10032. is( 5, time_remaining( 5, 5, 10, 5 ), 'time_remaining: 5, 5, 10, 5 -> 5' ) ;
  10033. is( 25, time_remaining( 5, 5, 10, 0 ), 'time_remaining: 5, 5, 10, 0 -> ( 5 / 1 ) * ( 10 - 5) = 25' ) ;
  10034. is( 25, time_remaining( 5, 5, 10, 1 ), 'time_remaining: 5, 5, 10, 1 -> ( 5 / 1 ) * ( 10 - 5) = 25' ) ;
  10035. note( 'Leaving tests_time_remaining()' ) ;
  10036. return ;
  10037. }
  10038. sub cache_map
  10039. {
  10040. my ( $cache_files_ref, $h1_msgs_ref, $h2_msgs_ref ) = @_;
  10041. my ( %map1_2, %map2_1, %done2 ) ;
  10042. my $h1_msgs_hash_ref = { } ;
  10043. my $h2_msgs_hash_ref = { } ;
  10044. @{ $h1_msgs_hash_ref }{ @{ $h1_msgs_ref } } = ( ) ;
  10045. @{ $h2_msgs_hash_ref }{ @{ $h2_msgs_ref } } = ( ) ;
  10046. foreach my $file ( sort @{ $cache_files_ref } ) {
  10047. $debugcache and myprint( "C12: $file\n" ) ;
  10048. ( $uid1, $uid2 ) = match_a_cache_file( $file ) ;
  10049. if ( exists( $h1_msgs_hash_ref->{ defined $uid1 ? $uid1 : q{} } )
  10050. and exists( $h2_msgs_hash_ref->{ defined $uid2 ? $uid2 : q{} } ) ) {
  10051. # keep only the greatest uid2
  10052. # 130_2301 and
  10053. # 130_231 => keep only 130 -> 2301
  10054. # keep only the greatest uid1
  10055. # 1601_260 and
  10056. # 161_260 => keep only 1601 -> 260
  10057. my $max_uid2 = max( $uid2, $map1_2{ $uid1 } || $MINUS_ONE ) ;
  10058. if ( exists $done2{ $max_uid2 } ) {
  10059. if ( $done2{ $max_uid2 } < $uid1 ) {
  10060. $map1_2{ $uid1 } = $max_uid2 ;
  10061. delete $map1_2{ $done2{ $max_uid2 } } ;
  10062. $done2{ $max_uid2 } = $uid1 ;
  10063. }
  10064. }else{
  10065. $map1_2{ $uid1 } = $max_uid2 ;
  10066. $done2{ $max_uid2 } = $uid1 ;
  10067. }
  10068. };
  10069. }
  10070. %map2_1 = reverse %map1_2 ;
  10071. return( \%map1_2, \%map2_1) ;
  10072. }
  10073. sub tests_cache_map
  10074. {
  10075. note( 'Entering tests_cache_map()' ) ;
  10076. #$debugcache = 1 ;
  10077. my @cache_files = qw (
  10078. 100_200
  10079. 101_201
  10080. 120_220
  10081. 142_242
  10082. 143_243
  10083. 177_277
  10084. 177_278
  10085. 177_279
  10086. 155_255
  10087. 180_280
  10088. 181_280
  10089. 182_280
  10090. 130_231
  10091. 130_2301
  10092. 161_260
  10093. 1601_260
  10094. ) ;
  10095. my $msgs_1 = [120, 142, 143, 144, 161, 1601, 177, 182, 130 ];
  10096. my $msgs_2 = [ 242, 243, 260, 299, 377, 279, 255, 280, 231, 2301 ];
  10097. my( $c12, $c21 ) ;
  10098. ok( ( $c12, $c21 ) = cache_map( \@cache_files, $msgs_1, $msgs_2 ), 'cache_map: 02' );
  10099. my $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
  10100. my $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
  10101. ok( 0 == compare_lists( [ 130, 142, 143, 177, 182, 1601 ], $a1 ), 'cache_map: 03' );
  10102. ok( 0 == compare_lists( [ 242, 243, 260, 279, 280, 2301 ], $a2 ), 'cache_map: 04' );
  10103. ok( ! $c12->{161}, 'cache_map: ! 161 -> 260' );
  10104. ok( 260 == $c12->{1601}, 'cache_map: 1601 -> 260' );
  10105. ok( 2301 == $c12->{130}, 'cache_map: 130 -> 2301' );
  10106. #myprint( $c12->{1601}, "\n" ) ;
  10107. note( 'Leaving tests_cache_map()' ) ;
  10108. return ;
  10109. }
  10110. sub cache_dir_fix
  10111. {
  10112. my $cache_dir = shift ;
  10113. $cache_dir =~ s/([;<>\*\|`&\$!#\(\)\[\]\{\}:'"\\])/\\$1/xg ;
  10114. #myprint( "cache_dir_fix: $cache_dir\n" ) ;
  10115. return( $cache_dir ) ;
  10116. }
  10117. sub tests_cache_dir_fix
  10118. {
  10119. note( 'Entering tests_cache_dir_fix()' ) ;
  10120. ok( 'lalala' eq cache_dir_fix('lalala'), 'cache_dir_fix: lalala -> lalala' );
  10121. ok( 'ii\\\\ii' eq cache_dir_fix('ii\ii'), 'cache_dir_fix: ii\ii -> ii\\\\ii' );
  10122. ok( 'ii@ii' eq cache_dir_fix('ii@ii'), 'cache_dir_fix: ii@ii -> ii@ii' );
  10123. ok( 'ii@ii\\:ii' eq cache_dir_fix('ii@ii:ii'), 'cache_dir_fix: ii@ii:ii -> ii@ii\\:ii' );
  10124. ok( 'i\\\\i\\\\ii' eq cache_dir_fix('i\i\ii'), 'cache_dir_fix: i\i\ii -> i\\\\i\\\\ii' );
  10125. ok( 'i\\\\ii' eq cache_dir_fix('i\\ii'), 'cache_dir_fix: i\\ii -> i\\\\\\\\ii' );
  10126. ok( '\\\\ ' eq cache_dir_fix('\\ '), 'cache_dir_fix: \\ -> \\\\\ ' );
  10127. ok( '\\\\ ' eq cache_dir_fix('\ '), 'cache_dir_fix: \ -> \\\\\ ' );
  10128. ok( '\[bracket\]' eq cache_dir_fix('[bracket]'), 'cache_dir_fix: [bracket] -> \[bracket\]' );
  10129. note( 'Leaving tests_cache_dir_fix()' ) ;
  10130. return ;
  10131. }
  10132. sub cache_dir_fix_win
  10133. {
  10134. my $cache_dir = shift ;
  10135. $cache_dir =~ s/(\[|\])/[$1]/xg ;
  10136. #myprint( "cache_dir_fix_win: $cache_dir\n" ) ;
  10137. return( $cache_dir ) ;
  10138. }
  10139. sub tests_cache_dir_fix_win
  10140. {
  10141. note( 'Entering tests_cache_dir_fix_win()' ) ;
  10142. ok( 'lalala' eq cache_dir_fix_win('lalala'), 'cache_dir_fix_win: lalala -> lalala' );
  10143. ok( '[[]bracket[]]' eq cache_dir_fix_win('[bracket]'), 'cache_dir_fix_win: [bracket] -> [[]bracket[]]' );
  10144. note( 'Leaving tests_cache_dir_fix_win()' ) ;
  10145. return ;
  10146. }
  10147. sub get_cache
  10148. {
  10149. my ( $cache_dir, $h1_msgs_ref, $h2_msgs_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) = @_;
  10150. $debugcache and myprint( "Entering get_cache\n" ) ;
  10151. -d $cache_dir or return( undef ); # exit if cache directory doesn't exist
  10152. $debugcache and myprint( "cache_dir : $cache_dir\n" ) ;
  10153. if ( 'MSWin32' ne $OSNAME ) {
  10154. $cache_dir = cache_dir_fix( $cache_dir ) ;
  10155. }else{
  10156. $cache_dir = cache_dir_fix_win( $cache_dir ) ;
  10157. }
  10158. $debugcache and myprint( "cache_dir_fix: $cache_dir\n" ) ;
  10159. my @cache_files = bsd_glob( "$cache_dir/*" ) ;
  10160. #$debugcache and myprint( "cache_files: [@cache_files]\n" ) ;
  10161. $debugcache and myprint( 'cache_files: ', scalar @cache_files , " files found\n" ) ;
  10162. my( $cache_1_2_ref, $cache_2_1_ref )
  10163. = cache_map( \@cache_files, $h1_msgs_ref, $h2_msgs_ref ) ;
  10164. clean_cache( \@cache_files, $cache_1_2_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) ;
  10165. $debugcache and myprint( "Exiting get_cache\n" ) ;
  10166. return( $cache_1_2_ref, $cache_2_1_ref ) ;
  10167. }
  10168. sub tests_get_cache
  10169. {
  10170. note( 'Entering tests_get_cache()' ) ;
  10171. ok( not( get_cache('/cache_no_exist') ), 'get_cache: /cache_no_exist' );
  10172. ok( ( not -d 'W/tmp/cache/F1/F2' or rmtree( 'W/tmp/cache/F1/F2' ) ), 'get_cache: rmtree W/tmp/cache/F1/F2' ) ;
  10173. ok( mkpath( 'W/tmp/cache/F1/F2' ), 'get_cache: mkpath W/tmp/cache/F1/F2' ) ;
  10174. my @test_files_cache = ( qw(
  10175. W/tmp/cache/F1/F2/100_200
  10176. W/tmp/cache/F1/F2/101_201
  10177. W/tmp/cache/F1/F2/120_220
  10178. W/tmp/cache/F1/F2/142_242
  10179. W/tmp/cache/F1/F2/143_243
  10180. W/tmp/cache/F1/F2/177_277
  10181. W/tmp/cache/F1/F2/177_377
  10182. W/tmp/cache/F1/F2/177_777
  10183. W/tmp/cache/F1/F2/155_255
  10184. ) ) ;
  10185. ok( touch( @test_files_cache ), 'get_cache: touch W/tmp/cache/F1/F2/...' ) ;
  10186. # on cache: 100_200 101_201 142_242 143_243 177_277 177_377 177_777 155_255
  10187. # on live:
  10188. my $msgs_1 = [120, 142, 143, 144, 177 ];
  10189. my $msgs_2 = [ 242, 243, 299, 377, 777, 255 ];
  10190. my $msgs_all_1 = { 120 => 0, 142 => 0, 143 => 0, 144 => 0, 177 => 0 } ;
  10191. my $msgs_all_2 = { 242 => 0, 243 => 0, 299 => 0, 377 => 0, 777 => 0, 255 => 0 } ;
  10192. my( $c12, $c21 ) ;
  10193. ok( ( $c12, $c21 ) = get_cache( 'W/tmp/cache/F1/F2', $msgs_1, $msgs_2, $msgs_all_1, $msgs_all_2 ), 'get_cache: 02' );
  10194. my $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
  10195. my $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
  10196. ok( 0 == compare_lists( [ 142, 143, 177 ], $a1 ), 'get_cache: 03' );
  10197. ok( 0 == compare_lists( [ 242, 243, 777 ], $a2 ), 'get_cache: 04' );
  10198. ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 142_242');
  10199. ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 143_243');
  10200. ok( ! -f 'W/tmp/cache/F1/F2/100_200', 'get_cache: file removed 100_200');
  10201. ok( ! -f 'W/tmp/cache/F1/F2/101_201', 'get_cache: file removed 101_201');
  10202. # test clean_cache executed
  10203. $maxage = 2 ;
  10204. ok( touch(@test_files_cache), 'get_cache: touch W/tmp/cache/F1/F2/...' ) ;
  10205. ok( ( $c12, $c21 ) = get_cache('W/tmp/cache/F1/F2', $msgs_1, $msgs_2, $msgs_all_1, $msgs_all_2 ), 'get_cache: 02' );
  10206. ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 142_242');
  10207. ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 143_243');
  10208. ok( ! -f 'W/tmp/cache/F1/F2/100_200', 'get_cache: file NOT removed 100_200');
  10209. ok( ! -f 'W/tmp/cache/F1/F2/101_201', 'get_cache: file NOT removed 101_201');
  10210. # strange files
  10211. #$debugcache = 1 ;
  10212. $maxage = undef ;
  10213. ok( ( not -d 'W/tmp/cache/rr\uee' or rmtree( 'W/tmp/cache/rr\uee' )), 'get_cache: rmtree W/tmp/cache/rr\uee' ) ;
  10214. ok( mkpath( 'W/tmp/cache/rr\uee' ), 'get_cache: mkpath W/tmp/cache/rr\uee' ) ;
  10215. @test_files_cache = ( qw(
  10216. W/tmp/cache/rr\uee/100_200
  10217. W/tmp/cache/rr\uee/101_201
  10218. W/tmp/cache/rr\uee/120_220
  10219. W/tmp/cache/rr\uee/142_242
  10220. W/tmp/cache/rr\uee/143_243
  10221. W/tmp/cache/rr\uee/177_277
  10222. W/tmp/cache/rr\uee/177_377
  10223. W/tmp/cache/rr\uee/177_777
  10224. W/tmp/cache/rr\uee/155_255
  10225. ) ) ;
  10226. ok( touch(@test_files_cache), 'get_cache: touch strange W/tmp/cache/...' ) ;
  10227. # on cache: 100_200 101_201 142_242 143_243 177_277 177_377 177_777 155_255
  10228. # on live:
  10229. $msgs_1 = [120, 142, 143, 144, 177 ] ;
  10230. $msgs_2 = [ 242, 243, 299, 377, 777, 255 ] ;
  10231. $msgs_all_1 = { 120 => q{}, 142 => q{}, 143 => q{}, 144 => q{}, 177 => q{} } ;
  10232. $msgs_all_2 = { 242 => q{}, 243 => q{}, 299 => q{}, 377 => q{}, 777 => q{}, 255 => q{} } ;
  10233. 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' );
  10234. $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
  10235. $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
  10236. ok( 0 == compare_lists( [ 142, 143, 177 ], $a1 ), 'get_cache: strange path 03' );
  10237. ok( 0 == compare_lists( [ 242, 243, 777 ], $a2 ), 'get_cache: strange path 04' );
  10238. ok( -f 'W/tmp/cache/rr\uee/142_242', 'get_cache: strange path file kept 142_242');
  10239. ok( -f 'W/tmp/cache/rr\uee/142_242', 'get_cache: strange path file kept 143_243');
  10240. ok( ! -f 'W/tmp/cache/rr\uee/100_200', 'get_cache: strange path file removed 100_200');
  10241. ok( ! -f 'W/tmp/cache/rr\uee/101_201', 'get_cache: strange path file removed 101_201');
  10242. note( 'Leaving tests_get_cache()' ) ;
  10243. return ;
  10244. }
  10245. sub match_a_cache_file
  10246. {
  10247. my $file = shift ;
  10248. my ( $cache_uid1, $cache_uid2 ) ;
  10249. return( ( undef, undef ) ) if ( ! $file ) ;
  10250. if ( $file =~ m{(?:^|/)(\d+)_(\d+)$}x ) {
  10251. $cache_uid1 = $1 ;
  10252. $cache_uid2 = $2 ;
  10253. }
  10254. return( $cache_uid1, $cache_uid2 ) ;
  10255. }
  10256. sub tests_match_a_cache_file
  10257. {
  10258. note( 'Entering tests_match_a_cache_file()' ) ;
  10259. my ( $tuid1, $tuid2 ) ;
  10260. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( ), 'match_a_cache_file: no arg' ) ;
  10261. ok( ! defined $tuid1 , 'match_a_cache_file: no arg 1' ) ;
  10262. ok( ! defined $tuid2 , 'match_a_cache_file: no arg 2' ) ;
  10263. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( q{} ), 'match_a_cache_file: empty arg' ) ;
  10264. ok( ! defined $tuid1 , 'match_a_cache_file: empty arg 1' ) ;
  10265. ok( ! defined $tuid2 , 'match_a_cache_file: empty arg 2' ) ;
  10266. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '000_000' ), 'match_a_cache_file: 000_000' ) ;
  10267. ok( '000' eq $tuid1, 'match_a_cache_file: 000_000 1' ) ;
  10268. ok( '000' eq $tuid2, 'match_a_cache_file: 000_000 2' ) ;
  10269. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '123_456' ), 'match_a_cache_file: 123_456' ) ;
  10270. ok( '123' eq $tuid1, 'match_a_cache_file: 123_456 1' ) ;
  10271. ok( '456' eq $tuid2, 'match_a_cache_file: 123_456 2' ) ;
  10272. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '/tmp/truc/123_456' ), 'match_a_cache_file: /tmp/truc/123_456' ) ;
  10273. ok( '123' eq $tuid1, 'match_a_cache_file: /tmp/truc/123_456 1' ) ;
  10274. ok( '456' eq $tuid2, 'match_a_cache_file: /tmp/truc/123_456 2' ) ;
  10275. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '/lala123_456' ), 'match_a_cache_file: NO /lala123_456' ) ;
  10276. ok( ! $tuid1, 'match_a_cache_file: /lala123_456 1' ) ;
  10277. ok( ! $tuid2, 'match_a_cache_file: /lala123_456 2' ) ;
  10278. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( 'la123_456' ), 'match_a_cache_file: NO la123_456' ) ;
  10279. ok( ! $tuid1, 'match_a_cache_file: la123_456 1' ) ;
  10280. ok( ! $tuid2, 'match_a_cache_file: la123_456 2' ) ;
  10281. note( 'Leaving tests_match_a_cache_file()' ) ;
  10282. return ;
  10283. }
  10284. sub clean_cache
  10285. {
  10286. my ( $cache_files_ref, $cache_1_2_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) = @_ ;
  10287. $debugcache and myprint( "Entering clean_cache\n" ) ;
  10288. $debugcache and myprint( map { "$_ -> " . $cache_1_2_ref->{ $_ } . "\n" } keys %{ $cache_1_2_ref } ) ;
  10289. foreach my $file ( @{ $cache_files_ref } ) {
  10290. $debugcache and myprint( "$file\n" ) ;
  10291. my ( $cache_uid1, $cache_uid2 ) = match_a_cache_file( $file ) ;
  10292. $debugcache and myprint( "u1: $cache_uid1 u2: $cache_uid2 c12: ", $cache_1_2_ref->{ $cache_uid1 } || q{}, "\n") ;
  10293. # or ( ! exists( $cache_1_2_ref->{ $cache_uid1 } ) )
  10294. # or ( ! ( $cache_uid2 == $cache_1_2_ref->{ $cache_uid1 } ) )
  10295. if ( ( not defined $cache_uid1 )
  10296. or ( not defined $cache_uid2 )
  10297. or ( not exists $h1_msgs_all_hash_ref->{ $cache_uid1 } )
  10298. or ( not exists $h2_msgs_all_hash_ref->{ $cache_uid2 } )
  10299. ) {
  10300. $debugcache and myprint( "remove $file\n" ) ;
  10301. unlink $file or myprint( "$OS_ERROR" ) ;
  10302. }
  10303. }
  10304. $debugcache and myprint( "Exiting clean_cache\n" ) ;
  10305. return( 1 ) ;
  10306. }
  10307. sub tests_clean_cache
  10308. {
  10309. note( 'Entering tests_clean_cache()' ) ;
  10310. ok( ( not -d 'W/tmp/cache/G1/G2' or rmtree( 'W/tmp/cache/G1/G2' )), 'clean_cache: rmtree W/tmp/cache/G1/G2' ) ;
  10311. ok( mkpath( 'W/tmp/cache/G1/G2' ), 'clean_cache: mkpath W/tmp/cache/G1/G2' ) ;
  10312. my @test_files_cache = ( qw(
  10313. W/tmp/cache/G1/G2/100_200
  10314. W/tmp/cache/G1/G2/101_201
  10315. W/tmp/cache/G1/G2/120_220
  10316. W/tmp/cache/G1/G2/142_242
  10317. W/tmp/cache/G1/G2/143_243
  10318. W/tmp/cache/G1/G2/177_277
  10319. W/tmp/cache/G1/G2/177_377
  10320. W/tmp/cache/G1/G2/177_777
  10321. W/tmp/cache/G1/G2/155_255
  10322. ) ) ;
  10323. ok( touch(@test_files_cache), 'clean_cache: touch W/tmp/cache/G1/G2/...' ) ;
  10324. ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache: 100_200 before' );
  10325. ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache: 142_242 before' );
  10326. ok( -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache: 177_277 before' );
  10327. ok( -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache: 177_377 before' );
  10328. ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache: 177_777 before' );
  10329. ok( -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache: 155_255 before' );
  10330. my $cache = {
  10331. 142 => 242,
  10332. 177 => 777,
  10333. } ;
  10334. my $all_1 = {
  10335. 142 => q{},
  10336. 177 => q{},
  10337. } ;
  10338. my $all_2 = {
  10339. 200 => q{},
  10340. 242 => q{},
  10341. 777 => q{},
  10342. } ;
  10343. ok( clean_cache( \@test_files_cache, $cache, $all_1, $all_2 ), 'clean_cache: ' ) ;
  10344. ok( ! -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache: 100_200 after' );
  10345. ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache: 142_242 after' );
  10346. ok( ! -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache: 177_277 after' );
  10347. ok( ! -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache: 177_377 after' );
  10348. ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache: 177_777 after' );
  10349. ok( ! -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache: 155_255 after' );
  10350. note( 'Leaving tests_clean_cache()' ) ;
  10351. return ;
  10352. }
  10353. sub tests_clean_cache_2
  10354. {
  10355. note( 'Entering tests_clean_cache_2()' ) ;
  10356. ok( ( not -d 'W/tmp/cache/G1/G2' or rmtree( 'W/tmp/cache/G1/G2' )), 'clean_cache_2: rmtree W/tmp/cache/G1/G2' ) ;
  10357. ok( mkpath( 'W/tmp/cache/G1/G2' ), 'clean_cache_2: mkpath W/tmp/cache/G1/G2' ) ;
  10358. my @test_files_cache = ( qw(
  10359. W/tmp/cache/G1/G2/100_200
  10360. W/tmp/cache/G1/G2/101_201
  10361. W/tmp/cache/G1/G2/120_220
  10362. W/tmp/cache/G1/G2/142_242
  10363. W/tmp/cache/G1/G2/143_243
  10364. W/tmp/cache/G1/G2/177_277
  10365. W/tmp/cache/G1/G2/177_377
  10366. W/tmp/cache/G1/G2/177_777
  10367. W/tmp/cache/G1/G2/155_255
  10368. ) ) ;
  10369. ok( touch(@test_files_cache), 'clean_cache_2: touch W/tmp/cache/G1/G2/...' ) ;
  10370. ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache_2: 100_200 before' );
  10371. ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache_2: 142_242 before' );
  10372. ok( -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache_2: 177_277 before' );
  10373. ok( -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache_2: 177_377 before' );
  10374. ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache_2: 177_777 before' );
  10375. ok( -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache_2: 155_255 before' );
  10376. my $cache = {
  10377. 142 => 242,
  10378. 177 => 777,
  10379. } ;
  10380. my $all_1 = {
  10381. $NUMBER_100 => q{},
  10382. 142 => q{},
  10383. 177 => q{},
  10384. } ;
  10385. my $all_2 = {
  10386. 200 => q{},
  10387. 242 => q{},
  10388. 777 => q{},
  10389. } ;
  10390. ok( clean_cache( \@test_files_cache, $cache, $all_1, $all_2 ), 'clean_cache_2: ' ) ;
  10391. ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache_2: 100_200 after' );
  10392. ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache_2: 142_242 after' );
  10393. ok( ! -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache_2: 177_277 after' );
  10394. ok( ! -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache_2: 177_377 after' );
  10395. ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache_2: 177_777 after' );
  10396. ok( ! -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache_2: 155_255 after' );
  10397. note( 'Leaving tests_clean_cache_2()' ) ;
  10398. return ;
  10399. }
  10400. sub tests_mkpath
  10401. {
  10402. note( 'Entering tests_mkpath()' ) ;
  10403. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' )), 'mkpath: mkpath W/tmp/tests/' ) ;
  10404. SKIP: {
  10405. skip( 'Tests only for Unix', 10 ) if ( 'MSWin32' eq $OSNAME ) ;
  10406. my $long_path_unix = '123456789/' x 30 ;
  10407. ok( ( -d "W/tmp/tests/long/$long_path_unix" or mkpath( "W/tmp/tests/long/$long_path_unix" ) ), 'mkpath: mkpath 300 char' ) ;
  10408. ok( -d "W/tmp/tests/long/$long_path_unix", 'mkpath: mkpath > 300 char verified' ) ;
  10409. ok( ( -d "W/tmp/tests/long/$long_path_unix" and rmtree( 'W/tmp/tests/long/' ) ), 'mkpath: rmtree 300 char' ) ;
  10410. ok( ! -d "W/tmp/tests/long/$long_path_unix", 'mkpath: rmtree 300 char verified' ) ;
  10411. ok( ( -d 'W/tmp/tests/trailing_dots...' or mkpath( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: mkpath trailing_dots...' ) ;
  10412. ok( -d 'W/tmp/tests/trailing_dots...', 'mkpath: mkpath trailing_dots... verified' ) ;
  10413. ok( ( -d 'W/tmp/tests/trailing_dots...' and rmtree( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: rmtree trailing_dots...' ) ;
  10414. ok( ! -d 'W/tmp/tests/trailing_dots...', 'mkpath: rmtree trailing_dots... verified' ) ;
  10415. eval { ok( 1 / 0, 'mkpath: divide by 0' ) ; } or ok( 1, 'mkpath: can not divide by 0' ) ;
  10416. ok( 1, 'mkpath: still alive' ) ;
  10417. } ;
  10418. SKIP: {
  10419. skip( 'Tests only for MSWin32', 13 ) if ( 'MSWin32' ne $OSNAME ) ;
  10420. my $long_path_2_prefix = ".\\imapsync_tests" || '\\\?\\E:\\TEMP\\imapsync_tests' ;
  10421. myprint( "long_path_2_prefix: $long_path_2_prefix\n" ) ;
  10422. my $long_path_100 = $long_path_2_prefix . '\\' . '123456789\\' x 10 . 'END' ;
  10423. my $long_path_300 = $long_path_2_prefix . '\\' . '123456789\\' x 30 . 'END' ;
  10424. #myprint( "$long_path_100\n" ) ;
  10425. ok( ( -d $long_path_2_prefix or mkpath( $long_path_2_prefix ) ), 'mkpath: -d mkpath small path' ) ;
  10426. ok( ( -d $long_path_2_prefix ), 'mkpath: -d mkpath small path done' ) ;
  10427. ok( ( -d $long_path_100 or mkpath( $long_path_100 ) ), 'mkpath: mkpath > 100 char' ) ;
  10428. ok( ( -d $long_path_100 ), 'mkpath: -d mkpath > 200 char done' ) ;
  10429. ok( ( -d $long_path_2_prefix and rmtree( $long_path_2_prefix ) ), 'mkpath: rmtree > 100 char' ) ;
  10430. ok( (! -d $long_path_2_prefix ), 'mkpath: ! -d rmtree done' ) ;
  10431. # Without the eval the following mkpath 300 just kill the whole process without a whisper
  10432. #myprint( "$long_path_300\n" ) ;
  10433. eval { ok( ( -d $long_path_300 or mkpath( $long_path_300 ) ), 'mkpath: create a path with 300 characters' ) ; }
  10434. or ok( 1, 'mkpath: can not create a path with 300 characters' ) ;
  10435. ok( ( ( ! -d $long_path_300 ) or -d $long_path_300 and rmtree( $long_path_300 ) ), 'mkpath: rmtree the 300 character path' ) ;
  10436. ok( 1, 'mkpath: still alive' ) ;
  10437. ok( ( -d 'W/tmp/tests/trailing_dots...' or mkpath( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: mkpath trailing_dots...' ) ;
  10438. ok( -d 'W/tmp/tests/trailing_dots...', 'mkpath: mkpath trailing_dots... verified' ) ;
  10439. ok( ( -d 'W/tmp/tests/trailing_dots...' and rmtree( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: rmtree trailing_dots...' ) ;
  10440. ok( ! -d 'W/tmp/tests/trailing_dots...', 'mkpath: rmtree trailing_dots... verified' ) ;
  10441. } ;
  10442. note( 'Leaving tests_mkpath()' ) ;
  10443. # Keep this because of the eval used by the caller (failed badly?)
  10444. return 1 ;
  10445. }
  10446. sub tests_touch
  10447. {
  10448. note( 'Entering tests_touch()' ) ;
  10449. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' )), 'touch: mkpath W/tmp/tests/' ) ;
  10450. ok( 1 == touch( 'W/tmp/tests/lala'), 'touch: W/tmp/tests/lala') ;
  10451. ok( 1 == touch( 'W/tmp/tests/\y'), 'touch: W/tmp/tests/\y') ;
  10452. ok( 0 == touch( '/no/no/no/aaa'), 'touch: not /aaa') ;
  10453. ok( 1 == touch( 'W/tmp/tests/lili', 'W/tmp/tests/lolo'), 'touch: 2 files') ;
  10454. ok( 0 == touch( 'W/tmp/tests/\y', '/no/no/aaa'), 'touch: 2 files, 1 fails' ) ;
  10455. note( 'Leaving tests_touch()' ) ;
  10456. return ;
  10457. }
  10458. sub touch
  10459. {
  10460. my @files = @_ ;
  10461. my $failures = 0 ;
  10462. foreach my $file ( @files ) {
  10463. my $fh = IO::File->new ;
  10464. if ( $fh->open(">> $file" ) ) {
  10465. $fh->close ;
  10466. }else{
  10467. myprint( "Could not open file $file in write/append mode\n" ) ;
  10468. $failures++ ;
  10469. }
  10470. }
  10471. return( ! $failures );
  10472. }
  10473. sub tests_tmpdir_has_colon_bug
  10474. {
  10475. note( 'Entering tests_tmpdir_has_colon_bug()' ) ;
  10476. ok( 0 == tmpdir_has_colon_bug( q{} ), 'tmpdir_has_colon_bug: ' ) ;
  10477. ok( 0 == tmpdir_has_colon_bug( '/tmp' ), 'tmpdir_has_colon_bug: /tmp' ) ;
  10478. ok( 1 == tmpdir_has_colon_bug( 'C:' ), 'tmpdir_has_colon_bug: C:' ) ;
  10479. ok( 1 == tmpdir_has_colon_bug( 'C:\temp' ), 'tmpdir_has_colon_bug: C:\temp' ) ;
  10480. note( 'Leaving tests_tmpdir_has_colon_bug()' ) ;
  10481. return ;
  10482. }
  10483. sub tmpdir_has_colon_bug
  10484. {
  10485. my $path = shift ;
  10486. my $path_filtered = filter_forbidden_characters( $path ) ;
  10487. if ( $path_filtered ne $path ) {
  10488. ( -d $path_filtered ) and myprint( "Path $path was previously mistakely changed to $path_filtered\n" ) ;
  10489. return( 1 ) ;
  10490. }
  10491. return( 0 ) ;
  10492. }
  10493. sub tmpdir_fix_colon_bug
  10494. {
  10495. my $mysync = shift ;
  10496. my $err = 0 ;
  10497. if ( not (-d $mysync->{ tmpdir } and -r _ and -w _) ) {
  10498. myprint( "tmpdir $mysync->{ tmpdir } is not valid\n" ) ;
  10499. return( 0 ) ;
  10500. }
  10501. my $cachedir_new = "$mysync->{ tmpdir }/imapsync_cache" ;
  10502. if ( not tmpdir_has_colon_bug( $cachedir_new ) ) { return( 0 ) } ;
  10503. # check if old cache directory already exists
  10504. my $cachedir_old = filter_forbidden_characters( $cachedir_new ) ;
  10505. if ( not ( -d $cachedir_old ) ) {
  10506. myprint( "Old cache directory $cachedir_new no exists, nothing to do\n" ) ;
  10507. return( 1 ) ;
  10508. }
  10509. # check if new cache directory already exists
  10510. if ( -d $cachedir_new ) {
  10511. myprint( "New fixed cache directory $cachedir_new already exists, not moving the old one $cachedir_old. Fix this manually.\n" ) ;
  10512. return( 0 ) ;
  10513. }else{
  10514. # move the old one to the new place
  10515. myprint( "Moving $cachedir_old to $cachedir_new Do not interrupt this task.\n" ) ;
  10516. File::Copy::Recursive::rmove( $cachedir_old, $cachedir_new )
  10517. or do {
  10518. myprint( "Could not move $cachedir_old to $cachedir_new\n" ) ;
  10519. $err++ ;
  10520. } ;
  10521. # check it succeeded
  10522. if ( -d $cachedir_new and -r _ and -w _ ) {
  10523. myprint( "New fixed cache directory $cachedir_new ok\n" ) ;
  10524. }else{
  10525. myprint( "New fixed cache directory $cachedir_new does not exist\n" ) ;
  10526. $err++ ;
  10527. }
  10528. if ( -d $cachedir_old ) {
  10529. myprint( "Old cache directory $cachedir_old still exists\n" ) ;
  10530. $err++ ;
  10531. }else{
  10532. myprint( "Old cache directory $cachedir_old successfully moved\n" ) ;
  10533. }
  10534. }
  10535. return( not $err ) ;
  10536. }
  10537. sub tests_cache_folder
  10538. {
  10539. note( 'Entering tests_cache_folder()' ) ;
  10540. ok( '/path/fold1/fold2' eq cache_folder( q{}, '/path', 'fold1', 'fold2'), 'cache_folder: /path, fold1, fold2 -> /path/fold1/fold2' ) ;
  10541. ok( '/pa_th/fold1/fold2' eq cache_folder( q{}, '/pa*th', 'fold1', 'fold2'), 'cache_folder: /pa*th, fold1, fold2 -> /path/fold1/fold2' ) ;
  10542. 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' ) ;
  10543. ok( 'D:/path/fold1/fold2' eq cache_folder( 'D:', '/path', 'fold1', 'fold2'), 'cache_folder: /path, fold1, fold2 -> /path/fold1/fold2' ) ;
  10544. ok( 'D:/pa_th/fold1/fold2' eq cache_folder( 'D:', '/pa*th', 'fold1', 'fold2'), 'cache_folder: /pa*th, fold1, fold2 -> /path/fold1/fold2' ) ;
  10545. 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' ) ;
  10546. ok( '//' eq cache_folder( q{}, q{}, q{}, q{}), 'cache_folder: -> //' ) ;
  10547. ok( '//_______' eq cache_folder( q{}, q{}, q{}, '*|?:"<>'), 'cache_folder: *|?:"<> -> //_______' ) ;
  10548. note( 'Leaving tests_cache_folder()' ) ;
  10549. return ;
  10550. }
  10551. sub cache_folder
  10552. {
  10553. my( $cache_base, $cache_dir, $h1_fold, $h2_fold ) = @_ ;
  10554. my $sep_1 = $sync->{ h1_sep } || '/';
  10555. my $sep_2 = $sync->{ h2_sep } || '/';
  10556. #myprint( "$cache_dir h1_fold $h1_fold sep1 $sep_1 h2_fold $h2_fold sep2 $sep_2\n" ) ;
  10557. $h1_fold = convert_sep_to_slash( $h1_fold, $sep_1 ) ;
  10558. $h2_fold = convert_sep_to_slash( $h2_fold, $sep_2 ) ;
  10559. my $cache_folder = "$cache_base" . filter_forbidden_characters( "$cache_dir/$h1_fold/$h2_fold" ) ;
  10560. #myprint( "cache_folder [$cache_folder]\n" ) ;
  10561. return( $cache_folder ) ;
  10562. }
  10563. sub tests_filter_forbidden_characters
  10564. {
  10565. note( 'Entering tests_filter_forbidden_characters()' ) ;
  10566. is( undef , filter_forbidden_characters( ), 'filter_forbidden_characters: no args -> undef' ) ;
  10567. is( 'a_b' , filter_forbidden_characters( 'a_b' ), 'filter_forbidden_characters: a_b -> a_b' ) ;
  10568. is( 'a_b' , filter_forbidden_characters( 'a*b' ), 'filter_forbidden_characters: a*b -> a_b' ) ;
  10569. is( 'a_b' , filter_forbidden_characters( 'a|b' ), 'filter_forbidden_characters: a|b -> a_b' ) ;
  10570. is( 'a_b' , filter_forbidden_characters( 'a?b' ), 'filter_forbidden_characters: a?b -> a_b' ) ;
  10571. is( 'a________b', filter_forbidden_characters( q{a*|?:"<>'b} ), q{filter_forbidden_characters: a*|?:"<>'b -> a________b} ) ;
  10572. is( 'a_b_' , filter_forbidden_characters( 'a b ' ), 'filter_forbidden_characters: "a b " -> "a_b_"' ) ;
  10573. is( 'a_b' , filter_forbidden_characters( "a\tb" ), 'filter_forbidden_characters: a\tb -> a_b' ) ;
  10574. is( "a_b" , filter_forbidden_characters( "a\rb" ), 'filter_forbidden_characters: a\rb -> a_b' ) ;
  10575. is( "a_b" , filter_forbidden_characters( "a\nb" ), 'filter_forbidden_characters: a\nb -> a_b' ) ;
  10576. is( "a_b" , filter_forbidden_characters( "a\\b" ), 'filter_forbidden_characters: a\b -> a_b' ) ;
  10577. is( 'a-b' , filter_forbidden_characters( 'a-b' ), 'filter_forbidden_characters: a-b -> a-b' ) ;
  10578. is( 'a__-__-__-__-__b' , filter_forbidden_characters( 'aé-è-à -ç-Öb' ), 'filter_forbidden_characters: aé-è-à -ç-Öb -> a__-__-__-__-__b' ) ;
  10579. is( 'abcdABCDwxyzWXYZ012789' , filter_forbidden_characters( 'abcdABCDwxyzWXYZ012789' ),
  10580. 'filter_forbidden_characters: abcdABCDwxyzWXYZ012789 -> abcdABCDwxyzWXYZ012789' ) ;
  10581. note( 'Leaving tests_filter_forbidden_characters()' ) ;
  10582. return ;
  10583. }
  10584. sub filter_forbidden_characters
  10585. {
  10586. my $string = shift ;
  10587. if ( ! defined $string ) { return ; }
  10588. $string =~ s{[\Q*|?:"<>' \E\t\r\n\\]}{_}xg ;
  10589. # replace all non-ascii and control characters by _
  10590. $string =~ s/[[:^ascii:][:cntrl:]]/_/xg ;
  10591. #myprint( "[$string]\n" ) ;
  10592. return( $string ) ;
  10593. }
  10594. sub tests_convert_sep_to_slash
  10595. {
  10596. note( 'Entering tests_convert_sep_to_slash()' ) ;
  10597. ok(q{} eq convert_sep_to_slash(q{}, '/'), 'convert_sep_to_slash: no folder');
  10598. ok('INBOX' eq convert_sep_to_slash('INBOX', '/'), 'convert_sep_to_slash: INBOX');
  10599. ok('INBOX/foo' eq convert_sep_to_slash('INBOX/foo', '/'), 'convert_sep_to_slash: INBOX/foo');
  10600. ok('INBOX/foo' eq convert_sep_to_slash('INBOX_foo', '_'), 'convert_sep_to_slash: INBOX_foo');
  10601. ok('INBOX/foo/zob' eq convert_sep_to_slash('INBOX_foo_zob', '_'), 'convert_sep_to_slash: INBOX_foo_zob');
  10602. ok('INBOX/foo' eq convert_sep_to_slash('INBOX.foo', '.'), 'convert_sep_to_slash: INBOX.foo');
  10603. ok('INBOX/foo/hi' eq convert_sep_to_slash('INBOX.foo.hi', '.'), 'convert_sep_to_slash: INBOX.foo.hi');
  10604. note( 'Leaving tests_convert_sep_to_slash()' ) ;
  10605. return ;
  10606. }
  10607. sub convert_sep_to_slash
  10608. {
  10609. my ( $folder, $sep ) = @_ ;
  10610. $folder =~ s{\Q$sep\E}{/}xg ;
  10611. return( $folder ) ;
  10612. }
  10613. sub tests_regexmess
  10614. {
  10615. note( 'Entering tests_regexmess()' ) ;
  10616. ok( 'blabla' eq regexmess( 'blabla' ), 'regexmess: no regexmess, nothing to do' ) ;
  10617. @regexmess = ( 'lalala' ) ;
  10618. ok( not( defined regexmess( 'popopo' ) ), 'regexmess: bad regex lalala' ) ;
  10619. @regexmess = ( 's/p/Z/g' ) ;
  10620. ok( 'ZoZoZo' eq regexmess( 'popopo' ), 'regexmess: s/p/Z/g' ) ;
  10621. @regexmess = ( 's{c}{C}gxms' ) ;
  10622. ok("H1: abC\nH2: Cde\n\nBody abC"
  10623. eq regexmess( "H1: abc\nH2: cde\n\nBody abc"),
  10624. 'regexmess: c->C');
  10625. @regexmess = ( 's{\AFrom\ }{From:}gxms' ) ;
  10626. ok( q{}
  10627. eq regexmess(q{}),
  10628. 'regexmess: From mbox 1 add colon blank');
  10629. ok( 'From:<tartanpion@machin.truc>'
  10630. eq regexmess('From <tartanpion@machin.truc>'),
  10631. 'regexmess: From mbox 2 add colo');
  10632. ok( "\n" . 'From <tartanpion@machin.truc>'
  10633. eq regexmess("\n" . 'From <tartanpion@machin.truc>'),
  10634. 'regexmess: From mbox 3 add colo') ;
  10635. ok( "From: zzz\n" . 'From <tartanpion@machin.truc>'
  10636. eq regexmess("From zzz\n" . 'From <tartanpion@machin.truc>'),
  10637. 'regexmess: From mbox 4 add colo') ;
  10638. @regexmess = ( 's{\AFrom\ [^\n]*(\n)?}{}gxms' ) ;
  10639. ok( q{}
  10640. eq regexmess(q{}),
  10641. 'regexmess: From mbox 1 remove, blank');
  10642. ok( q{}
  10643. eq regexmess('From <tartanpion@machin.truc>'),
  10644. 'regexmess: From mbox 2 remove');
  10645. ok( "\n" . 'From <tartanpion@machin.truc>'
  10646. eq regexmess("\n" . 'From <tartanpion@machin.truc>'),
  10647. 'regexmess: From mbox 3 remove');
  10648. #myprint( "[", regexmess("From zzz\n" . 'From <tartanpion@machin.truc>'), "]" ) ;
  10649. ok( q{} . 'From <tartanpion@machin.truc>'
  10650. eq regexmess("From zzz\n" . 'From <tartanpion@machin.truc>'),
  10651. 'regexmess: From mbox 4 remove');
  10652. is(
  10653. <<'EOM'
  10654. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10655. From:<tartanpion@machin.truc>
  10656. Hello,
  10657. Bye.
  10658. EOM
  10659. , regexmess(
  10660. <<'EOM'
  10661. From zzz
  10662. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10663. From:<tartanpion@machin.truc>
  10664. Hello,
  10665. Bye.
  10666. EOM
  10667. ), 'regexmess: From mbox 5 remove');
  10668. @regexmess = ( 's{\A((?:[^\n]+\n)+|)^Disposition-Notification-To:[^\n]*\n(\r?\n|.*\n\r?\n)}{$1$2}xms' ) ; # SUPER SUPER BEST!
  10669. ok(
  10670. <<'EOM'
  10671. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10672. From:<tartanpion@machin.truc>
  10673. Hello,
  10674. Bye.
  10675. EOM
  10676. eq regexmess(
  10677. <<'EOM'
  10678. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10679. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10680. From:<tartanpion@machin.truc>
  10681. Hello,
  10682. Bye.
  10683. EOM
  10684. ),
  10685. 'regexmess: 1 Delete header Disposition-Notification-To:');
  10686. ok(
  10687. <<'EOM'
  10688. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10689. From:<tartanpion@machin.truc>
  10690. Hello,
  10691. Bye.
  10692. EOM
  10693. eq regexmess(
  10694. <<'EOM'
  10695. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10696. From:<tartanpion@machin.truc>
  10697. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10698. Hello,
  10699. Bye.
  10700. EOM
  10701. ),
  10702. 'regexmess: 2 Delete header Disposition-Notification-To:');
  10703. ok(
  10704. <<'EOM'
  10705. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10706. From:<tartanpion@machin.truc>
  10707. Hello,
  10708. Bye.
  10709. EOM
  10710. eq regexmess(
  10711. <<'EOM'
  10712. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10713. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10714. From:<tartanpion@machin.truc>
  10715. Hello,
  10716. Bye.
  10717. EOM
  10718. ),
  10719. 'regexmess: 3 Delete header Disposition-Notification-To:');
  10720. ok(
  10721. <<'EOM'
  10722. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10723. From:<tartanpion@machin.truc>
  10724. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10725. Bye.
  10726. EOM
  10727. eq regexmess(
  10728. <<'EOM'
  10729. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10730. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10731. From:<tartanpion@machin.truc>
  10732. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10733. Bye.
  10734. EOM
  10735. ),
  10736. 'regexmess: 4 Delete header Disposition-Notification-To:');
  10737. ok(
  10738. <<'EOM'
  10739. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10740. From:<tartanpion@machin.truc>
  10741. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10742. Bye.
  10743. EOM
  10744. eq regexmess(
  10745. <<'EOM'
  10746. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10747. From:<tartanpion@machin.truc>
  10748. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10749. Bye.
  10750. EOM
  10751. ),
  10752. 'regexmess: 5 Delete header Disposition-Notification-To:');
  10753. ok(
  10754. <<'EOM'
  10755. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10756. From:<tartanpion@machin.truc>
  10757. Hello,
  10758. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10759. Bye.
  10760. EOM
  10761. eq regexmess(
  10762. <<'EOM'
  10763. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10764. From:<tartanpion@machin.truc>
  10765. Hello,
  10766. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10767. Bye.
  10768. EOM
  10769. ),
  10770. 'regexmess: 6 Delete header Disposition-Notification-To:');
  10771. ok(
  10772. <<'EOM'
  10773. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10774. From:<tartanpion@machin.truc>
  10775. Hello,
  10776. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10777. Bye.
  10778. EOM
  10779. eq regexmess(
  10780. <<'EOM'
  10781. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10782. From:<tartanpion@machin.truc>
  10783. Hello,
  10784. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10785. Bye.
  10786. EOM
  10787. ),
  10788. 'regexmess: 7 Delete header Disposition-Notification-To:');
  10789. ok(
  10790. <<'EOM'
  10791. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10792. From:<tartanpion@machin.truc>
  10793. Hello,
  10794. Bye.
  10795. EOM
  10796. eq regexmess(
  10797. <<'EOM'
  10798. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10799. From:<tartanpion@machin.truc>
  10800. Hello,
  10801. Bye.
  10802. EOM
  10803. ),
  10804. 'regexmess: 8 Delete header Disposition-Notification-To:');
  10805. ok(
  10806. <<'EOM'
  10807. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10808. From:<tartanpion@machin.truc>
  10809. Hello,
  10810. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10811. Bye.
  10812. EOM
  10813. eq regexmess(
  10814. <<'EOM'
  10815. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10816. From:<tartanpion@machin.truc>
  10817. Hello,
  10818. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10819. Bye.
  10820. EOM
  10821. ),
  10822. 'regexmess: 9 Delete header Disposition-Notification-To:');
  10823. ok(
  10824. <<'EOM'
  10825. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10826. From:<tartanpion@machin.truc>
  10827. Hello,
  10828. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10829. Bye.
  10830. EOM
  10831. eq regexmess(
  10832. <<'EOM'
  10833. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10834. From:<tartanpion@machin.truc>
  10835. Hello,
  10836. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10837. Bye.
  10838. EOM
  10839. ),
  10840. 'regexmess: 10 Delete header Disposition-Notification-To:');
  10841. ok(
  10842. <<'EOM'
  10843. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10844. From:<tartanpion@machin.truc>
  10845. Hello,
  10846. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10847. Bye.
  10848. EOM
  10849. eq regexmess(
  10850. <<'EOM'
  10851. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10852. From:<tartanpion@machin.truc>
  10853. Hello,
  10854. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10855. Bye.
  10856. EOM
  10857. ),
  10858. 'regexmess: 11 Delete header Disposition-Notification-To:');
  10859. ok(
  10860. <<'EOM'
  10861. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10862. From:<tartanpion@machin.truc>
  10863. Hello,
  10864. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10865. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10866. Bye.
  10867. EOM
  10868. eq regexmess(
  10869. <<'EOM'
  10870. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10871. From:<tartanpion@machin.truc>
  10872. Hello,
  10873. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10874. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10875. Bye.
  10876. EOM
  10877. ),
  10878. 'regexmess: 12 Delete header Disposition-Notification-To:');
  10879. @regexmess = ( 's{\A(.*?(?! ^$))^Disposition-Notification-To:(.*?)$}{$1X-Disposition-Notification-To:$2}igxms' ) ; # BAD!
  10880. @regexmess = ( 's{\A((?:[^\n]+\n)+|)(^Disposition-Notification-To:[^\n]*\n)(\r?\n|.*\n\r?\n)}{$1X-$2$3}ims' ) ;
  10881. ok(
  10882. <<'EOM'
  10883. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10884. From:<tartanpion@machin.truc>
  10885. Hello,
  10886. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10887. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10888. Bye.
  10889. EOM
  10890. eq regexmess(
  10891. <<'EOM'
  10892. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10893. From:<tartanpion@machin.truc>
  10894. Hello,
  10895. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10896. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10897. Bye.
  10898. EOM
  10899. ),
  10900. 'regexmess: 13 Delete header Disposition-Notification-To:');
  10901. ok(
  10902. <<'EOM'
  10903. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10904. X-Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10905. From:<tartanpion@machin.truc>
  10906. Hello,
  10907. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10908. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10909. Bye.
  10910. EOM
  10911. eq regexmess(
  10912. <<'EOM'
  10913. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10914. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10915. From:<tartanpion@machin.truc>
  10916. Hello,
  10917. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10918. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10919. Bye.
  10920. EOM
  10921. ),
  10922. 'regexmess: 14 Delete header Disposition-Notification-To:');
  10923. ok(
  10924. <<'EOM'
  10925. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10926. X-Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10927. From:<tartanpion@machin.truc>
  10928. Hello,
  10929. Bye.
  10930. EOM
  10931. eq regexmess(
  10932. <<'EOM'
  10933. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10934. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10935. From:<tartanpion@machin.truc>
  10936. Hello,
  10937. Bye.
  10938. EOM
  10939. ),
  10940. 'regexmess: 15 Delete header Disposition-Notification-To:');
  10941. ok(
  10942. <<'EOM'
  10943. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10944. From:<tartanpion@machin.truc>
  10945. X-Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10946. Hello,
  10947. Bye.
  10948. EOM
  10949. eq regexmess(
  10950. <<'EOM'
  10951. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10952. From:<tartanpion@machin.truc>
  10953. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10954. Hello,
  10955. Bye.
  10956. EOM
  10957. ),
  10958. 'regexmess: 16 Delete header Disposition-Notification-To:');
  10959. ok(
  10960. <<'EOM'
  10961. X-Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10962. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10963. From:<tartanpion@machin.truc>
  10964. Hello,
  10965. Bye.
  10966. EOM
  10967. eq regexmess(
  10968. <<'EOM'
  10969. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  10970. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10971. From:<tartanpion@machin.truc>
  10972. Hello,
  10973. Bye.
  10974. EOM
  10975. ),
  10976. 'regexmess: 17 Delete header Disposition-Notification-To:');
  10977. @regexmess = ( 's/.{11}\K.*//gs' ) ;
  10978. is( "0123456789\n", regexmess( "0123456789\n" x 100 ), 'regexmess: truncate whole message after 11 characters' ) ;
  10979. is( "0123456789\n", regexmess( "0123456789\n" x 100_000 ), 'regexmess: truncate whole message after 11 characters ~ 1MB' ) ;
  10980. @regexmess = ( 's/.{10000}\K.*//gs' ) ;
  10981. is( "123456789\n" x 1000, regexmess( "123456789\n" x 100_000 ), 'regexmess: truncate whole message after 10000 characters ~ 1MB' ) ;
  10982. @regexmess = ( 's/^(X-Ham-Report.*?\n)^X-/X-/sm' ) ;
  10983. is(
  10984. <<'EOM'
  10985. X-Spam-Score: -1
  10986. X-Spam-Bar: /
  10987. X-Spam-Flag: NO
  10988. Date: Sat, 10 Jul 2010 05:34:45 -0700
  10989. From:<tartanpion@machin.truc>
  10990. Hello,
  10991. Bye.
  10992. EOM
  10993. ,
  10994. regexmess(
  10995. <<'EOM'
  10996. X-Spam-Score: -1
  10997. X-Spam-Bar: /
  10998. X-Ham-Report: =?utf-8?Q?Spam_detection_software=2C_running?=
  10999. =?utf-8?Q?_on_the_system_=22ohp-ag006.int200?=
  11000. _has_NOT_identified_thi?=
  11001. =?utf-8?Q?s_incoming_email_as_spam.__The_o?=
  11002. _message_has_been_attac?=
  11003. =?utf-8?Q?hed_to_this_so_you_can_view_it_o?=
  11004. ___________________________?=
  11005. =?utf-8?Q?__author's_domain
  11006. X-Spam-Flag: NO
  11007. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11008. From:<tartanpion@machin.truc>
  11009. Hello,
  11010. Bye.
  11011. EOM
  11012. ),
  11013. 'regexmess: Delete header X-Ham-Report:');
  11014. # regex to play with Date: from the FAQ
  11015. #@regexmess = 's{\A(.*?(?! ^$))^Date:(.*?)$}{$1Date:$2\nX-Date:$2}gxms'
  11016. # Change 8bit characters in whole email to X characters
  11017. @regexmess = ( 's{[\x80-\xff]}{X}gxms' ) ;
  11018. is( 'X-8bit: kaka 1 XX kiki', regexmess('X-8bit: kaka 1 ¤ kiki'), 'regexmess: 1 Change 8bit characters in whole email to X characters');
  11019. # Same change but using tr
  11020. @regexmess = ( 'tr [\x80-\xff] [X]' ) ;
  11021. is( 'X-8bit: kaka 1 XXXX kiki', regexmess('X-8bit: kaka 1 ¤£ kiki'), 'regexmess: 2 Change 8bit characters in whole email to X characters, using tr');
  11022. # Add a final \r\n if missing
  11023. @regexmess = ( 's{(?<![\n])\z}{\r\n}gxms' ) ;
  11024. is( "\r\n", regexmess(""), 'regexmess: 1. Add a final \r\n if missing. Missing' ) ;
  11025. is( "abc\r\n", regexmess("abc"), 'regexmess: 2. Add a final \r\n if missing. Missing' ) ;
  11026. is( "abc\ndef\r\n", regexmess("abc\ndef"), 'regexmess: 3. Add a final \r\n if missing. Missing' ) ;
  11027. is( "abc\r\ndef\r\n", regexmess("abc\r\ndef"), 'regexmess: 3. Add a final \r\n if missing. Missing' ) ;
  11028. is( "\r\n", regexmess("\r\n"), 'regexmess: 3. Add a final \r\n if missing. Not missing' ) ;
  11029. is( "abc\n", regexmess("abc\n"), 'regexmess: 4. Add a final \r\n if missing. Not missing' ) ;
  11030. is( "abc\r\n", regexmess("abc\r\n"), 'regexmess: 4. Add a final \r\n if missing. Not missing' ) ;
  11031. is( "abc\ndef\n", regexmess("abc\ndef\n"), 'regexmess: 4. Add a final \r\n if missing. Not missing' ) ;
  11032. is( "abc\r\ndef\r\n", regexmess("abc\r\ndef\r\n"), 'regexmess: 4. Add a final \r\n if missing. Not missing' ) ;
  11033. # Remove the fucking buggy X-Spam-Report: a bad header on several lines that can even begin without a space!
  11034. @regexmess = ( 's{X-Spam-Report:.*?\n(^[^\n]+:|^\r?\n)}{$1}xms' ) ;
  11035. # Damien regexes:
  11036. #@regexmess = ( 's{X-Spam-Report:.*?\n(^[a-zA-Z0-9\-]+:)}{$1}xms' ) ;
  11037. #@regexmess = ( 's{X-Spam-Report:.*?\n(^[a-zA-Z0-9\-]+:|^\r?\n)}{$1}xms' ) ;
  11038. is(
  11039. <<'EOM'
  11040. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11041. From:<tartanpion@machin.truc>
  11042. LaSuite: super
  11043. Hello,
  11044. Bye.
  11045. EOM
  11046. , regexmess(
  11047. <<'EOM'
  11048. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11049. From:<tartanpion@machin.truc>
  11050. X-Spam-Report: caca
  11051. caca
  11052. caca
  11053. caca
  11054. LaSuite: super
  11055. Hello,
  11056. Bye.
  11057. EOM
  11058. ), 'regexmess: 1 remove buggy X-Spam-Report: across several lines, not the final header');
  11059. is(
  11060. <<'EOM'
  11061. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11062. From:<tartanpion@machin.truc>
  11063. LaSuite: super
  11064. LaSuite2: super 2
  11065. Hello,
  11066. Bye.
  11067. EOM
  11068. , regexmess(
  11069. <<'EOM'
  11070. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11071. From:<tartanpion@machin.truc>
  11072. X-Spam-Report: caca
  11073. caca
  11074. caca
  11075. caca
  11076. LaSuite: super
  11077. LaSuite2: super 2
  11078. Hello,
  11079. Bye.
  11080. EOM
  11081. ), 'regexmess: 2 remove buggy X-Spam-Report: across several lines, not the final header');
  11082. is(
  11083. <<'EOM'
  11084. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11085. From:<tartanpion@machin.truc>
  11086. LaSuite: super
  11087. LaSuite2: super 2
  11088. Hello,
  11089. Bye.
  11090. EOM
  11091. , regexmess(
  11092. <<'EOM'
  11093. X-Spam-Report: caca
  11094. caca
  11095. caca
  11096. caca
  11097. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11098. From:<tartanpion@machin.truc>
  11099. LaSuite: super
  11100. LaSuite2: super 2
  11101. Hello,
  11102. Bye.
  11103. EOM
  11104. ), 'regexmess: 3 remove buggy X-Spam-Report: across several lines, first header');
  11105. is(
  11106. <<'EOM'
  11107. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11108. From:<tartanpion@machin.truc>
  11109. Hello,
  11110. Bye.
  11111. EOM
  11112. , regexmess(
  11113. <<'EOM'
  11114. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11115. From:<tartanpion@machin.truc>
  11116. X-Spam-Report: caca
  11117. caca
  11118. caca
  11119. caca
  11120. Hello,
  11121. Bye.
  11122. EOM
  11123. ), 'regexmess: 4 remove buggy X-Spam-Report: across several lines, final header');
  11124. is(
  11125. <<'EOM'
  11126. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11127. From:<tartanpion@machin.truc>
  11128. Hello,
  11129. Bye.
  11130. EOM
  11131. , regexmess(
  11132. <<'EOM'
  11133. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11134. From:<tartanpion@machin.truc>
  11135. Hello,
  11136. Bye.
  11137. EOM
  11138. ), 'regexmess: 5 remove buggy X-Spam-Report: not there at all');
  11139. is(
  11140. <<"EOM"
  11141. Date: Sat, 10 Jul 2010 05:34:45 -0700\r
  11142. From:<tartanpion>\r
  11143. LaSuite: super\r
  11144. LaSuite2: super 2\r
  11145. \r
  11146. Hello,\r
  11147. Bye.\r
  11148. EOM
  11149. , regexmess(
  11150. <<"EOM"
  11151. X-Spam-Report: caca\r
  11152. caca\r
  11153. caca\r
  11154. caca\r
  11155. Date: Sat, 10 Jul 2010 05:34:45 -0700\r
  11156. From:<tartanpion>\r
  11157. LaSuite: super\r
  11158. LaSuite2: super 2\r
  11159. \r
  11160. Hello,\r
  11161. Bye.\r
  11162. EOM
  11163. ), 'regexmess: 6 remove buggy X-Spam-Report: across several lines, first header, with \r');
  11164. is(
  11165. <<"EOM"
  11166. Date: Sat, 10 Jul 2010 05:34:45 -0700\r
  11167. From:<tartanpion>\r
  11168. LaSuite: super\r
  11169. LaSuite2: super 2\r
  11170. \r
  11171. Hello,\r
  11172. Bye.\r
  11173. EOM
  11174. , regexmess(
  11175. <<"EOM"
  11176. Date: Sat, 10 Jul 2010 05:34:45 -0700\r
  11177. From:<tartanpion>\r
  11178. X-Spam-Report: caca\r
  11179. caca\r
  11180. caca\r
  11181. caca\r
  11182. LaSuite: super\r
  11183. LaSuite2: super 2\r
  11184. \r
  11185. Hello,\r
  11186. Bye.\r
  11187. EOM
  11188. ), 'regexmess: 7 remove buggy X-Spam-Report: across several lines, middle header, with \r');
  11189. is(
  11190. <<"EOM"
  11191. Date: Sat, 10 Jul 2010 05:34:45 -0700\r
  11192. From:<tartanpion>\r
  11193. \r
  11194. Hello,\r
  11195. Bye.\r
  11196. EOM
  11197. , regexmess(
  11198. <<"EOM"
  11199. Date: Sat, 10 Jul 2010 05:34:45 -0700\r
  11200. From:<tartanpion>\r
  11201. X-Spam-Report: caca\r
  11202. caca\r
  11203. caca\r
  11204. caca\r
  11205. \r
  11206. Hello,\r
  11207. Bye.\r
  11208. EOM
  11209. ), 'regexmess: 8 remove buggy X-Spam-Report: across several lines, final header, with \r');
  11210. undef @regexmess ;
  11211. note( 'Leaving tests_regexmess()' ) ;
  11212. return ;
  11213. }
  11214. sub regexmess
  11215. {
  11216. my ( $string ) = @_ ;
  11217. foreach my $regexmess ( @regexmess ) {
  11218. $sync->{ debug } and myprint( "eval \$string =~ $regexmess\n" ) ;
  11219. my $ret = eval "\$string =~ $regexmess ; 1" ;
  11220. #myprint( "eval [$ret]\n" ) ;
  11221. if ( ( not $ret ) or $EVAL_ERROR ) {
  11222. myprint( "Error: eval regexmess '$regexmess': $EVAL_ERROR" ) ;
  11223. return( undef ) ;
  11224. }
  11225. }
  11226. $sync->{ debug } and myprint( "$string\n" ) ;
  11227. return( $string ) ;
  11228. }
  11229. sub tests_skipmess
  11230. {
  11231. note( 'Entering tests_skipmess()' ) ;
  11232. ok( not( defined skipmess( 'blabla' ) ), 'skipmess, no skipmess, no skip' ) ;
  11233. @skipmess = ('[') ;
  11234. ok( not( defined skipmess( 'popopo' ) ), 'skipmess, bad regex [' ) ;
  11235. @skipmess = ('lalala') ;
  11236. ok( not( defined skipmess( 'popopo' ) ), 'skipmess, bad regex lalala' ) ;
  11237. @skipmess = ('/popopo/') ;
  11238. ok( 1 == skipmess( 'popopo' ), 'skipmess, popopo match regex /popopo/' ) ;
  11239. @skipmess = ('/popopo/') ;
  11240. ok( 0 == skipmess( 'rrrrrr' ), 'skipmess, rrrrrr does not match regex /popopo/' ) ;
  11241. @skipmess = ('m{^$}') ;
  11242. ok( 1 == skipmess( q{} ), 'skipmess: empty string yes' ) ;
  11243. ok( 0 == skipmess( 'Hi!' ), 'skipmess: empty string no' ) ;
  11244. @skipmess = ('m{i}') ;
  11245. ok( 1 == skipmess( 'Hi!' ), 'skipmess: i string yes' ) ;
  11246. ok( 0 == skipmess( 'Bye!' ), 'skipmess: i string no' ) ;
  11247. @skipmess = ('m{[\x80-\xff]}') ;
  11248. ok( 0 == skipmess( 'Hi!' ), 'skipmess: i 8bit no' ) ;
  11249. ok( 1 == skipmess( "\xff" ), 'skipmess: \xff 8bit yes' ) ;
  11250. @skipmess = ('m{A}', 'm{B}') ;
  11251. ok( 0 == skipmess( 'Hi!' ), 'skipmess: A or B no' ) ;
  11252. ok( 0 == skipmess( 'lala' ), 'skipmess: A or B no' ) ;
  11253. ok( 0 == skipmess( "\xff" ), 'skipmess: A or B no' ) ;
  11254. ok( 1 == skipmess( 'AB' ), 'skipmess: A or B yes' ) ;
  11255. ok( 1 == skipmess( 'BA' ), 'skipmess: A or B yes' ) ;
  11256. ok( 1 == skipmess( 'AA' ), 'skipmess: A or B yes' ) ;
  11257. ok( 1 == skipmess( 'Ok Bye' ), 'skipmess: A or B yes' ) ;
  11258. @skipmess = ( 'm#\A((?:[^\n]+\n)+|)^Content-Type: Message/Partial;[^\n]*\n(?:\n|.*\n\n)#ism' ) ; # SUPER BEST!
  11259. ok( 1 == skipmess(
  11260. <<'EOM'
  11261. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11262. Content-Type: Message/Partial; blabla
  11263. From:<tartanpion@machin.truc>
  11264. Hello!
  11265. Bye.
  11266. EOM
  11267. ),
  11268. 'skipmess: 1 match Content-Type: Message/Partial' ) ;
  11269. ok( 0 == skipmess(
  11270. <<'EOM'
  11271. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11272. From:<tartanpion@machin.truc>
  11273. Hello!
  11274. Bye.
  11275. EOM
  11276. ),
  11277. 'skipmess: 2 not match Content-Type: Message/Partial' ) ;
  11278. ok( 1 == skipmess(
  11279. <<'EOM'
  11280. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11281. From:<tartanpion@machin.truc>
  11282. Content-Type: Message/Partial; blabla
  11283. Hello!
  11284. Bye.
  11285. EOM
  11286. ),
  11287. 'skipmess: 3 match Content-Type: Message/Partial' ) ;
  11288. ok( 0 == skipmess(
  11289. <<'EOM'
  11290. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11291. From:<tartanpion@machin.truc>
  11292. Hello!
  11293. Content-Type: Message/Partial; blabla
  11294. Bye.
  11295. EOM
  11296. ),
  11297. 'skipmess: 4 not match Content-Type: Message/Partial' ) ;
  11298. ok( 0 == skipmess(
  11299. <<'EOM'
  11300. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11301. From:<tartanpion@machin.truc>
  11302. Hello!
  11303. Content-Type: Message/Partial; blabla
  11304. Bye.
  11305. EOM
  11306. ),
  11307. 'skipmess: 5 not match Content-Type: Message/Partial' ) ;
  11308. ok( 1 == skipmess(
  11309. <<'EOM'
  11310. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11311. Content-Type: Message/Partial; blabla
  11312. From:<tartanpion@machin.truc>
  11313. Hello!
  11314. Content-Type: Message/Partial; blabla
  11315. Bye.
  11316. EOM
  11317. ),
  11318. 'skipmess: 6 match Content-Type: Message/Partial' ) ;
  11319. ok( 1 == skipmess(
  11320. <<'EOM'
  11321. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11322. Content-Type: Message/Partial;
  11323. From:<tartanpion@machin.truc>
  11324. Hello!
  11325. Bye.
  11326. EOM
  11327. ),
  11328. 'skipmess: 7 match Content-Type: Message/Partial' ) ;
  11329. ok( 1 == skipmess(
  11330. <<'EOM'
  11331. Date: Wed, 2 Jul 2014 02:26:40 +0000
  11332. MIME-Version: 1.0
  11333. Content-Type: message/partial;
  11334. id="TAN_U_P<1404267997.00007489ed17>";
  11335. number=3;
  11336. total=3
  11337. 6HQ6Hh3CdXj77qEGixerQ6zHx0OnQ/Cf5On4W0Y6vtU2crABZQtD46Hx1EOh8dDz4+OnTr1G
  11338. Hello!
  11339. Bye.
  11340. EOM
  11341. ),
  11342. 'skipmess: 8 match Content-Type: Message/Partial' ) ;
  11343. ok( 1 == skipmess(
  11344. <<'EOM'
  11345. Return-Path: <gilles@lamiral.info>
  11346. Received: by lamiral.info (Postfix, from userid 1000)
  11347. id 21EB12443BF; Mon, 2 Mar 2015 15:38:35 +0100 (CET)
  11348. Subject: test: aethaecohngiexao
  11349. To: <tata@petite.lamiral.info>
  11350. X-Mailer: mail (GNU Mailutils 2.2)
  11351. Message-Id: <20150302143835.21EB12443BF@lamiral.info>
  11352. Content-Type: message/partial;
  11353. id="TAN_U_P<1404267997.00007489ed17>";
  11354. number=3;
  11355. total=3
  11356. Date: Mon, 2 Mar 2015 15:38:34 +0100 (CET)
  11357. From: gilles@lamiral.info (Gilles LAMIRAL)
  11358. test: aethaecohngiexao
  11359. EOM
  11360. ),
  11361. 'skipmess: 9 match Content-Type: Message/Partial' ) ;
  11362. ok( 1 == skipmess(
  11363. <<'EOM'
  11364. Date: Mon, 2 Mar 2015 15:38:34 +0100 (CET)
  11365. From: gilles@lamiral.info (Gilles LAMIRAL)
  11366. Content-Type: message/partial;
  11367. id="TAN_U_P<1404267997.00007489ed17>";
  11368. number=3;
  11369. total=3
  11370. test: aethaecohngiexao
  11371. EOM
  11372. . "lalala\n" x 3_000_000
  11373. ),
  11374. 'skipmess: 10 match Content-Type: Message/Partial' ) ;
  11375. ok( 0 == skipmess(
  11376. <<'EOM'
  11377. Date: Mon, 2 Mar 2015 15:38:34 +0100 (CET)
  11378. From: gilles@lamiral.info (Gilles LAMIRAL)
  11379. test: aethaecohngiexao
  11380. EOM
  11381. . "lalala\n" x 3_000_000
  11382. ),
  11383. 'skipmess: 11 match Content-Type: Message/Partial' ) ;
  11384. ok( 0 == skipmess(
  11385. <<"EOM"
  11386. From: fff\r
  11387. To: fff\r
  11388. Subject: Testing imapsync --skipmess\r
  11389. Date: Mon, 22 Aug 2011 08:40:20 +0800\r
  11390. Mime-Version: 1.0\r
  11391. Content-Type: text/plain; charset=iso-8859-1\r
  11392. Content-Transfer-Encoding: 7bit\r
  11393. \r
  11394. EOM
  11395. . qq{!#"d%&'()*+,-./0123456789:;<=>?\@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefg\r\n } x 32_730
  11396. ),
  11397. 'skipmess: 12 not match Content-Type: Message/Partial' ) ;
  11398. # Complex regular subexpression recursion limit (32766) exceeded with more lines
  11399. # exit;
  11400. undef @skipmess ;
  11401. note( 'Leaving tests_skipmess()' ) ;
  11402. return ;
  11403. }
  11404. sub tests_skipmess_neg
  11405. {
  11406. note( 'Entering tests_skipmess_neg()' ) ;
  11407. @skipmess = ('m{i}') ;
  11408. ok( 1 == skipmess( 'Hi!' ), 'skipmess: i string yes' ) ;
  11409. ok( 0 == skipmess( 'Ho!' ), 'skipmess: i string no' ) ;
  11410. @skipmess = ('m{\A(?!.*i)}') ;
  11411. ok( 0 == skipmess( 'Hi!' ), 'skipmess: not i string no' ) ;
  11412. ok( 1 == skipmess( 'Ho!' ), 'skipmess: not i string yes' ) ;
  11413. @skipmess = ('m{\A(?!.*^From:[^\n]*tartanpion\@machin\.truc)}xms') ;
  11414. ok( 0 == skipmess(
  11415. <<'EOM'
  11416. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11417. From: <tartanpion@machin.truc>
  11418. Bye.
  11419. EOM
  11420. ),
  11421. 'skipmess: 1 not From tartanpion@machin.truc' ) ;
  11422. ok( 1 == skipmess(
  11423. <<'EOM'
  11424. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11425. From: <kikiki@machin.truc>
  11426. Bye.
  11427. EOM
  11428. ),
  11429. 'skipmess: 2 not From tartanpion@machin.truc' ) ;
  11430. ok( 0 == skipmess(
  11431. <<'EOM'
  11432. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11433. From: <tartanpion@machin.truc>
  11434. From: <tartanpion@machin.truc>
  11435. Bye.
  11436. EOM
  11437. ),
  11438. 'skipmess: 3 not From tartanpion@machin.truc' ) ;
  11439. ok( 1 == skipmess(
  11440. <<'EOM'
  11441. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11442. From: <kikiki@machin.truc>
  11443. From: <tartanpion@machin.truc>
  11444. Bye.
  11445. EOM
  11446. ),
  11447. 'skipmess: 4 not From tartanpion@machin.truc' ) ;
  11448. undef @skipmess ;
  11449. note( 'Leaving tests_skipmess_neg()' ) ;
  11450. return ;
  11451. }
  11452. sub skipmess
  11453. {
  11454. my ( $string ) = @_ ;
  11455. my $match ;
  11456. #myprint( "$string\n" ) ;
  11457. foreach my $skipmess ( @skipmess ) {
  11458. $sync->{ debug } and myprint( "eval \$match = \$string =~ $skipmess\n" ) ;
  11459. my $ret = eval "\$match = \$string =~ $skipmess ; 1" ;
  11460. #myprint( "eval [$ret]\n" ) ;
  11461. $sync->{ debug } and myprint( "match [$match]\n" ) ;
  11462. if ( ( not $ret ) or $EVAL_ERROR ) {
  11463. myprint( "Error: eval skipmess '$skipmess': $EVAL_ERROR" ) ;
  11464. return( undef ) ;
  11465. }
  11466. return( $match ) if ( $match ) ;
  11467. }
  11468. return( $match ) ;
  11469. }
  11470. sub tests_bytes_display_string_bin
  11471. {
  11472. note( 'Entering tests_bytes_display_string_bin()' ) ;
  11473. is( 'NA', bytes_display_string_bin( ), 'bytes_display_string_bin: no args => NA' ) ;
  11474. is( 'NA', bytes_display_string_bin( undef ), 'bytes_display_string_bin: undef => NA' ) ;
  11475. is( 'NA', bytes_display_string_bin( 'blabla' ), 'bytes_display_string_bin: blabla => NA' ) ;
  11476. is( '0.000 KiB', bytes_display_string_bin( 0 ), 'bytes_display_string_bin: 0 => 0.000 KiB' ) ;
  11477. is( '0.001 KiB', bytes_display_string_bin( 1 ), 'bytes_display_string_bin: 1 => 0.001 KiB' ) ;
  11478. is( '0.010 KiB', bytes_display_string_bin( 10 ), 'bytes_display_string_bin: 10 => 0.010 KiB' ) ;
  11479. is( '0.976 KiB', bytes_display_string_bin( 999 ), 'bytes_display_string_bin: 999 => 0.976 KiB' ) ;
  11480. note( bytes_display_string_bin( 999 ) ) ;
  11481. is( '0.999 KiB', bytes_display_string_bin( 1023 ), 'bytes_display_string_bin: 1023 => 0.999 KiB' ) ;
  11482. note( bytes_display_string_bin( 1023 ) ) ;
  11483. is( '1.000 KiB', bytes_display_string_bin( 1024 ), 'bytes_display_string_bin: 1024 => 1.000 KiB' ) ;
  11484. note( bytes_display_string_bin( 1024 ) ) ;
  11485. is( '1.001 KiB', bytes_display_string_bin( 1025 ), 'bytes_display_string_bin: 1025 => 1.001 KiB' ) ;
  11486. is( '9.999 KiB', bytes_display_string_bin( 10_239 ), 'bytes_display_string_bin: 10_239 => 9.999 KiB' ) ;
  11487. note( bytes_display_string_bin( 10_239 ) ) ;
  11488. is( '10.000 KiB', bytes_display_string_bin( 10_240 ), 'bytes_display_string_bin: 10_240 => 10.000 KiB' ) ;
  11489. note( bytes_display_string_bin( 10_240 ) ) ;
  11490. is( '999.999 KiB', bytes_display_string_bin( 1_023_999 ), 'bytes_display_string_bin: 1_023_999 => 999.999 KiB' ) ;
  11491. note( bytes_display_string_bin( 1_023_999 ) ) ;
  11492. is( '0.977 MiB', bytes_display_string_bin( 1_024_000 ), 'bytes_display_string_bin: 1_024_000 => 0.977 MiB' ) ;
  11493. note( bytes_display_string_bin( 1_024_000 ) ) ;
  11494. is( '0.999 MiB', bytes_display_string_bin( 1_047_527 ), 'bytes_display_string_bin: 1_047_527 => 0.999 MiB' ) ;
  11495. note( bytes_display_string_bin( 1_047_527 ) ) ;
  11496. is( '0.999 MiB', bytes_display_string_bin( 1_048_051 ), 'bytes_display_string_bin: 1_048_051 => 0.999 MiB' ) ;
  11497. note( bytes_display_string_bin( 1_048_051 ) ) ;
  11498. is( '1.000 MiB', bytes_display_string_bin( 1_048_052 ), 'bytes_display_string_bin: 1_048_052 => 1.000 MiB' ) ;
  11499. note( bytes_display_string_bin( 1_048_052 ) ) ;
  11500. is( '1.000 MiB', bytes_display_string_bin( 1_048_575 ), 'bytes_display_string_bin: 1_048_575 => 1.000 MiB' ) ;
  11501. is( '1.000 MiB', bytes_display_string_bin( 1_048_576 ), 'bytes_display_string_bin: 1_048_576 => 1.000 MiB' ) ;
  11502. is( '1.000 GiB', bytes_display_string_bin( 1_073_741_823 ), 'bytes_display_string_bin: 1_073_741_823 => 1.000 GiB' ) ;
  11503. is( '1.000 GiB', bytes_display_string_bin( 1_073_741_824 ), 'bytes_display_string_bin: 1_073_741_824 => 1.000 GiB' ) ;
  11504. is( '1.000 TiB', bytes_display_string_bin( 1_099_511_627_775 ), 'bytes_display_string_bin: 1_099_511_627_775 => 1.000 TiB' ) ;
  11505. is( '1.000 TiB', bytes_display_string_bin( 1_099_511_627_776 ), 'bytes_display_string_bin: 1_099_511_627_776 => 1.000 TiB' ) ;
  11506. is( '1.000 PiB', bytes_display_string_bin( 1_125_899_906_842_623 ), 'bytes_display_string_bin: 1_125_899_906_842_623 => 1.000 PiB' ) ;
  11507. is( '1.000 PiB', bytes_display_string_bin( 1_125_899_906_842_624 ), 'bytes_display_string_bin: 1_125_899_906_842_624 => 1.000 PiB' ) ;
  11508. is( '1024.000 PiB', bytes_display_string_bin( 1_152_921_504_606_846_975 ), 'bytes_display_string_bin: 1_152_921_504_606_846_975 => 1024.000 PiB' ) ;
  11509. is( '1024.000 PiB', bytes_display_string_bin( 1_152_921_504_606_846_976 ), 'bytes_display_string_bin: 1_152_921_504_606_846_976 => 1024.000 PiB' ) ;
  11510. is( '1048576.000 PiB', bytes_display_string_bin( 1_180_591_620_717_411_303_424 ), 'bytes_display_string_bin: 1_180_591_620_717_411_303_424 => 1048576.000 PiB' ) ;
  11511. note( bytes_display_string_bin( 1_180_591_620_717_411_303_424 ) ) ;
  11512. note( bytes_display_string_bin( 3_000_000_000 ) ) ;
  11513. note( 'Leaving tests_bytes_display_string_bin()' ) ;
  11514. return ;
  11515. }
  11516. sub bytes_display_string_bin
  11517. {
  11518. my ( $bytes ) = @_ ;
  11519. my $readable_value = q{} ;
  11520. if ( ! defined( $bytes ) ) {
  11521. return( 'NA' ) ;
  11522. }
  11523. if ( not match_number( $bytes ) ) {
  11524. return( 'NA' ) ;
  11525. }
  11526. SWITCH: {
  11527. if ( abs( $bytes ) < ( 1000 * $KIBI ) ) {
  11528. $readable_value = mysprintf( '%.3f KiB', $bytes / $KIBI) ;
  11529. last SWITCH ;
  11530. }
  11531. if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI ) ) {
  11532. $readable_value = mysprintf( '%.3f MiB', $bytes / ($KIBI * $KIBI) ) ;
  11533. last SWITCH ;
  11534. }
  11535. if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI * $KIBI) ) {
  11536. $readable_value = mysprintf( '%.3f GiB', $bytes / ($KIBI * $KIBI * $KIBI) ) ;
  11537. last SWITCH ;
  11538. }
  11539. if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI * $KIBI * $KIBI) ) {
  11540. $readable_value = mysprintf( '%.3f TiB', $bytes / ($KIBI * $KIBI * $KIBI * $KIBI) ) ;
  11541. last SWITCH ;
  11542. } else {
  11543. $readable_value = mysprintf( '%.3f PiB', $bytes / ($KIBI * $KIBI * $KIBI * $KIBI * $KIBI) ) ;
  11544. }
  11545. # if you have exabytes (EiB) of email to transfer, you have too much email!
  11546. }
  11547. #myprint( "$bytes = $readable_value\n" ) ;
  11548. return( $readable_value ) ;
  11549. }
  11550. sub tests_bytes_display_string_dec
  11551. {
  11552. note( 'Entering tests_bytes_display_string_dec()' ) ;
  11553. is( 'NA', bytes_display_string_dec( ), 'bytes_display_string_dec: no args => NA' ) ;
  11554. is( 'NA', bytes_display_string_dec( undef ), 'bytes_display_string_dec: undef => NA' ) ;
  11555. is( 'NA', bytes_display_string_dec( 'blabla' ), 'bytes_display_string_dec: blabla => NA' ) ;
  11556. is( '0 bytes', bytes_display_string_dec( 0 ), 'bytes_display_string_dec: 0 => 0 bytes' ) ;
  11557. is( '1 bytes', bytes_display_string_dec( 1 ), 'bytes_display_string_dec: 1 => 1 bytes' ) ;
  11558. is( '10 bytes', bytes_display_string_dec( 10 ), 'bytes_display_string_dec: 10 => 10 bytes' ) ;
  11559. is( '999 bytes', bytes_display_string_dec( 999 ), 'bytes_display_string_dec: 999 => 999 bytes' ) ;
  11560. is( '1.000 KB', bytes_display_string_dec( 1000 ), 'bytes_display_string_dec: 1000 => 1.000 KB' ) ;
  11561. is( '1.001 KB', bytes_display_string_dec( 1001 ), 'bytes_display_string_dec: 1000 => 1.1001 KB' ) ;
  11562. is( '999.999 KB', bytes_display_string_dec( 999_999 ), 'bytes_display_string_dec: 999_999 => 999.999 KB' ) ;
  11563. is( '1.000 MB', bytes_display_string_dec( 1_000_000 ), 'bytes_display_string_dec: 1_000_000 => 1.000 MB' ) ;
  11564. is( '1.000 MB', bytes_display_string_dec( 1_000_500 ), 'bytes_display_string_dec: 1_000_500 => 1.000 MB' ) ;
  11565. is( '1.001 MB', bytes_display_string_dec( 1_000_501 ), 'bytes_display_string_dec: 1_000_501 => 1.001 MB' ) ;
  11566. is( '999.999 MB', bytes_display_string_dec( 999_999_000 ), 'bytes_display_string_dec: 999_999_000 => 999.999 MB' ) ;
  11567. is( '999.999 MB', bytes_display_string_dec( 999_999_499 ), 'bytes_display_string_dec: 999_999_499 => 999.999 MB' ) ;
  11568. is( '1.000 GB', bytes_display_string_dec( 999_999_500 ), 'bytes_display_string_dec: 999_999_500 => 1.000 GB' ) ;
  11569. is( '1.000 GB', bytes_display_string_dec( 1_000_000_000 ), 'bytes_display_string_dec: 1_000_000_000 => 1.000 GB' ) ;
  11570. is( '1.000 GB', bytes_display_string_dec( 1_000_500_000 ), 'bytes_display_string_dec: 1_000_500_000 => 1.000 GB' ) ;
  11571. is( '1.001 GB', bytes_display_string_dec( 1_000_500_001 ), 'bytes_display_string_dec: 1_000_501_000 => 1.001 GB' ) ;
  11572. is( '999.999 GB', bytes_display_string_dec( 999_999_000_000 ), 'bytes_display_string_dec: 999_999_000_000 => 999.999 GB' ) ;
  11573. is( '999.999 GB', bytes_display_string_dec( 999_999_499_999 ), 'bytes_display_string_dec: 999_999_499_999 => 999.999 GB' ) ;
  11574. is( '1.000 TB', bytes_display_string_dec( 999_999_500_000 ), 'bytes_display_string_dec: 999_999_500_000 => 1.000 TB' ) ;
  11575. is( '1.000 TB', bytes_display_string_dec( 1_000_000_000_000 ), 'bytes_display_string_dec: 1_000_000_000_000 => 1.000 TB' ) ;
  11576. is( '1.000 TB', bytes_display_string_dec( 1_000_500_000_000 ), 'bytes_display_string_dec: 1_000_500_000_000 => 1.000 TB' ) ;
  11577. is( '1.001 TB', bytes_display_string_dec( 1_000_500_000_001 ), 'bytes_display_string_dec: 1_000_500_000_000 => 1.000 TB' ) ;
  11578. is( '999.999 TB', bytes_display_string_dec( 999_999_000_000_000 ), 'bytes_display_string_dec: 999_999_000_000_000 => 999.999 TB' ) ;
  11579. is( '999.999 TB', bytes_display_string_dec( 999_999_499_999_999 ), 'bytes_display_string_dec: 999_999_499_999_999 => 999.999 TB' ) ;
  11580. is( '1.000 PB', bytes_display_string_dec( 999_999_500_000_000 ), 'bytes_display_string_dec: 999_999_500_000_000 => 1.000 PB' ) ;
  11581. is( '3.000 GB', bytes_display_string_dec( 3_000_000_000 ), 'bytes_display_string_dec: 3_000_000_000 => 3.000 GB' ) ;
  11582. note( 'Leaving tests_bytes_display_string_dec()' ) ;
  11583. return ;
  11584. }
  11585. sub bytes_display_string_dec
  11586. {
  11587. my ( $bytes ) = @_ ;
  11588. my $readable_value = q{} ;
  11589. if ( ! defined( $bytes ) ) {
  11590. return( 'NA' ) ;
  11591. }
  11592. if ( not match_number( $bytes ) ) {
  11593. return( 'NA' ) ;
  11594. }
  11595. SWITCH: {
  11596. if ( abs( $bytes ) < ( 1000 ) ) {
  11597. $readable_value = mysprintf( '%.0f bytes', $bytes ) ;
  11598. last SWITCH ;
  11599. }
  11600. if ( abs( $bytes ) < ( 1000**2 ) ) {
  11601. $readable_value = mysprintf( '%.3f KB', $bytes / 1000 ) ;
  11602. last SWITCH ;
  11603. }
  11604. if ( abs( $bytes ) < ( 999_999_500 ) ) {
  11605. $readable_value = mysprintf( '%.3f MB', $bytes / ( 1000**2 ) ) ;
  11606. last SWITCH ;
  11607. }
  11608. if ( abs( $bytes ) < ( 999_999_500_000 ) ) {
  11609. $readable_value = mysprintf( '%.3f GB', $bytes / ( 1000**3 ) ) ;
  11610. last SWITCH ;
  11611. }
  11612. if ( abs( $bytes ) < ( 999_999_500_000_000 ) ) {
  11613. $readable_value = mysprintf( '%.3f TB', $bytes / ( 1000**4 ) ) ;
  11614. last SWITCH ;
  11615. } else {
  11616. $readable_value = mysprintf( '%.3f PB', $bytes / ( 1000**5 ) ) ;
  11617. }
  11618. # if you have exabytes (EiB) of email to transfer, you have too much email!
  11619. }
  11620. #myprint( "$bytes = $readable_value\n" ) ;
  11621. return( $readable_value ) ;
  11622. }
  11623. sub tests_useheader_suggestion
  11624. {
  11625. note( 'Entering tests_useheader_suggestion()' ) ;
  11626. is( undef, useheader_suggestion( ), 'useheader_suggestion: no args => undef' ) ;
  11627. my $mysync = {} ;
  11628. $mysync->{ h1_nb_msg_noheader } = 0 ;
  11629. is( q{}, useheader_suggestion( $mysync ), 'useheader_suggestion: h1_nb_msg_noheader count null => no suggestion' ) ;
  11630. $mysync->{ h1_nb_msg_noheader } = 2 ;
  11631. is( q{in order to sync those 2 unidentified messages, add option --addheader}, useheader_suggestion( $mysync ),
  11632. 'useheader_suggestion: h1_nb_msg_noheader count 2 => suggestion of --addheader' ) ;
  11633. note( 'Leaving tests_useheader_suggestion()' ) ;
  11634. return ;
  11635. }
  11636. sub useheader_suggestion
  11637. {
  11638. my $mysync = shift ;
  11639. if ( ! defined $mysync->{ h1_nb_msg_noheader } )
  11640. {
  11641. return ;
  11642. }
  11643. elsif ( 1 <= $mysync->{ h1_nb_msg_noheader } )
  11644. {
  11645. return qq{in order to sync those $mysync->{ h1_nb_msg_noheader } unidentified messages, add option --addheader} ;
  11646. }
  11647. else
  11648. {
  11649. return q{} ;
  11650. }
  11651. return ;
  11652. }
  11653. sub do_and_print_stats
  11654. {
  11655. my $mysync = shift ;
  11656. if ( ! $mysync->{can_do_stats} ) {
  11657. return ;
  11658. }
  11659. my $timeend = time ;
  11660. my $timediff = $timeend - $mysync->{timestart} ;
  11661. my $timeend_str = localtimez( $timeend ) ;
  11662. my $cpu_time = cpu_time( $mysync ) ;
  11663. my $cpu_percent = cpu_percent( $mysync, $cpu_time, $timediff ) ;
  11664. my $cpu_percent_global = cpu_percent_global( $mysync, $cpu_percent ) ;
  11665. my $memory_consumption_at_end = memory_consumption( ) || 0 ;
  11666. my $memory_consumption_at_start = $mysync->{ memory_consumption_at_start } || 0 ;
  11667. my $memory_ratio = ( $mysync->{ biggest_message_transferred } ) ?
  11668. mysprintf( '%.1f', $memory_consumption_at_end / $mysync->{ biggest_message_transferred } ) : 'NA' ;
  11669. # my $useheader_suggestion = useheader_suggestion( $mysync ) ;
  11670. myprint( "++++ Statistics\n" ) ;
  11671. myprint( "Transfer started on : $mysync->{ timestart_str }\n" ) ;
  11672. myprint( "Transfer ended on : $timeend_str\n" ) ;
  11673. myprintf( "Transfer time : %.1f sec\n", $timediff ) ;
  11674. myprint( "Folders synced : $h1_folders_wanted_ct/$h1_folders_wanted_nb synced\n" ) ;
  11675. myprint( "Messages transferred : $mysync->{ nb_msg_transferred } " ) ;
  11676. myprint( "(could be $nb_msg_skipped_dry_mode without dry mode)" ) if ( $mysync->{dry} ) ;
  11677. myprint( "\n" ) ;
  11678. myprint( "Messages skipped : $mysync->{ nb_msg_skipped }\n" ) ;
  11679. myprint( "Messages found duplicate on host1 : $mysync->{ acc1 }->{ nb_msg_duplicate }\n" ) ;
  11680. myprint( "Messages found duplicate on host2 : $mysync->{ acc2 }->{ nb_msg_duplicate }\n" ) ;
  11681. myprint( "Messages found crossduplicate on host2 : $mysync->{ h2_nb_msg_crossdup }\n" ) ;
  11682. myprint( "Messages void (noheader) on host1 : $mysync->{ h1_nb_msg_noheader } ", useheader_suggestion( $mysync ), "\n" ) ;
  11683. myprint( "Messages void (noheader) on host2 : $h2_nb_msg_noheader\n" ) ;
  11684. nb_messages_in_1_not_in_2( $mysync ) ;
  11685. nb_messages_in_2_not_in_1( $mysync ) ;
  11686. myprintf( "Messages found in host1 not in host2 : %s messages\n", $mysync->{ nb_messages_in_1_not_in_2 } ) ;
  11687. myprintf( "Messages found in host2 not in host1 : %s messages\n", $mysync->{ nb_messages_in_2_not_in_1 } ) ;
  11688. myprint( "Messages deleted on host1 : $mysync->{ acc1 }->{ nb_msg_deleted }\n" ) ;
  11689. myprint( "Messages deleted on host2 : $mysync->{ acc2 }->{ nb_msg_deleted }\n" ) ;
  11690. myprintf( "Total bytes transferred : %s (%s)\n",
  11691. $mysync->{total_bytes_transferred},
  11692. bytes_display_string_bin( $mysync->{total_bytes_transferred} ) ) ;
  11693. myprintf( "Total bytes skipped : %s (%s)\n",
  11694. $mysync->{ total_bytes_skipped },
  11695. bytes_display_string_bin( $mysync->{ total_bytes_skipped } ) ) ;
  11696. $timediff ||= 1 ; # No division per 0
  11697. myprintf("Message rate : %.1f messages/s\n", $mysync->{nb_msg_transferred} / $timediff ) ;
  11698. myprintf("Average bandwidth rate : %.1f KiB/s\n", $mysync->{total_bytes_transferred} / $KIBI / $timediff ) ;
  11699. myprint( "Reconnections to host1 : $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT}\n" ) ;
  11700. myprint( "Reconnections to host2 : $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT}\n" ) ;
  11701. myprintf("Memory consumption at the end : %.1f MiB (started with %.1f MiB)\n",
  11702. $memory_consumption_at_end / $KIBI / $KIBI,
  11703. $memory_consumption_at_start / $KIBI / $KIBI ) ;
  11704. myprint( "Load end is : " . ( join( q{ }, loadavg( ) ) || 'unknown' ), " on $mysync->{cpu_number} cores\n" ) ;
  11705. myprint( "CPU time and %cpu : $cpu_time sec $cpu_percent %cpu $cpu_percent_global %allcpus\n" ) ;
  11706. myprintf("Biggest message transferred : %s bytes (%s)\n",
  11707. $mysync->{ biggest_message_transferred },
  11708. bytes_display_string_bin( $mysync->{ biggest_message_transferred } ) ) ;
  11709. myprint( "Memory/biggest message ratio : $memory_ratio\n" ) ;
  11710. if ( $mysync->{ foldersizesatend } and $mysync->{ foldersizes } ) {
  11711. my $nb_msg_start_diff = diff_or_NA( $mysync->{ h2_nb_msg_start }, $mysync->{ h1_nb_msg_start } ) ;
  11712. my $bytes_start_diff = diff_or_NA( $mysync->{ h2_bytes_start }, $mysync->{ h1_bytes_start } ) ;
  11713. myprintf("Start difference host2 - host1 : %s messages, %s bytes (%s)\n", $nb_msg_start_diff,
  11714. $bytes_start_diff,
  11715. bytes_display_string_bin( $bytes_start_diff ) ) ;
  11716. my $nb_msg_end_diff = diff_or_NA( $h2_nb_msg_end, $h1_nb_msg_end ) ;
  11717. my $bytes_end_diff = diff_or_NA( $h2_bytes_end, $h1_bytes_end ) ;
  11718. myprintf("Final difference host2 - host1 : %s messages, %s bytes (%s)\n", $nb_msg_end_diff,
  11719. $bytes_end_diff,
  11720. bytes_display_string_bin( $bytes_end_diff ) ) ;
  11721. }
  11722. comment_on_final_diff_in_1_not_in_2( $mysync ) ;
  11723. comment_on_final_diff_in_2_not_in_1( $mysync ) ;
  11724. myprint( "Detected $mysync->{nb_errors} errors\n\n" ) ;
  11725. myprint( $mysync->{ warn_release }, "\n" ) ;
  11726. myprint( homepage( ), "\n" ) ;
  11727. return ;
  11728. }
  11729. sub diff_or_NA
  11730. {
  11731. my( $n1, $n2 ) = @ARG ;
  11732. if ( not defined $n1 or not defined $n2 ) {
  11733. return 'NA' ;
  11734. }
  11735. if ( not match_number( $n1 )
  11736. or not match_number( $n2 ) ) {
  11737. return 'NA' ;
  11738. }
  11739. return( $n1 - $n2 ) ;
  11740. }
  11741. sub match_number
  11742. {
  11743. my $n = shift @ARG ;
  11744. if ( not defined $n ) {
  11745. return 0 ;
  11746. }
  11747. if ( $n =~ /[0-9]+\.?[0-9]?/x ) {
  11748. return 1 ;
  11749. }
  11750. else {
  11751. return 0 ;
  11752. }
  11753. }
  11754. sub tests_match_number
  11755. {
  11756. note( 'Entering tests_match_number()' ) ;
  11757. is( 0, match_number( ), 'match_number: no parameters => 0' ) ;
  11758. is( 0, match_number( undef ), 'match_number: undef => 0' ) ;
  11759. is( 0, match_number( 'blabla' ), 'match_number: blabla => 0' ) ;
  11760. is( 1, match_number( 0 ), 'match_number: 0 => 1' ) ;
  11761. is( 1, match_number( 1 ), 'match_number: 1 => 1' ) ;
  11762. is( 1, match_number( 1.0 ), 'match_number: 1.0 => 1' ) ;
  11763. is( 1, match_number( 0.0 ), 'match_number: 0.0 => 1' ) ;
  11764. note( 'Leaving tests_match_number()' ) ;
  11765. return ;
  11766. }
  11767. sub tests_diff_or_NA
  11768. {
  11769. note( 'Entering tests_diff_or_NA()' ) ;
  11770. is( 'NA', diff_or_NA( ), 'diff_or_NA: no parameters => NA' ) ;
  11771. is( 'NA', diff_or_NA( undef ), 'diff_or_NA: undef => NA' ) ;
  11772. is( 'NA', diff_or_NA( undef, undef ), 'diff_or_NA: undef undef => NA' ) ;
  11773. is( 'NA', diff_or_NA( undef, 1 ), 'diff_or_NA: undef 1 => NA' ) ;
  11774. is( 'NA', diff_or_NA( 1, undef ), 'diff_or_NA: 1 undef => NA' ) ;
  11775. is( 'NA', diff_or_NA( 'blabla', 1 ), 'diff_or_NA: blabla 1 => NA' ) ;
  11776. is( 'NA', diff_or_NA( 1, 'blabla' ), 'diff_or_NA: 1 blabla => NA' ) ;
  11777. is( 0, diff_or_NA( 1, 1 ), 'diff_or_NA: 1 1 => 0' ) ;
  11778. is( 1, diff_or_NA( 1, 0 ), 'diff_or_NA: 1 0 => 1' ) ;
  11779. is( -1, diff_or_NA( 0, 1 ), 'diff_or_NA: 0 1 => -1' ) ;
  11780. is( 0, diff_or_NA( 1.0, 1 ), 'diff_or_NA: 1.0 1 => 0' ) ;
  11781. is( 1, diff_or_NA( 1.0, 0 ), 'diff_or_NA: 1.0 0 => 1' ) ;
  11782. is( -1, diff_or_NA( 0, 1.0 ), 'diff_or_NA: 0 1.0 => -1' ) ;
  11783. note( 'Leaving tests_diff_or_NA()' ) ;
  11784. return ;
  11785. }
  11786. sub homepage
  11787. {
  11788. return( 'Homepage: https://imapsync.lamiral.info/' ) ;
  11789. }
  11790. sub load_modules
  11791. {
  11792. if ( $sync->{ssl1}
  11793. or $sync->{ssl2}
  11794. or $sync->{tls1}
  11795. or $sync->{tls2}) {
  11796. if ( $sync->{inet4} ) {
  11797. IO::Socket::SSL->import( 'inet4' ) ;
  11798. }
  11799. if ( $sync->{inet6} ) {
  11800. IO::Socket::SSL->import( 'inet6' ) ;
  11801. }
  11802. }
  11803. return ;
  11804. }
  11805. # Globals: $skipsize $wholeheaderifneeded
  11806. sub parse_header_msg
  11807. {
  11808. my ( $mysync, $imap, $m_uid, $s_heads, $s_fir, $side, $s_hash ) = @_ ;
  11809. my $head = $s_heads->{$m_uid} ;
  11810. my $headnum = scalar keys %{ $head } ;
  11811. $mysync->{ debug } and myprint( "$side: uid $m_uid number of headers, pass one: ", $headnum, "\n" ) ;
  11812. if ( ( ! $headnum ) and ( $wholeheaderifneeded ) ){
  11813. $mysync->{ debug } and myprint( "$side: uid $m_uid no header by parse_headers so taking whole header with BODY.PEEK[HEADER]\n" ) ;
  11814. $imap->fetch($m_uid, 'BODY.PEEK[HEADER]' ) ;
  11815. my $whole_header = $imap->_transaction_literals ;
  11816. #myprint( $whole_header ) ;
  11817. $head = decompose_header( $whole_header ) ;
  11818. $headnum = scalar keys %{ $head } ;
  11819. $mysync->{ debug } and myprint( "$side: uid $m_uid number of headers, pass two: ", $headnum, "\n" ) ;
  11820. }
  11821. #myprint( Data::Dumper->Dump( [ $head, \%useheader ] ) ) ;
  11822. my $headstr = header_construct( $mysync, $head, $side, $m_uid ) ;
  11823. if ( ( ! $headstr ) and ( $mysync->{addheader} ) and ( $side eq 'Host1' ) ) {
  11824. my $header = add_header( $m_uid ) ;
  11825. $mysync->{ debug } and myprint( "$side: uid $m_uid no header found so adding our own [$header]\n" ) ;
  11826. $headstr .= uc $header ;
  11827. $s_fir->{$m_uid}->{NO_HEADER} = 1;
  11828. }
  11829. return if ( ! $headstr ) ;
  11830. my $size = $s_fir->{$m_uid}->{'RFC822.SIZE'} ;
  11831. my $flags = $s_fir->{$m_uid}->{'FLAGS'} ;
  11832. my $idate = $s_fir->{$m_uid}->{'INTERNALDATE'} ;
  11833. $size = length $headstr unless ( $size ) ;
  11834. my $m_md5 = md5_base64( $headstr ) ;
  11835. my $key ;
  11836. if ( $skipsize ) {
  11837. $key = "$m_md5";
  11838. }
  11839. else {
  11840. $key = "$m_md5:$size";
  11841. }
  11842. if ( exists $s_hash->{"$key"} )
  11843. {
  11844. # 0 return code is used to identify duplicate message hash
  11845. my $dup_ref = $s_hash->{"$key"}->{'U'} ;
  11846. my $num = scalar( @{ $dup_ref } ) ;
  11847. push( @{ $dup_ref }, $m_uid ) ;
  11848. my $keydup = "$key#$num" ;
  11849. $mysync->{ debug } and myprint( "$side: uid $m_uid sig $keydup size $size idate $idate dup @{ $dup_ref }\n" ) ;
  11850. if ( $mysync->{ syncduplicates } )
  11851. {
  11852. $s_hash->{"$keydup"}{'5'} = $m_md5 ;
  11853. $s_hash->{"$keydup"}{'s'} = $size ;
  11854. $s_hash->{"$keydup"}{'D'} = $idate ;
  11855. $s_hash->{"$keydup"}{'F'} = $flags ;
  11856. $s_hash->{"$keydup"}{'m'} = $m_uid ;
  11857. }
  11858. return 0 ;
  11859. }
  11860. else
  11861. {
  11862. $s_hash->{"$key"}{'5'} = $m_md5 ;
  11863. $s_hash->{"$key"}{'s'} = $size ;
  11864. $s_hash->{"$key"}{'D'} = $idate ;
  11865. $s_hash->{"$key"}{'F'} = $flags ;
  11866. $s_hash->{"$key"}{'m'} = $m_uid ;
  11867. $s_hash->{"$key"}{'U'} = [ $m_uid ] ; # ? or [ ] ?
  11868. $mysync->{ debug } and myprint( "$side: uid $m_uid sig $key size $size idate $idate\n" ) ;
  11869. return( 1 ) ;
  11870. }
  11871. # we should not be here
  11872. return ;
  11873. }
  11874. sub tests_header_construct
  11875. {
  11876. note( 'Entering tests_header_construct()' ) ;
  11877. is( undef, header_construct( ), 'header_construct: no args => undef' ) ;
  11878. my $mysync = {} ;
  11879. my $head = {
  11880. 'key1' => [ 'val1_key1' ]
  11881. } ;
  11882. is( undef, header_construct( $mysync, $head, 'Host1', '1' ), 'header_construct: key1 val1_key1 no useheader => undef' ) ;
  11883. $mysync->{useheader}->{ 'KEY1' } = 1 ;
  11884. is( 'KEY1: VAL1_KEY1', header_construct( $mysync, $head, 'Host1', '1' ), 'header_construct: key1 val1_key1 => KEY1: VAL1_KEY1' ) ;
  11885. $head = {
  11886. 'key1' => [ 'val1_key1', 'val3_key1', 'val2_key1' ]
  11887. } ;
  11888. is( 'KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1', header_construct( $mysync, $head, 'Host1', '1' ),
  11889. 'header_construct: key1 val1_key1 val3_key1 val2_key1 => KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1' ) ;
  11890. $head = {
  11891. 'key1' => [ 'val1_key1', 'val3_key1', ' val2_key1' ]
  11892. } ;
  11893. is( 'KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1', header_construct( $mysync, $head, 'Host1', '1' ),
  11894. 'header_construct: key1 val1_key1 val3_key1 val2_key1 => KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1' ) ;
  11895. $mysync->{useheader}->{ 'ALL' } = 1 ;
  11896. is( 'KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1', header_construct( $mysync, $head, 'Host1', '1' ),
  11897. 'header_construct: key1 val1_key1 val3_key1 val2_key1 useheader ALL => KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1' ) ;
  11898. $mysync->{skipheader} = 'key1' ;
  11899. is( undef, header_construct( $mysync, $head, 'Host1', '1' ),
  11900. 'header_construct: key1 val1_key1 val3_key1 val2_key1 useheader ALL => undef' ) ;
  11901. $head = {
  11902. 'key1' => [ 'val1_key1', 'val3_key1', ' val2_key1' ],
  11903. 'key2' => [ 'val1_key2', 'val3_key2', ' val2_key2' ]
  11904. } ;
  11905. is( 'KEY2: VAL1_KEY2KEY2: VAL2_KEY2KEY2: VAL3_KEY2', header_construct( $mysync, $head, 'Host1', '1' ),
  11906. 'header_construct: ... useheader ALL skipheader key1 => KEY2: VAL1_KEY2KEY2: VAL2_KEY2KEY2: VAL3_KEY2' ) ;
  11907. note( 'Leaving tests_header_construct()' ) ;
  11908. return ;
  11909. }
  11910. # No global in header_construct
  11911. sub header_construct
  11912. {
  11913. my( $mysync, $head, $side, $m_uid ) = @_ ;
  11914. my @headstr ;
  11915. foreach my $h ( sort keys %{ $head } ) {
  11916. next if ( not ( exists $mysync->{useheader}->{ uc $h } )
  11917. and ( not exists $mysync->{useheader}->{ 'ALL' } )
  11918. ) ;
  11919. foreach my $val ( @{$head->{$h}} ) {
  11920. my $H = header_line_normalize( $h, $val ) ;
  11921. # show stuff in debug mode
  11922. $mysync->{ debug } and myprint( "$side uid $m_uid header [$H]", "\n" ) ;
  11923. if ( $mysync->{skipheader} and $H =~ m/$mysync->{skipheader}/xi) {
  11924. $mysync->{ debug } and myprint( "$side uid $m_uid skipping header [$H]\n" ) ;
  11925. next ;
  11926. }
  11927. push @headstr, $H ;
  11928. }
  11929. }
  11930. my $headstr = join( '', sort @headstr ) || undef ;
  11931. return( $headstr ) ;
  11932. }
  11933. sub header_line_normalize
  11934. {
  11935. my( $header_key, $header_val ) = @_ ;
  11936. # no 8-bit data in headers !
  11937. $header_val =~ s/[\x80-\xff]/X/xog;
  11938. # change tabulations to space (Gmail bug on with "Received:" on multilines)
  11939. $header_val =~ s/\t/\ /xgo ;
  11940. # remove the first blanks ( dbmail bug? )
  11941. $header_val =~ s/^\s*//xo;
  11942. # remove the last blanks ( Gmail bug )
  11943. $header_val =~ s/\s*$//xo;
  11944. # remove successive blanks ( Mailenable does it )
  11945. $header_val =~ s/\s+/ /xgo;
  11946. # remove Message-Id value domain part ( Mailenable changes it )
  11947. if ( ( $messageidnodomain ) and ( 'MESSAGE-ID' eq uc $header_key ) ) { $header_val =~ s/^([^@]+).*$/$1/xo ; }
  11948. # and uppercase header line
  11949. # (dbmail and dovecot)
  11950. my $header_line = uc "$header_key: $header_val" ;
  11951. return( $header_line ) ;
  11952. }
  11953. sub tests_header_line_normalize
  11954. {
  11955. note( 'Entering tests_header_line_normalize()' ) ;
  11956. ok( ': ' eq header_line_normalize( q{}, q{} ), 'header_line_normalize: empty args' ) ;
  11957. ok( 'HHH: VVV' eq header_line_normalize( 'hhh', 'vvv' ), 'header_line_normalize: hhh vvv ' ) ;
  11958. ok( 'HHH: VVV' eq header_line_normalize( 'hhh', ' vvv' ), 'header_line_normalize: remove first blancs' ) ;
  11959. ok( 'HHH: AA BB CCC D' eq header_line_normalize( 'hhh', 'aa bb ccc d' ), 'header_line_normalize: remove succesive blanks' ) ;
  11960. ok( 'HHH: AA BB CCC' eq header_line_normalize( 'hhh', 'aa bb ccc ' ), 'header_line_normalize: remove last blanks' ) ;
  11961. ok( 'HHH: VVV XX YY' eq header_line_normalize( 'hhh', "vvv\t\txx\tyy" ), 'header_line_normalize: tabs' ) ;
  11962. ok( 'HHH: XABX' eq header_line_normalize( 'hhh', "\x80AB\xff" ), 'header_line_normalize: 8bit' ) ;
  11963. note( 'Leaving tests_header_line_normalize()' ) ;
  11964. return ;
  11965. }
  11966. sub tests_firstline
  11967. {
  11968. note( 'Entering tests_firstline()' ) ;
  11969. is( q{}, firstline( 'W/tmp/tests/noexist.txt' ), 'firstline: getting empty string from inexisting W/tmp/tests/noexist.txt' ) ;
  11970. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'firstline: mkpath W/tmp/tests/' ) ;
  11971. is( "blabla\n" , string_to_file( "blabla\n", 'W/tmp/tests/firstline.txt' ), 'firstline: put blabla in W/tmp/tests/firstline.txt' ) ;
  11972. is( 'blabla' , firstline( 'W/tmp/tests/firstline.txt' ), 'firstline: get blabla from W/tmp/tests/firstline.txt' ) ;
  11973. is( q{} , string_to_file( q{}, 'W/tmp/tests/firstline2.txt' ), 'firstline: put empty string in W/tmp/tests/firstline2.txt' ) ;
  11974. is( q{} , firstline( 'W/tmp/tests/firstline2.txt' ), 'firstline: get empty string from W/tmp/tests/firstline2.txt' ) ;
  11975. is( "\n" , string_to_file( "\n", 'W/tmp/tests/firstline3.txt' ), 'firstline: put CR in W/tmp/tests/firstline3.txt' ) ;
  11976. is( q{} , firstline( 'W/tmp/tests/firstline3.txt' ), 'firstline: get empty string from W/tmp/tests/firstline3.txt' ) ;
  11977. 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' ) ;
  11978. is( 'blabla' , firstline( 'W/tmp/tests/firstline4.txt' ), 'firstline: get blabla from W/tmp/tests/firstline4.txt' ) ;
  11979. note( 'Leaving tests_firstline()' ) ;
  11980. return ;
  11981. }
  11982. sub firstline
  11983. {
  11984. # extract the first line of a file (without \n)
  11985. # return empty string if error or empty string
  11986. my $file = shift ;
  11987. my $line ;
  11988. $line = nthline( $file, 1 ) ;
  11989. return $line ;
  11990. }
  11991. sub tests_secondline
  11992. {
  11993. note( 'Entering tests_secondline()' ) ;
  11994. is( q{}, secondline( 'W/tmp/tests/noexist.txt' ), 'secondline: getting empty string from inexisting W/tmp/tests/noexist.txt' ) ;
  11995. is( q{}, secondline( 'W/tmp/tests/noexist.txt', 2 ), 'secondline: 2nd getting empty string from inexisting W/tmp/tests/noexist.txt' ) ;
  11996. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'secondline: mkpath W/tmp/tests/' ) ;
  11997. 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' ) ;
  11998. is( 'L2' , secondline( 'W/tmp/tests/secondline.txt' ), 'secondline: get L2 from W/tmp/tests/secondline.txt' ) ;
  11999. note( 'Leaving tests_secondline()' ) ;
  12000. return ;
  12001. }
  12002. sub secondline
  12003. {
  12004. # extract the second line of a file (without \n)
  12005. # return empty string if error or empty string
  12006. my $file = shift ;
  12007. my $line ;
  12008. $line = nthline( $file, 2 ) ;
  12009. return $line ;
  12010. }
  12011. sub tests_nthline
  12012. {
  12013. note( 'Entering tests_nthline()' ) ;
  12014. is( q{}, nthline( 'W/tmp/tests/noexist.txt' ), 'nthline: getting empty string from inexisting W/tmp/tests/noexist.txt' ) ;
  12015. is( q{}, nthline( 'W/tmp/tests/noexist.txt', 2 ), 'nthline: 2nd getting empty string from inexisting W/tmp/tests/noexist.txt' ) ;
  12016. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'nthline: mkpath W/tmp/tests/' ) ;
  12017. 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' ) ;
  12018. is( 'L3' , nthline( 'W/tmp/tests/nthline.txt', 3 ), 'nthline: get L3 from W/tmp/tests/nthline.txt' ) ;
  12019. note( 'Leaving tests_nthline()' ) ;
  12020. return ;
  12021. }
  12022. sub nthline
  12023. {
  12024. # extract the nth line of a file (without \n)
  12025. # return empty string if error or empty string
  12026. my $file = shift ;
  12027. my $num = shift ;
  12028. if ( ! all_defined( $file, $num ) ) { return q{} ; }
  12029. my $line ;
  12030. $line = ( file_to_array( $file ) )[$num - 1] ;
  12031. if ( ! defined $line )
  12032. {
  12033. return q{} ;
  12034. }
  12035. else
  12036. {
  12037. chomp $line ;
  12038. return $line ;
  12039. }
  12040. }
  12041. sub tests_file_to_array
  12042. {
  12043. note( 'Entering tests_file_to_array()' ) ;
  12044. is( undef, file_to_array( ), 'file_to_array: no args => undef' ) ;
  12045. is( undef, file_to_array( '/noexist' ), 'file_to_array: /noexist => undef' ) ;
  12046. is( undef, file_to_array( '/' ), 'file_to_array: reading a directory => undef' ) ;
  12047. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'file_to_array: mkpath W/tmp/tests/' ) ;
  12048. is( "L1\nL2\nL3\nL4\n" , string_to_file( "L1\nL2\nL3\nL4\n", 'W/tmp/tests/file_to_array.txt' ), 'file_to_array: put L1\nL2\nL3\nL4\n in W/tmp/tests/file_to_array.txt' ) ;
  12049. is_deeply( [ "L1\n", "L2\n", "L3\n", "L4\n" ] , [ file_to_array( 'W/tmp/tests/file_to_array.txt' ) ], 'file_to_array: get back L1\n L2\n L3\n L4\n from W/tmp/tests/file_to_array.txt' ) ;
  12050. note( 'Leaving tests_file_to_array()' ) ;
  12051. return ;
  12052. }
  12053. sub file_to_array
  12054. {
  12055. my( $file ) = shift ;
  12056. if ( ! $file ) { return ; }
  12057. if ( ! -e $file ) { return ; }
  12058. if ( ! -f $file ) { return ; }
  12059. if ( ! -r $file ) { return ; }
  12060. my @string ;
  12061. if ( open my $FILE, '<', $file )
  12062. {
  12063. @string = <$FILE> ;
  12064. close $FILE ;
  12065. return( @string ) ;
  12066. }
  12067. else
  12068. {
  12069. myprint( "Error reading file $file : $OS_ERROR\n" ) ;
  12070. return ;
  12071. }
  12072. }
  12073. sub tests_file_to_string
  12074. {
  12075. note( 'Entering tests_file_to_string()' ) ;
  12076. is( undef, file_to_string( ), 'file_to_string: no args => undef' ) ;
  12077. is( undef, file_to_string( '/noexist' ), 'file_to_string: /noexist => undef' ) ;
  12078. is( undef, file_to_string( '/' ), 'file_to_string: reading a directory => undef' ) ;
  12079. ok( file_to_string( $PROGRAM_NAME ), 'file_to_string: reading myself' ) ;
  12080. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'file_to_string: mkpath W/tmp/tests/' ) ;
  12081. is( 'lilili', string_to_file( 'lilili', 'W/tmp/tests/canbewritten' ), 'file_to_string: string_to_file filling W/tmp/tests/canbewritten with lilili' ) ;
  12082. is( 'lilili', file_to_string( 'W/tmp/tests/canbewritten' ), 'file_to_string: reading W/tmp/tests/canbewritten is lilili' ) ;
  12083. is( q{}, string_to_file( q{}, 'W/tmp/tests/empty' ), 'file_to_string: string_to_file filling W/tmp/tests/empty with empty string' ) ;
  12084. is( q{}, file_to_string( 'W/tmp/tests/empty' ), 'file_to_string: reading W/tmp/tests/empty is empty' ) ;
  12085. note( 'Leaving tests_file_to_string()' ) ;
  12086. return ;
  12087. }
  12088. sub file_to_string
  12089. {
  12090. my $file = shift ;
  12091. if ( ! $file ) { return ; }
  12092. if ( ! -e $file ) { return ; }
  12093. if ( ! -f $file ) { return ; }
  12094. if ( ! -r $file ) { return ; }
  12095. return( join q{}, file_to_array( $file ) ) ;
  12096. }
  12097. sub tests_string_to_file
  12098. {
  12099. note( 'Entering tests_string_to_file()' ) ;
  12100. is( undef, string_to_file( ), 'string_to_file: no args => undef' ) ;
  12101. is( undef, string_to_file( 'lalala' ), 'string_to_file: one arg => undef' ) ;
  12102. is( undef, string_to_file( 'lalala', '.' ), 'string_to_file: writing a directory => undef' ) ;
  12103. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'string_to_file: mkpath W/tmp/tests/' ) ;
  12104. is( 'lalala', string_to_file( 'lalala', 'W/tmp/tests/canbewritten' ), 'string_to_file: W/tmp/tests/canbewritten with lalala' ) ;
  12105. is( q{}, string_to_file( q{}, 'W/tmp/tests/empty' ), 'string_to_file: W/tmp/tests/empty with empty string' ) ;
  12106. SKIP: {
  12107. Readonly my $NB_UNX_tests_string_to_file => 1 ;
  12108. skip( 'Not on Unix non-root', $NB_UNX_tests_string_to_file ) if ('MSWin32' eq $OSNAME or '0' eq $EFFECTIVE_USER_ID ) ;
  12109. is( undef, string_to_file( 'lalala', '/cantouch' ), 'string_to_file: /cantouch denied => undef' ) ;
  12110. }
  12111. note( 'Leaving tests_string_to_file()' ) ;
  12112. return ;
  12113. }
  12114. sub string_to_file
  12115. {
  12116. my( $string, $file ) = @_ ;
  12117. if( ! defined $string ) { return ; }
  12118. if( ! defined $file ) { return ; }
  12119. if ( ! -e $file && ! -w dirname( $file ) ) {
  12120. myprint( "string_to_file: directory of $file is not writable\n" ) ;
  12121. return ;
  12122. }
  12123. if ( ! sysopen( FILE, $file, O_WRONLY|O_TRUNC|O_CREAT, 0600) ) {
  12124. myprint( "string_to_file: failure writing to $file with error: $OS_ERROR\n" ) ;
  12125. return ;
  12126. }
  12127. print FILE $string ;
  12128. close FILE ;
  12129. return $string ;
  12130. }
  12131. 0 and <<'MULTILINE_COMMENT' ;
  12132. This is a multiline comment.
  12133. Based on David Carter discussion, to do:
  12134. * Call parameters stay the same.
  12135. * Now always "return( $string, $error )". Descriptions below.
  12136. OK * Still capture STDOUT via "1> $output_tmpfile" to finish in $string and "return( $string, $error )"
  12137. OK * Now also capture STDERR via "2> $error_tmpfile" to finish in $error and "return( $string, $error )"
  12138. OK * in case of CHILD_ERROR, return( undef, $error )
  12139. and print $error, with folder/UID/maybeSubject context,
  12140. on console and at the end with the final error listing. Count this as a sync error.
  12141. * in case of good command, take final $string as is, unless void. In case $error with value then print it.
  12142. * in case of good command and final $string empty, consider it like CHILD_ERROR =>
  12143. return( undef, $error ) and print $error, with folder/UID/maybeSubject context,
  12144. on console and at the end with the final error listing. Count this as a sync error.
  12145. MULTILINE_COMMENT
  12146. # End of multiline comment.
  12147. sub pipemess
  12148. {
  12149. my ( $string, @commands ) = @_ ;
  12150. my $error = q{} ;
  12151. foreach my $command ( @commands ) {
  12152. my $input_tmpfile = "$sync->{ tmpdir }/imapsync_tmp_file.$PROCESS_ID.inp.txt" ;
  12153. my $output_tmpfile = "$sync->{ tmpdir }/imapsync_tmp_file.$PROCESS_ID.out.txt" ;
  12154. my $error_tmpfile = "$sync->{ tmpdir }/imapsync_tmp_file.$PROCESS_ID.err.txt" ;
  12155. string_to_file( $string, $input_tmpfile ) ;
  12156. ` $command < $input_tmpfile 1> $output_tmpfile 2> $error_tmpfile ` ;
  12157. my $is_command_ko = $CHILD_ERROR ;
  12158. my $error_cmd = file_to_string( $error_tmpfile ) ;
  12159. chomp( $error_cmd ) ;
  12160. $string = file_to_string( $output_tmpfile ) ;
  12161. my $string_len = length( $string ) ;
  12162. unlink $input_tmpfile, $output_tmpfile, $error_tmpfile ;
  12163. if ( $is_command_ko or ( ! $string_len ) ) {
  12164. my $cmd_exit_value = $CHILD_ERROR >> 8 ;
  12165. my $cmd_end_signal = $CHILD_ERROR & 127 ;
  12166. my $signal_log = ( $cmd_end_signal ) ? " signal $cmd_end_signal and" : q{} ;
  12167. 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} ;
  12168. myprint( $error_log ) ;
  12169. if ( wantarray ) {
  12170. return @{ [ undef, $error_log ] }
  12171. }else{
  12172. return ;
  12173. }
  12174. }
  12175. if ( $error_cmd ) {
  12176. $error .= qq{STDERR of --pipemess "$command": $error_cmd\n} ;
  12177. myprint( qq{STDERR of --pipemess "$command": $error_cmd\n} ) ;
  12178. }
  12179. }
  12180. #myprint( "[$string]\n" ) ;
  12181. if ( wantarray ) {
  12182. return ( $string, $error ) ;
  12183. }else{
  12184. return $string ;
  12185. }
  12186. }
  12187. sub tests_pipemess
  12188. {
  12189. note( 'Entering tests_pipemess()' ) ;
  12190. SKIP: {
  12191. Readonly my $NB_WIN_tests_pipemess => 3 ;
  12192. skip( 'Not on MSWin32', $NB_WIN_tests_pipemess ) if ('MSWin32' ne $OSNAME) ;
  12193. # Windows
  12194. # "type" command does not accept redirection of STDIN with <
  12195. # "sort" does
  12196. ok( "nochange\n" eq pipemess( 'nochange', 'sort' ), 'pipemess: nearly no change by sort' ) ;
  12197. ok( "nochange2\n" eq pipemess( 'nochange2', qw( sort sort ) ), 'pipemess: nearly no change by sort,sort' ) ;
  12198. # command not found
  12199. #diag( 'Warning and failure about cacaprout are on purpose' ) ;
  12200. ok( ! defined( pipemess( q{}, 'cacaprout' ) ), 'pipemess: command not found' ) ;
  12201. } ;
  12202. my ( $stringT, $errorT ) ;
  12203. SKIP: {
  12204. Readonly my $NB_UNX_tests_pipemess => 25 ;
  12205. skip( 'Not on Unix', $NB_UNX_tests_pipemess ) if ('MSWin32' eq $OSNAME) ;
  12206. # Unix
  12207. ok( 'nochange' eq pipemess( 'nochange', 'cat' ), 'pipemess: no change by cat' ) ;
  12208. ok( 'nochange2' eq pipemess( 'nochange2', 'cat', 'cat' ), 'pipemess: no change by cat,cat' ) ;
  12209. ok( " 1\tnumberize\n" eq pipemess( "numberize\n", 'cat -n' ), 'pipemess: numberize by cat -n' ) ;
  12210. ok( " 1\tnumberize\n 2\tnumberize\n" eq pipemess( "numberize\nnumberize\n", 'cat -n' ), 'pipemess: numberize by cat -n' ) ;
  12211. ok( "A\nB\nC\n" eq pipemess( "A\nC\nB\n", 'sort' ), 'pipemess: sort' ) ;
  12212. # command not found
  12213. #diag( 'Warning and failure about cacaprout are on purpose' ) ;
  12214. is( undef, pipemess( q{}, 'cacaprout' ), 'pipemess: command not found' ) ;
  12215. # success with true but no output at all
  12216. is( undef, pipemess( q{blabla}, 'true' ), 'pipemess: true but no output' ) ;
  12217. # failure with false and no output at all
  12218. is( undef, pipemess( q{blabla}, 'false' ), 'pipemess: false and no output' ) ;
  12219. # Failure since pipemess is not a real pipe, so first cat wait for standard input
  12220. is( q{blabla}, pipemess( q{blabla}, '( cat|cat ) ' ), 'pipemess: ok by ( cat|cat )' ) ;
  12221. ( $stringT, $errorT ) = pipemess( 'nochange', 'cat' ) ;
  12222. is( $stringT, 'nochange', 'pipemess: list context, no change by cat, string' ) ;
  12223. is( $errorT, q{}, 'pipemess: list context, no change by cat, no error' ) ;
  12224. ( $stringT, $errorT ) = pipemess( 'dontcare', 'true' ) ;
  12225. is( $stringT, undef, 'pipemess: list context, true but no output, string' ) ;
  12226. 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' ) ;
  12227. ( $stringT, $errorT ) = pipemess( 'dontcare', 'false' ) ;
  12228. is( $stringT, undef, 'pipemess: list context, false and no output, string' ) ;
  12229. like( $errorT, qr{\QFailure: --pipemess command "false" ended with "0" characters exit value "1" and STDERR ""\E}xm,
  12230. 'pipemess: list context, false and no output, error' ) ;
  12231. ( $stringT, $errorT ) = pipemess( 'dontcare', '/bin/echo -n blablabla' ) ;
  12232. is( $stringT, q{blablabla}, 'pipemess: list context, "echo -n blablabla", string' ) ;
  12233. is( $errorT, q{}, 'pipemess: list context, "echo blablabla", error' ) ;
  12234. ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo -n blablabla 3>&1 1>&2 2>&3 )' ) ;
  12235. is( $stringT, undef, 'pipemess: list context, "no output STDERR blablabla", string' ) ;
  12236. like( $errorT, qr{blablabla"}xm, 'pipemess: list context, "no output STDERR blablabla", error' ) ;
  12237. ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo -n blablabla 3>&1 1>&2 2>&3 )', 'false' ) ;
  12238. is( $stringT, undef, 'pipemess: list context, "no output STDERR blablabla then false", string' ) ;
  12239. like( $errorT, qr{blablabla"}xm, 'pipemess: list context, "no output STDERR blablabla then false", error' ) ;
  12240. ( $stringT, $errorT ) = pipemess( 'dontcare', 'false', '( echo -n blablabla 3>&1 1>&2 2>&3 )' ) ;
  12241. is( $stringT, undef, 'pipemess: list context, "false then STDERR blablabla", string' ) ;
  12242. like( $errorT, qr{\QFailure: --pipemess command "false" ended with "0" characters exit value "1" and STDERR ""\E}xm,
  12243. 'pipemess: list context, "false then STDERR blablabla", error' ) ;
  12244. ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo rrrrr ; echo -n error_blablabla 3>&1 1>&2 2>&3 )' ) ;
  12245. like( $stringT, qr{rrrrr}xm, 'pipemess: list context, "STDOUT rrrrr STDERR error_blablabla", string' ) ;
  12246. like( $errorT, qr{STDERR.*error_blablabla}xm, 'pipemess: list context, "STDOUT rrrrr STDERR error_blablabla", error' ) ;
  12247. }
  12248. ( $stringT, $errorT ) = pipemess( 'dontcare', 'cacaprout' ) ;
  12249. is( $stringT, undef, 'pipemess: list context, cacaprout not found, string' ) ;
  12250. like( $errorT, qr{\QFailure: --pipemess command "cacaprout" ended with "0" characters exit value\E}xm,
  12251. 'pipemess: list context, cacaprout not found, error' ) ;
  12252. note( 'Leaving tests_pipemess()' ) ;
  12253. return ;
  12254. }
  12255. sub tests_is_a_release_number
  12256. {
  12257. note( 'Entering tests_is_a_release_number()' ) ;
  12258. is( undef, is_a_release_number( ), 'is_a_release_number: no args => undef' ) ;
  12259. ok( is_a_release_number( $RELEASE_NUMBER_EXAMPLE_1 ), 'is_a_release_number 1.351' ) ;
  12260. ok( is_a_release_number( $RELEASE_NUMBER_EXAMPLE_2 ), 'is_a_release_number 42.4242' ) ;
  12261. ok( is_a_release_number( imapsync_version( $sync ) ), 'is_a_release_number imapsync_version( )' ) ;
  12262. ok( ! is_a_release_number( 'blabla' ), '! is_a_release_number blabla' ) ;
  12263. note( 'Leaving tests_is_a_release_number()' ) ;
  12264. return ;
  12265. }
  12266. sub is_a_release_number
  12267. {
  12268. my $number = shift ;
  12269. if ( ! defined $number ) { return ; }
  12270. return( $number =~ m{^\d+\.\d+$}xo ) ;
  12271. }
  12272. sub imapsync_version_public
  12273. {
  12274. my $local_version = imapsync_version( $sync ) ;
  12275. my $imapsync_basename = imapsync_basename( ) ;
  12276. my $context = imapsync_context( ) ;
  12277. my $agent_info = "$OSNAME system, perl "
  12278. . mysprintf( '%vd', $PERL_VERSION)
  12279. . ", Mail::IMAPClient $Mail::IMAPClient::VERSION"
  12280. . " $imapsync_basename"
  12281. . " $context" ;
  12282. my $sock = IO::Socket::INET->new(
  12283. PeerAddr => 'imapsync.lamiral.info',
  12284. PeerPort => 80,
  12285. Proto => 'tcp',
  12286. ) ;
  12287. return( 'unknown' ) if not $sock ;
  12288. print $sock
  12289. "GET /prj/imapsync/VERSION HTTP/1.0\r\n",
  12290. "User-Agent: imapsync/$local_version ($agent_info)\r\n",
  12291. "Host: ks.lamiral.info\r\n\r\n" ;
  12292. my @line = <$sock> ;
  12293. close $sock ;
  12294. my $last_release = $line[$LAST] ;
  12295. chomp $last_release ;
  12296. return( $last_release ) ;
  12297. }
  12298. sub not_long_imapsync_version_public
  12299. {
  12300. #myprint( "Entering not_long_imapsync_version_public\n" ) ;
  12301. my $fake = shift ;
  12302. if ( $fake ) { return $fake }
  12303. my $val ;
  12304. # Doesn't work with gethostbyname (see perlipc)
  12305. #local $SIG{ALRM} = sub { die "alarm\n" } ;
  12306. if ('MSWin32' eq $OSNAME) {
  12307. local $SIG{ALRM} = sub { die "alarm\n" } ;
  12308. }else{
  12309. POSIX::sigaction(SIGALRM,
  12310. POSIX::SigAction->new(sub { croak 'alarm' } ) )
  12311. or myprint( "Error setting SIGALRM handler: $OS_ERROR\n" ) ;
  12312. }
  12313. my $ret = eval {
  12314. alarm 3 ;
  12315. {
  12316. $val = imapsync_version_public( ) ;
  12317. #sleep 4 ;
  12318. #myprint( "End of imapsync_version_public\n" ) ;
  12319. }
  12320. alarm 0 ;
  12321. 1 ;
  12322. } ;
  12323. #myprint( "eval [$ret]\n" ) ;
  12324. if ( ( not $ret ) or $EVAL_ERROR ) {
  12325. #myprint( "$EVAL_ERROR" ) ;
  12326. if ($EVAL_ERROR =~ /alarm/) {
  12327. # timed out
  12328. return('timeout') ;
  12329. }else{
  12330. alarm 0 ;
  12331. return( 'unknown' ) ; # propagate unexpected errors
  12332. }
  12333. }else {
  12334. # Good!
  12335. return( $val ) ;
  12336. }
  12337. }
  12338. sub tests_not_long_imapsync_version_public
  12339. {
  12340. note( 'Entering tests_not_long_imapsync_version_public()' ) ;
  12341. is( 1, is_a_release_number( not_long_imapsync_version_public( ) ),
  12342. 'not_long_imapsync_version_public: public release is a number' ) ;
  12343. note( 'Leaving tests_not_long_imapsync_version_public()' ) ;
  12344. return ;
  12345. }
  12346. sub check_last_release
  12347. {
  12348. my $fake = shift ;
  12349. my $public_release = not_long_imapsync_version_public( $fake ) ;
  12350. $sync->{ debug } and myprint( "check_last_release: [$public_release]\n" ) ;
  12351. my $inline_help_when_on = '( Use --noreleasecheck to avoid this release check. )' ;
  12352. if ( $public_release eq 'unknown' ) {
  12353. return( 'Imapsync public release is unknown.' . $inline_help_when_on ) ;
  12354. }
  12355. if ( $public_release eq 'timeout' ) {
  12356. return( 'Imapsync public release is unknown (timeout).' . $inline_help_when_on ) ;
  12357. }
  12358. if ( ! is_a_release_number( $public_release ) ) {
  12359. return( "Imapsync public release is unknown ($public_release)." . $inline_help_when_on ) ;
  12360. }
  12361. my $imapsync_here = imapsync_version( $sync ) ;
  12362. if ( $public_release > $imapsync_here ) {
  12363. return( 'This imapsync is not up to date. ' . "( local $imapsync_here < official $public_release )" . $inline_help_when_on ) ;
  12364. }else{
  12365. return( 'This imapsync is up to date. ' . "( local $imapsync_here >= official $public_release )" . $inline_help_when_on ) ;
  12366. }
  12367. return( 'really unknown' ) ; # Should never arrive here
  12368. }
  12369. sub tests_check_last_release
  12370. {
  12371. note( 'Entering tests_check_last_release()' ) ;
  12372. diag( check_last_release( 1.1 ) ) ;
  12373. # \Q \E here to avoid putting \ before each space
  12374. like( check_last_release( 1.1 ), qr/\Qis up to date\E/mxs, 'check_last_release: up to date' ) ;
  12375. like( check_last_release( 1.1 ), qr/1\.1/mxs, 'check_last_release: up to date, include number' ) ;
  12376. diag( check_last_release( 999.999 ) ) ;
  12377. like( check_last_release( 999.999 ), qr/\Qnot up to date\E/mxs, 'check_last_release: not up to date' ) ;
  12378. like( check_last_release( 999.999 ), qr/999\.999/mxs, 'check_last_release: not up to date, include number' ) ;
  12379. like( check_last_release( 'unknown' ), qr/\QImapsync public release is unknown\E/mxs, 'check_last_release: unknown' ) ;
  12380. like( check_last_release( 'timeout' ), qr/\QImapsync public release is unknown (timeout)\E/mxs, 'check_last_release: timeout' ) ;
  12381. like( check_last_release( 'lalala' ), qr/\QImapsync public release is unknown (lalala)\E/mxs, 'check_last_release: lalala' ) ;
  12382. diag( check_last_release( ) ) ;
  12383. note( 'Leaving tests_check_last_release()' ) ;
  12384. return ;
  12385. }
  12386. sub tests_imapsync_context
  12387. {
  12388. note( 'Entering tests_imapsync_context()' ) ;
  12389. like( imapsync_context( ), qr/^CGI|^Docker|^DockerCGI|^Standard/, 'imapsync_context: CGI or Docker or DockerCGI or Standard' ) ;
  12390. note( 'Leaving tests_imapsync_context()' ) ;
  12391. return ;
  12392. }
  12393. sub imapsync_context
  12394. {
  12395. my $mysync = shift ;
  12396. my $context = q{} ;
  12397. if ( under_docker_context( $mysync ) && under_cgi_context( $mysync ) )
  12398. {
  12399. $context = 'DockerCGI' ;
  12400. }
  12401. elsif ( under_docker_context( $mysync ) )
  12402. {
  12403. $context = 'Docker' ;
  12404. }
  12405. elsif ( under_cgi_context( $mysync ) )
  12406. {
  12407. $context = 'CGI' ;
  12408. }
  12409. else
  12410. {
  12411. $context = 'Standard' ;
  12412. }
  12413. return $context ;
  12414. }
  12415. sub imapsync_version
  12416. {
  12417. my $mysync = shift ;
  12418. my $rcs = $mysync->{rcs} ;
  12419. my $version ;
  12420. $version = version_from_rcs( $rcs ) ;
  12421. return( $version ) ;
  12422. }
  12423. sub tests_version_from_rcs
  12424. {
  12425. note( 'Entering tests_version_from_rcs()' ) ;
  12426. is( undef, version_from_rcs( ), 'version_from_rcs: no args => undef' ) ;
  12427. 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' ) ;
  12428. is( 'UNKNOWN', version_from_rcs( 1.831 ), 'version_from_rcs: 1.831 => UNKNOWN' ) ;
  12429. note( 'Leaving tests_version_from_rcs()' ) ;
  12430. return ;
  12431. }
  12432. sub version_from_rcs
  12433. {
  12434. my $rcs = shift ;
  12435. if ( ! $rcs ) { return ; }
  12436. my $version = 'UNKNOWN' ;
  12437. if ( $rcs =~ m{,v\s+(\d+\.\d+)}mxso ) {
  12438. $version = $1
  12439. }
  12440. return( $version ) ;
  12441. }
  12442. sub tests_imapsync_basename
  12443. {
  12444. note( 'Entering tests_imapsync_basename()' ) ;
  12445. ok( imapsync_basename() =~ m/imapsync/, 'imapsync_basename: match imapsync');
  12446. ok( 'blabla' ne imapsync_basename(), 'imapsync_basename: do not equal blabla');
  12447. note( 'Leaving tests_imapsync_basename()' ) ;
  12448. return ;
  12449. }
  12450. sub imapsync_basename
  12451. {
  12452. return basename( $PROGRAM_NAME ) ;
  12453. }
  12454. sub localhost_info
  12455. {
  12456. my $mysync = shift ;
  12457. my( $infos ) = join( q{},
  12458. "Here is imapsync ", imapsync_version( $mysync ),
  12459. " on host " . hostname(),
  12460. ", a $OSNAME system with ",
  12461. ram_memory_info( ),
  12462. "\n",
  12463. 'with Perl ',
  12464. mysprintf( '%vd ', $PERL_VERSION),
  12465. "and Mail::IMAPClient $Mail::IMAPClient::VERSION",
  12466. ) ;
  12467. return( $infos ) ;
  12468. }
  12469. sub tests_cpu_number
  12470. {
  12471. note( 'Entering tests_cpu_number()' ) ;
  12472. is( 1, is_integer( cpu_number( ) ), "cpu_number: is_integer" ) ;
  12473. ok( 1 <= cpu_number( ), "cpu_number: 1 or more" ) ;
  12474. is( 1, cpu_number( 1 ), "cpu_number: 1 => 1" ) ;
  12475. is( 1, cpu_number( $MINUS_ONE ), "cpu_number: -1 => 1" ) ;
  12476. is( 1, cpu_number( 'lalala' ), "cpu_number: lalala => 1" ) ;
  12477. is( $NUMBER_42, cpu_number( $NUMBER_42 ), "cpu_number: $NUMBER_42 => $NUMBER_42" ) ;
  12478. note( "cpu_number = " . cpu_number( ) . "\n" ) ;
  12479. note( "hostname = " . hostname( ) . "\n" ) ;
  12480. SKIP: {
  12481. if ( ! ( 'i005' eq hostname() ) )
  12482. {
  12483. skip( 'cpu_number on host != i005 (FreeBSD)', 1 ) ;
  12484. }
  12485. is( 4, cpu_number( ), "cpu_number: on i005 (FreeBSD) => 4" ) ;
  12486. } ;
  12487. SKIP: {
  12488. if ( ! ( 'petite' eq hostname() ) )
  12489. {
  12490. skip( 'cpu_number on host != petite (Linux)', 1 ) ;
  12491. }
  12492. is( 2, cpu_number( ), "cpu_number: on petite (Linux) => 2" ) ;
  12493. } ;
  12494. SKIP: {
  12495. if ( ! ( skip_macosx( ) ) )
  12496. {
  12497. skip( 'cpu_number on host != polarhome macosx (Darwin MacOS X 10.7.5 Lion)', 1 ) ;
  12498. }
  12499. is( 2, cpu_number( ), "cpu_number: on polarhome macosx (Darwin MacOS X 10.7.5 Lion) => 2" ) ;
  12500. } ;
  12501. SKIP: {
  12502. if ( ! ( 'pcHPDV7-HP' eq hostname() ) )
  12503. {
  12504. skip( 'cpu_number on host != pcHPDV7-HP (Windows 7, 64bits)', 1 ) ;
  12505. }
  12506. is( 2, cpu_number( ), "cpu_number: on pcHPDV7-HP (Windows 7, 64bits) => 2" ) ;
  12507. } ;
  12508. SKIP: {
  12509. if ( ! ( 'CUILLERE' eq hostname() ) )
  12510. {
  12511. skip( 'cpu_number on host != CUILLERE (Windows XP, 32bits)', 1 ) ;
  12512. }
  12513. is( 1, cpu_number( ), "cpu_number: on CUILLERE (Windows XP, 32bits) => 1" ) ;
  12514. } ;
  12515. note( 'Leaving tests_cpu_number()' ) ;
  12516. return ;
  12517. }
  12518. sub cpu_number {
  12519. my $cpu_number_forced = shift ;
  12520. # Well, here 1 is better than 0 or undef
  12521. my $cpu_number = 1 ; # Default value, erased if better found
  12522. my @cpuinfo ;
  12523. if ( $ENV{"NUMBER_OF_PROCESSORS"} )
  12524. {
  12525. # might be under a Windows system
  12526. $cpu_number = $ENV{"NUMBER_OF_PROCESSORS"} ;
  12527. #myprint( "Number of processors found by env var NUMBER_OF_PROCESSORS: $cpu_number\n" ) ;
  12528. }
  12529. if ( 'darwin' eq $OSNAME )
  12530. {
  12531. $cpu_number = backtick( "sysctl -n hw.ncpu" ) ;
  12532. chomp( $cpu_number ) ;
  12533. #myprint( "Number of processors found by cmd 'sysctl -n hw.ncpu': $cpu_number\n" ) ;
  12534. }
  12535. if ( 'freebsd' eq $OSNAME )
  12536. {
  12537. $cpu_number = backtick( "sysctl -n kern.smp.cpus" ) ;
  12538. chomp( $cpu_number ) ;
  12539. #myprint( "Number of processors found by cmd 'sysctl -n kern.smp.cpus': $cpu_number\n" ) ;
  12540. }
  12541. if ( 'linux' eq $OSNAME && -e '/proc/cpuinfo' )
  12542. {
  12543. @cpuinfo = file_to_array( '/proc/cpuinfo' ) ;
  12544. $cpu_number = grep { /^processor/mxs } @cpuinfo ;
  12545. #myprint( "Number of processors found via /proc/cpuinfo: $cpu_number\n" ) ;
  12546. }
  12547. if ( defined $cpu_number_forced )
  12548. {
  12549. $cpu_number = $cpu_number_forced ;
  12550. }
  12551. return( integer_or_1( $cpu_number ) ) ;
  12552. }
  12553. sub tests_integer_or_1
  12554. {
  12555. note( 'Entering tests_integer_or_1()' ) ;
  12556. is( 1, integer_or_1( ), 'integer_or_1: no args => 1' ) ;
  12557. is( 1, integer_or_1( undef ), 'integer_or_1: undef => 1' ) ;
  12558. is( $NUMBER_10, integer_or_1( $NUMBER_10 ), 'integer_or_1: 10 => 10' ) ;
  12559. is( 1, integer_or_1( q{} ), 'integer_or_1: empty string => 1' ) ;
  12560. is( 1, integer_or_1( 'lalala' ), 'integer_or_1: lalala => 1' ) ;
  12561. note( 'Leaving tests_integer_or_1()' ) ;
  12562. return ;
  12563. }
  12564. sub integer_or_1
  12565. {
  12566. my $number = shift ;
  12567. if ( is_integer( $number ) ) {
  12568. return $number ;
  12569. }
  12570. # else
  12571. return 1 ;
  12572. }
  12573. sub tests_is_integer
  12574. {
  12575. note( 'Entering tests_is_integer()' ) ;
  12576. is( undef, is_integer( ), 'is_integer: no args => undef ' ) ;
  12577. ok( is_integer( 1 ), 'is_integer: 1 => yes ') ;
  12578. ok( is_integer( $NUMBER_42 ), 'is_integer: 42 => yes ') ;
  12579. ok( is_integer( "$NUMBER_42" ), 'is_integer: "$NUMBER_42" => yes ') ;
  12580. ok( is_integer( '42' ), 'is_integer: "42" => yes ') ;
  12581. ok( is_integer( $NUMBER_104_857_600 ), 'is_integer: 104_857_600 => yes') ;
  12582. ok( is_integer( "$NUMBER_104_857_600" ), 'is_integer: "$NUMBER_104_857_600" => yes') ;
  12583. ok( is_integer( '104857600' ), 'is_integer: 104857600 => yes') ;
  12584. ok( ! is_integer( 'blabla' ), 'is_integer: blabla => no' ) ;
  12585. ok( ! is_integer( q{} ), 'is_integer: empty string => no' ) ;
  12586. note( 'Leaving tests_is_integer()' ) ;
  12587. return ;
  12588. }
  12589. sub is_integer
  12590. {
  12591. my $number = shift ;
  12592. if ( ! defined $number ) { return ; }
  12593. return( $number =~ m{^\d+$}xo ) ;
  12594. }
  12595. sub tests_loadavg
  12596. {
  12597. note( 'Entering tests_loadavg()' ) ;
  12598. SKIP: {
  12599. skip( 'Tests for darwin', 3 ) if ('darwin' ne $OSNAME) ;
  12600. is( undef, loadavg( '/noexist' ), 'loadavg: /noexist => undef' ) ;
  12601. is_deeply(
  12602. [ '0.11', '0.22', '0.33' ],
  12603. [ loadavg( 'vm.loadavg: { 0.11 0.22 0.33 }' ) ],
  12604. 'loadavg: "vm.loadavg: { 0.11 0.22 0.33 }" => 0.11 0.22 0.33'
  12605. ) ;
  12606. note( join( " ", "loadavg:", loadavg( ) ) ) ;
  12607. is( 3, scalar( my @loadavg = loadavg( ) ), 'loadavg: 3 values' ) ;
  12608. } ;
  12609. SKIP: {
  12610. skip( 'Tests for linux', 3 ) if ('linux' ne $OSNAME) ;
  12611. is( undef, loadavg( '/noexist' ), 'loadavg: /noexist => undef' ) ;
  12612. ok( loadavg( ), 'loadavg: no args' ) ;
  12613. is_deeply( [ '0.39', '0.30', '0.37', '1/602' ],
  12614. [ loadavg( '0.39 0.30 0.37 1/602 6073' ) ],
  12615. 'loadavg 0.39 0.30 0.37 1/602 6073 => [0.39, 0.30, 0.37, 1/602]' ) ;
  12616. } ;
  12617. SKIP: {
  12618. skip( 'Tests for Windows', 1 ) if ('MSWin32' ne $OSNAME) ;
  12619. is_deeply( [ 0 ],
  12620. [ loadavg( ) ],
  12621. 'loadavg on MSWin32 => 0' ) ;
  12622. } ;
  12623. note( 'Leaving tests_loadavg()' ) ;
  12624. return ;
  12625. }
  12626. sub loadavg
  12627. {
  12628. if ( 'linux' eq $OSNAME ) {
  12629. return ( loadavg_linux( @ARG ) ) ;
  12630. }
  12631. if ( 'freebsd' eq $OSNAME ) {
  12632. return ( loadavg_freebsd( @ARG ) ) ;
  12633. }
  12634. if ( 'darwin' eq $OSNAME ) {
  12635. return ( loadavg_darwin( @ARG ) ) ;
  12636. }
  12637. if ( 'MSWin32' eq $OSNAME ) {
  12638. return ( loadavg_windows( @ARG ) ) ;
  12639. }
  12640. return( 'unknown' ) ;
  12641. }
  12642. sub loadavg_linux
  12643. {
  12644. my $line = shift ;
  12645. if ( ! $line ) {
  12646. $line = firstline( '/proc/loadavg' ) or return ;
  12647. }
  12648. my ( $avg_1_min, $avg_5_min, $avg_15_min, $current_runs ) = split /\s/mxs, $line ;
  12649. if ( all_defined( $avg_1_min, $avg_5_min, $avg_15_min ) ) {
  12650. $sync->{ debug } and myprint( "System load: $avg_1_min $avg_5_min $avg_15_min $current_runs\n" ) ;
  12651. return ( $avg_1_min, $avg_5_min, $avg_15_min, $current_runs ) ;
  12652. }
  12653. return ;
  12654. }
  12655. sub loadavg_freebsd
  12656. {
  12657. my $file = shift ;
  12658. # Example of output of command "sysctl vm.loadavg":
  12659. # vm.loadavg: { 0.15 0.08 0.08 }
  12660. my $loadavg ;
  12661. if ( ! defined $file ) {
  12662. eval {
  12663. $loadavg = `/sbin/sysctl vm.loadavg` ;
  12664. #myprint( "LOADAVG FREEBSD: $loadavg\n" ) ;
  12665. } ;
  12666. if ( $EVAL_ERROR ) { myprint( "[$EVAL_ERROR]\n" ) ; return ; }
  12667. }else{
  12668. $loadavg = firstline( $file ) or return ;
  12669. }
  12670. my ( $avg_1_min, $avg_5_min, $avg_15_min )
  12671. = $loadavg =~ /vm\.loadavg\s*[:=]\s*\{?\s*(\d+\.?\d*)\s+(\d+\.?\d*)\s+(\d+\.?\d*)/mxs ;
  12672. $sync->{ debug } and myprint( "System load: $avg_1_min $avg_5_min $avg_15_min\n" ) ;
  12673. return ( $avg_1_min, $avg_5_min, $avg_15_min ) ;
  12674. }
  12675. sub loadavg_darwin
  12676. {
  12677. my $line = shift ;
  12678. # Example of output of command "sysctl vm.loadavg":
  12679. # vm.loadavg: { 0.15 0.08 0.08 }
  12680. my $loadavg ;
  12681. if ( ! defined $line ) {
  12682. eval {
  12683. # $loadavg = `/usr/sbin/sysctl vm.loadavg` ;
  12684. $loadavg = `LANG= /usr/sbin/sysctl vm.loadavg` ;
  12685. #myprint( "LOADAVG DARWIN: $loadavg\n" ) ;
  12686. } ;
  12687. if ( $EVAL_ERROR ) { myprint( "[$EVAL_ERROR]\n" ) ; return ; }
  12688. }else{
  12689. $loadavg = $line ;
  12690. }
  12691. my ( $avg_1_min, $avg_5_min, $avg_15_min )
  12692. = $loadavg =~ /vm\.loadavg\s*[:=]\s*\{?\s*(\d+\.?\d*)\s+(\d+\.?\d*)\s+(\d+\.?\d*)/mxs ;
  12693. #$sync->{ debug } and myprint( "System load: $avg_1_min $avg_5_min $avg_15_min\n" ) ;
  12694. return ( $avg_1_min, $avg_5_min, $avg_15_min ) ;
  12695. }
  12696. sub loadavg_windows
  12697. {
  12698. my $file = shift ;
  12699. # Example of output of command "wmic cpu get loadpercentage":
  12700. # LoadPercentage
  12701. # 12
  12702. my $loadavg ;
  12703. if ( ! defined $file ) {
  12704. eval {
  12705. #$loadavg = `CMD wmic cpu get loadpercentage` ;
  12706. $loadavg = "LoadPercentage\n0\n" ;
  12707. #myprint( "LOADAVG WIN: $loadavg\n" ) ;
  12708. } ;
  12709. if ( $EVAL_ERROR ) { myprint( "[$EVAL_ERROR]\n" ) ; return ; }
  12710. }else{
  12711. $loadavg = file_to_string( $file ) or return ;
  12712. #myprint( "$loadavg" ) ;
  12713. }
  12714. $loadavg =~ /LoadPercentage\n(\d+)/xms ;
  12715. my $num = $1 ;
  12716. $num /= 100 ;
  12717. $sync->{ debug } and myprint( "System load: $num\n" ) ;
  12718. return ( $num ) ;
  12719. }
  12720. sub tests_load_and_delay
  12721. {
  12722. note( 'Entering tests_load_and_delay()' ) ;
  12723. is( undef, load_and_delay( ), 'load_and_delay: no args => undef ' ) ;
  12724. is( undef, load_and_delay( 1 ), 'load_and_delay: not 4 args => undef ' ) ;
  12725. is( undef, load_and_delay( 0, 1, 1, 1 ), 'load_and_delay: division per 0 => undef ' ) ;
  12726. # ( $cpu_num, $avg_1_min, $avg_5_min, $avg_15_min )
  12727. is( 0, load_and_delay( 1, 1, 1, 1 ), 'load_and_delay: one core, loads are all 1 => ok ' ) ;
  12728. is( 0, load_and_delay( 1, 1, 1, 1, 'lalala' ), 'load_and_delay: five arguments is ok' ) ;
  12729. is( 0, load_and_delay( 2, 2, 2, 2 ), 'load_and_delay: two core, loads are all 2 => ok ' ) ;
  12730. is( 0, load_and_delay( 2, 2, 4, 5 ), 'load_and_delay: two core, load1m is 2 => ok ' ) ;
  12731. is( 0, load_and_delay( 1, 0, 0, 0 ), 'load_and_delay: one core, load1m=0 load5m=0 load15m=0 => 0 ' ) ;
  12732. is( 0, load_and_delay( 1, 0, 0, 2 ), 'load_and_delay: one core, load1m=0 load5m=0 load15m=2 => 0 ' ) ;
  12733. is( 0, load_and_delay( 1, 0, 2, 0 ), 'load_and_delay: one core, load1m=0 load5m=2 load15m=0 => 0 ' ) ;
  12734. is( 0, load_and_delay( 1, 0, 2, 2 ), 'load_and_delay: one core, load1m=0 load5m=2 load15m=2 => 0 ' ) ;
  12735. is( 0, load_and_delay( 1, 0, 3, 3 ), 'load_and_delay: one core, load1m=0 load5m=3 load15m=3 => 0 ' ) ;
  12736. is( 0, load_and_delay( 1, 0, 4, 4 ), 'load_and_delay: one core, load1m=0 load5m=3 load15m=3 => 0 ' ) ;
  12737. is( 0, load_and_delay( 1, 2, 0, 0 ), 'load_and_delay: one core, load1m=2 load5m=0 load15m=0 => 0 ' ) ;
  12738. is( 0, load_and_delay( 1, 2, 0, 2 ), 'load_and_delay: one core, load1m=2 load5m=0 load15m=2 => 0 ' ) ;
  12739. is( 0, load_and_delay( 1, 2, 2, 0 ), 'load_and_delay: one core, load1m=2 load5m=2 load15m=0 => 0 ' ) ;
  12740. is( 0, load_and_delay( 1, 2, 2, 2 ), 'load_and_delay: one core, load1m=2 load5m=2 load15m=2 => 0 ' ) ;
  12741. is( 0, load_and_delay( 1, 2.9, 2.9, 2.9 ), 'load_and_delay: one core, load1m=2.9 load5m=2.9 load15m=2.9 => 0 ' ) ;
  12742. is( 0, load_and_delay( 1, 3, 0, 0 ), 'load_and_delay: one core, load1m=3 load5m=0 load15m=0 => 0 ' ) ;
  12743. is( 0, load_and_delay( 1, 3, 2.9, 2.9 ), 'load_and_delay: one core, load1m=3 load5m=2.9 load15m=2.9 => 0 ' ) ;
  12744. is( 0, load_and_delay( 1, 3, 3, 2.9 ), 'load_and_delay: one core, load1m=3 load5m=3 load15m=2.9 => 0 ' ) ;
  12745. is( 0, load_and_delay( 1, 3, 3, 3 ), 'load_and_delay: one core, load1m=3 load5m=3 load15m=3 => 0 ' ) ;
  12746. is( 1, load_and_delay( 1, 6, 0, 0 ), 'load_and_delay: one core, load1m=3 load5m=0 load15m=0 => 1 ' ) ;
  12747. is( 1, load_and_delay( 1, 6, 5.9, 5.9 ), 'load_and_delay: one core, load1m=3 load5m=2.9 load15m=2.9 => 1 ' ) ;
  12748. is( 5, load_and_delay( 1, 6, 6, 5.9 ), 'load_and_delay: one core, load1m=3 load5m=3 load15m=2.9 => 5 ' ) ;
  12749. is( 15, load_and_delay( 1, 6, 6, 6 ), 'load_and_delay: one core, load1m=3 load5m=3 load15m=3 => 15 ' ) ;
  12750. note( 'Leaving tests_load_and_delay()' ) ;
  12751. return ;
  12752. }
  12753. sub load_and_delay
  12754. {
  12755. # Basically return 0 if load is not heavy, ie <= 1 per processor
  12756. # Not enough arguments
  12757. if ( 4 > scalar @ARG ) { return ; }
  12758. my ( $cpu_num, $avg_1_min, $avg_5_min, $avg_15_min ) = @ARG ;
  12759. if ( 0 == $cpu_num ) { return ; }
  12760. # Let divide by number of cores
  12761. ( $avg_1_min, $avg_5_min, $avg_15_min ) = map { $_ / $cpu_num } ( $avg_1_min, $avg_5_min, $avg_15_min ) ;
  12762. # One of avg ok => ok, for now it is a OR
  12763. if ( $avg_1_min < 6 ) { return 0 ; }
  12764. if ( $avg_5_min < 6 ) { return 1 ; } # Retry in 1 minute
  12765. if ( $avg_15_min < 6 ) { return 5 ; } # Retry in 5 minutes
  12766. return 15 ; # Retry in 15 minutes
  12767. }
  12768. sub tests_cpu_time
  12769. {
  12770. note( 'Entering tests_cpu_time()' ) ;
  12771. ok( is_number( cpu_time( ) ), 'cpu_time: no args => a number' ) ;
  12772. my $mysync = { } ;
  12773. $mysync->{ debug } = 1 ;
  12774. ok( is_number( cpu_time( $mysync ) ), 'cpu_time: {} => a number' ) ;
  12775. note( 'Leaving tests_cpu_time()' ) ;
  12776. return ;
  12777. }
  12778. sub cpu_time
  12779. {
  12780. my $mysync = shift ;
  12781. my @cpu_times = times ;
  12782. if ( ! @cpu_times ) { return ; }
  12783. my $cpu_time = 0 ;
  12784. # last element is the sum of all elements
  12785. $cpu_time = ( map { $cpu_time += $_ } @cpu_times )[ -1 ] ;
  12786. my $cpu_time_round = mysprintf( '%.2f', $cpu_time ) ;
  12787. $mysync->{ debug } and myprint( join(' + ', @cpu_times), " = $cpu_time ~ $cpu_time_round\n" ) ;
  12788. return $cpu_time ;
  12789. }
  12790. sub tests_cpu_percent
  12791. {
  12792. note( 'Entering tests_cpu_percent()' ) ;
  12793. is( '0.0', cpu_percent( ), 'cpu_percent: no args => 0.0' ) ;
  12794. my $mysync = { } ;
  12795. $mysync->{ debug } = 1 ;
  12796. is( '0.0', cpu_percent( $mysync ), 'cpu_percent: {} => 0.0' ) ;
  12797. is( '0.0', cpu_percent( $mysync, 0 ), 'cpu_percent: {} 0 => 0.0' ) ;
  12798. is( '300.0', cpu_percent( $mysync, 3 ), 'cpu_percent: {} 3 => 300.0' ) ;
  12799. is( '30.0', cpu_percent( $mysync, 3, 10 ), 'cpu_percent: {} 3 10 => 30.0' ) ;
  12800. is( '0.0', cpu_percent( $mysync, 0, 10 ), 'cpu_percent: {} 0 10 => 0.0' ) ;
  12801. note( 'Leaving tests_cpu_percent()' ) ;
  12802. return ;
  12803. }
  12804. sub cpu_percent
  12805. {
  12806. my $mysync = shift ;
  12807. my $cpu_time = shift || 0 ;
  12808. my $timediff = shift || 1 ; # no division by 0
  12809. if ( $cpu_time > $timediff )
  12810. {
  12811. myprint( "Strange: cpu_time $cpu_time > timediff $timediff\n" ) ;
  12812. }
  12813. my $cpu_percent = 0 ;
  12814. $cpu_percent = mysprintf( '%.1f', 100 * $cpu_time / $timediff ) ;
  12815. $mysync->{ debug } and myprint( "cpu_percent: $cpu_percent \n" ) ;
  12816. return $cpu_percent ;
  12817. }
  12818. sub tests_cpu_percent_global
  12819. {
  12820. note( 'Entering tests_cpu_percent_global()' ) ;
  12821. is( '0.0', cpu_percent_global( ), 'cpu_percent_global: no args => 0' ) ;
  12822. my $mysync = { } ;
  12823. $mysync->{ debug } = 1 ;
  12824. is( '0.0', cpu_percent_global( $mysync ), 'cpu_percent_global: {} => 0' ) ;
  12825. is( '0.0', cpu_percent_global( $mysync, 0 ), 'cpu_percent_global: {} 0 => 0' ) ;
  12826. SKIP: {
  12827. if ( ! ( 'i005' eq hostname() ) )
  12828. {
  12829. skip( 'cpu_percent_global on host != i005', 1 ) ;
  12830. }
  12831. is( '25.0', cpu_percent_global( $mysync, 100 ), 'cpu_percent_global: {} 100 => 25 on host i005' ) ;
  12832. } ;
  12833. SKIP: {
  12834. if ( ! ( 'petite' eq hostname() ) )
  12835. {
  12836. skip( 'cpu_percent_global on host != petite', 1 ) ;
  12837. }
  12838. is( '50.0', cpu_percent_global( $mysync, 100 ), 'cpu_percent_global: {} 100 => 50 on host petite' ) ;
  12839. } ;
  12840. note( 'Leaving tests_cpu_percent_global()' ) ;
  12841. return ;
  12842. }
  12843. sub cpu_percent_global
  12844. {
  12845. my $mysync = shift ;
  12846. my $cpu_percent = shift || 0 ;
  12847. my $cpu_number = cpu_number( ) ;
  12848. my $cpu_percent_global ;
  12849. $cpu_percent_global = mysprintf( '%.1f', $cpu_percent / $cpu_number ) ;
  12850. $mysync->{ debug } and myprint( "cpu_percent_global: $cpu_percent_global \n" ) ;
  12851. return( $cpu_percent_global ) ;
  12852. }
  12853. sub ram_memory_info
  12854. {
  12855. # In GigaBytes so division by 1024 * 1024 * 1024
  12856. #
  12857. return(
  12858. sprintf( "%.1f/%.1f free GiB of RAM",
  12859. Sys::MemInfo::get("freemem") / ( $KIBI ** 3 ),
  12860. Sys::MemInfo::get("totalmem") / ( $KIBI ** 3 ),
  12861. )
  12862. ) ;
  12863. }
  12864. sub tests_memory_stress
  12865. {
  12866. note( 'Entering tests_memory_stress()' ) ;
  12867. is( undef, memory_stress( ), 'memory_stress: => undef' ) ;
  12868. note( 'Leaving tests_memory_stress()' ) ;
  12869. return ;
  12870. }
  12871. sub memory_stress
  12872. {
  12873. my $total_ram_in_MB = Sys::MemInfo::get("totalmem") / ( $KIBI * $KIBI ) ;
  12874. my $i = 1 ;
  12875. myprintf("Stress memory consumption before: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI ) ;
  12876. while ( $i < $total_ram_in_MB / 1.7 ) { $a .= "A" x 1000_000; $i++ } ;
  12877. myprintf("Stress memory consumption after: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI ) ;
  12878. return ;
  12879. }
  12880. sub tests_memory_consumption
  12881. {
  12882. note( 'Entering tests_memory_consumption()' ) ;
  12883. note( "memory_consumption: " . memory_consumption() . " bytes aka " . bytes_display_string_dec( memory_consumption() ) ) ;
  12884. like( memory_consumption( ), qr{\d+}xms,'memory_consumption no args') ;
  12885. like( memory_consumption( 1 ), qr{\d+}xms,'memory_consumption 1') ;
  12886. like( memory_consumption( $PROCESS_ID ), qr{\d+}xms,"memory_consumption_of_pids $PROCESS_ID") ;
  12887. like( memory_consumption_ratio(), qr{\d+}xms, 'memory_consumption_ratio' ) ;
  12888. like( memory_consumption_ratio(1), qr{\d+}xms, 'memory_consumption_ratio 1' ) ;
  12889. like( memory_consumption_ratio(10), qr{\d+}xms, 'memory_consumption_ratio 10' ) ;
  12890. note( 'Leaving tests_memory_consumption()' ) ;
  12891. return ;
  12892. }
  12893. sub memory_consumption
  12894. {
  12895. # memory consumed by imapsync until now in bytes
  12896. return( ( memory_consumption_of_pids( ) )[0] );
  12897. }
  12898. sub debugmemory
  12899. {
  12900. my $mysync = shift ;
  12901. if ( ! $mysync->{debugmemory} ) { return q{} ; }
  12902. my $precision = shift ;
  12903. return( mysprintf( "Memory consumption$precision: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI ) ) ;
  12904. }
  12905. sub memory_consumption_of_pids
  12906. {
  12907. my @pid = @_;
  12908. @pid = ( @pid ) ? @pid : ( $PROCESS_ID ) ;
  12909. $sync->{ debug } and myprint( "memory_consumption_of_pids PIDs: @pid\n" ) ;
  12910. my @val ;
  12911. if ( ( 'MSWin32' eq $OSNAME ) or ( 'cygwin' eq $OSNAME ) ) {
  12912. @val = memory_consumption_of_pids_win32( @pid ) ;
  12913. }
  12914. elsif ( 'darwin' eq $OSNAME )
  12915. {
  12916. @val = memory_consumption_of_pids_mac( @pid ) ;
  12917. }
  12918. else
  12919. {
  12920. # Unix
  12921. my @ps = qx{ ps -o vsz -p @pid } ;
  12922. shift @ps ; # First line is column name "VSZ"
  12923. chomp @ps ;
  12924. # convert to octets
  12925. @val = map { $_ * $KIBI } @ps ;
  12926. }
  12927. return( @val ) ;
  12928. }
  12929. sub memory_consumption_of_pids_mac
  12930. {
  12931. my @pid = @_ ;
  12932. # Use IPC::Open3 from perlcrit -3
  12933. # But it stalls on Darwin, I don't understand why!
  12934. #my @ps = backtick( "ps -o rss -p @pid" ) ;
  12935. #myprint( "ps: @ps" ) ;
  12936. my @ps = qx{ ps -o rss -p @pid } ;
  12937. shift @ps ; # First line is column name "RSS"
  12938. chomp @ps ;
  12939. my @val = map { $_ * $KIBI } @ps ;
  12940. return( @val ) ;
  12941. }
  12942. sub memory_consumption_of_pids_win32
  12943. {
  12944. # Windows
  12945. my @PID = @_;
  12946. my %PID;
  12947. # hash of pids as key values
  12948. map { $PID{$_}++ } @PID;
  12949. # Does not work but should work reading the tasklist documentation
  12950. #@ps = qx{ tasklist /FI "PID eq @PID" };
  12951. my @ps = qx{ tasklist /NH /FO CSV } ;
  12952. #my @ps = backtick( 'tasklist /NH /FO CSV' ) ;
  12953. #myprint( "-" x $STD_CHAR_PER_LINE, "\n", @ps, "-" x $STD_CHAR_PER_LINE, "\n" ) ;
  12954. my @val;
  12955. foreach my $line (@ps) {
  12956. my($name, $pid, $mem) = (split ',', $line )[0,1,4];
  12957. next if (! $pid);
  12958. #myprint( "[$name][$pid][$mem]" ) ;
  12959. if ($PID{remove_qq($pid)}) {
  12960. #myprint( "MATCH !\n" ) ;
  12961. chomp $mem ;
  12962. $mem = remove_qq($mem);
  12963. $mem = remove_Ko($mem);
  12964. $mem = remove_not_num($mem);
  12965. #myprint( "[$mem]\n" ) ;
  12966. push @val, $mem * $KIBI;
  12967. }
  12968. }
  12969. return(@val);
  12970. }
  12971. sub tests_backtick
  12972. {
  12973. note( 'Entering tests_backtick()' ) ;
  12974. is( undef, backtick( ), 'backtick: no args' ) ;
  12975. is( undef, backtick( q{} ), 'backtick: empty command' ) ;
  12976. SKIP: {
  12977. skip( 'test for MSWin32', 5 ) if ('MSWin32' ne $OSNAME) ;
  12978. my @output ;
  12979. @output = backtick( 'echo Hello World!' ) ;
  12980. # Add \r on Windows.
  12981. ok( "Hello World!\r\n" eq $output[0], 'backtick: echo Hello World!' ) ;
  12982. $sync->{ debug } and myprint( "[@output]" ) ;
  12983. @output = backtick( 'echo Hello & echo World!' ) ;
  12984. ok( "Hello \r\n" eq $output[0], 'backtick: echo Hello & echo World! line 1' ) ;
  12985. ok( "World!\r\n" eq $output[1], 'backtick: echo Hello & echo World! line 2' ) ;
  12986. $sync->{ debug } and myprint( "[@output][$output[0]][$output[1]]" ) ;
  12987. # Scalar context
  12988. ok( "Hello World!\r\n" eq backtick( 'echo Hello World!' ),
  12989. 'backtick: echo Hello World! scalar' ) ;
  12990. ok( "Hello \r\nWorld!\r\n" eq backtick( 'echo Hello & echo World!' ),
  12991. 'backtick: echo Hello & echo World! scalar 2 lines' ) ;
  12992. } ;
  12993. SKIP: {
  12994. skip( 'test for Unix', 7 ) if ('MSWin32' eq $OSNAME) ;
  12995. is( undef, backtick( 'aaaarrrg' ), 'backtick: aaaarrrg command not found' ) ;
  12996. # Array context
  12997. my @output ;
  12998. @output = backtick( 'echo Hello World!' ) ;
  12999. ok( "Hello World!\n" eq $output[0], 'backtick: echo Hello World!' ) ;
  13000. $sync->{ debug } and myprint( "[@output]" ) ;
  13001. @output = backtick( "echo Hello\necho World!" ) ;
  13002. ok( "Hello\n" eq $output[0], 'backtick: echo Hello; echo World! line 1' ) ;
  13003. ok( "World!\n" eq $output[1], 'backtick: echo Hello; echo World! line 2' ) ;
  13004. $sync->{ debug } and myprint( "[@output]" ) ;
  13005. # Scalar context
  13006. ok( "Hello World!\n" eq backtick( 'echo Hello World!' ),
  13007. 'backtick: echo Hello World! scalar' ) ;
  13008. ok( "Hello\nWorld!\n" eq backtick( "echo Hello\necho World!" ),
  13009. 'backtick: echo Hello; echo World! scalar 2 lines' ) ;
  13010. # Return error positive value, that's ok
  13011. is( undef, backtick( 'false' ), 'backtick: false returns no output' ) ;
  13012. my $mem = backtick( "ps -o vsz -p $PROCESS_ID" ) ;
  13013. $sync->{ debug } and myprint( "MEM=$mem\n" ) ;
  13014. }
  13015. note( 'Leaving tests_backtick()' ) ;
  13016. return ;
  13017. }
  13018. sub backtick
  13019. {
  13020. my $command = shift ;
  13021. if ( ! $command ) { return ; }
  13022. my ( $writer, $reader, $err ) ;
  13023. my @output ;
  13024. my $pid ;
  13025. my $eval = eval {
  13026. $pid = IPC::Open3::open3( $writer, $reader, $err, $command ) ;
  13027. } ;
  13028. if ( $EVAL_ERROR ) {
  13029. myprint( $EVAL_ERROR ) ;
  13030. return ;
  13031. }
  13032. if ( ! $eval ) { return ; }
  13033. if ( ! $pid ) { return ; }
  13034. waitpid( $pid, 0 ) ;
  13035. @output = <$reader>; # Output here
  13036. #
  13037. #my @errors = <$err>; #Errors here, instead of the console
  13038. if ( not @output ) { return ; }
  13039. #myprint( @output ) ;
  13040. if ( $output[0] =~ /\Qopen3: exec of $command failed\E/mxs ) { return ; }
  13041. if ( wantarray ) {
  13042. return( @output ) ;
  13043. } else {
  13044. return( join( q{}, @output) ) ;
  13045. }
  13046. }
  13047. sub tests_check_binary_embed_all_dyn_libs
  13048. {
  13049. note( 'Entering tests_check_binary_embed_all_dyn_libs()' ) ;
  13050. is( 1, check_binary_embed_all_dyn_libs( ), 'check_binary_embed_all_dyn_libs: no args => 1' ) ;
  13051. note( 'Leaving tests_check_binary_embed_all_dyn_libs()' ) ;
  13052. return ;
  13053. }
  13054. sub check_binary_embed_all_dyn_libs
  13055. {
  13056. my @search_dyn_lib_locale = search_dyn_lib_locale( ) ;
  13057. if ( @search_dyn_lib_locale )
  13058. {
  13059. myprint( "Found myself $PROGRAM_NAME pid $PROCESS_ID using locale dynamic libraries that seems out of myself:\n" ) ;
  13060. myprint( @search_dyn_lib_locale ) ;
  13061. if ( $PROGRAM_NAME =~ m{imapsync_bin_Darwin} )
  13062. {
  13063. return 0 ;
  13064. }
  13065. elsif ( $PROGRAM_NAME =~ m{imapsync.*\.exe} )
  13066. {
  13067. return 0 ;
  13068. }
  13069. else
  13070. {
  13071. # is always ok for non binary
  13072. return 1 ;
  13073. }
  13074. }
  13075. else
  13076. {
  13077. # Found only embedded dynamic lib
  13078. myprint( "Found only embedded dynamic lib. Good!\n" ) ;
  13079. return 1 ;
  13080. }
  13081. }
  13082. sub search_dyn_lib_locale
  13083. {
  13084. if ( 'darwin' eq $OSNAME )
  13085. {
  13086. return search_dyn_lib_locale_darwin( ) ;
  13087. }
  13088. if ( 'linux' eq $OSNAME )
  13089. {
  13090. return search_dyn_lib_locale_linux( ) ;
  13091. }
  13092. if ( 'MSWin32' eq $OSNAME )
  13093. {
  13094. return search_dyn_lib_locale_MSWin32( ) ;
  13095. }
  13096. }
  13097. sub search_dyn_lib_locale_darwin
  13098. {
  13099. my $command = qq{ lsof -p $PROCESS_ID | grep ' REG ' | grep .dylib | grep -v '/par-' } ;
  13100. myprint( "Search non embeded dynamic libs with the command: $command\n" ) ;
  13101. return backtick( $command ) ;
  13102. }
  13103. sub search_dyn_lib_locale_linux
  13104. {
  13105. my $command = qq{ lsof -p $PROCESS_ID | grep ' REG ' | grep -v '/tmp/par-' | grep '\.so' } ;
  13106. myprint( "Search non embeded dynamic libs with the command: $command\n" ) ;
  13107. return backtick( $command ) ;
  13108. }
  13109. sub search_dyn_lib_locale_MSWin32
  13110. {
  13111. my $command = qq{ Listdlls.exe $PROCESS_ID|findstr Strawberry } ;
  13112. # $command = qq{ Listdlls.exe $PROCESS_ID|findstr Strawberry } ;
  13113. myprint( "Search non embeded dynamic libs with the command: $command\n" ) ;
  13114. return qx( $command ) ;
  13115. }
  13116. sub remove_not_num
  13117. {
  13118. my $string = shift ;
  13119. $string =~ tr/0-9//cd ;
  13120. #myprint( "tr [$string]\n" ) ;
  13121. return( $string ) ;
  13122. }
  13123. sub tests_remove_not_num
  13124. {
  13125. note( 'Entering tests_remove_not_num()' ) ;
  13126. ok( '123' eq remove_not_num( 123 ), 'remove_not_num( 123 )' ) ;
  13127. ok( '123' eq remove_not_num( '123' ), q{remove_not_num( '123' )} ) ;
  13128. ok( '123' eq remove_not_num( '12 3' ), q{remove_not_num( '12 3' )} ) ;
  13129. ok( '123' eq remove_not_num( 'a 12 3 Ko' ), q{remove_not_num( 'a 12 3 Ko' )} ) ;
  13130. note( 'Leaving tests_remove_not_num()' ) ;
  13131. return ;
  13132. }
  13133. sub remove_Ko
  13134. {
  13135. my $string = shift;
  13136. if ($string =~ /^(.*)\sKo$/xo) {
  13137. return($1);
  13138. }else{
  13139. return($string);
  13140. }
  13141. }
  13142. sub remove_qq
  13143. {
  13144. my $string = shift;
  13145. if ($string =~ /^"(.*)"$/xo) {
  13146. return($1);
  13147. }else{
  13148. return($string);
  13149. }
  13150. }
  13151. sub memory_consumption_ratio
  13152. {
  13153. my ($base) = @_;
  13154. $base ||= 1;
  13155. my $consu = memory_consumption();
  13156. return($consu / $base);
  13157. }
  13158. sub date_from_rcs
  13159. {
  13160. my $d = shift ;
  13161. 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 ) ;
  13162. if ($d =~ m{(\d{4})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) {
  13163. # Handles the following format
  13164. # 2015/07/10 11:05:59 -- Generated by RCS Date tag.
  13165. #myprint( "$d\n" ) ;
  13166. #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
  13167. my ($year, $month, $day, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6) ;
  13168. $month = $num2mon{$month} ;
  13169. $d = "$day-$month-$year $hour:$min:$sec +0000" ;
  13170. #myprint( "$d\n" ) ;
  13171. }
  13172. return( $d ) ;
  13173. }
  13174. sub tests_date_from_rcs
  13175. {
  13176. note( 'Entering tests_date_from_rcs()' ) ;
  13177. ok('19-Sep-2015 16:11:07 +0000'
  13178. eq date_from_rcs('Date: 2015/09/19 16:11:07 '), 'date_from_rcs from RCS date' ) ;
  13179. note( 'Leaving tests_date_from_rcs()' ) ;
  13180. return ;
  13181. }
  13182. sub good_date
  13183. {
  13184. # two incoming formats:
  13185. # header Tue, 24 Aug 2010 16:00:00 +0200
  13186. # internal 24-Aug-2010 16:00:00 +0200
  13187. # outgoing format: internal date format
  13188. # 24-Aug-2010 16:00:00 +0200
  13189. my $d = shift ;
  13190. return(q{}) if not defined $d;
  13191. SWITCH: {
  13192. if ( $d =~ m{(\d?)(\d-...-\d{4})(\s\d{2}:\d{2}:\d{2})(\s(?:\+|-)\d{4})?}xo ) {
  13193. #myprint( "internal: [$1][$2][$3][$4]\n" ) ;
  13194. my ($day_1, $date_rest, $hour, $zone) = ($1,$2,$3,$4) ;
  13195. $day_1 = '0' if ($day_1 eq q{}) ;
  13196. $zone = ' +0000' if not defined $zone ;
  13197. $d = $day_1 . $date_rest . $hour . $zone ;
  13198. last SWITCH ;
  13199. }
  13200. 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 ) {
  13201. # Handles any combination of following formats
  13202. # Tue, 24 Aug 2010 16:00:00 +0200 -- Standard
  13203. # 24 Aug 2010 16:00:00 +0200 -- Missing Day of Week
  13204. # Tue, 24 Aug 97 16:00:00 +0200 -- Two digit year
  13205. # Tue, 24 Aug 1997 16.00.00 +0200 -- Periods instead of colons
  13206. # Tue, 24 Aug 1997 16:00:00 +0200 -- Extra whitespace between year and hour
  13207. # Tue, 24 Aug 1997 6:5:2 +0200 -- Single digit hour, min, or second
  13208. # Tue, 24, Aug 1997 16:00:00 +0200 -- Extra comma
  13209. #myprint( "header: [$1][$2][$3][$4][$5][$6][$7][$8]\n" ) ;
  13210. my ($day, $month, $year, $hour, $min, $sec, $zone) = ($1,$2,$3,$4,$5,$6,$7,$8);
  13211. $year = '19' . $year if length($year) == 2 && $year =~ m/^[789]/xo;
  13212. $year = '20' . $year if length($year) == 2;
  13213. $month = substr $month, 0, 3 if length($month) > 4;
  13214. $day = mysprintf( '%02d', $day);
  13215. $hour = mysprintf( '%02d', $hour);
  13216. $min = mysprintf( '%02d', $min);
  13217. $sec = '00' if not defined $sec ;
  13218. $sec = mysprintf( '%02d', $sec ) ;
  13219. $zone = '+0000' if not defined $zone ;
  13220. $d = "$day-$month-$year $hour:$min:$sec $zone" ;
  13221. last SWITCH ;
  13222. }
  13223. 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 ) {
  13224. # Handles any combination of following formats
  13225. # Sun Aug 20 11:55:09 2006
  13226. # Wed Jan 24 11:58:38 MST 2007
  13227. # Wed Jan 2 08:40:57 2008
  13228. #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
  13229. my ($month, $day, $hour, $min, $sec, $year) = ($1,$2,$3,$4,$5,$6);
  13230. $day = mysprintf( '%02d', $day ) ;
  13231. $hour = mysprintf( '%02d', $hour ) ;
  13232. $min = mysprintf( '%02d', $min ) ;
  13233. $sec = mysprintf( '%02d', $sec ) ;
  13234. $d = "$day-$month-$year $hour:$min:$sec +0000" ;
  13235. last SWITCH ;
  13236. }
  13237. 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 ) ;
  13238. if ($d =~ m{(\d{4})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) {
  13239. # Handles the following format
  13240. # 2015/07/10 11:05:59 -- Generated by RCS Date tag.
  13241. #myprint( "$d\n" ) ;
  13242. #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
  13243. my ($year, $month, $day, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6) ;
  13244. $month = $num2mon{$month} ;
  13245. $d = "$day-$month-$year $hour:$min:$sec +0000" ;
  13246. #myprint( "$d\n" ) ;
  13247. last SWITCH ;
  13248. }
  13249. if ($d =~ m{(\d{2})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) {
  13250. # Handles the following format
  13251. # 02/06/09 22:18:08 -- Generated by AVTECH TemPageR devices
  13252. #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
  13253. my ($month, $day, $year, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6);
  13254. $year = '20' . $year;
  13255. $month = $num2mon{$month};
  13256. $d = "$day-$month-$year $hour:$min:$sec +0000";
  13257. last SWITCH ;
  13258. }
  13259. 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 ) {
  13260. # Handles the following format
  13261. # Saturday, December 14, 2002 05:00 PM - KBtoys.com order confirmations
  13262. my ($month, $day, $year, $hour, $min, $apm) = ($1,$2,$3,$4,$5,$6);
  13263. $hour += 12 if $apm eq 'PM' ;
  13264. $day = mysprintf( '%02d', $day ) ;
  13265. $d = "$day-$month-$year $hour:$min:00 +0000" ;
  13266. last SWITCH ;
  13267. }
  13268. if ($d =~ m{(\w{3})\s(\d{1,2})\s(\d{4})\s(\d{2}):(\d{2}):(\d{2})\s((?:\+|-)\d{4})}xo ) {
  13269. # Handles the following format
  13270. # Saturday, December 14, 2002 05:00 PM - jr.com order confirmations
  13271. my ($month, $day, $year, $hour, $min, $sec, $zone) = ($1,$2,$3,$4,$5,$6,$7);
  13272. $day = mysprintf( '%02d', $day ) ;
  13273. $d = "$day-$month-$year $hour:$min:$sec $zone";
  13274. last SWITCH ;
  13275. }
  13276. if ($d =~ m{(\d{1,2})-(\w{3})-(\d{4})}xo ) {
  13277. # Handles the following format
  13278. # 21-Jun-2001 - register.com domain transfer email circa 2001
  13279. my ($day, $month, $year) = ($1,$2,$3);
  13280. $day = mysprintf( '%02d', $day);
  13281. $d = "$day-$month-$year 11:11:11 +0000";
  13282. last SWITCH ;
  13283. }
  13284. # unknown or unmatch => return same string
  13285. return($d);
  13286. }
  13287. $d = qq("$d") ;
  13288. return( $d ) ;
  13289. }
  13290. sub tests_good_date
  13291. {
  13292. note( 'Entering tests_good_date()' ) ;
  13293. ok(q{} eq good_date(), 'good_date no arg');
  13294. ok('"24-Aug-2010 16:00:00 +0200"' eq good_date('24-Aug-2010 16:00:00 +0200'), 'good_date internal 2digit zone');
  13295. ok('"24-Aug-2010 16:00:00 +0000"' eq good_date('24-Aug-2010 16:00:00'), 'good_date internal 2digit no zone');
  13296. ok('"01-Sep-2010 16:00:00 +0200"' eq good_date( '1-Sep-2010 16:00:00 +0200'), 'good_date internal SP 1digit');
  13297. ok('"24-Aug-2010 16:00:00 +0200"' eq good_date('Tue, 24 Aug 2010 16:00:00 +0200'), 'good_date header 2digit zone');
  13298. ok('"01-Sep-2010 16:00:00 +0000"' eq good_date('Wed, 1 Sep 2010 16:00:00'), 'good_date header SP 1digit zone');
  13299. 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');
  13300. 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');
  13301. ok('"06-Feb-2009 22:18:08 +0000"' eq good_date('02/06/09 22:18:08'), 'good_date header TemPageR');
  13302. 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');
  13303. 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');
  13304. 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');
  13305. 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');
  13306. ok('"24-Aug-2067 16:00:00 +0200"' eq good_date('Tue, 24 Aug 67 16:00:00 +0200'), 'good_date header 2digit year');
  13307. ok('"24-Aug-1977 16:00:00 +0200"' eq good_date('Tue, 24 Aug 77 16:00:00 +0200'), 'good_date header 2digit year');
  13308. ok('"24-Aug-1987 16:00:00 +0200"' eq good_date('Tue, 24 Aug 87 16:00:00 +0200'), 'good_date header 2digit year');
  13309. ok('"24-Aug-1997 16:00:00 +0200"' eq good_date('Tue, 24 Aug 97 16:00:00 +0200'), 'good_date header 2digit year');
  13310. ok('"24-Aug-2004 16:00:00 +0200"' eq good_date('Tue, 24 Aug 04 16:00:00 +0200'), 'good_date header 2digit year');
  13311. 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');
  13312. 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');
  13313. 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');
  13314. ok('"24-Aug-1997 05:06:02 +0200"' eq good_date('Tue, 24, Aug 1997 05:06:02 +0200'), 'good_date header extra commas');
  13315. ok('"01-Oct-2003 12:45:24 +0000"' eq good_date('Wednesday, 01 October 2003 12:45:24 CDT'), 'good_date header no abbrev');
  13316. ok('"11-Jan-2005 17:58:27 -0500"' eq good_date('Tue, 11 Jan 2005 17:58:27 -0500'), 'good_date extra white space');
  13317. ok('"18-Dec-2002 15:07:00 +0000"' eq good_date('Wednesday, December 18, 2002 03:07 PM'), 'good_date kbtoys.com orders');
  13318. ok('"16-Dec-2004 02:01:49 -0500"' eq good_date('Dec 16 2004 02:01:49 -0500'), 'good_date jr.com orders');
  13319. ok('"21-Jun-2001 11:11:11 +0000"' eq good_date('21-Jun-2001'), 'good_date register.com domain transfer');
  13320. 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)');
  13321. ok('"19-Sep-2015 16:11:07 +0000"' eq good_date('Date: 2015/09/19 16:11:07 '), 'good_date from RCS date' ) ;
  13322. note( 'Leaving tests_good_date()' ) ;
  13323. return ;
  13324. }
  13325. sub tests_list_keys_in_2_not_in_1
  13326. {
  13327. note( 'Entering tests_list_keys_in_2_not_in_1()' ) ;
  13328. my @list;
  13329. ok( ! list_keys_in_2_not_in_1( {}, {}), 'list_keys_in_2_not_in_1: {} {}');
  13330. ok( 0 == compare_lists( [], [ list_keys_in_2_not_in_1( {}, {} ) ] ), 'list_keys_in_2_not_in_1: {} {}');
  13331. 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}');
  13332. 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}');
  13333. 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}');
  13334. 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}');
  13335. 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}');
  13336. note( 'Leaving tests_list_keys_in_2_not_in_1()' ) ;
  13337. return ;
  13338. }
  13339. sub list_keys_in_2_not_in_1
  13340. {
  13341. my $hash_1_ref = shift;
  13342. my $hash_2_ref = shift;
  13343. my @list;
  13344. foreach my $key ( sort keys %{ $hash_2_ref } ) {
  13345. #$sync->{ debug } and print "$key\n" ;
  13346. if ( exists $hash_1_ref->{$key} )
  13347. {
  13348. next ;
  13349. }
  13350. #$sync->{ debug } and print "list_keys_in_2_not_in_1: $key\n" ;
  13351. push @list, $key ;
  13352. }
  13353. #$sync->{ debug } and print "@list\n" ;
  13354. return( @list ) ;
  13355. }
  13356. sub list_folders_in_2_not_in_1
  13357. {
  13358. my ( @h2_folders_not_in_h1, %h2_folders_not_in_h1 ) ;
  13359. @h2_folders_not_in_h1 = list_keys_in_2_not_in_1( \%h1_folders_all, \%h2_folders_all ) ;
  13360. map { $h2_folders_not_in_h1{$_} = 1} @h2_folders_not_in_h1 ;
  13361. @h2_folders_not_in_h1 = list_keys_in_2_not_in_1( \%h2_folders_from_1_all, \%h2_folders_not_in_h1 ) ;
  13362. #$sync->{ debug } and print "h2_folders_not_in_h1: @h2_folders_not_in_h1\n" ;
  13363. return( reverse @h2_folders_not_in_h1 ) ;
  13364. }
  13365. sub tests_nb_messages_in_2_not_in_1
  13366. {
  13367. note( 'Entering tests_stats_across_folders()' ) ;
  13368. is( undef, nb_messages_in_2_not_in_1( ), 'nb_messages_in_2_not_in_1: no args => undef' ) ;
  13369. my $mysync->{ h1_folders_of_md5 }->{ 'some_id_01' }->{ 'some_folder_01' } = 1 ;
  13370. is( 0, nb_messages_in_2_not_in_1( $mysync ), 'nb_messages_in_2_not_in_1: no messages in 2 => 0' ) ;
  13371. $mysync->{ h1_folders_of_md5 }->{ 'some_id_in_1_and_2' }->{ 'some_folder_01' } = 2 ;
  13372. $mysync->{ h2_folders_of_md5 }->{ 'some_id_in_1_and_2' }->{ 'some_folder_02' } = 4 ;
  13373. is( 0, nb_messages_in_2_not_in_1( $mysync ), 'nb_messages_in_2_not_in_1: a common message => 0' ) ;
  13374. $mysync->{ h2_folders_of_md5 }->{ 'some_id_in_2_not_in_1' }->{ 'some_folder_02' } = 1 ;
  13375. 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' ) ;
  13376. $mysync->{ h2_folders_of_md5 }->{ 'some_other_id_in_2_not_in_1' }->{ 'some_folder_02' } = 3 ;
  13377. 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' ) ;
  13378. note( 'Leaving tests_stats_across_folders()' ) ;
  13379. return ;
  13380. }
  13381. sub nb_messages_in_2_not_in_1
  13382. {
  13383. my $mysync = shift ;
  13384. if ( not defined $mysync ) { return ; }
  13385. $mysync->{ nb_messages_in_2_not_in_1 } = scalar(
  13386. list_keys_in_2_not_in_1(
  13387. $mysync->{ h1_folders_of_md5 },
  13388. $mysync->{ h2_folders_of_md5 } ) ) ;
  13389. return $mysync->{ nb_messages_in_2_not_in_1 } ;
  13390. }
  13391. sub nb_messages_in_1_not_in_2
  13392. {
  13393. my $mysync = shift ;
  13394. if ( not defined $mysync ) { return ; }
  13395. $mysync->{ nb_messages_in_1_not_in_2 } = scalar(
  13396. list_keys_in_2_not_in_1(
  13397. $mysync->{ h2_folders_of_md5 },
  13398. $mysync->{ h1_folders_of_md5 } ) ) ;
  13399. return $mysync->{ nb_messages_in_1_not_in_2 } ;
  13400. }
  13401. sub comment_on_final_diff_in_1_not_in_2
  13402. {
  13403. my $mysync = shift ;
  13404. if ( not defined $mysync
  13405. or $mysync->{ justfolders }
  13406. or $mysync->{ useuid }
  13407. )
  13408. {
  13409. return ;
  13410. }
  13411. my $nb_identified_h1_messages = scalar( keys %{ $mysync->{ h1_folders_of_md5 } } ) ;
  13412. my $nb_identified_h2_messages = scalar( keys %{ $mysync->{ h2_folders_of_md5 } } ) ;
  13413. $mysync->{ debug } and myprint( "nb_keys h1_folders_of_md5 $nb_identified_h1_messages\n" ) ;
  13414. $mysync->{ debug } and myprint( "nb_keys h2_folders_of_md5 $nb_identified_h2_messages\n" ) ;
  13415. if ( 0 == $nb_identified_h1_messages ) { return ; }
  13416. # Calculate if not yet done
  13417. if ( not defined $mysync->{ nb_messages_in_1_not_in_2 } )
  13418. {
  13419. nb_messages_in_1_not_in_2( $mysync ) ;
  13420. }
  13421. if ( 0 == $mysync->{ nb_messages_in_1_not_in_2 } )
  13422. {
  13423. myprint( "The sync looks good, all ",
  13424. $nb_identified_h1_messages,
  13425. " identified messages in host1 are on host2.\n" ) ;
  13426. }
  13427. else
  13428. {
  13429. myprint( "The sync is not finished, there are ",
  13430. $mysync->{ nb_messages_in_1_not_in_2 },
  13431. " among ",
  13432. $nb_identified_h1_messages,
  13433. " identified messages in host1 that are not on host2.\n" ) ;
  13434. }
  13435. if ( 1 <= $mysync->{ h1_nb_msg_noheader } )
  13436. {
  13437. myprint( "There are ",
  13438. $mysync->{ h1_nb_msg_noheader },
  13439. " unidentified messages (usually Sent or Draft messages).",
  13440. " To sync them add option --addheader\n" ) ;
  13441. }
  13442. else
  13443. {
  13444. myprint( "There is no unidentified message on host1.\n" ) ;
  13445. }
  13446. return ;
  13447. }
  13448. sub comment_on_final_diff_in_2_not_in_1
  13449. {
  13450. my $mysync = shift ;
  13451. if ( not defined $mysync
  13452. or $mysync->{ justfolders }
  13453. or $mysync->{ useuid }
  13454. )
  13455. {
  13456. return ;
  13457. }
  13458. my $nb_identified_h2_messages = scalar( keys %{ $mysync->{ h2_folders_of_md5 } } ) ;
  13459. # Calculate if not done yet
  13460. if ( not defined $mysync->{ nb_messages_in_2_not_in_1 } )
  13461. {
  13462. nb_messages_in_2_not_in_1( $mysync ) ;
  13463. }
  13464. if ( 0 == $mysync->{ nb_messages_in_2_not_in_1 } )
  13465. {
  13466. myprint( "The sync is strict, all ",
  13467. $nb_identified_h2_messages,
  13468. " identified messages in host2 are on host1.\n" ) ;
  13469. }
  13470. else
  13471. {
  13472. myprint( "The sync is not strict, there are ",
  13473. $mysync->{ nb_messages_in_2_not_in_1 },
  13474. " among ",
  13475. $nb_identified_h2_messages,
  13476. " identified messages in host2 that are not on host1.",
  13477. " Use --delete2 and sync again to delete them and have a strict sync.\n"
  13478. ) ;
  13479. }
  13480. return ;
  13481. }
  13482. sub tests_match
  13483. {
  13484. note( 'Entering tests_match()' ) ;
  13485. # undef serie
  13486. is( undef, match( ), 'match: no args => undef' ) ;
  13487. is( undef, match( 'lalala' ), 'match: one args => undef' ) ;
  13488. # This one gives 0 under a binary made by pp
  13489. # but 1 under "normal" Perl interpreter. So a PAR bug?
  13490. #is( 1, match( q{}, q{} ), 'match: q{} =~ q{} => 1' ) ;
  13491. is( 'lalala', match( 'lalala', 'lalala' ), 'match: lalala =~ lalala => lalala' ) ;
  13492. is( 'lalala', match( 'lalala', '^lalala' ), 'match: lalala =~ ^lalala => lalala' ) ;
  13493. is( 'lalala', match( 'lalala', 'lalala$' ), 'match: lalala =~ lalala$ => lalala' ) ;
  13494. is( 'lalala', match( 'lalala', '^lalala$' ), 'match: lalala =~ ^lalala$ => lalala' ) ;
  13495. is( '_lalala_', match( '_lalala_', 'lalala' ), 'match: _lalala_ =~ lalala => _lalala_' ) ;
  13496. is( 'lalala', match( 'lalala', '.*' ), 'match: lalala =~ .* => lalala' ) ;
  13497. is( 'lalala', match( 'lalala', '.' ), 'match: lalala =~ . => lalala' ) ;
  13498. is( '/lalala/', match( '/lalala/', '/lalala/' ), 'match: /lalala/ =~ /lalala/ => /lalala/' ) ;
  13499. is( 0, match( 'foo', 's/foo/bar/g' ), 'match: foo =~ s/foo/bar/g => 0' ) ;
  13500. 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' ) ;
  13501. is( 0, match( 'lalala', 'ooo' ), 'match: lalala =~ ooo => 0' ) ;
  13502. is( 0, match( 'lalala', 'lal_ala' ), 'match: lalala =~ lal_ala => 0' ) ;
  13503. is( 0, match( 'lalala', '\.' ), 'match: lalala =~ \. => 0' ) ;
  13504. is( 0, match( 'lalalaX', '^lalala$' ), 'match: lalalaX =~ ^lalala$ => 0' ) ;
  13505. is( 0, match( 'lalala', '/lalala/' ), 'match: lalala =~ /lalala/ => 0' ) ;
  13506. is( 'LALALA', match( 'LALALA', '(?i:lalala)' ), 'match: LALALA =~ (?i:lalala) => 1' ) ;
  13507. is( undef, match( 'LALALA', '(?{`ls /`})' ), 'match: LALALA =~ (?{`ls /`}) => undef' ) ;
  13508. is( undef, match( 'LALALA', '(?{print "CACA"})' ), 'match: LALALA =~ (?{print "CACA"}) => undef' ) ;
  13509. is( undef, match( 'CACA', '(??{print "CACA"})' ), 'match: CACA =~ (??{print "CACA"}) => undef' ) ;
  13510. note( 'Leaving tests_match()' ) ;
  13511. return ;
  13512. }
  13513. sub match
  13514. {
  13515. my( $var, $regex ) = @ARG ;
  13516. # undef cases
  13517. if ( ( ! defined $var ) or ( ! defined $regex ) ) { return ; }
  13518. # normal cases
  13519. if ( eval { $var =~ qr{$regex} } ) {
  13520. return $var ;
  13521. }elsif ( $EVAL_ERROR ) {
  13522. myprint( "Fatal regex $regex\n" ) ;
  13523. return ;
  13524. } else {
  13525. return 0 ;
  13526. }
  13527. return ;
  13528. }
  13529. sub tests_notmatch
  13530. {
  13531. note( 'Entering tests_notmatch()' ) ;
  13532. # undef serie
  13533. is( undef, notmatch( ), 'notmatch: no args => undef' ) ;
  13534. is( undef, notmatch( 'lalala' ), 'notmatch: one args => undef' ) ;
  13535. is( 1, notmatch( 'lalala', '/lalala/' ), 'notmatch: lalala !~ /lalala/ => 1' ) ;
  13536. is( 0, notmatch( '/lalala/', '/lalala/' ), 'notmatch: /lalala/ !~ /lalala/ => 0' ) ;
  13537. is( 1, notmatch( 'lalala', '/ooo/' ), 'notmatch: lalala !~ /ooo/ => 1' ) ;
  13538. # This one gives 1 under a binary made by pp
  13539. # but 0 under "normal" Perl interpreter. So a PAR bug, same in tests_match .
  13540. #is( 0, notmatch( q{}, q{} ), 'notmatch: q{} !~ q{} => 0' ) ;
  13541. is( 0, notmatch( 'lalala', 'lalala' ), 'notmatch: lalala !~ lalala => 0' ) ;
  13542. is( 0, notmatch( 'lalala', '^lalala' ), 'notmatch: lalala !~ ^lalala => 0' ) ;
  13543. is( 0, notmatch( 'lalala', 'lalala$' ), 'notmatch: lalala !~ lalala$ => 0' ) ;
  13544. is( 0, notmatch( 'lalala', '^lalala$' ), 'notmatch: lalala !~ ^lalala$ => 0' ) ;
  13545. is( 0, notmatch( '_lalala_', 'lalala' ), 'notmatch: _lalala_ !~ lalala => 0' ) ;
  13546. is( 0, notmatch( 'lalala', '.*' ), 'notmatch: lalala !~ .* => 0' ) ;
  13547. is( 0, notmatch( 'lalala', '.' ), 'notmatch: lalala !~ . => 0' ) ;
  13548. is( 1, notmatch( 'lalala', 'ooo' ), 'notmatch: does not match regex => 1' ) ;
  13549. is( 1, notmatch( 'lalala', 'lal_ala' ), 'notmatch: does not match regex => 1' ) ;
  13550. is( 1, notmatch( 'lalala', '\.' ), 'notmatch: matches regex => 0' ) ;
  13551. is( 1, notmatch( 'lalalaX', '^lalala$' ), 'notmatch: does not match regex => 1' ) ;
  13552. note( 'Leaving tests_notmatch()' ) ;
  13553. return ;
  13554. }
  13555. sub notmatch
  13556. {
  13557. my( $var, $regex ) = @ARG ;
  13558. # undef cases
  13559. if ( ( ! defined $var ) or ( ! defined $regex ) ) { return ; }
  13560. # normal cases
  13561. if ( eval { $var !~ $regex } ) {
  13562. return 1 ;
  13563. }elsif ( $EVAL_ERROR ) {
  13564. myprint( "Fatal regex $regex\n" ) ;
  13565. return ;
  13566. }else{
  13567. return 0 ;
  13568. }
  13569. return ;
  13570. }
  13571. sub delete_folders_in_2_not_in_1
  13572. {
  13573. foreach my $folder ( @h2_folders_not_in_1 ) {
  13574. if ( defined $delete2foldersonly and eval "\$folder !~ $delete2foldersonly" ) {
  13575. myprint( "Not deleting $folder because of --delete2foldersonly $delete2foldersonly\n" ) ;
  13576. next ;
  13577. }
  13578. if ( defined $delete2foldersbutnot and eval "\$folder =~ $delete2foldersbutnot" ) {
  13579. myprint( "Not deleting $folder because of --delete2foldersbutnot $delete2foldersbutnot\n" ) ;
  13580. next ;
  13581. }
  13582. my $res = $sync->{dry} ; # always success in dry mode!
  13583. $sync->{imap2}->unsubscribe( $folder ) if ( ! $sync->{dry} ) ;
  13584. $res = $sync->{imap2}->delete( $folder ) if ( ! $sync->{dry} ) ;
  13585. if ( $res ) {
  13586. myprint( "Deleted $folder", "$sync->{dry_message}", "\n" ) ;
  13587. }else{
  13588. myprint( "Deleting $folder failed", "\n" ) ;
  13589. }
  13590. }
  13591. return ;
  13592. }
  13593. sub delete_folder
  13594. {
  13595. my ( $mysync, $imap, $folder, $Side ) = @_ ;
  13596. if ( ! $mysync ) { return ; }
  13597. if ( ! $imap ) { return ; }
  13598. if ( ! $folder ) { return ; }
  13599. $Side ||= 'HostX' ;
  13600. my $res = $mysync->{dry} ; # always success in dry mode!
  13601. if ( ! $mysync->{dry} ) {
  13602. $imap->unsubscribe( $folder ) ;
  13603. $res = $imap->delete( $folder ) ;
  13604. }
  13605. if ( $res ) {
  13606. myprint( "$Side deleted $folder", $mysync->{dry_message}, "\n" ) ;
  13607. return 1 ;
  13608. }else{
  13609. myprint( "$Side deleting $folder failed", "\n" ) ;
  13610. return ;
  13611. }
  13612. }
  13613. sub delete1emptyfolders
  13614. {
  13615. my $mysync = shift ;
  13616. if ( ! $mysync ) { return ; } # abort if no parameter
  13617. if ( ! $mysync->{delete1emptyfolders} ) { return ; } # abort if --delete1emptyfolders off
  13618. my $imap = $mysync->{imap1} ;
  13619. if ( ! $imap ) { return ; } # abort if no imap
  13620. if ( $imap->IsUnconnected( ) ) { return ; } # abort if disconnected
  13621. my %folders_kept ;
  13622. myprint( qq{Host1 deleting empty folders\n} ) ;
  13623. foreach my $folder ( reverse sort @{ $mysync->{h1_folders_wanted} } ) {
  13624. my $parenthood = $imap->is_parent( $folder ) ;
  13625. if ( defined $parenthood and $parenthood ) {
  13626. myprint( "Host1: folder $folder has subfolders\n" ) ;
  13627. $folders_kept{ $folder }++ ;
  13628. next ;
  13629. }
  13630. my $nb_messages_select = examine_folder_and_count( $mysync, $imap, $folder, 'Host1' ) ;
  13631. if ( ! defined $nb_messages_select ) { next ; } # Select failed => Neither continue nor keep this folder }
  13632. my $nb_messages_search = scalar( @{ $imap->messages( ) } ) ;
  13633. if ( 0 != $nb_messages_select and 0 != $nb_messages_search ) {
  13634. myprint( "Host1: folder $folder has messages: $nb_messages_search (search) $nb_messages_select (select)\n" ) ;
  13635. $folders_kept{ $folder }++ ;
  13636. next ;
  13637. }
  13638. if ( 0 != $nb_messages_select + $nb_messages_search ) {
  13639. myprint( "Host1: folder $folder odd messages count: $nb_messages_search (search) $nb_messages_select (select)\n" ) ;
  13640. $folders_kept{ $folder }++ ;
  13641. next ;
  13642. }
  13643. # Here we must have 0 messages by messages() aka "SEARCH ALL" and also "EXAMINE"
  13644. if ( uc $folder eq 'INBOX' ) {
  13645. myprint( "Host1: Not deleting $folder\n" ) ;
  13646. $folders_kept{ $folder }++ ;
  13647. next ;
  13648. }
  13649. myprint( "Host1: deleting empty folder $folder\n" ) ;
  13650. # can not delete a SELECTed or EXAMINEd folder so closing it
  13651. # could changed be SELECT INBOX
  13652. $imap->close( ) ; # close after examine does not expunge; anyway expunging an empty folder...
  13653. if ( delete_folder( $mysync, $imap, $folder, 'Host1' ) ) {
  13654. next ; # Deleted, good!
  13655. }else{
  13656. $folders_kept{ $folder }++ ;
  13657. next ; # Not deleted, bad!
  13658. }
  13659. }
  13660. remove_deleted_folders_from_wanted_list( $mysync, %folders_kept ) ;
  13661. myprint( qq{Host1 ended deleting empty folders\n} ) ;
  13662. return ;
  13663. }
  13664. sub remove_deleted_folders_from_wanted_list
  13665. {
  13666. my ( $mysync, %folders_kept ) = @ARG ;
  13667. my @h1_folders_wanted_init = @{ $mysync->{h1_folders_wanted} } ;
  13668. my @h1_folders_wanted_last ;
  13669. foreach my $folder ( @h1_folders_wanted_init ) {
  13670. if ( $folders_kept{ $folder } ) {
  13671. push @h1_folders_wanted_last, $folder ;
  13672. }
  13673. }
  13674. @{ $mysync->{h1_folders_wanted} } = @h1_folders_wanted_last ;
  13675. return ;
  13676. }
  13677. sub examine_folder_and_count
  13678. {
  13679. my ( $mysync, $imap, $folder, $Side ) = @_ ;
  13680. $Side ||= 'HostX' ;
  13681. if ( ! examine_folder( $mysync, $imap, $folder, $Side ) ) {
  13682. return ;
  13683. }
  13684. my $nb_messages_select = count_from_select( $imap->History ) ;
  13685. return $nb_messages_select ;
  13686. }
  13687. sub tests_delete1emptyfolders
  13688. {
  13689. note( 'Entering tests_delete1emptyfolders()' ) ;
  13690. is( undef, delete1emptyfolders( ), q{delete1emptyfolders: undef} ) ;
  13691. my $syncT ;
  13692. is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: undef 2} ) ;
  13693. my $imapT ;
  13694. $syncT->{imap1} = $imapT ;
  13695. is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: undef imap} ) ;
  13696. require_ok( "Test::MockObject" ) ;
  13697. $imapT = Test::MockObject->new( ) ;
  13698. $syncT->{imap1} = $imapT ;
  13699. $imapT->set_true( 'IsUnconnected' ) ;
  13700. is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: Unconnected imap} ) ;
  13701. # Now connected tests
  13702. $imapT->set_false( 'IsUnconnected' ) ;
  13703. $imapT->mock( 'LastError', sub { q{LastError mocked} } ) ;
  13704. $syncT->{delete1emptyfolders} = 0 ;
  13705. tests_delete1emptyfolders_unit(
  13706. $syncT,
  13707. [ qw{ INBOX DELME1 DELME2 } ],
  13708. [ qw{ INBOX DELME1 DELME2 } ],
  13709. q{tests_delete1emptyfolders: --delete1emptyfolders OFF}
  13710. ) ;
  13711. # All are parents => no deletion at all
  13712. $imapT->set_true( 'is_parent' ) ;
  13713. $syncT->{delete1emptyfolders} = 1 ;
  13714. tests_delete1emptyfolders_unit(
  13715. $syncT,
  13716. [ qw{ INBOX DELME1 DELME2 } ],
  13717. [ qw{ INBOX DELME1 DELME2 } ],
  13718. q{tests_delete1emptyfolders: --delete1emptyfolders ON}
  13719. ) ;
  13720. # No parents but examine false for all => skip all
  13721. $imapT->set_false( 'is_parent', 'examine' ) ;
  13722. tests_delete1emptyfolders_unit(
  13723. $syncT,
  13724. [ qw{ INBOX DELME1 DELME2 } ],
  13725. [ ],
  13726. q{tests_delete1emptyfolders: EXAMINE fails}
  13727. ) ;
  13728. # examine ok for all but History bad => skip all
  13729. $imapT->set_true( 'examine' ) ;
  13730. $imapT->mock( 'History', sub { ( q{History badly mocked} ) } ) ;
  13731. tests_delete1emptyfolders_unit(
  13732. $syncT,
  13733. [ qw{ INBOX DELME1 DELME2 } ],
  13734. [ ],
  13735. q{tests_delete1emptyfolders: examine ok but History badly mocked so count messages fails}
  13736. ) ;
  13737. # History good but some messages EXISTS == messages() => no deletion
  13738. $imapT->mock( 'History', sub { ( q{* 2 EXISTS} ) } ) ;
  13739. $imapT->mock( 'messages', sub { [ qw{ UID_1 UID_2 } ] } ) ;
  13740. tests_delete1emptyfolders_unit(
  13741. $syncT,
  13742. [ qw{ INBOX DELME1 DELME2 } ],
  13743. [ qw{ INBOX DELME1 DELME2 } ],
  13744. q{tests_delete1emptyfolders: History EXAMINE ok, several messages}
  13745. ) ;
  13746. # 0 EXISTS but != messages() => no deletion
  13747. $imapT->mock( 'History', sub { ( q{* 0 EXISTS} ) } ) ;
  13748. $imapT->mock( 'messages', sub { [ qw{ UID_1 UID_2 } ] } ) ;
  13749. tests_delete1emptyfolders_unit(
  13750. $syncT,
  13751. [ qw{ INBOX DELME1 DELME2 } ],
  13752. [ qw{ INBOX DELME1 DELME2 } ],
  13753. q{tests_delete1emptyfolders: 0 EXISTS but 2 by messages()}
  13754. ) ;
  13755. # 1 EXISTS but != 0 == messages() => no deletion
  13756. $imapT->mock( 'History', sub { ( q{* 1 EXISTS} ) } ) ;
  13757. $imapT->mock( 'messages', sub { [ ] } ) ;
  13758. tests_delete1emptyfolders_unit(
  13759. $syncT,
  13760. [ qw{ INBOX DELME1 DELME2 } ],
  13761. [ qw{ INBOX DELME1 DELME2 } ],
  13762. q{tests_delete1emptyfolders: 1 EXISTS but 0 by messages()}
  13763. ) ;
  13764. # 0 EXISTS and 0 == messages() => deletion except INBOX
  13765. $imapT->mock( 'History', sub { ( q{* 0 EXISTS} ) } ) ;
  13766. $imapT->mock( 'messages', sub { [ ] } ) ;
  13767. $imapT->set_true( qw{ delete close unsubscribe } ) ;
  13768. $syncT->{dry_message} = q{ (not really since in a mocked test)} ;
  13769. tests_delete1emptyfolders_unit(
  13770. $syncT,
  13771. [ qw{ INBOX DELME1 DELME2 } ],
  13772. [ qw{ INBOX } ],
  13773. q{tests_delete1emptyfolders: 0 EXISTS 0 by messages() delete folders, keep INBOX}
  13774. ) ;
  13775. note( 'Leaving tests_delete1emptyfolders()' ) ;
  13776. return ;
  13777. }
  13778. sub tests_delete1emptyfolders_unit
  13779. {
  13780. note( 'Entering tests_delete1emptyfolders_unit()' ) ;
  13781. my $syncT = shift ;
  13782. my $folders1wanted_init_ref = shift ;
  13783. my $folders1wanted_after_ref = shift ;
  13784. my $comment = shift || q{delete1emptyfolders:} ;
  13785. my @folders1wanted_init = @{ $folders1wanted_init_ref } ;
  13786. my @folders1wanted_after = @{ $folders1wanted_after_ref } ;
  13787. @{ $syncT->{h1_folders_wanted} } = @folders1wanted_init ;
  13788. is_deeply( $syncT->{h1_folders_wanted}, \@folders1wanted_init, qq{$comment, init check} ) ;
  13789. delete1emptyfolders( $syncT ) ;
  13790. is_deeply( $syncT->{h1_folders_wanted}, \@folders1wanted_after, qq{$comment, after check} ) ;
  13791. note( 'Leaving tests_delete1emptyfolders_unit()' ) ;
  13792. return ;
  13793. }
  13794. sub extract_header
  13795. {
  13796. my $string = shift ;
  13797. my ( $header ) = split /\n\n/x, $string ;
  13798. if ( ! $header ) { return( q{} ) ; }
  13799. #myprint( "[$header]\n" ) ;
  13800. return( $header ) ;
  13801. }
  13802. sub tests_extract_header
  13803. {
  13804. note( 'Entering tests_extract_header()' ) ;
  13805. my $h = <<'EOM';
  13806. Message-Id: <20100428101817.A66CB162474E@plume.est.belle>
  13807. Date: Wed, 28 Apr 2010 12:18:17 +0200 (CEST)
  13808. From: gilles@louloutte.dyndns.org (Gilles LAMIRAL)
  13809. EOM
  13810. chomp $h ;
  13811. ok( $h eq extract_header(
  13812. <<'EOM'
  13813. Message-Id: <20100428101817.A66CB162474E@plume.est.belle>
  13814. Date: Wed, 28 Apr 2010 12:18:17 +0200 (CEST)
  13815. From: gilles@louloutte.dyndns.org (Gilles LAMIRAL)
  13816. body
  13817. lalala
  13818. EOM
  13819. ), 'extract_header: 1') ;
  13820. note( 'Leaving tests_extract_header()' ) ;
  13821. return ;
  13822. }
  13823. sub decompose_header{
  13824. my $string = shift ;
  13825. # a hash, for a keyword header KEY value are list of strings [VAL1, VAL1_other, etc]
  13826. # Think of multiple "Received:" header lines.
  13827. my $header = { } ;
  13828. my ($key, $val ) ;
  13829. my @line = split /\n|\r\n/x, $string ;
  13830. foreach my $line ( @line ) {
  13831. #myprint( "DDD $line\n" ) ;
  13832. # End of header
  13833. last if ( $line =~ m{^$}xo ) ;
  13834. # Key: value
  13835. if ( $line =~ m/(^[^:]+):\s(.*)/xo ) {
  13836. $key = $1 ;
  13837. $val = $2 ;
  13838. $debugdev and myprint( "DDD KV [$key] [$val]\n" ) ;
  13839. push @{ $header->{ $key } }, $val ;
  13840. # blanc and value => value from previous line continues
  13841. }elsif( $line =~ m/^(\s+)(.*)/xo ) {
  13842. $val = $2 ;
  13843. $debugdev and myprint( "DDD V [$val]\n" ) ;
  13844. @{ $header->{ $key } }[ $LAST ] .= " $val" if $key ;
  13845. # dirty line?
  13846. }else{
  13847. next ;
  13848. }
  13849. }
  13850. #myprint( Data::Dumper->Dump( [ $header ] ) ) ;
  13851. return( $header ) ;
  13852. }
  13853. sub tests_decompose_header{
  13854. note( 'Entering tests_decompose_header()' ) ;
  13855. my $header_dec ;
  13856. $header_dec = decompose_header(
  13857. <<'EOH'
  13858. KEY_1: VAL_1
  13859. KEY_2: VAL_2
  13860. VAL_2_+
  13861. VAL_2_++
  13862. KEY_3: VAL_3
  13863. KEY_1: VAL_1_other
  13864. KEY_4: VAL_4
  13865. VAL_4_+
  13866. KEY_5 BLANC: VAL_5
  13867. KEY_6_BAD_BODY: VAL_6
  13868. EOH
  13869. ) ;
  13870. ok( 'VAL_3'
  13871. eq $header_dec->{ 'KEY_3' }[0], 'decompose_header: VAL_3' ) ;
  13872. ok( 'VAL_1'
  13873. eq $header_dec->{ 'KEY_1' }[0], 'decompose_header: VAL_1' ) ;
  13874. ok( 'VAL_1_other'
  13875. eq $header_dec->{ 'KEY_1' }[1], 'decompose_header: VAL_1_other' ) ;
  13876. ok( 'VAL_2 VAL_2_+ VAL_2_++'
  13877. eq $header_dec->{ 'KEY_2' }[0], 'decompose_header: VAL_2 VAL_2_+ VAL_2_++' ) ;
  13878. ok( 'VAL_4 VAL_4_+'
  13879. eq $header_dec->{ 'KEY_4' }[0], 'decompose_header: VAL_4 VAL_4_+' ) ;
  13880. ok( ' VAL_5'
  13881. eq $header_dec->{ 'KEY_5 BLANC' }[0], 'decompose_header: KEY_5 BLANC' ) ;
  13882. ok( not( defined $header_dec->{ 'KEY_6_BAD_BODY' }[0] ), 'decompose_header: KEY_6_BAD_BODY' ) ;
  13883. $header_dec = decompose_header(
  13884. <<'EOH'
  13885. Message-Id: <20100428101817.A66CB162474E@plume.est.belle>
  13886. Date: Wed, 28 Apr 2010 12:18:17 +0200 (CEST)
  13887. From: gilles@louloutte.dyndns.org (Gilles LAMIRAL)
  13888. EOH
  13889. ) ;
  13890. ok( '<20100428101817.A66CB162474E@plume.est.belle>'
  13891. eq $header_dec->{ 'Message-Id' }[0], 'decompose_header: 1' ) ;
  13892. $header_dec = decompose_header(
  13893. <<'EOH'
  13894. Return-Path: <gilles@louloutte.dyndns.org>
  13895. Received: by plume.est.belle (Postfix, from userid 1000)
  13896. id 120A71624742; Wed, 28 Apr 2010 01:46:40 +0200 (CEST)
  13897. Subject: test:eekahceishukohpe
  13898. EOH
  13899. ) ;
  13900. ok(
  13901. 'by plume.est.belle (Postfix, from userid 1000) id 120A71624742; Wed, 28 Apr 2010 01:46:40 +0200 (CEST)'
  13902. eq $header_dec->{ 'Received' }[0], 'decompose_header: 2' ) ;
  13903. $header_dec = decompose_header(
  13904. <<'EOH'
  13905. Received: from plume (localhost [127.0.0.1])
  13906. by plume.est.belle (Postfix) with ESMTP id C6EB73F6C9
  13907. for <gilles@localhost>; Mon, 26 Nov 2007 10:39:06 +0100 (CET)
  13908. Received: from plume [192.168.68.7]
  13909. by plume with POP3 (fetchmail-6.3.6)
  13910. for <gilles@localhost> (single-drop); Mon, 26 Nov 2007 10:39:06 +0100 (CET)
  13911. EOH
  13912. ) ;
  13913. ok(
  13914. '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)'
  13915. eq $header_dec->{ 'Received' }[0], 'decompose_header: 3' ) ;
  13916. ok(
  13917. '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)'
  13918. eq $header_dec->{ 'Received' }[1], 'decompose_header: 3' ) ;
  13919. # Bad header beginning with a blank character
  13920. $header_dec = decompose_header(
  13921. <<'EOH'
  13922. KEY_1: VAL_1
  13923. KEY_2: VAL_2
  13924. VAL_2_+
  13925. VAL_2_++
  13926. KEY_3: VAL_3
  13927. KEY_1: VAL_1_other
  13928. EOH
  13929. ) ;
  13930. ok( 'VAL_3'
  13931. eq $header_dec->{ 'KEY_3' }[0], 'decompose_header: Bad header VAL_3' ) ;
  13932. ok( 'VAL_1_other'
  13933. eq $header_dec->{ 'KEY_1' }[0], 'decompose_header: Bad header VAL_1_other' ) ;
  13934. ok( 'VAL_2 VAL_2_+ VAL_2_++'
  13935. eq $header_dec->{ 'KEY_2' }[0], 'decompose_header: Bad header VAL_2 VAL_2_+ VAL_2_++' ) ;
  13936. note( 'Leaving tests_decompose_header()' ) ;
  13937. return ;
  13938. }
  13939. sub tests_epoch
  13940. {
  13941. note( 'Entering tests_epoch()' ) ;
  13942. ok( '1282658400' eq epoch( '24-Aug-2010 16:00:00 +0200' ), 'epoch 24-Aug-2010 16:00:00 +0200 -> 1282658400' ) ;
  13943. ok( '1282658400' eq epoch( '24-Aug-2010 14:00:00 +0000' ), 'epoch 24-Aug-2010 14:00:00 +0000 -> 1282658400' ) ;
  13944. ok( '1282658400' eq epoch( '24-Aug-2010 12:00:00 -0200' ), 'epoch 24-Aug-2010 12:00:00 -0200 -> 1282658400' ) ;
  13945. ok( '1282658400' eq epoch( '24-Aug-2010 16:01:00 +0201' ), 'epoch 24-Aug-2010 16:01:00 +0201 -> 1282658400' ) ;
  13946. ok( '1282658400' eq epoch( '24-Aug-2010 14:01:00 +0001' ), 'epoch 24-Aug-2010 14:01:00 +0001 -> 1282658400' ) ;
  13947. ok( '1280671200' eq epoch( '1-Aug-2010 16:00:00 +0200' ), 'epoch 1-Aug-2010 16:00:00 +0200 -> 1280671200' ) ;
  13948. ok( '1280671200' eq epoch( '1-Aug-2010 14:00:00 +0000' ), 'epoch 1-Aug-2010 14:00:00 +0000 -> 1280671200' ) ;
  13949. ok( '1280671200' eq epoch( '1-Aug-2010 12:00:00 -0200' ), 'epoch 1-Aug-2010 12:00:00 -0200 -> 1280671200' ) ;
  13950. ok( '1280671200' eq epoch( '1-Aug-2010 16:01:00 +0201' ), 'epoch 1-Aug-2010 16:01:00 +0201 -> 1280671200' ) ;
  13951. ok( '1280671200' eq epoch( '1-Aug-2010 14:01:00 +0001' ), 'epoch 1-Aug-2010 14:01:00 +0001 -> 1280671200' ) ;
  13952. is( '1280671200', epoch( '1-Aug-2010 14:01:00 +0001' ), 'epoch 1-Aug-2010 14:01:00 +0001 -> 1280671200' ) ;
  13953. is( '946684800', epoch( '00-Jan-0000 00:00:00 +0000' ), 'epoch 1-Aug-2010 14:01:00 +0001 -> 1280671200' ) ;
  13954. note( 'Leaving tests_epoch()' ) ;
  13955. return ;
  13956. }
  13957. sub epoch
  13958. {
  13959. # incoming format:
  13960. # internal date 24-Aug-2010 16:00:00 +0200
  13961. # outgoing format: epoch
  13962. my $d = shift ;
  13963. return(q{}) if not defined $d;
  13964. my ( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m ) ;
  13965. my $time ;
  13966. 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 ) {
  13967. #myprint( "internal: [$1][$2][$3][$4][$5][$6][$7][$8][$9]\n" ) ;
  13968. ( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m )
  13969. = ( $1, $2, $3, $4, $5, $6, $7, $8, $9 ) ;
  13970. #myprint( "( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m )\n" ) ;
  13971. $sign = +1 if ( '+' eq $sign ) ;
  13972. $sign = $MINUS_ONE if ( '-' eq $sign ) ;
  13973. if ( 0 == $mday ) {
  13974. myprint( "buggy day in $d. Fixed to 01\n" ) ;
  13975. $mday = '01' ;
  13976. }
  13977. $time = timegm( $sec, $min, $hour, $mday, $month_abrev{$month}, $year )
  13978. - $sign * ( 3600 * $zone_h + 60 * $zone_m ) ;
  13979. #myprint( "$time ", scalar localtime($time), "\n");
  13980. }
  13981. return( $time ) ;
  13982. }
  13983. sub tests_add_header
  13984. {
  13985. note( 'Entering tests_add_header()' ) ;
  13986. ok( 'Message-Id: <mistake@imapsync>' eq add_header(), 'add_header no arg' ) ;
  13987. ok( 'Message-Id: <123456789@imapsync>' eq add_header( '123456789' ), 'add_header 123456789' ) ;
  13988. note( 'Leaving tests_add_header()' ) ;
  13989. return ;
  13990. }
  13991. sub add_header
  13992. {
  13993. my $header_uid = shift || 'mistake' ;
  13994. my $header_Message_Id = 'Message-Id: <' . $header_uid . '@imapsync>' ;
  13995. return( $header_Message_Id ) ;
  13996. }
  13997. sub tests_max_line_length
  13998. {
  13999. note( 'Entering tests_max_line_length()' ) ;
  14000. ok( 0 == max_line_length( q{} ), 'max_line_length: 0 == null string' ) ;
  14001. ok( 1 == max_line_length( "\n" ), 'max_line_length: 1 == \n' ) ;
  14002. ok( 1 == max_line_length( "\n\n" ), 'max_line_length: 1 == \n\n' ) ;
  14003. ok( 1 == max_line_length( "\n" x 500 ), 'max_line_length: 1 == 500 \n' ) ;
  14004. ok( 1 == max_line_length( 'a' ), 'max_line_length: 1 == a' ) ;
  14005. ok( 2 == max_line_length( "a\na" ), 'max_line_length: 2 == a\na' ) ;
  14006. ok( 2 == max_line_length( "a\na\n" ), 'max_line_length: 2 == a\na\n' ) ;
  14007. ok( 3 == max_line_length( "a\nab\n" ), 'max_line_length: 3 == a\nab\n' ) ;
  14008. ok( 3 == max_line_length( "a\nab\n" x 1_000 ), 'max_line_length: 3 == 1_000 a\nab\n' ) ;
  14009. ok( 3 == max_line_length( "a\nab\nabc" ), 'max_line_length: 3 == a\nab\nabc' ) ;
  14010. ok( 4 == max_line_length( "a\nab\nabc\n" ), 'max_line_length: 4 == a\nab\nabc\n' ) ;
  14011. ok( 5 == max_line_length( "a\nabcd\nabc\n" ), 'max_line_length: 5 == a\nabcd\nabc\n' ) ;
  14012. 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' ) ;
  14013. note( 'Leaving tests_max_line_length()' ) ;
  14014. return ;
  14015. }
  14016. sub max_line_length
  14017. {
  14018. my $string = shift ;
  14019. my $max = 0 ;
  14020. while ( $string =~ m/([^\n]*\n?)/msxg ) {
  14021. $max = max( $max, length $1 ) ;
  14022. }
  14023. return( $max ) ;
  14024. }
  14025. sub set_checknoabletosearch
  14026. {
  14027. my $mysync = shift @ARG ;
  14028. if ( defined $mysync->{ checknoabletosearch } )
  14029. {
  14030. return ;
  14031. }
  14032. elsif ( $mysync->{ justfolders } )
  14033. {
  14034. $mysync->{ checknoabletosearch } = 0 ;
  14035. }
  14036. else
  14037. {
  14038. $mysync->{ checknoabletosearch } = 1 ;
  14039. }
  14040. return ;
  14041. }
  14042. sub tests_setlogfile
  14043. {
  14044. note( 'Entering tests_setlogfile()' ) ;
  14045. my $mysync = {} ;
  14046. $mysync->{logdir} = 'vallogdir' ;
  14047. $mysync->{logfile} = 'vallogfile.txt' ;
  14048. is( 'vallogdir/vallogfile.txt', setlogfile( $mysync ),
  14049. 'setlogfile: logdir vallogdir, logfile vallogfile.txt, vallogdir/vallogfile.txt' ) ;
  14050. SKIP: {
  14051. skip( 'Too hard to have a well known timezone on Windows', 9 ) if ( 'MSWin32' eq $OSNAME ) ;
  14052. local $ENV{TZ} = 'GMT' ;
  14053. $mysync = {
  14054. timestart => 2,
  14055. } ;
  14056. is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000__.txt", setlogfile( $mysync ),
  14057. "setlogfile: default is like $DEFAULT_LOGDIR/1970_01_01_00_00_02_000__.txt" ) ;
  14058. $mysync = {
  14059. timestart => 2,
  14060. user1 => 'user1',
  14061. user2 => 'user2',
  14062. abort => 1,
  14063. } ;
  14064. is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_abort.txt", setlogfile( $mysync ),
  14065. "setlogfile: default is like $DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_abort.txt" ) ;
  14066. $mysync = {
  14067. timestart => 2,
  14068. user1 => 'user1',
  14069. user2 => 'user2',
  14070. remote => 'zzz',
  14071. } ;
  14072. is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_remote.txt", setlogfile( $mysync ),
  14073. "setlogfile: default is like $DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_remote.txt" ) ;
  14074. $mysync = {
  14075. timestart => 2,
  14076. user1 => 'user1',
  14077. user2 => 'user2',
  14078. remote => 'zzz',
  14079. abort => 1,
  14080. } ;
  14081. is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_remote_abort.txt", setlogfile( $mysync ),
  14082. "setlogfile: default is like $DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_remote_abort.txt" ) ;
  14083. $mysync = {
  14084. timestart => 2,
  14085. user1 => 'user1',
  14086. user2 => 'user2',
  14087. } ;
  14088. is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2.txt", setlogfile( $mysync ),
  14089. "setlogfile: default is like $DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2.txt" ) ;
  14090. $mysync->{logdir} = undef ;
  14091. $mysync->{logfile} = undef ;
  14092. is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2.txt", setlogfile( $mysync ),
  14093. "setlogfile: logdir undef, $DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2.txt" ) ;
  14094. $mysync->{logdir} = q{} ;
  14095. $mysync->{logfile} = undef ;
  14096. is( '1970_01_01_00_00_02_000_user1_user2.txt', setlogfile( $mysync ),
  14097. 'setlogfile: logdir empty, 1970_01_01_00_00_02_000_user1_user2.txt' ) ;
  14098. $mysync->{logdir} = 'vallogdir' ;
  14099. $mysync->{logfile} = undef ;
  14100. is( 'vallogdir/1970_01_01_00_00_02_000_user1_user2.txt', setlogfile( $mysync ),
  14101. 'setlogfile: logdir vallogdir, vallogdir/1970_01_01_00_00_02_000_user1_user2.txt' ) ;
  14102. $mysync = {
  14103. user1 => 'us/er1a*|?:"<>b',
  14104. user2 => 'u/ser2a*|?:"<>b',
  14105. } ;
  14106. is( "$DEFAULT_LOGDIR/1970_01_01_00_00_00_000_us_er1a_______b_u_ser2a_______b.txt", setlogfile( $mysync ),
  14107. "setlogfile: logdir undef, $DEFAULT_LOGDIR/1970_01_01_00_00_00_000_us_er1a_______b_u_ser2a_______b.txt" ) ;
  14108. } ;
  14109. note( 'Leaving tests_setlogfile()' ) ;
  14110. return ;
  14111. }
  14112. sub setlogfile
  14113. {
  14114. my( $mysync ) = shift ;
  14115. # When aborting another process the log file name finishes with "_abort.txt"
  14116. my $abort_suffix = ( $mysync->{ abort } ) ? '_abort' : q{} ;
  14117. # When acting as a proxy the log file name finishes with "_remote.txt"
  14118. # proxy mode is not done in imapsync, it is done by proximapsync
  14119. my $remote_suffix = ( $mysync->{ remote } ) ? '_remote' : q{} ;
  14120. my $suffix = (
  14121. filter_forbidden_characters( slash_to_underscore( $mysync->{ user1 } ) ) || q{} )
  14122. . '_'
  14123. . ( filter_forbidden_characters( slash_to_underscore( $mysync->{ user2 } ) ) || q{} )
  14124. . $remote_suffix . $abort_suffix ;
  14125. $mysync->{ logdir } = defined $mysync->{ logdir } ? $mysync->{ logdir } : $DEFAULT_LOGDIR ;
  14126. $mysync->{ logfile } = defined $mysync->{ logfile }
  14127. ? "$mysync->{ logdir }/$mysync->{ logfile }"
  14128. : logfile( $mysync->{ timestart }, $suffix, $mysync->{ logdir } ) ;
  14129. return( $mysync->{ logfile } ) ;
  14130. }
  14131. sub tests_logfile
  14132. {
  14133. note( 'Entering tests_logfile()' ) ;
  14134. SKIP: {
  14135. # Too hard to have a well known timezone on Windows
  14136. skip( 'Too hard to have a well known timezone on Windows', 10 ) if ( 'MSWin32' eq $OSNAME ) ;
  14137. local $ENV{TZ} = 'GMT' ;
  14138. {
  14139. POSIX::tzset unless ('MSWin32' eq $OSNAME) ;
  14140. is( '1970_01_01_00_00_00_000.txt', logfile( ), 'logfile: no args => 1970_01_01_00_00_00.txt' ) ;
  14141. is( '1970_01_01_00_00_00_000.txt', logfile( 0 ), 'logfile: 0 => 1970_01_01_00_00_00.txt' ) ;
  14142. is( '1970_01_01_00_01_01_000.txt', logfile( 61 ), 'logfile: 0 => 1970_01_01_00_01_01.txt' ) ;
  14143. is( '1970_01_01_00_01_01_234.txt', logfile( 61.234 ), 'logfile: 0 => 1970_01_01_00_01_01.txt' ) ;
  14144. 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' ) ;
  14145. 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' ) ;
  14146. 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' ) ;
  14147. 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' ) ;
  14148. is( '2010_08_24_14_01_01_234_poup.txt', logfile( 1_282_658_461.2347, 'poup' ),
  14149. 'logfile: 1_282_658_461.2347 poup => 2010_08_24_14_01_01_234_poup.txt' ) ;
  14150. is( 'dirdir/2010_08_24_14_01_01_234_poup.txt', logfile( 1_282_658_461.2347, 'poup', 'dirdir' ),
  14151. 'logfile: 1_282_658_461.2347 poup dirdir => dirdir/2010_08_24_14_01_01_234_poup.txt' ) ;
  14152. }
  14153. POSIX::tzset unless ('MSWin32' eq $OSNAME) ;
  14154. } ;
  14155. note( 'Leaving tests_logfile()' ) ;
  14156. return ;
  14157. }
  14158. sub logfile
  14159. {
  14160. my ( $time, $suffix, $dir ) = @_ ;
  14161. $time ||= 0 ;
  14162. $suffix ||= q{} ;
  14163. $suffix =~ tr/ //ds ;
  14164. my $sep_suffix = ( $suffix ) ? '_' : q{} ;
  14165. $dir ||= q{} ;
  14166. my $sep_dir = ( $dir ) ? '/' : q{} ;
  14167. my $date_str = POSIX::strftime( '%Y_%m_%d_%H_%M_%S', localtime $time ) ;
  14168. # Because of ab tests or web accesses, more than one sync withing one second is possible
  14169. # so we add also milliseconds
  14170. $date_str .= sprintf "_%03d", ($time - int( $time ) ) * 1000 ; # without rounding
  14171. my $logfile = "${dir}${sep_dir}${date_str}${sep_suffix}${suffix}.txt" ;
  14172. return( $logfile ) ;
  14173. }
  14174. sub tests_localtimez
  14175. {
  14176. note( 'Entering tests_localtimez()' ) ;
  14177. SKIP: {
  14178. # Too hard to have a well known timezone on Windows
  14179. skip( 'Too hard to have a well known timezone on Windows', 1 ) if ( 'MSWin32' eq $OSNAME ) ;
  14180. local $ENV{TZ} = 'GMT' ;
  14181. like( localtimez( 0 ), qr'1970-01-01 00:00:00 \+0000 (GMT|UTC)', 'localtimez: 0 => match 1970-01-01 00:00:00 +0000 GMT' ) ;
  14182. }
  14183. is( localtimez( ), localtimez( time ), 'localtimez: undef => equals currrent' ) ;
  14184. note( 'Leaving tests_localtimez()' ) ;
  14185. return ;
  14186. }
  14187. sub localtimez
  14188. {
  14189. my $time = shift ;
  14190. $time = defined( $time ) ? $time : time ;
  14191. my $datetimestr = POSIX::strftime( '%A %e %B %Y-%m-%d %H:%M:%S %z %Z', localtime( $time ) ) ;
  14192. #myprint( "$datetimestr\n" ) ;
  14193. return $datetimestr ;
  14194. }
  14195. sub tests_slash_to_underscore
  14196. {
  14197. note( 'Entering tests_slash_to_underscore()' ) ;
  14198. is( undef, slash_to_underscore( ), 'slash_to_underscore: no parameters => undef' ) ;
  14199. is( '_', slash_to_underscore( '/' ), 'slash_to_underscore: / => _' ) ;
  14200. is( '_abc_def_', slash_to_underscore( '/abc/def/' ), 'slash_to_underscore: /abc/def/ => _abc_def_' ) ;
  14201. note( 'Leaving tests_slash_to_underscore()' ) ;
  14202. return ;
  14203. }
  14204. sub slash_to_underscore
  14205. {
  14206. my $string = shift ;
  14207. if ( ! defined $string ) { return ; }
  14208. $string =~ tr{/}{_} ;
  14209. return( $string ) ;
  14210. }
  14211. sub tests_million_folders_baby_2
  14212. {
  14213. note( 'Entering tests_million_folders_baby_2()' ) ;
  14214. my %long ;
  14215. @long{ 1 .. 900_000 } = (1) x 900_000 ;
  14216. #myprint( %long, "\n" ) ;
  14217. my $pasglop = 0 ;
  14218. foreach my $elem ( 1 .. 900_000 ) {
  14219. #$debug and myprint( "$elem " ) ;
  14220. if ( not exists $long{ $elem } ) {
  14221. $pasglop++ ;
  14222. }
  14223. }
  14224. ok( 0 == $pasglop, 'tests_million_folders_baby_2: search among 900_000' ) ;
  14225. # myprint( "$pasglop\n" ) ;
  14226. note( 'Leaving tests_million_folders_baby_2()' ) ;
  14227. return ;
  14228. }
  14229. sub tests_always_fail
  14230. {
  14231. note( 'Entering tests_always_fail()' ) ;
  14232. is( 0, 1, 'always_fail: 0 is 1' ) ;
  14233. note( 'Leaving tests_always_fail()' ) ;
  14234. return ;
  14235. }
  14236. sub tests_logfileprepa
  14237. {
  14238. note( 'Entering tests_logfileprepa()' ) ;
  14239. is( undef, logfileprepa( ), 'logfileprepa: no args => undef' ) ;
  14240. my $logfile = 'W/tmp/tests/tests_logfileprepa.txt' ;
  14241. is( 1, logfileprepa( $logfile ), 'logfileprepa: W/tmp/tests/tests_logfileprepa.txt => 1' ) ;
  14242. note( 'Leaving tests_logfileprepa()' ) ;
  14243. return ;
  14244. }
  14245. sub logfileprepa
  14246. {
  14247. my $logfile = shift ;
  14248. if ( ! defined( $logfile ) )
  14249. {
  14250. return ;
  14251. }else
  14252. {
  14253. #myprint( "[$logfile]\n" ) ;
  14254. my $dirname = dirname( $logfile ) ;
  14255. do_valid_directory( $dirname ) || return( 0 ) ;
  14256. return( 1 ) ;
  14257. }
  14258. }
  14259. sub tests_teelaunch
  14260. {
  14261. note( 'Entering tests_teelaunch()' ) ;
  14262. is( undef, teelaunch( ), 'teelaunch: no args => undef' ) ;
  14263. my $mysync = {} ;
  14264. is( undef, teelaunch( $mysync ), 'teelaunch: arg empty {} => undef' ) ;
  14265. $mysync->{logfile} = q{} ;
  14266. is( undef, teelaunch( $mysync ), 'teelaunch: logfile empty string => undef' ) ;
  14267. # First time, learning IO::Tee intrasics
  14268. $mysync->{logfile} = 'W/tmp/tests/tests_teelaunch.txt' ;
  14269. isa_ok( my $tee = teelaunch( $mysync ), 'IO::Tee' , 'teelaunch: logfile W/tmp/tests/tests_teelaunch.txt' ) ;
  14270. is( 1, print( $tee "Hi!\n" ), 'teelaunch: write Hi!') ;
  14271. is( "Hi!\n", file_to_string( 'W/tmp/tests/tests_teelaunch.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch.txt is Hi!\n' ) ;
  14272. is( 1, print( $tee "Hoo\n" ), 'teelaunch: write Hoo') ;
  14273. 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' ) ;
  14274. # closing so tee won't be happy
  14275. close $mysync->{logfile_handle} ;
  14276. is( undef, print( $tee "Argh1\n" ), 'teelaunch: write Argh1') ;
  14277. is( undef, print( $tee "Argh2\n" ), 'teelaunch: write Argh2') ;
  14278. # write not done
  14279. is( "Hi!\nHoo\n", file_to_string( 'W/tmp/tests/tests_teelaunch.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch.txt is still Hi!\nHoo\n' ) ;
  14280. print join( ' ', $tee->handles ), "\n";
  14281. is( 2, scalar $tee->handles, 'teelaunch: 2 handles') ;
  14282. shift @{*{$tee}};
  14283. print join(' ', $tee->handles), "\n" ;
  14284. is( 1, scalar $tee->handles, 'teelaunch: 1 handle') ;
  14285. is( 1, print( $tee "Argh3\n" ), 'teelaunch: write Argh3 yeah') ;
  14286. shift @{*{$tee}};
  14287. # will not print anything now
  14288. is( 0, scalar $tee->handles, 'teelaunch: 0 handle') ;
  14289. is( 1, print( $tee "Argh 4\n" ), 'teelaunch: write Argh4 no') ;
  14290. # Second time, lesson learnt IO::Tee
  14291. $mysync->{logfile} = 'W/tmp/tests/tests_teelaunch2.txt' ;
  14292. isa_ok( $tee = teelaunch( $mysync ), 'IO::Tee' , 'teelaunch: logfile W/tmp/tests/tests_teelaunch2.txt' ) ;
  14293. is( 1, print( $tee "Hi!\n" ), 'teelaunch: write Hi!') ;
  14294. is( "Hi!\n", file_to_string( 'W/tmp/tests/tests_teelaunch2.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch2.txt is Hi!\n' ) ;
  14295. is( 1, print( $tee "Hoo\n" ), 'teelaunch: write Hoo') ;
  14296. is( "Hi!\nHoo\n", file_to_string( 'W/tmp/tests/tests_teelaunch2.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch2.txt is Hi!\nHoo\n' ) ;
  14297. is( 1, teefinish( $mysync ), 'teefinish: return 1') ;
  14298. is( 1, print( $tee "Argh1\n" ), 'teelaunch: write Argh1') ;
  14299. is( 1, print( $tee "Argh2\n" ), 'teelaunch: write Argh2') ;
  14300. is( "Hi!\nHoo\n", file_to_string( 'W/tmp/tests/tests_teelaunch2.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch2.txt is still Hi!\nHoo\n' ) ;
  14301. is( 1, teefinish( $mysync ), 'teefinish: still return 1') ;
  14302. note( 'Leaving tests_teelaunch()' ) ;
  14303. return ;
  14304. }
  14305. sub teelaunch
  14306. {
  14307. my $mysync = shift ;
  14308. if ( ! defined( $mysync ) )
  14309. {
  14310. return ;
  14311. }
  14312. my $logfile = $mysync->{logfile} ;
  14313. if ( ! $logfile )
  14314. {
  14315. return ;
  14316. }
  14317. logfileprepa( $logfile ) || croak "Error no valid directory to write log file $logfile : $OS_ERROR" ;
  14318. # This is a log file opened during the whole sync
  14319. ## no critic (InputOutput::RequireBriefOpen)
  14320. open my $logfile_handle, '>', $logfile
  14321. or croak( "Can not open $logfile for write: $OS_ERROR" ) ;
  14322. binmode $logfile_handle, ":encoding(UTF-8)" ;
  14323. my $tee = IO::Tee->new( $logfile_handle, \*STDOUT ) ;
  14324. $tee->autoflush( 1 ) ;
  14325. $mysync->{logfile_handle} = $logfile_handle ;
  14326. $mysync->{tee} = $tee ;
  14327. return $tee ;
  14328. }
  14329. sub teefinish
  14330. {
  14331. my $mysync = shift ;
  14332. if ( ! defined( $mysync ) ) { return ; }
  14333. my $tee = $mysync->{tee} ;
  14334. if ( ! defined( $tee ) ) { return ; }
  14335. if ( 2 == scalar $tee->handles )
  14336. {
  14337. shift @{*{$tee}};
  14338. }
  14339. else
  14340. {
  14341. # nothing
  14342. }
  14343. return scalar $tee->handles ;
  14344. }
  14345. sub getpwuid_any_os
  14346. {
  14347. my $uid = shift ;
  14348. return( scalar getlogin ) if ( 'MSWin32' eq $OSNAME ) ; # Windows system
  14349. return( scalar getpwuid $uid ) ; # Unix system
  14350. }
  14351. sub abortifneeded
  14352. {
  14353. my $mysync = shift ;
  14354. if ( -e $mysync->{ abortfile } )
  14355. {
  14356. myprint( "Asked to terminate by file $mysync->{ abortfile }\n" ) ;
  14357. do_and_print_stats( $mysync ) ;
  14358. myprint( "You should resynchronize those accounts by running a sync again,\n",
  14359. "since some messages and entire folders might still be missing on host2.\n"
  14360. ) ;
  14361. exit_clean( $mysync, $EXIT_BY_FILE ) ;
  14362. return ;
  14363. }
  14364. else
  14365. {
  14366. return ;
  14367. }
  14368. }
  14369. sub simulong
  14370. {
  14371. my $mysync = shift ;
  14372. my $max_seconds = $mysync->{ simulong } ;
  14373. if ( ! $max_seconds ) { return ; }
  14374. my $division = 5 ;
  14375. my $last_count = int( $division * $max_seconds ) ;
  14376. $mysync->{ debug } and myprint "last_count $last_count = int( division $division * max_seconds $max_seconds)\n" ;
  14377. foreach my $i ( 1 .. ( $last_count ) ) {
  14378. myprint( "Are you still here ETA: " . ( $last_count - $i ) . "/$last_count msgs left\n" ) ;
  14379. #this one is for testing huge page behavior
  14380. #myprint( "Are you still here ETA: " . ($last_count - $i) . "/$last_count msgs left\n" . ( "Ah" x 40 . "\n") x 4000 ) ;
  14381. sleep( 1 / $division ) ;
  14382. abortifneeded( $mysync ) ;
  14383. }
  14384. return ;
  14385. }
  14386. sub printenv
  14387. {
  14388. myprint( "Environment variables listing:\n",
  14389. ( map { "$_ => $ENV{$_}\n" } sort keys %ENV),
  14390. "Environment variables listing end\n" ) ;
  14391. return ;
  14392. }
  14393. sub unittestssuite
  14394. {
  14395. my $mysync = shift ;
  14396. if ( ! ( $mysync->{ tests } or $mysync->{ testsdebug } or $mysync->{ testsunit } ) ) {
  14397. return ;
  14398. }
  14399. my $test_builder = Test::More->builder ;
  14400. tests( $mysync ) ;
  14401. testsdebug( $mysync ) ;
  14402. testunitsession( $mysync ) ;
  14403. my @summary = $test_builder->summary() ;
  14404. my @details = $test_builder->details() ;
  14405. my $nb_tests_run = scalar( @summary ) ;
  14406. my $nb_tests_expected = $test_builder->expected_tests() ;
  14407. my $nb_tests_failed = count_0s( @summary ) ;
  14408. my $tests_failed = report_failures( @details ) ;
  14409. if ( $nb_tests_failed or ( $nb_tests_run != $nb_tests_expected ) ) {
  14410. #$test_builder->reset( ) ;
  14411. myprint( "Summary of tests: failed $nb_tests_failed tests, run $nb_tests_run tests, expected to run $nb_tests_expected tests.\n",
  14412. "List of failed tests:\n", $tests_failed ) ;
  14413. return $EXIT_TESTS_FAILED ;
  14414. }
  14415. cleanup_mess_from_tests( ) ;
  14416. return 0 ;
  14417. }
  14418. sub cleanup_mess_from_tests
  14419. {
  14420. undef @pipemess ;
  14421. return ;
  14422. }
  14423. sub after_get_options
  14424. {
  14425. my $mysync = shift ;
  14426. my $numopt = shift ;
  14427. # exit with --help option or no option at all
  14428. $mysync->{ debug } and myprint( "numopt:$numopt\n" ) ;
  14429. if ( $help or not $numopt ) {
  14430. myprint( usage( $mysync ) ) ;
  14431. exit ;
  14432. }
  14433. return ;
  14434. }
  14435. sub tests_remove_edging_blanks
  14436. {
  14437. note( 'Entering tests_remove_edging_blanks()' ) ;
  14438. is( undef, remove_edging_blanks( ), 'remove_edging_blanks: no args => undef' ) ;
  14439. is( 'abcd', remove_edging_blanks( 'abcd' ), 'remove_edging_blanks: abcd => abcd' ) ;
  14440. is( 'ab cd', remove_edging_blanks( ' ab cd ' ), 'remove_edging_blanks: " ab cd " => "ab cd"' ) ;
  14441. note( 'Leaving tests_remove_edging_blanks()' ) ;
  14442. return ;
  14443. }
  14444. sub remove_edging_blanks
  14445. {
  14446. my $string = shift ;
  14447. if ( ! defined $string )
  14448. {
  14449. return ;
  14450. }
  14451. $string =~ s,^ +| +$,,g ;
  14452. return $string ;
  14453. }
  14454. sub tests_sanitize
  14455. {
  14456. note( 'Entering tests_remove_edging_blanks()' ) ;
  14457. is( undef, sanitize( ), 'sanitize: no args => undef' ) ;
  14458. my $mysync = {} ;
  14459. $mysync->{ host1 } = ' example.com ' ;
  14460. $mysync->{ user1 } = ' to to ' ;
  14461. $mysync->{ password1 } = ' sex is good! ' ;
  14462. is( undef, sanitize( $mysync ), 'sanitize: => undef' ) ;
  14463. is( 'example.com', $mysync->{ host1 }, 'sanitize: host1 " example.com " => "example.com"' ) ;
  14464. is( 'to to', $mysync->{ user1 }, 'sanitize: user1 " to to " => "to to"' ) ;
  14465. is( 'sex is good!', $mysync->{ password1 }, 'sanitize: password1 " sex is good! " => "sex is good!"' ) ;
  14466. note( 'Leaving tests_remove_edging_blanks()' ) ;
  14467. return ;
  14468. }
  14469. sub sanitize
  14470. {
  14471. my $mysync = shift ;
  14472. if ( ! defined $mysync )
  14473. {
  14474. return ;
  14475. }
  14476. foreach my $parameter ( qw( host1 host2 user1 user2 password1 password2 ) )
  14477. {
  14478. $mysync->{ $parameter } = remove_edging_blanks( $mysync->{ $parameter } ) ;
  14479. }
  14480. return ;
  14481. }
  14482. sub easyany
  14483. {
  14484. my $mysync = shift ;
  14485. # Gmail
  14486. if ( $mysync->{gmail1} and $mysync->{gmail2} ) {
  14487. $mysync->{ debug } and myprint( "gmail1 gmail2\n") ;
  14488. gmail12( $mysync ) ;
  14489. return ;
  14490. }
  14491. if ( $mysync->{gmail1} ) {
  14492. $mysync->{ debug } and myprint( "gmail1\n" ) ;
  14493. gmail1( $mysync ) ;
  14494. }
  14495. if ( $mysync->{gmail2} ) {
  14496. $mysync->{ debug } and myprint( "gmail2\n" ) ;
  14497. gmail2( $mysync ) ;
  14498. }
  14499. # Office 365
  14500. if ( $mysync->{office1} ) {
  14501. office1( $mysync ) ;
  14502. }
  14503. if ( $mysync->{office2} ) {
  14504. office2( $mysync ) ;
  14505. }
  14506. # Exchange
  14507. if ( $mysync->{exchange1} ) {
  14508. exchange1( $mysync ) ;
  14509. }
  14510. if ( $mysync->{exchange2} ) {
  14511. exchange2( $mysync ) ;
  14512. }
  14513. # Domino
  14514. if ( $mysync->{domino1} ) {
  14515. domino1( $mysync ) ;
  14516. }
  14517. if ( $mysync->{domino2} ) {
  14518. domino2( $mysync ) ;
  14519. }
  14520. return ;
  14521. }
  14522. # From and for https://imapsync.lamiral.info/FAQ.d/FAQ.Gmail.txt
  14523. sub gmail12
  14524. {
  14525. my $mysync = shift ;
  14526. # Gmail at host1 and host2
  14527. $mysync->{host1} ||= 'imap.gmail.com' ;
  14528. $mysync->{ssl1} = ( defined $mysync->{ssl1} ) ? $mysync->{ssl1} : 1 ;
  14529. $mysync->{host2} ||= 'imap.gmail.com' ;
  14530. $mysync->{ssl2} = ( defined $mysync->{ssl2} ) ? $mysync->{ssl2} : 1 ;
  14531. $mysync->{maxbytespersecond} ||= 20_000 ; # should be less than 10_000 when computed from Gmail documentation
  14532. $mysync->{maxbytesafter} ||= 1_000_000_000 ; # In fact it is documented as half: 500_000_000
  14533. $mysync->{automap} = ( defined $mysync->{automap} ) ? $mysync->{automap} : 1 ;
  14534. $mysync->{maxsleep} = ( defined $mysync->{maxsleep} ) ? $mysync->{maxsleep} : $MAX_SLEEP ; ;
  14535. $skipcrossduplicates = ( defined $skipcrossduplicates ) ? $skipcrossduplicates : 0 ;
  14536. $mysync->{ synclabels } = ( defined $mysync->{ synclabels } ) ? $mysync->{ synclabels } : 1 ;
  14537. $mysync->{ resynclabels } = ( defined $mysync->{ resynclabels } ) ? $mysync->{ resynclabels } : 1 ;
  14538. push @useheader, 'X-Gmail-Received', 'Message-Id' ;
  14539. push @exclude, '\[Gmail\]$' ;
  14540. push @folderlast, '[Gmail]/All Mail' ;
  14541. return ;
  14542. }
  14543. sub gmail1
  14544. {
  14545. my $mysync = shift ;
  14546. # Gmail at host2
  14547. $mysync->{host1} ||= 'imap.gmail.com' ;
  14548. $mysync->{ssl1} = ( defined $mysync->{ssl1} ) ? $mysync->{ssl1} : 1 ;
  14549. $mysync->{maxbytespersecond} ||= 40_000 ; # should be 30_000 computed from by Gmail documentation
  14550. $mysync->{maxbytesafter} ||= 3_000_000_000 ; #
  14551. $mysync->{automap} = ( defined $mysync->{automap} ) ? $mysync->{automap} : 1 ;
  14552. $mysync->{maxsleep} = ( defined $mysync->{maxsleep} ) ? $mysync->{maxsleep} : $MAX_SLEEP ; ;
  14553. $skipcrossduplicates = ( defined $skipcrossduplicates ) ? $skipcrossduplicates : 1 ;
  14554. push @useheader, 'X-Gmail-Received', 'Message-Id' ;
  14555. push @{ $mysync->{ regextrans2 } }, 's,\[Gmail\].,,' ;
  14556. push @folderlast, '[Gmail]/All Mail' ;
  14557. return ;
  14558. }
  14559. sub gmail2
  14560. {
  14561. my $mysync = shift ;
  14562. # Gmail at host2
  14563. $mysync->{ host2 } ||= 'imap.gmail.com' ;
  14564. $mysync->{ ssl2 } = ( defined $mysync->{ ssl2 } ) ? $mysync->{ ssl2 } : 1 ;
  14565. $mysync->{ maxbytespersecond } ||= 20_000 ; # should be less than 10_000 computed from by Gmail documentation
  14566. $mysync->{ maxbytesafter } ||= 1_000_000_000 ; # In fact it is documented as half: 500_000_000
  14567. $mysync->{ automap } = ( defined $mysync->{ automap } ) ? $mysync->{ automap } : 1 ;
  14568. #$skipcrossduplicates = ( defined $skipcrossduplicates ) ? $skipcrossduplicates : 1 ;
  14569. $mysync->{ expunge1 } = ( defined $mysync->{ expunge1 } ) ? $mysync->{ expunge1 } : 1 ;
  14570. $mysync->{ addheader } = ( defined $mysync->{ addheader } ) ? $mysync->{ addheader } : 1 ;
  14571. $mysync->{ maxsleep } = ( defined $mysync->{ maxsleep } ) ? $mysync->{ maxsleep } : $MAX_SLEEP ; ;
  14572. #$mysync->{ maxsize } = ( defined $mysync->{ maxsize } ) ? $mysync->{ maxsize } : $GMAIL_MAXSIZE ;
  14573. if ( ! $mysync->{ noexclude } ) {
  14574. push @exclude, '\[Gmail\]$' ;
  14575. }
  14576. push @useheader, 'Message-Id' ;
  14577. push @{ $mysync->{ regextrans2 } }, 's,\[Gmail\].,,' ;
  14578. # push @{ $mysync->{ regextrans2 } }, 's/[ ]+/_/g' ; # is now replaced
  14579. # by the two more specific following regexes,
  14580. # they remove just the beginning and trailing blanks, not all.
  14581. push @{ $mysync->{ regextrans2 } }, 's,^ +| +$,,g' ;
  14582. push @{ $mysync->{ regextrans2 } }, 's,/ +| +/,/,g' ;
  14583. #
  14584. push @{ $mysync->{ regextrans2 } }, q{s/['\\^"]/_/g} ; # Verified this
  14585. push @folderlast, '[Gmail]/All Mail' ;
  14586. return ;
  14587. }
  14588. # From https://imapsync.lamiral.info/FAQ.d/FAQ.Exchange.txt
  14589. sub office1
  14590. {
  14591. # Office 365 at host1
  14592. my $mysync = shift ;
  14593. output( $mysync, q{Option --office1 is like: --host1 outlook.office365.com --ssl1 --exclude "^Files$"} . "\n" ) ;
  14594. output( $mysync, "Option --office1 (cont) : unless overrided with --host1 otherhost --nossl1 --noexclude\n" ) ;
  14595. $mysync->{host1} ||= 'outlook.office365.com' ;
  14596. $mysync->{ssl1} = ( defined $mysync->{ssl1} ) ? $mysync->{ssl1} : 1 ;
  14597. if ( ! $mysync->{noexclude} ) {
  14598. push @exclude, '^Files$' ;
  14599. }
  14600. return ;
  14601. }
  14602. sub office2
  14603. {
  14604. # Office 365 at host2
  14605. my $mysync = shift ;
  14606. output( $mysync, qq{Option --office2 is like: --host2 outlook.office365.com --ssl2 --maxsize 45_000_000 --maxmessagespersecond 4\n} ) ;
  14607. output( $mysync, qq{Option --office2 (cont) : --disarmreadreceipts --regexmess "wrap 10500" --f1f2 "Files=Files_renamed_by_imapsync"\n} ) ;
  14608. output( $mysync, qq{Option --office2 (cont) : unless overrided with --host2 otherhost --nossl2 ... --nodisarmreadreceipts --noregexmess\n} ) ;
  14609. output( $mysync, qq{Option --office2 (cont) : and --nof1f2 to avoid Files folder renamed to Files_renamed_by_imapsync\n} ) ;
  14610. $mysync->{host2} ||= 'outlook.office365.com' ;
  14611. $mysync->{ssl2} = ( defined $mysync->{ssl2} ) ? $mysync->{ssl2} : 1 ;
  14612. $mysync->{ maxsize } ||= 45_000_000 ;
  14613. $mysync->{maxmessagespersecond} ||= 4 ;
  14614. #push @{ $mysync->{ regexflag } }, 's/\\\\Flagged//g' ; # No problem without! tested 2018_09_10
  14615. $disarmreadreceipts = ( defined $disarmreadreceipts ) ? $disarmreadreceipts : 1 ;
  14616. # I dislike double negation but here is one
  14617. if ( ! $mysync->{noregexmess} )
  14618. {
  14619. push @regexmess, 's,(.{10239}),$1\r\n,g' ;
  14620. }
  14621. # and another...
  14622. if ( ! $mysync->{nof1f2} )
  14623. {
  14624. push @{ $mysync->{f1f2} }, 'Files=Files_renamed_by_imapsync' ;
  14625. }
  14626. return ;
  14627. }
  14628. sub exchange1
  14629. {
  14630. # Exchange 2010/2013 at host1
  14631. my $mysync = shift ;
  14632. output( $mysync, "Option --exchange1 does nothing (except printing this line...)\n" ) ;
  14633. # Well nothing to do so far
  14634. return ;
  14635. }
  14636. sub exchange2
  14637. {
  14638. # Exchange 2010/2013 at host2
  14639. my $mysync = shift ;
  14640. output( $mysync, "Option --exchange2 is like: --maxsize 10_000_000 --maxmessagespersecond 4 --disarmreadreceipts\n" ) ;
  14641. output( $mysync, "Option --exchange2 (cont) : --regexflag del Flagged --regexmess wrap 10500\n" ) ;
  14642. output( $mysync, "Option --exchange2 (cont) : unless overrided with --maxsize xxx --nodisarmreadreceipts --noregexflag --noregexmess\n" ) ;
  14643. $mysync->{ maxsize } ||= 10_000_000 ;
  14644. $mysync->{maxmessagespersecond} ||= 4 ;
  14645. $disarmreadreceipts = ( defined $disarmreadreceipts ) ? $disarmreadreceipts : 1 ;
  14646. # I dislike double negation but here are two
  14647. if ( ! $mysync->{noregexflag} ) {
  14648. push @{ $mysync->{ regexflag } }, 's/\\\\Flagged//g' ;
  14649. }
  14650. if ( ! $mysync->{noregexmess} ) {
  14651. push @regexmess, 's,(.{10239}),$1\r\n,g' ;
  14652. }
  14653. return ;
  14654. }
  14655. sub domino1
  14656. {
  14657. # Domino at host1
  14658. my $mysync = shift ;
  14659. $mysync->{ sep1 } = q{\\} ;
  14660. $prefix1 = q{} ;
  14661. $messageidnodomain = ( defined $messageidnodomain ) ? $messageidnodomain : 1 ;
  14662. return ;
  14663. }
  14664. sub domino2
  14665. {
  14666. # Domino at host1
  14667. my $mysync = shift ;
  14668. $mysync->{ sep2 } = q{\\} ;
  14669. $prefix2 = q{} ;
  14670. $messageidnodomain = ( defined $messageidnodomain ) ? $messageidnodomain : 1 ;
  14671. push @{ $mysync->{ regextrans2 } }, 's,^Inbox\\\\(.*),$1,i' ;
  14672. return ;
  14673. }
  14674. sub tests_resolv
  14675. {
  14676. note( 'Entering tests_resolv()' ) ;
  14677. # is( , resolv( ), 'resolv: => ' ) ;
  14678. is( undef, resolv( ), 'resolv: no args => undef' ) ;
  14679. is( undef, resolv( q{} ), 'resolv: empty string => undef' ) ;
  14680. is( undef, resolv( 'hostnotexist' ), 'resolv: hostnotexist => undef' ) ;
  14681. is( '127.0.0.1', resolv( '127.0.0.1' ), 'resolv: 127.0.0.1 => 127.0.0.1' ) ;
  14682. is( '127.0.0.1', resolv( 'localhost' ), 'resolv: localhost => 127.0.0.1' ) ;
  14683. is( '2001:41d0:2:84e0::1', resolv( 'imapsync.lamiral.info' ), 'resolv: imapsync.lamiral.info => 2001:41d0:2:84e0::1' ) ;
  14684. # ip6-localhost ( in /etc/hosts )
  14685. is( '::1', resolv( 'ip6-localhost' ), 'resolv: ip6-localhost => ::1' ) ;
  14686. is( '::1', resolv( '::1' ), 'resolv: ::1 => ::1' ) ;
  14687. # ks2ipv6 now has CNAME ks6ipv6
  14688. is( '2001:41d0:8:d8b6::1', resolv( '2001:41d0:8:d8b6::1' ), 'resolv: 2001:41d0:8:d8b6::1 => 2001:41d0:8:d8b6::1' ) ;
  14689. is( '2001:41d0:8:9951::1', resolv( 'ks6ipv6.lamiral.info' ), 'resolv: ks6ipv6.lamiral.info => 2001:41d0:8:9951::1' ) ;
  14690. # ks6
  14691. is( '2001:41d0:8:9951::1', resolv( '2001:41d0:8:9951::1' ), 'resolv: 2001:41d0:8:9951::1 => 2001:41d0:8:9951::1' ) ;
  14692. is( '2001:41d0:8:9951::1', resolv( 'ks6ipv6.lamiral.info' ), 'resolv: ks6ipv6.lamiral.info => 2001:41d0:8:9951::1' ) ;
  14693. # ks3
  14694. is( '2001:41d0:8:bebd::1', resolv( '2001:41d0:8:bebd::1' ), 'resolv: 2001:41d0:8:bebd::1 => 2001:41d0:8:bebd::1' ) ;
  14695. is( '2001:41d0:8:bebd::1', resolv( 'ks3ipv6.lamiral.info' ), 'resolv: ks3ipv6.lamiral.info => 2001:41d0:8:bebd::1' ) ;
  14696. note( 'Leaving tests_resolv()' ) ;
  14697. return ;
  14698. }
  14699. sub resolv
  14700. {
  14701. my $host = shift @ARG ;
  14702. if ( ! $host ) { return ; }
  14703. my $addr ;
  14704. if ( defined &Socket::getaddrinfo ) {
  14705. $addr = resolv_with_getaddrinfo( $host ) ;
  14706. return( $addr ) ;
  14707. }
  14708. my $iaddr = inet_aton( $host ) ;
  14709. if ( ! $iaddr ) { return ; }
  14710. $addr = inet_ntoa( $iaddr ) ;
  14711. return $addr ;
  14712. }
  14713. sub resolv_with_getaddrinfo
  14714. {
  14715. my $host = shift @ARG ;
  14716. $sync->{ debug } and myprint( "Entering resolv_with_getaddrinfo( $host )\n" ) ;
  14717. if ( ! $host ) { return ; }
  14718. my ( $err_getaddrinfo, @res ) = Socket::getaddrinfo( $host, "", { socktype => Socket::SOCK_RAW } ) ;
  14719. if ( $err_getaddrinfo ) {
  14720. myprint( "Cannot getaddrinfo of $host: $err_getaddrinfo\n" ) ;
  14721. return ;
  14722. }
  14723. my @addr ;
  14724. while( my $ai = shift @res ) {
  14725. my ( $err_getnameinfo, $ipaddr ) = Socket::getnameinfo( $ai->{addr}, Socket::NI_NUMERICHOST(), Socket::NIx_NOSERV() ) ;
  14726. if ( $err_getnameinfo ) {
  14727. myprint( "Cannot getnameinfo of $host: $err_getnameinfo\n" ) ;
  14728. return ;
  14729. }else{
  14730. $sync->{ debug } and myprint( "$host => $ipaddr\n" ) ;
  14731. push @addr, $ipaddr ;
  14732. my $reverse ;
  14733. ( $err_getnameinfo, $reverse ) = Socket::getnameinfo( $ai->{addr}, 0, Socket::NIx_NOSERV() ) ;
  14734. $sync->{ debug } and myprint( "$host => $ipaddr => $reverse\n" ) ;
  14735. }
  14736. $sync->{ debug } and myprint( "$host => $ipaddr\n" ) ;
  14737. }
  14738. $sync->{ debug } and myprint( "Leaving resolv_with_getaddrinfo( $host => $addr[0])\n" ) ;
  14739. return $addr[0] ;
  14740. }
  14741. sub tests_resolvrev
  14742. {
  14743. note( 'Entering tests_resolvrev()' ) ;
  14744. # is( , resolvrev( ), 'resolvrev: => ' ) ;
  14745. is( undef, resolvrev( ), 'resolvrev: no args => undef' ) ;
  14746. is( undef, resolvrev( q{} ), 'resolvrev: empty string => undef' ) ;
  14747. is( undef, resolvrev( 'hostnotexist' ), 'resolvrev: hostnotexist => undef' ) ;
  14748. is( 'localhost', resolvrev( '127.0.0.1' ), 'resolvrev: 127.0.0.1 => localhost' ) ;
  14749. is( 'localhost', resolvrev( 'localhost' ), 'resolvrev: localhost => localhost' ) ;
  14750. is( 'ks.lamiral.info', resolvrev( 'imapsync.lamiral.info' ), 'resolvrev: imapsync.lamiral.info => ks.lamiral.info' ) ;
  14751. # ip6-localhost ( in /etc/hosts )
  14752. is( 'ip6-localhost', resolvrev( 'ip6-localhost' ), 'resolvrev: ip6-localhost => ip6-localhost' ) ;
  14753. is( 'ip6-localhost', resolvrev( '::1' ), 'resolvrev: ::1 => ip6-localhost' ) ;
  14754. # ks2
  14755. is( 'ks6ipv6.lamiral.info', resolvrev( '2001:41d0:8:d8b6::1' ), 'resolvrev: 2001:41d0:8:d8b6::1 => ks6ipv6.lamiral.info' ) ;
  14756. is( 'ks6ipv6.lamiral.info', resolvrev( 'ks6ipv6.lamiral.info' ), 'resolvrev: ks6ipv6.lamiral.info => ks6ipv6.lamiral.info' ) ;
  14757. # ks3
  14758. is( 'ks3ipv6.lamiral.info', resolvrev( '2001:41d0:8:bebd::1' ), 'resolvrev: 2001:41d0:8:bebd::1 => ks3ipv6.lamiral.info' ) ;
  14759. is( 'ks3ipv6.lamiral.info', resolvrev( 'ks3ipv6.lamiral.info' ), 'resolvrev: ks3ipv6.lamiral.info => ks3ipv6.lamiral.info' ) ;
  14760. note( 'Leaving tests_resolvrev()' ) ;
  14761. return ;
  14762. }
  14763. sub resolvrev
  14764. {
  14765. my $host = shift @ARG ;
  14766. if ( ! $host ) { return ; }
  14767. if ( defined &Socket::getaddrinfo ) {
  14768. my $name = resolvrev_with_getaddrinfo( $host ) ;
  14769. return( $name ) ;
  14770. }
  14771. return ;
  14772. }
  14773. sub resolvrev_with_getaddrinfo
  14774. {
  14775. my $host = shift @ARG ;
  14776. if ( ! $host ) { return ; }
  14777. my ( $err, @res ) = Socket::getaddrinfo( $host, "", { socktype => Socket::SOCK_RAW } ) ;
  14778. if ( $err ) {
  14779. myprint( "Cannot getaddrinfo of $host: $err\n" ) ;
  14780. return ;
  14781. }
  14782. my @name ;
  14783. while( my $ai = shift @res ) {
  14784. my ( $err, $reverse ) = Socket::getnameinfo( $ai->{addr}, 0, Socket::NIx_NOSERV() ) ;
  14785. if ( $err ) {
  14786. myprint( "Cannot getnameinfo of $host: $err\n" ) ;
  14787. return ;
  14788. }
  14789. $sync->{ debug } and myprint( "$host => $reverse\n" ) ;
  14790. push @name, $reverse ;
  14791. }
  14792. return $name[0] ;
  14793. }
  14794. sub tests_imapsping
  14795. {
  14796. note( 'Entering tests_imapsping()' ) ;
  14797. is( undef, imapsping( ), 'imapsping: no args => undef' ) ;
  14798. is( undef, imapsping( 'hostnotexist' ), 'imapsping: hostnotexist => undef' ) ;
  14799. is( 1, imapsping( 'imapsync.lamiral.info' ), 'imapsping: imapsync.lamiral.info => 1' ) ;
  14800. is( 1, imapsping( 'ks6ipv6.lamiral.info' ), 'imapsping: ks6ipv6.lamiral.info => 1' ) ;
  14801. note( 'Leaving tests_imapsping()' ) ;
  14802. return ;
  14803. }
  14804. sub imapsping
  14805. {
  14806. my $host = shift ;
  14807. return tcpping( $host, $IMAP_SSL_PORT ) ;
  14808. }
  14809. sub tests_tcpping
  14810. {
  14811. note( 'Entering tests_tcpping()' ) ;
  14812. is( undef, tcpping( ), 'tcpping: no args => undef' ) ;
  14813. is( undef, tcpping( 'hostnotexist' ), 'tcpping: one arg => undef' ) ;
  14814. is( undef, tcpping( undef, 888 ), 'tcpping: arg undef, port => undef' ) ;
  14815. is( undef, tcpping( 'hostnotexist', 993 ), 'tcpping: hostnotexist 993 => undef' ) ;
  14816. is( undef, tcpping( 'hostnotexist', 888 ), 'tcpping: hostnotexist 888 => undef' ) ;
  14817. is( 1, tcpping( 'imapsync.lamiral.info', 993 ), 'tcpping: imapsync.lamiral.info 993 => 1' ) ;
  14818. is( 0, tcpping( 'imapsync.lamiral.info', 888 ), 'tcpping: imapsync.lamiral.info 888 => 0' ) ;
  14819. is( 1, tcpping( '5.135.158.182', 993 ), 'tcpping: 5.135.158.182 993 => 1' ) ;
  14820. is( 0, tcpping( '5.135.158.182', 888 ), 'tcpping: 5.135.158.182 888 => 0' ) ;
  14821. # Net::Ping supports ipv6 only after release 1.50
  14822. # http://cpansearch.perl.org/src/RURBAN/Net-Ping-2.59/Changes
  14823. # Anyway I plan to avoid Net-Ping for that too long standing feature
  14824. # Net-Ping is integrated in Perl itself, who knows ipv6 for a long time
  14825. is( 1, tcpping( '2001:41d0:8:d8b6::1', 993 ), 'tcpping: 2001:41d0:8:d8b6::1 993 => 1' ) ;
  14826. is( 0, tcpping( '2001:41d0:8:d8b6::1', 888 ), 'tcpping: 2001:41d0:8:d8b6::1 888 => 0' ) ;
  14827. note( 'Leaving tests_tcpping()' ) ;
  14828. return ;
  14829. }
  14830. sub tcpping
  14831. {
  14832. if ( 2 != scalar( @ARG ) ) {
  14833. return ;
  14834. }
  14835. my ( $host, $port ) = @ARG ;
  14836. if ( ! $host ) { return ; }
  14837. if ( ! $port ) { return ; }
  14838. my $mytimeout = $TCP_PING_TIMEOUT ;
  14839. require Net::Ping ;
  14840. #my $p = Net::Ping->new( 'tcp' ) ;
  14841. my $p = Net::Ping->new( ) ;
  14842. $p->{port_num} = $port ;
  14843. $p->service_check( 1 ) ;
  14844. $p->hires( 1 ) ;
  14845. my ($ping_ok, $rtt, $ip ) = $p->ping( $host, $mytimeout ) ;
  14846. if ( ! defined $ping_ok ) { return ; }
  14847. my $rtt_approx = sprintf( "%.3f", $rtt ) ;
  14848. $sync->{ debug } and myprint( "Host $host timeout $mytimeout port $port ok $ping_ok ip $ip acked in $rtt_approx s\n" ) ;
  14849. $p->close( ) ;
  14850. if( $ping_ok ) {
  14851. return 1 ;
  14852. }else{
  14853. return 0 ;
  14854. }
  14855. }
  14856. sub tests_sslcheck
  14857. {
  14858. note( 'Entering tests_sslcheck()' ) ;
  14859. my $mysync ;
  14860. is( undef, sslcheck( $mysync ), 'sslcheck: no sslcheck => undef' ) ;
  14861. $mysync = {
  14862. sslcheck => 1,
  14863. } ;
  14864. is( 0, sslcheck( $mysync ), 'sslcheck: no host => 0' ) ;
  14865. $mysync = {
  14866. sslcheck => 1,
  14867. host1 => 'test1.lamiral.info',
  14868. tls1 => 1,
  14869. } ;
  14870. is( 0, sslcheck( $mysync ), 'sslcheck: tls1 => 0' ) ;
  14871. $mysync = {
  14872. sslcheck => 1,
  14873. host1 => 'test1.lamiral.info',
  14874. } ;
  14875. is( 1, sslcheck( $mysync ), 'sslcheck: test1.lamiral.info => 1' ) ;
  14876. is( 1, $mysync->{ssl1}, 'sslcheck: test1.lamiral.info => ssl1 1' ) ;
  14877. $mysync->{sslcheck} = 0 ;
  14878. is( undef, sslcheck( $mysync ), 'sslcheck: sslcheck off => undef' ) ;
  14879. $mysync = {
  14880. sslcheck => 1,
  14881. host1 => 'test1.lamiral.info',
  14882. host2 => 'test2.lamiral.info',
  14883. } ;
  14884. is( 2, sslcheck( $mysync ), 'sslcheck: test1.lamiral.info + test2.lamiral.info => 2' ) ;
  14885. $mysync = {
  14886. sslcheck => 1,
  14887. host1 => 'test1.lamiral.info',
  14888. host2 => 'test2.lamiral.info',
  14889. tls1 => 1,
  14890. } ;
  14891. is( 1, sslcheck( $mysync ), 'sslcheck: test1.lamiral.info + test2.lamiral.info + tls1 => 1' ) ;
  14892. note( 'Leaving tests_sslcheck()' ) ;
  14893. return ;
  14894. }
  14895. sub sslcheck
  14896. {
  14897. my $mysync = shift ;
  14898. if ( ! $mysync->{sslcheck} ) {
  14899. return ;
  14900. }
  14901. my $nb_on = 0 ;
  14902. $mysync->{ debug } and myprint( "sslcheck\n" ) ;
  14903. if (
  14904. ( ! defined $mysync->{port1} )
  14905. and
  14906. ( ! defined $mysync->{tls1} )
  14907. and
  14908. ( ! defined $mysync->{ssl1} )
  14909. and
  14910. ( defined $mysync->{host1} )
  14911. ) {
  14912. myprint( "Host1: probing ssl on port $IMAP_SSL_PORT ( use --nosslcheck to avoid this ssl probe ) \n" ) ;
  14913. if ( probe_imapssl( $mysync->{host1} ) ) {
  14914. $mysync->{ssl1} = 1 ;
  14915. 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" ) ;
  14916. $nb_on++ ;
  14917. }else{
  14918. myprint( "Host1: sslcheck did not detected open ssl port $IMAP_SSL_PORT. Will use standard $IMAP_PORT port.\n" ) ;
  14919. }
  14920. }
  14921. if (
  14922. ( ! defined $mysync->{port2} )
  14923. and
  14924. ( ! defined $mysync->{tls2} )
  14925. and
  14926. ( ! defined $mysync->{ssl2} )
  14927. and
  14928. ( defined $mysync->{host2} )
  14929. ) {
  14930. myprint( "Host2: probing ssl on port $IMAP_SSL_PORT ( use --nosslcheck to avoid this ssl probe ) \n" ) ;
  14931. if ( probe_imapssl( $mysync->{host2} ) ) {
  14932. $mysync->{ssl2} = 1 ;
  14933. 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" ) ;
  14934. $nb_on++ ;
  14935. }else{
  14936. myprint( "Host2: sslcheck did not detected open ssl port $IMAP_SSL_PORT. Will use standard $IMAP_PORT port.\n" ) ;
  14937. }
  14938. }
  14939. return $nb_on ;
  14940. }
  14941. sub testslive_init
  14942. {
  14943. my $mysync = shift ;
  14944. $mysync->{host1} ||= 'test1.lamiral.info' ;
  14945. $mysync->{user1} ||= 'test1' ;
  14946. $mysync->{password1} ||= 'secret1' ;
  14947. $mysync->{host2} ||= 'test2.lamiral.info' ;
  14948. $mysync->{user2} ||= 'test2' ;
  14949. $mysync->{password2} ||= 'secret2' ;
  14950. return ;
  14951. }
  14952. sub testslive6_init
  14953. {
  14954. my $mysync = shift ;
  14955. $mysync->{host1} ||= 'ks6ipv6.lamiral.info' ;
  14956. $mysync->{user1} ||= 'test1' ;
  14957. $mysync->{password1} ||= 'secret1' ;
  14958. $mysync->{host2} ||= 'ks6ipv6.lamiral.info' ;
  14959. $mysync->{user2} ||= 'test2' ;
  14960. $mysync->{password2} ||= 'secret2' ;
  14961. return ;
  14962. }
  14963. sub tests_backslash_caret
  14964. {
  14965. note( 'Entering tests_backslash_caret()' ) ;
  14966. is( "lalala", backslash_caret( "lalala" ), 'backslash_caret: lalala => lalala' ) ;
  14967. is( "lalala\n", backslash_caret( "lalala\n" ), 'backslash_caret: lalala => lalala 2nd' ) ;
  14968. is( '^', backslash_caret( '\\' ), 'backslash_caret: \\ => ^' ) ;
  14969. is( "^\n", backslash_caret( "\\\n" ), 'backslash_caret: \\ => ^' ) ;
  14970. is( "\\lalala", backslash_caret( "\\lalala" ), 'backslash_caret: \\lalala => \\lalala' ) ;
  14971. is( "\\lal\\ala", backslash_caret( "\\lal\\ala" ), 'backslash_caret: \\lal\\ala => \\lal\\ala' ) ;
  14972. is( "\\lalala\n", backslash_caret( "\\lalala\n" ), 'backslash_caret: \\lalala => \\lalala 2nd' ) ;
  14973. is( "lalala^\n", backslash_caret( "lalala\\\n" ), 'backslash_caret: lalala\\\n => lalala^\n' ) ;
  14974. is( "lalala^\nlalala^\n", backslash_caret( "lalala\\\nlalala\\\n" ), 'backslash_caret: lalala\\\nlalala\\\n => lalala^\nlalala^\n' ) ;
  14975. is( "lal\\ala^\nlalala^\n", backslash_caret( "lal\\ala\\\nlalala\\\n" ), 'backslash_caret: lal\\ala\\\nlalala\\\n => lal\\ala^\nlalala^\n' ) ;
  14976. note( 'Leaving tests_backslash_caret()' ) ;
  14977. return ;
  14978. }
  14979. sub backslash_caret
  14980. {
  14981. my $string = shift ;
  14982. $string =~ s{\\ $ }{^}gxms ;
  14983. return $string ;
  14984. }
  14985. sub tests_split_around_equal
  14986. {
  14987. note( 'Entering tests_split_around_equal()' ) ;
  14988. is( undef, split_around_equal( ), 'split_around_equal: no args => undef' ) ;
  14989. is_deeply( { toto => 'titi' }, { split_around_equal( 'toto=titi' ) }, 'split_around_equal: toto=titi => toto => titi' ) ;
  14990. is_deeply( { A => 'B', C => 'D' }, { split_around_equal( 'A=B=C=D' ) }, 'split_around_equal: toto=titi => toto => titi' ) ;
  14991. is_deeply( { A => 'B', C => 'D' }, { split_around_equal( 'A=B', 'C=D' ) }, 'split_around_equal: A=B C=D => A => B, C=>D' ) ;
  14992. note( 'Leaving tests_split_around_equal()' ) ;
  14993. return ;
  14994. }
  14995. sub split_around_equal
  14996. {
  14997. if ( ! @ARG ) { return ; } ;
  14998. return map { split /=/mxs, $_ } @ARG ;
  14999. }
  15000. sub tests_sig_install
  15001. {
  15002. note( 'Entering tests_sig_install()' ) ;
  15003. my $mysync ;
  15004. is( undef, sig_install( ), 'sig_install: no args => undef' ) ;
  15005. is( undef, sig_install( $mysync ), 'sig_install: arg undef => undef' ) ;
  15006. $mysync = { } ;
  15007. is( undef, sig_install( $mysync ), 'sig_install: empty hash => undef' ) ;
  15008. SKIP: {
  15009. Readonly my $SKIP_15 => 15 ;
  15010. if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests only for Unix', $SKIP_15 ) ; }
  15011. # Default to ignore USR1 USR2 in case future install fails
  15012. local $SIG{ USR1 } = local $SIG{ USR2 } = sub { } ;
  15013. kill( 'USR1', $PROCESS_ID ) ;
  15014. $mysync->{ debugsig } = 1 ;
  15015. # Assign USR1 to call sub tototo
  15016. # Surely a better value than undef should be returned when doing real signal stuff
  15017. is( undef, sig_install( $mysync, 'tototo', 'USR1' ), 'sig_install: USR1 tototo' ) ;
  15018. is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 1' ) ;
  15019. is( 1, $mysync->{ tototo_calls }, 'sig_install: tototo call nb 1' ) ;
  15020. #return ;
  15021. # Assign USR2 to call sub tototo
  15022. is( undef, sig_install( $mysync, 'tototo', 'USR2' ), 'sig_install: USR2 tototo' ) ;
  15023. is( 1, kill( 'USR2', $PROCESS_ID ), 'sig_install: kill USR2 myself 1' ) ;
  15024. is( 2, $mysync->{ tototo_calls }, 'sig_install: tototo call nb 2' ) ;
  15025. is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 2' ) ;
  15026. is( 3, $mysync->{ tototo_calls }, 'sig_install: tototo call nb 3' ) ;
  15027. local $SIG{ USR1 } = local $SIG{ USR2 } = sub { } ;
  15028. is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 3' ) ;
  15029. is( 3, $mysync->{ tototo_calls }, 'sig_install: tototo call still nb 3' ) ;
  15030. # Assign USR1 + USR2 to call sub tototo
  15031. is( undef, sig_install( $mysync, 'tototo', 'USR1', 'USR2' ), 'sig_install: USR1 USR2 tototo' ) ;
  15032. is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 4' ) ;
  15033. is( 4, $mysync->{ tototo_calls }, 'sig_install: tototo call now nb 4' ) ;
  15034. is( 1, kill( 'USR2', $PROCESS_ID ), 'sig_install: kill USR1 myself 2' ) ;
  15035. is( 5, $mysync->{ tototo_calls }, 'sig_install: tototo call now nb 5' ) ;
  15036. }
  15037. note( 'Leaving tests_sig_install()' ) ;
  15038. return ;
  15039. }
  15040. #
  15041. sub sig_install
  15042. {
  15043. my $mysync = shift ;
  15044. if ( ! $mysync ) { return ; }
  15045. my $mysubname = shift ;
  15046. if ( ! $mysubname ) { return ; }
  15047. if ( ! @ARG ) { return ; }
  15048. my @signals = @ARG ;
  15049. my $mysub = \&$mysubname ;
  15050. #$mysync->{ debugsig } = 1 ;
  15051. $mysync->{ debugsig } and myprint( "In sig_install with sub $mysubname and signal @ARG\n" ) ;
  15052. my $subsignal = sub {
  15053. my $signame = shift ;
  15054. $mysync->{ debugsig } and myprint( "In subsignal with $signame and $mysubname\n" ) ;
  15055. &$mysub( $mysync, $signame ) ;
  15056. } ;
  15057. foreach my $signal ( @signals ) {
  15058. $mysync->{ debugsig } and myprint( "Installing signal $signal to call sub $mysubname\n") ;
  15059. output( $mysync, "kill -$signal $PROCESS_ID # special behavior: call to sub $mysubname\n" ) ;
  15060. ## no critic (RequireLocalizedPunctuationVars)
  15061. $SIG{ $signal } = $subsignal ;
  15062. }
  15063. return ;
  15064. }
  15065. sub tototo
  15066. {
  15067. my $mysync = shift ;
  15068. myprint("In tototo with @ARG\n" ) ;
  15069. $mysync->{ tototo_calls } += 1 ;
  15070. return ;
  15071. }
  15072. sub mygetppid
  15073. {
  15074. if ( 'MSWin32' eq $OSNAME ) {
  15075. return( 'unknown under MSWin32 (too complicated)' ) ;
  15076. } else {
  15077. # Unix
  15078. return( getppid( ) ) ;
  15079. }
  15080. }
  15081. sub tests_toggle_sleep
  15082. {
  15083. note( 'Entering tests_toggle_sleep()' ) ;
  15084. is( undef, toggle_sleep( ), 'toggle_sleep: no args => undef' ) ;
  15085. my $mysync ;
  15086. is( undef, toggle_sleep( $mysync ), 'toggle_sleep: undef => undef' ) ;
  15087. $mysync = { } ;
  15088. is( undef, toggle_sleep( $mysync ), 'toggle_sleep: no maxsleep => undef' ) ;
  15089. $mysync->{maxsleep} = 3 ;
  15090. is( 0, toggle_sleep( $mysync ), 'toggle_sleep: 3 => 0' ) ;
  15091. is( $MAX_SLEEP, toggle_sleep( $mysync ), "toggle_sleep: 0 => $MAX_SLEEP" ) ;
  15092. is( 0, toggle_sleep( $mysync ), "toggle_sleep: $MAX_SLEEP => 0" ) ;
  15093. is( $MAX_SLEEP, toggle_sleep( $mysync ), "toggle_sleep: 0 => $MAX_SLEEP" ) ;
  15094. is( 0, toggle_sleep( $mysync ), "toggle_sleep: $MAX_SLEEP => 0" ) ;
  15095. SKIP: {
  15096. Readonly my $SKIP_9 => 9 ;
  15097. if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests only for Unix', $SKIP_9 ) ; }
  15098. # Default to ignore USR1 USR2 in case future install fails
  15099. local $SIG{ USR1 } = sub { } ;
  15100. kill( 'USR1', $PROCESS_ID ) ;
  15101. $mysync->{ debugsig } = 1 ;
  15102. # Assign USR1 to call sub toggle_sleep
  15103. is( undef, sig_install( $mysync, \&toggle_sleep, 'USR1' ), 'toggle_sleep: install USR1 toggle_sleep' ) ;
  15104. $mysync->{maxsleep} = 4 ;
  15105. is( 1, kill( 'USR1', $PROCESS_ID ), 'toggle_sleep: kill USR1 myself' ) ;
  15106. is( 0, $mysync->{ maxsleep }, 'toggle_sleep: toggle_sleep called => sleeps are 0s' ) ;
  15107. is( 1, kill( 'USR1', $PROCESS_ID ), 'toggle_sleep: kill USR1 myself again' ) ;
  15108. is( $MAX_SLEEP, $mysync->{ maxsleep }, "toggle_sleep: toggle_sleep called => sleeps are ${MAX_SLEEP}s" ) ;
  15109. is( 1, kill( 'USR1', $PROCESS_ID ), 'toggle_sleep: kill USR1 myself' ) ;
  15110. is( 0, $mysync->{ maxsleep }, 'toggle_sleep: toggle_sleep called => sleeps are 0s' ) ;
  15111. is( 1, kill( 'USR1', $PROCESS_ID ), 'toggle_sleep: kill USR1 myself again' ) ;
  15112. is( $MAX_SLEEP, $mysync->{ maxsleep }, "toggle_sleep: toggle_sleep called => sleeps are ${MAX_SLEEP}s" ) ;
  15113. }
  15114. note( 'Leaving tests_toggle_sleep()' ) ;
  15115. return ;
  15116. }
  15117. sub toggle_sleep
  15118. {
  15119. my $mysync = shift ;
  15120. myprint("In toggle_sleep with @ARG\n" ) ;
  15121. if ( !defined( $mysync ) ) { return ; }
  15122. if ( !defined( $mysync->{maxsleep} ) ) { return ; }
  15123. $mysync->{ maxsleep } = max( 0, $MAX_SLEEP - $mysync->{maxsleep} ) ;
  15124. myprint("Resetting maxsleep to ", $mysync->{maxsleep}, "s\n" ) ;
  15125. return $mysync->{maxsleep} ;
  15126. }
  15127. sub mypod2usage
  15128. {
  15129. my $fh_pod2usage = shift ;
  15130. pod2usage(
  15131. -exitval => 'NOEXIT',
  15132. -noperldoc => 1,
  15133. -verbose => 99,
  15134. -sections => [ qw(NAME VERSION USAGE OPTIONS) ],
  15135. -indent => 1,
  15136. -loose => 1,
  15137. -output => $fh_pod2usage,
  15138. ) ;
  15139. return ;
  15140. }
  15141. sub usage
  15142. {
  15143. my $mysync = shift ;
  15144. if ( ! defined $mysync ) { return ; }
  15145. my $usage = q{} ;
  15146. my $usage_from_pod ;
  15147. my $usage_footer = usage_footer( $mysync ) ;
  15148. # pod2usage writes on a filehandle only and I want a variable
  15149. open my $fh_pod2usage, ">", \$usage_from_pod
  15150. or do { warn $OS_ERROR ; return ; } ;
  15151. mypod2usage( $fh_pod2usage ) ;
  15152. close $fh_pod2usage ;
  15153. if ( 'MSWin32' eq $OSNAME ) {
  15154. $usage_from_pod = backslash_caret( $usage_from_pod ) ;
  15155. }
  15156. $usage = join( q{}, $usage_from_pod, $usage_footer ) ;
  15157. return( $usage ) ;
  15158. }
  15159. sub tests_usage
  15160. {
  15161. note( 'Entering tests_usage()' ) ;
  15162. my $usage ;
  15163. like( $usage = usage( $sync ), qr/Name:/, 'usage: contains Name:' ) ;
  15164. myprint( $usage ) ;
  15165. like( $usage, qr/Version:/, 'usage: contains Version:' ) ;
  15166. like( $usage, qr/Usage:/, 'usage: contains Usage:' ) ;
  15167. like( $usage, qr/imapsync/, 'usage: contains imapsync' ) ;
  15168. is( undef, usage( ), 'usage: no args => undef' ) ;
  15169. note( 'Leaving tests_usage()' ) ;
  15170. return ;
  15171. }
  15172. sub usage_footer
  15173. {
  15174. my $mysync = shift ;
  15175. my $footer = q{} ;
  15176. my $localhost_info = localhost_info( $mysync ) ;
  15177. my $rcs = $mysync->{rcs} ;
  15178. my $homepage = homepage( ) ;
  15179. my $imapsync_release = $STR_use_releasecheck ;
  15180. if ( $mysync->{releasecheck} ) {
  15181. $imapsync_release = check_last_release( ) ;
  15182. }
  15183. $footer = qq{$localhost_info
  15184. $rcs
  15185. $imapsync_release
  15186. $homepage
  15187. } ;
  15188. return( $footer ) ;
  15189. }
  15190. sub usage_complete
  15191. {
  15192. # Unused, I guess this function could be deleted
  15193. my $usage = <<'EOF' ;
  15194. --skipheader reg : Don't take into account header keyword
  15195. matching reg ex: --skipheader 'X.*'
  15196. --skipsize : Don't take message size into account to compare
  15197. messages on both sides. On by default.
  15198. Use --no-skipsize for using size comparaison.
  15199. --allowsizemismatch : allow RFC822.SIZE != fetched msg size
  15200. consider also --skipsize to avoid duplicate messages
  15201. when running syncs more than one time per mailbox
  15202. --reconnectretry1 int : reconnect to host1 if connection is lost up to
  15203. int times per imap command (default is 3)
  15204. --reconnectretry2 int : same as --reconnectretry1 but for host2
  15205. --split1 int : split the requests in several parts on host1.
  15206. int is the number of messages handled per request.
  15207. default is like --split1 100.
  15208. --split2 int : same thing on host2.
  15209. --nofixInboxINBOX : Don't fix Inbox INBOX mapping.
  15210. EOF
  15211. return( $usage ) ;
  15212. }
  15213. sub setvalfromcgikey
  15214. {
  15215. my ( $mysync, $mycgi, $key, $val ) = @ARG ;
  15216. my $badthings = 0 ;
  15217. my ( $name, $type, $struct ) ;
  15218. if ( $key !~ m/^([\w\d\|]+)([=:][isf])?([\+!\@\%])?$/mxs )
  15219. {
  15220. $badthings++ ;
  15221. next ; # Unknown item
  15222. }
  15223. else
  15224. {
  15225. $name = [ split '|', $1, 1 ]->[0] ; # option name ab|cd|ef => keep only ab
  15226. $type = $2 ; # = or : followed by i or s or f
  15227. $struct = $3 ; # + or ! or @ or %
  15228. }
  15229. if ( ( $struct || q{} ) eq '+' )
  15230. {
  15231. ${$val} = $mycgi->param( $name ) ; # "Incremental" integer
  15232. }
  15233. elsif ( $type )
  15234. {
  15235. my @values = $mycgi->multi_param( $name ) ;
  15236. #myprint( "type[$type]values[@values]\$struct[", $struct || q{}, "]val[$val]ref(val)[", ref($val), "]\n" ) ;
  15237. if ( ( $struct || q{} ) eq '%' or ref( $val ) eq 'HASH' )
  15238. {
  15239. setvalfromhash( $val, $type, @values ) ;
  15240. }
  15241. else
  15242. {
  15243. setvalfromlist( $mysync, $val, $name, $type, $struct, @values ) ;
  15244. }
  15245. }
  15246. else
  15247. {
  15248. setvalfromcheckbox( $mysync, $mycgi, $key, $name, $val ) ;
  15249. }
  15250. return $badthings ;
  15251. }
  15252. sub setvalfromlist
  15253. {
  15254. my ( $mysync, $val, $name, $type, $struct, @values ) = @ARG ;
  15255. if ( $type =~ m/i$/mxs )
  15256. {
  15257. @values = map { q{} ne $_ ? int $_ : undef } @values ;
  15258. }
  15259. elsif ( $type =~ m/f$/mxs )
  15260. {
  15261. @values = map { 0 + $_ } @values ;
  15262. }
  15263. if ( ( $struct || q{} ) eq '@' )
  15264. {
  15265. @{ ${$val} } = @values ;
  15266. my @option = map { +( "--$name", "$_" ) } @values ;
  15267. push @{ $mysync->{ cmdcgi } }, @option ;
  15268. }
  15269. elsif ( ref( $val ) eq 'ARRAY' )
  15270. {
  15271. @{$val} = @values ;
  15272. }
  15273. elsif ( my $value = $values[0] )
  15274. {
  15275. ${$val} = $value ;
  15276. push @{ $mysync->{ cmdcgi } }, "--$name", $value ;
  15277. }
  15278. else
  15279. {
  15280. }
  15281. return ;
  15282. }
  15283. sub setvalfromhash
  15284. {
  15285. my ( $val, $type, @values ) = @ARG ;
  15286. my %values = map { split /=/mxs, $_ } @values ;
  15287. if ( $type =~ m/i$/mxs )
  15288. {
  15289. foreach my $k ( keys %values )
  15290. {
  15291. $values{$k} = int $values{$k} ;
  15292. }
  15293. }
  15294. elsif ( $type =~ m/f$/mxs )
  15295. {
  15296. foreach my $k ( keys %values ) {
  15297. $values{$k} = 0 + $values{$k};
  15298. }
  15299. }
  15300. if ( 'REF' eq ref $val )
  15301. {
  15302. %{ ${$val} } = %values ;
  15303. }
  15304. else
  15305. {
  15306. %{$val} = %values ;
  15307. }
  15308. return ;
  15309. }
  15310. sub setvalfromcheckbox
  15311. {
  15312. my ( $mysync, $mycgi, $key, $name, $val ) = @ARG ;
  15313. # Checkbox
  15314. # --noname is set by name=0 or name=
  15315. my $value = $mycgi->param( $name ) ;
  15316. if ( defined $value )
  15317. {
  15318. ${$val} = $value ;
  15319. if ( $value )
  15320. {
  15321. push @{ $mysync->{ cmdcgi } }, "--$name" ;
  15322. }
  15323. else
  15324. {
  15325. push @{ $mysync->{ cmdcgi } }, "--no$name" ;
  15326. }
  15327. }
  15328. else
  15329. {
  15330. ${$val} = undef ;
  15331. }
  15332. return ;
  15333. }
  15334. sub myGetOptions
  15335. {
  15336. # Started as a copy of Luke Ross Getopt::Long::CGI
  15337. # https://metacpan.org/release/Getopt-Long-CGI
  15338. # So this sub function is under the same license as Getopt-Long-CGI Luke Ross wants it,
  15339. # which was Perl 5.6 or later licenses at the date of the copy.
  15340. # It also applies for the sub functions called from this one.
  15341. my $mysync = shift @ARG ;
  15342. my $arguments_ref = shift @ARG ;
  15343. my %options = @ARG ;
  15344. my $mycgi = $mysync->{cgi} ;
  15345. if ( not under_cgi_context() ) {
  15346. # Not CGI - pass upstream for normal command line handling
  15347. return Getopt::Long::GetOptionsFromArray( $arguments_ref, %options ) ;
  15348. }
  15349. # We must be in CGI context now
  15350. if ( ! defined( $mycgi ) ) { return ; }
  15351. my $badthings = 0 ;
  15352. foreach my $key ( sort keys %options ) {
  15353. my $val = $options{$key} ;
  15354. $badthings += setvalfromcgikey( $mysync, $mycgi, $key, $val ) ;
  15355. }
  15356. if ( $badthings ) {
  15357. return ; # undef or ()
  15358. }
  15359. else {
  15360. return ( 1 ) ;
  15361. }
  15362. }
  15363. sub tests_get_options_extra
  15364. {
  15365. note( 'Entering tests_get_options_extra()' ) ;
  15366. is( undef, get_options_extra( ), 'get_options_extra: no args => undef' ) ;
  15367. my $mysync = { } ;
  15368. is( undef, get_options_extra( $mysync ), 'get_options_extra: undef => undef' ) ;
  15369. my $cwd_save = getcwd( ) ;
  15370. ok( (-d 'W/tmp/tests/options_extra/' or mkpath( 'W/tmp/tests/options_extra/' )), 'get_options_extra: mkpath W/tmp/tests/options_extra/' ) ;
  15371. chdir 'W/tmp/tests/options_extra/' ;
  15372. is( '--debugimap1', string_to_file( '--debugimap1', 'options_extra.txt' ), 'get_options_extra: string_to_file filling options_extra.txt with --debugimap1' ) ;
  15373. is( '--debugimap1', file_to_string( 'options_extra.txt' ), 'get_options_extra: reading options_extra.txt is --debugimap1' ) ;
  15374. is( '', get_options_extra( $mysync ), 'get_options_extra: --debugimap1 in options_extra.txt => nothing left, empty string return' ) ;
  15375. is( 1, $mysync->{ acc1 }->{ debugimap }, 'get_options_extra: --debugimap1 in options_extra.txt => ok, acc1->debugimap = 1' ) ;
  15376. is( '--tls1 proutcaca', string_to_file( '--tls1 proutcaca', 'options_extra.txt' ), 'get_options_extra: string_to_file filling options_extra.txt with --tls1 proutcaca' ) ;
  15377. is( 'proutcaca', get_options_extra( $mysync ), 'get_options_extra: --tls1 proutcaca in options_extra.txt => proutcaca left, proutcaca return' ) ;
  15378. chdir $cwd_save ;
  15379. note( 'Leaving tests_get_options_extra()' ) ;
  15380. return ;
  15381. }
  15382. sub get_options_extra
  15383. {
  15384. my $mysync = shift @ARG ;
  15385. if ( ! defined $mysync ) { return ; }
  15386. if ( -f -r 'options_extra.txt' )
  15387. {
  15388. my $cwd = getcwd( ) ;
  15389. my $string = firstline( 'options_extra.txt' ) ;
  15390. my $rest = get_options_from_string( $mysync, $string ) ;
  15391. output( $mysync, "Reading extra options from file options_extra.txt (cwd: $cwd) : $string\n" ) ;
  15392. return $rest ;
  15393. }
  15394. else
  15395. {
  15396. return ;
  15397. }
  15398. }
  15399. sub tests_get_options_from_string
  15400. {
  15401. note( 'Entering tests_get_options_from_string()' ) ;
  15402. is( undef, get_options_from_string( ), 'get_options_from_string: no args => undef' ) ;
  15403. my $mysync = { } ;
  15404. is( undef, get_options_from_string( $mysync ), 'get_options_from_string: undef => undef' ) ;
  15405. is( '', get_options_from_string( $mysync, '--debugimap1' ),
  15406. 'get_options_from_string: --debugimap1 => ok, nothing left, empty string return' ) ;
  15407. is( 1, $mysync->{ acc1 }->{ debugimap }, 'get_options_from_string: --debugimap1 => ok, acc1->debugimap = 1' ) ;
  15408. $mysync = { } ; # reset
  15409. is( 'caca', get_options_from_string( $mysync, '--debugimap1 caca' ),
  15410. 'get_options_from_string: --debugimap1 caca => ok, caca left, caca return' ) ;
  15411. is( 1, $mysync->{ acc1 }->{ debugimap }, 'get_options_from_string: --debugimap1 => ok, acc1->debugimap = 1' ) ;
  15412. is( 'popo roro', get_options_from_string( $mysync, '--debugimap2 popo roro' ),
  15413. 'get_options_from_string: --debugimap1 popo roro => ok, popo roro left, popo roro return' ) ;
  15414. is( 1, $mysync->{ acc2 }->{ debugimap }, 'get_options_from_string: --debugimap2 popo roro => ok, acc2->debugimap = 1' ) ;
  15415. is( 1, $mysync->{ acc1 }->{ debugimap }, 'get_options_from_string: acc1->debugimap = 1 still' ) ;
  15416. is( '', get_options_from_string( $mysync, '--nodebugimap1 --debugflags --errorsmax 2' ),
  15417. 'get_options_from_string: --nodebugimap1 --debugflags --errorsmax 2 => ok, empty string return' ) ;
  15418. is( 0, $mysync->{ acc1 }->{ debugimap }, 'get_options_from_string: acc1->debugimap = 0 now' ) ;
  15419. is( 1, $debugflags, 'get_options_from_string: debugflags = 1 now' ) ;
  15420. is( 2, $mysync->{ errorsmax }, 'get_options_from_string: mysync->errorsmax = 2 now' ) ;
  15421. is( '', get_options_from_string( $mysync, '--folder "IN BOX" --folder JOE' ),
  15422. 'get_options_from_string: --folder "IN BOX" --folder JOE => ok, empty string return' ) ;
  15423. is_deeply( [ 'IN BOX', 'JOE' ], [@{$mysync->{ folder }}], 'get_options_from_string: "IN BOX" "JOE"' ) ;
  15424. is( '', get_options_from_string( $mysync, '--debugflags --koko' ),
  15425. 'get_options_from_string: --debugflags --koko => ok, empty string return, with "Unknown option: koko" on STDERR' ) ;
  15426. note( 'Leaving tests_get_options_from_string()' ) ;
  15427. return ;
  15428. }
  15429. sub get_options_from_string
  15430. {
  15431. my $mysync = shift @ARG ;
  15432. my $mystring = shift @ARG ;
  15433. if ( ! defined $mystring ) { return ; }
  15434. my ( $ret, $args ) = Getopt::Long::GetOptionsFromString( $mystring,
  15435. 'debugimap!' => \$mysync->{ debugimap },
  15436. 'debugimap1!' => \$mysync->{ acc1 }->{ debugimap },
  15437. 'debugimap2!' => \$mysync->{ acc2 }->{ debugimap },
  15438. 'debugflags!' => \$debugflags,
  15439. 'debugsleep=f' => \$mysync->{ debugsleep },
  15440. 'errorsmax=i' => \$mysync->{ errorsmax },
  15441. 'folder=s@' => \$mysync->{ folder },
  15442. 'timeout=f' => \$mysync->{ timeout },
  15443. 'timeout1=f' => \$mysync->{ acc1 }->{ timeout },
  15444. 'timeout2=f' => \$mysync->{ acc2 }->{ timeout },
  15445. 'keepalive1!' => \$mysync->{ acc1 }->{ keepalive },
  15446. 'keepalive2!' => \$mysync->{ acc2 }->{ keepalive },
  15447. 'reconnectretry1=i' => \$mysync->{ acc1 }->{ reconnectretry },
  15448. 'reconnectretry2=i' => \$mysync->{ acc2 }->{ reconnectretry },
  15449. 'ssl1!' => \$mysync->{ ssl1 },
  15450. 'ssl2!' => \$mysync->{ ssl2 },
  15451. 'tls1!' => \$mysync->{ tls1 },
  15452. 'tls2!' => \$mysync->{ tls2 },
  15453. 'compress1!' => \$mysync->{ acc1 }->{ compress },
  15454. 'compress2!' => \$mysync->{ acc2 }->{ compress },
  15455. ) ;
  15456. my $left = join( ' ', @$args ) ;
  15457. return $left ;
  15458. }
  15459. sub tests_get_options_cgi_context
  15460. {
  15461. note( 'Entering tests_get_options_cgi_context()' ) ;
  15462. # Temporary, have to think harder about testing CGI context in command line --tests
  15463. # API:
  15464. # * input arguments: two ways, command line or CGI
  15465. # * the program arguments
  15466. # * QUERY_STRING env variable
  15467. # * return
  15468. # * QUERY_STRING length
  15469. # CGI context
  15470. local $ENV{SERVER_SOFTWARE} = 'Votre serviteur' ;
  15471. # Real full test
  15472. # = 'host1=test1.lamiral.info&user1=test1&password1=secret1&host2=test2.lamiral.info&user2=test2&password2=secret2&debugenv=on'
  15473. my $mysync ;
  15474. is( undef, get_options( $mysync ), 'get_options cgi context: no CGI module => undef' ) ;
  15475. # skip all next tests if the CGI module is not available
  15476. SKIP: {
  15477. if ( ! eval { require CGI ; } ) {
  15478. skip( "CGI Perl module is not installed", 19 ) ;
  15479. }
  15480. CGI->import( qw( -no_debug -utf8 ) ) ;
  15481. is( undef, get_options( $mysync ), 'get_options cgi context: no CGI param => undef' ) ;
  15482. # Testing boolean
  15483. $mysync->{cgi} = CGI->new( 'version=on&debugenv=on' ) ;
  15484. local $ENV{'QUERY_STRING'} = 'version=on&debugenv=on' ;
  15485. is( 22, get_options( $mysync ), 'get_options cgi context: QUERY_STRING => 22' ) ;
  15486. is( 'on', $mysync->{ version }, 'get_options cgi context: --version => on' ) ;
  15487. # debugenv is not allowed in cgi context
  15488. is( undef, $mysync->{debugenv}, 'get_options cgi context: $mysync->{debugenv} => undef' ) ;
  15489. # QUERY_STRING in this test is only for return value of get_options
  15490. # Have to think harder, GET/POST context, is this return value a good thing?
  15491. local $ENV{'QUERY_STRING'} = 'host1=test1.lamiral.info&user1=test1' ;
  15492. $mysync->{cgi} = CGI->new( 'host1=test1.lamiral.info&user1=test1' ) ;
  15493. is( 36, get_options( $mysync, ), 'get_options cgi context: QUERY_STRING => 36' ) ;
  15494. is( 'test1', $mysync->{user1}, 'get_options cgi context: $mysync->{user1} => test1' ) ;
  15495. #local $ENV{'QUERY_STRING'} = undef ;
  15496. # Testing s@ as ref
  15497. $mysync->{cgi} = CGI->new( 'folder=fd1' ) ;
  15498. get_options( $mysync ) ;
  15499. is_deeply( [ 'fd1' ], $mysync->{ folder }, 'get_options cgi context: $mysync->{ folder } => fd1' ) ;
  15500. $mysync->{cgi} = CGI->new( 'folder=fd1&folder=fd2' ) ;
  15501. get_options( $mysync ) ;
  15502. is_deeply( [ 'fd1', 'fd2' ], $mysync->{ folder }, 'get_options cgi context: $mysync->{ folder } => fd1, fd2' ) ;
  15503. # Testing %
  15504. $mysync->{cgi} = CGI->new( 'f1f2h=s1=d1&f1f2h=s2=d2&f1f2h=s3=d3' ) ;
  15505. get_options( $mysync ) ;
  15506. is_deeply( { 's1' => 'd1', 's2' => 'd2', 's3' => 'd3' },
  15507. $mysync->{f1f2h}, 'get_options cgi context: f1f2h => s1=d1 s2=d2 s3=d3' ) ;
  15508. # Testing boolean ! with --noxxx, doesnot work
  15509. $mysync->{cgi} = CGI->new( 'nodry=on' ) ;
  15510. get_options( $mysync ) ;
  15511. is( undef, $mysync->{dry}, 'get_options cgi context: --nodry => $mysync->{dry} => undef' ) ;
  15512. $mysync->{cgi} = CGI->new( 'host1=example.com' ) ;
  15513. get_options( $mysync ) ;
  15514. is( 'example.com', $mysync->{host1}, 'get_options cgi context: --host1=example.com => $mysync->{host1} => example.com' ) ;
  15515. #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
  15516. $mysync->{cgi} = CGI->new( 'simulong=' ) ;
  15517. get_options( $mysync ) ;
  15518. is( undef, $mysync->{simulong}, 'get_options cgi context: --simulong= => $mysync->{simulong} => undef' ) ;
  15519. $mysync->{cgi} = CGI->new( 'simulong' ) ;
  15520. get_options( $mysync ) ;
  15521. is( undef, $mysync->{simulong}, 'get_options cgi context: --simulong => $mysync->{simulong} => undef' ) ;
  15522. $mysync->{cgi} = CGI->new( 'simulong=4' ) ;
  15523. get_options( $mysync ) ;
  15524. is( 4, $mysync->{simulong}, 'get_options cgi context: --simulong=4 => $mysync->{simulong} => 4' ) ;
  15525. is( undef, $mysync->{ folder }, 'get_options cgi context: --simulong=4 => $mysync->{ folder } => undef' ) ;
  15526. #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
  15527. $mysync ={} ;
  15528. $mysync->{cgi} = CGI->new( 'testslive=on' ) ;
  15529. get_options( $mysync ) ;
  15530. is( 'on', $mysync->{ testslive }, 'get_options cgi context: --testslive=on => testslive => on' ) ;
  15531. #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
  15532. $mysync ={} ;
  15533. $mysync->{cgi} = CGI->new( 'log=0' ) ;
  15534. get_options( $mysync ) ;
  15535. is( 0, $mysync->{ log }, 'get_options cgi context: --log=0 => log => 0' ) ;
  15536. #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
  15537. # What is this fucked up indentation?
  15538. }
  15539. note( 'Leaving tests_get_options_cgi_context()' ) ;
  15540. return ;
  15541. }
  15542. sub get_options_cgi
  15543. {
  15544. # In CGI context arguments are not in @ARGV but in QUERY_STRING variable (with GET).
  15545. my $mysync = shift @ARG ;
  15546. $mysync->{cgi} || return ;
  15547. my @arguments = @ARG ;
  15548. # final 0 is used to print usage when no option is given
  15549. my $numopt = length $ENV{'QUERY_STRING'} || 1 ;
  15550. $mysync->{f1f2h} = {} ;
  15551. my $opt_ret = myGetOptions(
  15552. $mysync,
  15553. \@arguments,
  15554. 'abort' => \$mysync->{ abort },
  15555. 'abortbyfile' => \$mysync->{ abortbyfile },
  15556. 'host1=s' => \$mysync->{ host1 },
  15557. 'host2=s' => \$mysync->{ host2 },
  15558. 'user1=s' => \$mysync->{ user1 },
  15559. 'user2=s' => \$mysync->{ user2 },
  15560. 'password1=s' => \$mysync->{ password1 },
  15561. 'password2=s' => \$mysync->{ password2 },
  15562. 'dry!' => \$mysync->{ dry },
  15563. 'dry1!' => \$mysync->{ dry1 },
  15564. 'version' => \$mysync->{ version },
  15565. 'ssl1!' => \$mysync->{ ssl1 },
  15566. 'ssl2!' => \$mysync->{ ssl2 },
  15567. 'tls1!' => \$mysync->{ tls1 },
  15568. 'tls2!' => \$mysync->{ tls2 },
  15569. 'compress1!' => \$mysync->{ acc1 }->{ compress },
  15570. 'compress2!' => \$mysync->{ acc2 }->{ compress },
  15571. 'justbanner!' => \$mysync->{ justbanner },
  15572. 'justlogin!' => \$mysync->{ justlogin },
  15573. 'justconnect!' => \$mysync->{ justconnect },
  15574. 'addheader!' => \$mysync->{ addheader },
  15575. 'automap!' => \$mysync->{ automap },
  15576. 'justautomap!' => \$mysync->{ justautomap },
  15577. 'gmail1' => \$mysync->{ gmail1 },
  15578. 'gmail2' => \$mysync->{ gmail2 },
  15579. 'office1' => \$mysync->{ office1 },
  15580. 'office2' => \$mysync->{ office2 },
  15581. 'exchange1' => \$mysync->{ exchange1 },
  15582. 'exchange2' => \$mysync->{ exchange2 },
  15583. 'domino1' => \$mysync->{ domino1 },
  15584. 'domino2' => \$mysync->{ domino2 },
  15585. 'f1f2=s@' => \$mysync->{ f1f2 },
  15586. 'f1f2h=s%' => \$mysync->{ f1f2h },
  15587. 'folder=s@' => \$mysync->{ folder },
  15588. 'testslive!' => \$mysync->{ testslive },
  15589. 'testslive6!' => \$mysync->{ testslive6 },
  15590. 'releasecheck!' => \$mysync->{ releasecheck },
  15591. 'simulong=f' => \$mysync->{ simulong },
  15592. 'debugsleep=f' => \$mysync->{ debugsleep },
  15593. 'subfolder1=s' => \$mysync->{ subfolder1 },
  15594. 'subfolder2=s' => \$mysync->{ subfolder2 },
  15595. 'justfolders!' => \$mysync->{ justfolders },
  15596. 'justfoldersizes!' => \$mysync->{ justfoldersizes },
  15597. 'delete1!' => \$mysync->{ delete1 },
  15598. 'delete2!' => \$mysync->{ delete2 },
  15599. 'delete2duplicates!' => \$mysync->{ delete2duplicates },
  15600. 'tail!' => \$mysync->{ tail },
  15601. 'tmphash=s' => \$mysync->{ tmphash },
  15602. 'exitwhenover=i' => \$mysync->{ exitwhenover },
  15603. 'syncduplicates!' => \$mysync->{ syncduplicates },
  15604. 'log!' => \$mysync->{ log },
  15605. 'loglogfile!' => \$mysync->{ loglogfile },
  15606. # f1f2h=s% could be removed but
  15607. # tests_get_options_cgi() should be split before
  15608. # with a sub tests_myGetOptions()
  15609. ) ;
  15610. $mysync->{ debug } and output( $mysync, "get options: [$opt_ret][$numopt]\n" ) ;
  15611. if ( ! $opt_ret ) {
  15612. return ;
  15613. }
  15614. return $numopt ;
  15615. }
  15616. sub get_options_cmd
  15617. {
  15618. my $mysync = shift @ARG ;
  15619. my @arguments = @ARG ;
  15620. my $mycgi = $mysync->{cgi} ;
  15621. # final 0 is used to print usage when no option is given on command line
  15622. my $numopt = scalar @arguments || 0 ;
  15623. my $argv = join "\x00", @arguments ;
  15624. if ( $argv =~ m/-delete\x002/x ) {
  15625. output( $mysync, "May be you mean --delete2 instead of --delete 2\n" ) ;
  15626. return ;
  15627. }
  15628. $mysync->{f1f2h} = {} ;
  15629. my $opt_ret = myGetOptions(
  15630. $mysync,
  15631. \@arguments,
  15632. 'debug!' => \$mysync->{ debug },
  15633. 'debuglist!' => \$debuglist,
  15634. 'debugcontent!' => \$mysync->{ debugcontent },
  15635. 'debugsleep=f' => \$mysync->{ debugsleep },
  15636. 'debugflags!' => \$debugflags,
  15637. 'debugimap!' => \$mysync->{ debugimap },
  15638. 'debugimap1!' => \$mysync->{ acc1 }->{ debugimap },
  15639. 'debugimap2!' => \$mysync->{ acc2 }->{ debugimap },
  15640. 'debugdev!' => \$debugdev,
  15641. 'debugmemory!' => \$mysync->{debugmemory},
  15642. 'debugfolders!' => \$mysync->{debugfolders},
  15643. 'debugssl=i' => \$mysync->{debugssl},
  15644. 'debugcgi!' => \$debugcgi,
  15645. 'debugenv!' => \$mysync->{debugenv},
  15646. 'debugsig!' => \$mysync->{debugsig},
  15647. 'debuglabels!' => \$mysync->{debuglabels},
  15648. 'simulong=f' => \$mysync->{simulong},
  15649. 'abort' => \$mysync->{abort},
  15650. 'abortbyfile' => \$mysync->{abortbyfile},
  15651. 'host1=s' => \$mysync->{ host1 },
  15652. 'host2=s' => \$mysync->{ host2 },
  15653. 'port1=i' => \$mysync->{ port1 },
  15654. 'port2=i' => \$mysync->{ port2 },
  15655. 'inet4|ipv4' => \$mysync->{ inet4 },
  15656. 'inet6|ipv6' => \$mysync->{ inet6 },
  15657. 'user1=s' => \$mysync->{ user1 },
  15658. 'user2=s' => \$mysync->{ user2 },
  15659. 'gmail1' => \$mysync->{gmail1},
  15660. 'gmail2' => \$mysync->{gmail2},
  15661. 'office1' => \$mysync->{office1},
  15662. 'office2' => \$mysync->{office2},
  15663. 'exchange1' => \$mysync->{exchange1},
  15664. 'exchange2' => \$mysync->{exchange2},
  15665. 'domino1' => \$mysync->{domino1},
  15666. 'domino2' => \$mysync->{domino2},
  15667. 'domain1=s' => \$mysync->{ acc1 }->{ domain },
  15668. 'domain2=s' => \$mysync->{ acc2 }->{ domain },
  15669. 'password1=s' => \$mysync->{password1},
  15670. 'password2=s' => \$mysync->{password2},
  15671. 'passfile1=s' => \$mysync->{ passfile1 },
  15672. 'passfile2=s' => \$mysync->{ passfile2 },
  15673. 'authmd5!' => \$authmd5,
  15674. 'authmd51!' => \$authmd51,
  15675. 'authmd52!' => \$authmd52,
  15676. 'trylogin!' => \$mysync->{ trylogin },
  15677. 'oauthdirect1=s' => \$mysync->{ acc1 }->{ oauthdirect },
  15678. 'oauthdirect2=s' => \$mysync->{ acc2 }->{ oauthdirect },
  15679. 'oauthaccesstoken1=s' => \$mysync->{ acc1 }->{ oauthaccesstoken },
  15680. 'oauthaccesstoken2=s' => \$mysync->{ acc2 }->{ oauthaccesstoken },
  15681. 'sep1=s' => \$mysync->{ sep1 },
  15682. 'sep2=s' => \$mysync->{ sep2 },
  15683. 'sanitize!' => \$mysync->{ sanitize },
  15684. 'folder=s@' => \$mysync->{ folder },
  15685. 'folderrec=s' => \@folderrec,
  15686. 'include=s' => \@include,
  15687. 'exclude=s' => \@exclude,
  15688. 'noexclude' => \$mysync->{noexclude},
  15689. 'folderfirst=s' => \@folderfirst,
  15690. 'folderlast=s' => \@folderlast,
  15691. 'prefix1=s' => \$prefix1,
  15692. 'prefix2=s' => \$prefix2,
  15693. 'subfolder1=s' => \$mysync->{ subfolder1 },
  15694. 'subfolder2=s' => \$mysync->{ subfolder2 },
  15695. 'fixslash2!' => \$mysync->{ fixslash2 },
  15696. 'fixInboxINBOX!' => \$fixInboxINBOX,
  15697. 'regextrans2=s@' => \$mysync->{ regextrans2 },
  15698. 'mixfolders!' => \$mixfolders,
  15699. 'skipemptyfolders!' => \$mysync->{ skipemptyfolders },
  15700. 'regexmess=s' => \@regexmess,
  15701. 'noregexmess' => \$mysync->{noregexmess},
  15702. 'skipmess=s' => \@skipmess,
  15703. 'pipemess=s' => \@pipemess,
  15704. 'pipemesscheck!' => \$pipemesscheck,
  15705. 'disarmreadreceipts!' => \$disarmreadreceipts,
  15706. 'regexflag=s@' => \$mysync->{ regexflag },
  15707. 'noregexflag' => \$mysync->{ noregexflag },
  15708. 'filterflags!' => \$mysync->{ filterflags },
  15709. 'filterbuggyflags!' => \$mysync->{ filterbuggyflags },
  15710. 'flagscase!' => \$mysync->{ flagscase },
  15711. 'syncflagsaftercopy!' => \$syncflagsaftercopy,
  15712. 'resyncflags!' => \$mysync->{ resyncflags },
  15713. 'synclabels!' => \$mysync->{ synclabels },
  15714. 'resynclabels!' => \$mysync->{ resynclabels },
  15715. 'delete|delete1!' => \$mysync->{ delete1 },
  15716. 'delete2!' => \$mysync->{ delete2 },
  15717. 'delete2duplicates!' => \$mysync->{ delete2duplicates },
  15718. 'delete2folders!' => \$delete2folders,
  15719. 'delete2foldersonly=s' => \$delete2foldersonly,
  15720. 'delete2foldersbutnot=s' => \$delete2foldersbutnot,
  15721. 'syncinternaldates!' => \$syncinternaldates,
  15722. 'idatefromheader!' => \$idatefromheader,
  15723. 'syncacls!' => \$mysync->{ syncacls },
  15724. 'maxsize=i' => \$mysync->{ maxsize },
  15725. 'appendlimit=i' => \$mysync->{ appendlimit },
  15726. 'truncmess=i' => \$mysync->{ truncmess },
  15727. 'minsize=i' => \$minsize,
  15728. 'maxage=f' => \$maxage,
  15729. 'minage=f' => \$minage,
  15730. 'search=s' => \$search,
  15731. 'search1=s' => \$mysync->{ search1 },
  15732. 'search2=s' => \$mysync->{ search2 },
  15733. 'foldersizes!' => \$mysync->{ foldersizes },
  15734. 'foldersizesatend!' => \$mysync->{ foldersizesatend },
  15735. 'dry!' => \$mysync->{dry},
  15736. 'dry1!' => \$mysync->{dry1},
  15737. 'expunge1|expunge!' => \$mysync->{ expunge1 },
  15738. 'expunge2!' => \$mysync->{ expunge2 },
  15739. 'uidexpunge2!' => \$mysync->{ uidexpunge2 },
  15740. 'subscribed' => \$subscribed,
  15741. 'subscribe!' => \$subscribe,
  15742. 'subscribeall|subscribe_all!' => \$subscribeall,
  15743. 'justbanner!' => \$mysync->{ justbanner },
  15744. 'justfolders!'=> \$mysync->{ justfolders },
  15745. 'justfoldersizes!' => \$mysync->{ justfoldersizes },
  15746. 'version' => \$mysync->{version},
  15747. 'help' => \$help,
  15748. 'timeout=f' => \$mysync->{timeout},
  15749. 'timeout1=f' => \$mysync->{ acc1 }->{timeout},
  15750. 'timeout2=f' => \$mysync->{ acc2 }->{timeout},
  15751. 'skipheader=s' => \$mysync->{ skipheader },
  15752. 'useheader=s' => \@useheader,
  15753. 'wholeheaderifneeded!' => \$wholeheaderifneeded,
  15754. 'messageidnodomain!' => \$messageidnodomain,
  15755. 'skipsize!' => \$skipsize,
  15756. 'allowsizemismatch!' => \$allowsizemismatch,
  15757. 'fastio1!' => \$mysync->{ acc1 }->{ fastio },
  15758. 'fastio2!' => \$mysync->{ acc2 }->{ fastio },
  15759. 'sslcheck!' => \$mysync->{sslcheck},
  15760. 'ssl1!' => \$mysync->{ssl1},
  15761. 'ssl2!' => \$mysync->{ssl2},
  15762. 'ssl1_ssl_version=s' => \$mysync->{ acc1 }->{sslargs}->{SSL_version},
  15763. 'ssl2_ssl_version=s' => \$mysync->{ acc2 }->{sslargs}->{SSL_version},
  15764. 'sslargs1=s%' => \$mysync->{ acc1 }->{sslargs},
  15765. 'sslargs2=s%' => \$mysync->{ acc2 }->{sslargs},
  15766. 'tls1!' => \$mysync->{tls1},
  15767. 'tls2!' => \$mysync->{tls2},
  15768. 'uid1!' => \$uid1,
  15769. 'uid2!' => \$uid2,
  15770. 'authmech1=s' => \$mysync->{ acc1 }->{ authmech },
  15771. 'authmech2=s' => \$mysync->{ acc2 }->{ authmech },
  15772. 'authuser1=s' => \$mysync->{ acc1 }->{ authuser },
  15773. 'authuser2=s' => \$mysync->{ acc2 }->{ authuser },
  15774. 'proxyauth1' => \$mysync->{ acc1 }->{ proxyauth },
  15775. 'proxyauth2' => \$mysync->{ acc2 }->{ proxyauth },
  15776. 'compress1!' => \$mysync->{ acc1 }->{ compress },
  15777. 'compress2!' => \$mysync->{ acc2 }->{ compress },
  15778. 'keepalive1!' => \$mysync->{ acc1 }->{ keepalive },
  15779. 'keepalive2!' => \$mysync->{ acc2 }->{ keepalive },
  15780. 'split1=i' => \$split1,
  15781. 'split2=i' => \$split2,
  15782. 'buffersize=i' => \$buffersize,
  15783. 'reconnectretry1=i' => \$mysync->{ acc1 }->{ reconnectretry },
  15784. 'reconnectretry2=i' => \$mysync->{ acc2 }->{ reconnectretry },
  15785. 'tests!' => \$mysync->{ tests },
  15786. 'testsdebug|tests_debug!' => \$mysync->{ testsdebug },
  15787. 'testsunit=s@' => \$mysync->{testsunit},
  15788. 'testslive!' => \$mysync->{testslive},
  15789. 'testslive6!' => \$mysync->{testslive6},
  15790. 'justlogin!' => \$mysync->{justlogin},
  15791. 'justconnect!' => \$mysync->{justconnect},
  15792. 'tmpdir=s' => \$mysync->{ tmpdir },
  15793. 'pidfile=s' => \$mysync->{pidfile},
  15794. 'pidfilelocking!' => \$mysync->{pidfilelocking},
  15795. 'sigexit=s@' => \$mysync->{ sigexit },
  15796. 'sigreconnect=s@' => \$mysync->{ sigreconnect },
  15797. 'sigignore=s@' => \$mysync->{ sigignore },
  15798. 'releasecheck!' => \$mysync->{releasecheck},
  15799. 'modulesversion|modules_version!' => \$modulesversion,
  15800. 'usecache!' => \$usecache,
  15801. 'cacheaftercopy!' => \$cacheaftercopy,
  15802. 'debugcache!' => \$debugcache,
  15803. 'useuid!' => \$useuid,
  15804. 'addheader!' => \$mysync->{addheader},
  15805. 'exitwhenover=i' => \$mysync->{ exitwhenover },
  15806. 'checkselectable!' => \$mysync->{ checkselectable },
  15807. 'checkfoldersexist!' => \$mysync->{ checkfoldersexist },
  15808. 'checkmessageexists!' => \$checkmessageexists,
  15809. 'expungeaftereach!' => \$mysync->{ expungeaftereach },
  15810. 'abletosearch!' => \$mysync->{abletosearch},
  15811. 'abletosearch1!' => \$mysync->{abletosearch1},
  15812. 'abletosearch2!' => \$mysync->{abletosearch2},
  15813. 'showpasswords!' => \$mysync->{showpasswords},
  15814. 'maxlinelength=i' => \$maxlinelength,
  15815. 'maxlinelengthcmd=s' => \$maxlinelengthcmd,
  15816. 'minmaxlinelength=i' => \$minmaxlinelength,
  15817. 'debugmaxlinelength!' => \$debugmaxlinelength,
  15818. 'fixcolonbug!' => \$fixcolonbug,
  15819. 'create_folder_old!' => \$create_folder_old,
  15820. 'maxmessagespersecond=f' => \$mysync->{maxmessagespersecond},
  15821. 'maxbytespersecond=i' => \$mysync->{maxbytespersecond},
  15822. 'maxbytesafter=i' => \$mysync->{maxbytesafter},
  15823. 'maxsleep=f' => \$mysync->{maxsleep},
  15824. 'skipcrossduplicates!' => \$skipcrossduplicates,
  15825. 'debugcrossduplicates!' => \$debugcrossduplicates,
  15826. 'log!' => \$mysync->{log},
  15827. 'tail!' => \$mysync->{tail},
  15828. 'logfile=s' => \$mysync->{logfile},
  15829. 'logdir=s' => \$mysync->{logdir},
  15830. 'errorsmax=i' => \$mysync->{errorsmax},
  15831. 'errorsdump!' => \$mysync->{ errorsdump },
  15832. 'fetch_hash_set=s' => \$fetch_hash_set,
  15833. 'automap!' => \$mysync->{automap},
  15834. 'justautomap!' => \$mysync->{justautomap},
  15835. 'id!' => \$mysync->{id},
  15836. 'f1f2=s@' => \$mysync->{f1f2},
  15837. 'nof1f2' => \$mysync->{nof1f2},
  15838. 'f1f2h=s%' => \$mysync->{f1f2h},
  15839. 'justfolderlists!' => \$mysync->{justfolderlists},
  15840. 'delete1emptyfolders' => \$mysync->{delete1emptyfolders},
  15841. 'checknoabletosearch!' => \$mysync->{checknoabletosearch},
  15842. 'syncduplicates!' => \$mysync->{ syncduplicates },
  15843. 'dockercontext!' => \$mysync->{ dockercontext },
  15844. ) ;
  15845. #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
  15846. $mysync->{ debug } and output( $mysync, "get options: [$opt_ret][$numopt]\n" ) ;
  15847. my $numopt_after = scalar @arguments ;
  15848. #myprint( "get options: [$opt_ret][$numopt][$numopt_after]\n" ) ;
  15849. # The $arguments[0] test is just because parallel adds "" when it is
  15850. # used with {=7=} in sync_parallel_unix.sh
  15851. if ( $numopt_after and $arguments[0] ) {
  15852. myprint(
  15853. "Found ", scalar( @arguments ), " extra arguments : [@arguments]\n",
  15854. "It usually means a quoting issue in the command line ",
  15855. "or some misspelling options.\n",
  15856. ) ;
  15857. return ;
  15858. }
  15859. if ( ! $opt_ret ) {
  15860. return ;
  15861. }
  15862. return $numopt ;
  15863. }
  15864. sub tests_get_options
  15865. {
  15866. note( 'Entering tests_get_options()' ) ;
  15867. # CAVEAT: still setting global variables, be careful
  15868. # with tests, the context increases! $debug stays on for example.
  15869. # API:
  15870. # * input arguments: two ways, command line or CGI
  15871. # * the program arguments
  15872. # * QUERY_STRING env variable
  15873. # * return
  15874. # * undef if bad things happened like
  15875. # * options not known
  15876. # * --delete 2 input
  15877. # * number of arguments or QUERY_STRING length
  15878. my $mysync = { } ;
  15879. is( undef, get_options( $mysync, qw( --noexist ) ), 'get_options: --noexist => undef' ) ;
  15880. is( undef, $mysync->{ noexist }, 'get_options: --noexist => undef' ) ;
  15881. $mysync = { } ;
  15882. is( undef, get_options( $mysync, qw( --lalala --noexist --version ) ), 'get_options: --lalala --noexist --version => undef' ) ;
  15883. is( 1, $mysync->{ version }, 'get_options: --version => 1' ) ;
  15884. is( undef, $mysync->{ noexist }, 'get_options: --noexist => undef' ) ;
  15885. $mysync = { } ;
  15886. is( 1, get_options( $mysync, qw( --delete2 ) ), 'get_options: --delete2 => 1' ) ;
  15887. is( 1, $mysync->{ delete2 }, 'get_options: --delete2 => var delete2 = 1' ) ;
  15888. $mysync = { } ;
  15889. is( undef, get_options( $mysync, qw( --delete 2 ) ), 'get_options: --delete 2 => var undef' ) ;
  15890. is( undef, $mysync->{ delete1 }, 'get_options: --delete 2 => var still undef ; good!' ) ;
  15891. $mysync = { } ;
  15892. is( undef, get_options( $mysync, "--delete 2" ), 'get_options: --delete 2 => undef' ) ;
  15893. is( 1, get_options( $mysync, "--version" ), 'get_options: --version => 1' ) ;
  15894. is( 1, get_options( $mysync, "--help" ), 'get_options: --help => 1' ) ;
  15895. is( undef, get_options( $mysync, qw( --noexist --version ) ), 'get_options: --debug --noexist --version => undef' ) ;
  15896. is( 1, get_options( $mysync, qw( --version ) ), 'get_options: --version => 1' ) ;
  15897. is( undef, get_options( $mysync, qw( extra ) ), 'get_options: extra => undef' ) ;
  15898. is( undef, get_options( $mysync, qw( extra1 --version extra2 ) ), 'get_options: extra1 --version extra2 => undef' ) ;
  15899. $mysync = { } ;
  15900. is( 2, get_options( $mysync, qw( --host1 HOST_01) ), 'get_options: --host1 HOST_01 => 1' ) ;
  15901. is( 'HOST_01', $mysync->{ host1 }, 'get_options: --host1 HOST_01 => HOST_01' ) ;
  15902. #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
  15903. note( 'Leaving tests_get_options()' ) ;
  15904. return ;
  15905. }
  15906. sub get_options
  15907. {
  15908. my $mysync = shift @ARG ;
  15909. my @arguments = @ARG ;
  15910. #myprint( "1 mysync: ", Data::Dumper->Dump( [ $mysync ] ) ) ;
  15911. my $ret ;
  15912. if ( under_cgi_context( ) ) {
  15913. # CGI context
  15914. $ret = get_options_cgi( $mysync, @arguments ) ;
  15915. }else{
  15916. # Command line context ;
  15917. $ret = get_options_cmd( $mysync, @arguments ) ;
  15918. } ;
  15919. #myprint( "2 mysync: ", Data::Dumper->Dump( [ $mysync ] ) ) ;
  15920. foreach my $key ( sort keys %{ $mysync } ) {
  15921. if ( ! defined $mysync->{$key} ) {
  15922. delete $mysync->{$key} ;
  15923. next ;
  15924. }
  15925. if ( 'ARRAY' eq ref( $mysync->{$key} )
  15926. and 0 == scalar( @{ $mysync->{$key} } ) ) {
  15927. delete $mysync->{$key} ;
  15928. }
  15929. }
  15930. return $ret ;
  15931. }
  15932. sub tests_infos
  15933. {
  15934. note( 'Entering tests_infos()' ) ;
  15935. note( "OSNAME=$OSNAME" ) ;
  15936. note( "hostname=". hostname() ) ;
  15937. note( "cwd=" . getcwd( ) ) ;
  15938. note( "PROGRAM_NAME=$PROGRAM_NAME" ) ;
  15939. my $stat = stat("$PROGRAM_NAME") ;
  15940. my $perms = sprintf( "%04o\n", $stat->mode & oct($PERMISSION_FILTER) ) ;
  15941. note( "permissions=$perms" ) ;
  15942. note( "PROCESS_ID=$PROCESS_ID" ) ;
  15943. note( "REAL_USER_ID=$REAL_USER_ID" ) ;
  15944. note( "EFFECTIVE_USER_ID=$EFFECTIVE_USER_ID" ) ;
  15945. note( "context: " . imapsync_context( $sync ) ) ;
  15946. note( "memory_consumption: " . memory_consumption() . " bytes aka " . bytes_display_string_dec( memory_consumption() ) ) ;
  15947. cpu_number
  15948. note( "cpu_number: " . cpu_number() ) ;
  15949. note( $sync->{rcs} ) ;
  15950. note( 'Leaving tests_infos()' ) ;
  15951. return ;
  15952. }
  15953. sub condition_to_leave_after_tests
  15954. {
  15955. my $mysync = shift ;
  15956. if ( $mysync->{ testslive } or $mysync->{ testslive6 } )
  15957. {
  15958. return 0 ;
  15959. }
  15960. if ( $mysync->{ tests }
  15961. or $mysync->{ testsdebug }
  15962. or $mysync->{ testsunit }
  15963. )
  15964. {
  15965. return 1 ;
  15966. }
  15967. }
  15968. sub testunitsession
  15969. {
  15970. my $mysync = shift ;
  15971. if ( ! $mysync ) { return ; }
  15972. if ( ! $mysync->{ testsunit } ) { return ; }
  15973. my @functions = @{ $mysync->{ testsunit } } ;
  15974. if ( ! @functions ) { return ; }
  15975. SKIP: {
  15976. if ( ! @functions ) { skip 'No test in normal run' ; }
  15977. testsunit( @functions ) ;
  15978. done_testing( ) ;
  15979. }
  15980. return ;
  15981. }
  15982. sub tests_count_0s
  15983. {
  15984. note( 'Entering tests_count_zeros()' ) ;
  15985. is( 0, count_0s( ), 'count_0s: no parameters => 0' ) ;
  15986. is( 1, count_0s( 0 ), 'count_0s: 0 => 1' ) ;
  15987. is( 0, count_0s( 1 ), 'count_0s: 1 => 0' ) ;
  15988. is( 1, count_0s( 1, 0, 1 ), 'count_0s: 1, 0, 1 => 1' ) ;
  15989. is( 2, count_0s( 1, 0, 1, 0 ), 'count_0s: 1, 0, 1, 0 => 2' ) ;
  15990. note( 'Leaving tests_count_zeros()' ) ;
  15991. return ;
  15992. }
  15993. sub count_0s
  15994. {
  15995. my @array = @ARG ;
  15996. if ( ! @array ) { return 0 ; }
  15997. my $nb_zeros = 0 ;
  15998. map { $_ == 0 and $nb_zeros += 1 } @array ;
  15999. return $nb_zeros ;
  16000. }
  16001. sub tests_report_failures
  16002. {
  16003. note( 'Entering tests_report_failures()' ) ;
  16004. is( undef, report_failures( ), 'report_failures: no parameters => undef' ) ;
  16005. is( "nb 1 - first\n", report_failures( ({'ok' => 0, name => 'first'}) ), 'report_failures: "first" failed => nb 1 - first' ) ;
  16006. is( q{}, report_failures( ( {'ok' => 1, name => 'first'} ) ), 'report_failures: "first" success =>' ) ;
  16007. is( "nb 2 - second\n", report_failures( ( {'ok' => 1, name => 'second'}, {'ok' => 0, name => 'second'} ) ), 'report_failures: "second" failed => nb 2 - second' ) ;
  16008. 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' ) ;
  16009. note( 'Leaving tests_report_failures()' ) ;
  16010. return ;
  16011. }
  16012. sub report_failures
  16013. {
  16014. my @details = @ARG ;
  16015. if ( ! @details ) { return ; }
  16016. my $counter = 1 ;
  16017. my $report = q{} ;
  16018. foreach my $details ( @details ) {
  16019. if ( ! $details->{ 'ok' } ) {
  16020. my $name = $details->{ 'name' } || 'NONAME' ;
  16021. $report .= "nb $counter - $name\n" ;
  16022. }
  16023. $counter += 1 ;
  16024. }
  16025. return $report ;
  16026. }
  16027. sub tests_true
  16028. {
  16029. note( 'Entering tests_true()' ) ;
  16030. is( 1, 1, 'true: 1 is 1' ) ;
  16031. note( 'Leaving tests_true()' ) ;
  16032. return ;
  16033. }
  16034. sub tests_testsunit
  16035. {
  16036. note( 'Entering tests_testunit()' ) ;
  16037. is( undef, testsunit( ), 'testsunit: no parameters => undef' ) ;
  16038. is( undef, testsunit( undef ), 'testsunit: an undef parameter => undef' ) ;
  16039. is( undef, testsunit( q{} ), 'testsunit: an empty parameter => undef' ) ;
  16040. is( undef, testsunit( 'idonotexist' ), 'testsunit: a do not exist function as parameter => undef' ) ;
  16041. is( undef, testsunit( 'tests_true' ), 'testsunit: tests_true => undef' ) ;
  16042. note( 'Leaving tests_testunit()' ) ;
  16043. return ;
  16044. }
  16045. sub testsunit
  16046. {
  16047. my @functions = @ARG ;
  16048. if ( ! @functions ) { #
  16049. myprint( "testsunit warning: no argument given\n" ) ;
  16050. return ;
  16051. }
  16052. foreach my $function ( @functions ) {
  16053. if ( ! $function ) {
  16054. myprint( "testsunit warning: argument is empty\n" ) ;
  16055. next ;
  16056. }
  16057. if ( ! exists &$function ) {
  16058. myprint( "testsunit warning: function $function does not exist\n" ) ;
  16059. next ;
  16060. }
  16061. if ( ! defined &$function ) {
  16062. myprint( "testsunit warning: function $function is not defined\n" ) ;
  16063. next ;
  16064. }
  16065. my $function_ref = \&{ $function } ;
  16066. &$function_ref() ;
  16067. }
  16068. return ;
  16069. }
  16070. sub testsdebug
  16071. {
  16072. # Now a little obsolete since there is
  16073. # imapsync ... --testsunit "anyfunction"
  16074. my $mysync = shift ;
  16075. if ( ! $mysync->{ testsdebug } ) { return ; }
  16076. SKIP: {
  16077. if ( ! $mysync->{ testsdebug } ) {
  16078. skip 'No test in normal run' ;
  16079. }
  16080. note( 'Entering testsdebug()' ) ;
  16081. #ok( ( ( not -d 'W/tmp/tests' ) or rmtree( 'W/tmp/tests/' ) ), 'testsdebug: rmtree W/tmp/tests' ) ;
  16082. #tests_check_binary_embed_all_dyn_libs( ) ;
  16083. #tests_killpid_by_parent( ) ;
  16084. #tests_killpid_by_brother( ) ;
  16085. #tests_kill_zero( ) ;
  16086. #tests_connect_socket( ) ;
  16087. #tests_probe_imapssl( ) ;
  16088. #tests_cpu_number( ) ;
  16089. #tests_mailimapclient_connect( ) ;
  16090. tests_loadavg( ) ;
  16091. #tests_always_fail( ) ;
  16092. note( 'Leaving testsdebug()' ) ;
  16093. done_testing( ) ;
  16094. }
  16095. return ;
  16096. }
  16097. sub tests
  16098. {
  16099. my $mysync = shift ;
  16100. if ( ! $mysync->{ tests } ) { return ; }
  16101. SKIP: {
  16102. skip 'No test in normal run' if ( ! $mysync->{ tests } ) ;
  16103. note( 'Entering tests()' ) ;
  16104. tests_folder_routines( ) ;
  16105. tests_compare_lists( ) ;
  16106. tests_regexmess( ) ;
  16107. tests_skipmess( ) ;
  16108. tests_regexflags( );
  16109. tests_ucsecond( ) ;
  16110. tests_permanentflags();
  16111. tests_flags_filter( ) ;
  16112. tests_separator_invert( ) ;
  16113. tests_imap2_folder_name( ) ;
  16114. tests_command_line_nopassword( ) ;
  16115. tests_good_date( ) ;
  16116. tests_max( ) ;
  16117. tests_remove_not_num();
  16118. tests_memory_consumption( ) ;
  16119. tests_is_a_release_number();
  16120. tests_imapsync_basename();
  16121. tests_list_keys_in_2_not_in_1();
  16122. tests_convert_sep_to_slash( ) ;
  16123. tests_match_a_cache_file( ) ;
  16124. tests_cache_map( ) ;
  16125. tests_get_cache( ) ;
  16126. tests_clean_cache( ) ;
  16127. tests_clean_cache_2( ) ;
  16128. tests_touch( ) ;
  16129. tests_flagscase( ) ;
  16130. tests_mkpath( ) ;
  16131. tests_extract_header( ) ;
  16132. tests_decompose_header( ) ;
  16133. tests_epoch( ) ;
  16134. tests_add_header( ) ;
  16135. tests_cache_dir_fix( ) ;
  16136. tests_cache_dir_fix_win( ) ;
  16137. tests_filter_forbidden_characters( ) ;
  16138. tests_cache_folder( ) ;
  16139. tests_time_remaining( ) ;
  16140. tests_decompose_regex( ) ;
  16141. tests_backtick( ) ;
  16142. tests_bytes_display_string_bin( ) ;
  16143. tests_bytes_display_string_dec( ) ;
  16144. tests_header_line_normalize( ) ;
  16145. tests_fix_Inbox_INBOX_mapping( ) ;
  16146. tests_max_line_length( ) ;
  16147. tests_subject( ) ;
  16148. tests_msgs_from_maxmin( ) ;
  16149. tests_tmpdir_has_colon_bug( ) ;
  16150. tests_sleep_max_messages( ) ;
  16151. tests_sleep_max_bytes( ) ;
  16152. tests_logfile( ) ;
  16153. tests_setlogfile( ) ;
  16154. tests_jux_utf8_old( ) ;
  16155. tests_jux_utf8( ) ;
  16156. tests_pipemess( ) ;
  16157. tests_jux_utf8_list( ) ;
  16158. tests_guess_prefix( ) ;
  16159. tests_guess_separator( ) ;
  16160. tests_format_for_imap_arg( ) ;
  16161. tests_imapsync_id( ) ;
  16162. tests_date_from_rcs( ) ;
  16163. tests_quota_extract_storage_limit_in_bytes( ) ;
  16164. tests_quota_extract_storage_current_in_bytes( ) ;
  16165. tests_guess_special( ) ;
  16166. tests_do_valid_directory( ) ;
  16167. tests_delete1emptyfolders( ) ;
  16168. tests_message_for_host2( ) ;
  16169. tests_length_ref( ) ;
  16170. tests_firstline( ) ;
  16171. tests_diff_or_NA( ) ;
  16172. tests_match_number( ) ;
  16173. tests_all_defined( ) ;
  16174. tests_special_from_folders_hash( ) ;
  16175. tests_notmatch( ) ;
  16176. tests_match( ) ;
  16177. tests_get_options( ) ;
  16178. tests_get_options_cgi_context( ) ;
  16179. tests_rand32( ) ;
  16180. tests_hashsynclocal( ) ;
  16181. tests_hashsync( ) ;
  16182. tests_output( ) ;
  16183. tests_output_reset_with( ) ;
  16184. tests_output_start( ) ;
  16185. tests_check_last_release( ) ;
  16186. tests_loadavg( ) ;
  16187. tests_cpu_number( ) ;
  16188. tests_load_and_delay( ) ;
  16189. #tests_imapsping( ) ;
  16190. #tests_tcpping( ) ;
  16191. tests_sslcheck( ) ;
  16192. tests_not_long_imapsync_version_public( ) ;
  16193. tests_reconnect_if_needed( ) ;
  16194. tests_reconnect_12_if_needed( ) ;
  16195. tests_sleep_if_needed( ) ;
  16196. tests_string_to_file( ) ;
  16197. tests_file_to_string( ) ;
  16198. tests_under_cgi_context( ) ;
  16199. tests_umask( ) ;
  16200. tests_umask_str( ) ;
  16201. tests_set_umask( ) ;
  16202. tests_createhashfileifneeded( ) ;
  16203. tests_slash_to_underscore( ) ;
  16204. tests_testsunit( ) ;
  16205. tests_count_0s( ) ;
  16206. tests_report_failures( ) ;
  16207. tests_min( ) ;
  16208. #tests_connect_socket( ) ;
  16209. #tests_resolvrev( ) ;
  16210. tests_usage( ) ;
  16211. tests_version_from_rcs( ) ;
  16212. tests_backslash_caret( ) ;
  16213. #tests_mailimapclient_connect_bug( ) ; # it fails with Mail-IMAPClient <= 3.39
  16214. tests_write_pidfile( ) ;
  16215. tests_remove_pidfile_not_running( ) ;
  16216. tests_match_a_pid_number( ) ;
  16217. tests_prefix_seperator_invertion( ) ;
  16218. tests_is_integer( ) ;
  16219. tests_integer_or_1( ) ;
  16220. tests_is_number( ) ;
  16221. tests_sig_install( ) ;
  16222. tests_template( ) ;
  16223. tests_split_around_equal( ) ;
  16224. tests_toggle_sleep( ) ;
  16225. tests_labels( ) ;
  16226. tests_synclabels( ) ;
  16227. tests_uidexpunge_or_expunge( ) ;
  16228. tests_appendlimit_from_capability( ) ;
  16229. tests_maxsize_setting( ) ;
  16230. tests_mock_capability( ) ;
  16231. tests_appendlimit( ) ;
  16232. tests_capability_of( ) ;
  16233. tests_search_in_array( ) ;
  16234. tests_operators_and_exclam_precedence( ) ;
  16235. tests_teelaunch( ) ;
  16236. tests_logfileprepa( ) ;
  16237. tests_useheader_suggestion( ) ;
  16238. tests_nb_messages_in_2_not_in_1( ) ;
  16239. tests_labels_add_subfolder2( ) ;
  16240. tests_labels_remove_subfolder1( ) ;
  16241. tests_resynclabels( ) ;
  16242. tests_labels_remove_special( ) ;
  16243. tests_uniq( ) ;
  16244. tests_remove_from_requested_folders( ) ;
  16245. tests_errors_log( ) ;
  16246. tests_add_subfolder1_to_folderrec( ) ;
  16247. tests_sanitize_subfolder( ) ;
  16248. tests_remove_edging_blanks( ) ;
  16249. tests_sanitize( ) ;
  16250. tests_remove_last_char_if_is( ) ;
  16251. tests_check_binary_embed_all_dyn_libs( ) ;
  16252. tests_nthline( ) ;
  16253. tests_secondline( ) ;
  16254. tests_tail( ) ;
  16255. tests_truncmess( ) ;
  16256. tests_eta( ) ;
  16257. tests_timesince( ) ;
  16258. tests_timenext( ) ;
  16259. tests_foldersize( ) ;
  16260. tests_imapsync_context( ) ;
  16261. tests_abort( ) ;
  16262. tests_probe_imapssl( ) ;
  16263. tests_mailimapclient_connect( ) ;
  16264. tests_checknoabletosearch( ) ;
  16265. tests_errorsdump( ) ;
  16266. tests_errorsanalyse( ) ;
  16267. tests_most_common_error( ) ;
  16268. tests_errorclassify( ) ;
  16269. tests_error_type( ) ;
  16270. tests_sanitize_host( ) ;
  16271. tests_hmac_sha1_hex( ) ;
  16272. tests_total_bytes_max_reached( ) ;
  16273. tests_header_construct( ) ;
  16274. tests_remove_doublequotes_if_any( ) ;
  16275. tests_login_imap( ) ;
  16276. tests_login_imap_oauth( ) ;
  16277. tests_skipmess_neg( ) ;
  16278. tests_localtimez( ) ;
  16279. tests_file_to_array( ) ;
  16280. tests_cpu_time( ) ;
  16281. tests_cpu_percent( ) ;
  16282. tests_cpu_percent_global( ) ;
  16283. tests_flags_for_host2( ) ;
  16284. tests_under_docker_context( ) ;
  16285. tests_exit_value( ) ;
  16286. tests_comment_of_error_type( ) ;
  16287. tests_debugcontent( ) ;
  16288. tests_compress_ssl( ) ;
  16289. tests_compress( ) ;
  16290. tests_get_options_extra( ) ;
  16291. tests_get_options_from_string( ) ;
  16292. tests_infos( ) ;
  16293. #tests_resolv( ) ;
  16294. # Those three are for later use, when webserver will be inside imapsync
  16295. # or will be deleted them if I abandon the project.
  16296. #tests_killpid_by_parent( ) ;
  16297. #tests_killpid_by_brother( ) ;
  16298. #tests_kill_zero( ) ;
  16299. #tests_always_fail( ) ;
  16300. done_testing( 1860 ) ;
  16301. note( 'Leaving tests()' ) ;
  16302. }
  16303. return ;
  16304. }
  16305. sub tests_template
  16306. {
  16307. note( 'Entering tests_template()' ) ;
  16308. is( undef, template( ), 'template: no args => undef' ) ;
  16309. my $mysync = { } ;
  16310. is( undef, template( $mysync ), 'template: undef => undef' ) ;
  16311. is_deeply( {}, {}, 'template: a hash is a hash' ) ;
  16312. is_deeply( [], [], 'template: an array is an array' ) ;
  16313. note( 'Leaving tests_template()' ) ;
  16314. return ;
  16315. }
  16316. sub template
  16317. {
  16318. my $mysync = shift @ARG ;
  16319. return ;
  16320. }