imapsync 809 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693126941269512696126971269812699127001270112702127031270412705127061270712708127091271012711127121271312714127151271612717127181271912720127211272212723127241272512726127271272812729127301273112732127331273412735127361273712738127391274012741127421274312744127451274612747127481274912750127511275212753127541275512756127571275812759127601276112762127631276412765127661276712768127691277012771127721277312774127751277612777127781277912780127811278212783127841278512786127871278812789127901279112792127931279412795127961279712798127991280012801128021280312804128051280612807128081280912810128111281212813128141281512816128171281812819128201282112822128231282412825128261282712828128291283012831128321283312834128351283612837128381283912840128411284212843128441284512846128471284812849128501285112852128531285412855128561285712858128591286012861128621286312864128651286612867128681286912870128711287212873128741287512876128771287812879128801288112882128831288412885128861288712888128891289012891128921289312894128951289612897128981289912900129011290212903129041290512906129071290812909129101291112912129131291412915129161291712918129191292012921129221292312924129251292612927129281292912930129311293212933129341293512936129371293812939129401294112942129431294412945129461294712948129491295012951129521295312954129551295612957129581295912960129611296212963129641296512966129671296812969129701297112972129731297412975129761297712978129791298012981129821298312984129851298612987129881298912990129911299212993129941299512996129971299812999130001300113002130031300413005130061300713008130091301013011130121301313014130151301613017130181301913020130211302213023130241302513026130271302813029130301303113032130331303413035130361303713038130391304013041130421304313044130451304613047130481304913050130511305213053130541305513056130571305813059130601306113062130631306413065130661306713068130691307013071130721307313074130751307613077130781307913080130811308213083130841308513086130871308813089130901309113092130931309413095130961309713098130991310013101131021310313104131051310613107131081310913110131111311213113131141311513116131171311813119131201312113122131231312413125131261312713128131291313013131131321313313134131351313613137131381313913140131411314213143131441314513146131471314813149131501315113152131531315413155131561315713158131591316013161131621316313164131651316613167131681316913170131711317213173131741317513176131771317813179131801318113182131831318413185131861318713188131891319013191131921319313194131951319613197131981319913200132011320213203132041320513206132071320813209132101321113212132131321413215132161321713218132191322013221132221322313224132251322613227132281322913230132311323213233132341323513236132371323813239132401324113242132431324413245132461324713248132491325013251132521325313254132551325613257132581325913260132611326213263132641326513266132671326813269132701327113272132731327413275132761327713278132791328013281132821328313284132851328613287132881328913290132911329213293132941329513296132971329813299133001330113302133031330413305133061330713308133091331013311133121331313314133151331613317133181331913320133211332213323133241332513326133271332813329133301333113332133331333413335133361333713338133391334013341133421334313344133451334613347133481334913350133511335213353133541335513356133571335813359133601336113362133631336413365133661336713368133691337013371133721337313374133751337613377133781337913380133811338213383133841338513386133871338813389133901339113392133931339413395133961339713398133991340013401134021340313404134051340613407134081340913410134111341213413134141341513416134171341813419134201342113422134231342413425134261342713428134291343013431134321343313434134351343613437134381343913440134411344213443134441344513446134471344813449134501345113452134531345413455134561345713458134591346013461134621346313464134651346613467134681346913470134711347213473134741347513476134771347813479134801348113482134831348413485134861348713488134891349013491134921349313494134951349613497134981349913500135011350213503135041350513506135071350813509135101351113512135131351413515135161351713518135191352013521135221352313524135251352613527135281352913530135311353213533135341353513536135371353813539135401354113542135431354413545135461354713548135491355013551135521355313554135551355613557135581355913560135611356213563135641356513566135671356813569135701357113572135731357413575135761357713578135791358013581135821358313584135851358613587135881358913590135911359213593135941359513596135971359813599136001360113602136031360413605136061360713608136091361013611136121361313614136151361613617136181361913620136211362213623136241362513626136271362813629136301363113632136331363413635136361363713638136391364013641136421364313644136451364613647136481364913650136511365213653136541365513656136571365813659136601366113662136631366413665136661366713668136691367013671136721367313674136751367613677136781367913680136811368213683136841368513686136871368813689136901369113692136931369413695136961369713698136991370013701137021370313704137051370613707137081370913710137111371213713137141371513716137171371813719137201372113722137231372413725137261372713728137291373013731137321373313734137351373613737137381373913740137411374213743137441374513746137471374813749137501375113752137531375413755137561375713758137591376013761137621376313764137651376613767137681376913770137711377213773137741377513776137771377813779137801378113782137831378413785137861378713788137891379013791137921379313794137951379613797137981379913800138011380213803138041380513806138071380813809138101381113812138131381413815138161381713818138191382013821138221382313824138251382613827138281382913830138311383213833138341383513836138371383813839138401384113842138431384413845138461384713848138491385013851138521385313854138551385613857138581385913860138611386213863138641386513866138671386813869138701387113872138731387413875138761387713878138791388013881138821388313884138851388613887138881388913890138911389213893138941389513896138971389813899139001390113902139031390413905139061390713908139091391013911139121391313914139151391613917139181391913920139211392213923139241392513926139271392813929139301393113932139331393413935139361393713938139391394013941139421394313944139451394613947139481394913950139511395213953139541395513956139571395813959139601396113962139631396413965139661396713968139691397013971139721397313974139751397613977139781397913980139811398213983139841398513986139871398813989139901399113992139931399413995139961399713998139991400014001140021400314004140051400614007140081400914010140111401214013140141401514016140171401814019140201402114022140231402414025140261402714028140291403014031140321403314034140351403614037140381403914040140411404214043140441404514046140471404814049140501405114052140531405414055140561405714058140591406014061140621406314064140651406614067140681406914070140711407214073140741407514076140771407814079140801408114082140831408414085140861408714088140891409014091140921409314094140951409614097140981409914100141011410214103141041410514106141071410814109141101411114112141131411414115141161411714118141191412014121141221412314124141251412614127141281412914130141311413214133141341413514136141371413814139141401414114142141431414414145141461414714148141491415014151141521415314154141551415614157141581415914160141611416214163141641416514166141671416814169141701417114172141731417414175141761417714178141791418014181141821418314184141851418614187141881418914190141911419214193141941419514196141971419814199142001420114202142031420414205142061420714208142091421014211142121421314214142151421614217142181421914220142211422214223142241422514226142271422814229142301423114232142331423414235142361423714238142391424014241142421424314244142451424614247142481424914250142511425214253142541425514256142571425814259142601426114262142631426414265142661426714268142691427014271142721427314274142751427614277142781427914280142811428214283142841428514286142871428814289142901429114292142931429414295142961429714298142991430014301143021430314304143051430614307143081430914310143111431214313143141431514316143171431814319143201432114322143231432414325143261432714328143291433014331143321433314334143351433614337143381433914340143411434214343143441434514346143471434814349143501435114352143531435414355143561435714358143591436014361143621436314364143651436614367143681436914370143711437214373143741437514376143771437814379143801438114382143831438414385143861438714388143891439014391143921439314394143951439614397143981439914400144011440214403144041440514406144071440814409144101441114412144131441414415144161441714418144191442014421144221442314424144251442614427144281442914430144311443214433144341443514436144371443814439144401444114442144431444414445144461444714448144491445014451144521445314454144551445614457144581445914460144611446214463144641446514466144671446814469144701447114472144731447414475144761447714478144791448014481144821448314484144851448614487144881448914490144911449214493144941449514496144971449814499145001450114502145031450414505145061450714508145091451014511145121451314514145151451614517145181451914520145211452214523145241452514526145271452814529145301453114532145331453414535145361453714538145391454014541145421454314544145451454614547145481454914550145511455214553145541455514556145571455814559145601456114562145631456414565145661456714568145691457014571145721457314574145751457614577145781457914580145811458214583145841458514586145871458814589145901459114592145931459414595145961459714598145991460014601146021460314604146051460614607146081460914610146111461214613146141461514616146171461814619146201462114622146231462414625146261462714628146291463014631146321463314634146351463614637146381463914640146411464214643146441464514646146471464814649146501465114652146531465414655146561465714658146591466014661146621466314664146651466614667146681466914670146711467214673146741467514676146771467814679146801468114682146831468414685146861468714688146891469014691146921469314694146951469614697146981469914700147011470214703147041470514706147071470814709147101471114712147131471414715147161471714718147191472014721147221472314724147251472614727147281472914730147311473214733147341473514736147371473814739147401474114742147431474414745147461474714748147491475014751147521475314754147551475614757147581475914760147611476214763147641476514766147671476814769147701477114772147731477414775147761477714778147791478014781147821478314784147851478614787147881478914790147911479214793147941479514796147971479814799148001480114802148031480414805148061480714808148091481014811148121481314814148151481614817148181481914820148211482214823148241482514826148271482814829148301483114832148331483414835148361483714838148391484014841148421484314844148451484614847148481484914850148511485214853148541485514856148571485814859148601486114862148631486414865148661486714868148691487014871148721487314874148751487614877148781487914880148811488214883148841488514886148871488814889148901489114892148931489414895148961489714898148991490014901149021490314904149051490614907149081490914910149111491214913149141491514916149171491814919149201492114922149231492414925149261492714928149291493014931149321493314934149351493614937149381493914940149411494214943149441494514946149471494814949149501495114952149531495414955149561495714958149591496014961149621496314964149651496614967149681496914970149711497214973149741497514976149771497814979149801498114982149831498414985149861498714988149891499014991149921499314994149951499614997149981499915000150011500215003150041500515006150071500815009150101501115012150131501415015150161501715018150191502015021150221502315024150251502615027150281502915030150311503215033150341503515036150371503815039150401504115042150431504415045150461504715048150491505015051150521505315054150551505615057150581505915060150611506215063150641506515066150671506815069150701507115072150731507415075150761507715078150791508015081150821508315084150851508615087150881508915090150911509215093150941509515096150971509815099151001510115102151031510415105151061510715108151091511015111151121511315114151151511615117151181511915120151211512215123151241512515126151271512815129151301513115132151331513415135151361513715138151391514015141151421514315144151451514615147151481514915150151511515215153151541515515156151571515815159151601516115162151631516415165151661516715168151691517015171151721517315174151751517615177151781517915180151811518215183151841518515186151871518815189151901519115192151931519415195151961519715198151991520015201152021520315204152051520615207152081520915210152111521215213152141521515216152171521815219152201522115222152231522415225152261522715228152291523015231152321523315234152351523615237152381523915240152411524215243152441524515246152471524815249152501525115252152531525415255152561525715258152591526015261152621526315264152651526615267152681526915270152711527215273152741527515276152771527815279152801528115282152831528415285152861528715288152891529015291152921529315294152951529615297152981529915300153011530215303153041530515306153071530815309153101531115312153131531415315153161531715318153191532015321153221532315324153251532615327153281532915330153311533215333153341533515336153371533815339153401534115342153431534415345153461534715348153491535015351153521535315354153551535615357153581535915360153611536215363153641536515366153671536815369153701537115372153731537415375153761537715378153791538015381153821538315384153851538615387153881538915390153911539215393153941539515396153971539815399154001540115402154031540415405154061540715408154091541015411154121541315414154151541615417154181541915420154211542215423154241542515426154271542815429154301543115432154331543415435154361543715438154391544015441154421544315444154451544615447154481544915450154511545215453154541545515456154571545815459154601546115462154631546415465154661546715468154691547015471154721547315474154751547615477154781547915480154811548215483154841548515486154871548815489154901549115492154931549415495154961549715498154991550015501155021550315504155051550615507155081550915510155111551215513155141551515516155171551815519155201552115522155231552415525155261552715528155291553015531155321553315534155351553615537155381553915540155411554215543155441554515546155471554815549155501555115552155531555415555155561555715558155591556015561155621556315564155651556615567155681556915570155711557215573155741557515576155771557815579155801558115582155831558415585155861558715588155891559015591155921559315594155951559615597155981559915600156011560215603156041560515606156071560815609156101561115612156131561415615156161561715618156191562015621156221562315624156251562615627156281562915630156311563215633156341563515636156371563815639156401564115642156431564415645156461564715648156491565015651156521565315654156551565615657156581565915660156611566215663156641566515666156671566815669156701567115672156731567415675156761567715678156791568015681156821568315684156851568615687156881568915690156911569215693156941569515696156971569815699157001570115702157031570415705157061570715708157091571015711157121571315714157151571615717157181571915720157211572215723157241572515726157271572815729157301573115732157331573415735157361573715738157391574015741157421574315744157451574615747157481574915750157511575215753157541575515756157571575815759157601576115762157631576415765157661576715768157691577015771157721577315774157751577615777157781577915780157811578215783157841578515786157871578815789157901579115792157931579415795157961579715798157991580015801158021580315804158051580615807158081580915810158111581215813158141581515816158171581815819158201582115822158231582415825158261582715828158291583015831158321583315834158351583615837158381583915840158411584215843158441584515846158471584815849158501585115852158531585415855158561585715858158591586015861158621586315864158651586615867158681586915870158711587215873158741587515876158771587815879158801588115882158831588415885158861588715888158891589015891158921589315894158951589615897158981589915900159011590215903159041590515906159071590815909159101591115912159131591415915159161591715918159191592015921159221592315924159251592615927159281592915930159311593215933159341593515936159371593815939159401594115942159431594415945159461594715948159491595015951159521595315954159551595615957159581595915960159611596215963159641596515966159671596815969159701597115972159731597415975159761597715978159791598015981159821598315984159851598615987159881598915990159911599215993159941599515996159971599815999160001600116002160031600416005160061600716008160091601016011160121601316014160151601616017160181601916020160211602216023160241602516026160271602816029160301603116032160331603416035160361603716038160391604016041160421604316044160451604616047160481604916050160511605216053160541605516056160571605816059160601606116062160631606416065160661606716068160691607016071160721607316074160751607616077160781607916080160811608216083160841608516086160871608816089160901609116092160931609416095160961609716098160991610016101161021610316104161051610616107161081610916110161111611216113161141611516116161171611816119161201612116122161231612416125161261612716128161291613016131161321613316134161351613616137161381613916140161411614216143161441614516146161471614816149161501615116152161531615416155161561615716158161591616016161161621616316164161651616616167161681616916170161711617216173161741617516176161771617816179161801618116182161831618416185161861618716188161891619016191161921619316194161951619616197161981619916200162011620216203162041620516206162071620816209162101621116212162131621416215162161621716218162191622016221162221622316224162251622616227162281622916230162311623216233162341623516236162371623816239162401624116242162431624416245162461624716248162491625016251162521625316254162551625616257162581625916260162611626216263162641626516266162671626816269162701627116272162731627416275162761627716278162791628016281162821628316284162851628616287162881628916290162911629216293162941629516296162971629816299163001630116302163031630416305163061630716308163091631016311163121631316314163151631616317163181631916320163211632216323163241632516326163271632816329163301633116332163331633416335163361633716338163391634016341163421634316344163451634616347163481634916350163511635216353163541635516356163571635816359163601636116362163631636416365163661636716368163691637016371163721637316374163751637616377163781637916380163811638216383163841638516386163871638816389163901639116392163931639416395163961639716398163991640016401164021640316404164051640616407164081640916410164111641216413164141641516416164171641816419164201642116422164231642416425164261642716428164291643016431164321643316434164351643616437164381643916440164411644216443164441644516446164471644816449164501645116452164531645416455164561645716458164591646016461164621646316464164651646616467164681646916470164711647216473164741647516476164771647816479164801648116482164831648416485164861648716488164891649016491164921649316494164951649616497164981649916500165011650216503165041650516506165071650816509165101651116512165131651416515165161651716518165191652016521165221652316524165251652616527165281652916530165311653216533165341653516536165371653816539165401654116542165431654416545165461654716548165491655016551165521655316554165551655616557165581655916560165611656216563165641656516566165671656816569165701657116572165731657416575165761657716578165791658016581165821658316584165851658616587165881658916590165911659216593165941659516596165971659816599166001660116602166031660416605166061660716608166091661016611166121661316614166151661616617166181661916620166211662216623166241662516626166271662816629166301663116632166331663416635166361663716638166391664016641166421664316644166451664616647166481664916650166511665216653166541665516656166571665816659166601666116662166631666416665166661666716668166691667016671166721667316674166751667616677166781667916680166811668216683166841668516686166871668816689166901669116692166931669416695166961669716698166991670016701167021670316704167051670616707167081670916710167111671216713167141671516716167171671816719167201672116722167231672416725167261672716728167291673016731167321673316734167351673616737167381673916740167411674216743167441674516746167471674816749167501675116752167531675416755167561675716758167591676016761167621676316764167651676616767167681676916770167711677216773167741677516776167771677816779167801678116782167831678416785167861678716788167891679016791167921679316794167951679616797167981679916800168011680216803168041680516806168071680816809168101681116812168131681416815168161681716818168191682016821168221682316824168251682616827168281682916830168311683216833168341683516836168371683816839168401684116842168431684416845168461684716848168491685016851168521685316854168551685616857168581685916860168611686216863168641686516866168671686816869168701687116872168731687416875168761687716878168791688016881168821688316884168851688616887168881688916890168911689216893168941689516896168971689816899169001690116902169031690416905169061690716908169091691016911169121691316914169151691616917169181691916920169211692216923169241692516926169271692816929169301693116932169331693416935169361693716938169391694016941169421694316944169451694616947169481694916950169511695216953169541695516956169571695816959169601696116962169631696416965169661696716968169691697016971169721697316974169751697616977169781697916980169811698216983169841698516986169871698816989169901699116992169931699416995169961699716998169991700017001170021700317004170051700617007170081700917010170111701217013170141701517016170171701817019170201702117022170231702417025170261702717028170291703017031170321703317034170351703617037170381703917040170411704217043170441704517046170471704817049170501705117052170531705417055170561705717058170591706017061170621706317064170651706617067170681706917070170711707217073170741707517076170771707817079170801708117082170831708417085170861708717088170891709017091170921709317094170951709617097170981709917100171011710217103171041710517106171071710817109171101711117112171131711417115171161711717118171191712017121171221712317124171251712617127171281712917130171311713217133171341713517136171371713817139171401714117142171431714417145171461714717148171491715017151171521715317154171551715617157171581715917160171611716217163171641716517166171671716817169171701717117172171731717417175171761717717178171791718017181171821718317184171851718617187171881718917190171911719217193171941719517196171971719817199172001720117202172031720417205172061720717208172091721017211172121721317214172151721617217172181721917220172211722217223172241722517226172271722817229172301723117232172331723417235172361723717238172391724017241172421724317244172451724617247172481724917250172511725217253172541725517256172571725817259172601726117262172631726417265172661726717268172691727017271172721727317274172751727617277172781727917280172811728217283172841728517286172871728817289172901729117292172931729417295172961729717298172991730017301173021730317304173051730617307173081730917310173111731217313173141731517316173171731817319173201732117322173231732417325173261732717328173291733017331173321733317334173351733617337173381733917340173411734217343173441734517346173471734817349173501735117352173531735417355173561735717358173591736017361173621736317364173651736617367173681736917370173711737217373173741737517376173771737817379173801738117382173831738417385173861738717388173891739017391173921739317394173951739617397173981739917400174011740217403174041740517406174071740817409174101741117412174131741417415174161741717418174191742017421174221742317424174251742617427174281742917430174311743217433174341743517436174371743817439174401744117442174431744417445174461744717448174491745017451174521745317454174551745617457174581745917460174611746217463174641746517466174671746817469174701747117472174731747417475174761747717478174791748017481174821748317484174851748617487174881748917490174911749217493174941749517496174971749817499175001750117502175031750417505175061750717508175091751017511175121751317514175151751617517175181751917520175211752217523175241752517526175271752817529175301753117532175331753417535175361753717538175391754017541175421754317544175451754617547175481754917550175511755217553175541755517556175571755817559175601756117562175631756417565175661756717568175691757017571175721757317574175751757617577175781757917580175811758217583175841758517586175871758817589175901759117592175931759417595175961759717598175991760017601176021760317604176051760617607176081760917610176111761217613176141761517616176171761817619176201762117622176231762417625176261762717628176291763017631176321763317634176351763617637176381763917640176411764217643176441764517646176471764817649176501765117652176531765417655176561765717658176591766017661176621766317664176651766617667176681766917670176711767217673176741767517676176771767817679176801768117682176831768417685176861768717688176891769017691176921769317694176951769617697176981769917700177011770217703177041770517706177071770817709177101771117712177131771417715177161771717718177191772017721177221772317724177251772617727177281772917730177311773217733177341773517736177371773817739177401774117742177431774417745177461774717748177491775017751177521775317754177551775617757177581775917760177611776217763177641776517766177671776817769177701777117772177731777417775177761777717778177791778017781177821778317784177851778617787177881778917790177911779217793177941779517796177971779817799178001780117802178031780417805178061780717808178091781017811178121781317814178151781617817178181781917820178211782217823178241782517826178271782817829178301783117832178331783417835178361783717838178391784017841178421784317844178451784617847178481784917850178511785217853178541785517856178571785817859178601786117862178631786417865178661786717868178691787017871178721787317874178751787617877178781787917880178811788217883178841788517886178871788817889178901789117892178931789417895178961789717898178991790017901179021790317904179051790617907179081790917910179111791217913179141791517916179171791817919179201792117922179231792417925179261792717928179291793017931179321793317934179351793617937179381793917940179411794217943179441794517946179471794817949179501795117952179531795417955179561795717958179591796017961179621796317964179651796617967179681796917970179711797217973179741797517976179771797817979179801798117982179831798417985179861798717988179891799017991179921799317994179951799617997179981799918000180011800218003180041800518006180071800818009180101801118012180131801418015180161801718018180191802018021180221802318024180251802618027180281802918030180311803218033180341803518036180371803818039180401804118042180431804418045180461804718048180491805018051180521805318054180551805618057180581805918060180611806218063180641806518066180671806818069180701807118072180731807418075180761807718078180791808018081180821808318084180851808618087180881808918090180911809218093180941809518096180971809818099181001810118102181031810418105181061810718108181091811018111181121811318114181151811618117181181811918120181211812218123181241812518126181271812818129181301813118132181331813418135181361813718138181391814018141181421814318144181451814618147181481814918150181511815218153181541815518156181571815818159181601816118162181631816418165181661816718168181691817018171181721817318174181751817618177181781817918180181811818218183181841818518186181871818818189181901819118192181931819418195181961819718198181991820018201182021820318204182051820618207182081820918210182111821218213182141821518216182171821818219182201822118222182231822418225182261822718228182291823018231182321823318234182351823618237182381823918240182411824218243182441824518246182471824818249182501825118252182531825418255182561825718258182591826018261182621826318264182651826618267182681826918270182711827218273182741827518276182771827818279182801828118282182831828418285182861828718288182891829018291182921829318294182951829618297182981829918300183011830218303183041830518306183071830818309183101831118312183131831418315183161831718318183191832018321183221832318324183251832618327183281832918330183311833218333183341833518336183371833818339183401834118342183431834418345183461834718348183491835018351183521835318354183551835618357183581835918360183611836218363183641836518366183671836818369183701837118372183731837418375183761837718378183791838018381183821838318384183851838618387183881838918390183911839218393183941839518396183971839818399184001840118402184031840418405184061840718408184091841018411184121841318414184151841618417184181841918420184211842218423184241842518426184271842818429184301843118432184331843418435184361843718438184391844018441184421844318444184451844618447184481844918450184511845218453184541845518456184571845818459184601846118462184631846418465184661846718468184691847018471184721847318474184751847618477184781847918480184811848218483184841848518486184871848818489184901849118492184931849418495184961849718498184991850018501185021850318504185051850618507185081850918510185111851218513185141851518516185171851818519185201852118522185231852418525185261852718528185291853018531185321853318534185351853618537185381853918540185411854218543185441854518546185471854818549185501855118552185531855418555185561855718558185591856018561185621856318564185651856618567185681856918570185711857218573185741857518576185771857818579185801858118582185831858418585185861858718588185891859018591185921859318594185951859618597185981859918600186011860218603186041860518606186071860818609186101861118612186131861418615186161861718618186191862018621186221862318624186251862618627186281862918630186311863218633186341863518636186371863818639186401864118642186431864418645186461864718648186491865018651186521865318654186551865618657186581865918660186611866218663186641866518666186671866818669186701867118672186731867418675186761867718678186791868018681186821868318684186851868618687186881868918690186911869218693186941869518696186971869818699187001870118702187031870418705187061870718708187091871018711187121871318714187151871618717187181871918720187211872218723187241872518726187271872818729187301873118732187331873418735187361873718738187391874018741187421874318744187451874618747187481874918750187511875218753187541875518756187571875818759187601876118762187631876418765187661876718768187691877018771187721877318774187751877618777187781877918780187811878218783187841878518786187871878818789187901879118792187931879418795187961879718798187991880018801188021880318804188051880618807188081880918810188111881218813188141881518816188171881818819188201882118822188231882418825188261882718828188291883018831188321883318834188351883618837188381883918840188411884218843188441884518846188471884818849188501885118852188531885418855188561885718858188591886018861188621886318864188651886618867188681886918870188711887218873188741887518876188771887818879188801888118882188831888418885188861888718888188891889018891188921889318894188951889618897188981889918900189011890218903189041890518906189071890818909189101891118912189131891418915189161891718918189191892018921189221892318924189251892618927189281892918930189311893218933189341893518936189371893818939189401894118942189431894418945189461894718948189491895018951189521895318954189551895618957189581895918960189611896218963189641896518966189671896818969189701897118972189731897418975189761897718978189791898018981189821898318984189851898618987189881898918990189911899218993189941899518996189971899818999190001900119002190031900419005190061900719008190091901019011190121901319014190151901619017190181901919020190211902219023190241902519026190271902819029190301903119032190331903419035190361903719038190391904019041190421904319044190451904619047190481904919050190511905219053190541905519056190571905819059190601906119062190631906419065190661906719068190691907019071190721907319074190751907619077190781907919080190811908219083190841908519086190871908819089190901909119092190931909419095190961909719098190991910019101191021910319104191051910619107191081910919110191111911219113191141911519116191171911819119191201912119122191231912419125191261912719128191291913019131191321913319134191351913619137191381913919140191411914219143191441914519146191471914819149191501915119152191531915419155191561915719158191591916019161191621916319164191651916619167191681916919170191711917219173191741917519176191771917819179191801918119182191831918419185191861918719188191891919019191191921919319194191951919619197191981919919200192011920219203192041920519206192071920819209192101921119212192131921419215192161921719218192191922019221192221922319224192251922619227192281922919230192311923219233192341923519236192371923819239192401924119242192431924419245192461924719248192491925019251192521925319254192551925619257192581925919260192611926219263192641926519266192671926819269192701927119272192731927419275192761927719278192791928019281192821928319284192851928619287192881928919290192911929219293192941929519296192971929819299193001930119302193031930419305193061930719308193091931019311193121931319314193151931619317193181931919320193211932219323193241932519326193271932819329193301933119332193331933419335193361933719338193391934019341193421934319344193451934619347193481934919350193511935219353193541935519356193571935819359193601936119362193631936419365193661936719368193691937019371193721937319374193751937619377193781937919380193811938219383193841938519386193871938819389193901939119392193931939419395193961939719398193991940019401194021940319404194051940619407194081940919410194111941219413194141941519416194171941819419194201942119422194231942419425194261942719428194291943019431194321943319434194351943619437194381943919440194411944219443194441944519446194471944819449194501945119452194531945419455194561945719458194591946019461194621946319464194651946619467194681946919470194711947219473194741947519476194771947819479194801948119482194831948419485194861948719488194891949019491194921949319494194951949619497194981949919500195011950219503195041950519506195071950819509195101951119512195131951419515195161951719518195191952019521195221952319524195251952619527195281952919530195311953219533195341953519536195371953819539195401954119542195431954419545195461954719548195491955019551195521955319554195551955619557195581955919560195611956219563195641956519566195671956819569195701957119572195731957419575195761957719578195791958019581195821958319584195851958619587195881958919590195911959219593195941959519596195971959819599196001960119602196031960419605196061960719608196091961019611196121961319614196151961619617196181961919620196211962219623196241962519626196271962819629196301963119632196331963419635196361963719638196391964019641196421964319644196451964619647196481964919650196511965219653196541965519656196571965819659196601966119662196631966419665196661966719668196691967019671196721967319674196751967619677196781967919680196811968219683196841968519686196871968819689196901969119692196931969419695196961969719698196991970019701197021970319704197051970619707197081970919710197111971219713197141971519716197171971819719197201972119722197231972419725197261972719728197291973019731197321973319734197351973619737197381973919740197411974219743197441974519746197471974819749197501975119752197531975419755197561975719758197591976019761197621976319764197651976619767197681976919770197711977219773197741977519776197771977819779197801978119782197831978419785197861978719788197891979019791197921979319794197951979619797197981979919800198011980219803198041980519806198071980819809198101981119812198131981419815198161981719818198191982019821198221982319824198251982619827198281982919830198311983219833198341983519836198371983819839198401984119842198431984419845198461984719848198491985019851198521985319854198551985619857198581985919860198611986219863198641986519866198671986819869198701987119872198731987419875198761987719878198791988019881198821988319884198851988619887198881988919890198911989219893198941989519896198971989819899199001990119902199031990419905199061990719908199091991019911199121991319914199151991619917199181991919920199211992219923199241992519926199271992819929199301993119932199331993419935199361993719938199391994019941199421994319944199451994619947199481994919950199511995219953199541995519956199571995819959199601996119962199631996419965199661996719968199691997019971199721997319974199751997619977199781997919980199811998219983199841998519986199871998819989199901999119992199931999419995199961999719998199992000020001200022000320004200052000620007200082000920010200112001220013200142001520016200172001820019200202002120022200232002420025200262002720028200292003020031200322003320034200352003620037200382003920040200412004220043200442004520046200472004820049200502005120052200532005420055200562005720058200592006020061200622006320064200652006620067200682006920070200712007220073200742007520076200772007820079200802008120082200832008420085200862008720088200892009020091200922009320094200952009620097200982009920100201012010220103201042010520106201072010820109201102011120112201132011420115201162011720118201192012020121201222012320124201252012620127201282012920130201312013220133201342013520136201372013820139201402014120142201432014420145201462014720148201492015020151201522015320154201552015620157201582015920160201612016220163201642016520166201672016820169201702017120172201732017420175201762017720178201792018020181201822018320184201852018620187201882018920190201912019220193201942019520196201972019820199202002020120202202032020420205202062020720208202092021020211202122021320214202152021620217202182021920220202212022220223202242022520226202272022820229202302023120232202332023420235202362023720238202392024020241202422024320244202452024620247202482024920250202512025220253202542025520256202572025820259202602026120262202632026420265202662026720268202692027020271202722027320274202752027620277202782027920280202812028220283202842028520286202872028820289202902029120292202932029420295202962029720298202992030020301203022030320304203052030620307203082030920310203112031220313203142031520316203172031820319203202032120322203232032420325203262032720328203292033020331203322033320334203352033620337203382033920340203412034220343203442034520346203472034820349203502035120352203532035420355203562035720358203592036020361203622036320364203652036620367203682036920370203712037220373203742037520376203772037820379203802038120382203832038420385203862038720388203892039020391203922039320394203952039620397203982039920400204012040220403204042040520406204072040820409204102041120412204132041420415204162041720418204192042020421204222042320424204252042620427204282042920430204312043220433204342043520436204372043820439204402044120442204432044420445204462044720448204492045020451204522045320454204552045620457204582045920460204612046220463204642046520466204672046820469204702047120472204732047420475204762047720478204792048020481204822048320484204852048620487204882048920490204912049220493204942049520496204972049820499205002050120502205032050420505205062050720508205092051020511205122051320514205152051620517205182051920520205212052220523205242052520526205272052820529205302053120532205332053420535205362053720538205392054020541205422054320544205452054620547205482054920550205512055220553205542055520556205572055820559205602056120562205632056420565205662056720568205692057020571205722057320574205752057620577205782057920580205812058220583205842058520586205872058820589205902059120592205932059420595205962059720598205992060020601206022060320604206052060620607206082060920610206112061220613206142061520616206172061820619206202062120622206232062420625206262062720628206292063020631206322063320634206352063620637206382063920640206412064220643206442064520646206472064820649206502065120652206532065420655206562065720658206592066020661206622066320664206652066620667206682066920670206712067220673206742067520676206772067820679206802068120682206832068420685206862068720688206892069020691206922069320694206952069620697206982069920700207012070220703207042070520706207072070820709207102071120712207132071420715207162071720718207192072020721207222072320724207252072620727207282072920730207312073220733207342073520736207372073820739207402074120742207432074420745207462074720748207492075020751207522075320754207552075620757207582075920760207612076220763207642076520766207672076820769207702077120772207732077420775207762077720778207792078020781207822078320784207852078620787207882078920790207912079220793207942079520796207972079820799208002080120802208032080420805208062080720808208092081020811208122081320814208152081620817208182081920820208212082220823208242082520826208272082820829208302083120832208332083420835208362083720838208392084020841208422084320844208452084620847208482084920850208512085220853208542085520856208572085820859208602086120862208632086420865208662086720868208692087020871208722087320874208752087620877208782087920880208812088220883208842088520886208872088820889208902089120892208932089420895208962089720898208992090020901209022090320904209052090620907209082090920910209112091220913209142091520916209172091820919209202092120922209232092420925209262092720928209292093020931209322093320934209352093620937209382093920940209412094220943209442094520946209472094820949209502095120952209532095420955209562095720958209592096020961209622096320964209652096620967209682096920970209712097220973209742097520976209772097820979209802098120982209832098420985209862098720988209892099020991209922099320994209952099620997209982099921000210012100221003210042100521006210072100821009210102101121012210132101421015210162101721018210192102021021210222102321024210252102621027210282102921030210312103221033210342103521036210372103821039210402104121042210432104421045210462104721048210492105021051210522105321054210552105621057210582105921060210612106221063210642106521066210672106821069210702107121072210732107421075210762107721078210792108021081210822108321084210852108621087210882108921090210912109221093210942109521096210972109821099211002110121102211032110421105211062110721108211092111021111211122111321114211152111621117211182111921120211212112221123211242112521126211272112821129211302113121132211332113421135211362113721138211392114021141211422114321144211452114621147211482114921150211512115221153211542115521156211572115821159211602116121162211632116421165211662116721168211692117021171211722117321174211752117621177211782117921180211812118221183211842118521186211872118821189211902119121192211932119421195211962119721198211992120021201212022120321204212052120621207212082120921210212112121221213212142121521216212172121821219212202122121222212232122421225212262122721228212292123021231212322123321234212352123621237212382123921240212412124221243212442124521246212472124821249212502125121252212532125421255212562125721258212592126021261212622126321264212652126621267212682126921270212712127221273212742127521276212772127821279212802128121282212832128421285212862128721288212892129021291212922129321294212952129621297212982129921300213012130221303213042130521306213072130821309213102131121312213132131421315213162131721318213192132021321213222132321324213252132621327213282132921330213312133221333213342133521336213372133821339213402134121342213432134421345213462134721348213492135021351213522135321354213552135621357213582135921360213612136221363213642136521366213672136821369213702137121372213732137421375213762137721378213792138021381213822138321384213852138621387213882138921390213912139221393213942139521396213972139821399214002140121402214032140421405214062140721408214092141021411214122141321414214152141621417214182141921420214212142221423214242142521426214272142821429214302143121432214332143421435214362143721438214392144021441214422144321444214452144621447214482144921450214512145221453214542145521456214572145821459214602146121462214632146421465214662146721468214692147021471214722147321474214752147621477214782147921480214812148221483214842148521486214872148821489214902149121492214932149421495214962149721498214992150021501215022150321504215052150621507215082150921510215112151221513215142151521516215172151821519215202152121522215232152421525215262152721528215292153021531215322153321534215352153621537215382153921540215412154221543215442154521546215472154821549215502155121552215532155421555215562155721558215592156021561215622156321564215652156621567215682156921570215712157221573215742157521576215772157821579215802158121582215832158421585215862158721588215892159021591215922159321594215952159621597215982159921600216012160221603216042160521606216072160821609216102161121612216132161421615216162161721618216192162021621216222162321624216252162621627216282162921630216312163221633216342163521636216372163821639216402164121642216432164421645216462164721648216492165021651216522165321654216552165621657216582165921660216612166221663216642166521666216672166821669216702167121672216732167421675216762167721678216792168021681216822168321684216852168621687216882168921690216912169221693216942169521696216972169821699217002170121702217032170421705217062170721708217092171021711217122171321714217152171621717217182171921720217212172221723217242172521726217272172821729217302173121732217332173421735217362173721738217392174021741217422174321744217452174621747217482174921750217512175221753217542175521756217572175821759217602176121762217632176421765217662176721768217692177021771217722177321774217752177621777217782177921780217812178221783217842178521786217872178821789217902179121792217932179421795217962179721798217992180021801218022180321804218052180621807218082180921810218112181221813218142181521816218172181821819218202182121822218232182421825218262182721828218292183021831218322183321834218352183621837218382183921840218412184221843218442184521846218472184821849218502185121852218532185421855218562185721858218592186021861218622186321864218652186621867218682186921870218712187221873218742187521876218772187821879218802188121882218832188421885218862188721888218892189021891218922189321894218952189621897218982189921900219012190221903219042190521906219072190821909219102191121912219132191421915219162191721918219192192021921219222192321924219252192621927219282192921930219312193221933219342193521936219372193821939219402194121942219432194421945219462194721948219492195021951219522195321954219552195621957219582195921960219612196221963219642196521966219672196821969219702197121972219732197421975219762197721978219792198021981219822198321984
  1. #!/usr/bin/env perl
  2. # $Id: imapsync,v 2.229 2022/09/14 18:08:24 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.229 $
  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 recursive imap
  34. transfers from one mailbox to another. If you don't understand the
  35. previous sentence, it's normal, it's pedantic computer-oriented
  36. jargon.
  37. All folders are transferred, recursively, meaning the whole folder
  38. hierarchy is taken, all messages in them, and all message flags (\Seen
  39. \Answered \Flagged etc.) are synced too.
  40. Imapsync reduces the amount of data transferred by not transferring a
  41. given message if it already resides on the destination side. Messages
  42. that are on the destination side but not on the source side stay as
  43. they are. See the --delete2 option to have strict sync and delete
  44. them.
  45. How does imapsync know a message is already on both sides? Same
  46. specific headers and the transfer is done only once. By default, the
  47. identification headers are "Message-Id:" and "Received:" lines but
  48. this choice can be changed with the --useheader option, most often a
  49. duplicate problem is solved by using --useheader "Message-Id"
  50. All flags are preserved, unread messages will stay unread, read ones
  51. will stay read, deleted will stay deleted. In the IMAP protocol, a
  52. deleted message is not deleted, it is marked \Deleted and can be
  53. undeleted. Real destruction comes with the EXPUNGE or UIDEXPUNGE IMAP
  54. commands.
  55. You can abort the transfer at any time and restart it later, imapsync
  56. works well with bad connections and interruptions, by design. On a
  57. terminal hit Ctr-c twice within two seconds to abort the program. Hit
  58. Ctr-c just once makes imapsync reconnect to both imap servers.
  59. How do you know the sync is finished and well done?
  60. When imapsync ends by itself it mentions it with lines like those:
  61. Exiting with return value 0 (EX_OK: successful termination) 0/50 nb_errors/max_errors PID 301
  62. Removing pidfile /tmp/imapsync.pid
  63. 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 )
  64. If you don't have those lines it means that either the sync process is
  65. still running (or eventually hanging indefinitely) or that it ended
  66. without a whisper, a strong kill -9 on Linux for example.
  67. If you have those final lines then it means the sync process is properly
  68. finished. It may have encountered problems though.
  69. A good synchronization is mentioned by some lines above the last ones,
  70. especially those three lines:
  71. The sync looks good, all 1745 identified messages in host1 are on host2.
  72. There is no unidentified message on host1.
  73. Detected 0 errors
  74. Imapsync mentions the total sizes of both accounts at the beginning of
  75. the sync and also at the end. Sometimes, even with a strict sync,
  76. those total sizes differ, and sometimes they differ a lot. The
  77. difference is not a good criterion to conclude the sync went wrong.
  78. Why? That's because message sizes given by the imap servers are not
  79. always accurate, they are not always the same as the actual message
  80. sizes of the messages transferred by imapsync. Imapsync use the sizes
  81. given by the imap servers to calculate the big total size. They can
  82. differ. In the early days, Imapsync used the sizes of the messages as
  83. one of the criteria to identify the messages, different sizes implied
  84. different messages; but it was a mistake, the same message had
  85. different sizes on both sides sometimes, depending on the imap
  86. servers.
  87. Another explanation for a big total size difference is that Gmail
  88. doesn't count the size of duplicate messages across folders twice,
  89. while imapsync does.
  90. A classical scenario is synchronizing a mailbox B from another mailbox
  91. A where you just want to keep a strict copy of A in B. Strict meaning
  92. all messages in A will be in B but no more.
  93. For a strict synchronization, use the option --delete2. The option
  94. --delete2 deletes the messages in the host2 folder B that are not in
  95. the host1 folder A. If you also need to destroy host2 folders that are
  96. not in host1 then use --delete2folders. See also --delete2foldersonly
  97. and --delete2foldersbutnot to set up exceptions on folders to
  98. destroy. INBOX will never be destroyed, it's a mandatory folder in
  99. IMAP so imapsync doesn't even try to remove it.
  100. A different scenario is to delete the messages from the source mailbox
  101. after a successful transfer, it can be a good feature when migrating
  102. mailboxes since messages will be only on one side. The source account
  103. will only have messages that are not on the destination yet, ie,
  104. messages that arrived after a sync or that failed to be transferred.
  105. In that case, use the --delete1 option. Option --delete1 implies also
  106. the option --expunge1 so all messages marked deleted on host1 will be
  107. deleted. In IMAP protocol deleting a message does not delete it, it
  108. marks it with the flag \Deleted, allowing an undelete. Expunging a
  109. folder removes, definitively, all the messages marked as \Deleted in
  110. this folder.
  111. You can also decide to remove empty folders once all of their messages
  112. have been transferred. Add --delete1emptyfolders to obtain this
  113. behavior.
  114. Imapsync is not adequate for maintaining two active imap accounts in
  115. synchronization when the user plays independently on both sides. Use
  116. offlineimap (written by John Goerzen) or mbsync (written by Michael
  117. R. Elkins) for a 2 ways synchronization.
  118. =head1 OPTIONS
  119. usage: imapsync [options]
  120. The standard options are the six values forming the credentials.
  121. Three values on each side are needed to login into the IMAP
  122. servers. These six values are a hostname, a username, and a password,
  123. two times.
  124. Here are the conventions used in the following descriptions of the
  125. options:
  126. str means a string
  127. int means an integer number
  128. flo means a float number
  129. reg means a regular expression
  130. cmd means a command
  131. --dry : Makes imapsync do nothing for real; it just prints what
  132. would be done without --dry.
  133. =head2 OPTIONS/credentials
  134. --host1 str : Source or "from" imap server.
  135. --port1 int : Port to connect on host1.
  136. Optional since default ports are the
  137. well known ports imap/143 or imaps/993.
  138. --user1 str : User to login on host1.
  139. --password1 str : Password of user1.
  140. --host2 str : "destination" imap server.
  141. --port2 int : Port to connect on host2. Optional
  142. --user2 str : User to login on host2.
  143. --password2 str : Password of user2.
  144. --showpasswords : Shows passwords on output instead of "MASKED".
  145. Useful to restart a complete run by just reading
  146. the command line used in the log,
  147. or to debug passwords.
  148. It's not a secure practice at all!
  149. --passfile1 str : Password file for the user1. It must contain the
  150. password on the first line. This option avoids showing
  151. the password on the command line like --password1 does.
  152. --passfile2 str : Password file for the user2.
  153. You can also pass the passwords in the environment variables
  154. IMAPSYNC_PASSWORD1 and IMAPSYNC_PASSWORD2. If you don't pass
  155. the user1 password via --password1 nor --passfile1 nor $IMAPSYNC_PASSWORD1
  156. then imapsync will prompt to enter the password on the terminal.
  157. Same thing for user2 password.
  158. =head2 OPTIONS/encryption
  159. --nossl1 : Do not use a SSL connection on host1.
  160. --ssl1 : Use a SSL connection on host1. On by default if possible.
  161. --nossl2 : Do not use a SSL connection on host2.
  162. --ssl2 : Use a SSL connection on host2. On by default if possible.
  163. --notls1 : Do not use a TLS connection on host1.
  164. --tls1 : Use a TLS connection on host1. On by default if possible.
  165. --notls2 : Do not use a TLS connection on host2.
  166. --tls2 : Use a TLS connection on host2. On by default if possible.
  167. --debugssl int : SSL debug mode from 0 to 4.
  168. --sslargs1 str : Pass any ssl parameter for host1 ssl or tls connection. Example:
  169. --sslargs1 SSL_verify_mode=1 --sslargs1 SSL_version=SSLv3
  170. See all possibilities in the new() method of IO::Socket::SSL
  171. http://search.cpan.org/perldoc?IO::Socket::SSL#Description_Of_Methods
  172. --sslargs2 str : Pass any ssl parameter for host2 ssl or tls connection.
  173. See --sslargs1
  174. =head2 OPTIONS/authentication
  175. --authmech1 str : Auth mechanism to use with host1:
  176. PLAIN, LOGIN, CRAM-MD5 etc. Use UPPERCASE.
  177. --authmech2 str : Auth mechanism to use with host2. See --authmech1
  178. --authuser1 str : User to auth with on host1 (admin user).
  179. Avoid using --authmech1 SOMETHING with --authuser1.
  180. --authuser2 str : User to auth with on host2 (admin user).
  181. --proxyauth1 : Use proxyauth on host1. Requires --authuser1.
  182. Required by Sun/iPlanet/Netscape IMAP servers to
  183. be able to use an administrative user.
  184. --proxyauth2 : Use proxyauth on host2. Requires --authuser2.
  185. --authmd51 : Use MD5 authentication for host1.
  186. --authmd52 : Use MD5 authentication for host2.
  187. --domain1 str : Domain on host1 (NTLM authentication).
  188. --domain2 str : Domain on host2 (NTLM authentication).
  189. --oauthaccesstoken1 str : The access token to authenticate with OAUTH2.
  190. It will be combined with the --user1 value to form the
  191. string to pass with XOAUTH2 authentication.
  192. The password given by --password1 or --passfile1
  193. is ignored but needed on the command line.
  194. Instead of the access token itself, the value can be a
  195. file containing the access token on the first line.
  196. If the value is a file, imapsync reads its first line
  197. and take this line as the access token. The advantage
  198. of the file is that if the access token changes then
  199. imapsync can read it again when it needs to reconnect
  200. during a run.
  201. --oauthaccesstoken2 str : same thing as --oauthaccesstoken1
  202. --oauthdirect1 str : The direct string to pass with XOAUTH2 authentication.
  203. The password given by --password1 or --passfile1 and
  204. the user given by --user1 are ignored but they are
  205. needed to be on the command line. Consider it a bug.
  206. --oauthdirect2 str : same thing as oauthdirect1
  207. =head2 OPTIONS/folders
  208. --folder str : Sync this folder.
  209. --folder str : and this one, etc.
  210. --folderrec str : Sync this folder recursively.
  211. --folderrec str : and this one, etc.
  212. --folderfirst str : Sync this folder first. Ex. --folderfirst "INBOX"
  213. --folderfirst str : then this one, etc.
  214. --folderlast str : Sync this folder last. --folderlast "[Gmail]/All Mail"
  215. --folderlast str : then this one, etc.
  216. --nomixfolders : Do not merge folders when host1 is case-sensitive
  217. while host2 is not (like Exchange). Only the first
  218. similar folder is synced. Example: with folders
  219. "Sent", "SENT" and "sent" on host1, only "Sent"
  220. will be synced to host2.
  221. --skipemptyfolders : Empty host1 folders are not created on host2.
  222. --include reg : Sync folders matching this regular expression
  223. --include reg : or this one, etc.
  224. If both --include --exclude options are used, then
  225. include is done before.
  226. --exclude reg : Skips folders matching this regular expression
  227. Several folders to avoid:
  228. --exclude 'fold1|fold2|f3' skips fold1, fold2 and f3.
  229. --exclude reg : or this one, etc.
  230. --automap : guesses folders mapping, for folders well known as
  231. "Sent", "Junk", "Drafts", "All", "Archive", "Flagged".
  232. --f1f2 str1=str2 : Force folder str1 to be synced to str2,
  233. --f1f2 overrides --automap and --regextrans2.
  234. Use several --f1f2 options to map several folders.
  235. Option --f1f2 is a one to one only folder mapping,
  236. str1 and str2 have to be full path folder names.
  237. --subfolder2 str : Syncs the whole host1 folders hierarchy under the
  238. host2 folder named str.
  239. It does it internally by adding three
  240. --regextrans2 options before all others.
  241. Add --debug to see what's really going on.
  242. --subfolder1 str : Syncs the host1 folders hierarchy which is under folder
  243. str to the root hierarchy of host2.
  244. It's the couterpart of a sync done by --subfolder2
  245. when doing it in the reverse order.
  246. Backup/Restore scenario:
  247. Use --subfolder2 str for a backup to the folder str
  248. on host2. Then use --subfolder1 str for restoring
  249. from the folder str, after inverting
  250. host1/host2 user1/user2 values.
  251. --subscribed : Transfers subscribed folders.
  252. --subscribe : Subscribe to the folders transferred on the
  253. host2 that are subscribed on host1. On by default.
  254. --subscribeall : Subscribe to the folders transferred on the
  255. host2 even if they are not subscribed on host1.
  256. --prefix1 str : Remove prefix str to all destination folders,
  257. usually "INBOX." or "INBOX/" or an empty string "".
  258. imapsync guesses the prefix if host1 imap server
  259. does not have NAMESPACE capability. So this option
  260. should not be used most of the time.
  261. --prefix2 str : Add prefix to all host2 folders. See --prefix1
  262. --sep1 str : Host1 separator. This option should not be used
  263. most of the time.
  264. Imapsync gets the separator from the server itself,
  265. by using NAMESPACE, or it tries to guess it
  266. from the folders listing (it counts
  267. characters / . \\ \ in folder names and choose the
  268. more frequent, or finally / if nothing is found.
  269. --sep2 str : Host2 separator. See --sep1
  270. --regextrans2 reg : Apply the whole regex to each destination folders.
  271. --regextrans2 reg : and this one. etc.
  272. When you play with the --regextrans2 option, first
  273. add also the safe options --dry --justfolders
  274. Then, when happy, remove --dry for a run, then
  275. remove --justfolders for the next ones.
  276. Have in mind that --regextrans2 is applied after
  277. the automatic prefix and separator inversion.
  278. For examples see:
  279. https://imapsync.lamiral.info/FAQ.d/FAQ.Folders_Mapping.txt
  280. =head2 OPTIONS/folders sizes
  281. --nofoldersizes : Do not calculate the size of each folder at the
  282. beginning of the sync. Default is to calculate them.
  283. --nofoldersizesatend: Do not calculate the size of each folder at the
  284. end of the sync. Default is to calculate them.
  285. --justfoldersizes : Exit after having printed the initial folder sizes.
  286. =head2 OPTIONS/tmp
  287. --tmpdir str : Where to store temporary files and subdirectories.
  288. Will be created if it doesn't exist.
  289. Default is system specific, Unix is /tmp but
  290. /tmp is often too small and deleted at reboot.
  291. --tmpdir /var/tmp should be better.
  292. --pidfile str : The file where imapsync pid is written,
  293. it can be dirname/filename complete path.
  294. The default name is imapsync.pid in tmpdir.
  295. --pidfilelocking : Abort if pidfile already exists. Useful to avoid
  296. concurrent transfers on the same mailbox.
  297. =head2 OPTIONS/log
  298. --nolog : Turn off logging on file
  299. --logfile str : Change the default log filename (can be dirname/filename).
  300. --logdir str : Change the default log directory. Default is LOG_imapsync/
  301. The default logfile name is for example
  302. LOG_imapsync/2019_12_22_23_57_59_532_user1_user2.txt
  303. where:
  304. 2019_12_22_23_57_59_532 is nearly the date of the start
  305. YYYY_MM_DD_HH_MM_SS_mmm
  306. year_month_day_hour_minute_second_millisecond
  307. and user1 user2 are the --user1 --user2 values.
  308. =head2 OPTIONS/messages
  309. --skipmess reg : Skips messages matching the regex.
  310. Example: 'm/[\x80-\xff]/' # to avoid 8bits messages.
  311. --skipmess is applied before --regexmess
  312. --skipmess reg : or this one, etc.
  313. --skipcrossduplicates : Avoid copying messages that are already copied
  314. in another folder, good from Gmail to XYZ when
  315. XYZ is not also Gmail.
  316. Activated with --gmail1 unless --noskipcrossduplicates
  317. --debugcrossduplicates : Prints which messages (UIDs) are skipped with
  318. --skipcrossduplicates and in what other folders
  319. they are.
  320. --pipemess cmd : Apply this cmd command to each message content
  321. before the copy.
  322. --pipemess cmd : and this one, etc.
  323. With several --pipemess, the output of each cmd
  324. command (STDOUT) is given to the input (STDIN)
  325. of the next command.
  326. For example,
  327. --pipemess cmd1 --pipemess cmd2 --pipemess cmd3
  328. is like a Unix pipe:
  329. "cat message | cmd1 | cmd2 | cmd3"
  330. --disarmreadreceipts : Disarms read receipts (host2 Exchange issue)
  331. --regexmess reg : Apply the whole regex to each message before transfer.
  332. Example: 's/\000/ /g' # to replace null characters
  333. by spaces.
  334. --regexmess reg : and this one, etc.
  335. --truncmess int : truncates messages when their size exceed the int
  336. value, specified in bytes. Good to sync too big
  337. messages or to "suppress" attachments.
  338. Have in mind that this way, messages become
  339. uncoherent somehow.
  340. =head2 OPTIONS/labels
  341. Gmail present labels as folders in imap. Imapsync can accelerate the sync
  342. by syncing X-GM-LABELS, it will avoid to transfer messages when they are
  343. already on host2 in another folder.
  344. --synclabels : Syncs also Gmail labels when a message is copied to host2.
  345. Activated by default with --gmail1 --gmail2 unless
  346. --nosynclabels is added.
  347. --resynclabels : Resyncs Gmail labels when a message is already on host2.
  348. Activated by default with --gmail1 --gmail2 unless
  349. --noresynclabels is added.
  350. For Gmail syncs, see also:
  351. https://imapsync.lamiral.info/FAQ.d/FAQ.Gmail.txt
  352. =head2 OPTIONS/flags
  353. If you encounter flag problems see also:
  354. https://imapsync.lamiral.info/FAQ.d/FAQ.Flags.txt
  355. --regexflag reg : Apply the whole regex to each flags list.
  356. Example: 's/"Junk"//g' # to remove "Junk" flag.
  357. --regexflag reg : then this one, etc.
  358. --resyncflags : Resync flags for already transferred messages.
  359. On by default.
  360. --noresyncflags : Do not resync flags for already transferred messages.
  361. May be useful when a user has already started to play
  362. with its host2 account.
  363. --filterbuggyflags : Filter flags known to be buggy and generators of errors
  364. "BAD Invalid system flag" or "NO APPEND Invalid flag list".
  365. =head2 OPTIONS/deletions
  366. --delete1 : Deletes messages on host1 server after a successful
  367. transfer. Option --delete1 has the following behavior:
  368. it marks messages as deleted with the IMAP flag
  369. \Deleted, then messages are really deleted with an
  370. EXPUNGE IMAP command. If expunging after each message
  371. slows down too much the sync then use
  372. --noexpungeaftereach to speed up, expunging will then be
  373. done only twice per folder, one at the beginning and
  374. one at the end of a folder sync.
  375. --expunge1 : Expunge messages on host1 just before syncing a folder.
  376. Expunge is done per folder.
  377. Expunge aims is to really delete messages marked deleted.
  378. An expunge is also done after each message copied
  379. if option --delete1 is set (unless --noexpungeaftereach).
  380. --noexpunge1 : Do not expunge messages on host1.
  381. --delete1emptyfolders : Deletes empty folders on host1, INBOX excepted.
  382. Useful with --delete1 since what remains on host1
  383. is only what failed to be synced.
  384. --delete2 : Delete messages in the host2 account that are not in
  385. the host1 account. Useful for backup or pre-sync.
  386. --delete2 implies --uidexpunge2
  387. --delete2duplicates : Deletes messages in host2 that are duplicates in host2.
  388. Works only without --useuid since duplicates are
  389. detected with an header part of each message.
  390. NB: --delete2duplicates is far less violent than --delete2
  391. since it removes only duplicates.
  392. --delete2folders : Delete folders in host2 that are not in host1.
  393. For safety, first try it like this, it is safe:
  394. --delete2folders --dry --justfolders --nofoldersizes
  395. and see what folders will be deleted.
  396. --delete2foldersonly reg : Delete only folders matching the regex reg.
  397. Example: --delete2foldersonly "/^Junk$|^INBOX.Junk$/"
  398. This option activates --delete2folders
  399. --delete2foldersbutnot reg : Do not delete folders matching the regex rex.
  400. Example: --delete2foldersbutnot "/Tasks$|Contacts$|Foo$/"
  401. This option activates --delete2folders
  402. --noexpunge2 : Do not expunge messages on host2.
  403. --nouidexpunge2 : Do not uidexpunge messages on the host2 account
  404. that are not on the host1 account.
  405. =head2 OPTIONS/dates
  406. If you encounter problems with dates, see also:
  407. https://imapsync.lamiral.info/FAQ.d/FAQ.Dates.txt
  408. --syncinternaldates : Sets the internal dates on host2 as the same as host1.
  409. Turned on by default. Internal date is the date
  410. a message arrived on a host (Unix mtime usually).
  411. --idatefromheader : Sets the internal dates on host2 as same as the
  412. ones in "Date:" headers.
  413. =head2 OPTIONS/message selection
  414. --maxsize int : Skip messages larger (or equal) than int bytes
  415. --minsize int : Skip messages smaller (or equal) than int bytes
  416. --maxage int : Skip messages older than int days.
  417. final stats (skipped) don't count older messages
  418. see also --minage
  419. --minage int : Skip messages newer than int days.
  420. final stats (skipped) don't count newer messages
  421. You can do (+ zone are the messages selected):
  422. past|----maxage+++++++++++++++>now
  423. past|+++++++++++++++minage---->now
  424. past|----maxage+++++minage---->now (intersection)
  425. past|++++minage-----maxage++++>now (union)
  426. --search str : Selects only messages returned by this IMAP SEARCH
  427. command. Applied on both sides.
  428. For a complete set of what can be search see
  429. https://imapsync.lamiral.info/FAQ.d/FAQ.Messages_Selection.txt
  430. --search1 str : Same as --search but for selecting host1 messages only.
  431. --search2 str : Same as --search but for selecting host2 messages only.
  432. So --search CRIT equals --search1 CRIT --search2 CRIT
  433. --noabletosearch : Makes --minage and --maxage options use the internal
  434. dates given by a FETCH imap command instead of the
  435. "Date:" header. Internal date is the arrival date
  436. in the mailbox.
  437. --noabletosearch equals --noabletosearch1 --noabletosearch2
  438. --noabletosearch1 : Like --noabletosearch but for host1 only.
  439. --noabletosearch2 : Like --noabletosearch but for host2 only.
  440. --maxlinelength int : skip messages with a line length longer than int bytes.
  441. RFC 2822 says it must be no more than 1000 bytes but
  442. real life servers and email clients do more.
  443. --useheader str : Use this header to compare messages on both sides.
  444. Example: "Message-Id" or "Received" or "Date".
  445. --useheader str and this one, etc.
  446. --syncduplicates : Sync also duplicates. Off by default.
  447. --usecache : Use cache to speed up next syncs. Off by default.
  448. --nousecache : Do not use cache. Caveat: --useuid --nousecache creates
  449. duplicates on multiple runs.
  450. --useuid : Use UIDs instead of headers as a criterion to recognize
  451. messages. Option --usecache is then implied unless
  452. --nousecache is used.
  453. =head2 OPTIONS/miscellaneous
  454. --syncacls : Synchronizes acls (Access Control Lists).
  455. Acls in IMAP are not standardized, be careful
  456. since one acl code on one side may signify something
  457. else on the other one.
  458. --nosyncacls : Does not synchronize acls. This is the default.
  459. --addheader : When a message has no headers to be identified,
  460. --addheader adds a "Message-Id" header,
  461. like "Message-Id: 12345@imapsync", where 12345
  462. is the imap UID of the message on the host1 folder.
  463. Useful to sync folders "Sent" or "Draft".
  464. =head2 OPTIONS/debugging
  465. --debug : Debug mode.
  466. --debugfolders : Debug mode for the folders part only.
  467. --debugcontent : Debug content of the messages transferred. Huge output.
  468. --debugflags : Debug mode for flags.
  469. --debugimap1 : IMAP debug mode for host1. Very verbose.
  470. --debugimap2 : IMAP debug mode for host2. Very verbose.
  471. --debugimap : IMAP debug mode for host1 and host2. Twice very verbose.
  472. --debugmemory : Debug mode showing memory consumption after each copy.
  473. --errorsmax int : Exit when int number of errors is reached. Default is 50.
  474. --tests : Run local non-regression tests. Exit code 0 means all ok.
  475. --testslive : Run a live test with test1.lamiral.info imap server.
  476. Useful to check the basics. Needs internet connection.
  477. --testslive6 : Run a live test with ks6ipv6.lamiral.info imap server.
  478. Useful to check the ipv6 connectivity. Needs internet.
  479. =head2 OPTIONS/specific
  480. --gmail1 : sets --host1 to Gmail and other options. See FAQ.Gmail.txt
  481. --gmail2 : sets --host2 to Gmail and other options. See FAQ.Gmail.txt
  482. --office1 : sets --host1 to Office365 and other options. See FAQ.Office365.txt
  483. --office2 : sets --host2 to Office365 and other options. See FAQ.Office365.txt
  484. --exchange1 : sets options for Exchange. See FAQ.Exchange.txt
  485. --exchange2 : sets options for Exchange. See FAQ.Exchange.txt
  486. --domino1 : sets options for Domino. See FAQ.Domino.txt
  487. --domino2 : sets options for Domino. See FAQ.Domino.txt
  488. =head2 OPTIONS/behavior
  489. --timeout1 flo : Connection timeout in seconds for host1.
  490. Default is 120 and 0 means no timeout at all.
  491. --timeout2 flo : Connection timeout in seconds for host2.
  492. Default is 120 and 0 means no timeout at all.
  493. Caveat, under CGI context, you may encounter a timeout
  494. from the webserver, killing imapsync and the imap connections.
  495. See the document INSTALL.OnlineUI.txt and search
  496. for "Timeout" for how to deal with this issue.
  497. --keepalive1 : https://metacpan.org/pod/Mail::IMAPClient#Keepalive
  498. Some firewalls and network gears like to timeout connections
  499. prematurely if the connection sits idle.
  500. This option enables SO_KEEPALIVE on the host1 socket.
  501. --keepalive1 is on by default since imapsync release 2.169
  502. Use --nokeepalive1 to disable it.
  503. --keepalive2 : Same as --keepalive2 but for host2.
  504. Use --nokeepalive2 to disable it.
  505. --maxmessagespersecond flo : limits the average number of messages
  506. transferred per second.
  507. --maxbytespersecond int : limits the average transfer rate per second.
  508. --maxbytesafter int : starts --maxbytespersecond limitation only after
  509. --maxbytesafter amount of data transferred.
  510. --maxsleep flo : do not sleep more than int seconds.
  511. On by default, 2 seconds max, like --maxsleep 2
  512. --abort : terminates a previous call still running.
  513. It uses the pidfile to know what process to abort.
  514. --exitwhenover int : Stop syncing and exits when int total bytes
  515. transferred is reached.
  516. --version : Print only the software version.
  517. --noreleasecheck : Do not check for any new imapsync release.
  518. --releasecheck : Check for new imapsync release.
  519. it's an http request to
  520. http://imapsync.lamiral.info/prj/imapsync/VERSION
  521. --emailreport1 : Put the email final report in host1 INBOX
  522. --emailreport2 : Put the email final report in host2 INBOX
  523. --noemailreport1 : Do not put the email final report in host1 INBOX
  524. --noemailreport2 : Do not put the email final report in host2 INBOX
  525. --noid : Do not send/receive IMAP "ID" command to imap servers.
  526. --justconnect : Just connect to both servers and print useful
  527. information. Need only --host1 and --host2 options.
  528. Obsolete since "imapsync --host1 imaphost" alone
  529. implies --justconnect
  530. --justlogin : Just login to both host1 and host2 with users
  531. credentials, then exit.
  532. --justfolders : Do only things about folders (ignore messages).
  533. --help : print this help.
  534. Example: to synchronize imap account "test1" on "test1.lamiral.info"
  535. to imap account "test2" on "test2.lamiral.info"
  536. with test1 password "secret1"
  537. and test2 password "secret2"
  538. imapsync \
  539. --host1 test1.lamiral.info --user1 test1 --password1 secret1 \
  540. --host2 test2.lamiral.info --user2 test2 --password2 secret2
  541. =cut
  542. # comment
  543. =pod
  544. =head1 SECURITY
  545. You can use --passfile1 instead of --password1 to mention the password
  546. since it is safer. With --password1 option, on Linux, any user on your
  547. host can see the password by using the 'ps auxwwww' command. Using a
  548. variable (like IMAPSYNC_PASSWORD1) is also dangerous because of the
  549. 'ps auxwwwwe' command. So, saving the password in a well protected
  550. file (600 or rw-------) is the best solution.
  551. Imapsync activates ssl or tls encryption by default, if possible.
  552. What detailed behavior is under this "if possible"?
  553. Imapsync activates ssl if the well known port imaps port (993) is open
  554. on the imap servers. If the imaps port is closed then it open a normal
  555. (clear) connection on port 143 but it looks for TLS support in the
  556. CAPABILITY list of the servers. If TLS is supported then imapsync goes
  557. to encryption with STARTTLS.
  558. If the automatic ssl and the tls detections fail then imapsync will
  559. not protect against sniffing activities on the network, especially for
  560. passwords.
  561. If you want to force ssl or tls just use --ssl1 --ssl2 or --tls1
  562. --tls2
  563. See also the document FAQ.Security.txt in the FAQ.d/ directory or at
  564. https://imapsync.lamiral.info/FAQ.d/FAQ.Security.txt
  565. =head1 EXIT STATUS
  566. Imapsync will exit with a 0 status (return code) if everything went
  567. good. Otherwise, it exits with a non-zero status. That's classical
  568. Unix behavior. Here is the list of the exit code values (an integer
  569. between 0 and 255). In Bourne Shells, this exit code value can be
  570. retrieved within the variable value "$?" if you read it just after the
  571. imapsync call.
  572. The names reflect their meaning:
  573. =for comment
  574. egrep '^Readonly my.*\$EX' imapsync | egrep -o 'EX.*' | sed 's_^_ _'
  575. EX_OK => 0 ; #/* successful termination */
  576. EX_USAGE => 64 ; #/* command line usage error */
  577. EX_NOINPUT => 66 ; #/* cannot open input */
  578. EX_UNAVAILABLE => 69 ; #/* service unavailable */
  579. EX_SOFTWARE => 70 ; #/* internal software error */
  580. EXIT_CATCH_ALL => 1 ; # Any other error
  581. EXIT_BY_SIGNAL => 6 ; # Should be 128+n where n is the sig_num
  582. EXIT_BY_FILE => 7 ;
  583. EXIT_PID_FILE_ERROR => 8 ;
  584. EXIT_CONNECTION_FAILURE => 10 ;
  585. EXIT_TLS_FAILURE => 12 ;
  586. EXIT_AUTHENTICATION_FAILURE => 16 ;
  587. EXIT_SUBFOLDER1_NO_EXISTS => 21 ;
  588. EXIT_WITH_ERRORS => 111 ;
  589. EXIT_WITH_ERRORS_MAX => 112 ;
  590. EXIT_OVERQUOTA => 113 ;
  591. EXIT_ERR_APPEND => 114 ;
  592. EXIT_ERR_FETCH => 115 ;
  593. EXIT_ERR_CREATE => 116 ;
  594. EXIT_ERR_SELECT => 117 ;
  595. EXIT_TRANSFER_EXCEEDED => 118 ;
  596. EXIT_ERR_APPEND_VIRUS => 119 ;
  597. EXIT_TESTS_FAILED => 254 ; # Like Test::More API
  598. EXIT_CONNECTION_FAILURE_HOST1 => 101 ;
  599. EXIT_CONNECTION_FAILURE_HOST2 => 102 ;
  600. EXIT_AUTHENTICATION_FAILURE_USER1 => 161 ;
  601. EXIT_AUTHENTICATION_FAILURE_USER2 => 162 ;
  602. =head1 LICENSE AND COPYRIGHT
  603. Imapsync is free, open, public but not always gratis software cover by
  604. the NOLIMIT Public License, now called NLPL. See the LICENSE file
  605. included in the distribution or just read the following simple
  606. sentence as it IS the license text:
  607. "No limits to do anything with this work and this license."
  608. In case it is not long enough, I repeat:
  609. "No limits to do anything with this work and this license."
  610. Look at https://imapsync.lamiral.info/LICENSE
  611. =head1 AUTHOR
  612. Gilles LAMIRAL <gilles@lamiral.info>
  613. Good feedback is always welcome.
  614. Bad feedback is very often welcome.
  615. Gilles LAMIRAL earns his living by writing, installing, configuring
  616. and sometimes teaching free, open, and often gratis software. Imapsync
  617. used to be "always gratis" but now it is only "often gratis" because
  618. imapsync is sold by its author, your servitor, a good way to maintain
  619. and support free open public software tools over decades.
  620. =head1 BUGS AND LIMITATIONS
  621. See https://imapsync.lamiral.info/FAQ.d/FAQ.Reporting_Bugs.txt
  622. =head1 IMAP SERVERS supported
  623. See https://imapsync.lamiral.info/S/imapservers.shtml
  624. =head1 HUGE MIGRATION
  625. If you have many mailboxes to migrate think about a little shell
  626. program. Write a file called file.txt (for example) containing users
  627. and passwords. The separator used in this example is ';'
  628. The file.txt file contains:
  629. user001_1;password001_1;user001_2;password001_2
  630. user002_1;password002_1;user002_2;password002_2
  631. user003_1;password003_1;user003_2;password003_2
  632. user004_1;password004_1;user004_2;password004_2
  633. user005_1;password005_1;user005_2;password005_2
  634. ...
  635. On Unix the shell program can be:
  636. { while IFS=';' read u1 p1 u2 p2; do
  637. imapsync --host1 imap.side1.org --user1 "$u1" --password1 "$p1" \
  638. --host2 imap.side2.org --user2 "$u2" --password2 "$p2" ...
  639. done ; } < file.txt
  640. On Windows the batch program can be:
  641. FOR /F "tokens=1,2,3,4 delims=; eol=#" %%G IN (file.txt) DO imapsync ^
  642. --host1 imap.side1.org --user1 %%G --password1 %%H ^
  643. --host2 imap.side2.org --user2 %%I --password2 %%J ...
  644. The ... have to be replaced by nothing or any imapsync option.
  645. Welcome in shell or batch programming !
  646. You will find already written scripts at
  647. https://imapsync.lamiral.info/examples/
  648. =head1 INSTALL
  649. Imapsync works under any Unix with Perl.
  650. Imapsync works under most Windows (2000, XP, Vista, Seven, Eight, Ten
  651. and all Server releases 2000, 2003, 2008 and R2, 2012 and R2, 2016)
  652. as a standalone binary software called imapsync.exe, usually launched
  653. from a batch file in order to avoid always typing the options. There
  654. is also a 32bit binary called imapsync_32bit.exe
  655. Imapsync works under OS X as a standalone binary software called
  656. imapsync_bin_Darwin
  657. Purchase latest imapsync at
  658. https://imapsync.lamiral.info/
  659. You'll receive a link to a compressed tarball called
  660. imapsync-x.xx.tgz where x.xx is the version number.
  661. Untar the tarball where you want (on Unix):
  662. tar xzvf imapsync-x.xx.tgz
  663. Go into the directory imapsync-x.xx and read the INSTALL file.
  664. As mentioned at https://imapsync.lamiral.info/#install
  665. the INSTALL file can also be found at
  666. https://imapsync.lamiral.info/INSTALL.d/INSTALL.ANY.txt
  667. It is now split in several files for each system
  668. https://imapsync.lamiral.info/INSTALL.d/
  669. =head1 CONFIGURATION
  670. There is no specific configuration file for imapsync, everything is
  671. specified by the command line parameters and the default behavior.
  672. =head1 HACKING
  673. Feel free to hack imapsync as the NOLIMIT license permits it.
  674. =head1 SIMILAR SOFTWARE
  675. See also https://imapsync.lamiral.info/S/external.shtml
  676. for a better up to date list.
  677. List verified on Friday July 1, 2021.
  678. imapsync: https://github.com/imapsync/imapsync (this is an imapsync copy, sometimes delayed, with --noreleasecheck by default since release 1.592, 2014/05/22)
  679. 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
  680. imaputils: https://github.com/mtsatsenko/imaputils (very old imap_tools fork)
  681. Doveadm-Sync: https://wiki2.dovecot.org/Tools/Doveadm/Sync ( Dovecot sync tool )
  682. davmail: http://davmail.sourceforge.net/
  683. offlineimap: http://offlineimap.org/
  684. fdm: https://github.com/nicm/fdm
  685. mbsync: http://isync.sourceforge.net/
  686. mailsync: http://mailsync.sourceforge.net/
  687. mailutil: https://www.washington.edu/imap/ part of the UW IMAP toolkit. (well, seems abandoned now)
  688. imaprepl: https://bl0rg.net/software/ http://freecode.com/projects/imap-repl/
  689. imapcopy (Pascal): http://www.ardiehl.de/imapcopy/
  690. imapcopy (Java): https://code.google.com/archive/p/imapcopy/
  691. imapsize: http://www.broobles.com/imapsize/
  692. migrationtool: http://sourceforge.net/projects/migrationtool/
  693. imapmigrate: http://sourceforge.net/projects/cyrus-utils/
  694. larch: https://github.com/rgrove/larch (derived from wonko_imapsync, good at Gmail)
  695. wonko_imapsync: http://wonko.com/article/554 (superseded by larch)
  696. pop2imap: http://www.linux-france.org/prj/pop2imap/ (I wrote that too)
  697. exchange-away: http://exchange-away.sourceforge.net/
  698. SyncBackPro: http://www.2brightsparks.com/syncback/sbpro.html
  699. ImapSyncClient: https://github.com/ridaamirini/ImapSyncClient
  700. MailStore: https://www.mailstore.com/en/products/mailstore-home/
  701. mnIMAPSync: https://github.com/manusa/mnIMAPSync
  702. imap-upload: http://imap-upload.sourceforge.net/ (A tool for uploading a local mbox file to IMAP4 server)
  703. imapbackup: https://github.com/rcarmo/imapbackup (A Python script for incremental backups of IMAP mailboxes)
  704. BitRecover email-backup 99 USD, 299 USD https://www.bitrecover.com/email-backup/.
  705. ImportExportTools: https://addons.thunderbird.net/en-us/thunderbird/addon/importexporttools/ ImportExportTools for Mozilla Thunderbird by Paolo Kaosmos. ImportExportTools does not do IMAP.
  706. rximapmail: https://sourceforge.net/projects/rximapmail/
  707. CodeTwo: https://www.codetwo.com/ but CodeTwo does imap source to Office365 only.
  708. =head1 HISTORY
  709. I initially wrote imapsync in July 2001 because an enterprise, called
  710. BaSystemes, paid me to install a new imap server without losing huge
  711. old mailboxes located in a far away remote imap server, accessible by
  712. an often broken low-bandwidth ISDN link.
  713. I had to verify every mailbox was well transferred, all folders, all
  714. messages, without wasting bandwidth or creating duplicates upon
  715. resyncs. The imapsync design was made with the beautiful rsync command
  716. in mind.
  717. Imapsync started its life as a patch of the copy_folder.pl script. The
  718. script copy_folder.pl comes from the Mail-IMAPClient-2.1.3 perl module
  719. tarball source (more precisely in the examples/ directory of the
  720. Mail-IMAPClient tarball).
  721. So many changes happened since then that I wonder if it remains any
  722. lines of the original copy_folder.pl in imapsync source code.
  723. =cut
  724. # use pragmas
  725. #
  726. use strict ;
  727. use warnings ;
  728. use Carp ;
  729. use Cwd ;
  730. use Compress::Zlib ;
  731. use Data::Dumper ;
  732. use Digest::HMAC_SHA1 qw( hmac_sha1 hmac_sha1_hex ) ;
  733. use Digest::MD5 qw( md5 md5_hex md5_base64 ) ;
  734. use Encode ;
  735. use Encode::IMAPUTF7 ;
  736. use English qw( -no_match_vars ) ;
  737. use Errno qw(EAGAIN EPIPE ECONNRESET) ;
  738. use Fcntl ;
  739. use File::Basename ;
  740. use File::Copy::Recursive ;
  741. use File::Glob qw( :glob ) ;
  742. use File::Path qw( mkpath rmtree ) ;
  743. use File::Spec ;
  744. use File::stat ;
  745. use Getopt::Long ( ) ;
  746. use IO::File ;
  747. use IO::Socket qw( :crlf SOL_SOCKET SO_KEEPALIVE ) ;
  748. use IO::Socket::INET6 ;
  749. use IO::Socket::SSL ;
  750. use IO::Tee ;
  751. use IPC::Open3 'open3' ;
  752. #use locale ;
  753. use Mail::IMAPClient 3.30 ;
  754. use MIME::Base64 ;
  755. use Pod::Usage qw(pod2usage) ;
  756. use POSIX qw( uname SIGALRM :sys_wait_h ) ;
  757. use Sys::Hostname ;
  758. use Term::ReadKey ;
  759. use Test::More ;
  760. use Time::HiRes qw( time sleep ) ;
  761. use Time::Local ;
  762. use Unicode::String ;
  763. use Readonly ;
  764. use Sys::MemInfo ;
  765. use Regexp::Common ;
  766. use Text::ParseWords ; # for quotewords()
  767. use File::Tail ;
  768. local $OUTPUT_AUTOFLUSH = 1 ;
  769. # constants
  770. # Let us do like sysexits.h
  771. # /usr/include/sysexits.h
  772. # and https://www.tldp.org/LDP/abs/html/exitcodes.html
  773. # Should avoid 2 126 127 128..128+64=192 255
  774. # Should use 0 1 3..125 193..254
  775. Readonly my $EX_OK => 0 ; #/* successful termination */
  776. Readonly my $EX_USAGE => 64 ; #/* command line usage error */
  777. #Readonly my $EX_DATAERR => 65 ; #/* data format error */
  778. Readonly my $EX_NOINPUT => 66 ; #/* cannot open input */
  779. #Readonly my $EX_NOUSER => 67 ; #/* addressee unknown */
  780. #Readonly my $EX_NOHOST => 68 ; #/* host name unknown */
  781. Readonly my $EX_UNAVAILABLE => 69 ; #/* service unavailable */
  782. Readonly my $EX_SOFTWARE => 70 ; #/* internal software error */
  783. #Readonly my $EX_OSERR => 71 ; #/* system error (e.g., can't fork) */
  784. #Readonly my $EX_OSFILE => 72 ; #/* critical OS file missing */
  785. #Readonly my $EX_CANTCREAT => 73 ; #/* can't create (user) output file */
  786. #Readonly my $EX_IOERR => 74 ; #/* input/output error */
  787. #Readonly my $EX_TEMPFAIL => 75 ; #/* temp failure; user is invited to retry */
  788. #Readonly my $EX_PROTOCOL => 76 ; #/* remote error in protocol */
  789. #Readonly my $EX_NOPERM => 77 ; #/* permission denied */
  790. #Readonly my $EX_CONFIG => 78 ; #/* configuration error */
  791. # Mine
  792. Readonly my $EXIT_CATCH_ALL => 1 ; # Any other error
  793. Readonly my $EXIT_BY_SIGNAL => 6 ; # Should be 128+n where n is the sig_num
  794. Readonly my $EXIT_BY_FILE => 7 ;
  795. Readonly my $EXIT_PID_FILE_ERROR => 8 ;
  796. Readonly my $EXIT_CONNECTION_FAILURE => 10 ;
  797. Readonly my $EXIT_TLS_FAILURE => 12 ;
  798. Readonly my $EXIT_AUTHENTICATION_FAILURE => 16 ;
  799. Readonly my $EXIT_SUBFOLDER1_NO_EXISTS => 21 ;
  800. Readonly my $EXIT_WITH_ERRORS => 111 ;
  801. Readonly my $EXIT_WITH_ERRORS_MAX => 112 ;
  802. Readonly my $EXIT_OVERQUOTA => 113 ;
  803. Readonly my $EXIT_ERR_APPEND => 114 ;
  804. Readonly my $EXIT_ERR_FETCH => 115 ;
  805. Readonly my $EXIT_ERR_CREATE => 116 ;
  806. Readonly my $EXIT_ERR_SELECT => 117 ;
  807. Readonly my $EXIT_TRANSFER_EXCEEDED => 118 ;
  808. Readonly my $EXIT_ERR_APPEND_VIRUS => 119 ;
  809. Readonly my $EXIT_ERR_FLAGS => 120 ;
  810. Readonly my $EXIT_TESTS_FAILED => 254 ; # Like Test::More API
  811. Readonly my $EXIT_CONNECTION_FAILURE_HOST1 => 101 ;
  812. Readonly my $EXIT_CONNECTION_FAILURE_HOST2 => 102 ;
  813. Readonly my $EXIT_AUTHENTICATION_FAILURE_USER1 => 161 ;
  814. Readonly my $EXIT_AUTHENTICATION_FAILURE_USER2 => 162 ;
  815. Readonly my %EXIT_TXT => (
  816. $EX_OK => 'EX_OK: successful termination',
  817. $EX_USAGE => 'EX_USAGE: command line usage error',
  818. $EX_NOINPUT => 'EX_NOINPUT: cannot open input',
  819. $EX_UNAVAILABLE => 'EX_UNAVAILABLE: service unavailable',
  820. $EX_SOFTWARE => 'EX_SOFTWARE: internal software error',
  821. $EXIT_CATCH_ALL => 'EXIT_CATCH_ALL',
  822. $EXIT_BY_SIGNAL => 'EXIT_BY_SIGNAL',
  823. $EXIT_BY_FILE => 'EXIT_BY_FILE',
  824. $EXIT_PID_FILE_ERROR => 'EXIT_PID_FILE_ERROR' ,
  825. $EXIT_CONNECTION_FAILURE => 'EXIT_CONNECTION_FAILURE',
  826. $EXIT_TLS_FAILURE => 'EXIT_TLS_FAILURE',
  827. $EXIT_AUTHENTICATION_FAILURE => 'EXIT_AUTHENTICATION_FAILURE',
  828. $EXIT_SUBFOLDER1_NO_EXISTS => 'EXIT_SUBFOLDER1_NO_EXISTS',
  829. $EXIT_WITH_ERRORS => 'EXIT_WITH_ERRORS',
  830. $EXIT_WITH_ERRORS_MAX => 'EXIT_WITH_ERRORS_MAX',
  831. $EXIT_OVERQUOTA => 'EXIT_OVERQUOTA',
  832. $EXIT_ERR_APPEND => 'EXIT_ERR_APPEND',
  833. $EXIT_ERR_APPEND_VIRUS => 'EXIT_ERR_APPEND_VIRUS',
  834. $EXIT_ERR_FETCH => 'EXIT_ERR_FETCH',
  835. $EXIT_ERR_FLAGS => 'EXIT_ERR_FLAGS',
  836. $EXIT_ERR_CREATE => 'EXIT_ERR_CREATE',
  837. $EXIT_ERR_SELECT => 'EXIT_ERR_SELECT',
  838. $EXIT_TESTS_FAILED => 'EXIT_TESTS_FAILED',
  839. $EXIT_TRANSFER_EXCEEDED => 'EXIT_TRANSFER_EXCEEDED',
  840. $EXIT_CONNECTION_FAILURE_HOST1 => 'EXIT_CONNECTION_FAILURE_HOST1',
  841. $EXIT_CONNECTION_FAILURE_HOST2 => 'EXIT_CONNECTION_FAILURE_HOST2',
  842. $EXIT_AUTHENTICATION_FAILURE_USER1 => 'EXIT_AUTHENTICATION_FAILURE_USER1',
  843. $EXIT_AUTHENTICATION_FAILURE_USER2 => 'EXIT_AUTHENTICATION_FAILURE_USER2',
  844. ) ;
  845. Readonly my %EXIT_VALUE_OF_ERR_TYPE => (
  846. ERR_APPEND_SIZE => $EXIT_ERR_APPEND,
  847. ERR_OVERQUOTA => $EXIT_OVERQUOTA,
  848. ERR_APPEND => $EXIT_ERR_APPEND,
  849. ERR_APPEND_VIRUS => $EXIT_ERR_APPEND_VIRUS,
  850. ERR_CREATE => $EXIT_ERR_CREATE,
  851. ERR_SELECT => $EXIT_ERR_SELECT,
  852. ERR_Host1_FETCH => $EXIT_ERR_FETCH,
  853. ERR_FLAGS => $EXIT_ERR_FLAGS,
  854. ERR_UNCLASSIFIED => $EXIT_WITH_ERRORS,
  855. ERR_NOTHING_REPORTED => $EXIT_WITH_ERRORS,
  856. ERR_TRANSFER_EXCEEDED => $EXIT_TRANSFER_EXCEEDED,
  857. ERR_CONNECTION_FAILURE_HOST1 => $EXIT_CONNECTION_FAILURE_HOST1,
  858. ERR_CONNECTION_FAILURE_HOST2 => $EXIT_CONNECTION_FAILURE_HOST2,
  859. ERR_AUTHENTICATION_FAILURE_USER1 => $EXIT_AUTHENTICATION_FAILURE_USER1,
  860. ERR_AUTHENTICATION_FAILURE_USER2 => $EXIT_AUTHENTICATION_FAILURE_USER2,
  861. ERR_EXIT_TLS_FAILURE => $EXIT_TLS_FAILURE,
  862. ) ;
  863. Readonly my %COMMENT_OF_ERR_TYPE => (
  864. ERR_APPEND_SIZE => \&comment_err_append_size,
  865. ERR_OVERQUOTA => \&comment_err_overquota,
  866. ERR_APPEND => \&comment_err_blank,
  867. ERR_APPEND_VIRUS => \&comment_err_blank,
  868. ERR_CREATE => \&comment_err_blank,
  869. ERR_SELECT => \&comment_err_blank,
  870. ERR_Host1_FETCH => \&comment_err_blank,
  871. ERR_FLAGS => \&comment_err_flags,
  872. ERR_UNCLASSIFIED => \&comment_err_blank,
  873. ERR_NOTHING_REPORTED => \&comment_err_blank,
  874. ERR_TRANSFER_EXCEEDED => \&comment_err_transfer_exceeded,
  875. ERR_CONNECTION_FAILURE_HOST1 => \&comment_err_connection_failure_host1,
  876. ERR_CONNECTION_FAILURE_HOST2 => \&comment_err_connection_failure_host2,
  877. ERR_AUTHENTICATION_FAILURE_USER1 => \&comment_err_authentication_failure_host1,
  878. ERR_AUTHENTICATION_FAILURE_USER2 => \&comment_err_authentication_failure_host2,
  879. ERR_EXIT_TLS_FAILURE => \&comment_err_blank,
  880. ) ;
  881. sub comment_err_blank
  882. {
  883. return '' ;
  884. }
  885. sub comment_err_append_size
  886. {
  887. my $mysync = shift @ARG ;
  888. my $comment = "The destination server refuses too big messages. Use --truncmess option. Read https://imapsync.lamiral.info/FAQ.d/FAQ.Messages_Too_Big.txt" ;
  889. return $comment ;
  890. }
  891. sub comment_err_authentication_failure_host1
  892. {
  893. my $mysync = shift @ARG ;
  894. my $comment = "Check the credentials for $mysync->{ user1 }." ;
  895. return $comment ;
  896. }
  897. sub comment_err_authentication_failure_host2
  898. {
  899. my $mysync = shift @ARG ;
  900. my $comment = "Check the credentials for $mysync->{ user2 }." ;
  901. return $comment ;
  902. }
  903. sub comment_err_connection_failure_host1
  904. {
  905. my $mysync = shift @ARG ;
  906. my $comment = "Check that host1 $mysync->{ host1 } on port $mysync->{ port1 } is the right IMAP server to be contacted for your mailbox." ;
  907. return $comment ;
  908. }
  909. sub comment_err_connection_failure_host2
  910. {
  911. my $mysync = shift @ARG ;
  912. my $comment = "Check that host1 $mysync->{ host2 } on port $mysync->{ port2 } is the right IMAP server to be contacted for your mailbox." ;
  913. return $comment ;
  914. }
  915. sub comment_err_overquota
  916. {
  917. my $mysync = shift @ARG ;
  918. my $comment = 'The destination mailbox is 100% full, get free space on it and then resume the sync.' ;
  919. return $comment ;
  920. }
  921. sub comment_err_transfer_exceeded
  922. {
  923. my $mysync = shift @ARG ;
  924. my $size_limit_human = bytes_display_string_dec( $mysync->{ exitwhenover } ) ;
  925. my $comment = "The maximum transfer size for a single sync is reached ( over $size_limit_human ). Relaunch the sync to sync more." ;
  926. return $comment ;
  927. }
  928. sub comment_err_flags
  929. {
  930. my $mysync = shift @ARG ;
  931. my $comment = 'Many STORE errors with FLAGS. Retry with the option --noresyncflags' ;
  932. return $comment ;
  933. }
  934. Readonly my $DEFAULT_LOGDIR => 'LOG_imapsync' ;
  935. Readonly my $ERRORS_MAX => 50 ; # exit after 50 errors.
  936. Readonly my $ERRORS_MAX_CGI => 500 ; # exit after 500 errors in CGI context.
  937. Readonly my $INTERVAL_TO_EXIT => 2 ; # interval max to exit instead of reconnect
  938. Readonly my $SPLIT => 100 ; # By default, 100 at a time, not more.
  939. Readonly my $SPLIT_FACTOR => 10 ; # init_imap() calls Maxcommandlength( $SPLIT_FACTOR * $split )
  940. # which means default Maxcommandlength is 10*100 = 1000 characters ;
  941. Readonly my $IMAP_PORT => 143 ; # Well know port for IMAP
  942. Readonly my $IMAP_SSL_PORT => 993 ; # Well know port for IMAP over SSL
  943. Readonly my $LAST => -1 ;
  944. Readonly my $MINUS_ONE => -1 ;
  945. Readonly my $MINUS_TWO => -2 ;
  946. Readonly my $RELEASE_NUMBER_EXAMPLE_1 => '1.351' ;
  947. Readonly my $RELEASE_NUMBER_EXAMPLE_2 => 42.4242 ;
  948. Readonly my $TCP_PING_TIMEOUT => 5 ;
  949. Readonly my $DEFAULT_TIMEOUT => 120 ;
  950. Readonly my $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND => 3 ;
  951. Readonly my $DEFAULT_BUFFER_SIZE => 4096 ;
  952. Readonly my $MAX_SLEEP => 2 ; # 2 seconds max for limiting too long sleeps from --maxbytespersecond and --maxmessagespersecond
  953. Readonly my $DEFAULT_EXPIRATION_TIME_OAUTH2_PK12 => 3600 ;
  954. Readonly my $PERMISSION_FILTER => 7777 ;
  955. Readonly my $KIBI => 1024 ;
  956. Readonly my $NUMBER_10 => 10 ;
  957. Readonly my $NUMBER_42 => 42 ;
  958. Readonly my $NUMBER_100 => 100 ;
  959. Readonly my $NUMBER_200 => 200 ;
  960. Readonly my $NUMBER_300 => 300 ;
  961. Readonly my $NUMBER_123456 => 123_456 ;
  962. Readonly my $NUMBER_654321 => 654_321 ;
  963. Readonly my $NUMBER_20_000 => 20_000 ;
  964. Readonly my $QUOTA_PERCENT_LIMIT => 90 ;
  965. Readonly my $NUMBER_104_857_600 => 104_857_600 ;
  966. Readonly my $SIZE_MAX_STR => 64 ;
  967. Readonly my $NB_SECONDS_IN_A_DAY => 86_400 ;
  968. Readonly my $STD_CHAR_PER_LINE => 80 ;
  969. Readonly my $TRUE => 1 ;
  970. Readonly my $FALSE => 0 ;
  971. Readonly my $LAST_RESSORT_SEPARATOR => q{/} ;
  972. Readonly my $CGI_TMPDIR_TOP => '/var/tmp/imapsync_cgi' ;
  973. Readonly my $CGI_HASHFILE => '/var/tmp/imapsync_hash' ;
  974. Readonly my $UMASK_PARANO => '0077' ;
  975. Readonly my $STR_use_releasecheck => q{Check if a new imapsync release is available by adding --releasecheck} ;
  976. Readonly my $GMAIL_MAXSIZE => 35_651_584 ;
  977. Readonly my $FORCE => 1 ;
  978. # if ( 'MSWin32' eq $OSNAME )
  979. # if ( 'darwin' eq $OSNAME )
  980. # if ( 'linux' eq $OSNAME )
  981. # global variables
  982. # Currently working to finish with only $sync, $acc1, $acc2
  983. # Not finished yet...
  984. my(
  985. $sync, $acc1, $acc2,
  986. $debugdev, $debugmaxlinelength, $debugcgi,
  987. @include, @exclude, @folderrec,
  988. @folderfirst, @folderlast,
  989. @h1_folders_all, %h1_folders_all,
  990. @h2_folders_all, %h2_folders_all,
  991. @h2_folders_from_1_wanted, %h2_folders_from_1_all,
  992. %requested_folder,
  993. $h1_folders_wanted_nb, $h1_folders_wanted_ct,
  994. @h2_folders_not_in_1,
  995. %h1_subscribed_folder, %h2_subscribed_folder,
  996. %h2_folders_from_1_wanted,
  997. %h2_folders_from_1_several,
  998. $prefix1, $prefix2,
  999. @regexmess, @skipmess, @pipemess, $pipemesscheck,
  1000. $syncflagsaftercopy,
  1001. $syncinternaldates,
  1002. $idatefromheader,
  1003. $minsize, $maxage, $minage,
  1004. $search,
  1005. @useheader, %useheader,
  1006. $skipsize, $allowsizemismatch, $buffersize,
  1007. $authmd5, $authmd51, $authmd52,
  1008. $subscribed, $subscribe, $subscribeall,
  1009. $help,
  1010. $nb_msg_skipped_dry_mode,
  1011. $h2_nb_msg_noheader,
  1012. $h1_bytes_processed,
  1013. $h1_nb_msg_end, $h1_bytes_end,
  1014. $h2_nb_msg_end, $h2_bytes_end,
  1015. $timestart_int,
  1016. $uid1, $uid2,
  1017. $split1, $split2,
  1018. $modulesversion,
  1019. $delete2folders, $delete2foldersonly, $delete2foldersbutnot,
  1020. $debugcache, $cacheaftercopy,
  1021. $wholeheaderifneeded, %h1_msgs_copy_by_uid, $useuid, $h2_uidguess,
  1022. $checkmessageexists,
  1023. $messageidnodomain,
  1024. $fixInboxINBOX,
  1025. $maxlinelength, $maxlinelengthcmd,
  1026. $minmaxlinelength,
  1027. $fixcolonbug,
  1028. $create_folder_old,
  1029. $disarmreadreceipts,
  1030. $mixfolders,
  1031. $fetch_hash_set,
  1032. $cgidir,
  1033. %month_abrev,
  1034. $SSL_VERIFY_POLICY,
  1035. ) ;
  1036. exit( single_sync( $sync, $acc1, $acc2 ) ) ;
  1037. sub single_sync
  1038. {
  1039. # main program
  1040. # global variables initialization
  1041. # I'm currently removing all global variables except $sync $acc1 $acc2
  1042. # passing each of them under
  1043. # $sync->{variable_name}
  1044. # or $acc1->{variable_name}
  1045. # or $acc1->{variable_name}
  1046. #
  1047. $acc1 = {} ;
  1048. $acc2 = {} ;
  1049. $sync->{ acc1 } = $acc1 ;
  1050. $sync->{ acc2 } = $acc2 ;
  1051. $acc1->{ Side } = 'Host1' ;
  1052. $acc2->{ Side } = 'Host2' ;
  1053. $acc1->{ N } = '1' ;
  1054. $acc2->{ N } = '2' ;
  1055. $sync->{timestart} = time ; # Is a float because of use Time::HiRres
  1056. $sync->{rcs} = q{$Id: imapsync,v 2.229 2022/09/14 18:08:24 gilles Exp gilles $} ;
  1057. $sync->{ memory_consumption_at_start } = memory_consumption_of_myself( ) || 0 ;
  1058. my @loadavg = loadavg( ) ;
  1059. $sync->{ total_bytes_transferred } = 0 ;
  1060. $sync->{ total_bytes_skipped } = 0 ;
  1061. $sync->{ nb_msg_transferred } = 0 ;
  1062. $sync->{ nb_msg_skipped } = $nb_msg_skipped_dry_mode = 0 ;
  1063. $sync->{ acc1 }->{ nb_msg_deleted } = 0 ;
  1064. $sync->{ acc2 }->{ nb_msg_deleted } = 0 ;
  1065. $sync->{ acc1 }->{ nb_msg_duplicate } = 0 ;
  1066. $sync->{ acc2 }->{ nb_msg_duplicate } = 0 ;
  1067. $sync->{ h1_nb_msg_noheader } = 0 ;
  1068. $h2_nb_msg_noheader = 0 ;
  1069. $sync->{ h1_nb_msg_start } = 0 ;
  1070. $sync->{ h1_bytes_start } = 0 ;
  1071. $sync->{ h2_nb_msg_start } = 0 ;
  1072. $sync->{ h2_bytes_start } = 0 ;
  1073. $sync->{ h1_nb_msg_processed } = $h1_bytes_processed = 0 ;
  1074. $sync->{ h2_nb_msg_crossdup } = 0 ;
  1075. #$h1_nb_msg_end = $h1_bytes_end = 0 ;
  1076. #$h2_nb_msg_end = $h2_bytes_end = 0 ;
  1077. $sync->{ nb_errors } = 0;
  1078. $sync->{ biggest_message_transferred } = 0;
  1079. %month_abrev = (
  1080. Jan => '00',
  1081. Feb => '01',
  1082. Mar => '02',
  1083. Apr => '03',
  1084. May => '04',
  1085. Jun => '05',
  1086. Jul => '06',
  1087. Aug => '07',
  1088. Sep => '08',
  1089. Oct => '09',
  1090. Nov => '10',
  1091. Dec => '11',
  1092. );
  1093. # Just create a CGI object if under cgi context only.
  1094. # Needed for the get_options() call
  1095. cgibegin( $sync ) ;
  1096. # In cgi context, printing must start by the header so we delay other prints by using output() storage
  1097. my $options_good = get_options( $sync, @ARGV ) ;
  1098. $sync->{ cpu_number } = cpu_number( ) ;
  1099. $sync->{ heavy_load_reached } = heavy_load_reached( $sync ) ;
  1100. $sync->{ loadavg } = join( q{ }, $loadavg[ 0 ] )
  1101. . " on $sync->{cpu_number} cores and "
  1102. . ram_memory_info( $sync ) ;
  1103. # Is it the first myprint?
  1104. cgibuildheader( $sync ) ;
  1105. docker_context( $sync ) ;
  1106. print_output_if_needed( $sync ) ;
  1107. output_reset_with( $sync ) ;
  1108. # don't go on if options are not all known.
  1109. if ( ! defined $options_good ) { exit $EX_USAGE ; }
  1110. # If you want releasecheck not to be done by default (like the github maintainer),
  1111. # then just uncomment the first "$sync->{releasecheck} =" line, the line ending with "0 ;",
  1112. # the second line (ending with "1 ;") can then stay active or be commented,
  1113. # the result will be the same: no releasecheck by default (because 0 is then the defined value).
  1114. $sync->{releasecheck} = defined $sync->{releasecheck} ? $sync->{releasecheck} : 0 ;
  1115. #$sync->{releasecheck} = defined $sync->{releasecheck} ? $sync->{releasecheck} : 1 ;
  1116. # just the version
  1117. if ( $sync->{ version } ) {
  1118. myprint( imapsync_version( $sync ), "\n" ) ;
  1119. return 0 ;
  1120. }
  1121. #$sync->{debugenv} = 1 ;
  1122. $sync->{debugenv} and printenv( $sync ) ; # if option --debugenv
  1123. load_modules( ) ;
  1124. # after_get_options call usage and exit if --help or options were not well got
  1125. after_get_options( $sync, $options_good ) ;
  1126. #local $ENV{TZ} = 'GMT' if ( under_cgi_context( $sync ) and 'MSWin32' ne $OSNAME ) ;
  1127. #output( $sync, localtime(time) . " " . gmtime(time) . "\n" ) ;
  1128. make_var_array_to_a_hash( $sync ) ;
  1129. # Under CGI environment, fix caveat emptor potential issues
  1130. cgisetcontext( $sync ) ;
  1131. get_options_extra( $sync ) ;
  1132. # --gmail --gmail --exchange --office etc.
  1133. easyany( $sync ) ;
  1134. $sync->{ sanitize } = defined $sync->{ sanitize } ? $sync->{ sanitize } : 1 ;
  1135. sanitize( $sync ) ;
  1136. $sync->{ tmpdir } ||= File::Spec->tmpdir( ) ;
  1137. # Unit tests
  1138. my $unittestssuite = unittestssuite( $sync ) ;
  1139. if ( condition_to_leave_after_tests( $sync ) )
  1140. {
  1141. return $unittestssuite ;
  1142. }
  1143. # init live varaiables
  1144. if ( $sync->{ testslive } )
  1145. {
  1146. testslive_init( $sync ) ;
  1147. }
  1148. if ( $sync->{ testslive6 } )
  1149. {
  1150. testslive6_init( $sync ) ;
  1151. }
  1152. define_pidfile( $sync ) ;
  1153. if ( $sync->{ abortbyfile } ) { $sync->{ abort } = 1 ; }
  1154. install_signals( $sync ) ;
  1155. $sync->{ loglogfilename } = '../list_all_logs_auto.txt' ;
  1156. $sync->{ log } = defined $sync->{ log } ? $sync->{ log } : 1 ;
  1157. $sync->{ errorsdump } = defined $sync->{ errorsdump } ? $sync->{ errorsdump } : 1 ;
  1158. $sync->{ errorsmax } = defined $sync->{ errorsmax } ? $sync->{ errorsmax } : $ERRORS_MAX ;
  1159. $sync->{ emailreport1 } = defined $sync->{ emailreport1 } ? $sync->{ emailreport1 } : 0 ;
  1160. $sync->{ emailreport2 } = defined $sync->{ emailreport2 } ? $sync->{ emailreport2 } : 0 ;
  1161. # log and output
  1162. binmode STDOUT, ":encoding(UTF-8)" ;
  1163. if ( $sync->{ log } ) {
  1164. $sync->{ logdir } = setlogdir( $sync ) ;
  1165. $sync->{ logfile } = setlogfile( $sync, $sync->{ logfile } ) ;
  1166. $sync->{ tee } = teelaunch( $sync, $sync->{ logfile } ) ;
  1167. # now $sync->{tee} is a filehandle to STDOUT and the logfile
  1168. }
  1169. #binmode STDERR, ":encoding(UTF-8)" ;
  1170. # STDERR goes to the same place: LOG and STDOUT if logging is on
  1171. # or just STDOUT
  1172. #
  1173. stderr_to_stdout( $sync ) ;
  1174. if ( usecache_and_skipcrossduplicates( $sync ) )
  1175. {
  1176. $sync->{ nb_errors }++ ;
  1177. exit_clean( $sync, $EX_USAGE, "Error: can not have both --usecache and --skipcrossduplicates\n" ) ;
  1178. }
  1179. $timestart_int = int( $sync->{timestart} ) ;
  1180. $sync->{timebefore} = $sync->{timestart} ;
  1181. $sync->{ timestart_str } = localtimez( $sync->{timestart} ) ;
  1182. # The prints in the log starts here
  1183. myprint( localhost_info( $sync ), "\n" ) ;
  1184. myprint( "Transfer started at $sync->{ timestart_str }\n" ) ;
  1185. myprint( "PID is $PROCESS_ID my PPID is ", mygetppid( ), "\n" ) ;
  1186. announcelogfile( $sync ) ;
  1187. myprint( "Load is " . ( join( q{ }, loadavg( ) ) || 'unknown' ), " on $sync->{cpu_number} cores\n" ) ;
  1188. #myprintf( "Memory consumption so far: %.1f MiB\n", memory_consumption_of_myself( ) / $KIBI / $KIBI ) ;
  1189. myprint( 'Current directory is ' . getcwd( ) . "\n" ) ;
  1190. myprint( 'Real user id is ' . getpwuid_any_os( $REAL_USER_ID ) . " (uid $REAL_USER_ID)\n" ) ;
  1191. myprint( 'Effective user id is ' . getpwuid_any_os( $EFFECTIVE_USER_ID ). " (euid $EFFECTIVE_USER_ID)\n" ) ;
  1192. $modulesversion = defined $modulesversion ? $modulesversion : 1 ;
  1193. $sync->{ warn_release } = ( $sync->{ releasecheck } ) ? check_last_release( ) : $STR_use_releasecheck ;
  1194. $wholeheaderifneeded = defined $wholeheaderifneeded ? $wholeheaderifneeded : 1;
  1195. # Activate --usecache if --useuid is set and there is no --nousecache
  1196. $sync->{ usecache } = 1 if ( $useuid and ( ! defined $sync->{ usecache } ) ) ;
  1197. $cacheaftercopy = 1 if ( $sync->{ usecache } and ( ! defined $cacheaftercopy ) ) ;
  1198. $sync->{ checkfoldersexist } = defined $sync->{ checkfoldersexist } ? $sync->{ checkfoldersexist } : 1 ;
  1199. $checkmessageexists = defined $checkmessageexists ? $checkmessageexists : 0 ;
  1200. $sync->{ expungeaftereach } = defined $sync->{ expungeaftereach } ? $sync->{ expungeaftereach } : 1 ;
  1201. # abletosearch is on by default
  1202. $sync->{abletosearch} = defined $sync->{abletosearch} ? $sync->{abletosearch} : 1 ;
  1203. $sync->{abletosearch1} = defined $sync->{abletosearch1} ? $sync->{abletosearch1} : $sync->{abletosearch} ;
  1204. $sync->{abletosearch2} = defined $sync->{abletosearch2} ? $sync->{abletosearch2} : $sync->{abletosearch} ;
  1205. $checkmessageexists = 0 if ( not $sync->{abletosearch1} ) ;
  1206. $sync->{ trylogin } = defined $sync->{ trylogin } ? $sync->{ trylogin } : 1 ;
  1207. $sync->{showpasswords} = defined $sync->{showpasswords} ? $sync->{showpasswords} : 0 ;
  1208. $sync->{ fixslash2 } = defined $sync->{ fixslash2 } ? $sync->{ fixslash2 } : 1 ;
  1209. $fixInboxINBOX = defined $fixInboxINBOX ? $fixInboxINBOX : 1 ;
  1210. $create_folder_old = defined $create_folder_old ? $create_folder_old : 0 ;
  1211. $mixfolders = defined $mixfolders ? $mixfolders : 1 ;
  1212. $sync->{automap} = defined $sync->{automap} ? $sync->{automap} : 0 ;
  1213. $sync->{ delete2duplicates } = determine_delete2duplicates( $sync ) ;
  1214. $sync->{maxmessagespersecond} = defined $sync->{maxmessagespersecond} ? $sync->{maxmessagespersecond} : 0 ;
  1215. $sync->{maxbytespersecond} = defined $sync->{maxbytespersecond} ? $sync->{maxbytespersecond} : 0 ;
  1216. $sync->{sslcheck} = defined $sync->{sslcheck} ? $sync->{sslcheck} : 1 ;
  1217. myprint( banner_imapsync( $sync, @ARGV ) ) ;
  1218. myprint( "Temp directory is $sync->{ tmpdir } ( to change it use --tmpdir dirpath )\n" ) ;
  1219. myprint( output( $sync ) ) ;
  1220. output_reset_with( $sync ) ;
  1221. do_valid_directory( $sync->{ tmpdir } ) || croak "Error creating tmpdir $sync->{ tmpdir } : $OS_ERROR" ;
  1222. remove_pidfile_not_running( $sync->{ pidfile } ) ;
  1223. # if another imapsync is running then tail -f its logfile and exit
  1224. # useful in cgi context
  1225. if ( $sync->{ tail } and tail( $sync ) )
  1226. {
  1227. exit_clean( $sync, $EX_OK, "Tail -f finished. Now finishing myself processus $PROCESS_ID\n" ) ;
  1228. exit $EX_OK ;
  1229. }
  1230. if ( ! write_pidfile( $sync ) ) {
  1231. 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" ) ;
  1232. exit $EXIT_PID_FILE_ERROR ;
  1233. }
  1234. # New place for abort
  1235. # abort before simulong in order to be able to abort a simulong sync
  1236. if ( $sync->{ abort } )
  1237. {
  1238. abort( $sync ) ;
  1239. # well, the abort job is done, because even when not succeeded
  1240. # in aborting another run, this run has to end without doing any
  1241. # thing else
  1242. exit $EX_OK ;
  1243. }
  1244. if ( $sync->{ memorystress } ) { tests_memory_stress() ; }
  1245. # simulong is just a loop printing some lines for xx seconds with option "--simulong xx".
  1246. simulong( $sync ) ;
  1247. # New place for cgi_exit_on_heavy_load 2019_03_03
  1248. # because I want to log it
  1249. # Can break here if load is too heavy
  1250. # Have in mind the CGI header has already a 503 Service Unavailable printed.
  1251. cgi_exit_on_heavy_load( $sync ) ;
  1252. $fixcolonbug = defined $fixcolonbug ? $fixcolonbug : 1 ;
  1253. if ( $sync->{ usecache } and $fixcolonbug ) { tmpdir_fix_colon_bug( $sync ) } ;
  1254. $modulesversion and myprint( "Modules version list ( use --no-modulesversion to turn off printing this Perl modules list ):\n", modulesversion(), "\n" ) ;
  1255. check_lib_version( $sync ) or
  1256. croak "imapsync needs perl lib Mail::IMAPClient release 3.30 or superior.\n";
  1257. if ( $sync->{ justbanner } )
  1258. {
  1259. myprint( "Exiting because of --justbanner\n" ) ;
  1260. exit_clean( $sync, $EX_OK ) ;
  1261. }
  1262. # turn on RFC standard flags correction like \SEEN -> \Seen
  1263. $sync->{ flagscase } = defined $sync->{ flagscase } ? $sync->{ flagscase } : 1 ;
  1264. # Use PERMANENTFLAGS if available
  1265. $sync->{ filterflags } = defined $sync->{ filterflags } ? $sync->{ filterflags } : 1 ;
  1266. filterbuggyflags( $sync ) ;
  1267. # sync flags just after an APPEND, some servers ignore the flags given in the APPEND
  1268. # like MailEnable IMAP server.
  1269. # Off by default since it takes time.
  1270. $syncflagsaftercopy = defined $syncflagsaftercopy ? $syncflagsaftercopy : 0 ;
  1271. # update flags on host2 for already transferred messages
  1272. $sync->{resyncflags} = defined $sync->{resyncflags} ? $sync->{resyncflags} : 1 ;
  1273. if ( $sync->{resyncflags} ) {
  1274. myprint( "Info: will resync flags for already transferred messages. Use --noresyncflags to not resync flags.\n" ) ;
  1275. }else{
  1276. myprint( "Info: will not resync flags for already transferred messages. Use --resyncflags to resync flags.\n" ) ;
  1277. }
  1278. sslcheck( $sync ) ;
  1279. #print Data::Dumper->Dump( [ \$sync ] ) ;
  1280. $split1 ||= $SPLIT ;
  1281. $split2 ||= $SPLIT ;
  1282. #$sync->{host1} || missing_option( $sync, '--host1' ) ;
  1283. $sync->{host1} = sanitize_host( $sync->{host1} ) ;
  1284. $sync->{port1} ||= ( $sync->{ssl1} ) ? $IMAP_SSL_PORT : $IMAP_PORT ;
  1285. #$sync->{host2} || missing_option( $sync, '--host2' ) ;
  1286. $sync->{host2} = sanitize_host( $sync->{host2} ) ;
  1287. $sync->{port2} ||= ( $sync->{ssl2} ) ? $IMAP_SSL_PORT : $IMAP_PORT ;
  1288. $acc1->{ debugimap } = $acc2->{ debugimap } = 1 if ( $sync->{ debugimap } ) ;
  1289. # Set on debug mode if one of the imap dialogs are in debug.
  1290. # imap dialog without the debug mode is scary and useless.
  1291. $sync->{ debug } = 1 if ( $acc1->{ debugimap } or $acc2->{ debugimap } ) ;
  1292. # By default, don't take size to compare
  1293. $skipsize = (defined $skipsize) ? $skipsize : 1;
  1294. $uid1 = defined $uid1 ? $uid1 : 1;
  1295. $uid2 = defined $uid2 ? $uid2 : 1;
  1296. $subscribe = defined $subscribe ? $subscribe : 1;
  1297. # Allow size mismatch by default
  1298. $allowsizemismatch = defined $allowsizemismatch ? $allowsizemismatch : 1;
  1299. if ( defined $delete2foldersbutnot or defined $delete2foldersonly ) {
  1300. $delete2folders = 1 ;
  1301. }
  1302. my %SSL_VERIFY_STR ;
  1303. Readonly $SSL_VERIFY_POLICY => IO::Socket::SSL::SSL_VERIFY_NONE( ) ;
  1304. Readonly %SSL_VERIFY_STR => (
  1305. IO::Socket::SSL::SSL_VERIFY_NONE( ) => 'SSL_VERIFY_NONE, ie, do not check the server certificate.' ,
  1306. IO::Socket::SSL::SSL_VERIFY_PEER( ) => 'SSL_VERIFY_PEER, ie, check the server certificate.' ,
  1307. ) ;
  1308. $IO::Socket::SSL::DEBUG = defined( $sync->{debugssl} ) ? $sync->{debugssl} : 1 ;
  1309. if ( $sync->{ssl1} or $sync->{ssl2} or $sync->{tls1} or $sync->{tls2}) {
  1310. 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" ) ;
  1311. }
  1312. if ( $sync->{ssl1} ) {
  1313. 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} ) ;
  1314. 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" ) ;
  1315. # $sync->{ acc1 }->{sslargs}->{SSL_verify_mode}
  1316. }
  1317. if ( $sync->{ssl2} ) {
  1318. 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} ) ;
  1319. 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" ) ;
  1320. }
  1321. # ID on by default since 1.832
  1322. $sync->{id} = defined $sync->{id} ? $sync->{id} : 1 ;
  1323. if ( $sync->{justconnect}
  1324. or not $sync->{user1}
  1325. or not $sync->{user2}
  1326. or not $sync->{host1}
  1327. or not $sync->{host2}
  1328. )
  1329. {
  1330. my $justconnect = justconnect( $sync ) ;
  1331. myprint( debugmemory( $sync, " after justconnect() call" ) ) ;
  1332. exit_clean( $sync, $EX_OK,
  1333. "Exiting after a justconnect on host(s): $justconnect\n"
  1334. ) ;
  1335. }
  1336. #$sync->{user1} || missing_option( $sync, '--user1' ) ;
  1337. #$sync->{user2} || missing_option( $sync, '--user2' ) ;
  1338. $syncinternaldates = defined $syncinternaldates ? $syncinternaldates : 1;
  1339. # Turn on expunge if there is not explicit option --noexpunge1 and option
  1340. # --delete1 is given.
  1341. # Done because --delete1 --noexpunge1 is very dangerous on the second run:
  1342. # the Deleted flag is then synced to all previously transferred messages.
  1343. # So --delete1 implies --expunge1 is a better usability default behavior.
  1344. if ( $sync->{ delete1 } ) {
  1345. if ( ! defined $sync->{ expunge1 } ) {
  1346. myprint( "Info: turning on --expunge1 because --delete1 --noexpunge1 is very dangerous on the second run.\n" ) ;
  1347. $sync->{ expunge1 } = 1 ;
  1348. }
  1349. myprint( "Info: if expunging after each message slows down too much the sync then use --noexpungeaftereach to speed up\n" ) ;
  1350. }
  1351. if ( $sync->{ uidexpunge2 } and not Mail::IMAPClient->can( 'uidexpunge' ) ) {
  1352. myprint( "Failure: uidexpunge not supported (IMAPClient release < 3.17), use nothing or --expunge2 instead\n" ) ;
  1353. $sync->{nb_errors}++ ;
  1354. exit_clean( $sync, $EX_SOFTWARE ) ;
  1355. }
  1356. if ( ( $sync->{ delete2 } or $sync->{ delete2duplicates } ) and not defined $sync->{ uidexpunge2 } ) {
  1357. if ( Mail::IMAPClient->can( 'uidexpunge' ) ) {
  1358. myprint( "Info: will act as --uidexpunge2\n" ) ;
  1359. $sync->{ uidexpunge2 } = 1 ;
  1360. }elsif ( not defined $sync->{ expunge2 } ) {
  1361. myprint( "Info: will act as --expunge2 (no uidexpunge support)\n" ) ;
  1362. $sync->{ expunge2 } = 1 ;
  1363. }
  1364. }
  1365. if ( $sync->{ delete1 } and $sync->{ delete2 } ) {
  1366. myprint( "Warning: using --delete1 and --delete2 together is almost always a bad idea. "
  1367. . "You should probably launch two runs, the first with --delete2 for a strict sync, "
  1368. . "then the second with --delete1 to remove messages from the source account. "
  1369. . "Exiting imapsync.\n" ) ;
  1370. $sync->{ nb_errors }++ ;
  1371. exit_clean( $sync, $EX_USAGE ) ;
  1372. }
  1373. if ( $idatefromheader ) {
  1374. myprint( 'Turned ON idatefromheader, ',
  1375. "will set the internal dates on host2 from the 'Date:' header line.\n" ) ;
  1376. $syncinternaldates = 0 ;
  1377. }
  1378. if ( $syncinternaldates ) {
  1379. myprint( 'Info: turned ON syncinternaldates, ',
  1380. "will set the internal dates (arrival dates) on host2 same as host1.\n" ) ;
  1381. }else{
  1382. myprint( "Info: turned OFF syncinternaldates\n" ) ;
  1383. }
  1384. if ( defined $authmd5 and $authmd5 ) {
  1385. $authmd51 = 1 ;
  1386. $authmd52 = 1 ;
  1387. }
  1388. if ( defined $authmd51 and $authmd51 ) {
  1389. $acc1->{ authmech } ||= 'CRAM-MD5' ;
  1390. }
  1391. else{
  1392. $acc1->{ authmech } ||= $acc1->{ authuser } ? 'PLAIN' : 'LOGIN' ;
  1393. }
  1394. if ( defined $authmd52 and $authmd52 ) {
  1395. $acc2->{ authmech } ||= 'CRAM-MD5';
  1396. }
  1397. else{
  1398. $acc2->{ authmech } ||= $acc2->{ authuser } ? 'PLAIN' : 'LOGIN';
  1399. }
  1400. $acc1->{ authmech } = uc $acc1->{ authmech } ;
  1401. $acc2->{ authmech } = uc $acc2->{ authmech } ;
  1402. if ( defined $acc1->{ proxyauth } && !$acc1->{ authuser } )
  1403. {
  1404. missing_option( $sync, 'With --proxyauth1, --authuser1' ) ;
  1405. }
  1406. if ( defined $acc2->{ proxyauth } && !$acc2->{ authuser } )
  1407. {
  1408. missing_option( $sync, 'With --proxyauth2, --authuser2' ) ;
  1409. }
  1410. myprint( "Host1: will try to use $acc1->{ authmech } authentication on host1\n") ;
  1411. myprint( "Host2: will try to use $acc2->{ authmech } authentication on host2\n") ;
  1412. $sync->{ timeout } = defined $sync->{ timeout } ?$sync->{ timeout } : $DEFAULT_TIMEOUT ;
  1413. $sync->{ acc1 }->{timeout} = defined $sync->{ acc1 }->{timeout} ? $sync->{ acc1 }->{timeout} : $sync->{ timeout } ;
  1414. myprint( "Host1: imap connection timeout is $sync->{ acc1 }->{timeout} seconds\n") ;
  1415. $sync->{ acc2 }->{timeout} = defined $sync->{ acc2 }->{timeout} ? $sync->{ acc2 }->{timeout} : $sync->{ timeout } ;
  1416. myprint( "Host2: imap connection timeout is $sync->{ acc2 }->{timeout} seconds\n" ) ;
  1417. keepalive1( $sync ) ;
  1418. keepalive2( $sync ) ;
  1419. if ( under_cgi_context( $sync ) )
  1420. {
  1421. myprint( "Under CGI context, a timeout can occur from the webserver, see https://imapsync.lamiral.info/INSTALL.d/INSTALL.OnlineUI.txt\n" ) ;
  1422. }
  1423. $sync->{ syncacls } = defined $sync->{ syncacls } ? $sync->{ syncacls } : 0 ;
  1424. # No folders sizes at the beginning if --justfolders, unless really wanted.
  1425. if (
  1426. $sync->{ justfolders }
  1427. and not defined $sync->{ foldersizes }
  1428. and not $sync->{ justfoldersizes } )
  1429. {
  1430. $sync->{ foldersizes } = 0 ;
  1431. $sync->{ foldersizesatend } = 1 ;
  1432. }
  1433. $sync->{ foldersizes } = ( defined $sync->{ foldersizes } ) ? $sync->{ foldersizes } : 1 ;
  1434. $sync->{ foldersizesatend } = ( defined $sync->{ foldersizesatend } ) ? $sync->{ foldersizesatend } : $sync->{ foldersizes } ;
  1435. #$sync->{ checknoabletosearch } = ( defined $sync->{ checknoabletosearch } ) ? $sync->{ checknoabletosearch } : 1 ;
  1436. set_checknoabletosearch( $sync ) ;
  1437. $acc1->{ fastio } = defined $acc1->{ fastio } ? $acc1->{ fastio } : 0 ;
  1438. $acc2->{ fastio } = defined $acc2->{ fastio } ? $acc2->{ fastio } : 0 ;
  1439. $acc1->{ reconnectretry } = defined $acc1->{ reconnectretry } ? $acc1->{ reconnectretry } : $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ;
  1440. $acc2->{ reconnectretry } = defined $acc2->{ reconnectretry } ? $acc2->{ reconnectretry } : $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ;
  1441. # IMAP compression on by default
  1442. #$acc1->{ compress } = defined $acc1->{ compress } ? $acc1->{ compress } : 0 ;
  1443. #$acc2->{ compress } = defined $acc2->{ compress } ? $acc2->{ compress } : 0 ;
  1444. if ( ! @useheader ) { @useheader = qw( Message-Id Received ) ; }
  1445. # Make a hash %useheader of each --useheader 'key' in uppercase
  1446. for ( @useheader ) { $sync->{useheader}->{ uc $_ } = undef } ;
  1447. #myprint( Data::Dumper->Dump( [ \%useheader ] ) ) ;
  1448. #exit ;
  1449. myprint( "Host1: IMAP server [$sync->{host1}] port [$sync->{port1}] user [$sync->{user1}]\n" ) ;
  1450. myprint( "Host2: IMAP server [$sync->{host2}] port [$sync->{port2}] user [$sync->{user2}]\n" ) ;
  1451. get_password1( $sync ) ;
  1452. get_password2( $sync ) ;
  1453. # --dry1 make imapsync not fetching messages from host1, it is on when --dry is on.
  1454. # Use --dry --nodry1 to make imapsync fetching messages from host1,
  1455. # It is useful when debugging transformation options like --pipemess or --regexmess
  1456. $sync->{dry1} = defined $sync->{dry1} ? $sync->{dry1} : $sync->{dry} ;
  1457. $sync->{dry_message} = q{} ;
  1458. if( $sync->{dry} ) {
  1459. $sync->{dry_message} = "\t(not really since --dry mode)" ;
  1460. }
  1461. $sync->{ search1 } ||= $search if ( $search ) ;
  1462. $sync->{ search2 } ||= $search if ( $search ) ;
  1463. if ( $disarmreadreceipts )
  1464. {
  1465. push @regexmess, q{s{\A((?:[^\n]+\r\n)+|)(^Disposition-Notification-To:[^\n]*\n)(\r?\n|.*\n\r?\n)}{$1X-$2$3}ims} ;
  1466. }
  1467. $pipemesscheck = ( defined $pipemesscheck ) ? $pipemesscheck : 1 ;
  1468. if ( @pipemess and $pipemesscheck ) {
  1469. myprint( 'Checking each --pipemess command, '
  1470. . join( q{, }, @pipemess )
  1471. . ", with an space string. ( Can avoid this check with --nopipemesscheck )\n" ) ;
  1472. my $string = pipemess( q{ }, @pipemess ) ;
  1473. # string undef means something was bad.
  1474. if ( not ( defined $string ) ) {
  1475. $sync->{nb_errors}++ ;
  1476. exit_clean( $sync, $EX_USAGE,
  1477. "Error: one of --pipemess command is bad, check it\n"
  1478. ) ;
  1479. }
  1480. myprint( "Ok with each --pipemess @pipemess\n" ) ;
  1481. }
  1482. if ( $maxlinelengthcmd ) {
  1483. myprint( "Checking --maxlinelengthcmd command,
  1484. $maxlinelengthcmd, with an space string.\n"
  1485. ) ;
  1486. my $string = pipemess( q{ }, $maxlinelengthcmd ) ;
  1487. # string undef means something was bad.
  1488. if ( not ( defined $string ) ) {
  1489. $sync->{nb_errors}++ ;
  1490. exit_clean( $sync, $EX_USAGE,
  1491. "Error: --maxlinelengthcmd command is bad, check it\n"
  1492. ) ;
  1493. }
  1494. myprint( "Ok with --maxlinelengthcmd $maxlinelengthcmd\n" ) ;
  1495. }
  1496. if ( @regexmess ) {
  1497. my $string = regexmess( q{ } ) ;
  1498. myprint( "Checking each --regexmess command with an space string.\n" ) ;
  1499. # string undef means one of the eval regex was bad.
  1500. if ( not ( defined $string ) ) {
  1501. #errors_incr( $sync, 'Warning: one of --regexmess option may be bad, check them' ) ;
  1502. exit_clean( $sync, $EX_USAGE,
  1503. "Error: one of --regexmess option is bad, check it\n"
  1504. ) ;
  1505. }
  1506. myprint( "Ok with each --regexmess\n" ) ;
  1507. }
  1508. if ( @skipmess ) {
  1509. myprint( "Checking each --skipmess command with an space string.\n" ) ;
  1510. my $match = skipmess( q{ } ) ;
  1511. # match undef means one of the eval regex was bad.
  1512. if ( not ( defined $match ) ) {
  1513. $sync->{nb_errors}++ ;
  1514. exit_clean( $sync, $EX_USAGE,
  1515. "Error: one of --skipmess option is bad, check it\n"
  1516. ) ;
  1517. }
  1518. myprint( "Ok with each --skipmess\n" ) ;
  1519. }
  1520. if ( $sync->{ regexflag } ) {
  1521. myprint( "Checking each --regexflag command with an space string.\n" ) ;
  1522. my $string = regexflags( $sync, q{ } ) ;
  1523. # string undef means one of the eval regex was bad.
  1524. if ( not ( defined $string ) ) {
  1525. $sync->{nb_errors}++ ;
  1526. exit_clean( $sync, $EX_USAGE,
  1527. "Error: one of --regexflag option is bad, check it\n"
  1528. ) ;
  1529. }
  1530. myprint( "Ok with each --regexflag\n" ) ;
  1531. }
  1532. $sync->{imap1} = login_imap( $sync->{host1}, $sync->{port1}, $sync->{user1}, $sync->{password1},
  1533. $sync->{ssl1}, $sync->{tls1},
  1534. $uid1, $split1, $sync->{ acc1 }, $sync ) ;
  1535. $sync->{imap2} = login_imap( $sync->{host2}, $sync->{port2}, $sync->{user2}, $sync->{password2},
  1536. $sync->{ssl2}, $sync->{tls2},
  1537. $uid2, $split2, $sync->{ acc2 }, $sync ) ;
  1538. $sync->{ debug } and $sync->{imap1} and myprint( 'Host1 Buffer I/O: ', $sync->{imap1}->Buffer(), "\n" ) ;
  1539. $sync->{ debug } and $sync->{imap2} and myprint( 'Host2 Buffer I/O: ', $sync->{imap2}->Buffer(), "\n" ) ;
  1540. if ( ! $sync->{imap1} || ! $sync->{imap2} )
  1541. {
  1542. exit_most_errors( $sync ) ;
  1543. }
  1544. myprint( "Host1: state Authenticated\n" ) ;
  1545. myprint( "Host2: state Authenticated\n" ) ;
  1546. myprint( 'Host1 capability once authenticated: ', join(q{ }, @{ $sync->{imap1}->capability() || [] }), "\n" ) ;
  1547. #myprint( Data::Dumper->Dump( [ $sync->{imap1} ] ) ) ;
  1548. #myprint( "imap4rev1: " . $sync->{imap1}->imap4rev1() . "\n" ) ;
  1549. myprint( 'Host2 capability once authenticated: ', join(q{ }, @{ $sync->{imap2}->capability() || [] }), "\n" ) ;
  1550. imap_id_stuff( $sync ) ;
  1551. #quota( $sync, $sync->{imap1}, 'h1' ) ; # quota on host1 is useless and pollute host2 output.
  1552. quota( $sync, $sync->{imap2}, 'h2' ) ;
  1553. maxsize_setting( $sync ) ;
  1554. acc_compress_imap( $acc1 ) ;
  1555. acc_compress_imap( $acc2 ) ;
  1556. if ( $sync->{ justlogin } ) {
  1557. $sync->{imap1}->logout( ) ;
  1558. $sync->{imap2}->logout( ) ;
  1559. exit_clean( $sync, $EX_OK, "Exiting because of --justlogin\n" ) ;
  1560. }
  1561. #
  1562. # Folder stuff
  1563. #
  1564. $h1_folders_wanted_nb = 0 ; # counter of folders to be done.
  1565. $h1_folders_wanted_ct = 0 ; # counter of folders done.
  1566. # All folders on host1 and host2
  1567. @h1_folders_all = sort $sync->{imap1}->folders( ) ;
  1568. @h2_folders_all = sort $sync->{imap2}->folders( ) ;
  1569. myprint( 'Host1: found ', scalar @h1_folders_all , " folders.\n" ) ;
  1570. myprint( 'Host2: found ', scalar @h2_folders_all , " folders.\n" ) ;
  1571. foreach my $f ( @h1_folders_all )
  1572. {
  1573. $h1_folders_all{ $f } = 1
  1574. }
  1575. foreach my $f ( @h2_folders_all )
  1576. {
  1577. $h2_folders_all{ $f } = 1 ;
  1578. $sync->{h2_folders_all_UPPER}{ uc $f } = 1 ;
  1579. }
  1580. $sync->{h1_folders_all} = \%h1_folders_all ;
  1581. $sync->{h2_folders_all} = \%h2_folders_all ;
  1582. private_folders_separators_and_prefixes( ) ;
  1583. # Make a hash of subscribed folders in both servers.
  1584. for ( $sync->{imap1}->subscribed( ) ) { $h1_subscribed_folder{ $_ } = 1 } ;
  1585. for ( $sync->{imap2}->subscribed( ) ) { $h2_subscribed_folder{ $_ } = 1 } ;
  1586. if ( defined $sync->{ subfolder1 } ) {
  1587. subfolder1( $sync ) ;
  1588. }
  1589. if ( defined $sync->{ subfolder2 } ) {
  1590. subfolder2( $sync ) ;
  1591. }
  1592. if ( $fixInboxINBOX and ( my $reg = fix_Inbox_INBOX_mapping( \%h1_folders_all, \%h2_folders_all ) ) ) {
  1593. push @{ $sync->{ regextrans2 } }, $reg ;
  1594. }
  1595. if ( ( $sync->{ folder } and scalar @{ $sync->{ folder } } )
  1596. or $subscribed
  1597. or scalar @folderrec )
  1598. {
  1599. # folders given by option --folder
  1600. if ( $sync->{ folder } and scalar @{ $sync->{ folder } } ) {
  1601. add_to_requested_folders( @{ $sync->{ folder } } ) ;
  1602. }
  1603. # option --subscribed
  1604. if ( $subscribed ) {
  1605. add_to_requested_folders( keys %h1_subscribed_folder ) ;
  1606. }
  1607. # option --folderrec
  1608. if ( scalar @folderrec ) {
  1609. foreach my $folderrec ( @folderrec ) {
  1610. add_to_requested_folders( $sync->{imap1}->folders( $folderrec ) ) ;
  1611. }
  1612. }
  1613. }
  1614. else
  1615. {
  1616. # no include, no folder/subscribed/folderrec options => all folders
  1617. if ( not scalar @include ) {
  1618. 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" ) ;
  1619. add_to_requested_folders( @h1_folders_all ) ;
  1620. }
  1621. }
  1622. # consider (optional) includes and excludes
  1623. if ( scalar @include ) {
  1624. foreach my $include ( @include ) {
  1625. # No, do not add /x after the regex, never.
  1626. # Users would kill you!
  1627. my @included_folders = grep { /$include/ } @h1_folders_all ;
  1628. add_to_requested_folders( @included_folders ) ;
  1629. myprint( "Including folders matching pattern $include\n" . jux_utf8_list( @included_folders ) . "\n" ) ;
  1630. }
  1631. }
  1632. if ( scalar @exclude ) {
  1633. foreach my $exclude ( @exclude ) {
  1634. my @requested_folder = sort keys %requested_folder ;
  1635. # No, do not add /x after the regex, never.
  1636. # Users would kill you!
  1637. my @excluded_folders = grep { /$exclude/ } @requested_folder ;
  1638. remove_from_requested_folders( @excluded_folders ) ;
  1639. myprint( "Excluding folders matching pattern $exclude\n" . jux_utf8_list( @excluded_folders ) . "\n" ) ;
  1640. }
  1641. }
  1642. # sort before is not very powerful
  1643. # it adds --folderfirst and --folderlast even if they don't exist on host1
  1644. #@h1_folders_wanted = sort_requested_folders( ) ;
  1645. $sync->{h1_folders_wanted} = [ sort_requested_folders( ) ] ;
  1646. # Remove no selectable folders
  1647. if ( $sync->{ checkfoldersexist } ) {
  1648. my @h1_folders_wanted_exist ;
  1649. myprint( "Host1: Checking wanted folders exist. Use --nocheckfoldersexist to avoid this check (shared of public namespace targeted).\n" ) ;
  1650. foreach my $folder ( @{ $sync->{h1_folders_wanted} } ) {
  1651. ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "Checking $folder exists on host1\n" ) ;
  1652. if ( ! exists $h1_folders_all{ $folder } ) {
  1653. myprint( "Host1: warning! ignoring folder $folder because it is not in host1 whole folders list.\n" ) ;
  1654. next ;
  1655. }else{
  1656. push @h1_folders_wanted_exist, $folder ;
  1657. }
  1658. }
  1659. @{ $sync->{h1_folders_wanted} } = @h1_folders_wanted_exist ;
  1660. }else{
  1661. myprint( "Host1: Not checking that wanted folders exist. Remove --nocheckfoldersexist to get this check.\n" ) ;
  1662. }
  1663. setcheckselectable( $sync ) ;
  1664. checkselectable( $sync ) ;
  1665. # Local bugfix. OpenFind folders named like "kk \*123" are in fact "kk *123" (no \)
  1666. #foreach my $folder ( @{ $sync->{ h1_folders_wanted } } )
  1667. #{
  1668. # $folder =~ s{ \\\*}{ *}g ;
  1669. #}
  1670. # this hack is because LWP post does not pass well a hash in the $form parameter
  1671. # but it does pass well an array
  1672. #%{ $sync->{f1f2h} } = split_around_equal( @{ $sync->{f1f2} } ) ;
  1673. make_f1f2_array_to_a_hash( $sync ) ;
  1674. automap( $sync ) ;
  1675. foreach my $h1_fold ( @{ $sync->{h1_folders_wanted} } ) {
  1676. my $h2_fold ;
  1677. $h2_fold = imap2_folder_name( $sync, $h1_fold ) ;
  1678. $h2_folders_from_1_wanted{ $h2_fold }++ ;
  1679. if ( 1 < $h2_folders_from_1_wanted{ $h2_fold } ) {
  1680. $h2_folders_from_1_several{ $h2_fold }++ ;
  1681. }
  1682. }
  1683. @h2_folders_from_1_wanted = sort keys %h2_folders_from_1_wanted;
  1684. foreach my $h1_fold ( @h1_folders_all ) {
  1685. my $h2_fold ;
  1686. $h2_fold = imap2_folder_name( $sync, $h1_fold ) ;
  1687. $h2_folders_from_1_all{ $h2_fold }++ ;
  1688. # Follows a fix to avoid deleting folder $sync->{ subfolder2 }
  1689. # because it usually does not exist on host1.
  1690. if ( $sync->{ subfolder2 } )
  1691. {
  1692. $h2_folders_from_1_all{ $sync->{ h2_prefix } . $sync->{ subfolder2 } }++ ;
  1693. $h2_folders_from_1_all{ $sync->{ subfolder2 } }++ ;
  1694. }
  1695. }
  1696. myprint( << 'END_LISTING' ) ;
  1697. ++++ Listing folders
  1698. All foldernames are presented between brackets like [X] where X is the foldername.
  1699. When a foldername contains non-ASCII characters it is presented in the form
  1700. [X] = [Y] where
  1701. X is the imap foldername you have to use in command line options and
  1702. Y is the utf8 output just printed for convenience, to recognize it.
  1703. END_LISTING
  1704. myprint(
  1705. "Host1: folders list (first the raw imap format then the [X] = [Y]):\n",
  1706. $sync->{imap1}->list( ),
  1707. "\n",
  1708. jux_utf8_list( @h1_folders_all ),
  1709. "\n",
  1710. "Host2: folders list (first the raw imap format then the [X] = [Y]):\n",
  1711. $sync->{imap2}->list( ),
  1712. "\n",
  1713. jux_utf8_list( @h2_folders_all ),
  1714. "\n",
  1715. q{}
  1716. ) ;
  1717. if ( $subscribed ) {
  1718. myprint(
  1719. 'Host1 subscribed folders list: ',
  1720. jux_utf8_list( sort keys %h1_subscribed_folder ), "\n",
  1721. ) ;
  1722. }
  1723. @h2_folders_not_in_1 = list_folders_in_2_not_in_1( ) ;
  1724. if ( @h2_folders_not_in_1 ) {
  1725. myprint( "Folders in host2 not in host1:\n",
  1726. jux_utf8_list( @h2_folders_not_in_1 ), "\n" ) ;
  1727. }
  1728. if ( keys %{ $sync->{f1f2auto} } ) {
  1729. myprint( "Folders mapping from --automap feature (use --f1f2 to override any mapping):\n" ) ;
  1730. foreach my $h1_fold ( keys %{ $sync->{f1f2auto} } ) {
  1731. my $h2_fold = $sync->{f1f2auto}{$h1_fold} ;
  1732. myprintf( "%-40s -> %-40s\n",
  1733. jux_utf8( $h1_fold ), jux_utf8( $h2_fold ) ) ;
  1734. }
  1735. myprint( "\n" ) ;
  1736. }
  1737. if ( keys %{ $sync->{f1f2h} } ) {
  1738. myprint( "Folders mapping from --f1f2 options, it overrides --automap:\n" ) ;
  1739. foreach my $h1_fold ( keys %{ $sync->{f1f2h} } ) {
  1740. my $h2_fold = $sync->{f1f2h}{$h1_fold} ;
  1741. my $warn = q{} ;
  1742. if ( not exists $h1_folders_all{ $h1_fold } ) {
  1743. $warn = "BUT $h1_fold does NOT exist on host1!" ;
  1744. }
  1745. myprintf( "%-40s -> %-40s %s\n",
  1746. jux_utf8( $h1_fold ), jux_utf8( $h2_fold ), $warn ) ;
  1747. }
  1748. myprint( "\n" ) ;
  1749. }
  1750. exit_clean( $sync, $EX_OK, "Exiting because of --justfolderlists\n" ) if ( $sync->{ justfolderlists } ) ;
  1751. exit_clean( $sync, $EX_OK, "Exiting because of --justautomap\n" ) if ( $sync->{ justautomap } ) ;
  1752. debugsleep( $sync ) ;
  1753. if ( $sync->{ skipemptyfolders } )
  1754. {
  1755. myprint( "Host1: will not syncing empty folders on host1. Use --noskipemptyfolders to create them anyway on host2\n") ;
  1756. }
  1757. if ( $sync->{ checknoabletosearch } )
  1758. {
  1759. myprint( "Checking SEARCH ALL works on both accounts. To avoid that check, use --nochecknoabletosearch\n" ) ;
  1760. my $check1 = checknoabletosearch( $sync, $sync->{ imap1 }, 'INBOX', 'Host1' ) ;
  1761. my $check2 = checknoabletosearch( $sync, $sync->{ imap2 }, 'INBOX', 'Host2' ) ;
  1762. if ( $check1 or $check2 )
  1763. {
  1764. myprint( "At least one account can not SEARCH ALL. So acting like --noabletosearch\n" ) ;
  1765. $sync->{abletosearch} = 0 ;
  1766. $sync->{abletosearch1} = 0 ;
  1767. $sync->{abletosearch2} = 0 ;
  1768. }
  1769. else
  1770. {
  1771. myprint( "Good! SEARCH ALL works on both accounts.\n" ) ;
  1772. }
  1773. }
  1774. if ( $sync->{ foldersizes } ) {
  1775. foldersizes_at_the_beggining( $sync ) ;
  1776. #foldersizes_at_the_beggining_old( $sync ) ;
  1777. }
  1778. if ( $sync->{ justfoldersizes } )
  1779. {
  1780. exit_clean( $sync, $EX_OK, "Exiting because of --justfoldersizes\n" ) ;
  1781. }
  1782. $sync->{can_do_stats} = 1 ;
  1783. if ( $sync->{ delete1emptyfolders } ) {
  1784. delete1emptyfolders( $sync ) ;
  1785. }
  1786. delete_folders_in_2_not_in_1( ) if $delete2folders ;
  1787. # folder loop
  1788. $h1_folders_wanted_nb = scalar @{ $sync->{h1_folders_wanted} } ;
  1789. myprint( "++++ Looping on each one of $h1_folders_wanted_nb folders to sync\n" ) ;
  1790. $sync->{begin_transfer_time} = time ;
  1791. my %uid_candidate_for_deletion ;
  1792. my %uid_candidate_no_deletion ;
  1793. $sync->{ h2_folders_of_md5 } = { } ;
  1794. FOLDER: foreach my $h1_fold ( @{ $sync->{h1_folders_wanted} } )
  1795. {
  1796. $sync->{ h1_current_folder } = $h1_fold ;
  1797. eta_print( $sync ) ;
  1798. abortifneeded( $sync ) ;
  1799. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1800. my $h2_fold = imap2_folder_name( $sync, $h1_fold ) ;
  1801. $sync->{ h2_current_folder } = $h2_fold ;
  1802. $h1_folders_wanted_ct++ ;
  1803. myprintf( "Folder %7s %-35s -> %-35s\n", "$h1_folders_wanted_ct/$h1_folders_wanted_nb",
  1804. jux_utf8( $h1_fold ), jux_utf8( $h2_fold ) ) ;
  1805. myprint( debugmemory( $sync, " at folder loop" ) ) ;
  1806. # host1 can not be fetched read only, select is needed because of expunge.
  1807. select_folder( $sync, $sync->{imap1}, $h1_fold, 'Host1' ) or next FOLDER ;
  1808. debugsleep( $sync ) ;
  1809. my $h1_msgs_all_hash_ref ;
  1810. my @h1_msgs ;
  1811. my $h1_msgs_nb ;
  1812. my $h1_msgs_nb_from_select ;
  1813. $h1_msgs_nb_from_select = count_from_select( $sync->{imap1}->History ) ;
  1814. myprint( "Host1: folder [$h1_fold] has $h1_msgs_nb_from_select messages in total (mentioned by SELECT)\n" ) ;
  1815. if ( $sync->{ skipemptyfolders } and 0 == $h1_msgs_nb_from_select ) {
  1816. myprint( "Host1: skipping empty host1 folder [$h1_fold]\n" ) ;
  1817. next FOLDER ;
  1818. }
  1819. # Code added from https://github.com/imapsync/imapsync/issues/95
  1820. # Thanks jh1995
  1821. # Goal: do not create folder if --search or --max/minage return 0 message.
  1822. # even if there are messages by SELECT (no not real empty, empty for the user point of vue).
  1823. if ( $sync->{ skipemptyfolders } or $sync->{ dry } )
  1824. {
  1825. $h1_msgs_all_hash_ref = { } ;
  1826. @h1_msgs = select_msgs( $sync->{imap1}, $h1_msgs_all_hash_ref, $sync->{ search1 }, $sync->{abletosearch1}, $h1_fold ) ;
  1827. $h1_msgs_nb = scalar( @h1_msgs ) ;
  1828. if ( 0 == $h1_msgs_nb and $sync->{ skipemptyfolders } ) {
  1829. myprint( "Host1: skipping empty host1 folder [$h1_fold] (0 message found by SEARCH)\n" ) ;
  1830. next FOLDER ;
  1831. }
  1832. }
  1833. if ( ! exists $h2_folders_all{ $h2_fold } ) {
  1834. # In --dry mode I could count the messages to be transfered instead of 0
  1835. # Messages transferred : 0 (could be 0 without dry mode)
  1836. if ( ! create_folder( $sync, $sync->{imap2}, $h2_fold, $h1_fold ) )
  1837. {
  1838. if ( $sync->{ dry } )
  1839. {
  1840. $nb_msg_skipped_dry_mode += $h1_msgs_nb ;
  1841. }
  1842. next FOLDER ;
  1843. }
  1844. }
  1845. acls_sync( $sync, $h1_fold, $h2_fold ) ;
  1846. # Sometimes the folder on host2 is listed (it exists) but is
  1847. # not selectable but becomes selectable by a create (Gmail)
  1848. select_folder( $sync, $sync->{imap2}, $h2_fold, 'Host2' )
  1849. or ( create_folder( $sync, $sync->{imap2}, $h2_fold, $h1_fold )
  1850. and select_folder( $sync, $sync->{imap2}, $h2_fold, 'Host2' ) )
  1851. or next FOLDER ;
  1852. my @select_results = $sync->{imap2}->Results( ) ;
  1853. my $h2_fold_nb_messages = count_from_select( @select_results ) ;
  1854. myprint( "Host2: folder [$h2_fold] has $h2_fold_nb_messages messages in total (mentioned by SELECT)\n" ) ;
  1855. $sync->{ permanentflags2 } = permanentflags( $sync, @select_results ) ;
  1856. myprint( "Host2: folder [$h2_fold] permanentflags: $sync->{ permanentflags2 }\n" ) ;
  1857. if ( $sync->{ expunge1 } )
  1858. {
  1859. myprint( "Host1: Expunging $h1_fold $sync->{dry_message}\n" ) ;
  1860. if ( ! $sync->{dry} )
  1861. {
  1862. $sync->{imap1}->expunge( ) ;
  1863. }
  1864. }
  1865. if ( ( ( $subscribe and exists $h1_subscribed_folder{ $h1_fold } ) or $subscribeall )
  1866. and not exists $h2_subscribed_folder{ $h2_fold } )
  1867. {
  1868. myprint( "Host2: Subscribing to folder $h2_fold\n" ) ;
  1869. if ( ! $sync->{dry} ) { $sync->{imap2}->subscribe( $h2_fold ) } ;
  1870. }
  1871. next FOLDER if ( $sync->{ justfolders } ) ;
  1872. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1873. if ( ! defined $h1_msgs_nb )
  1874. {
  1875. $h1_msgs_all_hash_ref = { } ;
  1876. @h1_msgs = select_msgs( $sync->{imap1}, $h1_msgs_all_hash_ref, $sync->{ search1 }, $sync->{abletosearch1}, $h1_fold );
  1877. $h1_msgs_nb = scalar @h1_msgs ;
  1878. }else{
  1879. # select_msgs already done.
  1880. }
  1881. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1882. myprint( "Host1: folder [$h1_fold] considering $h1_msgs_nb messages\n" ) ;
  1883. ( $sync->{ debug } or $sync->{ debuglist } ) and myprint( "Host1: folder [$h1_fold] considering $h1_msgs_nb messages, LIST gives: @h1_msgs\n" ) ;
  1884. $sync->{ debug } and myprint( "Host1: selecting messages of folder [$h1_fold] took ", timenext( $sync ), " s\n" ) ;
  1885. my $h2_msgs_all_hash_ref = { } ;
  1886. my @h2_msgs = select_msgs( $sync->{imap2}, $h2_msgs_all_hash_ref, $sync->{ search2 }, $sync->{abletosearch2}, $h2_fold ) ;
  1887. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1888. my $h2_msgs_nb = scalar @h2_msgs ;
  1889. myprint( "Host2: folder [$h2_fold] considering $h2_msgs_nb messages\n" ) ;
  1890. ( $sync->{ debug } or $sync->{ debuglist } ) and myprint( "Host2: folder [$h2_fold] considering $h2_msgs_nb messages, LIST gives: @h2_msgs\n" ) ;
  1891. $sync->{ debug } and myprint( "Host2: selecting messages of folder [$h2_fold] took ", timenext( $sync ), " s\n" ) ;
  1892. my $cache_base = "$sync->{ tmpdir }/imapsync_cache/" ;
  1893. my $cache_dir = cache_folder( $cache_base,
  1894. "$sync->{host1}/$sync->{user1}/$sync->{host2}/$sync->{user2}", $h1_fold, $h2_fold ) ;
  1895. my ( $cache_1_2_ref, $cache_2_1_ref ) = ( {}, {} ) ;
  1896. my $h1_uidvalidity = $sync->{imap1}->uidvalidity( ) || q{} ;
  1897. my $h2_uidvalidity = $sync->{imap2}->uidvalidity( ) || q{} ;
  1898. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1899. if ( $sync->{ usecache } ) {
  1900. myprint( "Local cache directory: $cache_dir ( " . length( $cache_dir ) . " characters long )\n" ) ;
  1901. mkpath( "$cache_dir" ) ;
  1902. ( $cache_1_2_ref, $cache_2_1_ref )
  1903. = get_cache( $cache_dir, \@h1_msgs, \@h2_msgs, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) ;
  1904. myprint( 'CACHE h1 h2: ', scalar keys %{ $cache_1_2_ref } , " files\n" ) ;
  1905. $sync->{ debug } and myprint( '[',
  1906. map ( { "$_->$cache_1_2_ref->{$_} " } keys %{ $cache_1_2_ref } ), " ]\n" ) ;
  1907. }
  1908. my %h1_hash = ( ) ;
  1909. my %h2_hash = ( ) ;
  1910. my ( %h1_msgs, %h2_msgs ) ;
  1911. @h1_msgs{ @h1_msgs } = ( ) ;
  1912. @h2_msgs{ @h2_msgs } = ( ) ;
  1913. my @h1_msgs_in_cache = sort { $a <=> $b } keys %{ $cache_1_2_ref } ;
  1914. my @h2_msgs_in_cache = sort { $a <=> $b } keys %{ $cache_2_1_ref } ;
  1915. my ( %h1_msgs_not_in_cache, %h2_msgs_not_in_cache ) ;
  1916. %h1_msgs_not_in_cache = %h1_msgs ;
  1917. %h2_msgs_not_in_cache = %h2_msgs ;
  1918. delete @h1_msgs_not_in_cache{ @h1_msgs_in_cache } ;
  1919. delete @h2_msgs_not_in_cache{ @h2_msgs_in_cache } ;
  1920. my @h1_msgs_not_in_cache = sort { $a <=> $b } keys %h1_msgs_not_in_cache ;
  1921. #myprint( "h1_msgs_not_in_cache: [@h1_msgs_not_in_cache]\n" ) ;
  1922. my @h2_msgs_not_in_cache = sort { $a <=> $b } keys %h2_msgs_not_in_cache ;
  1923. my @h2_msgs_delete2_not_in_cache = () ;
  1924. %h1_msgs_copy_by_uid = ( ) ;
  1925. if ( $useuid ) {
  1926. # use uid so we have to avoid getting header
  1927. @h1_msgs_copy_by_uid{ @h1_msgs_not_in_cache } = ( ) ;
  1928. @h2_msgs_delete2_not_in_cache = @h2_msgs_not_in_cache if $sync->{ usecache } ;
  1929. @h1_msgs_not_in_cache = ( ) ;
  1930. @h2_msgs_not_in_cache = ( ) ;
  1931. #myprint( "delete2: @h2_msgs_delete2_not_in_cache\n" ) ;
  1932. }
  1933. if ( $sync->{ debug } or ( 5000 <= scalar( @h1_msgs_not_in_cache ) ) )
  1934. {
  1935. myprint( "Host1: parsing headers of folder [$h1_fold]. It can take time for huge folders. Be patient.\n" ) ;
  1936. }
  1937. my ( $h1_heads_ref, $h1_fir_ref ) = ( {}, {} ) ;
  1938. $h1_heads_ref = $sync->{ imap1 }->parse_headers( [ @h1_msgs_not_in_cache ], @useheader ) if ( @h1_msgs_not_in_cache ) ;
  1939. $sync->{ debug } and myprint( "Host1: parsing headers of folder [$h1_fold] took ", timenext( $sync ), " s\n" ) ;
  1940. @{ $h1_fir_ref }{ @h1_msgs } = ( undef ) ;
  1941. $sync->{ debug } and myprint( "Host1: getting flags idate and sizes of folder [$h1_fold]\n" ) ;
  1942. my @h1_common_fetch_param = ( 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE' ) ;
  1943. if ( $sync->{ synclabels } or $sync->{ resynclabels } ) { push @h1_common_fetch_param, 'X-GM-LABELS' ; }
  1944. if ( $sync->{ abletosearch1 } )
  1945. {
  1946. $h1_fir_ref = $sync->{ imap1 }->fetch_hash( \@h1_msgs, @h1_common_fetch_param, $h1_fir_ref )
  1947. if ( @h1_msgs ) ;
  1948. }
  1949. else
  1950. {
  1951. my $fetch_hash_uids = $fetch_hash_set || "1:*" ;
  1952. $h1_fir_ref = $sync->{ imap1 }->fetch_hash( $fetch_hash_uids, @h1_common_fetch_param, $h1_fir_ref )
  1953. if ( @h1_msgs ) ;
  1954. }
  1955. $sync->{ debug } and myprint( "Host1: getting flags idate and sizes of folder [$h1_fold] took ", timenext( $sync ), " s\n" ) ;
  1956. if ( ! $h1_fir_ref )
  1957. {
  1958. my $error = join( q{}, "Host1: folder $h1_fold : Could not fetch_hash ",
  1959. scalar @h1_msgs, ' msgs: ', $sync->{imap1}->LastError || q{}, "\n" ) ;
  1960. errors_incr( $sync, $error ) ;
  1961. next FOLDER ;
  1962. }
  1963. my @h1_msgs_duplicate;
  1964. foreach my $m ( @h1_msgs_not_in_cache )
  1965. {
  1966. my $rc = parse_header_msg( $sync, $sync->{imap1}, $m, $h1_fold, $h1_heads_ref, $h1_fir_ref, 'Host1', \%h1_hash ) ;
  1967. if ( ! defined $rc )
  1968. {
  1969. my $h1_size = $h1_fir_ref->{$m}->{'RFC822.SIZE'} || 0;
  1970. myprint( "Host1: $h1_fold/$m size $h1_size ignored (no wanted headers so we ignore this message. To solve this: use --addheader)\n" ) ;
  1971. $sync->{ total_bytes_skipped } += $h1_size ;
  1972. $sync->{ nb_msg_skipped } += 1 ;
  1973. $sync->{ h1_nb_msg_noheader } +=1 ;
  1974. $sync->{ h1_nb_msg_processed } +=1 ;
  1975. } elsif( 0 == $rc )
  1976. {
  1977. # duplicate
  1978. push @h1_msgs_duplicate, $m;
  1979. # duplicate, same id same size?
  1980. my $h1_size = $h1_fir_ref->{$m}->{'RFC822.SIZE'} || 0;
  1981. $sync->{ acc1 }->{ nb_msg_duplicate } += 1;
  1982. if ( ! $sync->{ syncduplicates } ) {
  1983. $sync->{ nb_msg_skipped } += 1 ;
  1984. $sync->{ h1_nb_msg_processed } +=1 ;
  1985. }
  1986. }
  1987. }
  1988. my $h1_msgs_duplicate_nb = scalar @h1_msgs_duplicate ;
  1989. myprint( "Host1: folder [$h1_fold] selected $h1_msgs_nb messages, duplicates $h1_msgs_duplicate_nb\n" ) ;
  1990. $sync->{ debug } and myprint( 'Host1: whole time parsing headers took ', timenext( $sync ), " s\n" ) ;
  1991. # Getting headers and metada can be so long that host2 might be disconnected here
  1992. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  1993. if ( $sync->{ debug } or ( 5000 <= scalar( @h2_msgs_not_in_cache ) ) )
  1994. {
  1995. myprint( "Host2: parsing headers of folder [$h2_fold]. It can take time for huge folders. Be patient.\n" ) ;
  1996. }
  1997. my ( $h2_heads_ref, $h2_fir_ref ) = ( {}, {} );
  1998. $h2_heads_ref = $sync->{ imap2 }->parse_headers( [ @h2_msgs_not_in_cache ], @useheader ) if ( @h2_msgs_not_in_cache );
  1999. $sync->{ debug } and myprint( "Host2: parsing headers of folder [$h2_fold] took ", timenext( $sync ), " s\n" ) ;
  2000. $sync->{ debug } and myprint( "Host2: getting flags idate and sizes of folder [$h2_fold]\n" ) ;
  2001. @{ $h2_fir_ref }{ @h2_msgs } = ( ); # fetch_hash can select by uid with last arg as ref
  2002. my @h2_common_fetch_param = ( 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE' ) ;
  2003. if ( $sync->{ synclabels } or $sync->{ resynclabels } ) { push @h2_common_fetch_param, 'X-GM-LABELS' ; }
  2004. if ( $sync->{ abletosearch2 } and scalar( @h2_msgs ) ) {
  2005. $h2_fir_ref = $sync->{ imap2 }->fetch_hash( \@h2_msgs, @h2_common_fetch_param, $h2_fir_ref ) ;
  2006. }else{
  2007. my $fetch_hash_uids = $fetch_hash_set || "1:*" ;
  2008. $h2_fir_ref = $sync->{ imap2 }->fetch_hash( $fetch_hash_uids, @h2_common_fetch_param, $h2_fir_ref )
  2009. if ( @h2_msgs ) ;
  2010. }
  2011. $sync->{ debug } and myprint( "Host2: getting flags idate and sizes of folder [$h2_fold] took ", timenext( $sync ), " s\n" ) ;
  2012. my @h2_msgs_duplicate;
  2013. foreach my $m (@h2_msgs_not_in_cache) {
  2014. my $rc = parse_header_msg( $sync, $sync->{imap2}, $m, $h2_fold, $h2_heads_ref, $h2_fir_ref, 'Host2', \%h2_hash ) ;
  2015. my $h2_size = $h2_fir_ref->{$m}->{'RFC822.SIZE'} || 0 ;
  2016. if (! defined $rc ) {
  2017. myprint( "Host2: $h2_fold/$m size $h2_size ignored (no wanted headers so we ignore this message)\n" ) ;
  2018. $h2_nb_msg_noheader += 1 ;
  2019. } elsif( 0 == $rc ) {
  2020. # duplicate
  2021. $sync->{ acc2 }->{ nb_msg_duplicate } += 1 ;
  2022. push @h2_msgs_duplicate, $m ;
  2023. }
  2024. }
  2025. # %h2_folders_of_md5
  2026. foreach my $md5 ( keys %h2_hash ) {
  2027. $sync->{ h2_folders_of_md5 }->{ $md5 }->{ $h2_fold } ++ ;
  2028. }
  2029. # %h1_folders_of_md5
  2030. foreach my $md5 ( keys %h1_hash ) {
  2031. $sync->{ h1_folders_of_md5 }->{ $md5 }->{ $h2_fold } ++ ;
  2032. }
  2033. my $h2_msgs_duplicate_nb = scalar @h2_msgs_duplicate ;
  2034. myprint( "Host2: folder [$h2_fold] selected $h2_msgs_nb messages, duplicates $h2_msgs_duplicate_nb\n" ) ;
  2035. $sync->{ debug } and myprint( 'Host2 whole time parsing headers took ', timenext( $sync ), " s\n" ) ;
  2036. $sync->{ debug } and myprint( "++++ Verifying [$h1_fold] -> [$h2_fold]\n" ) ;
  2037. # messages in host1 that are not in host2
  2038. my @h1_hash_keys_sorted_by_uid
  2039. = sort {$h1_hash{$a}{'m'} <=> $h1_hash{$b}{'m'}} keys %h1_hash;
  2040. #myprint( map { $h1_hash{$_}{'m'} . q{ }} @h1_hash_keys_sorted_by_uid ) ;
  2041. my @h2_hash_keys_sorted_by_uid
  2042. = sort {$h2_hash{$a}{'m'} <=> $h2_hash{$b}{'m'}} keys %h2_hash;
  2043. # Deletions on account2.
  2044. if( $sync->{ delete2duplicates } and not exists $h2_folders_from_1_several{ $h2_fold } ) {
  2045. my @h2_expunge ;
  2046. foreach my $h2_msg ( @h2_msgs_duplicate ) {
  2047. myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted [duplicate] on host2 $sync->{dry_message}\n" ) ;
  2048. push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 } ;
  2049. if ( ! $sync->{ dry } ) {
  2050. $sync->{ imap2 }->delete_message( $h2_msg ) ;
  2051. $sync->{ acc2 }->{ nb_msg_deleted } += 1 ;
  2052. }
  2053. }
  2054. my $cnt = scalar @h2_expunge ;
  2055. if( @h2_expunge and not $sync->{ expunge2 } ) {
  2056. myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n" ) ;
  2057. $sync->{imap2}->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ;
  2058. }
  2059. if ( $sync->{ expunge2 } ){
  2060. myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n" ) ;
  2061. $sync->{imap2}->expunge( ) if ! $sync->{dry} ;
  2062. }
  2063. }
  2064. if( $sync->{ delete2 } and not exists $h2_folders_from_1_several{ $h2_fold } ) {
  2065. # No host1 folders f1a f1b ... going all to same f2 (via --regextrans2)
  2066. my @h2_expunge;
  2067. foreach my $m_id (@h2_hash_keys_sorted_by_uid) {
  2068. #myprint( "$m_id " ) ;
  2069. if ( ! exists $h1_hash{$m_id} ) {
  2070. my $h2_msg = $h2_hash{$m_id}{'m'};
  2071. my $h2_flags = $h2_hash{$m_id}{'F'} || q{};
  2072. my $isdel = $h2_flags =~ /\B\\Deleted\b/x ? 1 : 0;
  2073. myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted on host2 [$m_id] $sync->{dry_message}\n" )
  2074. if ! $isdel;
  2075. push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 };
  2076. if ( ! ( $sync->{ dry } or $isdel ) ) {
  2077. $sync->{ imap2 }->delete_message( $h2_msg );
  2078. $sync->{ acc2 }->{ nb_msg_deleted } += 1;
  2079. }
  2080. }
  2081. }
  2082. foreach my $h2_msg ( @h2_msgs_delete2_not_in_cache ) {
  2083. myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted [not in cache] on host2 $sync->{dry_message}\n" ) ;
  2084. push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 };
  2085. if ( ! $sync->{dry} ) {
  2086. $sync->{ imap2 }->delete_message( $h2_msg );
  2087. $sync->{ acc2 }->{ nb_msg_deleted } += 1;
  2088. }
  2089. }
  2090. my $cnt = scalar @h2_expunge ;
  2091. if( @h2_expunge and not $sync->{ expunge2 } ) {
  2092. myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n" ) ;
  2093. $sync->{imap2}->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ;
  2094. }
  2095. if ( $sync->{ expunge2 } ) {
  2096. myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n" ) ;
  2097. $sync->{imap2}->expunge( ) if ! $sync->{dry} ;
  2098. }
  2099. }
  2100. if( $sync->{ delete2 } and exists $h2_folders_from_1_several{ $h2_fold } ) {
  2101. myprint( "Host2: folder $h2_fold $h2_folders_from_1_several{ $h2_fold } folders left to sync there\n" ) ;
  2102. my @h2_expunge;
  2103. foreach my $m_id ( @h2_hash_keys_sorted_by_uid ) {
  2104. my $h2_msg = $h2_hash{ $m_id }{ 'm' } ;
  2105. if ( ! exists $h1_hash{ $m_id } ) {
  2106. my $h2_flags = $h2_hash{ $m_id }{ 'F' } || q{} ;
  2107. my $isdel = $h2_flags =~ /\B\\Deleted\b/x ? 1 : 0 ;
  2108. if ( ! $isdel ) {
  2109. $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion [$m_id]\n" ) ;
  2110. $uid_candidate_for_deletion{ $h2_fold }{ $h2_msg }++ ;
  2111. }
  2112. }else{
  2113. $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [$m_id]\n" ) ;
  2114. $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
  2115. }
  2116. }
  2117. foreach my $h2_msg ( @h2_msgs_delete2_not_in_cache ) {
  2118. myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion [not in cache]\n" ) ;
  2119. $uid_candidate_for_deletion{ $h2_fold }{ $h2_msg }++ ;
  2120. }
  2121. foreach my $h2_msg ( @h2_msgs_in_cache ) {
  2122. myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [in cache]\n" ) ;
  2123. $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
  2124. }
  2125. if ( 0 == $h2_folders_from_1_several{ $h2_fold } ) {
  2126. # last host1 folder going to $h2_fold
  2127. myprint( "Last host1 folder going to $h2_fold\n" ) ;
  2128. foreach my $h2_msg ( keys %{ $uid_candidate_for_deletion{ $h2_fold } } ) {
  2129. $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion\n" ) ;
  2130. if ( exists $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg } ) {
  2131. $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg canceled deletion\n" ) ;
  2132. }else{
  2133. myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted $sync->{dry_message}\n" ) ;
  2134. push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 } ;
  2135. if ( ! $sync->{ dry} ) {
  2136. $sync->{ imap2 }->delete_message( $h2_msg ) ;
  2137. $sync->{ acc2 }->{ nb_msg_deleted } += 1 ;
  2138. }
  2139. }
  2140. }
  2141. }
  2142. my $cnt = scalar @h2_expunge ;
  2143. if( @h2_expunge and not $sync->{ expunge2 } ) {
  2144. myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n" ) ;
  2145. $sync->{imap2}->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ;
  2146. }
  2147. if ( $sync->{ expunge2 } ) {
  2148. myprint( "Host2: Expunging host2 folder $h2_fold $sync->{dry_message}\n" ) ;
  2149. $sync->{imap2}->expunge( ) if ! $sync->{dry} ;
  2150. }
  2151. $h2_folders_from_1_several{ $h2_fold }-- ;
  2152. }
  2153. my $h2_uidnext = $sync->{imap2}->uidnext( $h2_fold ) ;
  2154. $sync->{ debug } and myprint( "Host2: uidnext is $h2_uidnext\n" ) ;
  2155. $h2_uidguess = $h2_uidnext ;
  2156. # Getting host2 headers, metada and delete2 stuff can be so long that host1 might be disconnected here
  2157. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  2158. my @h1_msgs_to_delete ;
  2159. MESS: foreach my $m_id (@h1_hash_keys_sorted_by_uid) {
  2160. abortifneeded( $sync ) ;
  2161. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  2162. #myprint( "h1_nb_msg_processed: $sync->{ h1_nb_msg_processed }\n" ) ;
  2163. my $h1_size = $h1_hash{$m_id}{'s'};
  2164. my $h1_msg = $h1_hash{$m_id}{'m'};
  2165. my $h1_idate = $h1_hash{$m_id}{'D'};
  2166. #my $labels = labels( $sync->{imap1}, $h1_msg ) ;
  2167. #print "LABELS: $labels\n" ;
  2168. if ( ( not exists $h2_hash{ $m_id } )
  2169. and ( not ( exists $sync->{ h2_folders_of_md5 }->{ $m_id } )
  2170. or not $sync->{ skipcrossduplicates } ) )
  2171. {
  2172. # copy
  2173. my $h2_msg = copy_message( $sync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $cache_dir ) ;
  2174. if ( $h2_msg and $sync->{ delete1 } and not $sync->{ expungeaftereach } ) {
  2175. # not expunged
  2176. push @h1_msgs_to_delete, $h1_msg ;
  2177. }
  2178. # A bug here with imapsync 1.920, fixed in 1.921
  2179. # Added $h2_msg in the condition. Errors of APPEND were not counted as missing messages on host2!
  2180. if ( $h2_msg and not $sync->{ dry } )
  2181. {
  2182. $sync->{ h2_folders_of_md5 }->{ $m_id }->{ $h2_fold } ++ ;
  2183. }
  2184. #
  2185. if( $sync->{ delete2 } and ( exists $h2_folders_from_1_several{ $h2_fold } ) and $h2_msg ) {
  2186. myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [fresh copy] on host2\n" ) ;
  2187. $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
  2188. }
  2189. if ( total_bytes_max_reached( $sync ) ) {
  2190. # Still a bug when using --delete1 --noexpungeaftereach
  2191. # same thing below on all total_bytes_max_reached!
  2192. last FOLDER ;
  2193. }
  2194. next MESS;
  2195. }
  2196. else
  2197. {
  2198. # already on host2
  2199. if ( exists $h2_hash{ $m_id } )
  2200. {
  2201. my $h2_msg = $h2_hash{$m_id}{'m'} ;
  2202. $sync->{ debug } and myprint( "Host1: found that msg $h1_fold/$h1_msg equals Host2 $h2_fold/$h2_msg\n" ) ;
  2203. if ( $sync->{ usecache } )
  2204. {
  2205. $debugcache and myprint( "touch $cache_dir/${h1_msg}_$h2_msg\n" ) ;
  2206. touch( "$cache_dir/${h1_msg}_$h2_msg" )
  2207. or croak( "Couldn't touch $cache_dir/${h1_msg}_$h2_msg" ) ;
  2208. }
  2209. }
  2210. elsif( exists $sync->{ h2_folders_of_md5 }->{ $m_id } )
  2211. {
  2212. my @folders_dup = keys %{ $sync->{ h2_folders_of_md5 }->{ $m_id } } ;
  2213. ( $sync->{ debug } or $sync->{ debugcrossduplicates } ) and myprint( "Host1: found that msg $h1_fold/$h1_msg is also in Host2 folders @folders_dup\n" ) ;
  2214. $sync->{ h2_nb_msg_crossdup } +=1 ;
  2215. }
  2216. $sync->{ total_bytes_skipped } += $h1_size ;
  2217. $sync->{ nb_msg_skipped } += 1 ;
  2218. $sync->{ h1_nb_msg_processed } +=1 ;
  2219. }
  2220. if ( exists $h2_hash{ $m_id } ) {
  2221. #$debug and myprint( "MESSAGE $m_id\n" ) ;
  2222. my $h2_msg = $h2_hash{$m_id}{'m'};
  2223. if ( $sync->{resyncflags} ) {
  2224. sync_flags_fir( $sync, $h1_fold, $h1_msg, $h2_fold, $h2_msg, $h1_fir_ref, $h2_fir_ref ) ;
  2225. }
  2226. # Good
  2227. my $h2_size = $h2_hash{$m_id}{'s'};
  2228. $sync->{ debug } and myprint(
  2229. "Host1: size msg $h1_fold/$h1_msg = $h1_size <> $h2_size = Host2 $h2_fold/$h2_msg\n" ) ;
  2230. if ( $sync->{ resynclabels } )
  2231. {
  2232. resynclabels( $sync, $h1_msg, $h2_msg, $h1_fir_ref, $h2_fir_ref, $h1_fold )
  2233. }
  2234. }
  2235. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  2236. if ( $sync->{ delete1 } ) {
  2237. push @h1_msgs_to_delete, $h1_msg ;
  2238. }
  2239. }
  2240. # END MESS: loop
  2241. # @h1_msgs_in_cache are already synced too.
  2242. delete_message_on_host1( $sync, $h1_fold, $sync->{ expunge1 }, @h1_msgs_to_delete, @h1_msgs_in_cache ) ;
  2243. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  2244. # MESS_IN_CACHE:
  2245. if ( ! $sync->{ delete1 } )
  2246. {
  2247. foreach my $h1_msg ( @h1_msgs_in_cache )
  2248. {
  2249. my $h2_msg = $cache_1_2_ref->{ $h1_msg } ;
  2250. $debugcache and myprint( "cache messages update flags $h1_msg->$h2_msg\n" ) ;
  2251. if ( $sync->{resyncflags} )
  2252. {
  2253. sync_flags_fir( $sync, $h1_fold, $h1_msg, $h2_fold, $h2_msg, $h1_fir_ref, $h2_fir_ref ) ;
  2254. }
  2255. my $h1_size = $h1_fir_ref->{ $h1_msg }->{ 'RFC822.SIZE' } || 0 ;
  2256. $sync->{ total_bytes_skipped } += $h1_size;
  2257. $sync->{ nb_msg_skipped } += 1;
  2258. $sync->{ h1_nb_msg_processed } +=1 ;
  2259. }
  2260. }
  2261. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  2262. @h1_msgs_to_delete = ( ) ;
  2263. #myprint( "Messages by uid: ", map { "$_ " } keys %h1_msgs_copy_by_uid, "\n" ) ;
  2264. # MESS_BY_UID:
  2265. foreach my $h1_msg ( sort { $a <=> $b } keys %h1_msgs_copy_by_uid )
  2266. {
  2267. abortifneeded( $sync ) ;
  2268. $sync->{ debug } and myprint( "Copy by uid $h1_fold/$h1_msg\n" ) ;
  2269. if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
  2270. my $h2_msg = copy_message( $sync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $cache_dir ) ;
  2271. if( $sync->{ delete2 } and exists $h2_folders_from_1_several{ $h2_fold } and $h2_msg ) {
  2272. myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [fresh copy] on host2\n" ) ;
  2273. $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
  2274. }
  2275. last FOLDER if total_bytes_max_reached( $sync ) ;
  2276. }
  2277. if ( $sync->{ expunge1 } ){
  2278. myprint( "Host1: Expunging folder $h1_fold $sync->{dry_message}\n" ) ;
  2279. if ( ! $sync->{dry} ) { $sync->{imap1}->expunge( ) } ;
  2280. }
  2281. if ( $sync->{ expunge2 } ){
  2282. myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n" ) ;
  2283. if ( ! $sync->{dry} ) { $sync->{imap2}->expunge( ) } ;
  2284. }
  2285. $sync->{ debug } and myprint( 'Time: ', timenext( $sync ), " s\n" ) ;
  2286. }
  2287. eta_print( $sync ) ;
  2288. myprint( "++++ End looping on each folder\n" ) ;
  2289. if ( $sync->{ delete1 } and $sync->{ delete1emptyfolders } ) {
  2290. delete1emptyfolders( $sync ) ;
  2291. }
  2292. ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( 'Time: ', timenext( $sync ), " s\n" ) ;
  2293. if ( $sync->{ foldersizesatend } ) {
  2294. myprint( << 'END_SIZE' ) ;
  2295. Folders sizes after the synchronization.
  2296. You can remove this foldersizes listing by using "--nofoldersizesatend"
  2297. END_SIZE
  2298. foldersizesatend( $sync ) ;
  2299. }
  2300. do_and_print_stats( $sync ) ;
  2301. if ( $sync->{ nb_errors } )
  2302. {
  2303. myprint( errors_listing( $sync ) ) ;
  2304. }
  2305. if ( $sync->{ testslive } or $sync->{ testslive6 } )
  2306. {
  2307. tests_live_result( $sync->{ nb_errors } ) ;
  2308. }
  2309. final_emails_reports( $sync ) ;
  2310. #$sync->{imap1}->State( 0 ); # Unconnected
  2311. if ( ! lost_connection( $sync, $sync->{imap1}, "for host1 [$sync->{host1}]" ) ) { $sync->{imap1}->logout( ) ; }
  2312. if ( ! lost_connection( $sync, $sync->{imap2}, "for host2 [$sync->{host2}]" ) ) { $sync->{imap2}->logout( ) ; }
  2313. if ( $sync->{ nb_errors } )
  2314. {
  2315. my $exit_value = exit_value( $sync, $sync->{ most_common_error } ) ;
  2316. exit_clean( $sync, $exit_value ) ;
  2317. }
  2318. else
  2319. {
  2320. exit_clean( $sync, $EX_OK ) ;
  2321. }
  2322. return ;
  2323. }
  2324. # END of sub single_sync
  2325. # subroutines
  2326. sub myprint
  2327. {
  2328. #print @ARG ;
  2329. print { $sync->{ tee } || \*STDOUT } @ARG ;
  2330. return ;
  2331. }
  2332. sub myprintf
  2333. {
  2334. printf { $sync->{ tee } || \*STDOUT } @ARG ;
  2335. return ;
  2336. }
  2337. sub mysprintf
  2338. {
  2339. my( $format, @list ) = @ARG ;
  2340. return sprintf $format, @list ;
  2341. }
  2342. sub output_start
  2343. {
  2344. my $mysync = shift @ARG ;
  2345. if ( not $mysync ) { return ; }
  2346. my @output = @ARG ;
  2347. $mysync->{ output } = join( q{}, @output ) . ( $mysync->{ output } || q{} ) ;
  2348. return $mysync->{ output } ;
  2349. }
  2350. sub tests_output_start
  2351. {
  2352. note( 'Entering tests_output_start()' ) ;
  2353. my $mysync = { } ;
  2354. is( undef, output_start( ), 'output_start: no args => undef' ) ;
  2355. is( q{}, output_start( $mysync ), 'output_start: one arg => ""' ) ;
  2356. is( 'rrrr', output_start( $mysync, 'rrrr' ), 'output_start: rrrr => rrrr' ) ;
  2357. is( 'aaaarrrr', output_start( $mysync, 'aaaa' ), 'output_start: aaaa => aaaarrrr' ) ;
  2358. is( "\naaaarrrr", output_start( $mysync, "\n" ), 'output_start: \n => \naaaarrrr' ) ;
  2359. is( "ABC\naaaarrrr", output_start( $mysync, 'A', 'B', 'C' ), 'output_start: A B C => ABC\naaaarrrr' ) ;
  2360. note( 'Leaving tests_output_start()' ) ;
  2361. return ;
  2362. }
  2363. sub tests_output
  2364. {
  2365. note( 'Entering tests_output()' ) ;
  2366. my $mysync = { } ;
  2367. is( undef, output( ), 'output: no args => undef' ) ;
  2368. is( q{}, output( $mysync ), 'output: one arg => ""' ) ;
  2369. is( 'rrrr', output( $mysync, 'rrrr' ), 'output: rrrr => rrrr' ) ;
  2370. is( 'rrrraaaa', output( $mysync, 'aaaa' ), 'output: aaaa => rrrraaaa' ) ;
  2371. is( "rrrraaaa\n", output( $mysync, "\n" ), 'output: \n => rrrraaaa\n' ) ;
  2372. is( "rrrraaaa\nABC", output( $mysync, 'A', 'B', 'C' ), 'output: A B C => rrrraaaaABC\n' ) ;
  2373. note( 'Leaving tests_output()' ) ;
  2374. return ;
  2375. }
  2376. sub output
  2377. {
  2378. my $mysync = shift @ARG ;
  2379. if ( not $mysync ) { return ; }
  2380. my @output = @ARG ;
  2381. $mysync->{ output } .= join( q{}, @output ) ;
  2382. return $mysync->{ output } ;
  2383. }
  2384. sub tests_output_reset_with
  2385. {
  2386. note( 'Entering tests_output_reset_with()' ) ;
  2387. my $mysync = { } ;
  2388. is( undef, output_reset_with( ), 'output_reset_with: no args => undef' ) ;
  2389. is( q{}, output_reset_with( $mysync ), 'output_reset_with: one arg => ""' ) ;
  2390. is( 'rrrr', output_reset_with( $mysync, 'rrrr' ), 'output_reset_with: rrrr => rrrr' ) ;
  2391. is( 'aaaa', output_reset_with( $mysync, 'aaaa' ), 'output_reset_with: aaaa => aaaa' ) ;
  2392. is( "\n", output_reset_with( $mysync, "\n" ), 'output_reset_with: \n => \n' ) ;
  2393. note( 'Leaving tests_output_reset_with()' ) ;
  2394. return ;
  2395. }
  2396. sub output_reset_with
  2397. {
  2398. my $mysync = shift @ARG ;
  2399. if ( not $mysync ) { return ; }
  2400. my @output = @ARG ;
  2401. $mysync->{ output } = join( q{}, @output ) ;
  2402. return $mysync->{ output } ;
  2403. }
  2404. sub tests_print_output_if_needed
  2405. {
  2406. note( 'Entering tests_print_output_if_needed()' ) ;
  2407. is( undef, print_output_if_needed( ), 'print_output_if_needed: no args => undef' ) ;
  2408. my $mysync = { } ;
  2409. is( q{}, print_output_if_needed( $mysync ), 'print_output_if_needed: undef => undef' ) ;
  2410. output( $mysync, "Hello\n" ) ;
  2411. is( "Hello\n", print_output_if_needed( $mysync ), 'print_output_if_needed: Hello => Hello' ) ;
  2412. $mysync->{ dockercontext } = 1 ;
  2413. is( "Hello\n", print_output_if_needed( $mysync ), 'print_output_if_needed: dockercontext + Hello => Hello' ) ;
  2414. $mysync->{ version } = 1 ;
  2415. is( q{}, print_output_if_needed( $mysync ), 'print_output_if_needed: dockercontext + Hello + --version => ""' ) ;
  2416. $mysync->{ dockercontext } = 0 ;
  2417. is( "Hello\n", print_output_if_needed( $mysync ), 'print_output_if_needed: Hello + --version => Hello' ) ;
  2418. note( 'Leaving tests_print_output_if_needed()' ) ;
  2419. return ;
  2420. }
  2421. sub print_output_if_needed
  2422. {
  2423. my $mysync = shift @ARG ;
  2424. if ( ! defined $mysync ) { return ; }
  2425. my $output = output( $mysync ) ;
  2426. if ( $mysync->{ version } && under_docker_context( $mysync ) )
  2427. {
  2428. return q{} ;
  2429. }
  2430. else
  2431. {
  2432. myprint( $output ) ;
  2433. return $output ;
  2434. }
  2435. }
  2436. sub stderr_to_stdout
  2437. {
  2438. my $mysync = shift @ARG ;
  2439. if ( $mysync->{ tee} )
  2440. {
  2441. *STDERR = *${ $mysync->{ tee } }{ IO } ;
  2442. }
  2443. else
  2444. {
  2445. *STDERR = *STDOUT ;
  2446. }
  2447. return ;
  2448. }
  2449. sub determine_delete2duplicates
  2450. {
  2451. my $mysync = shift @ARG ;
  2452. if ( defined $mysync->{ delete2duplicates } )
  2453. {
  2454. return $mysync->{ delete2duplicates } ;
  2455. }
  2456. if ( $mysync->{ syncduplicates } )
  2457. {
  2458. return 0 ;
  2459. }
  2460. if ( $sync->{ delete2 } )
  2461. {
  2462. return 1 ;
  2463. }
  2464. return ;
  2465. }
  2466. sub define_pidfile
  2467. {
  2468. my $mysync = shift @ARG ;
  2469. $mysync->{ pidfilelocking } = defined $mysync->{ pidfilelocking } ? $mysync->{ pidfilelocking } : 0 ;
  2470. my $host1 = $mysync->{ host1 } || q{} ;
  2471. my $user1 = $mysync->{ user1 } || q{} ;
  2472. my $host2 = $mysync->{ host2 } || q{} ;
  2473. my $user2 = $mysync->{ user2 } || q{} ;
  2474. my $account1_filtered = filter_forbidden_characters( slash_to_underscore( $host1 . '_' . $user1 ) ) || q{} ;
  2475. my $account2_filtered = filter_forbidden_characters( slash_to_underscore( $host2 . '_' . $user2 ) ) || q{} ;
  2476. my $pidfile_basename ;
  2477. if ( $ENV{ 'NET_SERVER_SOFTWARE' } and ( $ENV{ 'NET_SERVER_SOFTWARE' } =~ /Net::Server::HTTP/ ) )
  2478. {
  2479. # under local webserver
  2480. $pidfile_basename = 'imapsync' . '_' . $account1_filtered . '_' . $account2_filtered . '.pid' ;
  2481. }
  2482. else
  2483. {
  2484. $pidfile_basename = 'imapsync.pid' ;
  2485. }
  2486. $mysync->{ pidfile } = defined $mysync->{ pidfile } ? $mysync-> { pidfile } : $mysync->{ tmpdir } . "/$pidfile_basename" ;
  2487. $mysync->{ abortfile } = abortfile( $mysync, $PROCESS_ID ) ;
  2488. return ;
  2489. }
  2490. sub abortfile
  2491. {
  2492. my $mysync = shift @ARG ;
  2493. my $pid = shift @ARG ;
  2494. my $abortfile ;
  2495. if ( $mysync->{ abort } )
  2496. {
  2497. $abortfile = $mysync->{ pidfile } . "abort$pid" ;
  2498. }
  2499. else
  2500. {
  2501. $abortfile = $mysync->{ pidfile } . "abort$PROCESS_ID" ;
  2502. }
  2503. return $abortfile ;
  2504. }
  2505. sub tests_kill_zero
  2506. {
  2507. note( 'Entering tests_kill_zero()' ) ;
  2508. SKIP: {
  2509. if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests tests_kill_zero avoided on Windows', 8 ) ; }
  2510. is( 1, kill( 'ZERO', $PROCESS_ID ), "kill ZERO : myself $PROCESS_ID => 1" ) ;
  2511. is( 2, kill( 'ZERO', $PROCESS_ID, $PROCESS_ID ), "kill ZERO : myself $PROCESS_ID $PROCESS_ID => 2" ) ;
  2512. if ( (-e '/.dockerenv' ) or ( 0 == $EFFECTIVE_USER_ID) )
  2513. {
  2514. is( 1, kill( 'ZERO', 1 ), "kill ZERO : pid 1 => 1 (docker context or root)" ) ;
  2515. is( 2, kill( 'ZERO', $PROCESS_ID, 1 ), "kill ZERO : myself + pid 1, $PROCESS_ID 1 => 2 (docker context or root)" ) ;
  2516. }
  2517. else
  2518. {
  2519. is( 0, kill( 'ZERO', 1 ), "kill ZERO : pid 1 => 0 (non root)" ) ;
  2520. is( 1, kill( 'ZERO', $PROCESS_ID, 1 ), "kill ZERO : myself + pid 1, $PROCESS_ID 1 => 1 (one is non root)" ) ;
  2521. }
  2522. my $pid_1 = fork( ) ;
  2523. if ( $pid_1 )
  2524. {
  2525. # parent
  2526. }
  2527. else
  2528. {
  2529. # child
  2530. sleep 3 ;
  2531. exit ;
  2532. }
  2533. my $pid_2 ;
  2534. $pid_2 = fork( ) ;
  2535. if ( $pid_2 )
  2536. {
  2537. # I am the parent
  2538. ok( defined( $pid_2 ), "kill_zero: initial fork ok. I am the parent $PROCESS_ID" ) ;
  2539. ok( $pid_2 , "kill_zero: initial fork ok, child pid is $pid_2" ) ;
  2540. is( 3, kill( 'ZERO', $PROCESS_ID, $pid_2, $pid_1 ), "kill ZERO : myself $PROCESS_ID and child $pid_2 and brother $pid_1 => 3" ) ;
  2541. is( $pid_2, waitpid( $pid_2, 0 ), "kill_zero: child $pid_2 no more there => waitpid return $pid_2" ) ;
  2542. }
  2543. else
  2544. {
  2545. # I am the child
  2546. note( 'This one fails under Windows, kill ZERO returns 0 instead of 2' ) ;
  2547. is( 2, kill( 'ZERO', $PROCESS_ID, $pid_1 ), "kill ZERO : myself child $PROCESS_ID brother $pid_1 => 2" ) ;
  2548. myprint( "I am the child pid $PROCESS_ID, Exiting\n" ) ;
  2549. exit ;
  2550. }
  2551. wait( ) ;
  2552. # End of SKIP block
  2553. }
  2554. note( 'Leaving tests_kill_zero()' ) ;
  2555. return ;
  2556. }
  2557. sub tests_killpid_by_parent
  2558. {
  2559. note( 'Entering tests_killpid_by_parent()' ) ;
  2560. SKIP: {
  2561. if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests tests_killpid_by_parent avoided on Windows', 7 ) ; }
  2562. is( undef, killpid( ), 'killpid: no args => undef' ) ;
  2563. note( "killpid: trying to kill myself pid $PROCESS_ID, hope I will not succeed" ) ;
  2564. is( undef, killpid( $PROCESS_ID ), 'killpid: myself => undef' ) ;
  2565. local $SIG{'QUIT'} = sub { myprint "GOT SIG QUIT! I am PID $PROCESS_ID. Exiting\n" ; exit ; } ;
  2566. my $pid ;
  2567. $pid = fork( ) ;
  2568. if ( $pid )
  2569. {
  2570. # I am the parent
  2571. ok( defined( $pid ), "killpid: initial fork ok. I am the parent $PROCESS_ID" ) ;
  2572. ok( $pid , "killpid: initial fork ok, child pid is $pid" ) ;
  2573. is( 2, kill( 'ZERO', $PROCESS_ID, $pid ), "kill ZERO : myself $PROCESS_ID and child $pid => 2" ) ;
  2574. is( 1, killpid( $pid ), "killpid: child $pid killed => 1" ) ;
  2575. is( -1, waitpid( $pid, 0 ), "killpid: child $pid no more there => waitpid return -1" ) ;
  2576. }
  2577. else
  2578. {
  2579. # I am the child
  2580. myprint( "I am the child pid $PROCESS_ID, sleeping 1 + 3 seconds then kill myself\n" ) ;
  2581. sleep 1 ;
  2582. myprint( "I am the child pid $PROCESS_ID, slept 1 second, should be killed by my parent now, PPID " . mygetppid( ) . "\n" ) ;
  2583. sleep 3 ;
  2584. # this test should not be run. If it happens => failure.
  2585. ok( 0 == 1, "killpid: child pid $PROCESS_ID not dead => failure" ) ;
  2586. myprint( "I am the child pid $PROCESS_ID, killing myself failure... Exiting\n" ) ;
  2587. exit ;
  2588. }
  2589. # End of SKIP block
  2590. }
  2591. note( 'Leaving tests_killpid_by_parent()' ) ;
  2592. return ;
  2593. }
  2594. sub tests_killpid_by_brother
  2595. {
  2596. note( 'Entering tests_killpid_by_brother()' ) ;
  2597. SKIP: {
  2598. if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests tests_killpid_by_brother avoided on Windows', 2 ) ; }
  2599. local $SIG{'QUIT'} = sub { myprint "GOT SIG QUIT! I am PID $PROCESS_ID. Exiting\n" ; exit ; } ;
  2600. my $pid_parent = $PROCESS_ID ;
  2601. myprint( "I am the parent pid $pid_parent\n" ) ;
  2602. my $pid_1 = fork( ) ;
  2603. if ( $pid_1 )
  2604. {
  2605. # parent
  2606. }
  2607. else
  2608. {
  2609. # child
  2610. #while ( 1 ) { } ;
  2611. sleep 2 ;
  2612. sleep 2 ;
  2613. # this test should not be run. If it happens => failure.
  2614. # Well under Windows this always fails, shit!
  2615. ok( 0 == 1 or ( 'MSWin32' eq $OSNAME ) , "killpid: child pid $PROCESS_ID killing by brother but not dead => failure" ) ;
  2616. myprint( "I am the child pid $PROCESS_ID, killing by brother failed... Exiting\n" ) ;
  2617. exit ;
  2618. }
  2619. my $pid_2 ;
  2620. $pid_2 = fork( ) ;
  2621. if ( $pid_2 )
  2622. {
  2623. # parent
  2624. }
  2625. else
  2626. {
  2627. # I am the child
  2628. myprint( "I am the child pid $PROCESS_ID, my brother has pid $pid_1\n" ) ;
  2629. is( 1, killpid( $pid_1 ), "killpid: brother $pid_1 killed => 1" ) ;
  2630. sleep 2 ;
  2631. exit ;
  2632. }
  2633. #sleep 1 ;
  2634. is( $pid_1, waitpid( $pid_1, 0), "I am the parent $PROCESS_ID waitpid _1( $pid_1 )" ) ;
  2635. is( $pid_2, waitpid( $pid_2, 0 ), "I am the parent $PROCESS_ID waitpid _2( $pid_2 )" ) ;
  2636. # End of SKIP block
  2637. }
  2638. note( 'Leaving tests_killpid_by_brother()' ) ;
  2639. return ;
  2640. }
  2641. sub killpid
  2642. {
  2643. my $pidtokill = shift @ARG ;
  2644. if ( ! $pidtokill ) {
  2645. myprint( "No process to kill.\n" ) ;
  2646. return ;
  2647. }
  2648. if ( $PROCESS_ID == $pidtokill ) {
  2649. myprint( "I will not kill myself pid $PROCESS_ID via killpid. Sractch it!\n" ) ;
  2650. return ;
  2651. }
  2652. # First ask for suicide
  2653. if ( kill( 'ZERO', $pidtokill ) or ( 'MSWin32' eq $OSNAME ) ) {
  2654. myprint( "Sending signal QUIT to PID $pidtokill \n" ) ;
  2655. kill 'QUIT', $pidtokill ;
  2656. sleep 3 ;
  2657. waitpid( $pidtokill, WNOHANG) ;
  2658. }else{
  2659. myprint( "Can not send signal kill ZERO to PID $pidtokill.\n" ) ;
  2660. return ;
  2661. }
  2662. #while ( waitpid( $pidtokill, WNOHANG) > 0 ) { } ;
  2663. # Then murder
  2664. if ( kill( 'ZERO', $pidtokill ) or ( 'MSWin32' eq $OSNAME ) ) {
  2665. myprint( "Sending signal KILL to PID $pidtokill \n" ) ;
  2666. kill 'KILL', $pidtokill ;
  2667. sleep 1 ;
  2668. waitpid( $pidtokill, WNOHANG) ;
  2669. }else{
  2670. myprint( "Process PID $pidtokill ended.\n" ) ;
  2671. return 1;
  2672. }
  2673. # Well ...
  2674. if ( kill( 'ZERO', $pidtokill ) or ( 'xMSWin32' eq $OSNAME ) ) {
  2675. myprint( "Process PID $pidtokill seems still there. Can not do much.\n" ) ;
  2676. return ;
  2677. }else{
  2678. myprint( "Process PID $pidtokill ended.\n" ) ;
  2679. return 1;
  2680. }
  2681. return ;
  2682. }
  2683. sub tests_abort
  2684. {
  2685. note( 'Entering tests_abort()' ) ;
  2686. # Well, the abort behavior is tested by test.sh
  2687. is( undef, abort( ), 'abort: no args => undef' ) ;
  2688. note( 'Leaving tests_abort()' ) ;
  2689. return ;
  2690. }
  2691. sub abort
  2692. {
  2693. my $mysync = shift @ARG ;
  2694. myprint( "In abort\n" ) ;
  2695. if ( not $mysync ) { return ; }
  2696. if ( ! -r $mysync->{pidfile} ) {
  2697. myprint( "In abort: Can not read pidfile $mysync->{pidfile}\n" ) ;
  2698. return ;
  2699. }
  2700. my $pidtokill = firstline( $mysync->{pidfile} ) ;
  2701. if ( ! $pidtokill ) {
  2702. myprint( "In abort: No process to abort in $mysync->{pidfile}\n" ) ;
  2703. return ;
  2704. }
  2705. if ( ! match_a_pid_number( $pidtokill ) )
  2706. {
  2707. myprint( "In abort: pid $pidtokill in $mysync->{pidfile} is not a pid number\n" ) ;
  2708. return ;
  2709. }
  2710. if ( $mysync->{abortbyfile} )
  2711. {
  2712. abortbyfile( $mysync, $pidtokill ) ;
  2713. }
  2714. else
  2715. {
  2716. killpid( $pidtokill ) ;
  2717. }
  2718. return ;
  2719. }
  2720. sub abortbyfile
  2721. {
  2722. my $mysync = shift @ARG ;
  2723. my $pidtokill = shift @ARG ;
  2724. my $abortfile = abortfile( $mysync, $pidtokill ) ;
  2725. myprint( "touching $abortfile\n" ) ;
  2726. touch( $abortfile ) ;
  2727. return ;
  2728. }
  2729. sub tests_under_docker_context
  2730. {
  2731. note( 'Entering tests_under_docker_context()' ) ;
  2732. is( undef, under_docker_context( ), 'under_docker_context: no args => undef' ) ;
  2733. my $mysync = { } ;
  2734. $mysync->{ dockercontext } = 1 ;
  2735. is( 1, under_docker_context( $mysync ), 'under_docker_context: --dockercontext => 1' ) ;
  2736. $mysync->{ dockercontext } = 0 ;
  2737. is( 0, under_docker_context( $mysync ), 'under_docker_context: --nodockercontext => 0' ) ;
  2738. $mysync = { } ;
  2739. # Is not it a stupid test?
  2740. if ( under_docker_context( $mysync ) )
  2741. {
  2742. is( 1, under_docker_context( $mysync ), 'under_docker_context: docker context => 1' ) ;
  2743. }
  2744. else
  2745. {
  2746. is( 0, under_docker_context( $mysync ), 'under_docker_context: not docker context => 0' ) ;
  2747. }
  2748. note( 'Leaving tests_under_docker_context()' ) ;
  2749. return ;
  2750. }
  2751. sub under_docker_context
  2752. {
  2753. my $mysync = shift @ARG ;
  2754. if ( ! defined $mysync ) { return ; }
  2755. if ( defined $mysync->{ dockercontext } )
  2756. {
  2757. return( $mysync->{ dockercontext } ) ;
  2758. }
  2759. if ( -e '/.dockerenv' )
  2760. {
  2761. return 1 ;
  2762. }
  2763. else
  2764. {
  2765. return 0 ;
  2766. }
  2767. return ;
  2768. }
  2769. sub docker_context
  2770. {
  2771. my $mysync = shift @ARG ;
  2772. if ( ! under_docker_context( $mysync ) )
  2773. {
  2774. return ;
  2775. }
  2776. output( $mysync, "Docker context detected with the file /.dockerenv\n" ) ;
  2777. # No pidfile by default
  2778. $mysync->{ pidfile } = defined( $mysync->{ pidfile } ) ? $mysync->{ pidfile } : q{} ;
  2779. # No log by default
  2780. if ( defined( $mysync->{ log } ) )
  2781. {
  2782. 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" ) ;
  2783. }
  2784. else
  2785. {
  2786. output( $mysync, "No log by default in Docker context. Use --log to trigger logging to the logfile.\n" ) ;
  2787. $mysync->{ log } = 0 ;
  2788. }
  2789. # In case something is written relatively to .
  2790. my $tmp_dir = "/var/tmp/uid_$EFFECTIVE_USER_ID" ;
  2791. mkpath( $tmp_dir ) ; # silly? No. it is for imapsync --version being ok.
  2792. do_valid_directory( $tmp_dir ) ;
  2793. output( $mysync, "Changing current directory to $tmp_dir\n" ) ;
  2794. chdir $tmp_dir ;
  2795. return ;
  2796. }
  2797. sub cgibegin
  2798. {
  2799. my $mysync = shift @ARG ;
  2800. if ( ! under_cgi_context( $mysync ) ) { return ; }
  2801. require CGI ;
  2802. CGI->import( qw( -no_debug -utf8 ) ) ;
  2803. require CGI::Carp ;
  2804. CGI::Carp->import( qw( fatalsToBrowser ) ) ;
  2805. $mysync->{cgi} = CGI->new( ) ;
  2806. return ;
  2807. }
  2808. sub tests_under_cgi_context
  2809. {
  2810. note( 'Entering tests_under_cgi_context()' ) ;
  2811. # $ENV{SERVER_SOFTWARE} = 'under imapsync' ;
  2812. do {
  2813. # Not in cgi context
  2814. delete local $ENV{SERVER_SOFTWARE} ;
  2815. is( undef, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE unset => not in cgi context' ) ;
  2816. } ;
  2817. do {
  2818. # In cgi context
  2819. local $ENV{SERVER_SOFTWARE} = 'under imapsync' ;
  2820. is( 1, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE set => in cgi context' ) ;
  2821. } ;
  2822. do {
  2823. # Not in cgi context
  2824. delete local $ENV{SERVER_SOFTWARE} ;
  2825. is( undef, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE unset => not in cgi context' ) ;
  2826. } ;
  2827. do {
  2828. # In cgi context
  2829. local $ENV{SERVER_SOFTWARE} = 'under imapsync' ;
  2830. is( 1, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE set => in cgi context' ) ;
  2831. } ;
  2832. note( 'Leaving tests_under_cgi_context()' ) ;
  2833. return ;
  2834. }
  2835. sub under_cgi_context
  2836. {
  2837. my $mysync = shift @ARG ;
  2838. # Under cgi context
  2839. if ( $ENV{SERVER_SOFTWARE} ) {
  2840. return 1 ;
  2841. }
  2842. # Not in cgi context
  2843. return ;
  2844. }
  2845. sub cgibuildheader
  2846. {
  2847. my $mysync = shift @ARG ;
  2848. if ( ! under_cgi_context( $mysync ) ) { return ; }
  2849. my $imapsync_runs = $mysync->{cgi}->cookie( 'imapsync_runs' ) || 0 ;
  2850. my $cookie = $mysync->{cgi}->cookie(
  2851. -name => 'imapsync_runs',
  2852. -value => 1 + $imapsync_runs,
  2853. -expires => '+20y',
  2854. -path => '/cgi-bin/imapsync',
  2855. ) ;
  2856. my $httpheader ;
  2857. if ( $mysync->{ abort } )
  2858. {
  2859. $httpheader = $mysync->{cgi}->header(
  2860. -type => 'text/plain; charset=UTF-8',
  2861. -status => '200 OK to abort syncing IMAP boxes. ' . load_message( $mysync ),
  2862. ) ;
  2863. }
  2864. elsif( $mysync->{ heavy_load_reached } )
  2865. {
  2866. # https://tools.ietf.org/html/rfc2616#section-10.5.4
  2867. # 503 Service Unavailable
  2868. # The server is currently unable to handle the request due to a temporary overloading or maintenance of the server.
  2869. $httpheader = $mysync->{cgi}->header(
  2870. -type => 'text/plain; charset=UTF-8',
  2871. -status => '503 Service Unavailable. ' . "Be back later. " . load_message( $mysync ),
  2872. ) ;
  2873. }
  2874. else
  2875. {
  2876. $httpheader = $mysync->{cgi}->header(
  2877. -type => 'text/plain; charset=UTF-8',
  2878. -status => '200 OK to sync IMAP boxes. ' . load_message( $mysync ),
  2879. -cookie => $cookie,
  2880. ) ;
  2881. }
  2882. output_start( $mysync, $httpheader ) ;
  2883. return ;
  2884. }
  2885. sub load_message
  2886. {
  2887. my $mysync = shift @ARG ;
  2888. my $message = "Load on " . hostname() . " is $mysync->{ loadavg }" ;
  2889. return $message ;
  2890. }
  2891. sub cgi_exit_on_heavy_load
  2892. {
  2893. # Exit on heavy load in CGI context
  2894. my $mysync = shift @ARG ;
  2895. if ( ! under_cgi_context( $mysync ) ) { return ; }
  2896. if ( $mysync->{ abort } ) { return ; } # keep going to abort since some ressources will be free soon
  2897. if ( $mysync->{ heavy_load_reached } )
  2898. {
  2899. $mysync->{ nb_errors }++ ;
  2900. exit_clean( $mysync, $EX_UNAVAILABLE,
  2901. "Server is on heavy load. Be back later. " . load_message( $mysync ) . "\n"
  2902. ) ;
  2903. }
  2904. return ;
  2905. }
  2906. sub tests_heavy_load_reached
  2907. {
  2908. note( 'Entering tests_is_heavy_load_reached()' ) ;
  2909. like( heavy_load_reached( ), qr{^(0|1)$}xms, 'heavy_load_reached: no args => 0 or 1' ) ;
  2910. my $mysync = { } ;
  2911. like( heavy_load_reached( $mysync ), qr{^(0|1)$}xms, 'heavy_load_reached: { } => 0 or 1' ) ;
  2912. $mysync->{ exitonload } = 0 ;
  2913. is( 0, heavy_load_reached( $mysync ), 'heavy_load_reached: exitonload=0 => 0 ' ) ;
  2914. note( 'Leaving tests_heavy_load_reached()' ) ;
  2915. return ;
  2916. }
  2917. sub heavy_load_reached
  2918. {
  2919. my $mysync = shift @ARG ;
  2920. my $heavy_load_reached = 0 ;
  2921. if ( defined( $mysync->{ exitonload } ) && ( ! $mysync->{ exitonload } ) )
  2922. {
  2923. return 0 ;
  2924. }
  2925. my $heavy_load_reached_by_memory = heavy_load_reached_by_memory( $mysync ) ;
  2926. my $heavy_load_reached_by_cpu = heavy_load_reached_by_cpu( $mysync ) ;
  2927. if ( $heavy_load_reached_by_memory || $heavy_load_reached_by_cpu )
  2928. {
  2929. $heavy_load_reached = 1 ;
  2930. }
  2931. else
  2932. {
  2933. $heavy_load_reached = 0 ;
  2934. }
  2935. return $heavy_load_reached ;
  2936. }
  2937. sub tests_heavy_load_reached_by_cpu
  2938. {
  2939. note( 'Entering tests_heavy_load_reached_by_cpu()' ) ;
  2940. note( join( " ", loadavg( ), 'heavy_load_reached_by_cpu ', heavy_load_reached_by_cpu( ) ) ) ;
  2941. like( heavy_load_reached_by_cpu( ), qr{^(0|1)$}xms, 'heavy_load_reached_by_cpu: no args => 0 or 1' ) ;
  2942. my $mysync = { } ;
  2943. like( heavy_load_reached_by_cpu( $mysync ), qr{^(0|1)$}xms, 'heavy_load_reached_by_cpu: { } => 0 or 1' ) ;
  2944. note( 'Leaving tests_heavy_load_reached_by_cpu()' ) ;
  2945. return ;
  2946. }
  2947. sub heavy_load_reached_by_cpu
  2948. {
  2949. my $mysync = shift @ARG ;
  2950. my $heavy_load_reached = 0 ;
  2951. my $load_and_delay = load_per_cpu( $mysync ) ;
  2952. if ( $load_and_delay )
  2953. {
  2954. $heavy_load_reached = 1 ;
  2955. }
  2956. else
  2957. {
  2958. $heavy_load_reached = 0 ;
  2959. }
  2960. return $heavy_load_reached ;
  2961. }
  2962. sub tests_load_per_cpu
  2963. {
  2964. note( 'Entering tests_load_per_cpu()' ) ;
  2965. note( join( " ", 'loadavg:', loadavg( ), 'cpu_number:', cpu_number( ), 'load_per_cpu ', load_per_cpu( ) ) ) ;
  2966. like( load_per_cpu( ), qr{^([0-9.]+)$}xms, 'load_per_cpu: no args => number' ) ;
  2967. my $mysync = { } ;
  2968. like( load_per_cpu( $mysync ), qr{^([0-9.]+)$}xms, 'load_per_cpu: { } => number' ) ;
  2969. note( 'Leaving tests_load_per_cpu()' ) ;
  2970. return ;
  2971. }
  2972. sub load_per_cpu
  2973. {
  2974. my $mysync = shift @ARG ;
  2975. return load_and_delay( 1, cpu_number( ), loadavg( ) ) ;
  2976. }
  2977. sub tests_heavy_load_reached_by_memory
  2978. {
  2979. note( 'Entering tests_heavy_load_reached_by_memory()' ) ;
  2980. like( heavy_load_reached_by_memory( ), qr{^(0|1)$}xms, 'heavy_load_reached_by_memory: no args => 0 or 1' ) ;
  2981. my $mysync = { } ;
  2982. like( heavy_load_reached_by_memory( $mysync ), qr{^(0|1)$}xms, 'heavy_load_reached_by_memory: { } => 0 or 1' ) ;
  2983. note( 'Leaving tests_heavy_load_reached_by_memory()' ) ;
  2984. return ;
  2985. }
  2986. sub heavy_load_reached_by_memory
  2987. {
  2988. my $mysync = shift @ARG ;
  2989. my $heavy_load_reached = 0 ;
  2990. my $heavy_load_percent_threshold = heavy_load_percent_threshold( $mysync ) ;
  2991. my $memory_consumption_all_pids_percent = memory_consumption_all_pids_percent( $mysync ) ;
  2992. if ( $memory_consumption_all_pids_percent > $heavy_load_percent_threshold )
  2993. {
  2994. $heavy_load_reached = 1 ;
  2995. }
  2996. else
  2997. {
  2998. $heavy_load_reached = 0 ;
  2999. }
  3000. return $heavy_load_reached ;
  3001. }
  3002. sub tests_heavy_load_percent_threshold
  3003. {
  3004. note( 'Entering tests_heavy_load_percent_threshold()' ) ;
  3005. note( heavy_load_percent_threshold( ) . " (%)" ) ;
  3006. like( heavy_load_percent_threshold( ), qr{^\d+$}xms, 'heavy_load_percent_threshold: no args => integer' ) ;
  3007. my $mysync = { } ;
  3008. like( heavy_load_percent_threshold( ), qr{^\d+$}xms, 'heavy_load_percent_threshold: { } => integer' ) ;
  3009. note( 'Leaving tests_heavy_load_percent_threshold()' ) ;
  3010. return ;
  3011. }
  3012. sub heavy_load_percent_threshold
  3013. {
  3014. my $mysync = shift @ARG ;
  3015. my $total_memory_bytes = total_ram_memory_bytes( ) || return 0 ;
  3016. my $memory_footprint_average_bytes = 250_000_000 ;
  3017. my $heavy_load_percent_threshold = max( 0, int( 100 * ( $total_memory_bytes - ( 4 * $memory_footprint_average_bytes ) ) / $total_memory_bytes ) ) ;
  3018. return $heavy_load_percent_threshold ;
  3019. }
  3020. sub tests_set_umask
  3021. {
  3022. note( 'Entering tests_set_umask()' ) ;
  3023. my $save_umask = umask ;
  3024. my $mysync = {} ;
  3025. if ( 'MSWin32' eq $OSNAME ) {
  3026. is( undef, set_umask( $mysync ), "set_umask: set failure to $UMASK_PARANO on MSWin32" ) ;
  3027. }else{
  3028. is( 1, set_umask( $mysync ), "set_umask: set to $UMASK_PARANO" ) ;
  3029. }
  3030. umask $save_umask ;
  3031. note( 'Leaving tests_set_umask()' ) ;
  3032. return ;
  3033. }
  3034. sub set_umask
  3035. {
  3036. my $mysync = shift @ARG ;
  3037. my $previous_umask = umask_str( ) ;
  3038. my $new_umask = umask_str( $UMASK_PARANO ) ;
  3039. output( $mysync, "Umask set with $new_umask (was $previous_umask)\n" ) ;
  3040. if ( $new_umask eq $UMASK_PARANO ) {
  3041. return 1 ;
  3042. }
  3043. return ;
  3044. }
  3045. sub tests_umask_str
  3046. {
  3047. note( 'Entering tests_umask_str()' ) ;
  3048. my $save_umask = umask ;
  3049. is( umask_str( ), umask_str( ), 'umask_str: no parameters => idopotent' ) ;
  3050. is( my $save_umask_str = umask_str( ), umask_str( ), 'umask_str: no parameters => idopotent + save' ) ;
  3051. is( '0000', umask_str( q{ } ), 'umask_str: q{ } => 0000' ) ;
  3052. is( '0000', umask_str( q{} ), 'umask_str: q{} => 0000' ) ;
  3053. is( '0000', umask_str( '0000' ), 'umask_str: 0000 => 0000' ) ;
  3054. is( '0000', umask_str( '0' ), 'umask_str: 0 => 0000' ) ;
  3055. is( '0200', umask_str( '0200' ), 'umask_str: 0200 => 0200' ) ;
  3056. is( '0400', umask_str( '0400' ), 'umask_str: 0400 => 0400' ) ;
  3057. is( '0600', umask_str( '0600' ), 'umask_str: 0600 => 0600' ) ;
  3058. SKIP: {
  3059. if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests success only for Unix', 6 ) ; }
  3060. is( '0100', umask_str( '0100' ), 'umask_str: 0100 => 0100' ) ;
  3061. is( '0001', umask_str( '0001' ), 'umask_str: 0001 => 0001' ) ;
  3062. is( '0777', umask_str( '0777' ), 'umask_str: 0777 => 0777' ) ;
  3063. is( '0777', umask_str( '00777' ), 'umask_str: 00777 => 0777' ) ;
  3064. is( '0777', umask_str( ' 777 ' ), 'umask_str: 777 => 0777' ) ;
  3065. is( "$UMASK_PARANO", umask_str( $UMASK_PARANO ), "umask_str: UMASK_PARANO $UMASK_PARANO => $UMASK_PARANO" ) ;
  3066. }
  3067. is( $save_umask_str, umask_str( $save_umask_str ), 'umask_str: restore with str' ) ;
  3068. is( $save_umask, umask, 'umask_str: umask is restored, controlled by direct umask' ) ;
  3069. is( $save_umask, umask $save_umask, 'umask_str: umask is restored by direct umask' ) ;
  3070. is( $save_umask, umask, 'umask_str: umask initial controlled by direct umask' ) ;
  3071. note( 'Leaving tests_umask_str()' ) ;
  3072. return ;
  3073. }
  3074. sub umask_str
  3075. {
  3076. my $value = shift @ARG ;
  3077. if ( defined $value ) {
  3078. umask oct( $value ) ;
  3079. }
  3080. my $current = umask ;
  3081. return( sprintf( '%#04o', $current ) ) ;
  3082. }
  3083. sub tests_umask
  3084. {
  3085. note( 'Entering tests_umask()' ) ;
  3086. my $save_umask ;
  3087. is( umask, umask, 'umask: umask is umask' ) ;
  3088. is( $save_umask = umask, umask, "umask: umask is umask again + save it: $save_umask" ) ;
  3089. is( $save_umask, umask oct(0000), 'umask: umask 0000' ) ;
  3090. is( oct(0000), umask, 'umask: umask is now 0000' ) ;
  3091. is( oct(0000), umask oct(777), 'umask: umask 0777 call, previous 0000' ) ;
  3092. SKIP: {
  3093. if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests success only for Unix', 2 ) ; }
  3094. is( oct(777), umask, 'umask: umask is now 0777' ) ;
  3095. is( oct(777), umask $save_umask, "umask: umask $save_umask restore inital value, previous 0777" ) ;
  3096. }
  3097. ok( defined umask $save_umask, "umask: umask $save_umask restore inital value, previous defined" ) ;
  3098. is( $save_umask, umask, 'umask: umask is umask restored' ) ;
  3099. note( 'Leaving tests_umask()' ) ;
  3100. return ;
  3101. }
  3102. sub make_var_array_to_a_hash
  3103. {
  3104. my $mysync = shift @ARG ;
  3105. %{ $mysync->{ varh } } = split_around_equal( @{ $mysync->{ var } } ) ;
  3106. return ;
  3107. }
  3108. sub cgisetcontext
  3109. {
  3110. my $mysync = shift @ARG ;
  3111. if ( ! under_cgi_context( $mysync ) ) { return ; }
  3112. output( $mysync, "Under cgi context\n" ) ;
  3113. set_umask( $mysync ) ;
  3114. # Remove all content in unsafe evaled options
  3115. @{ $mysync->{ regextrans2 } } = ( ) ;
  3116. @{ $mysync->{ regexflag } } = buggyflagsregex( ) ;
  3117. @regexmess = ( ) ;
  3118. @skipmess = ( ) ;
  3119. @pipemess = ( ) ;
  3120. $delete2foldersonly = undef ;
  3121. $delete2foldersbutnot = undef ;
  3122. $maxlinelengthcmd = undef ;
  3123. # Set safe default values (I hope...)
  3124. #$mysync->{pidfile} = 'imapsync.pid' ;
  3125. $mysync->{ pidfilelocking } = 1 ;
  3126. $mysync->{ errorsmax } = $ERRORS_MAX_CGI ;
  3127. $modulesversion = 0 ;
  3128. $mysync->{ releasecheck } = defined $mysync->{ releasecheck } ? $mysync->{ releasecheck } : 1 ;
  3129. $mysync->{ usecache } = 0 ;
  3130. $mysync->{ showpasswords } = 0 ;
  3131. $mysync->{ acc1 }->{ debugimap } = 0 ;
  3132. $mysync->{ acc2 }->{ debugimap } = 0 ;
  3133. $mysync->{ acc1 }->{ reconnectretry } = $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ;
  3134. $mysync->{ acc2 }->{ reconnectretry } = $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ;
  3135. $pipemesscheck = 0 ;
  3136. $mysync->{ hashfile } = $CGI_HASHFILE ;
  3137. my $hashsynclocal = hashsynclocal( $mysync ) || die "Can not get hashsynclocal. Exiting\n" ;
  3138. if ( $ENV{ 'NET_SERVER_SOFTWARE' } and ( $ENV{ 'NET_SERVER_SOFTWARE' } =~ /Net::Server::HTTP/ ) )
  3139. {
  3140. # under local webserver
  3141. $cgidir = q{.} ;
  3142. }
  3143. else
  3144. {
  3145. $cgidir = $CGI_TMPDIR_TOP . '/' . $hashsynclocal ;
  3146. }
  3147. -d $cgidir or mkpath $cgidir or die "Can not create $cgidir: $OS_ERROR\n" ;
  3148. $mysync->{ tmpdir } = $cgidir ;
  3149. $mysync->{ logdir } = '' ;
  3150. chdir $cgidir or die "Can not cd to $cgidir: $OS_ERROR\n" ;
  3151. output( $mysync, cgienvcontext( $mysync ) ) ;
  3152. $mysync->{ debug } and output( $mysync, 'Current directory is ' . getcwd( ) . "\n" ) ;
  3153. $mysync->{ debug } and output( $mysync, 'Real user id is ' . getpwuid_any_os( $REAL_USER_ID ) . " (uid $REAL_USER_ID)\n" ) ;
  3154. $mysync->{ debug } and output( $mysync, 'Effective user id is ' . getpwuid_any_os( $EFFECTIVE_USER_ID ). " (euid $EFFECTIVE_USER_ID)\n" ) ;
  3155. $mysync->{ skipemptyfolders } = defined $mysync->{ skipemptyfolders } ? $mysync->{ skipemptyfolders } : 1 ;
  3156. # Out of memory with messages over 1 GB ?
  3157. $mysync->{ maxsize } = defined $mysync->{ maxsize } ? $mysync->{ maxsize } : 1_000_000_000 ;
  3158. # tail -f behaviour on by default
  3159. $mysync->{ tail } = defined $mysync->{ tail } ? $mysync->{ tail } : 1 ;
  3160. # not sure it's for good
  3161. @useheader = qw( Message-Id Received ) ;
  3162. # addheader on by default
  3163. $mysync->{ addheader } = defined $mysync->{ addheader } ? $mysync->{ addheader } : 1 ;
  3164. # sync duplicates by default in cgi context
  3165. $mysync->{ syncduplicates } = defined $mysync->{ syncduplicates } ? $mysync->{ syncduplicates } : 1 ;
  3166. # log the logfile name by default in cgi context
  3167. $mysync->{ loglogfile } = defined $mysync->{ loglogfile } ? $mysync->{ loglogfile } : 1 ;
  3168. # exit on heavy load
  3169. $mysync->{ exitonload } = defined $mysync->{ exitonload } ? $mysync->{ exitonload } : 1 ;
  3170. return ;
  3171. }
  3172. sub tests_cgienvcontext
  3173. {
  3174. note( 'Entering tests_cgienvcontext()' ) ;
  3175. is( '', cgienvcontext( ), 'cgienvcontext: no args => empty' ) ;
  3176. my $mysync = { } ;
  3177. is( '', cgienvcontext( $mysync ), 'cgienvcontext: undef => empty' ) ;
  3178. # environment SERVER_SOFTWARE alone
  3179. local $ENV{SERVER_SOFTWARE} = 'Chateau Lami' ;
  3180. is( "SERVER_SOFTWARE is Chateau Lami\n", cgienvcontext( $mysync ), 'cgienvcontext: SERVER_SOFTWARE=Chateau Lami' ) ;
  3181. # environment REMOTE_HOST and SERVER_SOFTWARE
  3182. $mysync = { } ;
  3183. local $ENV{REMOTE_HOST} = 'Votre serviteur' ;
  3184. is( "REMOTE_HOST is Votre serviteur\nSERVER_SOFTWARE is Chateau Lami\n", cgienvcontext( $mysync ), 'cgienvcontext: SERVER_SOFTWARE + REMOTE_HOST' ) ;
  3185. # environment REMOTE_HOST and SERVER_SOFTWARE and --var REMOTE_HOST
  3186. $mysync->{ varh }->{REMOTE_HOST} = 'Another Servant' ;
  3187. is( "REMOTE_HOST is Another Servant and Votre serviteur\nSERVER_SOFTWARE is Chateau Lami\n", cgienvcontext( $mysync ), 'cgienvcontext: SERVER_SOFTWARE + REMOTE_HOST + --var REMOTE_HOST' ) ;
  3188. # environment SERVER_SOFTWARE --var REMOTE_HOST only
  3189. local $ENV{REMOTE_HOST} = undef ;
  3190. $mysync = { } ;
  3191. $mysync->{ varh }->{REMOTE_HOST} = 'Another Servant' ;
  3192. is( "REMOTE_HOST is Another Servant\nSERVER_SOFTWARE is Chateau Lami\n", cgienvcontext( $mysync ), 'cgienvcontext: SERVER_SOFTWARE + --var REMOTE_HOST' ) ;
  3193. # environment SERVER_SOFTWARE --var REMOTE_HOST only
  3194. local $ENV{REMOTE_HOST} = undef ;
  3195. local $ENV{SERVER_SOFTWARE} = undef ;
  3196. $mysync = { } ;
  3197. $mysync->{ varh }->{REMOTE_HOST} = 'Another Servant' ;
  3198. is( "REMOTE_HOST is Another Servant\n", cgienvcontext( $mysync ), 'cgienvcontext: --var REMOTE_HOST' ) ;
  3199. note( 'Leaving tests_cgienvcontext()' ) ;
  3200. return ;
  3201. }
  3202. sub cgienvcontext
  3203. {
  3204. my $mysync = shift @ARG ;
  3205. if ( ! defined $mysync ) { return '' ; }
  3206. my $output = '' ;
  3207. for my $envvar ( qw( REMOTE_ADDR REMOTE_HOST HTTP_REFERER HTTP_USER_AGENT SERVER_SOFTWARE SERVER_NAME SERVER_ADDR SERVER_PORT SERVER_ADMIN HTTP_COOKIE ) )
  3208. {
  3209. # $envval comes from the web server via an environment variable
  3210. # $varval comes from the command line parameter --val or the cgi parameter val
  3211. my $envval = $ENV{ $envvar } || q{} ;
  3212. my $varval = $mysync->{ varh }->{ $envvar } ;
  3213. if ( $envval and not $varval )
  3214. {
  3215. $output .= "$envvar is $envval\n" ;
  3216. }
  3217. elsif ( not $envval and $varval )
  3218. {
  3219. $output .= "$envvar is $varval\n" ;
  3220. }
  3221. elsif ( $envval and $varval )
  3222. {
  3223. $output .= "$envvar is $varval and $envval\n" ;
  3224. }
  3225. }
  3226. return $output ;
  3227. }
  3228. sub announcelogfile
  3229. {
  3230. my $mysync = shift @ARG ;
  3231. if ( $mysync->{ log } )
  3232. {
  3233. myprint( "Log file is $mysync->{ logfile } ( to change it, use --logfile path ; or use --nolog to turn off logging )\n" ) ;
  3234. loglogfile( $mysync ) ;
  3235. }
  3236. else
  3237. {
  3238. myprint( "No log file because of option --nolog\n" ) ;
  3239. }
  3240. return ;
  3241. }
  3242. sub tests_loglogfile
  3243. {
  3244. note( 'Entering tests_loglogfile()' ) ;
  3245. is( undef, loglogfile( ), 'loglogfile: no args => undef' ) ;
  3246. my $mysync = { } ;
  3247. is( undef, loglogfile( $mysync ), 'loglogfile: undef => undef' ) ;
  3248. $mysync->{ loglogfile } = 1 ;
  3249. $mysync->{ log } = 1 ;
  3250. is( undef, loglogfile( $mysync ), 'loglogfile: no logfile => undef' ) ;
  3251. $mysync->{ logfile } = "logfile.txt" ;
  3252. $mysync->{ loglogfilename } = "W/tmp/tests/list_all_logs_auto.txt" ;
  3253. like( loglogfile( $mysync ), qr{logfile.txt}xms, 'loglogfile: logfile=logfile.txt => ' ) ;
  3254. note( 'Leaving tests_loglogfile()' ) ;
  3255. return ;
  3256. }
  3257. sub loglogfile
  3258. {
  3259. my $mysync = shift @ARG ;
  3260. if ( ! $mysync->{ loglogfile } ) { return ; }
  3261. if ( ! $mysync->{ log } ) { return ; }
  3262. if ( ! $mysync->{ logfile } ) { return ; }
  3263. my $loglogfilename = $mysync->{ loglogfilename } || return ;
  3264. my $absolutelogfilepath = absolutelogfilepath( $mysync ) ;
  3265. if ( condition_to_loglogfile( $mysync ) )
  3266. {
  3267. myprint( "Writing log file name $absolutelogfilepath to $loglogfilename\n" ) ;
  3268. append_to_file( $absolutelogfilepath, $loglogfilename ) ;
  3269. }
  3270. return $absolutelogfilepath ;
  3271. }
  3272. sub condition_to_loglogfile
  3273. {
  3274. my $mysync = shift @ARG ;
  3275. if ( defined $mysync->{ cmdcgi } and scalar( @{ $mysync->{ cmdcgi } } ) )
  3276. {
  3277. return 1 ;
  3278. }
  3279. else
  3280. {
  3281. return 0 ;
  3282. }
  3283. }
  3284. sub absolutelogfilepath
  3285. {
  3286. my $mysync = shift @ARG ;
  3287. # Fixme: add case when the logfile name is already absolute
  3288. my $cwd = getcwd( ) ;
  3289. my $absolutelogfilepath = "$cwd/$mysync->{ logfile }" ;
  3290. return $absolutelogfilepath ;
  3291. }
  3292. sub append_to_file
  3293. {
  3294. my $string = shift @ARG ;
  3295. my $filename = shift @ARG ;
  3296. if ( open( my $fh, '>>', $filename ) )
  3297. {
  3298. print $fh "$string\n" ;
  3299. close $fh ;
  3300. }
  3301. else
  3302. {
  3303. myprint( "Could not append $string to file $filename $!\n" ) ;
  3304. }
  3305. return ;
  3306. }
  3307. sub checkselectable
  3308. {
  3309. my $mysync = shift @ARG ;
  3310. if ( $mysync->{ checkselectable } ) {
  3311. my @h1_folders_wanted_selectable ;
  3312. myprint( "Host1: Checking wanted folders are selectable. Use --nocheckselectable to avoid this check.\n" ) ;
  3313. foreach my $folder ( @{ $mysync->{ h1_folders_wanted } } )
  3314. {
  3315. ( $mysync->{ debug } or $mysync->{ debugfolders } ) and myprint( "Checking $folder is selectable on host1\n" ) ;
  3316. # It does an imap command LIST "" $folder and then search for no \Noselect
  3317. if ( ! $mysync->{ imap1 }->selectable( $folder ) )
  3318. {
  3319. myprint( "Host1: warning! ignoring folder $folder because it is not selectable\n" ) ;
  3320. }else
  3321. {
  3322. push @h1_folders_wanted_selectable, $folder ;
  3323. }
  3324. }
  3325. @{ $mysync->{ h1_folders_wanted } } = @h1_folders_wanted_selectable ;
  3326. ( $mysync->{ debug } or $mysync->{ debugfolders } )
  3327. and myprint( 'Host1: checking folders took ', timenext( $mysync ), " s\n" ) ;
  3328. }
  3329. else
  3330. {
  3331. myprint( "Host1: Not checking that wanted folders are selectable. Use --checkselectable to force this check.\n" ) ;
  3332. }
  3333. return ;
  3334. }
  3335. sub setcheckselectable
  3336. {
  3337. my $mysync = shift @ARG ;
  3338. my $h1_folders_wanted_nb = scalar @{ $mysync->{ h1_folders_wanted } } ;
  3339. # 152 because 98% of host1 accounts have less than 152 folders on /X service.
  3340. # command to get this value:
  3341. # datamash_file_op_index G_Host1_Nb_folders.txt perc:98 4 %16.1f
  3342. if ( ! defined $mysync->{ checkselectable } )
  3343. {
  3344. if ( 152 >= $h1_folders_wanted_nb )
  3345. {
  3346. $mysync->{ checkselectable } = 1 ;
  3347. }else{
  3348. myprint( "Host1: Not checking that $h1_folders_wanted_nb wanted folders are selectable. Use --checkselectable to force this check.\n" ) ;
  3349. $mysync->{ checkselectable } = 0 ;
  3350. }
  3351. }
  3352. return ;
  3353. }
  3354. sub debugsleep
  3355. {
  3356. my $mysync = shift @ARG ;
  3357. if ( defined $mysync->{debugsleep} ) {
  3358. myprint( "Info: sleeping $mysync->{debugsleep}s\n" ) ;
  3359. sleep $mysync->{debugsleep} ;
  3360. }
  3361. return ;
  3362. }
  3363. # Globals:
  3364. # $fetch_hash_set
  3365. #
  3366. sub foldersize
  3367. {
  3368. # size of one folder
  3369. my ( $mysync, $side, $imap, $search_cmd, $abletosearch, $folder ) = @ARG ;
  3370. if ( ! all_defined( $mysync, $side, $imap, $folder ) )
  3371. {
  3372. return ;
  3373. }
  3374. # FTGate is RFC buggy with EXAMINE it does not act as SELECT
  3375. #if ( ! $imap->examine( $folder ) ) {
  3376. if ( ! $imap->select( $folder ) ) {
  3377. my $error = join q{},
  3378. "$side Folder $folder: Could not select: ",
  3379. $imap->LastError, "\n" ;
  3380. errors_incr( $mysync, $error ) ;
  3381. return ;
  3382. }
  3383. if ( $imap->IsUnconnected( ) )
  3384. {
  3385. return ;
  3386. }
  3387. my $hash_ref = { } ;
  3388. my @msgs = select_msgs( $imap, undef, $search_cmd, $abletosearch, $folder ) ;
  3389. my $nb_msgs = scalar @msgs ;
  3390. my $biggest_in_folder = 0 ;
  3391. @{ $hash_ref }{ @msgs } = ( undef ) if @msgs ;
  3392. my $stot = 0 ;
  3393. if ( $imap->IsUnconnected( ) )
  3394. {
  3395. return ;
  3396. }
  3397. if ( $nb_msgs > 0 and @msgs ) {
  3398. if ( $abletosearch ) {
  3399. if ( ! $imap->fetch_hash( \@msgs, 'RFC822.SIZE', $hash_ref) ) {
  3400. my $error = "$side failure with fetch_hash: $EVAL_ERROR\n" ;
  3401. errors_incr( $mysync, $error ) ;
  3402. return ;
  3403. }
  3404. }
  3405. else
  3406. {
  3407. my $fetch_hash_uids = $fetch_hash_set || "1:*" ;
  3408. if ( ! $imap->fetch_hash( $fetch_hash_uids, 'RFC822.SIZE', $hash_ref ) ) {
  3409. my $error = "$side failure with fetch_hash: $EVAL_ERROR\n" ;
  3410. errors_incr( $mysync, $error ) ;
  3411. return ;
  3412. }
  3413. }
  3414. for ( keys %{ $hash_ref } ) {
  3415. my $size = $hash_ref->{ $_ }->{ 'RFC822.SIZE' } ;
  3416. if ( defined $size )
  3417. {
  3418. $stot += $size ;
  3419. $biggest_in_folder = max( $biggest_in_folder, $size ) ;
  3420. }
  3421. }
  3422. }
  3423. return( $stot, $nb_msgs, $biggest_in_folder ) ;
  3424. }
  3425. # The old subroutine that performed just one side at a time.
  3426. # Still here for a while, until confident with sub foldersize_diff_compute()
  3427. sub foldersizes
  3428. {
  3429. my ( $mysync, $side, $imap, $search_cmd, $abletosearch, @folders ) = @_ ;
  3430. my $total_size = 0 ;
  3431. my $total_nb = 0 ;
  3432. my $biggest_in_all = 0 ;
  3433. my $nb_folders = scalar @folders ;
  3434. my $ct_folders = 0 ; # folder counter.
  3435. myprint( "++++ Calculating sizes of $nb_folders folders on $side\n" ) ;
  3436. foreach my $folder ( @folders ) {
  3437. my $stot = 0 ;
  3438. my $nb_msgs = 0 ;
  3439. my $biggest_in_folder = 0 ;
  3440. $ct_folders++ ;
  3441. myprintf( "$side folder %7s %-35s", "$ct_folders/$nb_folders", jux_utf8( $folder ) ) ;
  3442. if ( 'Host2' eq $side and not exists $mysync->{h2_folders_all_UPPER}{ uc $folder } ) {
  3443. myprint( " does not exist yet\n") ;
  3444. next ;
  3445. }
  3446. if ( 'Host1' eq $side and not exists $h1_folders_all{ $folder } ) {
  3447. myprint( " does not exist\n" ) ;
  3448. next ;
  3449. }
  3450. last if $imap->IsUnconnected( ) ;
  3451. ( $stot, $nb_msgs, $biggest_in_folder ) = foldersize( $mysync, $side, $imap, $search_cmd, $abletosearch, $folder ) ;
  3452. myprintf( ' Size: %9s', $stot ) ;
  3453. myprintf( ' Messages: %5s', $nb_msgs ) ;
  3454. myprintf( " Biggest: %9s\n", $biggest_in_folder ) ;
  3455. $total_size += $stot ;
  3456. $total_nb += $nb_msgs ;
  3457. $biggest_in_all = max( $biggest_in_all, $biggest_in_folder ) ;
  3458. }
  3459. myprintf( "%s Nb folders: %11s folders\n", $side, $nb_folders ) ;
  3460. myprintf( "%s Nb messages: %11s messages\n", $side, $total_nb ) ;
  3461. myprintf( "%s Total size: %11s bytes (%s)\n", $side, $total_size, bytes_display_string_bin( $total_size ) ) ;
  3462. myprintf( "%s Biggest message: %11s bytes (%s)\n", $side, $biggest_in_all, bytes_display_string_bin( $biggest_in_all ) ) ;
  3463. myprintf( "%s Time spent on sizing: %11.1f seconds\n", $side, timenext( $mysync ) ) ;
  3464. return( $total_nb, $total_size ) ;
  3465. }
  3466. sub foldersize_diff_present
  3467. {
  3468. my $mysync = shift @ARG ;
  3469. my $folder1 = shift @ARG ;
  3470. my $folder2 = shift @ARG ;
  3471. my $counter_str = shift @ARG ;
  3472. my $force = shift @ARG ;
  3473. my $values1_str ;
  3474. my $values2_str ;
  3475. if ( ! defined $mysync->{ folder1 }->{ $folder1 }->{ size } || $force )
  3476. {
  3477. foldersize_diff_compute( $mysync, $folder1, $folder2, $force ) ;
  3478. }
  3479. # again, but this time it means no availaible data.
  3480. if ( defined $mysync->{ folder1 }->{ $folder1 }->{ size } )
  3481. {
  3482. $values1_str = sprintf( "Size: %9s Messages: %5s Biggest: %9s\n",
  3483. $mysync->{ folder1 }->{ $folder1 }->{ size },
  3484. $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs },
  3485. $mysync->{ folder1 }->{ $folder1 }->{ biggest },
  3486. ) ;
  3487. }
  3488. else
  3489. {
  3490. $values1_str = " does not exist\n" ;
  3491. }
  3492. if ( defined $mysync->{ folder2 }->{ $folder2 }->{ size } )
  3493. {
  3494. $values2_str = sprintf( "Size: %9s Messages: %5s Biggest: %9s\n",
  3495. $mysync->{ folder2 }->{ $folder2 }->{ size },
  3496. $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs },
  3497. $mysync->{ folder2 }->{ $folder2 }->{ biggest },
  3498. ) ;
  3499. }
  3500. else
  3501. {
  3502. $values2_str = " does not exist yet\n" ;
  3503. }
  3504. myprintf( "Host1 folder %7s %-35s %s",
  3505. "$counter_str",
  3506. jux_utf8( $folder1 ),
  3507. $values1_str
  3508. ) ;
  3509. myprintf( "Host2 folder %7s %-35s %s",
  3510. "$counter_str",
  3511. jux_utf8( $folder2 ),
  3512. $values2_str
  3513. ) ;
  3514. myprintf( "Host2-Host1 %7s %-35s %9s %5s %9s\n\n",
  3515. "",
  3516. "",
  3517. $mysync->{ folder1 }->{ $folder1 }->{ size_diff },
  3518. $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs_diff },
  3519. $mysync->{ folder1 }->{ $folder1 }->{ biggest_diff },
  3520. ) ;
  3521. return ;
  3522. }
  3523. sub foldersize_diff_compute
  3524. {
  3525. my $mysync = shift @ARG ;
  3526. my $folder1 = shift @ARG ;
  3527. my $folder2 = shift @ARG ;
  3528. my $force = shift @ARG ;
  3529. my ( $size_1, $nb_msgs_1, $biggest_1 ) ;
  3530. # memoization
  3531. if (
  3532. exists $h1_folders_all{ $folder1 }
  3533. &&
  3534. (
  3535. ! defined $mysync->{ folder1 }->{ $folder1 }->{ size }
  3536. || $force
  3537. )
  3538. )
  3539. {
  3540. #myprint( "foldersize folder1 $h1_folders_all{ $folder1 }\n" ) ;
  3541. ( $size_1, $nb_msgs_1, $biggest_1 ) =
  3542. foldersize( $mysync,
  3543. 'Host1',
  3544. $mysync->{ imap1 },
  3545. $mysync->{ search1 },
  3546. $mysync->{ abletosearch1 },
  3547. $folder1
  3548. ) ;
  3549. $mysync->{ folder1 }->{ $folder1 }->{ size } = $size_1 ;
  3550. $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs } = $nb_msgs_1 ;
  3551. $mysync->{ folder1 }->{ $folder1 }->{ biggest } = $biggest_1 ;
  3552. }
  3553. else
  3554. {
  3555. $size_1 = $mysync->{ folder1 }->{ $folder1 }->{ size } ;
  3556. $nb_msgs_1 = $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs } ;
  3557. $biggest_1 = $mysync->{ folder1 }->{ $folder1 }->{ biggest } ;
  3558. }
  3559. my ( $size_2, $nb_msgs_2, $biggest_2 ) ;
  3560. if (
  3561. exists $mysync->{ h2_folders_all_UPPER }{ uc $folder2 }
  3562. &&
  3563. (
  3564. ! defined $mysync->{ folder2 }->{ $folder2 }->{ size }
  3565. || $force
  3566. )
  3567. )
  3568. {
  3569. #myprint( "foldersize folder2\n" ) ;
  3570. ( $size_2, $nb_msgs_2, $biggest_2 ) =
  3571. foldersize( $mysync,
  3572. 'Host2',
  3573. $mysync->{ imap2 },
  3574. $mysync->{ search2 },
  3575. $mysync->{ abletosearch2 },
  3576. $folder2
  3577. ) ;
  3578. $mysync->{ folder2 }->{ $folder2 }->{ size } = $size_2 ;
  3579. $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs } = $nb_msgs_2 ;
  3580. $mysync->{ folder2 }->{ $folder2 }->{ biggest } = $biggest_2 ;
  3581. }
  3582. else
  3583. {
  3584. $size_2 = $mysync->{ folder2 }->{ $folder2 }->{ size } ;
  3585. $nb_msgs_2 = $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs } ;
  3586. $biggest_2 = $mysync->{ folder2 }->{ $folder2 }->{ biggest } ;
  3587. }
  3588. my $size_diff = diff( $size_2, $size_1 ) ;
  3589. my $nb_msgs_diff = diff( $nb_msgs_2, $nb_msgs_1 ) ;
  3590. my $biggest_diff = diff( $biggest_2, $biggest_1 ) ;
  3591. $mysync->{ folder1 }->{ $folder1 }->{ size_diff } = $size_diff ;
  3592. $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs_diff } = $nb_msgs_diff ;
  3593. $mysync->{ folder1 }->{ $folder1 }->{ biggest_diff } = $biggest_diff ;
  3594. # It's redundant but easier to access later
  3595. $mysync->{ folder2 }->{ $folder2 }->{ size_diff } = $size_diff ;
  3596. $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs_diff } = $nb_msgs_diff ;
  3597. $mysync->{ folder2 }->{ $folder2 }->{ biggest_diff } = $biggest_diff ;
  3598. return ;
  3599. }
  3600. sub diff
  3601. {
  3602. my $x = shift @ARG ;
  3603. my $y = shift @ARG ;
  3604. $x ||= 0 ;
  3605. $y ||= 0 ;
  3606. return $x - $y ;
  3607. }
  3608. sub tests_add
  3609. {
  3610. note( 'Entering tests_add()' ) ;
  3611. is( 0, add( ), 'tests_add: no args => 0' ) ;
  3612. is( 0, add( undef ), 'tests_add: undef => 0' ) ;
  3613. is( 0, add( 0 ), 'tests_add: 0 => 0' ) ;
  3614. is( 0, add( 0, 0 ), 'tests_add: 0 0 => 0' ) ;
  3615. is( 0, add( 0, 0, 0 ), 'tests_add: 0 0 0 => 0' ) ;
  3616. is( 1, add( 1 ), 'tests_add: 1 => 1' ) ;
  3617. is( 2, add( 1, 1 ), 'tests_add: 1 1 => 2' ) ;
  3618. is( 3, add( 1, 1, 1 ), 'tests_add: 1 1 1 => 3' ) ;
  3619. is( 2, add( 1, undef, 1 ), 'tests_add: 1 undef 1 => 2' ) ;
  3620. is( 0, add( -1, 1 ), 'tests_add: -1 1 => 0' ) ;
  3621. is( 2.2, add( 1.1, 1.1 ), 'tests_add: 1.1 1.1 => 2.2' ) ;
  3622. is( 100, add( (1) x 100 ), 'tests_add: 1 1 ... 1 100 times => 100 list' ) ;
  3623. my @hundred_ones = (1) x 100 ;
  3624. is( 100, add( @hundred_ones ), 'tests_add: 1 1 ... 1 100 times => 100 array' ) ;
  3625. note( 'Leaving tests_add()' ) ;
  3626. return ;
  3627. }
  3628. sub add
  3629. {
  3630. my $sum = 0 ;
  3631. foreach my $number ( @ARG )
  3632. {
  3633. $sum += $number || 0 ;
  3634. }
  3635. return $sum ;
  3636. }
  3637. sub tests_checknoabletosearch
  3638. {
  3639. note( 'Entering checknoabletosearch()' ) ;
  3640. is( undef, checknoabletosearch( ), 'checknoabletosearch: no args => undef' ) ;
  3641. note( 'Leaving checknoabletosearch()' ) ;
  3642. return ;
  3643. }
  3644. sub checknoabletosearch
  3645. {
  3646. # call example: checknoabletosearch( $sync, $sync->{ imap1 }, 'INBOX', 'Host1' ) ;
  3647. # output:
  3648. # * undef if something is not ok to decide
  3649. # * 1 if SEARCH ALL failed
  3650. my( $mysync, $imap, $folder, $HostX ) = @ARG ;
  3651. if ( ! all_defined( $mysync, $imap, $folder, $HostX ) )
  3652. {
  3653. return ;
  3654. }
  3655. myprint( "$HostX: checking if SEARCH ALL works on $folder\n" ) ;
  3656. if ( ! select_folder( $mysync, $imap, $folder, $HostX ) )
  3657. {
  3658. myprint( "$HostX: can not SELECT folder [$folder]\n" ) ;
  3659. return ;
  3660. }
  3661. my $count_from_select = count_from_select( $imap->History ) ;
  3662. myprint( "$HostX: folder [$folder] has $count_from_select messages mentioned by SELECT\n" ) ;
  3663. my $msgs_all = $imap->messages( ) ;
  3664. if ( ! $msgs_all )
  3665. {
  3666. myprint( "$HostX: can not SEARCH ALL folder [$folder]\n" ) ;
  3667. myprint( "$HostX: ", $imap->LastError(), "\n" ) ;
  3668. return 1 ;
  3669. }
  3670. my $count_from_search_all = scalar( @{ $msgs_all } ) ;
  3671. myprint( "$HostX: folder [$folder] has $count_from_search_all messages found by SEARCH ALL\n" ) ;
  3672. if ( $count_from_select == $count_from_search_all )
  3673. {
  3674. myprint( "$HostX: folder [$folder] has the same messages count ($count_from_select) by SELECT and SEARCH ALL\n" ) ;
  3675. }
  3676. else
  3677. {
  3678. myprint( "$HostX: Warning, folder [$folder] has not the same count by SELECT ($count_from_select) and SEARCH ALL ($count_from_search_all)\n" ) ;
  3679. return 1 ;
  3680. }
  3681. return ;
  3682. }
  3683. sub foldersizes_diff_list
  3684. {
  3685. my $mysync = shift @ARG ;
  3686. my $force = shift @ARG ;
  3687. my @folders = @{ $mysync->{h1_folders_wanted} } ;
  3688. my $nb_folders = scalar @folders ;
  3689. my $ct_folders = 0 ; # folder counter.
  3690. foreach my $folder1 ( @folders )
  3691. {
  3692. $ct_folders++ ;
  3693. my $counter_str = "$ct_folders/$nb_folders" ;
  3694. my $folder2 = imap2_folder_name( $mysync, $folder1 ) ;
  3695. foldersize_diff_present( $mysync, $folder1, $folder2, $counter_str, $force ) ;
  3696. }
  3697. return ;
  3698. }
  3699. sub foldersizes_total
  3700. {
  3701. my $mysync = shift @ARG ;
  3702. my @folders_1 = @{ $mysync->{h1_folders_wanted} } ;
  3703. my @folders_2 = @h2_folders_from_1_wanted ;
  3704. my $nb_folders_1 = scalar( @folders_1 ) ;
  3705. my $nb_folders_2 = scalar( @folders_2 ) ;
  3706. my ( $total_size_1, $total_nb_1, $biggest_in_all_1 ) = ( 0, 0, 0 ) ;
  3707. my ( $total_size_2, $total_nb_2, $biggest_in_all_2 ) = ( 0, 0, 0 ) ;
  3708. foreach my $folder1 ( @folders_1 )
  3709. {
  3710. $total_size_1 = add( $total_size_1, $mysync->{ folder1 }->{ $folder1 }->{ size } ) ;
  3711. $total_nb_1 = add( $total_nb_1, $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs } ) ;
  3712. $biggest_in_all_1 = max( $biggest_in_all_1 , $mysync->{ folder1 }->{ $folder1 }->{ biggest } ) ;
  3713. }
  3714. foreach my $folder2 ( @folders_2 )
  3715. {
  3716. $total_size_2 = add( $total_size_2, $mysync->{ folder2 }->{ $folder2 }->{ size } ) ;
  3717. $total_nb_2 = add( $total_nb_2, $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs } ) ;
  3718. $biggest_in_all_2 = max( $biggest_in_all_2 , $mysync->{ folder2 }->{ $folder2 }->{ biggest } ) ;
  3719. }
  3720. myprintf( "Host1 Nb folders: %11s folders\n", $nb_folders_1 ) ;
  3721. myprintf( "Host2 Nb folders: %11s folders\n", $nb_folders_2 ) ;
  3722. myprint( "\n" ) ;
  3723. myprintf( "Host1 Nb messages: %11s messages\n", $total_nb_1 ) ;
  3724. myprintf( "Host2 Nb messages: %11s messages\n", $total_nb_2 ) ;
  3725. myprint( "\n" ) ;
  3726. myprintf( "Host1 Total size: %11s bytes (%s)\n", $total_size_1, bytes_display_string_bin( $total_size_1 ) ) ;
  3727. myprintf( "Host2 Total size: %11s bytes (%s)\n", $total_size_2, bytes_display_string_bin( $total_size_2 ) ) ;
  3728. myprint( "\n" ) ;
  3729. myprintf( "Host1 Biggest message: %11s bytes (%s)\n", $biggest_in_all_1, bytes_display_string_bin( $biggest_in_all_1 ) ) ;
  3730. myprintf( "Host2 Biggest message: %11s bytes (%s)\n", $biggest_in_all_2, bytes_display_string_bin( $biggest_in_all_2 ) ) ;
  3731. myprint( "\n" ) ;
  3732. myprintf( "Time spent on sizing: %11.1f seconds\n", timenext( $mysync ) ) ;
  3733. my @total_1_2 = ( $total_nb_1, $total_size_1, $total_nb_2, $total_size_2 ) ;
  3734. return @total_1_2 ;
  3735. }
  3736. sub foldersizesatend_old
  3737. {
  3738. my $mysync = shift @ARG ;
  3739. timenext( $mysync ) ;
  3740. return if ( $mysync->{imap1}->IsUnconnected( ) ) ;
  3741. return if ( $mysync->{imap2}->IsUnconnected( ) ) ;
  3742. # Get all folders on host2 again since new were created
  3743. @h2_folders_all = sort $mysync->{imap2}->folders();
  3744. for ( @h2_folders_all ) {
  3745. $h2_folders_all{ $_ } = 1 ;
  3746. $mysync->{h2_folders_all_UPPER}{ uc $_ } = 1 ;
  3747. } ;
  3748. ( $h1_nb_msg_end, $h1_bytes_end ) = foldersizes( $mysync, 'Host1', $mysync->{imap1}, $mysync->{ search1 }, $mysync->{abletosearch1}, @{ $mysync->{h1_folders_wanted} } ) ;
  3749. ( $h2_nb_msg_end, $h2_bytes_end ) = foldersizes( $mysync, 'Host2', $mysync->{imap2}, $mysync->{ search2 }, $mysync->{abletosearch2}, @h2_folders_from_1_wanted ) ;
  3750. if ( not all_defined( $h1_nb_msg_end, $h1_bytes_end, $h2_nb_msg_end, $h2_bytes_end ) ) {
  3751. my $error = "Failure getting foldersizes, final differences will not be calculated\n" ;
  3752. errors_incr( $mysync, $error ) ;
  3753. }
  3754. return ;
  3755. }
  3756. sub foldersizesatend
  3757. {
  3758. my $mysync = shift @ARG ;
  3759. timenext( $mysync ) ;
  3760. return if ( $mysync->{imap1}->IsUnconnected( ) ) ;
  3761. return if ( $mysync->{imap2}->IsUnconnected( ) ) ;
  3762. # Get all folders on host2 again since new were created
  3763. @h2_folders_all = sort $mysync->{imap2}->folders();
  3764. for ( @h2_folders_all ) {
  3765. $h2_folders_all{ $_ } = 1 ;
  3766. $mysync->{h2_folders_all_UPPER}{ uc $_ } = 1 ;
  3767. } ;
  3768. foldersizes_diff_list( $mysync, $FORCE ) ;
  3769. ( $h1_nb_msg_end, $h1_bytes_end, $h2_nb_msg_end, $h2_bytes_end )
  3770. = foldersizes_total( $mysync ) ;
  3771. if ( not all_defined( $h1_nb_msg_end, $h1_bytes_end, $h2_nb_msg_end, $h2_bytes_end ) ) {
  3772. my $error = "Failure getting foldersizes, final differences will not be calculated\n" ;
  3773. errors_incr( $mysync, $error ) ;
  3774. }
  3775. return ;
  3776. }
  3777. sub foldersizes_at_the_beggining
  3778. {
  3779. my $mysync = shift @ARG ;
  3780. myprint( << 'END_SIZE' ) ;
  3781. Folders sizes before the synchronization. It can take some time. Be patient.
  3782. You can remove foldersizes listings by using "--nofoldersizes" and "--nofoldersizesatend"
  3783. but then you will also lose the ETA (Estimation Time of Arrival) given after each message copy.
  3784. END_SIZE
  3785. foldersizes_diff_list( $mysync ) ;
  3786. ( $mysync->{ h1_nb_msg_start }, $mysync->{ h1_bytes_start },
  3787. $mysync->{ h2_nb_msg_start }, $mysync->{ h2_bytes_start } )
  3788. = foldersizes_total( $mysync ) ;
  3789. if ( not all_defined(
  3790. $mysync->{ h1_nb_msg_start },
  3791. $mysync->{ h1_bytes_start },
  3792. $mysync->{ h2_nb_msg_start },
  3793. $mysync->{ h2_bytes_start } ) )
  3794. {
  3795. my $error = "Failure getting foldersizes, ETA and final diff will not be displayed\n" ;
  3796. errors_incr( $mysync, $error ) ;
  3797. $mysync->{ foldersizes } = 0 ;
  3798. $mysync->{ foldersizesatend } = 0 ;
  3799. return ;
  3800. }
  3801. my $h2_bytes_limit = $mysync->{ acc2 }->{quota_limit_bytes} || 0 ;
  3802. if ( $h2_bytes_limit and ( $h2_bytes_limit < $mysync->{ h1_bytes_start } ) )
  3803. {
  3804. my $quota_percent = mysprintf( '%.0f', $NUMBER_100 * $mysync->{ h1_bytes_start } / $h2_bytes_limit ) ;
  3805. my $error = "Host2: Quota limit will be exceeded! Over $quota_percent % ( $mysync->{ h1_bytes_start } bytes / $h2_bytes_limit bytes )\n" ;
  3806. errors_incr( $mysync, $error ) ;
  3807. }
  3808. return ;
  3809. }
  3810. # Globals:
  3811. # @h2_folders_from_1_wanted
  3812. sub foldersizes_at_the_beggining_old
  3813. {
  3814. my $mysync = shift @ARG ;
  3815. myprint( << 'END_SIZE' ) ;
  3816. Folders sizes before the synchronization.
  3817. You can remove foldersizes listings by using "--nofoldersizes" and "--nofoldersizesatend"
  3818. but then you will also lose the ETA (Estimation Time of Arrival) given after each message copy.
  3819. END_SIZE
  3820. ( $mysync->{ h1_nb_msg_start }, $mysync->{ h1_bytes_start } ) =
  3821. foldersizes( $mysync, 'Host1', $mysync->{imap1}, $mysync->{ search1 },
  3822. $mysync->{abletosearch1}, @{ $mysync->{h1_folders_wanted} } ) ;
  3823. ( $mysync->{ h2_nb_msg_start }, $mysync->{ h2_bytes_start } ) =
  3824. foldersizes( $mysync, 'Host2', $mysync->{imap2}, $mysync->{ search2 },
  3825. $mysync->{abletosearch2}, @h2_folders_from_1_wanted ) ;
  3826. if ( not all_defined( $mysync->{ h1_nb_msg_start },
  3827. $mysync->{ h1_bytes_start }, $mysync->{ h2_nb_msg_start }, $mysync->{ h2_bytes_start } ) )
  3828. {
  3829. my $error = "Failure getting foldersizes, ETA and final diff will not be displayed\n" ;
  3830. errors_incr( $mysync, $error ) ;
  3831. $mysync->{ foldersizes } = 0 ;
  3832. $mysync->{ foldersizesatend } = 0 ;
  3833. return ;
  3834. }
  3835. my $h2_bytes_limit = $mysync->{ acc2 }->{quota_limit_bytes} || 0 ;
  3836. if ( $h2_bytes_limit and ( $h2_bytes_limit < $mysync->{ h1_bytes_start } ) )
  3837. {
  3838. my $quota_percent = mysprintf( '%.0f', $NUMBER_100 * $mysync->{ h1_bytes_start } / $h2_bytes_limit ) ;
  3839. my $error = "Host2: Quota limit will be exceeded! Over $quota_percent % ( $mysync->{ h1_bytes_start } bytes / $h2_bytes_limit bytes )\n" ;
  3840. errors_incr( $mysync, $error ) ;
  3841. }
  3842. return ;
  3843. }
  3844. sub tests_total_bytes_max_reached
  3845. {
  3846. note( 'Entering tests_total_bytes_max_reached()' ) ;
  3847. is( undef, total_bytes_max_reached( ), 'total_bytes_max_reached: no args => undef' ) ;
  3848. my $mysync = {} ;
  3849. is( undef, total_bytes_max_reached( $mysync ), 'total_bytes_max_reached: no exitwhenover => undef' ) ;
  3850. $mysync->{ exitwhenover } = 300 ;
  3851. is( undef, total_bytes_max_reached( $mysync ), 'total_bytes_max_reached: exitwhenover 300 but no total_bytes_transferred => undef' ) ;
  3852. $mysync->{ total_bytes_transferred } = 200 ;
  3853. is( undef, total_bytes_max_reached( $mysync ), 'total_bytes_max_reached: exitwhenover 300 but total_bytes_transferred 200 => undef' ) ;
  3854. $mysync->{ total_bytes_transferred } = 400 ;
  3855. is( 1, total_bytes_max_reached( $mysync ), 'total_bytes_max_reached: exitwhenover 300 but total_bytes_transferred 400 => 1' ) ;
  3856. note( 'Leaving tests_total_bytes_max_reached()' ) ;
  3857. return ;
  3858. }
  3859. sub total_bytes_max_reached
  3860. {
  3861. my $mysync = shift @ARG ;
  3862. if ( ! defined $mysync ) { return ; }
  3863. if ( ! $mysync->{ exitwhenover } )
  3864. {
  3865. return ;
  3866. }
  3867. if ( ! $mysync->{ total_bytes_transferred } )
  3868. {
  3869. return ;
  3870. }
  3871. if ( $mysync->{ total_bytes_transferred } >= $mysync->{ exitwhenover } )
  3872. {
  3873. my $error = "Maximum bytes transferred reached, $mysync->{total_bytes_transferred} >= $mysync->{ exitwhenover }, ending sync\n" ;
  3874. errors_incr( $mysync, $error ) ;
  3875. return( 1 ) ;
  3876. }
  3877. return ;
  3878. }
  3879. sub tests_mock_capability
  3880. {
  3881. note( 'Entering tests_mock_capability()' ) ;
  3882. my $myimap ;
  3883. ok( $myimap = mock_capability( ),
  3884. 'mock_capability: (1) no args => a Test::MockObject'
  3885. ) ;
  3886. ok( $myimap->isa( 'Test::MockObject' ),
  3887. 'mock_capability: (2) no args => a Test::MockObject'
  3888. ) ;
  3889. is( undef, $myimap->capability( ),
  3890. 'mock_capability: (3) no args => capability undef'
  3891. ) ;
  3892. ok( mock_capability( $myimap ),
  3893. 'mock_capability: (1) one arg => MockObject'
  3894. ) ;
  3895. is( undef, $myimap->capability( ),
  3896. 'mock_capability: (2) one arg OO style => capability undef'
  3897. ) ;
  3898. ok( mock_capability( $myimap, $NUMBER_123456 ),
  3899. 'mock_capability: (1) two args 123456 => capability 123456'
  3900. ) ;
  3901. is( $NUMBER_123456, $myimap->capability( ),
  3902. 'mock_capability: (2) two args 123456 => capability 123456'
  3903. ) ;
  3904. ok( mock_capability( $myimap, 'ABCD' ),
  3905. 'mock_capability: (1) two args ABCD => capability ABCD'
  3906. ) ;
  3907. is( 'ABCD', $myimap->capability( ),
  3908. 'mock_capability: (2) two args ABCD => capability ABCD'
  3909. ) ;
  3910. ok( mock_capability( $myimap, [ 'ABCD' ] ),
  3911. 'mock_capability: (1) two args [ ABCD ] => capability [ ABCD ]'
  3912. ) ;
  3913. is_deeply( [ 'ABCD' ], $myimap->capability( ),
  3914. 'mock_capability: (2) two args [ ABCD ] => capability [ ABCD ]'
  3915. ) ;
  3916. ok( mock_capability( $myimap, [ 'ABC', 'DEF' ] ),
  3917. 'mock_capability: (1) two args [ ABC, DEF ] => capability [ ABC, DEF ]'
  3918. ) ;
  3919. is_deeply( [ 'ABC', 'DEF' ], $myimap->capability( ),
  3920. 'mock_capability: (2) two args [ ABC, DEF ] => capability capability [ ABC, DEF ]'
  3921. ) ;
  3922. ok( mock_capability( $myimap, 'ABC', 'DEF' ),
  3923. 'mock_capability: (1) two args ABC, DEF => capability [ ABC, DEF ]'
  3924. ) ;
  3925. is_deeply( [ 'ABC', 'DEF' ], [ $myimap->capability( ) ],
  3926. 'mock_capability: (2) two args ABC, DEF => capability capability [ ABC, DEF ]'
  3927. ) ;
  3928. ok( mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ),
  3929. 'mock_capability: (1) two args IMAP4rev1, APPENDLIMIT=123456 => capability [ IMAP4rev1, APPENDLIMIT=123456 ]'
  3930. ) ;
  3931. is_deeply( [ 'IMAP4rev1', 'APPENDLIMIT=123456' ], [ $myimap->capability( ) ],
  3932. 'mock_capability: (2) two args IMAP4rev1, APPENDLIMIT=123456 => capability capability [ IMAP4rev1, APPENDLIMIT=123456 ]'
  3933. ) ;
  3934. note( 'Leaving tests_mock_capability()' ) ;
  3935. return ;
  3936. }
  3937. sub sig_install_toggle_sleep
  3938. {
  3939. my $mysync = shift @ARG ;
  3940. if ( 'MSWin32' ne $OSNAME ) {
  3941. #myprint( "sig_install( $mysync, \&toggle_sleep, 'USR1' )\n" ) ;
  3942. sig_install( $mysync, 'toggle_sleep', 'USR1' ) ;
  3943. }
  3944. #myprint( "Leaving sig_install_toggle_sleep\n" ) ;
  3945. return ;
  3946. }
  3947. sub mock_capability
  3948. {
  3949. my $myimap = shift @ARG ;
  3950. my @has_capability_value = @ARG ;
  3951. my ( $has_capability_value ) = @has_capability_value ;
  3952. if ( ! $myimap )
  3953. {
  3954. require_ok( "Test::MockObject" ) ;
  3955. $myimap = Test::MockObject->new( ) ;
  3956. }
  3957. $myimap->mock(
  3958. 'capability',
  3959. sub { return wantarray ?
  3960. @has_capability_value
  3961. : $has_capability_value ;
  3962. }
  3963. ) ;
  3964. return $myimap ;
  3965. }
  3966. sub tests_capability_of
  3967. {
  3968. note( 'Entering tests_capability_of()' ) ;
  3969. is( undef, capability_of( ),
  3970. 'capability_of: no args => undef' ) ;
  3971. my $myimap ;
  3972. is( undef, capability_of( $myimap ),
  3973. 'capability_of: undef => undef' ) ;
  3974. $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ;
  3975. is( undef, capability_of( $myimap, 'CACA' ),
  3976. 'capability_of: two args unknown capability => undef' ) ;
  3977. is( $NUMBER_123456, capability_of( $myimap, 'APPENDLIMIT' ),
  3978. 'capability_of: two args APPENDLIMIT 123456 => 123456 yeah!' ) ;
  3979. note( 'Leaving tests_capability_of()' ) ;
  3980. return ;
  3981. }
  3982. sub capability_of
  3983. {
  3984. my $imap = shift || return ;
  3985. my $capability_keyword = shift || return ;
  3986. my @capability = $imap->capability ;
  3987. if ( ! @capability ) { return ; }
  3988. my $capability_value = search_in_array( $capability_keyword, @capability ) ;
  3989. return $capability_value ;
  3990. }
  3991. sub tests_search_in_array
  3992. {
  3993. note( 'Entering tests_search_in_array()' ) ;
  3994. is( undef, search_in_array( 'KA' ),
  3995. 'search_in_array: no array => undef ' ) ;
  3996. is( 'VA', search_in_array( 'KA', ( 'KA=VA' ) ),
  3997. 'search_in_array: KA KA=VA => VA ' ) ;
  3998. is( 'VA', search_in_array( 'KA', ( 'KA=VA', 'KB=VB' ) ),
  3999. 'search_in_array: KA KA=VA KB=VB => VA ' ) ;
  4000. is( 'VB', search_in_array( 'KB', ( 'KA=VA', 'KB=VB' ) ),
  4001. 'search_in_array: KA=VA KB=VB => VB ' ) ;
  4002. note( 'Leaving tests_search_in_array()' ) ;
  4003. return ;
  4004. }
  4005. sub search_in_array
  4006. {
  4007. my ( $key, @array ) = @ARG ;
  4008. foreach my $item ( @array )
  4009. {
  4010. if ( $item =~ /([^=]+)=(.*)/ )
  4011. {
  4012. if ( $1 eq $key )
  4013. {
  4014. return $2 ;
  4015. }
  4016. }
  4017. }
  4018. return ;
  4019. }
  4020. sub tests_appendlimit_from_capability
  4021. {
  4022. note( 'Entering tests_appendlimit_from_capability()' ) ;
  4023. is( undef, appendlimit_from_capability( ),
  4024. 'appendlimit_from_capability: no args => undef'
  4025. ) ;
  4026. my $myimap ;
  4027. is( undef, appendlimit_from_capability( $myimap ),
  4028. 'appendlimit_from_capability: undef arg => undef'
  4029. ) ;
  4030. $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ;
  4031. # Normal behavior
  4032. is( $NUMBER_123456, appendlimit_from_capability( $myimap ),
  4033. 'appendlimit_from_capability: APPENDLIMIT=123456 => 123456'
  4034. ) ;
  4035. # Not a number
  4036. $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=ABC' ) ;
  4037. is( undef, appendlimit_from_capability( $myimap ),
  4038. 'appendlimit_from_capability: not a number => undef'
  4039. ) ;
  4040. note( 'Leaving tests_appendlimit_from_capability()' ) ;
  4041. return ;
  4042. }
  4043. sub appendlimit_from_capability
  4044. {
  4045. my $myimap = shift @ARG ;
  4046. if ( ! $myimap )
  4047. {
  4048. myprint( "Warn: no imap with call to appendlimit_from_capability\n" ) ;
  4049. return ;
  4050. }
  4051. #myprint( Data::Dumper->Dump( [ \$myimap ] ) ) ;
  4052. my $appendlimit = capability_of( $myimap, 'APPENDLIMIT' ) ;
  4053. #myprint( "has_capability APPENDLIMIT $appendlimit\n" ) ;
  4054. if ( is_integer( $appendlimit ) )
  4055. {
  4056. return $appendlimit ;
  4057. }
  4058. return ;
  4059. }
  4060. sub tests_appendlimit
  4061. {
  4062. note( 'Entering tests_appendlimit()' ) ;
  4063. is( undef, appendlimit( ),
  4064. 'appendlimit: no args => undef'
  4065. ) ;
  4066. my $mysync = { } ;
  4067. is( undef, appendlimit( $mysync ),
  4068. 'appendlimit: no imap2 => undef'
  4069. ) ;
  4070. my $myimap ;
  4071. $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ;
  4072. $mysync->{ imap2 } = $myimap ;
  4073. is( 123456, appendlimit( $mysync ),
  4074. 'appendlimit: imap2 with APPENDLIMIT=123456 => 123456'
  4075. ) ;
  4076. note( 'Leaving tests_appendlimit()' ) ;
  4077. return ;
  4078. }
  4079. sub appendlimit
  4080. {
  4081. my $mysync = shift || return ;
  4082. my $myimap = $mysync->{ imap2 } ;
  4083. my $appendlimit = appendlimit_from_capability( $myimap ) ;
  4084. if ( defined $appendlimit )
  4085. {
  4086. myprint( "Host2: found APPENDLIMIT=$appendlimit in CAPABILITY (use --appendlimit xxxx to override this automatic setting)\n" ) ;
  4087. return $appendlimit ;
  4088. }
  4089. return ;
  4090. }
  4091. sub tests_maxsize_setting
  4092. {
  4093. note( 'Entering tests_maxsize_setting()' ) ;
  4094. is( undef, maxsize_setting( ),
  4095. 'maxsize_setting: no args => undef'
  4096. ) ;
  4097. my $mysync ;
  4098. is( undef, maxsize_setting( $mysync ),
  4099. 'maxsize_setting: undef arg => undef'
  4100. ) ;
  4101. $mysync = { } ;
  4102. $mysync->{ maxsize } = $NUMBER_123456 ;
  4103. # --maxsize alone
  4104. is( $NUMBER_123456, maxsize_setting( $mysync ),
  4105. 'maxsize_setting: --maxsize 123456 alone => 123456'
  4106. ) ;
  4107. $mysync = { } ;
  4108. my $myimap ;
  4109. $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=654321' ) ;
  4110. $mysync->{ imap2 } = $myimap ;
  4111. # APPENDLIMIT alone
  4112. is( $NUMBER_654321, maxsize_setting( $mysync ),
  4113. 'maxsize_setting: APPENDLIMIT 654321 alone => 654321'
  4114. ) ;
  4115. is( $NUMBER_654321, $mysync->{ maxsize },
  4116. 'maxsize_setting: APPENDLIMIT 654321 alone => maxsize 654321'
  4117. ) ;
  4118. # APPENDLIMIT with --appendlimit => --appendlimit wins
  4119. $mysync->{ appendlimit } = $NUMBER_123456 ;
  4120. is( $NUMBER_123456, maxsize_setting( $mysync ),
  4121. 'maxsize_setting: APPENDLIMIT 654321 + --appendlimit 123456 => 123456'
  4122. ) ;
  4123. is( $NUMBER_123456, $mysync->{ maxsize },
  4124. 'maxsize_setting: APPENDLIMIT 654321 + --appendlimit 123456 => maxsize 123456'
  4125. ) ;
  4126. # Fresh
  4127. $mysync = { } ;
  4128. $mysync->{ imap2 } = $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=654321' ) ;
  4129. # Case: "APPENDLIMIT >= --maxsize" => maxsize.
  4130. $mysync->{ maxsize } = $NUMBER_123456 ;
  4131. is( $NUMBER_123456, maxsize_setting( $mysync ),
  4132. 'maxsize_setting: APPENDLIMIT 654321 --maxsize 123456 => 123456'
  4133. ) ;
  4134. # Case: "APPENDLIMIT < --maxsize" => APPENDLIMIT.
  4135. # Fresh
  4136. $mysync = { } ;
  4137. $mysync->{ imap2 } = $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ;
  4138. $mysync->{ maxsize } = $NUMBER_654321 ;
  4139. is( $NUMBER_123456, maxsize_setting( $mysync ),
  4140. 'maxsize_setting: APPENDLIMIT 123456 --maxsize 654321 => 123456 '
  4141. ) ;
  4142. # Now --truncmess stuff
  4143. note( 'Leaving tests_maxsize_setting()' ) ;
  4144. return ;
  4145. }
  4146. # Three variables to take account of
  4147. # appendlimit (given by --appendlimit or CAPABILITY...)
  4148. # maxsize
  4149. # truncmess
  4150. sub maxsize_setting
  4151. {
  4152. my $mysync = shift || return ;
  4153. if ( defined $mysync->{ appendlimit } )
  4154. {
  4155. myprint( "Host2: Getting appendlimit from --appendlimit $mysync->{ appendlimit }\n" ) ;
  4156. }
  4157. else
  4158. {
  4159. $mysync->{ appendlimit } = appendlimit( $mysync ) ;
  4160. }
  4161. if ( all_defined( $mysync->{ appendlimit }, $mysync->{ maxsize } ) )
  4162. {
  4163. my $min_maxsize_appendlimit = min( $mysync->{ maxsize }, $mysync->{ appendlimit } ) ;
  4164. myprint( "Host2: Setting maxsize to $min_maxsize_appendlimit (min of --maxsize $mysync->{ maxsize } and appendlimit $mysync->{ appendlimit }\n" ) ;
  4165. $mysync->{ maxsize } = $min_maxsize_appendlimit ;
  4166. return $mysync->{ maxsize } ;
  4167. }
  4168. elsif ( defined $mysync->{ appendlimit } )
  4169. {
  4170. myprint( "Host2: Setting maxsize to appendlimit $mysync->{ appendlimit }\n" ) ;
  4171. $mysync->{ maxsize } = $mysync->{ appendlimit } ;
  4172. return $mysync->{ maxsize } ;
  4173. }elsif ( defined $mysync->{ maxsize } )
  4174. {
  4175. return $mysync->{ maxsize } ;
  4176. }else
  4177. {
  4178. return ;
  4179. }
  4180. }
  4181. sub all_defined
  4182. {
  4183. if ( not @ARG ) {
  4184. return 0 ;
  4185. }
  4186. foreach my $elem ( @ARG ) {
  4187. if ( not defined $elem ) {
  4188. return 0 ;
  4189. }
  4190. }
  4191. return 1 ;
  4192. }
  4193. sub tests_all_defined
  4194. {
  4195. note( 'Entering tests_all_defined()' ) ;
  4196. is( 0, all_defined( ), 'all_defined: no param => 0' ) ;
  4197. is( 0, all_defined( () ), 'all_defined: void list => 0' ) ;
  4198. is( 0, all_defined( undef ), 'all_defined: undef => 0' ) ;
  4199. is( 0, all_defined( undef, undef ), 'all_defined: undef => 0' ) ;
  4200. is( 0, all_defined( 1, undef ), 'all_defined: 1 undef => 0' ) ;
  4201. is( 0, all_defined( undef, 1 ), 'all_defined: undef 1 => 0' ) ;
  4202. is( 1, all_defined( 1, 1 ), 'all_defined: 1 1 => 1' ) ;
  4203. is( 1, all_defined( (1, 1) ), 'all_defined: (1 1) => 1' ) ;
  4204. note( 'Leaving tests_all_defined()' ) ;
  4205. return ;
  4206. }
  4207. sub tests_hashsynclocal
  4208. {
  4209. note( 'Entering tests_hashsynclocal()' ) ;
  4210. my $mysync = {
  4211. host1 => q{},
  4212. user1 => q{},
  4213. password1 => q{},
  4214. host2 => q{},
  4215. user2 => q{},
  4216. password2 => q{},
  4217. } ;
  4218. is( undef, hashsynclocal( $mysync ), 'hashsynclocal: no hashfile name' ) ;
  4219. $mysync->{ hashfile } = q{} ;
  4220. is( undef, hashsynclocal( $mysync ), 'hashsynclocal: empty hashfile name' ) ;
  4221. $mysync->{ hashfile } = './noexist/rrr' ;
  4222. is( undef, hashsynclocal( $mysync ), 'hashsynclocal: no exists hashfile dir' ) ;
  4223. SKIP: {
  4224. if ( 'MSWin32' eq $OSNAME or '0' eq $EFFECTIVE_USER_ID ) { skip( 'Tests only for non-root Unix', 1 ) ; }
  4225. $mysync->{ hashfile } = '/rrr' ;
  4226. is( undef, hashsynclocal( $mysync ), 'hashsynclocal: permission denied' ) ;
  4227. }
  4228. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'hashsynclocal: mkpath W/tmp/tests/' ) ;
  4229. $mysync->{ hashfile } = 'W/tmp/tests/imapsync_hash' ;
  4230. ok( ! -e 'W/tmp/tests/imapsync_hash' || unlink 'W/tmp/tests/imapsync_hash', 'hashsynclocal: unlink W/tmp/tests/imapsync_hash' ) ;
  4231. ok( ! -e 'W/tmp/tests/imapsync_hash', 'hashsynclocal: verify there is no W/tmp/tests/imapsync_hash' ) ;
  4232. is( 'ecdeb4ede672794d173da4e08c52b8ee19b7d252', hashsynclocal( $mysync, 'mukksyhpmbixkxkpjlqivmlqsulpictj' ), 'hashsynclocal: creating/reading W/tmp/tests/imapsync_hash' ) ;
  4233. # A second time now
  4234. is( 'ecdeb4ede672794d173da4e08c52b8ee19b7d252', hashsynclocal( $mysync ), 'hashsynclocal: reading W/tmp/tests/imapsync_hash second time => same' ) ;
  4235. note( 'Leaving tests_hashsynclocal()' ) ;
  4236. return ;
  4237. }
  4238. sub hashsynclocal
  4239. {
  4240. my $mysync = shift @ARG ;
  4241. my $hashkey = shift @ARG ; # Optional, only there for tests
  4242. my $hashfile = $mysync->{ hashfile } ;
  4243. $hashfile = createhashfileifneeded( $hashfile, $hashkey ) ;
  4244. if ( ! $hashfile ) {
  4245. return ;
  4246. }
  4247. $hashkey = firstline( $hashfile ) ;
  4248. if ( ! $hashkey ) {
  4249. myprint( "No hashkey!\n" ) ;
  4250. return ;
  4251. }
  4252. my $hashsynclocal = hashsync( $mysync, $hashkey ) ;
  4253. return( $hashsynclocal ) ;
  4254. }
  4255. sub tests_hashsync
  4256. {
  4257. note( 'Entering tests_hashsync()' ) ;
  4258. is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hashsync( ), 'hashsync: no args' ) ;
  4259. is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hashsync( {}, q{} ), 'hashsync: empty args' ) ;
  4260. my $mysync ;
  4261. $mysync->{ host1 } = 'zzz' ;
  4262. is( 'e86a28a3611c1e7bbaf8057cd00ae122781a11fe', hashsync( $mysync, q{} ), 'hashsync: host1 zzz => ' ) ;
  4263. is( '6a7b451ac99eab1531ad8e6cd544b32420c552ac', hashsync( $mysync, q{A} ), 'hashsync: host1 zzz => ' ) ;
  4264. $mysync->{ host2 } = 'zzz' ;
  4265. is( '15959573e4a86763253a7aedb1a2b0c60d133dc2', hashsync( $mysync, q{} ), 'hashsync: + host2 zzz => ' ) ;
  4266. is( 'b8d4ab541b209c75928528020ca28ee43488bd8f', hashsync( $mysync, 'A' ), 'hashsync: + hashkey A => ' ) ;
  4267. $mysync = undef ;
  4268. is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hashsync( $mysync, q{} ), 'hashsync: undef $mysync' ) ;
  4269. $mysync->{ password1 } = 'abcd' ;
  4270. is( 'afa29ab8534495251ac8346a985717c54bc49c26', hashsync( $mysync, q{} ), 'hashsync: password1: abcd' ) ;
  4271. # A user reported a massive failure on /X (Thomas V. 21/04/2020 à 21:41 Subject: Error)
  4272. # "Wide character in subroutine entry at /usr/local/lib/perl5/site_perl/Digest/HMAC.pm"
  4273. # I can reproduce it now
  4274. # The eval is there to avoid a complete crash
  4275. # this one is fatal so it is commented
  4276. # is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', 1 / 0 , 'hashsync: 1 / 0 fatal' ) ;
  4277. my $eval ;
  4278. # this one is not fatal
  4279. is( undef, $eval = eval { 1 / 0 } , 'hashsync: 1/0 not fatal' ) ;
  4280. # this one neither
  4281. $mysync->{ password1 } = 'Ö' ;
  4282. is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', $eval = eval { hashsync( $mysync, q{} ) } , 'hashsync: password1: Ö with eval' ) ;
  4283. $mysync->{ password1 } = 'Ö' ;
  4284. is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hashsync( $mysync, q{} ), 'hashsync: password1: Ö without eval' ) ;
  4285. $mysync->{ password1 } = qq{\x{00D6}} ;
  4286. is( 'bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a', $eval = eval { hashsync( $mysync, q{} ) }, 'hashsync: password1: \x{00D6}' ) ; #
  4287. print qq{1 00D6:Ö\n} ;
  4288. print encode_utf8( qq{2 00D6:Ö\n} ) ;
  4289. print qq{3 00D6:\x{00D6}\n} ;
  4290. print encode_utf8( qq{4 00D6:\x{00D6}\n} ) ;
  4291. print qq{5 6536:收\n} ;
  4292. print encode_utf8( qq{6 6536:收\n} ) ;
  4293. # the next one prints "Wide character in print at ./imapsync line xxxx"
  4294. print qq{7 6536:\x{6536}\n} ;
  4295. print encode_utf8( qq{8 6536:\x{6536}\n} ) ;
  4296. $mysync->{ password1 } = qq{收} ;
  4297. is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hashsync( $mysync, q{} ), 'hashsync: password1: 收' ) ;
  4298. $mysync->{ password1 } = qq{\x{6536}} ;
  4299. is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', $eval = eval{ hashsync( $mysync, q{} ) }, 'hashsync: password1: \x{6536} with eval' ) ;
  4300. # No side effect.
  4301. $mysync->{ password1 } = 'abcd' ;
  4302. is( 'afa29ab8534495251ac8346a985717c54bc49c26', hashsync( $mysync, q{} ), 'hashsync: password1: abcd again' ) ;
  4303. note( 'Leaving tests_hashsync()' ) ;
  4304. return ;
  4305. }
  4306. sub hashsync
  4307. {
  4308. my $mysync = shift @ARG ;
  4309. my $hashkey = shift @ARG ;
  4310. my $mystring = join( q{},
  4311. $mysync->{ host1 } || q{},
  4312. $mysync->{ user1 } || q{},
  4313. $mysync->{ password1 } || q{},
  4314. $mysync->{ host2 } || q{},
  4315. $mysync->{ user2 } || q{},
  4316. $mysync->{ password2 } || q{},
  4317. ) ;
  4318. #my $hashsync = hmac_sha1_hex( $mystring, $hashkey ) ;
  4319. my $hashsync = hmac_sha1_hex_robust( $mystring, $hashkey ) ;
  4320. #myprint( "$hashsync\n" ) ;
  4321. return( $hashsync ) ;
  4322. }
  4323. sub tests_hmac_sha1_hex
  4324. {
  4325. note( 'Entering tests_hmac_sha1_hex()' ) ;
  4326. is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex( ), 'hmac_sha1_hex: no args => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
  4327. is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex( '' ), 'hmac_sha1_hex: empty string => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
  4328. is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex( '', '' ), 'hmac_sha1_hex: empty strings => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
  4329. is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex( '', '', 'caca' ), 'hmac_sha1_hex: empty strings + caca => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
  4330. # Good
  4331. is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hmac_sha1_hex( 'Ö' ), 'hmac_sha1_hex: Ö => f1a3f3dac3f137fd658027c11678b895f773ce55' ) ;
  4332. is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hmac_sha1_hex( encode_utf8(qq{\x{00D6}}) ), 'hmac_sha1_hex: encode_utf8 \x{00D6} => f1a3f3dac3f137fd658027c11678b895f773ce55' ) ;
  4333. # Bad
  4334. is( 'fe8dc3b9ba3e8850bb4a7b070b2279e911003af2', hmac_sha1_hex( encode_utf8( 'Ö' ) ), 'hmac_sha1_hex: encode_utf8 Ö => fe8dc3b9ba3e8850bb4a7b070b2279e911003af2' ) ;
  4335. is( 'bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a', hmac_sha1_hex( qq{\x{00D6}} ), 'hmac_sha1_hex: qq{\x{00D6}} => bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a' ) ;
  4336. # Good
  4337. is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex( 'A' ), 'hmac_sha1_hex: A => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
  4338. is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex( encode_utf8(qq{\x{0041}}) ), 'hmac_sha1_hex: encode_utf8 \x{0041} => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
  4339. is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex( encode_utf8( 'A' ) ), 'hmac_sha1_hex: encode_utf8 A => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
  4340. is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex( qq{\x{0041}} ), 'hmac_sha1_hex: \x{0041} => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
  4341. # Good
  4342. is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex( 'A', 'B' ), 'hmac_sha1_hex: A B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
  4343. is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex( encode_utf8(qq{\x{0041}}), 'B' ), 'hmac_sha1_hex: encode_utf8 \x{0041} B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
  4344. is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex( encode_utf8( 'A' ), 'B' ), 'hmac_sha1_hex: encode_utf8 A B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
  4345. is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex( qq{\x{0041}}, 'B' ), 'hmac_sha1_hex: \x{0041} B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
  4346. # http://unicode.scarfboy.com/?s=U%2B6536
  4347. # Good
  4348. is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex( '收' ), 'hmac_sha1_hex: 收 => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ;
  4349. is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex( encode_utf8(qq{\x{6536}}) ), 'hmac_sha1_hex: encode_utf8 \x{6536} => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ;
  4350. # Bad
  4351. is( 'e82217119628ad03e659cc89671d05ea4cee7238', hmac_sha1_hex( encode_utf8( '收' ) ), 'hmac_sha1_hex: encode_utf8 收 => e82217119628ad03e659cc89671d05ea4cee7238' ) ;
  4352. # Very very bad, perl dies...
  4353. #is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex( qq{\x{6536}} ), 'hmac_sha1_hex: \x{6536} => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ;
  4354. # Ok but well, bad indeed
  4355. is( undef, my $eval = eval{ hmac_sha1_hex( qq{\x{6536}} ) }, 'hmac_sha1_hex: \x{6536} => undef' ) ;
  4356. note( 'Leaving tests_hmac_sha1_hex()' ) ;
  4357. return ;
  4358. }
  4359. sub tests_hmac_sha1_hex_robust
  4360. {
  4361. note( 'Entering tests_hmac_sha1_hex_robust()' ) ;
  4362. is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex_robust( ), 'hmac_sha1_hex_robust: no args => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
  4363. is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex_robust( '' ), 'hmac_sha1_hex_robust: empty string => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
  4364. is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex_robust( '', '' ), 'hmac_sha1_hex_robust: empty strings => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
  4365. is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex_robust( '', '', 'caca' ), 'hmac_sha1_hex_robust: empty strings + caca => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ;
  4366. # Good
  4367. is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hmac_sha1_hex_robust( 'Ö' ), 'hmac_sha1_hex_robust: Ö => f1a3f3dac3f137fd658027c11678b895f773ce55' ) ;
  4368. is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hmac_sha1_hex_robust( encode_utf8(qq{\x{00D6}}) ), 'hmac_sha1_hex_robust: encode_utf8 \x{00D6} => f1a3f3dac3f137fd658027c11678b895f773ce55' ) ;
  4369. # Bad
  4370. is( 'fe8dc3b9ba3e8850bb4a7b070b2279e911003af2', hmac_sha1_hex_robust( encode_utf8( 'Ö' ) ), 'hmac_sha1_hex_robust: encode_utf8 Ö => fe8dc3b9ba3e8850bb4a7b070b2279e911003af2' ) ;
  4371. is( 'bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a', hmac_sha1_hex_robust( qq{\x{00D6}} ), 'hmac_sha1_hex_robust: qq{\x{00D6}} => bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a' ) ;
  4372. # Good
  4373. is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex_robust( 'A' ), 'hmac_sha1_hex_robust: A => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
  4374. is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex_robust( encode_utf8(qq{\x{0041}}) ), 'hmac_sha1_hex_robust: encode_utf8 \x{0041} => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
  4375. is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex_robust( encode_utf8( 'A' ) ), 'hmac_sha1_hex_robust: encode_utf8 A => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
  4376. is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex_robust( qq{\x{0041}} ), 'hmac_sha1_hex_robust: \x{0041} => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ;
  4377. # Good
  4378. is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex_robust( 'A', 'B' ), 'hmac_sha1_hex_robust: A B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
  4379. is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex_robust( encode_utf8(qq{\x{0041}}), 'B' ), 'hmac_sha1_hex_robust: encode_utf8 \x{0041} B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
  4380. is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex_robust( encode_utf8( 'A' ), 'B' ), 'hmac_sha1_hex_robust: encode_utf8 A B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
  4381. is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex_robust( qq{\x{0041}}, 'B' ), 'hmac_sha1_hex_robust: \x{0041} B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ;
  4382. # http://unicode.scarfboy.com/?s=U%2B6536
  4383. # Good
  4384. is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex_robust( '收' ), 'hmac_sha1_hex_robust: 收 => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ;
  4385. is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex_robust( encode_utf8(qq{\x{6536}}) ), 'hmac_sha1_hex_robust: encode_utf8 \x{6536} => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ;
  4386. # Bad
  4387. is( 'e82217119628ad03e659cc89671d05ea4cee7238', hmac_sha1_hex_robust( encode_utf8( '收' ) ), 'hmac_sha1_hex_robust: encode_utf8 收 => e82217119628ad03e659cc89671d05ea4cee7238' ) ;
  4388. # Good
  4389. is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex_robust( qq{\x{6536}} ), 'hmac_sha1_hex_robust: \x{6536} => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ;
  4390. # Good again
  4391. is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', my $eval = eval{ hmac_sha1_hex_robust( qq{\x{6536}} ) }, 'hmac_sha1_hex_robust: \x{6536} => undef' ) ;
  4392. note( 'Leaving tests_hmac_sha1_hex_robust()' ) ;
  4393. return ;
  4394. }
  4395. sub hmac_sha1_hex_robust
  4396. {
  4397. my $string = shift @ARG ;
  4398. my $val ;
  4399. if ( defined( $val = eval{ hmac_sha1_hex( $string, @ARG ) } ) )
  4400. {
  4401. return $val ;
  4402. }
  4403. elsif( defined( $val = eval{ hmac_sha1_hex( encode_utf8( $string ), @ARG ) } ) )
  4404. {
  4405. return $val ;
  4406. }
  4407. else
  4408. {
  4409. return ;
  4410. }
  4411. }
  4412. sub tests_createhashfileifneeded
  4413. {
  4414. note( 'Entering tests_createhashfileifneeded()' ) ;
  4415. is( undef, createhashfileifneeded( ), 'createhashfileifneeded: no parameters => undef' ) ;
  4416. note( 'Leaving tests_createhashfileifneeded()' ) ;
  4417. return ;
  4418. }
  4419. sub createhashfileifneeded
  4420. {
  4421. my $hashfile = shift @ARG ;
  4422. my $hashkey = shift || rand32( ) ;
  4423. # no name
  4424. if ( ! $hashfile ) {
  4425. return ;
  4426. }
  4427. # already there
  4428. if ( -e -r $hashfile ) {
  4429. return $hashfile ;
  4430. }
  4431. # not creatable
  4432. if ( ! -w dirname( $hashfile ) ) {
  4433. return ;
  4434. }
  4435. # creatable
  4436. open my $FILE_HANDLE, '>', $hashfile
  4437. or do {
  4438. myprint( "Could not open $hashfile for writing. Check permissions or disk space." ) ;
  4439. return ;
  4440. } ;
  4441. myprint( "Writing random hashkey in $hashfile, once for all times\n" ) ;
  4442. print $FILE_HANDLE $hashkey ;
  4443. close $FILE_HANDLE ;
  4444. # Should be there now
  4445. if ( -e -r $hashfile ) {
  4446. return $hashfile ;
  4447. }
  4448. # unknown failure
  4449. return ;
  4450. }
  4451. sub tests_rand32
  4452. {
  4453. note( 'Entering tests_rand32()' ) ;
  4454. my $string = rand32( ) ;
  4455. myprint( "$string\n" ) ;
  4456. is( 32, length( $string ), 'rand32: 32 characters long' ) ;
  4457. is( 32, length( rand32( ) ), 'rand32: 32 characters long, another one' ) ;
  4458. note( 'Leaving tests_rand32()' ) ;
  4459. return ;
  4460. }
  4461. sub rand32
  4462. {
  4463. my @chars = ( "a".."z" ) ;
  4464. my $string;
  4465. $string .= $chars[rand @chars] for 1..32 ;
  4466. return $string ;
  4467. }
  4468. sub imap_id_stuff
  4469. {
  4470. my $mysync = shift @ARG ;
  4471. if ( not $mysync->{id} ) { return ; } ;
  4472. $mysync->{h1_imap_id} = imap_id( $mysync, $mysync->{imap1}, 'Host1' ) ;
  4473. #myprint( 'Host1: ' . $mysync->{h1_imap_id} ) ;
  4474. $mysync->{h2_imap_id} = imap_id( $mysync, $mysync->{imap2}, 'Host2' ) ;
  4475. #myprint( 'Host2: ' . $mysync->{h2_imap_id} ) ;
  4476. return ;
  4477. }
  4478. sub imap_id
  4479. {
  4480. my ( $mysync, $imap, $Side ) = @_ ;
  4481. if ( not $mysync->{id} ) { return q{} ; } ;
  4482. $Side ||= q{} ;
  4483. my $imap_id_response = q{} ;
  4484. if ( not $imap->has_capability( 'ID' ) ) {
  4485. $imap_id_response = 'No ID capability' ;
  4486. myprint( "$Side: No ID capability\n" ) ;
  4487. }else{
  4488. my $id_inp = imapsync_id( $mysync, { side => lc $Side } ) ;
  4489. myprint( "$Side: found ID capability. Sending/receiving ID, presented in raw IMAP for now.\n"
  4490. . "In order to avoid sending/receiving ID, use option --noid\n" ) ;
  4491. my $debug_before = $imap->Debug( ) ;
  4492. $imap->Debug( 1 ) ;
  4493. my $id_out = $imap->tag_and_run( 'ID ' . $id_inp ) ;
  4494. #my $id_out = $imap->tag_and_run( 'ID NIL' ) ;
  4495. #myprint( "\n" ) ;
  4496. $imap->Debug( $debug_before ) ;
  4497. #$imap_id_response = Data::Dumper->Dump( [ $id_out ], [ 'IMAP_ID' ] ) ;
  4498. }
  4499. return( $imap_id_response ) ;
  4500. }
  4501. sub imapsync_id
  4502. {
  4503. my $mysync = shift @ARG ;
  4504. my $overhashref = shift @ARG ;
  4505. # See http://tools.ietf.org/html/rfc2971.html
  4506. my $imapsync_id = { } ;
  4507. my $imapsync_id_lamiral = {
  4508. name => 'imapsync',
  4509. version => imapsync_version( $mysync ),
  4510. os => $OSNAME,
  4511. vendor => 'Gilles LAMIRAL',
  4512. 'support-url' => 'https://imapsync.lamiral.info/',
  4513. # Example of date-time: 19-Sep-2015 08:56:07
  4514. date => date_from_rcs( q{$Date: 2022/09/14 18:08:24 $ } ),
  4515. } ;
  4516. my $imapsync_id_github = {
  4517. name => 'imapsync',
  4518. version => imapsync_version( $mysync ),
  4519. os => $OSNAME,
  4520. vendor => 'github',
  4521. 'support-url' => 'https://github.com/imapsync/imapsync',
  4522. date => date_from_rcs( q{$Date: 2022/09/14 18:08:24 $ } ),
  4523. } ;
  4524. $imapsync_id = $imapsync_id_lamiral ;
  4525. #$imapsync_id = $imapsync_id_github ;
  4526. my %mix = ( %{ $imapsync_id }, %{ $overhashref } ) ;
  4527. my $imapsync_id_str = format_for_imap_arg( \%mix ) ;
  4528. #myprint( "$imapsync_id_str\n" ) ;
  4529. return( $imapsync_id_str ) ;
  4530. }
  4531. sub tests_imapsync_id
  4532. {
  4533. note( 'Entering tests_imapsync_id()' ) ;
  4534. my $mysync ;
  4535. ok( '("name" "imapsync" "version" "111" "os" "beurk" "vendor" "Gilles LAMIRAL" "support-url" "https://imapsync.lamiral.info/" "date" "22-12-1968" "side" "host1")'
  4536. eq imapsync_id( $mysync,
  4537. {
  4538. version => 111,
  4539. os => 'beurk',
  4540. date => '22-12-1968',
  4541. side => 'host1'
  4542. }
  4543. ),
  4544. 'tests_imapsync_id override'
  4545. ) ;
  4546. note( 'Leaving tests_imapsync_id()' ) ;
  4547. return ;
  4548. }
  4549. sub format_for_imap_arg
  4550. {
  4551. my $ref = shift @ARG ;
  4552. my $string = q{} ;
  4553. my %terms = %{ $ref } ;
  4554. my @terms = ( ) ;
  4555. if ( not ( %terms ) ) { return( 'NIL' ) } ;
  4556. # sort like in RFC then add extra key/values
  4557. foreach my $key ( qw( name version os os-version vendor support-url address date command arguments environment) ) {
  4558. if ( $terms{ $key } ) {
  4559. push @terms, $key, $terms{ $key } ;
  4560. delete $terms{ $key } ;
  4561. }
  4562. }
  4563. push @terms, %terms ;
  4564. $string = '(' . ( join q{ }, map { '"' . $_ . '"' } @terms ) . ')' ;
  4565. return( $string ) ;
  4566. }
  4567. sub tests_format_for_imap_arg
  4568. {
  4569. note( 'Entering tests_format_for_imap_arg()' ) ;
  4570. ok( 'NIL' eq format_for_imap_arg( { } ), 'format_for_imap_arg empty hash ref' ) ;
  4571. ok( '("name" "toto")' eq format_for_imap_arg( { name => 'toto' } ), 'format_for_imap_arg { name => toto }' ) ;
  4572. ok( '("name" "toto" "key" "val")' eq format_for_imap_arg( { name => 'toto', key => 'val' } ), 'format_for_imap_arg 2 x key val' ) ;
  4573. note( 'Leaving tests_format_for_imap_arg()' ) ;
  4574. return ;
  4575. }
  4576. sub quota
  4577. {
  4578. my ( $mysync, $imap, $side ) = @_ ;
  4579. my %side = (
  4580. h1 => 'Host1',
  4581. h2 => 'Host2',
  4582. ) ;
  4583. my $Side = $side{ $side } ;
  4584. my $debug_before = $imap->Debug( ) ;
  4585. $imap->Debug( 1 ) ;
  4586. if ( not $imap->has_capability( 'QUOTA' ) )
  4587. {
  4588. myprint( "$Side: No QUOTA capability found, skipping it.\n" ) ;
  4589. $imap->Debug( $debug_before ) ;
  4590. return ;
  4591. } ;
  4592. myprint( "\n$Side: QUOTA capability found, presented in raw IMAP on next lines\n" ) ;
  4593. my $getquotaroot = $imap->getquotaroot( 'INBOX' ) ;
  4594. # Gmail INBOX quotaroot is "" but with it Mail::IMAPClient does a literal GETQUOTA {2} \n ""
  4595. #$imap->quota( 'ROOT' ) ;
  4596. #$imap->quota( '""' ) ;
  4597. myprint( "\n" ) ;
  4598. $imap->Debug( $debug_before ) ;
  4599. my $quota_limit_bytes = quota_extract_storage_limit_in_bytes( $mysync, $getquotaroot ) ;
  4600. my $quota_current_bytes = quota_extract_storage_current_in_bytes( $mysync, $getquotaroot ) ;
  4601. $mysync->{$side}->{quota_limit_bytes} = $quota_limit_bytes ;
  4602. $mysync->{$side}->{quota_current_bytes} = $quota_current_bytes ;
  4603. my $quota_percent ;
  4604. if ( $quota_limit_bytes > 0 ) {
  4605. $quota_percent = mysprintf( '%.2f', $NUMBER_100 * $quota_current_bytes / $quota_limit_bytes ) ;
  4606. }else{
  4607. $quota_percent = 0 ;
  4608. }
  4609. myprint( "$Side: Quota current storage is $quota_current_bytes bytes. Limit is $quota_limit_bytes bytes. So $quota_percent % full\n" ) ;
  4610. if ( $QUOTA_PERCENT_LIMIT < $quota_percent ) {
  4611. my $error = "$Side: $quota_percent % full: it is time to find a bigger place! ( $quota_current_bytes bytes / $quota_limit_bytes bytes )\n" ;
  4612. errors_incr( $mysync, $error ) ;
  4613. }
  4614. return ;
  4615. }
  4616. sub tests_quota_extract_storage_limit_in_bytes
  4617. {
  4618. note( 'Entering tests_quota_extract_storage_limit_in_bytes()' ) ;
  4619. my $mysync = {} ;
  4620. my $imap_output = [
  4621. '* QUOTAROOT "INBOX" "Storage quota" "Messages quota"',
  4622. '* QUOTA "Storage quota" (STORAGE 1 104857600)',
  4623. '* QUOTA "Messages quota" (MESSAGE 2 100000)',
  4624. '5 OK Getquotaroot completed.'
  4625. ] ;
  4626. is( $NUMBER_104_857_600 * $KIBI, quota_extract_storage_limit_in_bytes( $mysync, $imap_output ), 'quota_extract_storage_limit_in_bytes (STORAGE 1 104857600) => 104857600 * 1024') ;
  4627. $imap_output = [
  4628. '* QUOTAROOT "INBOX" "Storage quota" "Messages quota"',
  4629. 'QUOTA "user-defined quota (konsoleH)" (STORAGE 988 48829 MESSAGE 20 20)',
  4630. '5 OK Getquotaroot completed.'
  4631. ] ;
  4632. is( 48829 * 1024, quota_extract_storage_limit_in_bytes( $mysync, $imap_output ), 'quota_extract_storage_limit_in_bytes (STORAGE 988 48829 MESSAGE 20 20) => 48829 * 1024') ;
  4633. note( 'Leaving tests_quota_extract_storage_limit_in_bytes()' ) ;
  4634. return ;
  4635. }
  4636. sub quota_extract_storage_limit_in_bytes
  4637. {
  4638. my $mysync = shift @ARG ;
  4639. my $imap_output = shift @ARG ;
  4640. my $limit_kb ;
  4641. $limit_kb = ( map { /STORAGE\s+\d+\s+(\d+)/x ? $1 : () } @{ $imap_output } )[0] ;
  4642. $limit_kb ||= 0 ;
  4643. $mysync->{ debug } and myprint( "storage_limit_kb = $limit_kb\n" ) ;
  4644. return( $KIBI * $limit_kb ) ;
  4645. }
  4646. sub tests_quota_extract_storage_current_in_bytes
  4647. {
  4648. note( 'Entering tests_quota_extract_storage_current_in_bytes()' ) ;
  4649. my $mysync = {} ;
  4650. my $imap_output = [
  4651. '* QUOTAROOT "INBOX" "Storage quota" "Messages quota"',
  4652. '* QUOTA "Storage quota" (STORAGE 1 104857600)',
  4653. '* QUOTA "Messages quota" (MESSAGE 2 100000)',
  4654. '5 OK Getquotaroot completed.'
  4655. ] ;
  4656. is( 1*$KIBI, quota_extract_storage_current_in_bytes( $mysync, $imap_output ), 'quota_extract_storage_current_in_bytes: 1 => 1024 ') ;
  4657. $imap_output = [
  4658. '* QUOTAROOT "INBOX" "Storage quota" "Messages quota"',
  4659. 'QUOTA "user-defined quota (konsoleH)" (STORAGE 988 48829 MESSAGE 20 20)',
  4660. '5 OK Getquotaroot completed.'
  4661. ] ;
  4662. is( 988 * 1024, quota_extract_storage_current_in_bytes( $mysync, $imap_output ), 'quota_extract_storage_current_in_bytes (STORAGE 988 48829 MESSAGE 20 20) => 988 * 1024') ;
  4663. note( 'Leaving tests_quota_extract_storage_current_in_bytes()' ) ;
  4664. return ;
  4665. }
  4666. sub quota_extract_storage_current_in_bytes
  4667. {
  4668. my $mysync = shift @ARG ;
  4669. my $imap_output = shift @ARG ;
  4670. my $current_kb ;
  4671. $current_kb = ( map { /STORAGE\s+(\d+)\s+\d+/x ? $1 : () } @{ $imap_output } )[0] ;
  4672. $current_kb ||= 0 ;
  4673. $mysync->{ debug } and myprint( "storage_current_kb = $current_kb\n" ) ;
  4674. return( $KIBI * $current_kb ) ;
  4675. }
  4676. sub make_f1f2_array_to_a_hash
  4677. {
  4678. my $mysync = shift @ARG ;
  4679. %{ $mysync->{ f1f2h } } = split_around_equal( @{ $mysync->{ f1f2 } } ) ;
  4680. return ;
  4681. }
  4682. sub automap
  4683. {
  4684. my ( $mysync ) = @_ ;
  4685. if ( $mysync->{automap} ) {
  4686. myprint( "Turned on automapping folders ( use --noautomap to turn off automapping )\n" ) ;
  4687. }else{
  4688. myprint( "Turned off automapping folders ( use --automap to turn on automapping )\n" ) ;
  4689. return ;
  4690. }
  4691. $mysync->{h1_special} = special_from_folders_hash( $mysync, $mysync->{imap1}, 'Host1' ) ;
  4692. $mysync->{h2_special} = special_from_folders_hash( $mysync, $mysync->{imap2}, 'Host2' ) ;
  4693. build_possible_special( $mysync ) ;
  4694. build_guess_special( $mysync ) ;
  4695. build_automap( $mysync ) ;
  4696. return ;
  4697. }
  4698. sub build_guess_special
  4699. {
  4700. my ( $mysync ) = shift @ARG ;
  4701. foreach my $h1_fold ( sort keys %{ $mysync->{h1_folders_all} } ) {
  4702. my $special = guess_special( $h1_fold, $mysync->{possible_special}, $mysync->{h1_prefix} ) ;
  4703. if ( $special ) {
  4704. $mysync->{h1_special_guessed}{$h1_fold} = $special ;
  4705. my $already_guessed = $mysync->{h1_special_guessed}{$special} ;
  4706. if ( $already_guessed ) {
  4707. myprint( "Host1: $h1_fold not $special because set to $already_guessed\n" ) ;
  4708. }else{
  4709. $mysync->{h1_special_guessed}{$special} = $h1_fold ;
  4710. }
  4711. }
  4712. }
  4713. foreach my $h2_fold ( sort keys %{ $mysync->{h2_folders_all} } ) {
  4714. my $special = guess_special( $h2_fold, $mysync->{possible_special}, $mysync->{h2_prefix} ) ;
  4715. if ( $special ) {
  4716. $mysync->{h2_special_guessed}{$h2_fold} = $special ;
  4717. my $already_guessed = $mysync->{h2_special_guessed}{$special} ;
  4718. if ( $already_guessed ) {
  4719. myprint( "Host2: $h2_fold not $special because set to $already_guessed\n" ) ;
  4720. }else{
  4721. $mysync->{h2_special_guessed}{$special} = $h2_fold ;
  4722. }
  4723. }
  4724. }
  4725. return ;
  4726. }
  4727. sub guess_special
  4728. {
  4729. my( $folder, $possible_special_ref, $prefix ) = @_ ;
  4730. my $folder_no_prefix = $folder ;
  4731. $folder_no_prefix =~ s/\Q${prefix}\E//xms ;
  4732. #$debug and myprint( "folder_no_prefix: $folder_no_prefix\n" ) ;
  4733. my $guess_special = $possible_special_ref->{ $folder }
  4734. || $possible_special_ref->{ $folder_no_prefix }
  4735. || q{} ;
  4736. return( $guess_special ) ;
  4737. }
  4738. sub tests_guess_special
  4739. {
  4740. note( 'Entering tests_guess_special()' ) ;
  4741. my $possible_special_ref = build_possible_special( my $mysync ) ;
  4742. ok( '\Sent' eq guess_special( 'Sent', $possible_special_ref, q{} ) ,'guess_special: Sent => \Sent' ) ;
  4743. ok( q{} eq guess_special( 'Blabla', $possible_special_ref, q{} ) ,'guess_special: Blabla => q{}' ) ;
  4744. ok( '\Sent' eq guess_special( 'INBOX.Sent', $possible_special_ref, 'INBOX.' ) ,'guess_special: INBOX.Sent => \Sent' ) ;
  4745. ok( '\Sent' eq guess_special( 'IN BOX.Sent', $possible_special_ref, 'IN BOX.' ) ,'guess_special: IN BOX.Sent => \Sent' ) ;
  4746. note( 'Leaving tests_guess_special()' ) ;
  4747. return ;
  4748. }
  4749. sub build_automap
  4750. {
  4751. my $mysync = shift @ARG ;
  4752. $mysync->{ debug } and myprint( "Entering build_automap\n" ) ;
  4753. foreach my $h1_fold ( @{ $mysync->{h1_folders_wanted} } ) {
  4754. my $h2_fold ;
  4755. my $h1_special = $mysync->{h1_special}{$h1_fold} ;
  4756. my $h1_special_guessed = $mysync->{h1_special_guessed}{$h1_fold} ;
  4757. # Case 1: special on both sides.
  4758. if ( $h1_special
  4759. and exists $mysync->{h2_special}{$h1_special} ) {
  4760. $h2_fold = $mysync->{h2_special}{$h1_special} ;
  4761. $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ;
  4762. next ;
  4763. }
  4764. # Case 2: special on host1, not on host2
  4765. if ( $h1_special
  4766. and ( not exists $mysync->{h2_special}{$h1_special} )
  4767. and ( exists $mysync->{h2_special_guessed}{$h1_special} )
  4768. ) {
  4769. # special_guessed on host2
  4770. $h2_fold = $mysync->{h2_special_guessed}{$h1_special} ;
  4771. $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ;
  4772. next ;
  4773. }
  4774. # Case 3: no special on host1, special on host2
  4775. if ( ( not $h1_special )
  4776. and ( $h1_special_guessed )
  4777. and ( exists $mysync->{h2_special}{$h1_special_guessed} )
  4778. ) {
  4779. $h2_fold = $mysync->{h2_special}{$h1_special_guessed} ;
  4780. $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ;
  4781. next ;
  4782. }
  4783. # Case 4: no special on both sides.
  4784. if ( ( not $h1_special )
  4785. and ( $h1_special_guessed )
  4786. and ( not exists $mysync->{h2_special}{$h1_special_guessed} )
  4787. and ( exists $mysync->{h2_special_guessed}{$h1_special_guessed} )
  4788. ) {
  4789. $h2_fold = $mysync->{h2_special_guessed}{$h1_special_guessed} ;
  4790. $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ;
  4791. next ;
  4792. }
  4793. }
  4794. return( $mysync->{f1f2auto} ) ;
  4795. }
  4796. # I will not add what there is at:
  4797. # http://stackoverflow.com/questions/2185391/localized-gmail-imap-folders/2185548#2185548
  4798. # because it works well without
  4799. sub build_possible_special
  4800. {
  4801. my $mysync = shift @ARG ;
  4802. my $possible_special = { } ;
  4803. # All|Archive|Drafts|Flagged|Junk|Sent|Trash
  4804. $possible_special->{'\All'} = [ 'All', 'All Messages', '&BBIEQQQ1-' ] ;
  4805. $possible_special->{'\Archive'} = [ 'Archive', 'Archives', '&BBAEQARFBDgEMg-' ] ;
  4806. $possible_special->{'\Drafts'} = [ 'Drafts', 'DRAFTS', '&BCcENQRABD0EPgQyBDgEOgQ4-', 'Szkice', 'Wersje robocze' ] ;
  4807. $possible_special->{'\Flagged'} = [ 'Flagged', 'Starred', '&BB8EPgQ8BDUERwQ1BD0EPQRLBDU-' ] ;
  4808. $possible_special->{'\Junk'} = [ 'Junk', 'junk', 'Spam', 'SPAM', '&BCEEPwQwBDw-',
  4809. 'Potwierdzony spam', 'Wiadomo&AVs-ci-&AVs-mieci',
  4810. 'Junk E-Mail', 'Junk Email'] ;
  4811. $possible_special->{'\Sent'} = [ 'Sent', 'Sent Messages', 'Sent Items',
  4812. 'Gesendete Elemente', 'Gesendete Objekte',
  4813. '&AMk-l&AOk-ments envoy&AOk-s', 'E&AwE-le&AwE-ments envoye&AwE-s', 'Envoy&AOk-', 'Objets envoy&AOk-s',
  4814. 'Elementos enviados', 'Posta inviata',
  4815. '&kAFP4W4IMH8wojCkMMYw4A-',
  4816. '&BB4EQgQ,BEAEMAQyBDsENQQ9BD0ESwQ1-',
  4817. 'Elementy wys&AUI-ane'] ;
  4818. $possible_special->{'\Trash'} = [ 'Trash', 'TRASH',
  4819. '&BCMENAQwBDsENQQ9BD0ESwQ1-', '&BBoEPgRABDcEOAQ9BDA-',
  4820. 'Kosz',
  4821. 'Deleted Items', 'Deleted Messages' ] ;
  4822. foreach my $special ( qw( \All \Archive \Drafts \Flagged \Junk \Sent \Trash ) ){
  4823. foreach my $possible_folder ( @{ $possible_special->{$special} } ) {
  4824. $possible_special->{ $possible_folder } = $special ;
  4825. } ;
  4826. }
  4827. $mysync->{possible_special} = $possible_special ;
  4828. $mysync->{ debug } and myprint( Data::Dumper->Dump( [ $possible_special ], [ 'possible_special' ] ) ) ;
  4829. return( $possible_special ) ;
  4830. }
  4831. sub tests_special_from_folders_hash
  4832. {
  4833. note( 'Entering tests_special_from_folders_hash()' ) ;
  4834. my $mysync = {} ;
  4835. require_ok( "Test::MockObject" ) ;
  4836. my $imapT = Test::MockObject->new( ) ;
  4837. is( undef, special_from_folders_hash( ), 'special_from_folders_hash: no args' ) ;
  4838. is( undef, special_from_folders_hash( $mysync ), 'special_from_folders_hash: undef args' ) ;
  4839. is_deeply( {}, special_from_folders_hash( $mysync, $imapT ), 'special_from_folders_hash: $imap void' ) ;
  4840. $imapT->mock( 'folders_hash', sub { return( [ { name => 'Sent', attrs => [ '\Sent' ] } ] ) } ) ;
  4841. is_deeply( { Sent => '\Sent', '\Sent' => 'Sent' },
  4842. special_from_folders_hash( $mysync, $imapT ), 'special_from_folders_hash: $imap \Sent' ) ;
  4843. note( 'Leaving tests_special_from_folders_hash()' ) ;
  4844. return( ) ;
  4845. }
  4846. sub special_from_folders_hash
  4847. {
  4848. my ( $mysync, $imap, $side ) = @_ ;
  4849. my %special = ( ) ;
  4850. if ( ! defined $imap ) { return ; }
  4851. $side = defined $side ? $side : 'Host?' ;
  4852. if ( ! $imap->can( 'folders_hash' ) ) {
  4853. my $error = "$side: To have automagic rfc6154 folder mapping, upgrade Mail::IMAPClient >= 3.34\n" ;
  4854. errors_incr( $mysync, $error ) ;
  4855. return( \%special ) ; # empty hash ref
  4856. }
  4857. my $folders_hash = $imap->folders_hash( ) ;
  4858. foreach my $fhash (@{ $folders_hash } ) {
  4859. my @special = grep { /\\(?:All|Archive|Drafts|Flagged|Junk|Sent|Trash)/x } @{ $fhash->{attrs} } ;
  4860. if ( @special ) {
  4861. my $special = $special[0] ; # keep first one. Could be not very good.
  4862. if ( exists $special{ $special } ) {
  4863. myprintf( "%s: special %-20s = %s already assigned to %s\n",
  4864. $side, $fhash->{name}, join( q{ }, @special ), $special{ $special } ) ;
  4865. }else{
  4866. myprintf( "%s: special %-20s = %s\n",
  4867. $side, $fhash->{name}, join( q{ }, @special ) ) ;
  4868. $special{ $special } = $fhash->{name} ;
  4869. $special{ $fhash->{name} } = $special ; # double entry value => key
  4870. }
  4871. }
  4872. }
  4873. myprint( "\n" ) if ( %special ) ;
  4874. return( \%special ) ;
  4875. }
  4876. sub tests_errors_log
  4877. {
  4878. note( 'Entering tests_errors_log()' ) ;
  4879. is( undef, errors_log( ), 'errors_log: no args => undef' ) ;
  4880. my $mysync = {} ;
  4881. is( undef, errors_log( $mysync ), 'errors_log: empty => undef' ) ;
  4882. is_deeply( [ 'aieaie' ], [ errors_log( $mysync, 'aieaie' ) ], 'errors_log: aieaie => aieaie' ) ;
  4883. # cumulative
  4884. is_deeply( [ 'aieaie' ], [ errors_log( $mysync ) ], 'errors_log: nothing more => aieaie' ) ;
  4885. is_deeply( [ 'aieaie', 'ouille' ], [ errors_log( $mysync, 'ouille' ) ], 'errors_log: ouille => aieaie ouille' ) ;
  4886. is_deeply( [ 'aieaie', 'ouille' ], [ errors_log( $mysync ) ], 'errors_log: nothing more => aieaie ouille' ) ;
  4887. note( 'Leaving tests_errors_log()' ) ;
  4888. return ;
  4889. }
  4890. sub errors_log
  4891. {
  4892. my ( $mysync, @error ) = @ARG ;
  4893. if ( ! $mysync->{errors_log} ) {
  4894. $mysync->{errors_log} = [] ;
  4895. }
  4896. if ( @error ) {
  4897. push @{ $mysync->{errors_log} }, join( q{}, @error ) ;
  4898. }
  4899. if ( @{ $mysync->{errors_log} } ) {
  4900. return @{ $mysync->{errors_log} } ;
  4901. }
  4902. else {
  4903. return ;
  4904. }
  4905. }
  4906. sub tests_comment_of_error_type
  4907. {
  4908. note( 'Entering tests_comment_of_error_type()' ) ;
  4909. is( undef, comment_of_error_type( ), 'comment_of_error_type: no args => undef' ) ;
  4910. my $mysync = { } ;
  4911. is( undef, comment_of_error_type( $mysync ), 'comment_of_error_type: undef => undef' ) ;
  4912. is( "", comment_of_error_type( $mysync, '' ), 'comment_of_error_type: "" => ""' ) ;
  4913. is( "", comment_of_error_type( $mysync, 'blabla' ), 'comment_of_error_type: blabla => ""' ) ;
  4914. is( "", comment_of_error_type( $mysync, 'ERR_UNCLASSIFIED' ), 'comment_of_error_type: ERR_UNCLASSIFIED => ""' ) ;
  4915. like( comment_of_error_type( $mysync, 'ERR_OVERQUOTA' ), qr{100% full}, 'comment_of_error_type: ERR_OVERQUOTA => matches 100% full' ) ;
  4916. note( 'Leaving tests_comment_of_error_type()' ) ;
  4917. return ;
  4918. }
  4919. sub comment_of_error_type
  4920. {
  4921. my $mysync = shift @ARG ;
  4922. my $error_type = shift @ARG ;
  4923. if ( ! defined $mysync ) { return ; }
  4924. if ( ! defined $error_type ) { return ; }
  4925. my $comment ;
  4926. if ( exists( $COMMENT_OF_ERR_TYPE{ $error_type } ) )
  4927. {
  4928. $comment = $COMMENT_OF_ERR_TYPE{ $error_type }->( $mysync ) ;
  4929. }
  4930. else
  4931. {
  4932. $comment = "" ;
  4933. }
  4934. return $comment ;
  4935. }
  4936. sub tests_error_type
  4937. {
  4938. note( 'Entering tests_error_type()' ) ;
  4939. is( 'ERR_NOTHING_REPORTED', error_type( ), 'error_type: no args => ERR_NOTHING_REPORTED' ) ;
  4940. is( 'ERR_NOTHING_REPORTED', error_type( '' ), 'error_type: empty string => ERR_NOTHING_REPORTED' ) ;
  4941. is( 'ERR_UNCLASSIFIED', error_type( 'ERR_UNCLASSIFIED' ), 'error_type: ERR_UNCLASSIFIED => ERR_UNCLASSIFIED' ) ;
  4942. is( 'ERR_UNCLASSIFIED', error_type( 'aie' ), 'error_type: aie => ERR_UNCLASSIFIED' ) ;
  4943. is( 'ERR_UNCLASSIFIED', error_type( 'ouille' ), 'error_type: ouille => ERR_UNCLASSIFIED' ) ;
  4944. is( 'ERR_Host1_FETCH', error_type( 'Message xxx could not be fetched: blabla' ),
  4945. 'error_type: could not be fetched => ERR_Host1_FETCH'
  4946. ) ;
  4947. is( 'ERR_APPEND_SIZE',
  4948. error_type( 'could not append message xxx: BAD maximum message size exceeded' ),
  4949. 'error_type: could not append message xxx: BAD maximum message size exceeded => ERR_APPEND_SIZE'
  4950. ) ;
  4951. is( 'ERR_OVERQUOTA',
  4952. error_type( 'Quota limit will be exceeded' ),
  4953. 'error_type: Quota limit will be exceeded => ERR_OVERQUOTA'
  4954. ) ;
  4955. is( 'ERR_APPEND', error_type( 'could not append' ), 'error_type: could not append => ERR_APPEND' ) ;
  4956. is( 'ERR_CREATE',
  4957. error_type( 'Could not create folder' ),
  4958. 'error_type: Could not create folder => ERR_CREATE'
  4959. ) ;
  4960. is( 'ERR_SELECT',
  4961. error_type( 'Could not select: blabla' ),
  4962. 'error_type: Could not select: blabla => ERR_SELECT'
  4963. ) ;
  4964. #
  4965. #Maximum bytes transferred reached, 423 >= 100, ending sync
  4966. is( 'ERR_TRANSFER_EXCEEDED',
  4967. error_type( 'Maximum bytes transferred reached, blabla' ),
  4968. 'error_type: Maximum bytes transferred reached, blabla => ERR_TRANSFER_EXCEEDED'
  4969. ) ;
  4970. #
  4971. is( 'ERR_CONNECTION_FAILURE_HOST1',
  4972. error_type( 'Host1 failure: can not open imap connection on host1 [badhostkaka] with user [tata]: Unable to connect to badhostkaka: Invalid argument' ),
  4973. 'error_type: can not open imap connection on host1 => ERR_CONNECTION_FAILURE_HOST1'
  4974. ) ;
  4975. is( 'ERR_CONNECTION_FAILURE_HOST2',
  4976. error_type( 'Host2 failure: can not open imap connection on host2 [badhostkiki] with user [titi]: Unable to connect to badhostkiki: Invalid argument' ),
  4977. 'error_type: can not open imap connection on host2 => ERR_CONNECTION_FAILURE_HOST2'
  4978. ) ;
  4979. is( 'ERR_APPEND_VIRUS',
  4980. 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' ),
  4981. 'error_type: could not append ... virus => ERR_APPEND_VIRUS'
  4982. ) ;
  4983. is( 'ERR_FLAGS',
  4984. error_type( 'Host2: flags msg INBOX/957910 could not add flags [PasGlop \PasGlopRe]: 33 NO Error in IMAP command received by server.' ),
  4985. 'error_type: could not add flags => ERR_FLAGS'
  4986. ) ;
  4987. note( 'Leaving tests_error_type()' ) ;
  4988. return ;
  4989. }
  4990. # Could be implemented with https://metacpan.org/pod/Tie::RegexpHash
  4991. # with just a hash of error regexes as keys and types as values.
  4992. sub error_type
  4993. {
  4994. my $error = shift @ARG ;
  4995. if ( ! defined $error ) { return 'ERR_NOTHING_REPORTED' ; }
  4996. if ( ! $error ) { return 'ERR_NOTHING_REPORTED' ; }
  4997. #
  4998. if ( $error =~ m{Host1 failure: Error login on} ) { return 'ERR_AUTHENTICATION_FAILURE_USER1' } ;
  4999. if ( $error =~ m{Host2 failure: Error login on} ) { return 'ERR_AUTHENTICATION_FAILURE_USER2' } ;
  5000. if ( $error =~ m{Host. failure: Can not go to tls encryption on host.} ) { return 'ERR_EXIT_TLS_FAILURE' } ;
  5001. #
  5002. if ( $error =~ m{could not be fetched:} ) { return 'ERR_Host1_FETCH' } ;
  5003. # could not append .*BAD maximum message size exceeded
  5004. # could not append.*Maximum size of appendable message has been exceeded
  5005. if ( $error =~ m{could not append .*BAD maximum message size exceeded} )
  5006. { return 'ERR_APPEND_SIZE' ; } ;
  5007. if ( $error =~ m{could not append.*Maximum size of appendable message has been exceeded} )
  5008. { return 'ERR_APPEND_SIZE' ; } ;
  5009. # Could not create folder *[OVERQUOTA] Not enough disk quota
  5010. # could not append .*[OVERQUOTA] Not enough disk quota
  5011. # could not append .*[OVERQUOTA] Mailbox is full / Blocks limit exceeded / Inode limit exceeded
  5012. if ( $error =~ m{OVERQUOTA} ) { return 'ERR_OVERQUOTA' ; } ;
  5013. if ( $error =~ m{Quota limit will be exceeded} ) { return 'ERR_OVERQUOTA' ; } ;
  5014. if ( $error =~ m{full: it is time to find a bigger place} ) { return 'ERR_OVERQUOTA' ; } ;
  5015. # could not append ... to folder INBOX: 276 NO Message refused because it contains a virus
  5016. if ( $error =~ m{could not append.*virus} )
  5017. { return 'ERR_APPEND_VIRUS' ; } ;
  5018. # could not append .*Write failed 'Broken pipe'
  5019. # could not append .*timeout waiting .* for data from server
  5020. # could not append .*BAD Invalid Arguments: Unable to parse message
  5021. # could not append .*BAD Command Argument Error. 11
  5022. # could not append .*NO header limit reached
  5023. if ( $error =~ m{could not append} ) { return 'ERR_APPEND' ; } ;
  5024. # could not add flags
  5025. if ( $error =~ m{could not add flags} ) { return 'ERR_FLAGS' ; } ;
  5026. # Could not create folder .*Invalid mailbox name
  5027. if ( $error =~ m{Could not create folder} ) { return 'ERR_CREATE' ; } ;
  5028. # Could not select:.*NO [NOPERM] Permission denied
  5029. # Could not select:.*NO Mailbox doesn't exist
  5030. # Could not select:.*NO [SERVERBUG] Internal error occurred.
  5031. # Could not select:.*[CANNOT] Mailbox isn't a valid mbox file
  5032. if ( $error =~ m{Could not select:} ) { return 'ERR_SELECT' ; } ;
  5033. #Maximum bytes transferred reached, 423 >= 100, ending sync
  5034. if ( $error =~ m{Maximum bytes transferred reached} ) { return 'ERR_TRANSFER_EXCEEDED' ; } ;
  5035. if ( $error =~ m{can not open imap connection on host1} ) { return 'ERR_CONNECTION_FAILURE_HOST1' ; } ;
  5036. if ( $error =~ m{can not open imap connection on host2} ) { return 'ERR_CONNECTION_FAILURE_HOST2' ; } ;
  5037. # Default is ERR_UNCLASSIFIED
  5038. return 'ERR_UNCLASSIFIED' ;
  5039. }
  5040. sub tests_errorclassify
  5041. {
  5042. note( 'Entering tests_errorclassify()' ) ;
  5043. is( undef, errorclassify( ), 'errorclassify: no args => undef' ) ;
  5044. is_deeply( { 'ERR_UNCLASSIFIED' => 1 }, errorclassify( 'aie' ), 'errorclassify: aie => { ERR_UNCLASSIFIED => 1 }' ) ;
  5045. is_deeply( { 'ERR_UNCLASSIFIED' => 2 }, errorclassify( 'aie', 'ouille' ), 'errorclassify: aie ouille => { ERR_UNCLASSIFIED => 2 }' ) ;
  5046. is_deeply( { 'ERR_UNCLASSIFIED' => 2, 'ERR_NOTHING_REPORTED' => 1 }, errorclassify( 'aie', 'ouille', '' ), 'errorclassify: aie ouille "" => { ERR_UNCLASSIFIED => 2 }' ) ;
  5047. is_deeply( { 'ERR_UNCLASSIFIED' => 3 }, errorclassify( 'aie', 'ouille', 'aie' ), 'errorclassify: aie ouille aie => { ERR_UNCLASSIFIED => 3 }' ) ;
  5048. is_deeply( { 'ERR_UNCLASSIFIED' => 1, 'ERR_OVERQUOTA' => 2 }, errorclassify( 'aie', 'OVERQUOTA pipi', 'OVERQUOTA caca' ), 'errorclassify: aie OVERQUOTA OVERQUOTA' ) ;
  5049. is_deeply( { 'ERR_NOTHING_REPORTED' => 1 }, errorclassify( '' ), 'errorclassify: "" => { ERR_NOTHING_REPORTED => 1 }' ) ;
  5050. is_deeply( { 'ERR_NOTHING_REPORTED' => 2 }, errorclassify( '', '' ), 'errorclassify: "", "" => { ERR_NOTHING_REPORTED => 1 }' ) ;
  5051. note( 'Leaving tests_errorclassify()' ) ;
  5052. return ;
  5053. }
  5054. sub errorclassify
  5055. {
  5056. my @errors = @ARG ;
  5057. if ( ! @errors ) { return ; } ;
  5058. my $error_type_count = { } ;
  5059. foreach my $error ( @errors )
  5060. {
  5061. my $error_type = error_type( $error ) ;
  5062. $error_type_count->{ $error_type }++ ;
  5063. }
  5064. return $error_type_count ;
  5065. }
  5066. sub tests_most_common_error
  5067. {
  5068. note( 'Entering tests_most_common_error()' ) ;
  5069. is( 'ERR_NOTHING_REPORTED', most_common_error( ), 'most_common_error: no args => ERR_NOTHING_REPORTED' ) ;
  5070. is( 'ERR_NOTHING_REPORTED', most_common_error( {} ), 'most_common_error: empty hash ref => ERR_NOTHING_REPORTED' ) ;
  5071. is( 'ERR_NOTHING_REPORTED', most_common_error( 'blabla' ), 'most_common_error: not a hash ref => ERR_NOTHING_REPORTED' ) ;
  5072. is( 'ERR_FOO', most_common_error( { ERR_FOO => 1 } ), 'most_common_error: { ERR_FOO => 1 } => ERR_FOO' ) ;
  5073. is( 'ERR_BAR', most_common_error( { ERR_FOO => 1, ERR_BAR => 2 } ), 'most_common_error: { ERR_FOO => 1, ERR_BAR => 2 } => ERR_BAR' ) ;
  5074. is( 'ERR_FOO', most_common_error( { ERR_FOO => 2, ERR_BAR => 1 } ), 'most_common_error: { ERR_FOO => 2, ERR_BAR => 1 } => ERR_FOO' ) ;
  5075. # exaequo => first lexical wins. ERR_BAR <= ERR_FOO
  5076. is( 'ERR_BAR', most_common_error( { ERR_FOO => 2, ERR_BAR => 2 } ), 'most_common_error: { ERR_FOO => 2, ERR_BAR => 2 } => ERR_BAR' ) ;
  5077. is( 'A', most_common_error( { A => 5, B => 5, C => 5 } ), 'most_common_error: { A => 5, B => 5, C => 5 } => A' ) ;
  5078. is( 'B', most_common_error( { A => 5, B => 6, C => 6 } ), 'most_common_error: { A => 5, B => 6, C => 6 } => B' ) ;
  5079. is( 'C', most_common_error( { A => 5, B => 5, C => 7 } ), 'most_common_error: { A => 5, B => 5, C => 7 } => C' ) ;
  5080. is( 'C', most_common_error( { A => 5, B => 6, C => 7 } ), 'most_common_error: { A => 5, B => 5, C => 7 } => C' ) ;
  5081. note( 'Leaving tests_most_common_error()' ) ;
  5082. return ;
  5083. }
  5084. sub most_common_error
  5085. {
  5086. my $errors_counted_ref = shift @ARG ;
  5087. if ( ! defined $errors_counted_ref ) { return 'ERR_NOTHING_REPORTED' ; }
  5088. if ( 'HASH' ne ref $errors_counted_ref ) { return 'ERR_NOTHING_REPORTED' ; }
  5089. # empty hash
  5090. if ( !%{ $errors_counted_ref } ) { return 'ERR_NOTHING_REPORTED' ; }
  5091. # non empty hash
  5092. # in case of equality the winner error is the first in alphabetic order
  5093. my $most_common_error = ( sort
  5094. {
  5095. $errors_counted_ref->{$b} <=> $errors_counted_ref->{$a}
  5096. || $a cmp $b
  5097. } keys %{$errors_counted_ref} )[0] ;
  5098. return $most_common_error ;
  5099. }
  5100. sub tests_errorsanalyse
  5101. {
  5102. note( 'Entering tests_errorsanalyse()' ) ;
  5103. is( 'ERR_NOTHING_REPORTED', errorsanalyse( ), 'errorsanalyse: no args => ERR_NOTHING_REPORTED' ) ;
  5104. is( 'ERR_NOTHING_REPORTED', errorsanalyse( ( ) ), 'errorsanalyse: empty list => ERR_NOTHING_REPORTED' ) ;
  5105. is( 'ERR_UNCLASSIFIED', errorsanalyse( 'aie' ), 'errorsanalyse: aie => ERR_UNCLASSIFIED' ) ;
  5106. # in case of equality, empty wins
  5107. is( 'ERR_NOTHING_REPORTED', errorsanalyse( 'aie', '' ), 'errorsanalyse: aie => ERR_UNCLASSIFIED' ) ;
  5108. is( 'ERR_NOTHING_REPORTED', errorsanalyse( '', 'aie' ), 'errorsanalyse: aie => ERR_UNCLASSIFIED' ) ;
  5109. is( 'ERR_UNCLASSIFIED', errorsanalyse( 'aie', 'ouille' ), 'errorsanalyse: aie, ouille => ERR_UNCLASSIFIED' ) ;
  5110. is( 'ERR_UNCLASSIFIED', errorsanalyse( 'aie', 'ouille', '' ), 'errorsanalyse: aie, ouille, "" => ERR_UNCLASSIFIED' ) ;
  5111. is( 'ERR_UNCLASSIFIED', errorsanalyse( '', 'aie', 'ouille' ), 'errorsanalyse: aie, ouille, "" => ERR_UNCLASSIFIED' ) ;
  5112. is( 'ERR_NOTHING_REPORTED', errorsanalyse( '' ), 'errorsanalyse: "" => ERR_NOTHING_REPORTED' ) ;
  5113. is( 'ERR_NOTHING_REPORTED', errorsanalyse( ( '' ) ), 'errorsanalyse: ( "" ) => ERR_NOTHING_REPORTED' ) ;
  5114. is( 'ERR_NOTHING_REPORTED', errorsanalyse( ( '', '' ) ), 'errorsanalyse: ( "", "" ) => ERR_NOTHING_REPORTED' ) ;
  5115. note( 'Leaving tests_errorsanalyse()' ) ;
  5116. return ;
  5117. }
  5118. sub errorsanalyse
  5119. {
  5120. my @errors = @ARG ;
  5121. my $errors_types_counted = errorclassify( @errors ) ;
  5122. my $most_common_error = most_common_error( $errors_types_counted ) ;
  5123. return $most_common_error ;
  5124. }
  5125. sub tests_errorsdump
  5126. {
  5127. note( 'Entering tests_errorsdump()' ) ;
  5128. is( undef, errorsdump( ), 'errorsdump: no args => undef' ) ;
  5129. is( undef, errorsdump( ( ) ), 'errorsdump: empty list => undef' ) ;
  5130. is( "Err 1/1: ", errorsdump( '' ), 'errorsdump: one empty string => "Err 1/1: "' ) ;
  5131. is( "Err 1/1: aieaieaie", errorsdump( 'aieaieaie' ), 'errorsdump: aieaieaie => "Err 1/1: aieaieaie"' ) ;
  5132. is( "Err 1/2: Aie Err 2/2: Ouille", errorsdump( 'Aie ', 'Ouille' ), 'errorsdump: Aie Ouille => "Err 1/2: Aie Err 2/2: Ouille"' ) ;
  5133. note( 'Leaving tests_errorsdump()' ) ;
  5134. return ;
  5135. }
  5136. sub errorsdump
  5137. {
  5138. if ( ! @ARG ) { return ; }
  5139. my @errors_log = @ARG ;
  5140. my $nb_errors = @errors_log ;
  5141. my $error_num = 0 ;
  5142. my $errors_list = q{} ;
  5143. if ( @errors_log ) {
  5144. foreach my $error ( @errors_log )
  5145. {
  5146. $error_num++ ;
  5147. $errors_list .= "Err $error_num/$nb_errors: $error" ;
  5148. }
  5149. }
  5150. return( $errors_list ) ;
  5151. }
  5152. sub errors_listing
  5153. {
  5154. my $mysync = shift @ARG ;
  5155. $mysync->{ most_common_error } = errorsanalyse( errors_log( $mysync ) ) ;
  5156. my $errors_listing = '' ;
  5157. if ( $mysync->{ errorsdump } )
  5158. {
  5159. $errors_listing = join( '',
  5160. "++++ Listing $mysync->{nb_errors} errors encountered during the sync ( avoid this listing with --noerrorsdump ).\n",
  5161. errorsdump( errors_log( $mysync ) ),
  5162. ) ;
  5163. }
  5164. $errors_listing .= join( '',
  5165. "The most frequent error is $mysync->{ most_common_error }. ",
  5166. comment_of_error_type( $mysync, $mysync->{ most_common_error } ),
  5167. "\n",
  5168. ) ;
  5169. return $errors_listing ;
  5170. }
  5171. sub errors_incr
  5172. {
  5173. my ( $mysync, @error ) = @ARG ;
  5174. $mysync->{ nb_errors }++ ;
  5175. if ( @error ) {
  5176. errors_log( $mysync, @error ) ;
  5177. myprint( @error ) ;
  5178. }
  5179. $mysync->{ errorsmax } ||= $ERRORS_MAX ;
  5180. if ( $mysync->{ nb_errors } >= $mysync->{ errorsmax } )
  5181. {
  5182. myprint( errorsmax_msg( $mysync ) ) ;
  5183. myprint( errors_listing( $mysync ) ) ;
  5184. if ( $mysync->{ errorsdump } )
  5185. {
  5186. # again since errorsdump( ) can be very verbose and masquerade previous warning
  5187. myprint( errorsmax_msg( $mysync ) ) ;
  5188. }
  5189. my $exit_value = exit_value( $mysync, $mysync->{ most_common_error } ) ;
  5190. exit_clean( $mysync, $exit_value ) ;
  5191. }
  5192. return ;
  5193. }
  5194. sub errorsmax_msg
  5195. {
  5196. my $mysync = shift @ARG ;
  5197. my $msg = "Maximum number of errors $mysync->{errorsmax} reached "
  5198. . "( you can change $mysync->{errorsmax} to any value, for example 100 with --errorsmax 100 ). "
  5199. . "Exiting.\n" ;
  5200. return $msg ;
  5201. }
  5202. sub tests_live_result
  5203. {
  5204. note( 'Entering tests_live_result()' ) ;
  5205. my $nb_errors = shift @ARG ;
  5206. if ( $nb_errors ) {
  5207. myprint( "Live tests failed with $nb_errors errors\n" ) ;
  5208. } else {
  5209. myprint( "Live tests ended successfully\n" ) ;
  5210. }
  5211. note( 'Leaving tests_live_result()' ) ;
  5212. return ;
  5213. }
  5214. sub size_filtered_flag
  5215. {
  5216. my $mysync = shift @ARG ;
  5217. my $h1_size = shift @ARG ;
  5218. if ( defined $mysync->{ maxsize } and $h1_size >= $mysync->{ maxsize } ) {
  5219. return( 1 ) ;
  5220. }
  5221. if ( defined $minsize and $h1_size <= $minsize ) {
  5222. return( 1 ) ;
  5223. }
  5224. return( 0 ) ;
  5225. }
  5226. sub sync_flags_fir
  5227. {
  5228. my ( $mysync, $h1_fold, $h1_msg, $h2_fold, $h2_msg, $h1_fir_ref, $h2_fir_ref ) = @_ ;
  5229. if ( not defined $h1_msg ) { return } ;
  5230. if ( not defined $h2_msg ) { return } ;
  5231. my $h1_size = $h1_fir_ref->{$h1_msg}->{'RFC822.SIZE'} ;
  5232. return if size_filtered_flag( $mysync, $h1_size ) ;
  5233. # used cached flag values for efficiency
  5234. my $h1_flags = $h1_fir_ref->{ $h1_msg }->{ 'FLAGS' } || q{} ;
  5235. my $h2_flags = $h2_fir_ref->{ $h2_msg }->{ 'FLAGS' } || q{} ;
  5236. sync_flags( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags ) ;
  5237. return ;
  5238. }
  5239. sub sync_flags_after_copy
  5240. {
  5241. # Activated with option --syncflagsaftercopy
  5242. my( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg ) = @_ ;
  5243. if ( my @h2_flags = $mysync->{imap2}->flags( $h2_msg ) ) {
  5244. my $h2_flags = "@h2_flags" ;
  5245. ( $mysync->{ debug } or $mysync->{ debugflags } ) and myprint( "Host2: msg $h2_fold/$h2_msg flags before sync flags after copy ( $h2_flags )\n" ) ;
  5246. sync_flags( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags ) ;
  5247. }else{
  5248. myprint( "Host2: msg $h2_fold/$h2_msg could not get its flags for sync flags after copy\n" ) ;
  5249. }
  5250. return ;
  5251. }
  5252. sub sync_flags
  5253. {
  5254. my( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags ) = @_ ;
  5255. ( $mysync->{ debug } or $mysync->{ debugflags } ) and
  5256. myprint( "Host1: flags init msg $h1_fold/$h1_msg flags( $h1_flags ) Host2 msg $h2_fold/$h2_msg flags( $h2_flags )\n" ) ;
  5257. $h1_flags = flags_for_host2( $mysync, $h1_flags, $mysync->{ permanentflags2 } ) ;
  5258. $h2_flags = flagscase( $h2_flags ) ;
  5259. ( $mysync->{ debug } or $mysync->{ debugflags } ) and
  5260. myprint( "Host1: flags filt msg $h1_fold/$h1_msg flags( $h1_flags ) Host2 msg $h2_fold/$h2_msg flags( $h2_flags )\n" ) ;
  5261. # compare flags - set flags if there a difference
  5262. my @h1_flags = sort split(q{ }, $h1_flags );
  5263. my @h2_flags = sort split(q{ }, $h2_flags );
  5264. my $diff = compare_lists( \@h1_flags, \@h2_flags );
  5265. $diff and ( $mysync->{ debug } or $mysync->{ debugflags } )
  5266. and myprint( "Host2: flags msg $h2_fold/$h2_msg replacing h2 flags( $h2_flags ) with h1 flags( $h1_flags )\n" ) ;
  5267. # This sets flags exactly. So flags can be removed with this.
  5268. # When you remove a \Seen flag on host1 you want it
  5269. # to be removed on host2. Just add flags is not what
  5270. # we need most of the time, so no + like in "+FLAGS.SILENT".
  5271. if ( not $mysync->{ dry } and $diff and not $mysync->{ imap2 }->store( $h2_msg, "FLAGS.SILENT (@h1_flags)" ) ) {
  5272. my $error_msg = join q{}, "Host2: flags msg $h2_fold/$h2_msg could not add flags [@h1_flags]: ",
  5273. $mysync->{ imap2 }->LastError || q{}, "\n" ;
  5274. errors_incr( $mysync, $error_msg ) ;
  5275. }
  5276. return ;
  5277. }
  5278. sub _filter
  5279. {
  5280. my $mysync = shift @ARG ;
  5281. my $str = shift or return q{} ;
  5282. my $sz = $SIZE_MAX_STR ;
  5283. my $len = length $str ;
  5284. if ( not $mysync->{ debug } and $len > $sz*2 ) {
  5285. my $beg = substr $str, 0, $sz ;
  5286. my $end = substr $str, -$sz, $sz ;
  5287. $str = $beg . '...' . $end ;
  5288. }
  5289. $str =~ s/\012?\015$//x ;
  5290. return "(len=$len) " . $str ;
  5291. }
  5292. sub lost_connection
  5293. {
  5294. my( $mysync, $imap, $error_message ) = @_;
  5295. if ( $imap->IsUnconnected( ) ) {
  5296. $mysync->{nb_errors}++ ;
  5297. my $lcomm = $imap->LastIMAPCommand || q{} ;
  5298. my $einfo = imap_last_error( $imap ) ;
  5299. # if string is long try reduce to a more reasonable size
  5300. $lcomm = _filter( $mysync, $lcomm ) ;
  5301. $einfo = _filter( $mysync, $einfo ) ;
  5302. myprint( "Failure: last command: $lcomm\n") if ( $mysync->{ debug } && $lcomm) ;
  5303. myprint( "Failure: lost connection $error_message: ", $einfo, "\n") ;
  5304. return( 1 ) ;
  5305. }
  5306. else{
  5307. return( 0 ) ;
  5308. }
  5309. }
  5310. sub imap_last_error
  5311. {
  5312. my $imap = shift @ARG ;
  5313. my $einfo = $imap->LastError || @{$imap->History}[$LAST] || q{} ;
  5314. chomp( $einfo ) ;
  5315. return( $einfo ) ;
  5316. }
  5317. sub tests_max
  5318. {
  5319. note( 'Entering tests_max()' ) ;
  5320. is( 0, max( 0 ), 'max 0 => 0' ) ;
  5321. is( 1, max( 1 ), 'max 1 => 1' ) ;
  5322. is( $MINUS_ONE, max( $MINUS_ONE ), 'max -1 => -1') ;
  5323. is( undef, max( ), 'max no arg => undef' ) ;
  5324. is( undef, max( undef ), 'undef => undef' ) ;
  5325. is( undef, max( undef, undef ), 'undef, undef => undef' ) ;
  5326. is( $NUMBER_100, max( 1, $NUMBER_100 ), 'max 1 100 => 100' ) ;
  5327. is( $NUMBER_100, max( $NUMBER_100, 1 ), 'max 100 1 => 100' ) ;
  5328. is( $NUMBER_100, max( $NUMBER_100, $NUMBER_42, 1 ), 'max 100 42 1 => 100' ) ;
  5329. is( $NUMBER_100, max( $NUMBER_100, '42', 1 ), 'max 100 42 1 => 100' ) ;
  5330. is( $NUMBER_100, max( '100', '42', 1 ), 'max 100 42 1 => 100' ) ;
  5331. is( $NUMBER_100, max( $NUMBER_100, 'haha', 1 ), 'max 100 haha 1 => 100') ;
  5332. is( $NUMBER_100, max( 'bb', $NUMBER_100, 'haha' ), 'max bb 100 haha => 100') ;
  5333. is( $MINUS_ONE, max( q{}, $MINUS_ONE, 'haha' ), 'max "" -1 haha => -1') ;
  5334. is( $MINUS_ONE, max( q{}, $MINUS_ONE, $MINUS_TWO ), 'max "" -1 -2 => -1') ;
  5335. is( $MINUS_ONE, max( 'haha', $MINUS_ONE, $MINUS_TWO ), 'max haha -1 -2 => -1') ;
  5336. is( 1, max( $MINUS_ONE, 1 ), 'max -1 1 => 1') ;
  5337. is( 1, max( undef, 1 ), 'max undef 1 => 1' ) ;
  5338. is( 0, max( undef, 0 ), 'max undef 0 => 0' ) ;
  5339. is( 'haha', max( 'haha' ), 'max haha => haha') ;
  5340. is( 'bb', max( 'aa', 'bb' ), 'max aa bb => bb') ;
  5341. is( 'bb', max( 'bb', 'aa' ), 'max bb aa => bb') ;
  5342. is( 'bb', max( 'bb', 'aa', 'bb' ), 'max bb aa bb => bb') ;
  5343. note( 'Leaving tests_max()' ) ;
  5344. return ;
  5345. }
  5346. sub max
  5347. {
  5348. my @list = @_ ;
  5349. return( undef ) if ( 0 == scalar @list ) ;
  5350. my( @numbers, @notnumbers ) ;
  5351. foreach my $item ( @list )
  5352. {
  5353. if ( is_number( $item ) )
  5354. {
  5355. push @numbers, $item ;
  5356. }
  5357. elsif ( defined $item )
  5358. {
  5359. push @notnumbers, $item ;
  5360. }
  5361. }
  5362. my @sorted ;
  5363. if ( @numbers )
  5364. {
  5365. @sorted = sort { $a <=> $b } @numbers ;
  5366. }
  5367. elsif ( @notnumbers )
  5368. {
  5369. @sorted = sort { $a cmp $b } @notnumbers ;
  5370. }
  5371. else
  5372. {
  5373. return ;
  5374. }
  5375. return( pop @sorted ) ;
  5376. }
  5377. sub tests_is_number
  5378. {
  5379. note( 'Entering tests_is_number()' ) ;
  5380. is( undef, is_number( ), 'is_number: no args => undef ' ) ;
  5381. is( undef, is_number( undef ), 'is_number: undef => undef ' ) ;
  5382. ok( is_number( 1 ), 'is_number: 1 => 1' ) ;
  5383. ok( is_number( 1.1 ), 'is_number: 1.1 => 1' ) ;
  5384. ok( is_number( 0 ), 'is_number: 0 => 1' ) ;
  5385. ok( is_number( -1 ), 'is_number: -1 => 1' ) ;
  5386. ok( ! is_number( 1.1.1 ), 'is_number: 1.1.1 => no' ) ;
  5387. ok( ! is_number( q{} ), 'is_number: q{} => no' ) ;
  5388. ok( ! is_number( 'haha' ), 'is_number: haha => no' ) ;
  5389. ok( ! is_number( '0haha' ), 'is_number: 0haha => no' ) ;
  5390. ok( ! is_number( '2haha' ), 'is_number: 2haha => no' ) ;
  5391. ok( ! is_number( 'haha2' ), 'is_number: haha2 => no' ) ;
  5392. note( 'Leaving tests_is_number()' ) ;
  5393. return ;
  5394. }
  5395. sub is_number
  5396. {
  5397. my $item = shift @ARG ;
  5398. if ( ! defined $item ) { return ; }
  5399. if ( $item =~ /\A$RE{num}{real}\Z/ ) {
  5400. return 1 ;
  5401. }
  5402. return ;
  5403. }
  5404. sub tests_min
  5405. {
  5406. note( 'Entering tests_min()' ) ;
  5407. is( 0, min( 0 ), 'min 0 => 0' ) ;
  5408. is( 1, min( 1 ), 'min 1 => 1' ) ;
  5409. is( $MINUS_ONE, min( $MINUS_ONE ), 'min -1 => -1' ) ;
  5410. is( undef, min( ), 'min no arg => undef' ) ;
  5411. is( 1, min( 1, $NUMBER_100 ), 'min 1 100 => 1' ) ;
  5412. is( 1, min( $NUMBER_100, 1 ), 'min 100 1 => 1' ) ;
  5413. is( 1, min( $NUMBER_100, $NUMBER_42, 1 ), 'min 100 42 1 => 1' ) ;
  5414. is( 1, min( $NUMBER_100, '42', 1 ), 'min 100 42 1 => 1' ) ;
  5415. is( 1, min( '100', '42', 1 ), 'min 100 42 1 => 1' ) ;
  5416. is( 1, min( $NUMBER_100, 'haha', 1 ), 'min 100 haha 1 => 1') ;
  5417. is( $MINUS_ONE, min( $MINUS_ONE, 1 ), 'min -1 1 => -1') ;
  5418. is( 1, min( undef, 1 ), 'min undef 1 => 1' ) ;
  5419. is( 0, min( undef, 0 ), 'min undef 0 => 0' ) ;
  5420. is( 1, min( undef, 1 ), 'min undef 1 => 1' ) ;
  5421. is( 0, min( undef, 2, 0, 1 ), 'min undef, 2, 0, 1 => 0' ) ;
  5422. is( 'haha', min( 'haha' ), 'min haha => haha') ;
  5423. is( 'aa', min( 'aa', 'bb' ), 'min aa bb => aa') ;
  5424. is( 'aa', min( 'bb', 'aa' ), 'min bb aa bb => aa') ;
  5425. is( 'aa', min( 'bb', 'aa', 'bb' ), 'min bb aa bb => aa') ;
  5426. note( 'Leaving tests_min()' ) ;
  5427. return ;
  5428. }
  5429. sub min
  5430. {
  5431. my @list = @_ ;
  5432. return( undef ) if ( 0 == scalar @list ) ;
  5433. my( @numbers, @notnumbers ) ;
  5434. foreach my $item ( @list ) {
  5435. if ( is_number( $item ) ) {
  5436. push @numbers, $item ;
  5437. }else{
  5438. push @notnumbers, $item ;
  5439. }
  5440. }
  5441. my @sorted ;
  5442. if ( @numbers ) {
  5443. @sorted = sort { $a <=> $b } @numbers ;
  5444. }elsif( @notnumbers ) {
  5445. @sorted = sort { $a cmp $b } @notnumbers ;
  5446. }else{
  5447. return ;
  5448. }
  5449. return( shift @sorted ) ;
  5450. }
  5451. sub check_lib_version
  5452. {
  5453. my $mysync = shift @ARG ;
  5454. $mysync->{ debug } and myprint( "IMAPClient $Mail::IMAPClient::VERSION\n" ) ;
  5455. if ( '2.2.9' eq $Mail::IMAPClient::VERSION ) {
  5456. myprint( "imapsync no longer supports Mail::IMAPClient 2.2.9, upgrade it\n" ) ;
  5457. return 0 ;
  5458. }
  5459. else{
  5460. # 3.x.x is no longer buggy with imapsync.
  5461. # 3.30 or currently superior is imposed in the Perl "use Mail::IMAPClient line".
  5462. return 1 ;
  5463. }
  5464. return ;
  5465. }
  5466. sub module_version_str
  5467. {
  5468. my( $module_name, $module_version ) = @_ ;
  5469. my $str = mysprintf( "%-20s %s\n", $module_name, $module_version ) ;
  5470. return( $str ) ;
  5471. }
  5472. sub modulesversion
  5473. {
  5474. my @list_version;
  5475. my %modulesversion = (
  5476. 'Authen::NTLM' => sub { $Authen::NTLM::VERSION },
  5477. 'CGI' => sub { $CGI::VERSION },
  5478. 'Compress::Zlib' => sub { $Compress::Zlib::VERSION },
  5479. 'Crypt::OpenSSL::RSA' => sub { $Crypt::OpenSSL::RSA::VERSION },
  5480. 'Data::Uniqid' => sub { $Data::Uniqid::VERSION },
  5481. 'Digest::HMAC_MD5' => sub { $Digest::HMAC_MD5::VERSION },
  5482. 'Digest::HMAC_SHA1' => sub { $Digest::HMAC_SHA1::VERSION },
  5483. 'Digest::MD5' => sub { $Digest::MD5::VERSION },
  5484. 'Encode' => sub { $Encode::VERSION },
  5485. 'Encode::IMAPUTF7' => sub { $Encode::IMAPUTF7::VERSION },
  5486. 'File::Copy::Recursive' => sub { $File::Copy::Recursive::VERSION },
  5487. 'File::Spec' => sub { $File::Spec::VERSION },
  5488. 'Getopt::Long' => sub { $Getopt::Long::VERSION },
  5489. 'HTML::Entities' => sub { $HTML::Entities::VERSION },
  5490. 'IO::Socket' => sub { $IO::Socket::VERSION },
  5491. 'IO::Socket::INET' => sub { $IO::Socket::INET::VERSION },
  5492. 'IO::Socket::INET6' => sub { $IO::Socket::INET6::VERSION },
  5493. 'IO::Socket::IP' => sub { $IO::Socket::IP::VERSION },
  5494. 'IO::Socket::SSL' => sub { $IO::Socket::SSL::VERSION },
  5495. 'IO::Tee' => sub { $IO::Tee::VERSION },
  5496. 'JSON' => sub { $JSON::VERSION },
  5497. 'JSON::WebToken' => sub { $JSON::WebToken::VERSION },
  5498. 'LWP' => sub { $LWP::VERSION },
  5499. 'Mail::IMAPClient' => sub { $Mail::IMAPClient::VERSION },
  5500. 'MIME::Base64' => sub { $MIME::Base64::VERSION },
  5501. 'Net::Ping' => sub { $Net::Ping::VERSION },
  5502. 'Net::SSLeay' => sub { $Net::SSLeay::VERSION },
  5503. 'Term::ReadKey' => sub { $Term::ReadKey::VERSION },
  5504. 'Test::MockObject' => sub { $Test::MockObject::VERSION },
  5505. 'Time::HiRes' => sub { $Time::HiRes::VERSION },
  5506. 'Unicode::String' => sub { $Unicode::String::VERSION },
  5507. 'URI::Escape' => sub { $URI::Escape::VERSION },
  5508. #'Lalala' => sub { $Lalala::VERSION },
  5509. ) ;
  5510. foreach my $module_name ( sort keys %modulesversion ) {
  5511. # trick from http://www.perlmonks.org/?node_id=152122
  5512. my $file_name = $module_name . '.pm' ;
  5513. $file_name =~s,::,/,xmgs; # Foo::Bar::Baz => Foo/Bar/Baz.pm
  5514. my $v ;
  5515. eval {
  5516. require $file_name ;
  5517. $v = defined $modulesversion{ $module_name } ? $modulesversion{ $module_name }->() : q{?} ;
  5518. } or $v = q{Not installed} ;
  5519. push @list_version, module_version_str( $module_name, $v ) ;
  5520. }
  5521. return( @list_version ) ;
  5522. }
  5523. sub tests_command_line_nopassword
  5524. {
  5525. note( 'Entering tests_command_line_nopassword()' ) ;
  5526. ok( q{} eq command_line_nopassword(), 'command_line_nopassword void' );
  5527. my $mysync = {} ;
  5528. ok( '--blabla' eq command_line_nopassword( $mysync, '--blabla' ), 'command_line_nopassword --blabla' );
  5529. #myprint( command_line_nopassword((qw{ --password1 secret1 })), "\n" ) ;
  5530. ok( '--password1 MASKED' eq command_line_nopassword( $mysync, qw{ --password1 secret1}), 'command_line_nopassword --password1' );
  5531. ok( '--blabla --password1 MASKED --blibli'
  5532. eq command_line_nopassword( $mysync, qw{ --blabla --password1 secret1 --blibli } ), 'command_line_nopassword --password1 --blibli' );
  5533. $mysync->{showpasswords} = 1 ;
  5534. ok( q{} eq command_line_nopassword(), 'command_line_nopassword void' );
  5535. ok( '--blabla' eq command_line_nopassword( $mysync, '--blabla'), 'command_line_nopassword --blabla' );
  5536. #myprint( command_line_nopassword((qw{ --password1 secret1 })), "\n" ) ;
  5537. ok( '--password1 secret1' eq command_line_nopassword( $mysync, qw{ --password1 secret1} ), 'command_line_nopassword --password1' );
  5538. ok( '--blabla --password1 secret1 --blibli'
  5539. eq command_line_nopassword( $mysync, qw{ --blabla --password1 secret1 --blibli } ), 'command_line_nopassword --password1 --blibli' );
  5540. note( 'Leaving tests_command_line_nopassword()' ) ;
  5541. return ;
  5542. }
  5543. # Construct a command line copy with passwords replaced by MASKED.
  5544. sub command_line_nopassword
  5545. {
  5546. my $mysync = shift @ARG ;
  5547. my @argv = @ARG ;
  5548. my @argv_nopassword ;
  5549. if ( $mysync->{ cmdcgi } ) {
  5550. @argv_nopassword = mask_password_value( @{ $mysync->{ cmdcgi } } ) ;
  5551. return( "@argv_nopassword" ) ;
  5552. }
  5553. if ( $mysync->{showpasswords} )
  5554. {
  5555. return( "@argv" ) ;
  5556. }
  5557. @argv_nopassword = mask_password_value( @argv ) ;
  5558. return("@argv_nopassword") ;
  5559. }
  5560. sub mask_password_value
  5561. {
  5562. my @argv = @ARG ;
  5563. my @argv_nopassword ;
  5564. while ( @argv ) {
  5565. my $arg = shift @argv ; # option name or value
  5566. if ( $arg =~ m/-password[12]/x ) {
  5567. shift @argv ; # password value
  5568. push @argv_nopassword, $arg, 'MASKED' ; # option name and fake value
  5569. }else{
  5570. push @argv_nopassword, $arg ; # same option or value
  5571. }
  5572. }
  5573. return @argv_nopassword ;
  5574. }
  5575. sub tests_get_stdin_masked
  5576. {
  5577. note( 'Entering tests_get_stdin_masked()' ) ;
  5578. is( q{}, get_stdin_masked( ), 'get_stdin_masked: no args' ) ;
  5579. is( q{}, get_stdin_masked( 'Please ENTER: ' ), 'get_stdin_masked: ENTER' ) ;
  5580. note( 'Leaving tests_get_stdin_masked()' ) ;
  5581. return ;
  5582. }
  5583. #######################################################
  5584. # The issue is that prompt() does not prompt the prompt
  5585. # when the program is used like
  5586. # { sleep 2 ; echo blablabla ; } | ./imapsync ...--host1 lo --user1 tata --host2 lo --user2 titi
  5587. # use IO::Prompter ;
  5588. sub get_stdin_masked
  5589. {
  5590. my $prompt = shift || 'Say something: ' ;
  5591. local @ARGV = () ;
  5592. my $input = prompt(
  5593. -prompt => $prompt,
  5594. -echo => '*',
  5595. ) ;
  5596. #myprint( "You said: $input\n" ) ;
  5597. return $input ;
  5598. }
  5599. sub ask_for_password_new
  5600. {
  5601. my $prompt = shift @ARG ;
  5602. my $password = get_stdin_masked( $prompt ) ;
  5603. return $password ;
  5604. }
  5605. #########################################################
  5606. sub ask_for_password
  5607. {
  5608. my $prompt = shift @ARG ;
  5609. myprint( $prompt ) ;
  5610. Term::ReadKey::ReadMode( 2 ) ;
  5611. ## no critic (InputOutput::ProhibitExplicitStdin)
  5612. my $password = <STDIN> ;
  5613. chomp $password ;
  5614. myprint( "\nGot it\n" ) ;
  5615. Term::ReadKey::ReadMode( 0 ) ;
  5616. return $password ;
  5617. }
  5618. # Have to refactor get_password1() get_password2()
  5619. # to have only get_password() and two calls
  5620. sub get_password1
  5621. {
  5622. my $mysync = shift @ARG ;
  5623. $mysync->{ password1 }
  5624. || $mysync->{ passfile1 }
  5625. || 'PREAUTH' eq $mysync->{ acc1 }->{ authmech }
  5626. || 'EXTERNAL' eq $mysync->{ acc1 }->{ authmech }
  5627. || $ENV{IMAPSYNC_PASSWORD1}
  5628. || do
  5629. {
  5630. myprint( << 'FIN_PASSFILE' ) ;
  5631. If you are afraid of giving password on the command line arguments, you can put the
  5632. password of user1 in a file named file1 and use "--passfile1 file1" instead of typing it.
  5633. Then give this file restrictive permissions with the command "chmod 600 file1".
  5634. An other solution is to set the environment variable IMAPSYNC_PASSWORD1
  5635. FIN_PASSFILE
  5636. my $user = $mysync->{ acc1 }->{ authuser } || $mysync->{ user1 } ;
  5637. my $host = $mysync->{ host1 } ;
  5638. my $prompt = "What's the password for $user" . ' at ' . "$host? (not visible while you type, then enter RETURN) " ;
  5639. $mysync->{password1} = ask_for_password( $prompt ) ;
  5640. } ;
  5641. if ( defined $mysync->{ passfile1 } ) {
  5642. if ( ! -e -r $mysync->{ passfile1 } ) {
  5643. myprint( "Failure: file from parameter --passfile1 $mysync->{ passfile1 } does not exist or is not readable\n" ) ;
  5644. $mysync->{nb_errors}++ ;
  5645. exit_clean( $mysync, $EX_NOINPUT ) ;
  5646. }
  5647. # passfile1 readable
  5648. $mysync->{password1} = firstline ( $mysync->{ passfile1 } ) ;
  5649. return ;
  5650. }
  5651. if ( $ENV{IMAPSYNC_PASSWORD1} ) {
  5652. $mysync->{password1} = $ENV{IMAPSYNC_PASSWORD1} ;
  5653. return ;
  5654. }
  5655. return ;
  5656. }
  5657. sub get_password2
  5658. {
  5659. my $mysync = shift @ARG ;
  5660. $mysync->{password2}
  5661. || $mysync->{ passfile2 }
  5662. || 'PREAUTH' eq $mysync->{ acc2 }->{ authmech }
  5663. || 'EXTERNAL' eq $mysync->{ acc2 }->{ authmech }
  5664. || $ENV{IMAPSYNC_PASSWORD2}
  5665. || do
  5666. {
  5667. myprint( << 'FIN_PASSFILE' ) ;
  5668. If you are afraid of giving password on the command line arguments, you can put the
  5669. password of user2 in a file named file2 and use "--passfile2 file2" instead of typing it.
  5670. Then give this file restrictive permissions with the command "chmod 600 file2".
  5671. An other solution is to set the environment variable IMAPSYNC_PASSWORD2
  5672. FIN_PASSFILE
  5673. my $user = $mysync->{ acc2 }->{ authuser } || $mysync->{ user2 } ;
  5674. my $host = $mysync->{ host2 } ;
  5675. my $prompt = "What's the password for $user" . ' at ' . "$host? (not visible while you type, then enter RETURN) " ;
  5676. $mysync->{password2} = ask_for_password( $prompt ) ;
  5677. } ;
  5678. if ( defined $mysync->{ passfile2 } ) {
  5679. if ( ! -e -r $mysync->{ passfile2 } ) {
  5680. myprint( "Failure: file from parameter --passfile2 $mysync->{ passfile2 } does not exist or is not readable\n" ) ;
  5681. $mysync->{nb_errors}++ ;
  5682. exit_clean( $mysync, $EX_NOINPUT ) ;
  5683. }
  5684. # passfile2 readable
  5685. $mysync->{password2} = firstline ( $mysync->{ passfile2 } ) ;
  5686. return ;
  5687. }
  5688. if ( $ENV{IMAPSYNC_PASSWORD2} ) {
  5689. $mysync->{password2} = $ENV{IMAPSYNC_PASSWORD2} ;
  5690. return ;
  5691. }
  5692. return ;
  5693. }
  5694. sub remove_tmp_files
  5695. {
  5696. my $mysync = shift or return ;
  5697. $mysync->{pidfile} or return ;
  5698. if ( -e $mysync->{pidfile} ) {
  5699. myprint( "Removing pidfile $mysync->{pidfile}\n" ) ;
  5700. unlink $mysync->{pidfile} ;
  5701. }
  5702. if ( -e $mysync->{abortfile} ) {
  5703. myprint( "Removing pidfile $mysync->{abortfile}\n" ) ;
  5704. unlink $mysync->{abortfile} ;
  5705. }
  5706. return ;
  5707. }
  5708. sub cleanup_before_exit
  5709. {
  5710. my $mysync = shift @ARG ;
  5711. remove_tmp_files( $mysync ) ;
  5712. if ( $mysync->{ imap1 } and $mysync->{ imap1 }->IsConnected() )
  5713. {
  5714. myprint( "Disconnecting from host1 $mysync->{ host1 } user1 $mysync->{ user1 }\n" ) ;
  5715. $mysync->{ imap1 }->logout( ) ;
  5716. }
  5717. if ( $mysync->{ imap2 } and $mysync->{ imap2 }->IsConnected() )
  5718. {
  5719. myprint( "Disconnecting from host2 $mysync->{ host2 } user2 $mysync->{ user2 }\n" ) ;
  5720. $mysync->{ imap2 }->logout( ) ;
  5721. }
  5722. if ( $mysync->{ log } ) {
  5723. myprint( "Log file is $mysync->{logfile} ( to change it, use --logfile filepath ; or use --nolog to turn off logging )\n" ) ;
  5724. }
  5725. else
  5726. {
  5727. myprint( "No log file because of option --nolog\n" ) ;
  5728. }
  5729. if ( $mysync->{ log } ) {
  5730. #print( "Closing $mysync->{ logfile }\n" ) ;
  5731. teefinish( $mysync->{ tee } ) ;
  5732. }
  5733. $IO::Socket::SSL::DEBUG = 1 ;
  5734. return ;
  5735. }
  5736. sub tests_exit_value
  5737. {
  5738. note( 'Entering tests_exit_value()' ) ;
  5739. is( $EXIT_CATCH_ALL, exit_value( ), 'exit_value: no args => EXIT_CATCH_ALL' ) ;
  5740. my $mysync = { } ;
  5741. is( $EXIT_CATCH_ALL, exit_value( $mysync ), 'exit_value: undef => EXIT_CATCH_ALL' ) ;
  5742. is( $EXIT_CATCH_ALL, exit_value( $mysync, 'Blabla_unknown' ), 'exit_value: Blabla => EXIT_CATCH_ALL' ) ;
  5743. is( $EXIT_CATCH_ALL, exit_value( $mysync, '' ), 'exit_value: empty => EXIT_CATCH_ALL' ) ;
  5744. is( $EXIT_OVERQUOTA, exit_value( $mysync, 'ERR_OVERQUOTA' ), 'exit_value: ERR_OVERQUOTA => EXIT_OVERQUOTA' ) ;
  5745. is( $EXIT_TRANSFER_EXCEEDED, exit_value( $mysync, 'ERR_TRANSFER_EXCEEDED' ), 'exit_value: ERR_TRANSFER_EXCEEDED => EXIT_TRANSFER_EXCEEDED' ) ;
  5746. note( 'Leaving tests_exit_value()' ) ;
  5747. return ;
  5748. }
  5749. sub exit_value
  5750. {
  5751. my $mysync = shift @ARG ;
  5752. my $most_common_error = shift @ARG ;
  5753. if ( ! defined $most_common_error ) { return $EXIT_CATCH_ALL ; }
  5754. my $exit_value = $EXIT_VALUE_OF_ERR_TYPE{ $most_common_error } || $EXIT_CATCH_ALL ;
  5755. return $exit_value ;
  5756. }
  5757. sub exit_most_errors
  5758. {
  5759. my $mysync = shift @ARG ;
  5760. myprint( errors_listing( $mysync ) ) ;
  5761. my $exit_value = exit_value( $mysync, $mysync->{ most_common_error } ) ;
  5762. exit_clean( $mysync, $exit_value ) ;
  5763. return ;
  5764. }
  5765. sub exit_clean
  5766. {
  5767. my $mysync = shift @ARG ;
  5768. my $status = shift @ARG ;
  5769. my @messages = @ARG ;
  5770. if ( @messages )
  5771. {
  5772. myprint( @messages ) ;
  5773. }
  5774. myprint( "Exiting with return value $status ($EXIT_TXT{$status}) $mysync->{nb_errors}/$mysync->{errorsmax} nb_errors/max_errors PID $PROCESS_ID\n" ) ;
  5775. cleanup_before_exit( $mysync ) ;
  5776. exit $status ;
  5777. }
  5778. sub missing_option
  5779. {
  5780. my $mysync = shift @ARG ;
  5781. my $option = shift @ARG ;
  5782. $mysync->{nb_errors}++ ;
  5783. exit_clean( $mysync, $EX_USAGE, "$option option is mandatory, for help run $PROGRAM_NAME --help\n" ) ;
  5784. return ;
  5785. }
  5786. sub catch_ignore
  5787. {
  5788. my $mysync = shift @ARG ;
  5789. my $signame = shift @ARG ;
  5790. my $sigcounter = ++$mysync->{ sigcounter }{ $signame } ;
  5791. myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", mygetppid( ),
  5792. "). Received $sigcounter $signame signals so far. Thanks!\n" ) ;
  5793. do_and_print_stats( $mysync ) ;
  5794. return ;
  5795. }
  5796. sub catch_exit
  5797. {
  5798. my $mysync = shift @ARG ;
  5799. my $signame = shift || q{} ;
  5800. if ( $signame ) {
  5801. myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", mygetppid( ),
  5802. "). Asked to terminate\n" ) ;
  5803. if ( $mysync->{can_do_stats} ) {
  5804. do_and_print_stats( $mysync ) ;
  5805. myprint( "Ended by a signal $signame (my PID is $PROCESS_ID my PPID is ",
  5806. mygetppid( ), "). I am asked to terminate immediately.\n" ) ;
  5807. }
  5808. myprint( "You should resynchronize those accounts by running a sync again,\n",
  5809. "since some messages and entire folders might still be missing on host2.\n"
  5810. ) ;
  5811. ## no critic (RequireLocalizedPunctuationVars)
  5812. # Well, restore default action does not work well
  5813. $SIG{ $signame } = 'DEFAULT'; # restore default action
  5814. #$SIG{ 'TERM' } = 'DEFAULT'; # restore default action
  5815. # kill myself with $signame
  5816. # https://www.cons.org/cracauer/sigint.html
  5817. myprint( "Killing myself with signal $signame\n" ) ;
  5818. #cleanup_before_exit( $mysync ) ;
  5819. kill( $signame, $PROCESS_ID ) ;
  5820. #kill( 'TERM', $PROCESS_ID ) ;
  5821. #sleep 1 ;
  5822. #while ( 1 ) { } ;
  5823. $mysync->{nb_errors}++ ;
  5824. exit_clean( $mysync, $EXIT_BY_SIGNAL,
  5825. "Still there after killing myself with signal $signame...\n"
  5826. ) ;
  5827. }
  5828. else
  5829. {
  5830. $mysync->{nb_errors}++ ;
  5831. exit_clean( $mysync, $EXIT_BY_SIGNAL, "Exiting in catch_exit with no signal...\n" ) ;
  5832. }
  5833. return ;
  5834. }
  5835. sub catch_print
  5836. {
  5837. my $mysync = shift @ARG ;
  5838. my $signame = shift @ARG ;
  5839. my $sigcounter = ++$mysync->{ sigcounter }{ $signame } ;
  5840. myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", mygetppid( ),
  5841. "). Received $sigcounter $signame signals so far. Thanks!\n" ) ;
  5842. return ;
  5843. }
  5844. sub here_twice
  5845. {
  5846. my $mysync = shift @ARG ;
  5847. my $now = time ;
  5848. my $previous = $mysync->{lastcatch} || 0 ;
  5849. $mysync->{lastcatch} = $now ;
  5850. if ( $INTERVAL_TO_EXIT >= $now - $previous ) {
  5851. return $TRUE ;
  5852. }else{
  5853. return $FALSE ;
  5854. }
  5855. }
  5856. sub catch_reconnect
  5857. {
  5858. my $mysync = shift @ARG ;
  5859. my $signame = shift @ARG ;
  5860. if ( here_twice( $mysync ) ) {
  5861. myprint( "Got two signals $signame within $INTERVAL_TO_EXIT seconds. Exiting...\n" ) ;
  5862. catch_exit( $mysync, $signame ) ;
  5863. }else{
  5864. myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", mygetppid( ), ")\n",
  5865. "Hit 2 ctr-c within 2 seconds to exit the program\n",
  5866. "Hit only 1 ctr-c to reconnect to both imap servers\n",
  5867. ) ;
  5868. myprint( "For now only one signal $signame within $INTERVAL_TO_EXIT seconds.\n" ) ;
  5869. if ( ! defined $mysync->{imap1} ) { return ; }
  5870. if ( ! defined $mysync->{imap2} ) { return ; }
  5871. myprint( "Info: reconnecting to host1 imap server $mysync->{host1}\n" ) ;
  5872. $mysync->{imap1}->State( Mail::IMAPClient::Unconnected ) ;
  5873. $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT} += 1 ;
  5874. if ( $mysync->{imap1}->reconnect( ) )
  5875. {
  5876. myprint( "Info: reconnected to host1 imap server $mysync->{host1}\n" ) ;
  5877. }
  5878. else
  5879. {
  5880. $mysync->{nb_errors}++ ;
  5881. exit_clean( $mysync, $EXIT_CONNECTION_FAILURE ) ;
  5882. }
  5883. myprint( "Info: reconnecting to host2 imap server\n" ) ;
  5884. $mysync->{imap2}->State( Mail::IMAPClient::Unconnected ) ;
  5885. $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT} += 1 ;
  5886. if ( $mysync->{imap2}->reconnect( ) )
  5887. {
  5888. myprint( "Info: reconnected to host2 imap server $mysync->{host2}\n" ) ;
  5889. }
  5890. else
  5891. {
  5892. $mysync->{nb_errors}++ ;
  5893. exit_clean( $mysync, $EXIT_CONNECTION_FAILURE ) ;
  5894. }
  5895. myprint( "Info: reconnected to both imap servers\n" ) ;
  5896. }
  5897. return ;
  5898. }
  5899. sub install_signals
  5900. {
  5901. my $mysync = shift @ARG ;
  5902. if ( under_docker_context( $mysync ) )
  5903. {
  5904. # output( $mysync, "Under docker context so leaving signals as they are\n" ) ;
  5905. output( $mysync, "Under docker context so installing only signals to exit\n" ) ;
  5906. @{ $mysync->{ sigexit } } = ( defined( $mysync->{ sigexit } ) ) ? @{ $mysync->{ sigexit } } : ( 'INT', 'QUIT', 'TERM' ) ;
  5907. sig_install( $mysync, 'catch_exit', @{ $mysync->{ sigexit } } ) ;
  5908. }
  5909. else
  5910. {
  5911. # Unix signals
  5912. @{ $mysync->{ sigexit } } = ( defined( $mysync->{ sigexit } ) ) ? @{ $mysync->{ sigexit } } : ( 'QUIT', 'TERM' ) ;
  5913. @{ $mysync->{ sigreconnect } } = ( defined( $mysync->{ sigreconnect } ) ) ? @{ $mysync->{ sigreconnect } } : ( 'INT' ) ;
  5914. @{ $mysync->{ sigprint } } = ( defined( $mysync->{ sigprint } ) ) ? @{ $mysync->{ sigprint } } : ( 'HUP' ) ;
  5915. @{ $mysync->{ sigignore } } = ( defined( $mysync->{ sigignore } ) ) ? @{ $mysync->{ sigignore } } : ( ) ;
  5916. #local %SIG = %SIG ;
  5917. sig_install( $mysync, 'catch_exit', @{ $mysync->{ sigexit } } ) ;
  5918. sig_install( $mysync, 'catch_reconnect', @{ $mysync->{ sigreconnect } } ) ;
  5919. sig_install( $mysync, 'catch_print', @{ $mysync->{ sigprint } } ) ;
  5920. # --sigignore can override sigexit, sigreconnect and sigprint (for the same signals only)
  5921. sig_install( $mysync, 'catch_ignore', @{ $mysync->{ sigignore } } ) ;
  5922. # remove/add sleeping mechanism when receiving USR1 signal (except on Win32)
  5923. sig_install_toggle_sleep( $mysync ) ;
  5924. }
  5925. return ;
  5926. }
  5927. sub tests_reconnect_12_if_needed
  5928. {
  5929. note( 'Entering tests_reconnect_12_if_needed()' ) ;
  5930. my $mysync ;
  5931. $mysync->{imap1} = Mail::IMAPClient->new( ) ;
  5932. $mysync->{imap2} = Mail::IMAPClient->new( ) ;
  5933. $mysync->{imap1}->Server( 'test1.lamiral.info' ) ;
  5934. $mysync->{imap2}->Server( 'test2.lamiral.info' ) ;
  5935. is( 2, reconnect_12_if_needed( $mysync ), 'reconnect_12_if_needed: test1&test2 .lamiral.info => 1' ) ;
  5936. is( 1, $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_12_if_needed: test1.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ;
  5937. is( 1, $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_12_if_needed: test2.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ;
  5938. note( 'Leaving tests_reconnect_12_if_needed()' ) ;
  5939. return ;
  5940. }
  5941. sub reconnect_12_if_needed
  5942. {
  5943. my $mysync = shift @ARG ;
  5944. #return 2 ;
  5945. if ( ! reconnect_if_needed( $mysync->{imap1} ) ) {
  5946. return ;
  5947. }
  5948. if ( ! reconnect_if_needed( $mysync->{imap2} ) ) {
  5949. return ;
  5950. }
  5951. # both were good
  5952. return 2 ;
  5953. }
  5954. sub tests_reconnect_if_needed
  5955. {
  5956. note( 'Entering tests_reconnect_if_needed()' ) ;
  5957. my $myimap ;
  5958. is( undef, reconnect_if_needed( ), 'reconnect_if_needed: no args => undef' ) ;
  5959. is( undef, reconnect_if_needed( $myimap ), 'reconnect_if_needed: undef arg => undef' ) ;
  5960. $myimap = Mail::IMAPClient->new( ) ;
  5961. $myimap->Debug( 1 ) ;
  5962. is( undef, reconnect_if_needed( $myimap ), 'reconnect_if_needed: empty new Mail::IMAPClient => undef' ) ;
  5963. $myimap->Server( 'test.lamiral.info' ) ;
  5964. is( 1, reconnect_if_needed( $myimap ), 'reconnect_if_needed: test.lamiral.info => 1' ) ;
  5965. is( 1, $myimap->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_if_needed: test.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ;
  5966. note( 'Leaving tests_reconnect_if_needed()' ) ;
  5967. return ;
  5968. }
  5969. sub reconnect_if_needed
  5970. {
  5971. # return undef upon failure.
  5972. # return 1 upon connection success, with or without reconnection.
  5973. my $imap = shift @ARG ;
  5974. if ( ! defined $imap ) { return ; }
  5975. if ( ! $imap->Server( ) ) { return ; }
  5976. if ( $imap->IsUnconnected( ) ) {
  5977. $imap->{IMAPSYNC_RECONNECT_COUNT} += 1 ;
  5978. if ( $imap->reconnect( ) ) {
  5979. return 1 ;
  5980. }
  5981. }else{
  5982. return 1 ;
  5983. }
  5984. # A last forced one
  5985. $imap->State( Mail::IMAPClient::Unconnected ) ;
  5986. $imap->reconnect( ) ;
  5987. $imap->{IMAPSYNC_RECONNECT_COUNT} += 1 ;
  5988. if ( $imap->noop ) {
  5989. # NOOP is ok
  5990. return 1 ;
  5991. }
  5992. return ;
  5993. }
  5994. sub justconnect
  5995. {
  5996. my $mysync = shift @ARG ;
  5997. my $justconnect1 = justconnect1( $sync ) ;
  5998. my $justconnect2 = justconnect2( $sync ) ;
  5999. return "$justconnect1 $justconnect2";
  6000. }
  6001. sub justconnect1
  6002. {
  6003. my $mysync = shift @ARG ;
  6004. if ( $mysync->{host1} )
  6005. {
  6006. myprint( "Host1: Will just connect to $mysync->{host1} without login\n" ) ;
  6007. $mysync->{imap1} = connect_imap(
  6008. $mysync->{host1}, $mysync->{port1},
  6009. $mysync->{ssl1}, $mysync->{tls1},
  6010. $mysync->{ acc1 } ) ;
  6011. imap_id( $mysync, $mysync->{imap1}, $mysync->{ acc1 }->{ Side } ) ;
  6012. $mysync->{imap1}->logout( ) ;
  6013. return $mysync->{host1} ;
  6014. }
  6015. return q{} ;
  6016. }
  6017. sub justconnect2
  6018. {
  6019. my $mysync = shift @ARG ;
  6020. if ( $mysync->{host2} )
  6021. {
  6022. myprint( "Host2: Will just connect to $mysync->{host2} without login\n" ) ;
  6023. $mysync->{imap2} = connect_imap(
  6024. $mysync->{host2}, $mysync->{port2},
  6025. $mysync->{ssl2}, $mysync->{tls2},
  6026. $mysync->{ acc2 } ) ;
  6027. imap_id( $mysync, $mysync->{imap2}, $mysync->{ acc2 }->{ Side } ) ;
  6028. $mysync->{imap2}->logout( ) ;
  6029. return $mysync->{host2} ;
  6030. }
  6031. return q{} ;
  6032. }
  6033. sub skip_macosx
  6034. {
  6035. #return ;
  6036. # hostname is sometimes "macosx.polarhome.com" sometimes "macosx"
  6037. return( ( ( 'macosx.polarhome.com' eq hostname( ) ) || ( 'macosx' eq hostname( ) ) )
  6038. && ( 'darwin' eq $OSNAME ) ) ;
  6039. }
  6040. sub skip_macosx_binary
  6041. {
  6042. #return ;
  6043. return( skip_macosx( ) && ( $PROGRAM_NAME =~ m{imapsync_bin_Darwin} ) ) ;
  6044. }
  6045. sub tests_mailimapclient_connect
  6046. {
  6047. note( 'Entering tests_mailimapclient_connect()' ) ;
  6048. my $imap ;
  6049. # ipv4
  6050. ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect ipv4: new' ) ;
  6051. is( 'Mail::IMAPClient', ref( $imap ), 'mailimapclient_connect ipv4: ref is Mail::IMAPClient' ) ;
  6052. # Mail::IMAPClient 3.40 die on this... So we skip it, thanks to "mature" IO::Socket::IP
  6053. # Mail::IMAPClient 3.42 is ok so this test is back.
  6054. is( undef, $imap->connect( ), 'mailimapclient_connect ipv4: connect with no server => failure' ) ;
  6055. is( 'test.lamiral.info', $imap->Server( 'test.lamiral.info' ), 'mailimapclient_connect ipv4: setting Server(test.lamiral.info)' ) ;
  6056. is( 1, $imap->Debug( 1 ), 'mailimapclient_connect ipv4: setting Debug( 1 )' ) ;
  6057. is( 143, $imap->Port( 143 ), 'mailimapclient_connect ipv4: setting Port( 143 )' ) ;
  6058. is( 10, $imap->Timeout( 10 ), 'mailimapclient_connect ipv4: setting Timeout( 10 )' ) ;
  6059. like( ref( $imap->connect( ) ), qr/IO::Socket::INET|IO::Socket::IP/, 'mailimapclient_connect ipv4: connect to test.lamiral.info' ) ;
  6060. like( $imap->logout( ), qr/Mail::IMAPClient/, 'mailimapclient_connect ipv4: logout' ) ;
  6061. is( undef, undef $imap, 'mailimapclient_connect ipv4: free variable' ) ;
  6062. # ipv4 + ssl
  6063. ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect ipv4 + ssl: new' ) ;
  6064. is( 'test.lamiral.info', $imap->Server( 'test.lamiral.info' ), 'mailimapclient_connect ipv4 + ssl: setting Server(test.lamiral.info)' ) ;
  6065. is( 1, $imap->Debug( 1 ), 'mailimapclient_connect ipv4 + ssl: setting Debug( 1 )' ) ;
  6066. ok( $imap->Ssl( [ SSL_verify_mode => SSL_VERIFY_NONE, SSL_cipher_list => 'DEFAULT:!DH' ] ), 'mailimapclient_connect ipv4 + ssl: setting Ssl( SSL_VERIFY_NONE )' ) ;
  6067. is( 993, $imap->Port( 993 ), 'mailimapclient_connect ipv4 + ssl: setting Port( 993 )' ) ;
  6068. like( ref( $imap->connect( ) ), qr/IO::Socket::SSL/, 'mailimapclient_connect ipv4 + ssl: connect to test.lamiral.info' ) ;
  6069. like( $imap->logout( ), qr/Mail::IMAPClient/, 'mailimapclient_connect ipv4 + ssl: logout in ssl does not cause failure' ) ;
  6070. is( undef, undef $imap, 'mailimapclient_connect ipv4 + ssl: free variable' ) ;
  6071. # ipv6 + ssl
  6072. ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect ipv6 + ssl: new' ) ;
  6073. is( 'petiteipv6.lamiral.info', $imap->Server( 'petiteipv6.lamiral.info' ), 'mailimapclient_connect ipv6 + ssl: setting Server petiteipv6.lamiral.info' ) ;
  6074. is( 10, $imap->Timeout( 10 ), 'mailimapclient_connect ipv6: setting Timeout( 10 )' ) ;
  6075. ok( $imap->Ssl( [ SSL_verify_mode => SSL_VERIFY_NONE, SSL_cipher_list => 'DEFAULT:!DH' ] ), 'mailimapclient_connect ipv6 + ssl: setting Ssl( SSL_VERIFY_NONE )' ) ;
  6076. is( 993, $imap->Port( 993 ), 'mailimapclient_connect ipv6 + ssl: setting Port( 993 )' ) ;
  6077. SKIP: {
  6078. if (
  6079. 'CUILLERE' eq hostname()
  6080. or
  6081. skip_macosx()
  6082. or
  6083. -e '/.dockerenv'
  6084. or
  6085. 'pcHPDV7-HP' eq hostname()
  6086. )
  6087. {
  6088. skip( 'Tests avoided on CUILLERE/pcHPDV7-HP/macosx.polarhome.com/docker cannot do ipv6', 4 ) ;
  6089. }
  6090. is( 1, $imap->Debug( 1 ), 'mailimapclient_connect ipv4 + ssl: setting Debug( 1 )' ) ;
  6091. # It sounds stupid but it avoids failures on the next test about $imap->connect
  6092. 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' ) ;
  6093. like( ref( $imap->connect( ) ), qr/IO::Socket::SSL/, 'mailimapclient_connect ipv6 + ssl: connect to petiteipv6.lamiral.info' ) ;
  6094. # This one is ok on petite, not on ks2, do not know why, so commented.
  6095. like( ref( $imap->logout( ) ), qr/Mail::IMAPClient/, 'mailimapclient_connect ipv6 + ssl: logout in ssl is ok on petiteipv6.lamiral.info' ) ;
  6096. }
  6097. is( undef, undef $imap, 'mailimapclient_connect ipv6 + ssl: free variable' ) ;
  6098. note( 'Leaving tests_mailimapclient_connect()' ) ;
  6099. return ;
  6100. }
  6101. sub tests_mailimapclient_connect_bug
  6102. {
  6103. note( 'Entering tests_mailimapclient_connect_bug()' ) ;
  6104. my $imap ;
  6105. # ipv6
  6106. ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect_bug ipv6: new' ) ;
  6107. is( 'ks6ipv6.lamiral.info', $imap->Server( 'ks6ipv6.lamiral.info' ), 'mailimapclient_connect_bug ipv6: setting Server(ks6ipv6.lamiral.info)' ) ;
  6108. is( 143, $imap->Port( 143 ), 'mailimapclient_connect_bug ipv6: setting Port( 993 )' ) ;
  6109. SKIP: {
  6110. if (
  6111. 'CUILLERE' eq hostname()
  6112. or
  6113. skip_macosx()
  6114. or
  6115. -e '/.dockerenv'
  6116. or
  6117. 'pcHPDV7-HP' eq hostname()
  6118. )
  6119. {
  6120. skip( 'Tests avoided on CUILLERE/pcHPDV7-HP/macosx.polarhome.com/docker cannot do ipv6', 1 ) ;
  6121. }
  6122. like( ref( $imap->connect( ) ), qr/IO::Socket::INET/, 'mailimapclient_connect_bug ipv6: connect to ks6ipv6.lamiral.info' )
  6123. or diag( 'mailimapclient_connect_bug ipv6: ', $imap->LastError( ), $!, ) ;
  6124. }
  6125. #is( $imap->logout( ), undef, 'mailimapclient_connect_bug ipv6: logout in ssl causes failure' ) ;
  6126. is( undef, undef $imap, 'mailimapclient_connect_bug ipv6: free variable' ) ;
  6127. note( 'Leaving tests_mailimapclient_connect_bug()' ) ;
  6128. return ;
  6129. }
  6130. sub tests_connect_socket
  6131. {
  6132. note( 'Entering tests_connect_socket()' ) ;
  6133. is( undef, connect_socket( ), 'connect_socket: no args' ) ;
  6134. my $socket ;
  6135. my $imap ;
  6136. SKIP: {
  6137. if (
  6138. 'CUILLERE' eq hostname()
  6139. or
  6140. skip_macosx()
  6141. or
  6142. -e '/.dockerenv'
  6143. or
  6144. 'pcHPDV7-HP' eq hostname()
  6145. )
  6146. {
  6147. skip( 'Tests avoided on CUILLERE/pcHPDV7-HP/macosx.polarhome.com/docker cannot do ipv6', 2 ) ;
  6148. }
  6149. $socket = IO::Socket::INET6->new(
  6150. PeerAddr => 'ks6ipv6.lamiral.info',
  6151. PeerPort => 143,
  6152. ) ;
  6153. ok( $imap = connect_socket( $socket ), 'connect_socket: ks6ipv6.lamiral.info port 143 IO::Socket::INET6' ) ;
  6154. #$imap->Debug( 1 ) ;
  6155. # myprint( $imap->capability( ) ) ;
  6156. if ( $imap ) {
  6157. $imap->logout( ) ;
  6158. }
  6159. $IO::Socket::SSL::DEBUG = 4 ;
  6160. $socket = IO::Socket::SSL->new(
  6161. PeerHost => 'ks6ipv6.lamiral.info',
  6162. PeerPort => 993,
  6163. SSL_verify_mode => SSL_VERIFY_NONE,
  6164. SSL_cipher_list => 'DEFAULT:!DH',
  6165. ) ;
  6166. # myprint( $socket ) ;
  6167. ok( $imap = connect_socket( $socket ), 'connect_socket: ks6ipv6.lamiral.info port 993 IO::Socket::SSL' ) ;
  6168. #$imap->Debug( 1 ) ;
  6169. # myprint( $imap->capability( ) ) ;
  6170. # $socket->close( ) ;
  6171. if ( $imap ) {
  6172. $socket->close( ) ;
  6173. }
  6174. #$socket->close(SSL_no_shutdown => 1) ;
  6175. #$imap->logout( ) ;
  6176. #myprint( "\n" ) ;
  6177. #$imap->logout( ) ;
  6178. }
  6179. note( 'Leaving tests_connect_socket()' ) ;
  6180. return ;
  6181. }
  6182. sub connect_socket
  6183. {
  6184. my( $socket ) = @ARG ;
  6185. if ( ! defined $socket ) { return ; }
  6186. my $host = $socket->peerhost( ) ;
  6187. my $port = $socket->peerport( ) ;
  6188. #print "socket->peerhost: ", $socket->peerhost( ), "\n" ;
  6189. #print "socket->peerport: ", $socket->peerport( ), "\n" ;
  6190. my $imap = Mail::IMAPClient->new( ) ;
  6191. $imap->Socket( $socket ) ;
  6192. my $banner = $imap->Results()->[0] ;
  6193. #myprint( "banner: $banner" ) ;
  6194. return $imap ;
  6195. }
  6196. sub tests_probe_imapssl
  6197. {
  6198. note( 'Entering tests_probe_imapssl()' ) ;
  6199. is( undef, probe_imapssl( ), 'probe_imapssl: no args => undef' ) ;
  6200. is( undef, probe_imapssl( 'unknown' ), 'probe_imapssl: unknown => undef' ) ;
  6201. note( "hostname is: ", hostname() ) ;
  6202. SKIP: {
  6203. if (
  6204. 'CUILLERE' eq hostname()
  6205. or
  6206. skip_macosx()
  6207. or
  6208. -e '/.dockerenv'
  6209. or
  6210. 'pcHPDV7-HP' eq hostname()
  6211. )
  6212. {
  6213. skip( 'Tests avoided on CUILLERE or pcHPDV7-HP or Mac or docker: cannot do ipv6', 0 ) ;
  6214. }
  6215. # fed up with this one
  6216. #like( probe_imapssl( 'ks6ipv6.lamiral.info' ), qr/^\* OK/, 'probe_imapssl: ks6ipv6.lamiral.info matches "* OK"' ) ;
  6217. } ;
  6218. # It sounds stupid but it avoids failures on the next test about $imap->connect
  6219. ok( resolv( 'imap.gmail.com' ), 'resolv: imap.gmail.com => something' ) ;
  6220. like( probe_imapssl( 'imap.gmail.com' ), qr/^\* OK/, 'probe_imapssl: imap.gmail.com matches "* OK"' ) ;
  6221. like( probe_imapssl( 'test1.lamiral.info' ), qr/^\* OK/, 'probe_imapssl: test1.lamiral.info matches "* OK"' ) ;
  6222. note( 'Leaving tests_probe_imapssl()' ) ;
  6223. return ;
  6224. }
  6225. sub probe_imapssl
  6226. {
  6227. my $host = shift @ARG ;
  6228. if ( ! $host ) { return ; }
  6229. $sync->{ debug } and $IO::Socket::SSL::DEBUG = 4 ;
  6230. my $socket = IO::Socket::SSL->new(
  6231. PeerHost => $host,
  6232. PeerPort => $IMAP_SSL_PORT,
  6233. SSL_verifycn_scheme => 'imap',
  6234. SSL_verify_mode => $SSL_VERIFY_POLICY,
  6235. SSL_cipher_list => 'DEFAULT:!DH',
  6236. ) ;
  6237. if ( ! $socket ) { return ; }
  6238. $sync->{ debug } and print "socket: $socket\n" ;
  6239. my $banner ;
  6240. $socket->sysread( $banner, 65_536 ) ;
  6241. $sync->{ debug } and print "banner: $banner" ;
  6242. $socket->close( ) ;
  6243. return $banner ;
  6244. }
  6245. sub connect_imap
  6246. {
  6247. my( $host, $port, $ssl, $tls, $acc ) = @_ ;
  6248. my $imap = Mail::IMAPClient->new( ) ;
  6249. if ( $ssl ) { set_ssl( $imap, $acc ) }
  6250. $imap->Server( $host ) ;
  6251. $imap->Port( $port ) ;
  6252. $imap->Debug( $acc->{ debugimap } ) ;
  6253. $imap->Timeout( $acc->{ timeout } ) ;
  6254. #$imap->Keepalive( $acc->{ keepalive } ) ;
  6255. my $side = lc $acc->{ Side } ;
  6256. myprint( "$acc->{ Side }: connecting on $side [$host] port [$port]\n" ) ;
  6257. if ( ! $imap->connect( ) )
  6258. {
  6259. $sync->{nb_errors}++ ;
  6260. exit_clean( $sync, $EXIT_CONNECTION_FAILURE,
  6261. "$acc->{ Side }: Can not open imap connection on [$host]: ",
  6262. $imap->LastError,
  6263. " $OS_ERROR\n"
  6264. ) ;
  6265. }
  6266. myprint( "$acc->{ Side } IP address: ", $imap->Socket->peerhost(), " Local IP address: ", $imap->Socket->sockhost(), "\n" ) ;
  6267. my $banner = $imap->Results()->[0] ;
  6268. myprint( "$acc->{ Side } banner: $banner" ) ;
  6269. myprint( "$acc->{ Side } capability: ", join(q{ }, @{ $imap->capability() || [] }), "\n" ) ;
  6270. if ( $tls ) {
  6271. set_tls( $imap, $acc ) ;
  6272. if ( ! $imap->starttls( ) )
  6273. {
  6274. $sync->{nb_errors}++ ;
  6275. exit_clean( $sync, $EXIT_TLS_FAILURE,
  6276. "$acc->{ Side }: Can not go to tls encryption on $side [$host]:",
  6277. $imap->LastError, "\n"
  6278. ) ;
  6279. }
  6280. myprint( "$acc->{ Side }: Socket successfully converted to SSL\n" ) ;
  6281. }
  6282. return( $imap ) ;
  6283. }
  6284. sub tests_compress_ssl
  6285. {
  6286. note( 'Entering tests_compress_ssl()' ) ;
  6287. SKIP: {
  6288. if ( skip_macosx( ) )
  6289. {
  6290. skip( 'Tests avoided on host polarhome macosx, no clue "ssl3_get_server_certificate:certificate verify failed"', 12 ) ;
  6291. }
  6292. else
  6293. {
  6294. my $myimap ;
  6295. my $acc = {} ;
  6296. $acc->{ Side } = 'HostK' ;
  6297. $acc->{ authmech } = 'LOGIN' ;
  6298. $acc->{ debugimap } = 1 ;
  6299. $acc->{ compress } = 1 ;
  6300. $acc->{ N } = 'K' ;
  6301. ok(
  6302. $myimap = login_imap( 'test1.lamiral.info', 993, 'test1', 'secret1',
  6303. 1, undef,
  6304. 1, 100, $acc, {},
  6305. ), 'acc_compress_imap: test1.lamiral.info test1 ssl' ) ;
  6306. ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 ssl IsAuthenticated' ) ;
  6307. is( $acc->{ imap }, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info ok" ) ;
  6308. is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info 2nd nok" ) ;
  6309. ok(
  6310. $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1',
  6311. 0, undef,
  6312. 1, 100, $acc, {},
  6313. ), 'acc_compress_imap: test1.lamiral.info test1 tls' ) ;
  6314. ok( $myimap && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 tls IsAuthenticated' ) ;
  6315. is( $acc->{ imap }, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info tls ok" ) ;
  6316. is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info tls 2nd nok" ) ;
  6317. # Third, no compression
  6318. $acc->{ compress } = 0 ;
  6319. ok(
  6320. $myimap = login_imap( 'test1.lamiral.info', 993, 'test1', 'secret1',
  6321. 1, undef,
  6322. 1, 100, $acc, {},
  6323. ), 'acc_compress_imap: test1.lamiral.info test1 ssl' ) ;
  6324. ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 ssl IsAuthenticated' ) ;
  6325. is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info off ok" ) ;
  6326. is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info 2nd off ok" ) ;
  6327. }
  6328. }
  6329. note( 'Leaving tests_compress_ssl()' ) ;
  6330. return ;
  6331. }
  6332. sub tests_compress
  6333. {
  6334. note( 'Entering tests_compress()' ) ;
  6335. my $myimap ;
  6336. my $acc = {} ;
  6337. $acc->{ Side } = 'HostK' ;
  6338. $acc->{ authmech } = 'LOGIN' ;
  6339. $acc->{ debugimap } = 1 ;
  6340. $acc->{ compress } = 1 ;
  6341. $acc->{ N } = 'K' ;
  6342. ok(
  6343. $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1',
  6344. 0, 0,
  6345. 1, 100, $acc, {},
  6346. ), 'acc_compress_imap: test1.lamiral.info test1' ) ;
  6347. ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 IsAuthenticated' ) ;
  6348. is( $acc->{ imap }, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info ok" ) ;
  6349. is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info 2nd nok" ) ;
  6350. ok(
  6351. $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1',
  6352. 0, 0,
  6353. 1, 100, $acc, {},
  6354. ), 'acc_compress_imap: test1.lamiral.info test1 tls' ) ;
  6355. ok( $myimap && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 tls IsAuthenticated' ) ;
  6356. is( $acc->{ imap }, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info tls ok" ) ;
  6357. is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info tls 2nd nok" ) ;
  6358. # Third, no compression
  6359. $acc->{ compress } = 0 ;
  6360. ok(
  6361. $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1',
  6362. 0, 0,
  6363. 1, 100, $acc, {},
  6364. ), 'acc_compress_imap: test1.lamiral.info test1 ssl' ) ;
  6365. ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 ssl IsAuthenticated' ) ;
  6366. is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info off ok" ) ;
  6367. is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info 2nd off ok" ) ;
  6368. note( 'Leaving tests_compress()' ) ;
  6369. return ;
  6370. }
  6371. sub acc_compress_imap
  6372. {
  6373. my $acc = shift @ARG ;
  6374. if ( ! defined( $acc ) ) { return ; }
  6375. my $ret ;
  6376. my $imap = $acc->{ imap } ;
  6377. if ( ! defined $imap ) { return ; }
  6378. if ( $imap && $acc->{ compress } )
  6379. {
  6380. myprint( "$acc->{ Side }: Trying to turn imap compression on. Use --nocompress" . $acc->{ N } . " to avoid compression on " . lc( $acc->{ Side } ) . "\n" ) ;
  6381. if ( $ret = $imap->compress() )
  6382. {
  6383. myprint( "$acc->{ Side }: Compression is on now\n" ) ;
  6384. }
  6385. else
  6386. {
  6387. myprint( "$acc->{ Side }: Failed to turn compression on\n" ) ;
  6388. }
  6389. }
  6390. else
  6391. {
  6392. myprint( "$acc->{ Side }: Compression is off. Use --compress" . $acc->{ N } . " to allow compression on " . lc( $acc->{ Side } ) . "\n" ) ;
  6393. }
  6394. # $ret is $acc->{ imap } on success, undef on failure or when there is nothing to do.
  6395. return $ret ;
  6396. }
  6397. sub tests_login_imap
  6398. {
  6399. note( 'Entering tests_login_imap()' ) ;
  6400. is( undef, login_imap( ), 'login_imap: no args => undef' ) ;
  6401. SKIP: {
  6402. if ( skip_macosx( ) )
  6403. {
  6404. skip( 'Tests avoided only on binary on host polarhome macosx, no clue "ssl3_get_server_certificate:certificate verify failed"', 15 ) ;
  6405. }
  6406. else{
  6407. my $myimap ;
  6408. my $acc = {} ;
  6409. $acc->{ Side } = 'HostK' ;
  6410. $acc->{ authmech } = 'LOGIN' ;
  6411. #$IO::Socket::SSL::DEBUG = 4 ;
  6412. # Each month (trimester?):
  6413. # echo | openssl s_client -crlf -connect test1.lamiral.info:993
  6414. # ...
  6415. # certificate has expired
  6416. # Fix: ssh root@test1.lamiral.info 'apt update && apt upgrade && /etc/init.d/dovecot restart'
  6417. #
  6418. # or this one:
  6419. # echo | openssl s_client -crlf -connect test1.lamiral.info:993
  6420. # ...
  6421. # Verify return code: 9 (certificate is not yet valid)
  6422. # Fix: /etc/init.d/openntpd restart
  6423. # 2021_09_04 done
  6424. ok(
  6425. $myimap = login_imap( 'test1.lamiral.info', 993, 'test1', 'secret1',
  6426. 1, undef,
  6427. 1, 100, $acc, {},
  6428. ), 'login_imap: test1.lamiral.info test1 ssl' ) ;
  6429. ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: test1.lamiral.info test1 ssl IsAuthenticated' ) ;
  6430. is( $myimap, $acc->{ imap }, "login_imap: acc->{ imap } ok test1 ssl") ;
  6431. ok(
  6432. $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1',
  6433. 0, undef,
  6434. 1, 100, $acc, {},
  6435. ), 'login_imap: test1.lamiral.info test1 tls' ) ;
  6436. ok( $myimap && $myimap->IsAuthenticated( ), 'login_imap: test1.lamiral.info test1 tls IsAuthenticated' ) ;
  6437. is( $myimap, $acc->{ imap }, "login_imap: acc->{ imap } ok test1 tls") ;
  6438. #$IO::Socket::SSL::DEBUG = 4 ;
  6439. $acc->{sslargs} = { SSL_version => 'SSLv2' } ;
  6440. # SSLv2 not supported
  6441. is(
  6442. undef, $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1',
  6443. 0, undef,
  6444. 1, 100, $acc, {},
  6445. ), 'login_imap: test1.lamiral.info test1 tls SSLv2 not supported' ) ;
  6446. #SSL_verify_mode => 1
  6447. #SSL_version => 'TLSv1_1'
  6448. is( undef, $acc->{ imap }, "login_imap: acc->{ imap } test1 tls error => undef") ;
  6449. # I have left ? exit_clean to be replaced by errors_incr( $mysync, 'error message' )
  6450. # 1 in login_imap()
  6451. my $mysync = {} ;
  6452. $acc = {} ;
  6453. $acc->{ Side } = 'Host2' ;
  6454. $acc->{ authmech } = 'LOGIN' ;
  6455. is(
  6456. undef, login_imap( 'noresol.lamiral.info', 143, 'test1', 'secret1',
  6457. 0, undef,
  6458. 1, 100, $acc, $mysync,
  6459. ), 'login_imap: noresol.lamiral.info undef' ) ;
  6460. is( 'ERR_CONNECTION_FAILURE_HOST2', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host2 noresol.lamiral.info => ERR_CONNECTION_FAILURE_HOST2' ) ;
  6461. is( undef, $acc->{ imap }, "login_imap: acc->{ imap } noresol error => undef") ;
  6462. # authentication failure for user2
  6463. $mysync = {} ;
  6464. is(
  6465. undef, login_imap( 'test1.lamiral.info', 143, 'test1', 'Ce crétin',
  6466. 0, undef,
  6467. 1, 100, $acc, $mysync,
  6468. ), 'login_imap: user2 bad passord => undef' ) ;
  6469. is( 'ERR_AUTHENTICATION_FAILURE_USER2', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host2 bad password => ERR_AUTHENTICATION_FAILURE_USER2' ) ;
  6470. # authentication failure for user1
  6471. $mysync = {} ;
  6472. $acc = {} ;
  6473. $acc->{ Side } = 'Host1' ;
  6474. $acc->{ authmech } = 'LOGIN' ;
  6475. is(
  6476. undef, login_imap( 'test1.lamiral.info', 143, 'test1', 'Ce crétin',
  6477. 0, undef,
  6478. 1, 100, $acc, $mysync,
  6479. ), 'login_imap: user1 bad passord => undef' ) ;
  6480. is( 'ERR_AUTHENTICATION_FAILURE_USER1', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host1 bad password => ERR_AUTHENTICATION_FAILURE_USER1' ) ;
  6481. }
  6482. }
  6483. note( 'Leaving tests_login_imap()' ) ;
  6484. return ;
  6485. }
  6486. sub oauthgenerateaccess
  6487. {
  6488. if ( "petite" eq hostname() )
  6489. {
  6490. myprint( "oauthgenerateaccess\n" ) ;
  6491. my @output = backtick( 'cd oauth2 && pwd && ./generate_gmail_token imapsync.gl0@gmail.com' ) ;
  6492. myprint( @output ) ;
  6493. }
  6494. return ;
  6495. }
  6496. sub tests_login_imap_oauth
  6497. {
  6498. note( 'Entering tests_login_imap_oauth()' ) ;
  6499. oauthgenerateaccess() ;
  6500. SKIP: {
  6501. if ( skip_macosx_binary( ) )
  6502. {
  6503. skip( 'Tests avoided only on binary on host polarhome macosx, no clue "ssl3_get_server_certificate:certificate verify failed"', 6 ) ;
  6504. }
  6505. else
  6506. {
  6507. my $mysync ;
  6508. my $acc ;
  6509. # oauthdirect authentication failure for user2
  6510. $mysync = {} ;
  6511. $acc = {} ;
  6512. $acc->{ oauthdirect } = 'caca2' ;
  6513. $acc->{ debugimap } = 1 ;
  6514. $mysync->{ showpasswords } = 1 ;
  6515. $acc->{ Side } = 'Host2' ;
  6516. $acc->{ authmech } = 'QQQ' ;
  6517. is(
  6518. undef, login_imap( 'imap.gmail.com', 993, 'test1', 'Ce crétin',
  6519. 1, undef,
  6520. 1, 100, $acc, $mysync,
  6521. ), 'login_imap: user2 bad oauthdirect => undef' ) ;
  6522. is( 'ERR_AUTHENTICATION_FAILURE_USER2', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host2 bad oauthdirect => ERR_AUTHENTICATION_FAILURE_USER2' ) ;
  6523. # oauthdirect authentication failure for user1
  6524. $mysync = {} ;
  6525. $acc = {} ;
  6526. $acc->{ Side } = 'Host1' ;
  6527. $acc->{ oauthdirect } = 'caca1' ;
  6528. $acc->{ debugimap } = 1 ;
  6529. $mysync->{ showpasswords } = 1 ;
  6530. $acc->{ authmech } = 'QQQ' ;
  6531. is(
  6532. undef, login_imap( 'imap.gmail.com', 993, 'test1', 'Ce crétin',
  6533. 1, undef,
  6534. 1, 100, $acc, $mysync,
  6535. ), 'login_imap: user1 bad oauthdirect => undef' ) ;
  6536. is( 'ERR_AUTHENTICATION_FAILURE_USER1', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host1 bad oauthdirect => ERR_AUTHENTICATION_FAILURE_USER1' ) ;
  6537. # oauthdirect authentication failure for user1
  6538. $mysync = {} ;
  6539. $acc = {} ;
  6540. $acc->{ Side } = 'Host1' ;
  6541. $acc->{ oauthdirect } = '' ;
  6542. $acc->{ debugimap } = 1 ;
  6543. $mysync->{ showpasswords } = 1 ;
  6544. $acc->{ authmech } = 'QQQ' ;
  6545. is(
  6546. undef, login_imap( 'imap.gmail.com', 993, 'test1', 'Ce crétin',
  6547. 1, undef,
  6548. 1, 100, $acc, $mysync,
  6549. ), 'login_imap: user1 bad oauthdirect => undef' ) ;
  6550. is( 'ERR_AUTHENTICATION_FAILURE_USER1', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host1 no oauthdirect value => ERR_AUTHENTICATION_FAILURE_USER1' ) ;
  6551. }
  6552. }
  6553. # oauthdirect authentication success for user1
  6554. SKIP: {
  6555. if ( ! -r 'oauth2/D_oauth2_oauthdirect_imapsync.gl0@gmail.com.txt' )
  6556. {
  6557. skip( 'oauthdirect: no oauthdirect file', 6 ) ;
  6558. }
  6559. my $myimap ;
  6560. my $mysync = {} ;
  6561. my $acc = {} ;
  6562. $acc->{ Side } = 'Host1' ;
  6563. $acc->{ oauthdirect } = 'oauth2/D_oauth2_oauthdirect_imapsync.gl0@gmail.com.txt' ;
  6564. $acc->{ debugimap } = 1 ;
  6565. $mysync->{ showpasswords } = 1 ;
  6566. $acc->{ authmech } = 'QQQ' ;
  6567. isa_ok(
  6568. $myimap = login_imap( 'imap.gmail.com', 993, 'user_useless', 'password_useless',
  6569. 1, undef,
  6570. 1, 100, $acc, $mysync,
  6571. ), 'Mail::IMAPClient', 'login_imap: user1 good oauthdirect => Mail::IMAPClient' ) ;
  6572. ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthdirect IsAuthenticated' ) ;
  6573. ok( defined( $myimap ) && $myimap->logout( ), 'login_imap: gmail oauth2 oauthdirect logout' ) ;
  6574. ok( defined( $myimap ) && ! $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthdirect not IsAuthenticated after logout' ) ;
  6575. ok( defined( $myimap ) && $myimap->reconnect( ), 'login_imap: gmail oauth2 oauthdirect reconnect ok' ) ;
  6576. ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthdirect IsAuthenticated after reconnect' ) ;
  6577. }
  6578. # oauthaccesstoken authentication success for user1
  6579. SKIP: {
  6580. if ( ! -r 'oauth2/D_oauth2_access_token_imapsync.gl0@gmail.com.txt' )
  6581. {
  6582. skip( 'oauthaccesstoken: no access_token file', 6 ) ;
  6583. }
  6584. my $myimap ;
  6585. my $mysync = {} ;
  6586. my $acc = {} ;
  6587. $acc->{ Side } = 'Host1' ;
  6588. $acc->{ oauthaccesstoken } = 'oauth2/D_oauth2_access_token_imapsync.gl0@gmail.com.txt' ;
  6589. $acc->{ debugimap } = 1 ;
  6590. $mysync->{ showpasswords } = 1 ;
  6591. $acc->{ authmech } = 'QQQ' ;
  6592. isa_ok(
  6593. $myimap = login_imap( 'imap.gmail.com', 993, 'imapsync.gl0@gmail.com', 'password_useless',
  6594. 1, undef,
  6595. 1, 100, $acc, $mysync,
  6596. ), 'Mail::IMAPClient', 'login_imap: user1 good oauthaccesstoken => Mail::IMAPClient' ) ;
  6597. ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthaccesstoken IsAuthenticated' ) ;
  6598. ok( defined( $myimap ) && $myimap->logout( ), 'login_imap: gmail oauth2 oauthaccesstoken logout' ) ;
  6599. ok( defined( $myimap ) && ! $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthaccesstoken not IsAuthenticated after logout' ) ;
  6600. ok( defined( $myimap ) && $myimap->reconnect( ), 'login_imap: gmail oauth2 oauthaccesstoken reconnect ok' ) ;
  6601. ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthaccesstoken IsAuthenticated after reconnect' ) ;
  6602. }
  6603. note( 'Leaving tests_login_imap_oauth()' ) ;
  6604. return ;
  6605. }
  6606. sub login_imap
  6607. {
  6608. my @allargs = @_ ;
  6609. my(
  6610. $host, $port, $user, $password,
  6611. $ssl, $tls,
  6612. $uid, $split, $acc, $mysync ) = @allargs ;
  6613. $acc->{ imap } = undef ;
  6614. if ( ! all_defined( $host, $port, $user, $acc->{ Side } ) )
  6615. {
  6616. return ;
  6617. }
  6618. my $side = lc $acc->{ Side } ;
  6619. myprint( "$acc->{ Side }: connecting and login on $side [$host] port [$port] with user [$user]\n" ) ;
  6620. my $imap = init_imap( @allargs ) ;
  6621. if ( ! $imap->connect() )
  6622. {
  6623. my $error = "$acc->{ Side } failure: can not open imap connection on $side [$host] with user [$user]: "
  6624. . $imap->LastError . " $OS_ERROR\n" ;
  6625. errors_incr( $mysync, $error ) ;
  6626. return ;
  6627. }
  6628. # Add also $imap->Socket->sockhost() to help configuring firewalls, allowed rule.
  6629. myprint( "$acc->{ Side } IP address: ", $imap->Socket->peerhost(), " Local IP address: ", $imap->Socket->sockhost(), "\n" ) ;
  6630. my $banner = $imap->Results()->[0] ;
  6631. myprint( "$acc->{ Side } banner: $banner" ) ;
  6632. myprint( "$acc->{ Side } capability before authentication: ", join(q{ }, @{ $imap->capability() || [] }), "\n" ) ;
  6633. if ( (! $ssl) and (! defined $tls ) and $imap->has_capability( 'STARTTLS' ) ) {
  6634. myprint( "$acc->{ Side }: going to ssl because STARTTLS is in CAPABILITY. Use --notls1 or --notls2 to avoid that behavior\n" ) ;
  6635. $tls = 1 ;
  6636. }
  6637. #myprint( Data::Dumper->Dump( [ @allargs ] ) ) ;
  6638. if ( $tls ) {
  6639. set_tls( $imap, $acc ) ;
  6640. if ( ! $imap->starttls( ) )
  6641. {
  6642. my $error = "$acc->{ Side } failure: Can not go to tls encryption on $side [$host]: "
  6643. . $imap->LastError . "\n" ;
  6644. errors_incr( $mysync, $error ) ;
  6645. return ;
  6646. }
  6647. myprint( "$acc->{ Side }: Socket successfully converted to SSL\n" ) ;
  6648. }
  6649. if ( $acc->{ authmech } eq 'PREAUTH' ) {
  6650. if ( $imap->IsAuthenticated( ) ) {
  6651. $imap->Socket ;
  6652. myprintf("%s: Assuming PREAUTH for %s\n", $acc->{ Side }, $imap->Server ) ;
  6653. }else{
  6654. $mysync->{nb_errors}++ ;
  6655. exit_clean(
  6656. $mysync, $EXIT_AUTHENTICATION_FAILURE,
  6657. "$acc->{ Side } failure: error login on $side [$host] with user [$user] auth [PREAUTH]\n"
  6658. ) ;
  6659. }
  6660. }
  6661. if ( authenticate_imap( $imap, @allargs ) )
  6662. {
  6663. myprint( "$acc->{ Side }: success login on [$host] with user [$user] auth [$acc->{ authmech }] or [LOGIN]\n" ) ;
  6664. $acc->{ imap } = $imap ;
  6665. return( $imap ) ;
  6666. }
  6667. else
  6668. {
  6669. # The errors are already printed
  6670. myprint( "$acc->{ Side }: failed login on [$host] with user [$user] auth [$acc->{ authmech }]\n" ) ;
  6671. return ;
  6672. }
  6673. }
  6674. sub init_imap
  6675. {
  6676. my(
  6677. $host, $port, $user, $password,
  6678. $ssl, $tls,
  6679. $uid, $split, $acc, $mysync ) = @_ ;
  6680. my ( $imap ) ;
  6681. $imap = Mail::IMAPClient->new() ;
  6682. # Well, it does not change anything, does it?
  6683. # It does when suppressing the hack with *STDERR
  6684. $imap->Debug_fh( $mysync->{ tee } || *STDOUT ) ;
  6685. if ( $ssl ) { set_ssl( $imap, $acc ) }
  6686. if ( $tls ) { } # can not do set_tls() here because connect() will directly do a STARTTLS
  6687. $imap->Clear( 1 ) ;
  6688. $imap->Server( $host ) ;
  6689. $imap->Port( $port ) ;
  6690. $imap->Fast_io( $acc->{ fastio } ) ;
  6691. $imap->Buffer( $buffersize || $DEFAULT_BUFFER_SIZE ) ;
  6692. $imap->Uid( $uid ) ;
  6693. $imap->Peek( 1 ) ;
  6694. $imap->Debug( $acc->{ debugimap } ) ;
  6695. if ( $mysync->{ showpasswords } ) {
  6696. $imap->Showcredentials( 1 ) ;
  6697. }
  6698. if ( defined( $acc->{ timeout } ) )
  6699. {
  6700. $imap->Timeout( $acc->{ timeout } ) ;
  6701. }
  6702. if ( defined $acc->{ keepalive } )
  6703. {
  6704. $imap->Keepalive( $acc->{ keepalive } ) ;
  6705. }
  6706. if ( defined $acc->{ reconnectretry } )
  6707. {
  6708. $imap->Reconnectretry( $acc->{ reconnectretry } ) ;
  6709. }
  6710. $imap->{IMAPSYNC_RECONNECT_COUNT} = 0 ;
  6711. $imap->Ignoresizeerrors( $allowsizemismatch ) ;
  6712. $split and $imap->Maxcommandlength( $SPLIT_FACTOR * $split ) ;
  6713. return( $imap ) ;
  6714. }
  6715. sub authenticate_imap
  6716. {
  6717. my( $imap,
  6718. $host, $port, $user, $password,
  6719. $ssl, $tls,
  6720. $uid, $split, $acc, $mysync ) = @_ ;
  6721. check_capability( $imap, $acc->{ authmech }, $acc->{ Side } ) ;
  6722. $imap->User( $user ) ;
  6723. if ( defined $acc->{ domain } )
  6724. {
  6725. $imap->Domain( $acc->{ domain } ) ;
  6726. $mysync->{ debug } and myprint( "Domain: $acc->{ domain }\n" ) ;
  6727. }
  6728. $imap->Authuser( $acc->{ authuser } ) ;
  6729. $imap->Password( $password ) ;
  6730. if ( 'X-MASTERAUTH' eq $acc->{ authmech } )
  6731. {
  6732. xmasterauth( $imap ) ;
  6733. return 1 ;
  6734. }
  6735. if ( defined $acc->{ oauthdirect } )
  6736. {
  6737. $acc->{ authmech } = 'XOAUTH2 direct' ;
  6738. return( oauthdirect( $mysync, $acc, $imap, $host, $user ) ) ;
  6739. }
  6740. if ( defined $acc->{ oauthaccesstoken } )
  6741. {
  6742. $acc->{ authmech } = 'XOAUTH2 accesstoken' ;
  6743. return( oauthaccesstoken( $mysync, $acc, $imap, $host, $user ) ) ;
  6744. }
  6745. if ( $acc->{ proxyauth } ) {
  6746. $imap->Authmechanism(q{}) ;
  6747. $imap->User( $acc->{ authuser } ) ;
  6748. } else {
  6749. $imap->Authmechanism( $acc->{ authmech } ) unless ( $acc->{ authmech } eq 'LOGIN' or $acc->{ authmech } eq 'PREAUTH' ) ;
  6750. }
  6751. $imap->Authcallback(\&xoauth2) if ( 'XOAUTH2' eq $acc->{ authmech } ) ;
  6752. $imap->Authcallback(\&plainauth) if ( ( 'PLAIN' eq $acc->{ authmech } ) or ( 'EXTERNAL' eq $acc->{ authmech } ) ) ;
  6753. unless ( $acc->{ authmech } eq 'PREAUTH' or $imap->login( ) ) {
  6754. my $info = "$acc->{ Side } failure: Error login on [$host] with user [$user] auth" ;
  6755. my $einfo = imap_last_error( $imap ) ;
  6756. my $error = "$info [$acc->{ authmech }]: $einfo\n" ;
  6757. if ( ( $acc->{ authmech } eq 'LOGIN' ) or $imap->IsUnconnected( ) or $acc->{ authuser } ) {
  6758. $acc->{ authuser } ||= "" ;
  6759. myprint( "$acc->{ Side } info: authmech [$acc->{ authmech }] user [$user] authuser [$acc->{ authuser }] IsUnconnected [", $imap->IsUnconnected( ), "]\n" ) ;
  6760. errors_incr( $mysync, $error ) ;
  6761. return ;
  6762. }else{
  6763. errors_incr( $mysync, $error ) ;
  6764. }
  6765. # It is not secure to try plain text LOGIN when another authmech failed
  6766. # but I do it anyway. This behavior is optional as option --notrylogin will skip it.
  6767. if ( $mysync->{ trylogin } )
  6768. {
  6769. 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" ) ;
  6770. $imap->Authmechanism(q{}) ;
  6771. if ( ! $imap->login( ) )
  6772. {
  6773. failure_login( $mysync, $acc, 'LOGIN', $imap, $host, $user ) ;
  6774. return ;
  6775. }
  6776. else
  6777. {
  6778. myprint( "$acc->{ Side }: success login on [$host] with user [$user] auth [LOGIN] after [$acc->{ authmech }] failure\n" ) ;
  6779. }
  6780. }
  6781. else
  6782. {
  6783. 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" ) ;
  6784. return ;
  6785. }
  6786. }
  6787. if ( $acc->{ proxyauth } ) {
  6788. if ( ! $imap->proxyauth( $user ) ) {
  6789. failure_proxyauth( $mysync, $acc, $acc->{ authmech }, $imap, $host, $user ) ;
  6790. return ;
  6791. }
  6792. }
  6793. return 1;
  6794. }
  6795. sub failure_login
  6796. {
  6797. my( $mysync, $acc, $authmech, $imap, $host, $user ) = @ARG ;
  6798. my $info = "$acc->{ Side } failure: Error login on [$host] with user [$user] auth" ;
  6799. my $einfo = imap_last_error( $imap ) ;
  6800. my $error = "$info [$authmech]: $einfo\n" ;
  6801. errors_incr( $mysync, $error ) ;
  6802. return ;
  6803. }
  6804. # failure_login and failure_proxyauth function are similar but
  6805. # variable $error so no factoring
  6806. sub failure_proxyauth
  6807. {
  6808. my( $mysync, $acc, $authmech, $imap, $host, $user ) = @ARG ;
  6809. my $info = "$acc->{ Side } failure: Error login on [$host] with user [$user] auth" ;
  6810. my $einfo = imap_last_error( $imap ) ;
  6811. my $error = "$info [$authmech] using proxy-login as [$acc->{ authuser }]: $einfo\n" ;
  6812. errors_incr( $mysync, $error ) ;
  6813. return ;
  6814. }
  6815. sub oauthdirect
  6816. {
  6817. my( $mysync, $acc, $imap, $host, $user ) = @_ ;
  6818. my $oauthdirect_str ;
  6819. if ( -f -r $acc->{ oauthdirect } )
  6820. {
  6821. $oauthdirect_str = firstline( $acc->{ oauthdirect } ) ;
  6822. }
  6823. else
  6824. {
  6825. $oauthdirect_str = $acc->{ oauthdirect } || 'Please define oauthdirect value' ;
  6826. }
  6827. $imap->Authmechanism( 'XOAUTH2' ) ;
  6828. $imap->Authcallback( sub { return $oauthdirect_str } ) ;
  6829. #if ( $imap->authenticate('XOAUTH2', sub { return $oauthdirect_str } ) )
  6830. if ( $imap->login( ) )
  6831. {
  6832. return 1 ;
  6833. }
  6834. else
  6835. {
  6836. failure_login( $mysync, $acc, $acc->{ authmech }, $imap, $host, $user ) ;
  6837. return ;
  6838. }
  6839. return ;
  6840. }
  6841. sub oauthaccesstoken
  6842. {
  6843. my( $mysync, $acc, $imap, $host, $user ) = @_ ;
  6844. my $oauthaccesstoken_str ;
  6845. if ( -f -r $acc->{ oauthaccesstoken } )
  6846. {
  6847. $oauthaccesstoken_str = firstline( $acc->{ oauthaccesstoken } ) ;
  6848. }
  6849. else
  6850. {
  6851. $oauthaccesstoken_str = $acc->{ oauthaccesstoken } || 'Please define oauthaccesstoken value' ;
  6852. }
  6853. my $oauth_string = "user=" . $user . "\x01auth=Bearer ". $oauthaccesstoken_str . "\x01\x01" ;
  6854. #myprint "oauth_string: $oauth_string\n" ;
  6855. my $oauth_string_base64 = encode_base64( $oauth_string , '' ) ;
  6856. #myprint "oauth_string_base64: $oauth_string_base64\n" ;
  6857. my $oauthdirect_str = $oauth_string_base64 ;
  6858. $imap->Authmechanism( 'XOAUTH2' ) ;
  6859. $imap->Authcallback( sub { return $oauthdirect_str } ) ;
  6860. #if ( $imap->authenticate('XOAUTH2', sub { return $oauthdirect_str } ) )
  6861. if ( $imap->login( ) )
  6862. {
  6863. return 1 ;
  6864. }
  6865. else
  6866. {
  6867. failure_login( $mysync, $acc, $acc->{ authmech }, $imap, $host, $user ) ;
  6868. return ;
  6869. }
  6870. return ;
  6871. }
  6872. sub check_capability
  6873. {
  6874. my( $imap, $authmech, $Side ) = @_ ;
  6875. if ( $imap->has_capability( "AUTH=$authmech" )
  6876. or $imap->has_capability( $authmech ) )
  6877. {
  6878. myprintf("%s: %s says it has CAPABILITY for AUTHENTICATE %s\n",
  6879. $Side, $imap->Server, $authmech) ;
  6880. return ;
  6881. }
  6882. if ( $authmech eq 'LOGIN' )
  6883. {
  6884. # Well, the warning is so common and useless that I prefer to remove it
  6885. # No more "... says it has NO CAPABILITY for AUTHENTICATE LOGIN"
  6886. return ;
  6887. }
  6888. myprintf( "%s: %s says it has NO CAPABILITY for AUTHENTICATE %s\n",
  6889. $Side, $imap->Server, $authmech ) ;
  6890. if ( $authmech eq 'PLAIN' )
  6891. {
  6892. myprint( "$Side: frequently PLAIN is only supported with SSL, try --ssl or --tls options\n" ) ;
  6893. }
  6894. return ;
  6895. }
  6896. sub set_ssl
  6897. {
  6898. my ( $imap, $acc ) = @_ ;
  6899. # SSL_version can be
  6900. # SSLv3 SSLv2 SSLv23 SSLv23:!SSLv2 (last one is the default in IO-Socket-SSL-1.953)
  6901. #
  6902. my $sslargs_hash = $acc->{sslargs} ;
  6903. my $sslargs_default = {
  6904. SSL_verify_mode => $SSL_VERIFY_POLICY,
  6905. SSL_verifycn_scheme => 'imap',
  6906. SSL_cipher_list => 'DEFAULT:!DH',
  6907. } ;
  6908. # initiate with default values
  6909. my %sslargs_mix = %{ $sslargs_default } ;
  6910. # now override with passed values
  6911. @sslargs_mix{ keys %{ $sslargs_hash } } = values %{ $sslargs_hash } ;
  6912. # remove keys with undef values
  6913. foreach my $key ( keys %sslargs_mix ) {
  6914. delete $sslargs_mix{ $key } if ( not defined $sslargs_mix{ $key } ) ;
  6915. }
  6916. # back to an ARRAY
  6917. my @sslargs_mix = %sslargs_mix ;
  6918. #myprint( Data::Dumper->Dump( [ $sslargs_hash, $sslargs_default, \%sslargs_mix, \@sslargs_mix ] ) ) ;
  6919. $imap->Ssl( \@sslargs_mix ) ;
  6920. return ;
  6921. }
  6922. sub set_tls
  6923. {
  6924. my ( $imap, $acc ) = @_ ;
  6925. my $sslargs_hash = $acc->{sslargs} ;
  6926. my $sslargs_default = {
  6927. SSL_verify_mode => $SSL_VERIFY_POLICY,
  6928. SSL_cipher_list => 'DEFAULT:!DH',
  6929. } ;
  6930. #myprint( Data::Dumper->Dump( [ $acc, $sslargs_hash, $sslargs_default ] ) ) ;
  6931. # initiate with default values
  6932. my %sslargs_mix = %{ $sslargs_default } ;
  6933. # now override with passed values
  6934. @sslargs_mix{ keys %{ $sslargs_hash } } = values %{ $sslargs_hash } ;
  6935. # remove keys with undef values
  6936. foreach my $key ( keys %sslargs_mix ) {
  6937. delete $sslargs_mix{ $key } if ( not defined $sslargs_mix{ $key } ) ;
  6938. }
  6939. # back to an ARRAY
  6940. my @sslargs_mix = %sslargs_mix ;
  6941. $imap->Starttls( \@sslargs_mix ) ;
  6942. return ;
  6943. }
  6944. sub plainauth
  6945. {
  6946. my $code = shift;
  6947. my $imap = shift;
  6948. my $string = mysprintf("%s\x00%s\x00%s", $imap->User,
  6949. defined $imap->Authuser ? $imap->Authuser : "", $imap->Password);
  6950. return encode_base64("$string", q{});
  6951. }
  6952. # Copy from https://github.com/imapsync/imapsync/pull/25/files
  6953. # Changes "use" pragmas to "require".
  6954. # The openssl system call shall be replaced by pure Perl and
  6955. # https://metacpan.org/pod/Crypt::OpenSSL::PKCS12
  6956. # Now the Joaquin Lopez code:
  6957. #
  6958. # Used this as an example: https://gist.github.com/gsainio/6322375
  6959. #
  6960. # And this as a reference: https://developers.google.com/accounts/docs/OAuth2ServiceAccount
  6961. # (note there is an http/rest tab, where the real info is hidden away... went on a witch hunt
  6962. # until I noticed that...)
  6963. #
  6964. # This is targeted at gmail to maintain compatibility after google's oauth1 service is deactivated
  6965. # on May 5th, 2015: https://developers.google.com/gmail/oauth_protocol
  6966. # If there are other oauth2 implementations out there, this would need to be modified to be
  6967. # compatible
  6968. #
  6969. # This is a good guide on setting up the google api/apps side of the equation:
  6970. # http://www.limilabs.com/blog/oauth2-gmail-imap-service-account
  6971. #
  6972. # 2016/05/27: Updated to support oauth/key data in the .json files Google now defaults to
  6973. # when creating gmail service accounts. They're easier to work with since they neither
  6974. # requiring decrypting nor specifying the oauth2 client id separately.
  6975. #
  6976. # If the password arg ends in .json, it will assume this new json method, otherwise it
  6977. # will fallback to the "oauth client id;.p12" format it was previously using.
  6978. sub xoauth2
  6979. {
  6980. require JSON::WebToken ;
  6981. require LWP::UserAgent ;
  6982. require HTML::Entities ;
  6983. require JSON ;
  6984. require JSON::WebToken::Crypt::RSA ;
  6985. require Crypt::OpenSSL::PKCS12 ;
  6986. require Crypt::OpenSSL::RSA ;
  6987. require Encode::Byte ;
  6988. require IO::Socket::SSL ;
  6989. my $code = shift;
  6990. my $imap = shift;
  6991. my ($iss,$key);
  6992. if( $imap->Password =~ /^(.*\.json)$/x )
  6993. {
  6994. my $json = JSON->new( ) ;
  6995. my $filename = $1;
  6996. $sync->{ debug } and myprint( "XOAUTH2 json file: $filename\n" ) ;
  6997. my $FILE ;
  6998. if ( ! open( $FILE, '<', $filename ) )
  6999. {
  7000. $sync->{nb_errors}++ ;
  7001. exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE,
  7002. "error [$filename]: $OS_ERROR\n"
  7003. ) ;
  7004. }
  7005. my $jsonfile = $json->decode( join q{}, <$FILE> ) ;
  7006. close $FILE ;
  7007. $iss = $jsonfile->{client_id};
  7008. $key = $jsonfile->{private_key};
  7009. $sync->{ debug } and myprint( "Service account: $iss\n");
  7010. $sync->{ debug } and myprint( "Private key:\n$key\n");
  7011. }
  7012. else
  7013. {
  7014. # Get iss (service account address), keyfile name, and keypassword if necessary
  7015. ( $iss, my $keyfile, my $keypass ) = $imap->Password =~ /([\-\d\w\@\.]+);([a-zA-Z0-9 \_\-\.\/]+);?(.*)?/x ;
  7016. # Assume key password is google default if not provided
  7017. $keypass = 'notasecret' if not $keypass;
  7018. $sync->{ debug } and myprint( "Service account: $iss\nKey file: $keyfile\nKey password: $keypass\n");
  7019. # Get private key from p12 file
  7020. my $pkcs12 = Crypt::OpenSSL::PKCS12->new_from_file($keyfile);
  7021. $key = $pkcs12->private_key($keypass);
  7022. $sync->{ debug } and myprint( "Private key:\n$key\n");
  7023. }
  7024. # Create jwt of oauth2 request
  7025. my $time = time ;
  7026. my $jwt = JSON::WebToken->encode( {
  7027. 'iss' => $iss, # service account
  7028. 'scope' => 'https://mail.google.com/',
  7029. 'aud' => 'https://www.googleapis.com/oauth2/v3/token',
  7030. 'exp' => $time + $DEFAULT_EXPIRATION_TIME_OAUTH2_PK12,
  7031. 'iat' => $time,
  7032. 'prn' => $imap->User # user to auth as
  7033. },
  7034. $key, 'RS256', {'typ' => 'JWT'} ); # Crypt::OpenSSL::RSA needed here.
  7035. # Post oauth2 request
  7036. my $ua = LWP::UserAgent->new( ) ;
  7037. $ua->env_proxy( ) ;
  7038. my $response = $ua->post('https://www.googleapis.com/oauth2/v3/token',
  7039. { grant_type => HTML::Entities::encode_entities('urn:ietf:params:oauth:grant-type:jwt-bearer'),
  7040. assertion => $jwt } ) ;
  7041. unless( $response->is_success( ) ) {
  7042. $sync->{nb_errors}++ ;
  7043. exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE,
  7044. $response->code, "\n", $response->content, "\n"
  7045. ) ;
  7046. }else{
  7047. $sync->{ debug } and myprint( $response->content ) ;
  7048. }
  7049. # access_token in response is what we need
  7050. my $data = JSON::decode_json( $response->content ) ;
  7051. # format as oauth2 auth data
  7052. my $xoauth2_string = encode_base64( 'user=' . $imap->User . "\1auth=Bearer " . $data->{access_token} . "\1\1", q{} ) ;
  7053. $sync->{ debug } and myprint( "XOAUTH2 String: $xoauth2_string\n");
  7054. return($xoauth2_string);
  7055. }
  7056. sub xmasterauth
  7057. {
  7058. # This is Kerio auth admin
  7059. # This code comes from
  7060. # https://github.com/imapsync/imapsync/pull/53/files
  7061. my $imap = shift @ARG ;
  7062. my $user = $imap->User( ) ;
  7063. my $password = $imap->Password( ) ;
  7064. my $authmech = 'X-MASTERAUTH' ;
  7065. my @challenge = $imap->tag_and_run( $authmech, "+" ) ;
  7066. if ( not defined $challenge[0] )
  7067. {
  7068. $sync->{nb_errors}++ ;
  7069. exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE,
  7070. "Failure authenticate with $authmech: ",
  7071. $imap->LastError, "\n"
  7072. ) ;
  7073. return ; # hahaha!
  7074. }
  7075. $sync->{ debug } and myprint( "X-MASTERAUTH challenge: [@challenge]\n" ) ;
  7076. $challenge[1] =~ s/^\+ |^\s+|\s+$//g ;
  7077. if ( ! $imap->_imap_command( { addcrlf => 1, addtag => 0, tag => $imap->Count }, md5_hex( $challenge[1] . $password ) ) )
  7078. {
  7079. $sync->{nb_errors}++ ;
  7080. exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE,
  7081. "Failure authenticate with $authmech: ",
  7082. $imap->LastError, "\n"
  7083. ) ;
  7084. }
  7085. if ( ! $imap->tag_and_run( 'X-SETUSER ' . $user ) )
  7086. {
  7087. $sync->{nb_errors}++ ;
  7088. exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE,
  7089. "Failure authenticate with $authmech: ",
  7090. "X-SETUSER ", $imap->LastError, "\n"
  7091. ) ;
  7092. }
  7093. $imap->State( Mail::IMAPClient::Authenticated ) ;
  7094. # I comment this state because "Selected" state is usually done by SELECT or EXAMINE imap commands
  7095. # $imap->State( Mail::IMAPClient::Selected ) ;
  7096. return ;
  7097. }
  7098. sub keepalive1
  7099. {
  7100. my $mysync = shift @ARG ;
  7101. $mysync->{ acc1 }->{ keepalive } = defined $mysync->{ acc1 }->{ keepalive } ? $mysync->{ acc1 }->{ keepalive } : 1 ;
  7102. if ( $mysync->{ acc1 }->{ keepalive } )
  7103. {
  7104. myprint( "Host1: imap connection keepalive is on on host1. Use --nokeepalive1 to disable it.\n" ) ;
  7105. }
  7106. else
  7107. {
  7108. myprint( "Host1: imap connection keepalive is off on host1. Use --keepalive1 to enable it.\n" ) ;
  7109. }
  7110. return ;
  7111. }
  7112. sub keepalive2
  7113. {
  7114. my $mysync = shift @ARG ;
  7115. $mysync->{ acc2 }->{ keepalive } = defined $mysync->{ acc2 }->{ keepalive } ? $mysync->{ acc2 }->{ keepalive } : 1 ;
  7116. if ( $mysync->{ acc2 }->{ keepalive } )
  7117. {
  7118. myprint( "Host2: imap connection keepalive is on on host2. Use --nokeepalive2 to disable it.\n" ) ;
  7119. }
  7120. else
  7121. {
  7122. myprint( "Host2: imap connection keepalive is off on host2. Use --keepalive2 to enable it.\n" ) ;
  7123. }
  7124. return ;
  7125. }
  7126. sub banner_imapsync
  7127. {
  7128. my $mysync = shift @ARG ;
  7129. my @argv = @ARG ;
  7130. my $banner_imapsync = join q{},
  7131. q{$RCSfile: imapsync,v $ },
  7132. q{$Revision: 2.229 $ },
  7133. q{$Date: 2022/09/14 18:08:24 $ },
  7134. "\n",
  7135. "Command line used, run by $EXECUTABLE_NAME:\n",
  7136. "$PROGRAM_NAME ", command_line_nopassword( $mysync, @argv ), "\n" ;
  7137. return( $banner_imapsync ) ;
  7138. }
  7139. sub tests_do_valid_directory
  7140. {
  7141. note( 'Entering tests_do_valid_directory()' ) ;
  7142. is( 1, do_valid_directory( '.'), 'do_valid_directory: . good' ) ;
  7143. is( 1, do_valid_directory( './W/tmp/tests/valid/sub'), 'do_valid_directory: ./W/tmp/tests/valid/sub good' ) ;
  7144. Readonly my $NB_UNIX_tests_do_valid_directory_non_root => 2 ;
  7145. diag( "OSNAME=$OSNAME EFFECTIVE_USER_ID=$EFFECTIVE_USER_ID" ) ;
  7146. SKIP: {
  7147. skip( 'Tests only for non roor user', $NB_UNIX_tests_do_valid_directory_non_root ) if ( '0' eq $EFFECTIVE_USER_ID ) ;
  7148. diag( 'The "Error / is not writable" is on purpose' ) ;
  7149. ok( 0 == do_valid_directory( '/'), 'do_valid_directory: / bad' ) ;
  7150. diag( 'The "Error permission denied" on /noway is on purpose' ) ;
  7151. ok( 0 == do_valid_directory( '/noway'), 'do_valid_directory: /noway bad' ) ;
  7152. }
  7153. note( 'Leaving tests_do_valid_directory()' ) ;
  7154. return ;
  7155. }
  7156. sub do_valid_directory
  7157. {
  7158. my $dir = shift @ARG ;
  7159. # all good => return ok.
  7160. return( 1 ) if ( -d $dir and -r _ and -w _ ) ;
  7161. # exist but bad
  7162. if ( -e $dir and not -d _ ) {
  7163. myprint( "Error: $dir exists but is not a directory\n" ) ;
  7164. return( 0 ) ;
  7165. }
  7166. if ( -e $dir and not -w _ ) {
  7167. my $sb = stat $dir ;
  7168. myprintf( "Error: directory %s is not writable for user %s, permissions are %04o and owner is %s ( uid %s )\n",
  7169. $dir, getpwuid_any_os( $EFFECTIVE_USER_ID ), ($sb->mode & oct($PERMISSION_FILTER) ), getpwuid_any_os( $sb->uid ), $sb->uid( ) ) ;
  7170. return( 0 ) ;
  7171. }
  7172. # Trying to create it
  7173. myprint( "Creating directory $dir (current directory is " . getcwd( ) . ")\n" ) ;
  7174. if ( ! eval { mkpath( $dir ) } ) {
  7175. myprint( "$EVAL_ERROR" ) if ( $EVAL_ERROR ) ;
  7176. }
  7177. return( 1 ) if ( -d $dir and -r _ and -w _ ) ;
  7178. return( 0 ) ;
  7179. }
  7180. sub tests_match_a_pid_number
  7181. {
  7182. note( 'Entering tests_match_a_pid_number()' ) ;
  7183. is( undef, match_a_pid_number( ), 'match_a_pid_number: no args => undef' ) ;
  7184. is( undef, match_a_pid_number( q{} ), 'match_a_pid_number: "" => undef' ) ;
  7185. is( undef, match_a_pid_number( 'lalala' ), 'match_a_pid_number: lalala => undef' ) ;
  7186. is( 1, match_a_pid_number( 1 ), 'match_a_pid_number: 1 => 1' ) ;
  7187. is( 1, match_a_pid_number( 123 ), 'match_a_pid_number: 123 => 1' ) ;
  7188. is( 1, match_a_pid_number( -123 ), 'match_a_pid_number: -123 => 1' ) ;
  7189. is( 1, match_a_pid_number( '123' ), 'match_a_pid_number: "123" => 1' ) ;
  7190. is( 1, match_a_pid_number( '-123' ), 'match_a_pid_number: "-123" => 1' ) ;
  7191. is( undef, match_a_pid_number( 'a123' ), 'match_a_pid_number: a123 => undef' ) ;
  7192. is( undef, match_a_pid_number( '-a123' ), 'match_a_pid_number: -a123 => undef' ) ;
  7193. is( 1, match_a_pid_number( 99999 ), 'match_a_pid_number: 99999 => 1' ) ;
  7194. is( 1, match_a_pid_number( -99999 ), 'match_a_pid_number: -99999 => 1' ) ;
  7195. is( undef, match_a_pid_number( 0 ), 'match_a_pid_number: 0 => undef' ) ;
  7196. is( 1, match_a_pid_number( 100000 ), 'match_a_pid_number: 100000 => 1' ) ;
  7197. is( 1, match_a_pid_number( 123456 ), 'match_a_pid_number: 123456 => 1' ) ;
  7198. is( undef, match_a_pid_number( '-0' ), 'match_a_pid_number: "-0" => undef' ) ;
  7199. is( 1, match_a_pid_number( -100000 ), 'match_a_pid_number: -100000 => 1' ) ;
  7200. is( 1, match_a_pid_number( -123456 ), 'match_a_pid_number: -123456 => 1' ) ;
  7201. is( 1, match_a_pid_number( 2**22 ), 'match_a_pid_number: 2**22 => 1' ) ;
  7202. is( undef, match_a_pid_number( 2**22 + 1 ), 'match_a_pid_number: 2**22 + 1 => undef' ) ;
  7203. is( undef, match_a_pid_number( 4194304 + 1 ), 'match_a_pid_number: 2**22 + 1 = 4194305 => undef' ) ;
  7204. note( 'Leaving tests_match_a_pid_number()' ) ;
  7205. return ;
  7206. }
  7207. sub match_a_pid_number
  7208. {
  7209. my $pid = shift @ARG ;
  7210. if ( ! defined $pid ) { return ; }
  7211. #print "$pid\n" ;
  7212. if ( ! match( $pid, '^-?\d+$' ) ) { return ; }
  7213. #print "$pid\n" ;
  7214. # can be negative on Windows
  7215. #if ( 0 > $pid ) { return ; }
  7216. #if ( 65535 < $pid ) { return ; }
  7217. if ( 2**22 < abs( $pid ) ) { return ; }
  7218. if ( 0 == abs( $pid ) ) { return ; }
  7219. return 1 ;
  7220. }
  7221. sub tests_remove_pidfile_not_running
  7222. {
  7223. note( 'Entering tests_remove_pidfile_not_running()' ) ;
  7224. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'remove_pidfile_not_running: mkpath W/tmp/tests/' ) ;
  7225. is( undef, remove_pidfile_not_running( ), 'remove_pidfile_not_running: no args => undef' ) ;
  7226. is( undef, remove_pidfile_not_running( './W' ), 'remove_pidfile_not_running: a dir => undef' ) ;
  7227. is( undef, remove_pidfile_not_running( 'noexists' ), 'remove_pidfile_not_running: noexists => undef' ) ;
  7228. is( 1, touch( 'W/tmp/tests/empty.pid' ), 'remove_pidfile_not_running: prepa empty W/tmp/tests/empty.pid' ) ;
  7229. is( undef, remove_pidfile_not_running( 'W/tmp/tests/empty.pid' ), 'remove_pidfile_not_running: W/tmp/tests/empty.pid => undef' ) ;
  7230. is( 'lalala', string_to_file( 'lalala', 'W/tmp/tests/lalala.pid' ), 'remove_pidfile_not_running: prepa W/tmp/tests/lalala.pid' ) ;
  7231. is( undef, remove_pidfile_not_running( 'W/tmp/tests/lalala.pid' ), 'remove_pidfile_not_running: W/tmp/tests/lalala.pid => undef' ) ;
  7232. is( '55555', string_to_file( '55555', 'W/tmp/tests/notrunning.pid' ), 'remove_pidfile_not_running: prepa W/tmp/tests/notrunning.pid' ) ;
  7233. is( 1, remove_pidfile_not_running( 'W/tmp/tests/notrunning.pid' ), 'remove_pidfile_not_running: W/tmp/tests/notrunning.pid => 1' ) ;
  7234. is( $PROCESS_ID, string_to_file( $PROCESS_ID, 'W/tmp/tests/running.pid' ), 'remove_pidfile_not_running: prepa W/tmp/tests/running.pid' ) ;
  7235. is( undef, remove_pidfile_not_running( 'W/tmp/tests/running.pid' ), 'remove_pidfile_not_running: W/tmp/tests/running.pid => undef' ) ;
  7236. note( 'Leaving tests_remove_pidfile_not_running()' ) ;
  7237. return ;
  7238. }
  7239. sub remove_pidfile_not_running
  7240. {
  7241. #
  7242. my $pid_filename = shift @ARG ;
  7243. #myprint( "In remove_pidfile_not_running $pid_filename\n" ) ;
  7244. if ( ! $pid_filename ) { myprint( "No variable pid_filename\n" ) ; return } ;
  7245. if ( ! -e $pid_filename )
  7246. {
  7247. myprint( "File $pid_filename does not exist\n" ) ;
  7248. return ;
  7249. }
  7250. #myprint( "Still In remove_pidfile_not_running $pid_filename\n" ) ;
  7251. if ( ! -f $pid_filename ) { myprint( "File $pid_filename is not a file\n" ) ; return } ;
  7252. my $pid = firstline( $pid_filename ) ;
  7253. if ( ! match_a_pid_number( $pid ) ) { myprint( "In remove_pidfile_not_running: pid $pid in $pid_filename is not a pid number\n" ) ; return } ;
  7254. # can't kill myself => do nothing
  7255. if ( ! kill 'ZERO', $PROCESS_ID ) { myprint( "Can not kill ZERO myself $PROCESS_ID\n" ) ; return } ;
  7256. # can't kill ZERO the pid => it is gone or own by another user => remove pidfile
  7257. if ( ! kill 'ZERO', $pid ) {
  7258. myprint( "Removing old $pid_filename since its PID $pid is not running anymore (oo-killed?)\n" ) ;
  7259. if ( unlink $pid_filename ) {
  7260. myprint( "Removed old $pid_filename\n" ) ;
  7261. return 1 ;
  7262. }else{
  7263. myprint( "Could not remove old $pid_filename because $!\n" ) ;
  7264. return ;
  7265. }
  7266. }
  7267. myprint( "Another imapsync process $pid is running as says pidfile $pid_filename\n" ) ;
  7268. return ;
  7269. }
  7270. sub tests_tail
  7271. {
  7272. note( 'Entering tests_tail()' ) ;
  7273. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'tail: mkpath W/tmp/tests/' ) ;
  7274. ok( ( ! -e 'W/tmp/tests/tail.pid' || unlink 'W/tmp/tests/tail.pid' ), 'tail: unlink W/tmp/tests/tail.pid' ) ;
  7275. ok( ( ! -e 'W/tmp/tests/tail.txt' || unlink 'W/tmp/tests/tail.txt' ), 'tail: unlink W/tmp/tests/tail.txt' ) ;
  7276. is( undef, tail( ), 'tail: no args => undef' ) ;
  7277. my $mysync ;
  7278. is( undef, tail( $mysync ), 'tail: no pidfile => undef' ) ;
  7279. $mysync->{pidfile} = 'W/tmp/tests/tail.pid' ;
  7280. is( undef, tail( $mysync ), 'tail: no pidfilelocking => undef' ) ;
  7281. $mysync->{pidfilelocking} = 1 ;
  7282. is( undef, tail( $mysync ), 'tail: pidfile no exists => undef' ) ;
  7283. my $pidandlog = "33333\nW/tmp/tests/tail.txt\n" ;
  7284. is( $pidandlog, string_to_file( $pidandlog, $mysync->{pidfile} ), 'tail: put pid 33333 and tail.txt in pidfile' ) ;
  7285. is( undef, tail( $mysync ), 'tail: logfile to tail no exists => undef' ) ;
  7286. my $tailcontent = "L1\nL2\nL3\nL4\nL5\n" ;
  7287. is( $tailcontent, string_to_file( $tailcontent, 'W/tmp/tests/tail.txt' ),
  7288. 'tail: put L1\nL2\nL3\nL4\nL5\n in W/tmp/tests/tail.txt' ) ;
  7289. is( undef, tail( $mysync ), 'tail: fake pid in pidfile + tail off => 1' ) ;
  7290. $mysync->{ tail } = 1 ;
  7291. is( 1, tail( $mysync ), 'tail: fake pid in pidfile + tail on=> 1' ) ;
  7292. # put my own pid, won't do tail
  7293. $pidandlog = "$PROCESS_ID\nW/tmp/tests/tail.txt\n" ;
  7294. is( $pidandlog, string_to_file( $pidandlog, $mysync->{pidfile} ), 'tail: put my own PID in pidfile' ) ;
  7295. is( undef, tail( $mysync ), 'tail: my own pid in pidfile => undef' ) ;
  7296. note( 'Leaving tests_tail()' ) ;
  7297. return ;
  7298. }
  7299. sub tail
  7300. {
  7301. # return undef on failures
  7302. # return 1 on success
  7303. my $mysync = shift @ARG ;
  7304. # no tail when aborting!
  7305. if ( $mysync->{ abort } ) { return ; }
  7306. my $pidfile = $mysync->{pidfile} ;
  7307. my $lock = $mysync->{pidfilelocking} ;
  7308. my $tail = $mysync->{tail} ;
  7309. if ( ! $pidfile ) { return ; }
  7310. if ( ! $lock ) { return ; }
  7311. if ( ! $tail ) { return ; }
  7312. if ( ! -e $pidfile ) { return ; }
  7313. my $pidtotail = firstline( $pidfile ) ;
  7314. if ( ! $pidtotail ) { return ; }
  7315. # It should not happen but who knows...
  7316. if ( $pidtotail eq $PROCESS_ID ) { return ; }
  7317. my $filetotail = secondline( $pidfile ) ;
  7318. if ( ! $filetotail ) { return ; }
  7319. if ( ! -r $filetotail )
  7320. {
  7321. #myprint( "Error: can not read $filetotail\n" ) ;
  7322. return ;
  7323. }
  7324. myprint( "Doing a tail -f on $filetotail for processus pid $pidtotail until it is finished.\n" ) ;
  7325. my $file = File::Tail->new(
  7326. name => $filetotail,
  7327. nowait => 1,
  7328. interval => 1,
  7329. tail => 1,
  7330. adjustafter => 2
  7331. );
  7332. my $moretimes = 200 ;
  7333. # print one line at least
  7334. my $line = $file->read ;
  7335. myprint( $line ) ;
  7336. while ( isrunning( $pidtotail, \$moretimes ) and defined( $line = $file->read ) )
  7337. {
  7338. myprint( $line );
  7339. sleep( 0.02 ) ;
  7340. }
  7341. return 1 ;
  7342. }
  7343. sub isrunning
  7344. {
  7345. my $pidtocheck = shift @ARG ;
  7346. my $moretimes_ref = shift @ARG ;
  7347. if ( kill 'ZERO', $pidtocheck )
  7348. {
  7349. #myprint( "$pidtocheck running\n" ) ;
  7350. return 1 ;
  7351. }
  7352. elsif ( $$moretimes_ref >= 0 )
  7353. {
  7354. # continue to consider it running
  7355. $$moretimes_ref-- ;
  7356. return 1 ;
  7357. }
  7358. else
  7359. {
  7360. myprint( "Tailed processus $pidtocheck ended\n" ) ;
  7361. return ;
  7362. }
  7363. }
  7364. sub tests_write_pidfile
  7365. {
  7366. note( 'Entering tests_write_pidfile()' ) ;
  7367. my $mysync ;
  7368. is( 1, write_pidfile( ), 'write_pidfile: no args => 1' ) ;
  7369. # no pidfile => ok
  7370. $mysync->{pidfile} = q{} ;
  7371. is( 1, write_pidfile( $mysync ), 'write_pidfile: no pidfile => undef' ) ;
  7372. # The pidfile path is bad => failure
  7373. $mysync->{pidfile} = '/no/no/no.pid' ;
  7374. is( undef, write_pidfile( $mysync ), 'write_pidfile: no permission for /no/no/no.pid, no lock => undef' ) ;
  7375. $mysync->{pidfilelocking} = 1 ;
  7376. is( undef, write_pidfile( $mysync ), 'write_pidfile: no permission for /no/no/no.pid + lock => undef' ) ;
  7377. $mysync->{pidfile} = 'W/tmp/tests/test.pid' ;
  7378. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'write_pidfile: mkpath W/tmp/tests/' ) ;
  7379. is( 1, touch( $mysync->{pidfile} ), 'write_pidfile: lock prepa' ) ;
  7380. $mysync->{pidfilelocking} = 0 ;
  7381. is( 1, write_pidfile( $mysync ), 'write_pidfile: W/tmp/tests/test.pid + no lock => 1' ) ;
  7382. is( $PROCESS_ID, firstline( 'W/tmp/tests/test.pid' ), "write_pidfile: W/tmp/tests/test.pid contains $PROCESS_ID" ) ;
  7383. is( q{}, secondline( 'W/tmp/tests/test.pid' ), "write_pidfile: W/tmp/tests/test.pid contains no second line" ) ;
  7384. $mysync->{pidfilelocking} = 1 ;
  7385. is( undef, write_pidfile( $mysync ), 'write_pidfile: W/tmp/tests/test.pid + lock => undef' ) ;
  7386. $mysync->{pidfilelocking} = 0 ;
  7387. $mysync->{ logfile } = 'rrrr.txt' ;
  7388. is( 1, write_pidfile( $mysync ), 'write_pidfile: W/tmp/tests/test.pid + no lock + logfile => 1' ) ;
  7389. is( $PROCESS_ID, firstline( 'W/tmp/tests/test.pid' ), "write_pidfile: + no lock + logfile W/tmp/tests/test.pid contains $PROCESS_ID" ) ;
  7390. is( q{rrrr.txt}, secondline( 'W/tmp/tests/test.pid' ), "write_pidfile: + no lock + logfile W/tmp/tests/test.pid contains rrrr.txt" ) ;
  7391. note( 'Leaving tests_write_pidfile()' ) ;
  7392. return ;
  7393. }
  7394. sub write_pidfile
  7395. {
  7396. # returns undef if something is considered fatal
  7397. # returns 1 otherwise
  7398. #myprint( "In write_pidfile\n" ) ;
  7399. if ( ! @ARG ) { return 1 ; }
  7400. my $mysync = shift @ARG ;
  7401. # Do not write the pid file if the current process goal is to abort the process designed by the pid file
  7402. if ( $mysync->{ abort } ) { return 1 ; }
  7403. #
  7404. my $pid_filename = $mysync->{ pidfile } ;
  7405. my $lock = $mysync->{ pidfilelocking } ;
  7406. if ( ! $pid_filename )
  7407. {
  7408. myprint( "PID file is unset ( to set it, use --pidfile filepath ; to avoid it use --pidfile \"\" )\n" ) ;
  7409. return( 1 ) ;
  7410. }
  7411. myprint( "PID file is $pid_filename ( to change it, use --pidfile filepath ; to avoid it use --pidfile \"\" )\n" ) ;
  7412. if ( -e $pid_filename and $lock ) {
  7413. myprint( "$pid_filename already exists, another imapsync may be curently running. Aborting imapsync.\n" ) ;
  7414. return ;
  7415. }
  7416. if ( -e $pid_filename ) {
  7417. myprint( "$pid_filename already exists, overwriting it ( use --pidfilelocking to avoid concurrent runs )\n" ) ;
  7418. }
  7419. my $pid_string = "$PROCESS_ID\n" ;
  7420. my $pid_message = "Writing my PID $PROCESS_ID in $pid_filename\n" ;
  7421. if ( $mysync->{ logfile } )
  7422. {
  7423. $pid_string .= "$mysync->{ logfile }\n" ;
  7424. $pid_message .= "Writing also my logfile name in $pid_filename : $mysync->{ logfile }\n" ;
  7425. }
  7426. if ( open my $FILE_HANDLE, '>', $pid_filename ) {
  7427. myprint( $pid_message ) ;
  7428. print $FILE_HANDLE $pid_string ;
  7429. close $FILE_HANDLE ;
  7430. return( 1 ) ;
  7431. }
  7432. else
  7433. {
  7434. myprint( "Could not open $pid_filename for writing. Check permissions or disk space: $OS_ERROR\n" ) ;
  7435. return ;
  7436. }
  7437. }
  7438. sub fix_Inbox_INBOX_mapping
  7439. {
  7440. my( $h1_all, $h2_all ) = @_ ;
  7441. my $regex = q{} ;
  7442. SWITCH: {
  7443. if ( exists $h1_all->{INBOX} and exists $h2_all->{INBOX} ) { $regex = q{} ; last SWITCH ; } ;
  7444. if ( exists $h1_all->{Inbox} and exists $h2_all->{Inbox} ) { $regex = q{} ; last SWITCH ; } ;
  7445. if ( exists $h1_all->{INBOX} and exists $h2_all->{Inbox} ) { $regex = q{s/^INBOX$/Inbox/x} ; last SWITCH ; } ;
  7446. if ( exists $h1_all->{Inbox} and exists $h2_all->{INBOX} ) { $regex = q{s/^Inbox$/INBOX/x} ; last SWITCH ; } ;
  7447. } ;
  7448. return( $regex ) ;
  7449. }
  7450. sub tests_fix_Inbox_INBOX_mapping
  7451. {
  7452. note( 'Entering tests_fix_Inbox_INBOX_mapping()' ) ;
  7453. my( $h1_all, $h2_all ) ;
  7454. $h1_all = { 'INBOX' => q{} } ;
  7455. $h2_all = { 'INBOX' => q{} } ;
  7456. ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX INBOX' ) ;
  7457. $h1_all = { 'Inbox' => q{} } ;
  7458. $h2_all = { 'Inbox' => q{} } ;
  7459. ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: Inbox Inbox' ) ;
  7460. $h1_all = { 'INBOX' => q{} } ;
  7461. $h2_all = { 'Inbox' => q{} } ;
  7462. ok( q{s/^INBOX$/Inbox/x} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX Inbox' ) ;
  7463. $h1_all = { 'Inbox' => q{} } ;
  7464. $h2_all = { 'INBOX' => q{} } ;
  7465. ok( q{s/^Inbox$/INBOX/x} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: Inbox INBOX' ) ;
  7466. $h1_all = { 'INBOX' => q{} } ;
  7467. $h2_all = { 'rrrrr' => q{} } ;
  7468. ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX rrrrrr' ) ;
  7469. $h1_all = { 'rrrrr' => q{} } ;
  7470. $h2_all = { 'Inbox' => q{} } ;
  7471. ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: rrrrr Inbox' ) ;
  7472. note( 'Leaving tests_fix_Inbox_INBOX_mapping()' ) ;
  7473. return ;
  7474. }
  7475. sub jux_utf8_list
  7476. {
  7477. my @s_inp = @_ ;
  7478. my $s_out = q{} ;
  7479. foreach my $s ( @s_inp ) {
  7480. $s_out .= jux_utf8( $s ) . "\n" ;
  7481. }
  7482. return( $s_out ) ;
  7483. }
  7484. sub tests_jux_utf8_list
  7485. {
  7486. note( 'Entering tests_jux_utf8_list()' ) ;
  7487. use utf8 ;
  7488. is( q{}, jux_utf8_list( ), 'jux_utf8_list: void' ) ;
  7489. is( "[]\n", jux_utf8_list( q{} ), 'jux_utf8_list: empty string' ) ;
  7490. is( "[INBOX]\n", jux_utf8_list( 'INBOX' ), 'jux_utf8_list: INBOX' ) ;
  7491. is( "[&ANY-] = [Ö]\n", jux_utf8_list( '&ANY-' ), 'jux_utf8_list: [&ANY-] = [Ö]' ) ;
  7492. note( 'Leaving tests_jux_utf8_list()' ) ;
  7493. return( 0 ) ;
  7494. }
  7495. # editing utf8 can be tricky without an utf8 editor
  7496. sub tests_jux_utf8_old
  7497. {
  7498. note( 'Entering tests_jux_utf8_old()' ) ;
  7499. no utf8 ;
  7500. is( '[]', jux_utf8_old( q{} ), 'jux_utf8_old: void => []' ) ;
  7501. is( '[INBOX]', jux_utf8_old( 'INBOX'), 'jux_utf8_old: INBOX => [INBOX]' ) ;
  7502. is( '[&ZTZO9nux-] = [收件箱]', jux_utf8_old( '&ZTZO9nux-'), 'jux_utf8_old: => [&ZTZO9nux-] = [收件箱]' ) ;
  7503. is( '[&ANY-] = [Ö]', jux_utf8_old( '&ANY-'), 'jux_utf8_old: &ANY- => [&ANY-] = [Ö]' ) ;
  7504. # +BD8EQAQ1BDQEOwQ+BDM- SHOULD stay as is!
  7505. is( '[+BD8EQAQ1BDQEOwQ+BDM-] = [предлог]', jux_utf8_old( '+BD8EQAQ1BDQEOwQ+BDM-' ), 'jux_utf8_old: => [+BD8EQAQ1BDQEOwQ+BDM-] = [предлог]' ) ;
  7506. is( '[&BB8EQAQ+BDUEOgRC-] = [Проект]', jux_utf8_old( '&BB8EQAQ+BDUEOgRC-' ), 'jux_utf8_old: => [&BB8EQAQ+BDUEOgRC-] = [Проект]' ) ;
  7507. note( 'Leaving tests_jux_utf8_old()' ) ;
  7508. return ;
  7509. }
  7510. sub jux_utf8_old
  7511. {
  7512. # juxtapose utf8 at the right if different
  7513. my ( $s_utf7 ) = shift @ARG ;
  7514. my ( $s_utf8 ) = imap_utf7_decode_old( $s_utf7 ) ;
  7515. if ( $s_utf7 eq $s_utf8 ) {
  7516. #myprint( "[$s_utf7]\n" ) ;
  7517. return( "[$s_utf7]" ) ;
  7518. }else{
  7519. #myprint( "[$s_utf7] = [$s_utf8]\n" ) ;
  7520. return( "[$s_utf7] = [$s_utf8]" ) ;
  7521. }
  7522. }
  7523. # Copied from http://cpansearch.perl.org/src/FABPOT/Unicode-IMAPUtf7-2.01/lib/Unicode/IMAPUtf7.pm
  7524. # and then fixed with
  7525. # https://rt.cpan.org/Public/Bug/Display.html?id=11172
  7526. sub imap_utf7_decode_old
  7527. {
  7528. my ( $s ) = shift @ARG ;
  7529. # Algorithm
  7530. # On remplace , par / dans les BASE 64 (, entre & et -)
  7531. # On remplace les &, non suivi d'un - par +
  7532. # On remplace les &- par &
  7533. $s =~ s/&([^,&\-]*),([^,\-&]*)\-/&$1\/$2\-/xg ;
  7534. $s =~ s/&(?!\-)/\+/xg ;
  7535. $s =~ s/&\-/&/xg ;
  7536. return( Unicode::String::utf7( $s )->utf8 ) ;
  7537. }
  7538. sub tests_jux_utf8
  7539. {
  7540. note( 'Entering tests_jux_utf8()' ) ;
  7541. #no utf8 ;
  7542. use utf8 ;
  7543. #binmode STDOUT, ":encoding(UTF-8)" ;
  7544. binmode STDERR, ":encoding(UTF-8)" ;
  7545. # This test is because the binary can fail on it, a PAR.pm issue.
  7546. # The failure was with the underlying Encode::IMAPUTF7 module line 66 release 1.05
  7547. # Was solved by including Encode in imapsync and using "pp -x".
  7548. ok( find_encoding( "UTF-16BE"), 'jux_utf8: Encode::find_encoding: UTF-16BE' ) ;
  7549. #
  7550. is( '[]', jux_utf8( q{} ), 'jux_utf8: void => []' ) ;
  7551. is( '[INBOX]', jux_utf8( 'INBOX'), 'jux_utf8: INBOX => [INBOX]' ) ;
  7552. is( '[&ANY-] = [Ö]', jux_utf8( '&ANY-'), 'jux_utf8: &ANY- => [&ANY-] = [Ö]' ) ;
  7553. # +BD8EQAQ1BDQEOwQ+BDM- must stay as is
  7554. is( '[+BD8EQAQ1BDQEOwQ+BDM-]', jux_utf8( '+BD8EQAQ1BDQEOwQ+BDM-' ), 'jux_utf8: => [+BD8EQAQ1BDQEOwQ+BDM-] = [+BD8EQAQ1BDQEOwQ+BDM-]' ) ;
  7555. is( '[&BB8EQAQ+BDUEOgRC-] = [Проект]', jux_utf8( '&BB8EQAQ+BDUEOgRC-' ), 'jux_utf8: => [&BB8EQAQ+BDUEOgRC-] = [Проект]' ) ;
  7556. 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]' ) ;
  7557. my $str = Encode::IMAPUTF7::encode("IMAP-UTF-7", 'Réponses 1200+1201+1202' ) ;
  7558. is( '[R&AOk-ponses 1200+1201+1202] = [Réponses 1200+1201+1202]', jux_utf8( $str ), "jux_utf8: [$str] = [Réponses 1200+1201+1202]" ) ;
  7559. is( '[INBOX.&AOkA4ADnAPk-&-*] = [INBOX.éàçù&*]', jux_utf8( 'INBOX.&AOkA4ADnAPk-&-*' ), "jux_utf8: [INBOX.&AOkA4ADnAPk-&-*] = [INBOX.éàçù&*]" ) ;
  7560. is( '[&ZTZO9nux-] = [收件箱]', jux_utf8( '&ZTZO9nux-'), 'jux_utf8: => [&ZTZO9nux-] = [收件箱]' ) ;
  7561. #
  7562. #
  7563. is( '[!Old Emails]', jux_utf8( '!Old Emails'), 'jux_utf8: !Old Emails => [!Old Emails]' ) ;
  7564. is( '[2006 Budget & Fcst]', jux_utf8( '2006 Budget & Fcst'), 'jux_utf8: 2006 Budget & Fcst => [2006 Budget & Fcst]' ) ;
  7565. note( 'Leaving tests_jux_utf8()' ) ;
  7566. return ;
  7567. }
  7568. sub jux_utf8
  7569. {
  7570. #use utf8 ;
  7571. # juxtapose utf8 at the right if different
  7572. my ( $s_utf7 ) = shift @ARG ;
  7573. my ( $s_utf8 ) = imap_utf7_decode( $s_utf7 ) ;
  7574. if ( $s_utf7 eq $s_utf8 ) {
  7575. #myprint( "[$s_utf7]\n" ) ;
  7576. return( "[$s_utf7]" ) ;
  7577. }else{
  7578. #myprint( "[$s_utf7] = [$s_utf8]\n" ) ;
  7579. return( "[$s_utf7] = [$s_utf8]" ) ;
  7580. }
  7581. }
  7582. sub imap_utf7_decode
  7583. {
  7584. #use utf8 ;
  7585. my ( $s ) = shift @ARG ;
  7586. return( Encode::IMAPUTF7::decode("IMAP-UTF-7", $s ) ) ;
  7587. }
  7588. sub imap_utf7_encode
  7589. {
  7590. #use utf8 ;
  7591. my ( $s ) = shift @ARG ;
  7592. return( Encode::IMAPUTF7::encode("IMAP-UTF-7", $s ) ) ;
  7593. }
  7594. sub imap_utf7_encode_old
  7595. {
  7596. my ( $s ) = @_ ;
  7597. $s = Unicode::String::utf8( $s )->utf7 ;
  7598. $s =~ s/\+([^\/&\-]*)\/([^\/\-&]*)\-/\+$1,$2\-/xg ;
  7599. $s =~ s/&/&\-/xg ;
  7600. $s =~ s/\+([^+\-]+)?\-/&$1\-/xg ;
  7601. return( $s ) ;
  7602. }
  7603. sub select_folder
  7604. {
  7605. my ( $mysync, $imap, $folder, $hostside ) = @_ ;
  7606. if ( ! $imap->select( $folder ) ) {
  7607. my $error = join q{},
  7608. "$hostside folder $folder: Could not select: ",
  7609. $imap->LastError, "\n" ;
  7610. errors_incr( $mysync, $error ) ;
  7611. return( 0 ) ;
  7612. }else{
  7613. # ok select succeeded
  7614. return( 1 ) ;
  7615. }
  7616. }
  7617. sub examine_folder
  7618. {
  7619. my ( $mysync, $imap, $folder, $hostside ) = @_ ;
  7620. if ( ! $imap->examine( $folder ) ) {
  7621. my $error = join q{},
  7622. "$hostside folder $folder: Could not examine: ",
  7623. $imap->LastError, "\n" ;
  7624. errors_incr( $mysync, $error ) ;
  7625. return( 0 ) ;
  7626. }else{
  7627. # ok select succeeded
  7628. return( 1 ) ;
  7629. }
  7630. }
  7631. sub count_from_select
  7632. {
  7633. my @lines = @ARG ;
  7634. my $count ;
  7635. foreach my $line ( @lines ) {
  7636. #myprint( "line = [$line]\n" ) ;
  7637. if ( $line =~ m/^\*\s+(\d+)\s+EXISTS/x ) {
  7638. $count = $1 ;
  7639. return( $count ) ;
  7640. }
  7641. }
  7642. return( undef ) ;
  7643. }
  7644. sub create_folder_old
  7645. {
  7646. my $mysync = shift @ARG ;
  7647. my( $imap, $h2_fold, $h1_fold ) = @ARG ;
  7648. myprint( "Creating (old way) folder [$h2_fold] on host2\n" ) ;
  7649. if ( ( 'INBOX' eq uc $h2_fold )
  7650. and ( $imap->exists( $h2_fold ) ) ) {
  7651. myprint( "Folder [$h2_fold] already exists\n" ) ;
  7652. return( 1 ) ;
  7653. }
  7654. if ( ! $mysync->{dry} ){
  7655. if ( ! $imap->create( $h2_fold ) ) {
  7656. my $error = join q{},
  7657. "Could not create folder [$h2_fold] from [$h1_fold]: ",
  7658. $imap->LastError( ), "\n" ;
  7659. errors_incr( $mysync, $error ) ;
  7660. # success if folder exists ("already exists" error)
  7661. return( 1 ) if $imap->exists( $h2_fold ) ;
  7662. # failure since create failed
  7663. return( 0 ) ;
  7664. }else{
  7665. #create succeeded
  7666. myprint( "Created ( the old way ) folder [$h2_fold] on host2\n" ) ;
  7667. return( 1 ) ;
  7668. }
  7669. }else{
  7670. # dry mode, no folder so many imap will fail, assuming failure
  7671. myprint( "Created ( the old way ) folder [$h2_fold] on host2 $mysync->{dry_message}\n" ) ;
  7672. return( 0 ) ;
  7673. }
  7674. }
  7675. sub create_folder
  7676. {
  7677. my $mysync = shift @ARG ;
  7678. my( $myimap2 , $h2_fold , $h1_fold ) = @ARG ;
  7679. my( @parts , $parent ) ;
  7680. if ( $myimap2->IsUnconnected( ) ) {
  7681. myprint( "Host2: Unconnected state\n" ) ;
  7682. return( 0 ) ;
  7683. }
  7684. if ( $create_folder_old ) {
  7685. return( create_folder_old( $mysync, $myimap2 , $h2_fold , $h1_fold ) ) ;
  7686. }
  7687. # $imap->exists() calls $imap->status() that does an IMAP STATUS folder
  7688. myprint( "Creating folder [$h2_fold] on host2\n" ) ;
  7689. if ( ( 'INBOX' eq uc $h2_fold )
  7690. and ( $myimap2->exists( $h2_fold ) ) ) {
  7691. myprint( "Folder [$h2_fold] already exists\n" ) ;
  7692. return( 1 ) ;
  7693. }
  7694. if ( $mixfolders and $myimap2->exists( $h2_fold ) ) {
  7695. myprint( "Folder [$h2_fold] already exists (--nomixfolders is not set)\n" ) ;
  7696. return( 1 ) ;
  7697. }
  7698. if ( ( not $mixfolders ) and ( $myimap2->exists( $h2_fold ) ) ) {
  7699. myprint( "Folder [$h2_fold] already exists and --nomixfolders is set\n" ) ;
  7700. return( 0 ) ;
  7701. }
  7702. @parts = split /\Q$mysync->{ h2_sep }\E/x, $h2_fold ;
  7703. pop @parts ;
  7704. $parent = join $mysync->{ h2_sep }, @parts ;
  7705. $parent =~ s/^\s+|\s+$//xg ;
  7706. if ( ( $parent ne q{} ) and ( ! $myimap2->exists( $parent ) ) ) {
  7707. create_folder( $mysync, $myimap2 , $parent , $h1_fold ) ;
  7708. }
  7709. if ( ! $mysync->{dry} ) {
  7710. if ( ! $myimap2->create( $h2_fold ) ) {
  7711. my $error = join q{},
  7712. "Could not create folder [$h2_fold] from [$h1_fold]: " ,
  7713. $myimap2->LastError( ), "\n" ;
  7714. errors_incr( $mysync, $error ) ;
  7715. # success if folder exists ("already exists" error) or selectable
  7716. if ( $myimap2->exists( $h2_fold ) or select_folder( $mysync, $myimap2, $h2_fold, 'Host2' ) )
  7717. {
  7718. return( 1 ) ;
  7719. }
  7720. # failure since create failed + not exist + not selectable
  7721. return( 0 ) ;
  7722. }else{
  7723. #create succeeded
  7724. myprint( "Created folder [$h2_fold] on host2\n" ) ;
  7725. return( 1 ) ;
  7726. }
  7727. }else{
  7728. # dry mode, no folder so many imap will fail, assuming failure
  7729. myprint( "Created folder [$h2_fold] on host2 $mysync->{dry_message}\n" ) ;
  7730. if ( ! $mysync->{ justfolders } ) {
  7731. myprint( "Since --dry mode is on and folder [$h2_fold] on host2 does not exist yet, syncing messages will not be simulated.\n"
  7732. . "To simulate message syncing, use --justfolders without --dry to first create the missing folders then rerun the --dry sync.\n" ) ;
  7733. # The messages that could be transferred are counted and the number is given at the end.
  7734. }
  7735. return( 0 ) ;
  7736. }
  7737. }
  7738. sub tests_folder_routines
  7739. {
  7740. note( 'Entering tests_folder_routines()' ) ;
  7741. ok( !is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 1' );
  7742. ok( add_to_requested_folders('folder_foo'), 'add_to_requested_folders folder_foo' );
  7743. ok( is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 2' );
  7744. ok( !is_requested_folder('folder_NO_EXIST'), 'is_requested_folder folder_NO_EXIST' );
  7745. is_deeply( [ 'folder_foo' ], [ remove_from_requested_folders( 'folder_foo' ) ], 'removed folder_foo => folder_foo' ) ;
  7746. ok( !is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 3' );
  7747. my @f ;
  7748. ok( @f = add_to_requested_folders('folder_bar', 'folder_toto'), "add result: @f" );
  7749. ok( is_requested_folder('folder_bar'), 'is_requested_folder 4' );
  7750. ok( is_requested_folder('folder_toto'), 'is_requested_folder 5' );
  7751. ok( remove_from_requested_folders('folder_toto'), 'remove_from_requested_folders: ' );
  7752. ok( !is_requested_folder('folder_toto'), 'is_requested_folder 6' );
  7753. is_deeply( [ 'folder_bar' ], [ remove_from_requested_folders('folder_bar') ], 'remove_from_requested_folders: empty' ) ;
  7754. ok( 0 == compare_lists( [ sort_requested_folders( ) ], [] ), 'sort_requested_folders: all empty' ) ;
  7755. ok( add_to_requested_folders( 'A_99', 'M_55', 'Z_11' ), 'add_to_requested_folders M_55 Z_11' );
  7756. ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'A_99', 'M_55', 'Z_11' ] ), 'sort_requested_folders: middle' ) ;
  7757. @folderfirst = ( 'Z_11' ) ;
  7758. ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'A_99', 'M_55' ] ), 'sort_requested_folders: first+middle' ) ;
  7759. is_deeply( [ 'Z_11', 'A_99', 'M_55' ], [ sort_requested_folders( ) ], 'sort_requested_folders: first+middle is_deeply' ) ;
  7760. @folderlast = ( 'A_99' ) ;
  7761. ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'M_55', 'A_99' ] ), 'sort_requested_folders: first+middle+last 1' ) ;
  7762. ok( add_to_requested_folders('M_55', 'M_44',), 'add_to_requested_folders M_55 M_44' ) ;
  7763. ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'M_44', 'M_55', 'A_99'] ), 'sort_requested_folders: first+middle+last 2' ) ;
  7764. ok( add_to_requested_folders('A_88', 'Z_22',), 'add_to_requested_folders A_88 Z_22' ) ;
  7765. @folderfirst = qw( Z_22 Z_11 ) ;
  7766. @folderlast = qw( A_99 A_88 ) ;
  7767. 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' ) ;
  7768. undef @folderfirst ;
  7769. undef @folderlast ;
  7770. note( 'Leaving tests_folder_routines()' ) ;
  7771. return ;
  7772. }
  7773. sub sort_requested_folders
  7774. {
  7775. my @requested_folders_sorted = () ;
  7776. $sync->{ debug } and myprint "folderfirst: @folderfirst\n" ;
  7777. my @folderfirst_requested = remove_from_requested_folders( @folderfirst ) ;
  7778. #myprint "folderfirst_requested: @folderfirst_requested\n" ;
  7779. my @folderlast_requested = remove_from_requested_folders( @folderlast ) ;
  7780. my @middle = sort keys %requested_folder ;
  7781. @requested_folders_sorted = ( @folderfirst_requested, @middle, @folderlast_requested ) ;
  7782. $sync->{ debug } and myprint "requested_folders_sorted: @requested_folders_sorted\n" ;
  7783. add_to_requested_folders( @requested_folders_sorted ) ;
  7784. return( @requested_folders_sorted ) ;
  7785. }
  7786. sub is_requested_folder
  7787. {
  7788. my ( $folder ) = @_;
  7789. return( defined $requested_folder{ $folder } ) ;
  7790. }
  7791. sub add_to_requested_folders
  7792. {
  7793. my @wanted_folders = @_ ;
  7794. foreach my $folder ( @wanted_folders ) {
  7795. ++$requested_folder{ $folder } ;
  7796. }
  7797. return( keys %requested_folder ) ;
  7798. }
  7799. sub tests_remove_from_requested_folders
  7800. {
  7801. note( 'Entering tests_remove_from_requested_folders()' ) ;
  7802. is( undef, undef, 'remove_from_requested_folders: undef is undef' ) ;
  7803. is_deeply( [], [ remove_from_requested_folders( ) ], 'remove_from_requested_folders: no args' ) ;
  7804. %requested_folder = (
  7805. 'F1' => 1,
  7806. ) ;
  7807. is_deeply( [], [ remove_from_requested_folders( ) ], 'remove_from_requested_folders: remove nothing among F1 => nothing' ) ;
  7808. is_deeply( [], [ remove_from_requested_folders( 'Fno' ) ], 'remove_from_requested_folders: remove Fno among F1 => nothing' ) ;
  7809. is_deeply( [ 'F1' ], [ remove_from_requested_folders( 'F1' ) ], 'remove_from_requested_folders: remove F1 among F1 => F1' ) ;
  7810. is_deeply( { }, { %requested_folder }, 'remove_from_requested_folders: remove F1 among F1 => %requested_folder emptied' ) ;
  7811. %requested_folder = (
  7812. 'F1' => 1,
  7813. 'F2' => 1,
  7814. ) ;
  7815. is_deeply( [], [ remove_from_requested_folders( ) ], 'remove_from_requested_folders: remove nothing among F1 F2 => nothing' ) ;
  7816. is_deeply( [], [ remove_from_requested_folders( 'Fno' ) ], 'remove_from_requested_folders: remove Fno among F1 F2 => nothing' ) ;
  7817. is_deeply( [ 'F1' ], [ remove_from_requested_folders( 'F1' ) ], 'remove_from_requested_folders: remove F1 among F1 F2 => F1' ) ;
  7818. is_deeply( { 'F2' => 1 }, { %requested_folder }, 'remove_from_requested_folders: remove F1 among F1 F2 => %requested_folder F2' ) ;
  7819. is_deeply( [], [ remove_from_requested_folders( 'F1' ) ], 'remove_from_requested_folders: remove F1 among F2 => nothing' ) ;
  7820. is_deeply( [ 'F2' ], [ remove_from_requested_folders( 'F1', 'F2' ) ], 'remove_from_requested_folders: remove F1 F2 among F2 => F2' ) ;
  7821. is_deeply( {}, { %requested_folder }, 'remove_from_requested_folders: remove F1 among F1 F2 => %requested_folder F2' ) ;
  7822. %requested_folder = (
  7823. 'F1' => 1,
  7824. 'F2' => 1,
  7825. 'F3' => 1,
  7826. ) ;
  7827. is_deeply( [ 'F1', 'F2' ], [ remove_from_requested_folders( 'F1', 'F2' ) ], 'remove_from_requested_folders: remove F1 F2 among F1 F2 F3 => F1 F2' ) ;
  7828. is_deeply( { 'F3' => 1 }, { %requested_folder }, 'remove_from_requested_folders: remove F1 F2 among F1 F2 F3 => %requested_folder F3' ) ;
  7829. undef %requested_folder ;
  7830. note( 'Leaving tests_remove_from_requested_folders()' ) ;
  7831. return ;
  7832. }
  7833. sub remove_from_requested_folders
  7834. {
  7835. my @unwanted_folders = @_ ;
  7836. my @removed_folders = () ;
  7837. foreach my $folder ( @unwanted_folders ) {
  7838. if ( exists $requested_folder{ $folder } )
  7839. {
  7840. delete $requested_folder{ $folder } ;
  7841. push @removed_folders, $folder ;
  7842. }
  7843. }
  7844. return( @removed_folders ) ;
  7845. }
  7846. sub compare_lists
  7847. {
  7848. my ($list_1_ref, $list_2_ref) = @_;
  7849. return($MINUS_ONE) if ((not defined $list_1_ref) and defined $list_2_ref);
  7850. return(0) if ((not defined $list_1_ref) and not defined $list_2_ref); # end if no list
  7851. return(1) if (not defined $list_2_ref); # end if only one list
  7852. if (not ref $list_1_ref ) {$list_1_ref = [$list_1_ref]};
  7853. if (not ref $list_2_ref ) {$list_2_ref = [$list_2_ref]};
  7854. my $last_used_indice = $MINUS_ONE;
  7855. ELEMENT:
  7856. foreach my $indice ( 0 .. $#{ $list_1_ref } ) {
  7857. $last_used_indice = $indice ;
  7858. # End of list_2
  7859. return 1 if ($indice > $#{ $list_2_ref } ) ;
  7860. my $element_list_1 = $list_1_ref->[$indice] ;
  7861. my $element_list_2 = $list_2_ref->[$indice] ;
  7862. my $balance = $element_list_1 cmp $element_list_2 ;
  7863. next ELEMENT if ($balance == 0) ;
  7864. return $balance ;
  7865. }
  7866. # each element equal until last indice of list_1
  7867. return $MINUS_ONE if ($last_used_indice < $#{ $list_2_ref } ) ;
  7868. # same size, each element equal
  7869. return 0 ;
  7870. }
  7871. sub tests_compare_lists
  7872. {
  7873. note( 'Entering tests_compare_lists()' ) ;
  7874. my $empty_list_ref = [];
  7875. ok( 0 == compare_lists() , 'compare_lists, no args');
  7876. ok( 0 == compare_lists(undef) , 'compare_lists, undef = nothing');
  7877. ok( 0 == compare_lists(undef, undef) , 'compare_lists, undef = undef');
  7878. ok($MINUS_ONE == compare_lists(undef , []) , 'compare_lists, undef < []');
  7879. ok($MINUS_ONE == compare_lists(undef , [1]) , 'compare_lists, undef < [1]');
  7880. ok($MINUS_ONE == compare_lists(undef , [0]) , 'compare_lists, undef < [0]');
  7881. ok(+1 == compare_lists([]) , 'compare_lists, [] > nothing');
  7882. ok(+1 == compare_lists([], undef) , 'compare_lists, [] > undef');
  7883. ok( 0 == compare_lists([] , []) , 'compare_lists, [] = []');
  7884. ok($MINUS_ONE == compare_lists([] , [1]) , 'compare_lists, [] < [1]');
  7885. ok(+1 == compare_lists([1] , []) , 'compare_lists, [1] > []');
  7886. ok( 0 == compare_lists( [1], 1 ) , 'compare_lists, [1] = 1 ') ;
  7887. ok( 0 == compare_lists( 1 , [1] ) , 'compare_lists, 1 = [1]') ;
  7888. ok( 0 == compare_lists( 1 , 1 ) , 'compare_lists, 1 = 1 ') ;
  7889. ok( $MINUS_ONE == compare_lists( 0 , 1 ) , 'compare_lists, 0 < 1 ') ;
  7890. ok( $MINUS_ONE == compare_lists( $MINUS_ONE , 0 ) , 'compare_lists, -1 < 0 ') ;
  7891. ok( $MINUS_ONE == compare_lists( 1 , 2 ) , 'compare_lists, 1 < 2 ') ;
  7892. ok( +1 == compare_lists( 2 , 1 ) , 'compare_lists, 2 > 1 ') ;
  7893. ok( 0 == compare_lists([1,2], [1,2]) , 'compare_lists, [1,2] = [1,2]' ) ;
  7894. ok($MINUS_ONE == compare_lists([1], [1,2]) , 'compare_lists, [1] < [1,2]' ) ;
  7895. ok(+1 == compare_lists([2], [1,2]) , 'compare_lists, [2] > [1,2]' ) ;
  7896. ok($MINUS_ONE == compare_lists([1], [1,1]) , 'compare_lists, [1] < [1,1]' ) ;
  7897. ok(+1 == compare_lists([1, 1], [1]) , 'compare_lists, [1, 1] > [1]' ) ;
  7898. ok( 0 == compare_lists([1 .. $NUMBER_20_000] , [1 .. $NUMBER_20_000])
  7899. , 'compare_lists, [1..20_000] = [1..20_000]' ) ;
  7900. ok($MINUS_ONE == compare_lists([1], [2]) , 'compare_lists, [1] < [2]') ;
  7901. ok( 0 == compare_lists([2], [2]) , 'compare_lists, [0] = [2]') ;
  7902. ok(+1 == compare_lists([2], [1]) , 'compare_lists, [2] > [1]') ;
  7903. ok($MINUS_ONE == compare_lists(['a'], ['b']) , 'compare_lists, ["a"] < ["b"]') ;
  7904. ok( 0 == compare_lists(['a'], ['a']) , 'compare_lists, ["a"] = ["a"]') ;
  7905. ok( 0 == compare_lists(['ab'], ['ab']) , 'compare_lists, ["ab"] = ["ab"]') ;
  7906. ok(+1 == compare_lists(['b'], ['a']) , 'compare_lists, ["b"] > ["a"]') ;
  7907. ok($MINUS_ONE == compare_lists(['a'], ['aa']) , 'compare_lists, ["a"] < ["aa"]') ;
  7908. ok($MINUS_ONE == compare_lists(['a'], ['a', 'a']), 'compare_lists, ["a"] < ["a", "a"]') ;
  7909. ok( 0 == compare_lists([split q{ }, 'a b' ], ['a', 'b']), 'compare_lists, split') ;
  7910. ok( 0 == compare_lists([sort split q{ }, 'b a' ], ['a', 'b']), 'compare_lists, sort split') ;
  7911. note( 'Leaving tests_compare_lists()' ) ;
  7912. return ;
  7913. }
  7914. sub guess_prefix
  7915. {
  7916. my @foldernames = @_ ;
  7917. my $prefix_guessed = q{} ;
  7918. foreach my $folder ( @foldernames ) {
  7919. next if ( $folder =~ m{^INBOX$}xi ) ; # no guessing from INBOX
  7920. if ( $folder !~ m{^INBOX}xi ) {
  7921. $prefix_guessed = q{} ; # prefix empty guessed
  7922. last ;
  7923. }
  7924. if ( $folder =~ m{^(INBOX(?:\.|\/))}xi ) {
  7925. $prefix_guessed = $1 ; # prefix Inbox/ or INBOX. guessed
  7926. }
  7927. }
  7928. return( $prefix_guessed ) ;
  7929. }
  7930. sub tests_guess_prefix
  7931. {
  7932. note( 'Entering tests_guess_prefix()' ) ;
  7933. is( guess_prefix( ), q{}, 'guess_prefix: no args => empty string' ) ;
  7934. is( q{} , guess_prefix( 'INBOX' ), 'guess_prefix: INBOX alone' ) ;
  7935. is( q{} , guess_prefix( 'Inbox' ), 'guess_prefix: Inbox alone' ) ;
  7936. is( q{} , guess_prefix( 'INBOX' ), 'guess_prefix: INBOX alone' ) ;
  7937. is( 'INBOX/' , guess_prefix( 'INBOX', 'INBOX/Junk' ), 'guess_prefix: INBOX INBOX/Junk' ) ;
  7938. is( 'INBOX.' , guess_prefix( 'INBOX', 'INBOX.Junk' ), 'guess_prefix: INBOX INBOX.Junk' ) ;
  7939. is( 'Inbox/' , guess_prefix( 'Inbox', 'Inbox/Junk' ), 'guess_prefix: Inbox Inbox/Junk' ) ;
  7940. is( 'Inbox.' , guess_prefix( 'Inbox', 'Inbox.Junk' ), 'guess_prefix: Inbox Inbox.Junk' ) ;
  7941. is( 'INBOX/' , guess_prefix( 'INBOX', 'INBOX/Junk', 'INBOX/rrr' ), 'guess_prefix: INBOX INBOX/Junk INBOX/rrr' ) ;
  7942. is( q{} , guess_prefix( 'INBOX', 'INBOX/Junk', 'INBOX/rrr', 'zzz' ), 'guess_prefix: INBOX INBOX/Junk INBOX/rrr zzz' ) ;
  7943. is( q{} , guess_prefix( 'INBOX', 'Junk' ), 'guess_prefix: INBOX Junk' ) ;
  7944. is( q{} , guess_prefix( 'INBOX', 'Junk' ), 'guess_prefix: INBOX Junk' ) ;
  7945. note( 'Leaving tests_guess_prefix()' ) ;
  7946. return ;
  7947. }
  7948. sub get_prefix
  7949. {
  7950. my( $imap, $prefix_in, $prefix_opt, $Side, $folders_ref ) = @_ ;
  7951. my( $prefix_out, $prefix_guessed ) ;
  7952. ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "$Side: Getting prefix\n" ) ;
  7953. $prefix_guessed = guess_prefix( @{ $folders_ref } ) ;
  7954. myprint( "$Side: guessing prefix from folder listing: [$prefix_guessed]\n" ) ;
  7955. ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "$Side: Calling namespace capability\n" ) ;
  7956. if ( $imap->has_capability( 'namespace' ) ) {
  7957. my $r_namespace = $imap->namespace( ) ;
  7958. $prefix_out = $r_namespace->[0][0][0] ;
  7959. myprint( "$Side: prefix given by NAMESPACE: [$prefix_out]\n" ) ;
  7960. if ( defined $prefix_in ) {
  7961. myprint( "$Side: but using [$prefix_in] given by $prefix_opt\n" ) ;
  7962. $prefix_out = $prefix_in ;
  7963. return( $prefix_out ) ;
  7964. }else{
  7965. # all good
  7966. return( $prefix_out ) ;
  7967. }
  7968. }
  7969. else{
  7970. if ( defined $prefix_in ) {
  7971. myprint( "$Side: using [$prefix_in] given by $prefix_opt\n" ) ;
  7972. $prefix_out = $prefix_in ;
  7973. return( $prefix_out ) ;
  7974. }else{
  7975. myprint(
  7976. "$Side: No NAMESPACE capability so using guessed prefix [$prefix_guessed]\n",
  7977. help_to_guess_prefix( $imap, $prefix_opt ) ) ;
  7978. return( $prefix_guessed ) ;
  7979. }
  7980. }
  7981. return ;
  7982. }
  7983. sub guess_separator
  7984. {
  7985. my @foldernames = @_ ;
  7986. #return( undef ) unless ( @foldernames ) ;
  7987. my $sep_guessed ;
  7988. my %counter ;
  7989. foreach my $folder ( @foldernames ) {
  7990. $counter{'/'}++ while ( $folder =~ m{/}xg ) ; # count /
  7991. $counter{'.'}++ while ( $folder =~ m{\.}xg ) ; # count .
  7992. $counter{'\\\\'}++ while ( $folder =~ m{(\\){2}}xg ) ; # count \\
  7993. $counter{'\\'}++ while ( $folder =~ m{[^\\](\\){1}(?=[^\\])}xg ) ; # count \
  7994. }
  7995. my @race_sorted = sort { $counter{ $b } <=> $counter{ $a } } keys %counter ;
  7996. $sync->{ debug } and myprint( "@foldernames\n@race_sorted\n", %counter, "\n" ) ;
  7997. $sep_guessed = shift @race_sorted || $LAST_RESSORT_SEPARATOR ; # / when nothing found.
  7998. return( $sep_guessed ) ;
  7999. }
  8000. sub tests_guess_separator
  8001. {
  8002. note( 'Entering tests_guess_separator()' ) ;
  8003. ok( '/' eq guess_separator( ), 'guess_separator: no args' ) ;
  8004. ok( '/' eq guess_separator( 'abcd' ), 'guess_separator: abcd' ) ;
  8005. ok( '/' eq guess_separator( 'a/b/c.d' ), 'guess_separator: a/b/c.d' ) ;
  8006. ok( '.' eq guess_separator( 'a.b/c.d' ), 'guess_separator: a.b/c.d' ) ;
  8007. ok( '\\\\' eq guess_separator( 'a\\\\b\\\\c.c\\\\d/e/f' ), 'guess_separator: a\\\\b\\\\c.c\\\\d/e/f' ) ;
  8008. ok( '\\' eq guess_separator( 'a\\b\\c.c\\d/e/f' ), 'guess_separator: a\\b\\c.c\\d/e/f' ) ;
  8009. ok( '\\' eq guess_separator( 'a\\b' ), 'guess_separator: a\\b' ) ;
  8010. ok( '\\' eq guess_separator( 'a\\b\\c' ), 'guess_separator: a\\b\\c' ) ;
  8011. note( 'Leaving tests_guess_separator()' ) ;
  8012. return ;
  8013. }
  8014. sub get_separator
  8015. {
  8016. my( $imap, $sep_in, $sep_opt, $Side, $folders_ref ) = @_ ;
  8017. my( $sep_out, $sep_guessed ) ;
  8018. ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "$Side: Getting separator\n" ) ;
  8019. $sep_guessed = guess_separator( @{ $folders_ref } ) ;
  8020. myprint( "$Side: guessing separator from folder listing: [$sep_guessed]\n" ) ;
  8021. ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "$Side: calling namespace capability\n" ) ;
  8022. if ( $imap->has_capability( 'namespace' ) )
  8023. {
  8024. $sep_out = $imap->separator( ) ;
  8025. if ( defined $sep_out ) {
  8026. myprint( "$Side: separator given by NAMESPACE: [$sep_out]\n" ) ;
  8027. if ( defined $sep_in ) {
  8028. myprint( "$Side: but using [$sep_in] given by $sep_opt\n" ) ;
  8029. $sep_out = $sep_in ;
  8030. return( $sep_out ) ;
  8031. }else{
  8032. return( $sep_out ) ;
  8033. }
  8034. }else{
  8035. if ( defined $sep_in ) {
  8036. myprint( "$Side: NAMESPACE request failed but using [$sep_in] given by $sep_opt\n" ) ;
  8037. $sep_out = $sep_in ;
  8038. return( $sep_out ) ;
  8039. }else{
  8040. myprint(
  8041. "$Side: NAMESPACE request failed so using guessed separator [$sep_guessed]\n",
  8042. help_to_guess_sep( $imap, $sep_opt ) ) ;
  8043. return( $sep_guessed ) ;
  8044. }
  8045. }
  8046. }
  8047. else
  8048. {
  8049. if ( defined $sep_in ) {
  8050. myprint( "$Side: No NAMESPACE capability but using [$sep_in] given by $sep_opt\n" ) ;
  8051. $sep_out = $sep_in ;
  8052. return( $sep_out ) ;
  8053. }else{
  8054. myprint(
  8055. "$Side: No NAMESPACE capability, so using guessed separator [$sep_guessed]\n",
  8056. help_to_guess_sep( $imap, $sep_opt ) ) ;
  8057. return( $sep_guessed ) ;
  8058. }
  8059. }
  8060. return ;
  8061. }
  8062. sub help_to_guess_sep
  8063. {
  8064. my( $imap, $sep_opt ) = @_ ;
  8065. my $help_to_guess_sep = "You can set the separator character with the $sep_opt option,\n"
  8066. . "the complete listing of folders may help you to find it\n"
  8067. . folders_list_to_help( $imap ) ;
  8068. return( $help_to_guess_sep ) ;
  8069. }
  8070. sub help_to_guess_prefix
  8071. {
  8072. my( $imap, $prefix_opt ) = @_ ;
  8073. my $help_to_guess_prefix = "You can set the prefix namespace with the $prefix_opt option,\n"
  8074. . "the folowing listing of folders may help you to find it:\n"
  8075. . folders_list_to_help( $imap ) ;
  8076. return( $help_to_guess_prefix ) ;
  8077. }
  8078. sub folders_list_to_help
  8079. {
  8080. my( $imap ) = shift @ARG ;
  8081. my @folders = $imap->folders ;
  8082. my $listing = join q{}, map { "[$_]\n" } @folders ;
  8083. return( $listing ) ;
  8084. }
  8085. # Globals are $sync @h1_folders_all @h2_folders_all $prefix1 $prefix2
  8086. sub private_folders_separators_and_prefixes
  8087. {
  8088. # what are the private folders separators and prefixes for each server ?
  8089. ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "Getting separators\n" ) ;
  8090. $sync->{ h1_sep } = get_separator( $sync->{imap1}, $sync->{ sep1 }, '--sep1', 'Host1', \@h1_folders_all ) ;
  8091. $sync->{ h2_sep } = get_separator( $sync->{imap2}, $sync->{ sep2 }, '--sep2', 'Host2', \@h2_folders_all ) ;
  8092. $sync->{ h1_prefix } = get_prefix( $sync->{imap1}, $prefix1, '--prefix1', 'Host1', \@h1_folders_all ) ;
  8093. $sync->{ h2_prefix } = get_prefix( $sync->{imap2}, $prefix2, '--prefix2', 'Host2', \@h2_folders_all ) ;
  8094. myprint( "Host1: separator and prefix: [$sync->{ h1_sep }][$sync->{ h1_prefix }]\n" ) ;
  8095. myprint( "Host2: separator and prefix: [$sync->{ h2_sep }][$sync->{ h2_prefix }]\n" ) ;
  8096. return ;
  8097. }
  8098. sub subfolder1
  8099. {
  8100. my $mysync = shift @ARG ;
  8101. my $subfolder1 = sanitize_subfolder( $mysync->{ subfolder1 } ) ;
  8102. if ( $subfolder1 )
  8103. {
  8104. # turns off automap
  8105. myprint( "Turning off automapping folders because of --subfolder1\n" ) ;
  8106. $mysync->{ automap } = undef ;
  8107. myprint( "Sanitizing subfolder1: [$mysync->{ subfolder1 }] => [$subfolder1]\n" ) ;
  8108. $mysync->{ subfolder1 } = $subfolder1 ;
  8109. if ( ! add_subfolder1_to_folderrec( $mysync ) )
  8110. {
  8111. $mysync->{nb_errors}++ ;
  8112. exit_clean( $mysync, $EXIT_SUBFOLDER1_NO_EXISTS, "subfolder1 $subfolder1 does not exist\n" ) ;
  8113. }
  8114. }
  8115. else
  8116. {
  8117. $mysync->{ subfolder1 } = undef ;
  8118. }
  8119. return ;
  8120. }
  8121. sub subfolder2
  8122. {
  8123. my $mysync = shift @ARG ;
  8124. my $subfolder2 = sanitize_subfolder( $mysync->{ subfolder2 } ) ;
  8125. if ( $subfolder2 )
  8126. {
  8127. # turns off automap
  8128. myprint( "Turning off automapping folders because of --subfolder2\n" ) ;
  8129. $mysync->{ automap } = undef ;
  8130. myprint( "Sanitizing subfolder2: [$mysync->{ subfolder2 }] => [$subfolder2]\n" ) ;
  8131. $mysync->{ subfolder2 } = $subfolder2 ;
  8132. set_regextrans2_for_subfolder2( $mysync ) ;
  8133. }
  8134. else
  8135. {
  8136. $mysync->{ subfolder2 } = undef ;
  8137. }
  8138. return ;
  8139. }
  8140. sub tests_sanitize_subfolder
  8141. {
  8142. note( 'Entering tests_sanitize_subfolder()' ) ;
  8143. is( undef, sanitize_subfolder( ), 'sanitize_subfolder: no args => undef' ) ;
  8144. is( undef, sanitize_subfolder( q{} ), 'sanitize_subfolder: empty => undef' ) ;
  8145. is( undef, sanitize_subfolder( ' ' ), 'sanitize_subfolder: blank => undef' ) ;
  8146. is( undef, sanitize_subfolder( ' ' ), 'sanitize_subfolder: blanks => undef' ) ;
  8147. is( 'abcd', sanitize_subfolder( 'abcd' ), 'sanitize_subfolder: abcd => abcd' ) ;
  8148. is( 'ab cd', sanitize_subfolder( ' ab cd ' ), 'sanitize_subfolder: " ab cd " => "ab cd"' ) ;
  8149. is( 'abcd', sanitize_subfolder( q{a&~b#\\c[]=d;} ), 'sanitize_subfolder: "a&~b#\\c[]=d;" => "abcd"' ) ;
  8150. is( 'aA.b-_ 8c/dD', sanitize_subfolder( 'aA.b-_ 8c/dD' ), 'sanitize_subfolder: aA.b-_ 8c/dD => aA.b-_ 8c/dD' ) ;
  8151. note( 'Leaving tests_sanitize_subfolder()' ) ;
  8152. return ;
  8153. }
  8154. sub sanitize_subfolder
  8155. {
  8156. my $subfolder = shift @ARG ;
  8157. if ( ! $subfolder )
  8158. {
  8159. return ;
  8160. }
  8161. # Remove edging blanks
  8162. $subfolder =~ s,^ +| +$,,g ;
  8163. # Keep only abcd...ABCD...0123... and -_./
  8164. $subfolder =~ tr,-_a-zA-Z0-9./ ,,cd ;
  8165. # A blank subfolder is not a subfolder
  8166. if ( ! $subfolder )
  8167. {
  8168. return ;
  8169. }
  8170. else
  8171. {
  8172. return $subfolder ;
  8173. }
  8174. }
  8175. sub tests_sanitize_host
  8176. {
  8177. note( 'Entering tests_sanitize_host()' ) ;
  8178. is( undef, sanitize_host( ), 'sanitize_host: no args => undef' ) ;
  8179. is( '', sanitize_host( '' ), 'sanitize_host: empty => empty' ) ;
  8180. is( 'imap.example.org', sanitize_host( 'imap.example.org' ), 'sanitize_host: imap.example.org => imap.example.org' ) ;
  8181. is( 'imap.example.org', sanitize_host( ' imap.example.org' ), 'sanitize_host: imap.example.org 1 => imap.example.org' ) ;
  8182. is( 'imap.example.org', sanitize_host( 'imap.example.org ' ), 'sanitize_host: imap.example.org 2 => imap.example.org' ) ;
  8183. is( 'imap.example.org', sanitize_host( 'imap.exam ple.org' ), 'sanitize_host: imap.example.org 3 => imap.example.org' ) ;
  8184. is( 'imap.example.org', sanitize_host( ' imap.exam ple.org ' ), 'sanitize_host: imap.example.org 4 => imap.example.org' ) ;
  8185. is( 'imap.example.org', sanitize_host( 'imap.exa/mple.org/' ), 'sanitize_host: imap.example.org/ => imap.example.org' ) ;
  8186. note( 'Leaving tests_sanitize_host()' ) ;
  8187. return ;
  8188. }
  8189. sub sanitize_host
  8190. {
  8191. my $host = shift @ARG ;
  8192. if ( ! defined $host ) { return ; }
  8193. $host =~ tr{ /}{}d ;
  8194. return $host ;
  8195. }
  8196. sub tests_add_subfolder1_to_folderrec
  8197. {
  8198. note( 'Entering tests_add_subfolder1_to_folderrec()' ) ;
  8199. is( undef, add_subfolder1_to_folderrec( ), 'add_subfolder1_to_folderrec: undef => undef' ) ;
  8200. is_deeply( [], [ add_subfolder1_to_folderrec( ) ], 'add_subfolder1_to_folderrec: no args => empty array' ) ;
  8201. @folderrec = () ;
  8202. my $mysync = {} ;
  8203. is_deeply( [ ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: empty => empty array' ) ;
  8204. is_deeply( [ ], [ @folderrec ], 'add_subfolder1_to_folderrec: empty => empty folderrec' ) ;
  8205. $mysync->{ subfolder1 } = 'SUBI' ;
  8206. $h1_folders_all{ 'SUBI' } = 1 ;
  8207. $mysync->{ h1_prefix } = 'INBOX/' ;
  8208. is_deeply( [ 'SUBI' ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: SUBI => SUBI' ) ;
  8209. is_deeply( [ 'SUBI' ], [ @folderrec ], 'add_subfolder1_to_folderrec: SUBI => folderrec SUBI ' ) ;
  8210. @folderrec = () ;
  8211. $mysync->{ subfolder1 } = 'SUBO' ;
  8212. is_deeply( [ ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: SUBO no exists => empty array' ) ;
  8213. is_deeply( [ ], [ @folderrec ], 'add_subfolder1_to_folderrec: SUBO no exists => empty folderrec' ) ;
  8214. $h1_folders_all{ 'INBOX/SUBO' } = 1 ;
  8215. is_deeply( [ 'INBOX/SUBO' ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: SUBO + INBOX/SUBO exists => INBOX/SUBO' ) ;
  8216. is_deeply( [ 'INBOX/SUBO' ], [ @folderrec ], 'add_subfolder1_to_folderrec: SUBO + INBOX/SUBO exists => INBOX/SUBO folderrec' ) ;
  8217. note( 'Leaving tests_add_subfolder1_to_folderrec()' ) ;
  8218. return ;
  8219. }
  8220. sub add_subfolder1_to_folderrec
  8221. {
  8222. my $mysync = shift @ARG ;
  8223. if ( ! $mysync || ! $mysync->{ subfolder1 } )
  8224. {
  8225. return ;
  8226. }
  8227. my $subfolder1 = $mysync->{ subfolder1 } ;
  8228. my $subfolder1_extended = $mysync->{ h1_prefix } . $subfolder1 ;
  8229. if ( exists $h1_folders_all{ $subfolder1 } )
  8230. {
  8231. myprint( qq{Acting like --folderrec "$subfolder1"\n} ) ;
  8232. push @folderrec, $subfolder1 ;
  8233. }
  8234. elsif ( exists $h1_folders_all{ $subfolder1_extended } )
  8235. {
  8236. myprint( qq{Acting like --folderrec "$subfolder1_extended"\n} ) ;
  8237. push @folderrec, $subfolder1_extended ;
  8238. }
  8239. else
  8240. {
  8241. myprint( qq{Nor folder "$subfolder1" nor "$subfolder1_extended" exists on host1\n} ) ;
  8242. }
  8243. return @folderrec ;
  8244. }
  8245. sub set_regextrans2_for_subfolder2
  8246. {
  8247. my $mysync = shift @ARG ;
  8248. unshift @{ $mysync->{ regextrans2 } },
  8249. q(s,^$mysync->{ h2_prefix }(.*),$mysync->{ h2_prefix }$mysync->{ subfolder2 }$mysync->{ h2_sep }$1,),
  8250. q(s,^INBOX$,$mysync->{ h2_prefix }$mysync->{ subfolder2 }$mysync->{ h2_sep }INBOX,),
  8251. q(s,^($mysync->{ h2_prefix }){2},$mysync->{ h2_prefix },);
  8252. #myprint( "@{ $mysync->{ regextrans2 } }\n" ) ;
  8253. return ;
  8254. }
  8255. # Looks like no globals here
  8256. sub tests_imap2_folder_name
  8257. {
  8258. note( 'Entering tests_imap2_folder_name()' ) ;
  8259. my $mysync = {} ;
  8260. $mysync->{ h1_prefix } = q{} ;
  8261. $mysync->{ h2_prefix } = q{} ;
  8262. $mysync->{ h1_sep } = '/';
  8263. $mysync->{ h2_sep } = '.';
  8264. $mysync->{ debug } and myprint( <<"EOS"
  8265. prefix1: [$mysync->{ h1_prefix }]
  8266. prefix2: [$mysync->{ h2_prefix }]
  8267. sep1: [$sync->{ h1_sep }]
  8268. sep2: [$sync->{ h2_sep }]
  8269. EOS
  8270. ) ;
  8271. $mysync->{ fixslash2 } = 0 ;
  8272. is( q{INBOX}, imap2_folder_name( $mysync, q{} ), 'imap2_folder_name: empty string' ) ;
  8273. is( 'blabla', imap2_folder_name( $mysync, 'blabla' ), 'imap2_folder_name: blabla' ) ;
  8274. is('spam.spam', imap2_folder_name( $mysync, 'spam/spam' ), 'imap2_folder_name: spam/spam' ) ;
  8275. is( 'spam/spam', imap2_folder_name( $mysync, 'spam.spam' ), 'imap2_folder_name: spam.spam') ;
  8276. is( 'spam.spam/spam', imap2_folder_name( $mysync, 'spam/spam.spam' ), 'imap2_folder_name: spam/spam.spam' ) ;
  8277. is( 's pam.spam/sp am', imap2_folder_name( $mysync, 's pam/spam.sp am' ), 'imap2_folder_name: s pam/spam.sp am' ) ;
  8278. $mysync->{f1f2h}{ 'auto' } = 'moto' ;
  8279. is( 'moto', imap2_folder_name( $mysync, 'auto' ), 'imap2_folder_name: auto' ) ;
  8280. $mysync->{f1f2h}{ 'auto/auto' } = 'moto x 2' ;
  8281. is( 'moto x 2', imap2_folder_name( $mysync, 'auto/auto' ), 'imap2_folder_name: auto/auto' ) ;
  8282. @{ $mysync->{ regextrans2 } } = ( 's,/,X,g' ) ;
  8283. is( q{INBOX}, imap2_folder_name( $mysync, q{} ), 'imap2_folder_name: empty string [s,/,X,g]' ) ;
  8284. is( 'blabla', imap2_folder_name( $mysync, 'blabla' ), 'imap2_folder_name: blabla [s,/,X,g]' ) ;
  8285. is('spam.spam', imap2_folder_name( $mysync, 'spam/spam'), 'imap2_folder_name: spam/spam [s,/,X,g]');
  8286. is('spamXspam', imap2_folder_name( $mysync, 'spam.spam'), 'imap2_folder_name: spam.spam [s,/,X,g]');
  8287. is('spam.spamXspam', imap2_folder_name( $mysync, 'spam/spam.spam'), 'imap2_folder_name: spam/spam.spam [s,/,X,g]');
  8288. @{ $mysync->{ regextrans2 } } = ( 's, ,_,g' ) ;
  8289. is('blabla', imap2_folder_name( $mysync, 'blabla'), 'imap2_folder_name: blabla [s, ,_,g]');
  8290. is('bla_bla', imap2_folder_name( $mysync, 'bla bla'), 'imap2_folder_name: blabla [s, ,_,g]');
  8291. @{ $mysync->{ regextrans2 } } = ( q{s,(.*),\U$1,} ) ;
  8292. is( 'BLABLA', imap2_folder_name( $mysync, 'blabla' ), q{imap2_folder_name: blabla [s,\U(.*)\E,$1,]} ) ;
  8293. $mysync->{ fixslash2 } = 1 ;
  8294. @{ $mysync->{ regextrans2 } } = ( ) ;
  8295. is(q{INBOX}, imap2_folder_name( $mysync, q{}), 'imap2_folder_name: empty string');
  8296. is('blabla', imap2_folder_name( $mysync, 'blabla'), 'imap2_folder_name: blabla');
  8297. is('spam.spam', imap2_folder_name( $mysync, 'spam/spam'), 'imap2_folder_name: spam/spam -> spam.spam');
  8298. is('spam_spam', imap2_folder_name( $mysync, 'spam.spam'), 'imap2_folder_name: spam.spam -> spam_spam');
  8299. is('spam.spam_spam', imap2_folder_name( $mysync, 'spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam_spam');
  8300. 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');
  8301. $mysync->{ h1_sep } = '.';
  8302. $mysync->{ h2_sep } = '/';
  8303. is( q{INBOX}, imap2_folder_name( $mysync, q{}), 'imap2_folder_name: empty string');
  8304. is('blabla', imap2_folder_name( $mysync, 'blabla'), 'imap2_folder_name: blabla');
  8305. is('spam.spam', imap2_folder_name( $mysync, 'spam/spam'), 'imap2_folder_name: spam/spam -> spam.spam');
  8306. is('spam/spam', imap2_folder_name( $mysync, 'spam.spam'), 'imap2_folder_name: spam.spam -> spam/spam');
  8307. is('spam.spam/spam', imap2_folder_name( $mysync, 'spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam');
  8308. $mysync->{ fixslash2 } = 0 ;
  8309. $mysync->{ h1_prefix } = q{ };
  8310. is( 'spam.spam/spam', imap2_folder_name( $mysync, 'spam/spam.spam' ), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam' ) ;
  8311. is( 'spam.spam/spam', imap2_folder_name( $mysync, ' spam/spam.spam' ), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam' ) ;
  8312. $mysync->{ h1_sep } = '.' ;
  8313. $mysync->{ h2_sep } = '/' ;
  8314. $mysync->{ h1_prefix } = 'INBOX.' ;
  8315. $mysync->{ h2_prefix } = q{} ;
  8316. @{ $mysync->{ regextrans2 } } = ( q{s,(.*),\U$1,} ) ;
  8317. is( 'BLABLA', imap2_folder_name( $mysync, 'blabla' ), 'imap2_folder_name: blabla' ) ;
  8318. is( 'TEST/TEST/TEST/TEST', imap2_folder_name( $mysync, 'INBOX.TEST.test.Test.tesT' ), 'imap2_folder_name: INBOX.TEST.test.Test.tesT' ) ;
  8319. @{ $mysync->{ regextrans2 } } = ( q{s,(.*),\L$1,} ) ;
  8320. is( 'test/test/test/test', imap2_folder_name( $mysync, 'INBOX.TEST.test.Test.tesT' ), 'imap2_folder_name: INBOX.TEST.test.Test.tesT' ) ;
  8321. # INBOX
  8322. $mysync = {} ;
  8323. $mysync->{ h1_prefix } = q{Pf1.} ;
  8324. $mysync->{ h2_prefix } = q{Pf2/} ;
  8325. $mysync->{ h1_sep } = '.';
  8326. $mysync->{ h2_sep } = '/';
  8327. #
  8328. #$mysync->{ debug } = 1 ;
  8329. is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'F1.F2.F3' ), 'imap2_folder_name: F1.F2.F3 -> Pf2/F1/F2/F3' ) ;
  8330. is( 'Pf2/F1/INBOX', imap2_folder_name( $mysync, 'F1.INBOX' ), 'imap2_folder_name: F1.INBOX -> Pf2/F1/INBOX' ) ;
  8331. is( 'INBOX', imap2_folder_name( $mysync, 'INBOX' ), 'imap2_folder_name: INBOX -> INBOX' ) ;
  8332. is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'Pf1.F1.F2.F3' ), 'imap2_folder_name: Pf1.F1.F2.F3 -> Pf2/F1/F2/F3' ) ;
  8333. is( 'Pf2/F1/INBOX', imap2_folder_name( $mysync, 'Pf1.F1.INBOX' ), 'imap2_folder_name: Pf1.F1.INBOX -> Pf2/F1/INBOX' ) ;
  8334. is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.INBOX' ), 'imap2_folder_name: Pf1.INBOX -> INBOX' ) ; # not Pf2/INBOX: Yes I can!
  8335. # subfolder2
  8336. $mysync = {} ;
  8337. $mysync->{ h1_prefix } = q{} ;
  8338. $mysync->{ h2_prefix } = q{} ;
  8339. $mysync->{ h1_sep } = '/';
  8340. $mysync->{ h2_sep } = '.';
  8341. set_regextrans2_for_subfolder2( $mysync ) ;
  8342. $mysync->{ subfolder2 } = 'S1.S2' ;
  8343. is( 'S1.S2.F1.F2.F3', imap2_folder_name( $mysync, 'F1/F2/F3' ), 'imap2_folder_name: F1/F2/F3 -> S1.S2.F1.F2.F3' ) ;
  8344. is( 'S1.S2.INBOX', imap2_folder_name( $mysync, 'INBOX' ), 'imap2_folder_name: F1/F2/F3 -> S1.S2.INBOX' ) ;
  8345. $mysync = {} ;
  8346. $mysync->{ h1_prefix } = q{Pf1/} ;
  8347. $mysync->{ h2_prefix } = q{Pf2.} ;
  8348. $mysync->{ h1_sep } = '/';
  8349. $mysync->{ h2_sep } = '.';
  8350. #$mysync->{ debug } = 1 ;
  8351. set_regextrans2_for_subfolder2( $mysync ) ;
  8352. $mysync->{ subfolder2 } = 'Pf2.S1.S2' ;
  8353. 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' ) ;
  8354. is( 'Pf2.S1.S2.INBOX', imap2_folder_name( $mysync, 'INBOX' ), 'imap2_folder_name: INBOX -> Pf2.S1.S2.INBOX' ) ;
  8355. 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' ) ;
  8356. is( 'Pf2.S1.S2.INBOX', imap2_folder_name( $mysync, 'Pf1/INBOX' ), 'imap2_folder_name: INBOX -> Pf2.S1.S2.INBOX' ) ;
  8357. # subfolder1
  8358. # scenario as the reverse of the previous tests, separators point of vue
  8359. $mysync = {} ;
  8360. $mysync->{ h1_prefix } = q{Pf1.} ;
  8361. $mysync->{ h2_prefix } = q{Pf2/} ;
  8362. $mysync->{ h1_sep } = '.';
  8363. $mysync->{ h2_sep } = '/';
  8364. #$mysync->{ debug } = 1 ;
  8365. $mysync->{ subfolder1 } = 'S1.S2' ;
  8366. 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' ) ;
  8367. 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' ) ;
  8368. is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.INBOX' ), 'imap2_folder_name: S1.S2.INBOX -> INBOX' ) ;
  8369. is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2' ), 'imap2_folder_name: S1.S2 -> INBOX' ) ;
  8370. is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.' ), 'imap2_folder_name: S1.S2. -> INBOX' ) ;
  8371. is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.INBOX' ), 'imap2_folder_name: Pf1.S1.S2.INBOX -> INBOX' ) ;
  8372. is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2' ), 'imap2_folder_name: Pf1.S1.S2 -> INBOX' ) ;
  8373. is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.' ), 'imap2_folder_name: Pf1.S1.S2. -> INBOX' ) ;
  8374. $mysync->{ subfolder1 } = 'S1.S2.' ;
  8375. 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' ) ;
  8376. 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' ) ;
  8377. is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.INBOX' ), 'imap2_folder_name: S1.S2.INBOX -> INBOX' ) ;
  8378. is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2' ), 'imap2_folder_name: S1.S2 -> INBOX' ) ;
  8379. is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.' ), 'imap2_folder_name: S1.S2. -> INBOX' ) ;
  8380. is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.INBOX' ), 'imap2_folder_name: Pf1.S1.S2.INBOX -> INBOX' ) ;
  8381. is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2' ), 'imap2_folder_name: Pf1.S1.S2 -> INBOX' ) ;
  8382. is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.' ), 'imap2_folder_name: Pf1.S1.S2. -> INBOX' ) ;
  8383. # subfolder1
  8384. # scenario as Gmail
  8385. $mysync = {} ;
  8386. $mysync->{ h1_prefix } = q{} ;
  8387. $mysync->{ h2_prefix } = q{} ;
  8388. $mysync->{ h1_sep } = '/';
  8389. $mysync->{ h2_sep } = '/';
  8390. #$mysync->{ debug } = 1 ;
  8391. $mysync->{ subfolder1 } = 'S1/S2' ;
  8392. is( 'F1/F2/F3', imap2_folder_name( $mysync, 'S1/S2/F1/F2/F3' ), 'imap2_folder_name: S1/S2/F1/F2/F3 -> F1/F2/F3' ) ;
  8393. is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/INBOX' ), 'imap2_folder_name: S1/S2/INBOX -> INBOX' ) ;
  8394. is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2' ), 'imap2_folder_name: S1/S2 -> INBOX' ) ;
  8395. is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/' ), 'imap2_folder_name: S1/S2/ -> INBOX' ) ;
  8396. $mysync->{ subfolder1 } = 'S1/S2/' ;
  8397. is( 'F1/F2/F3', imap2_folder_name( $mysync, 'S1/S2/F1/F2/F3' ), 'imap2_folder_name: S1/S2/F1/F2/F3 -> F1/F2/F3' ) ;
  8398. is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/INBOX' ), 'imap2_folder_name: S1/S2/INBOX -> INBOX' ) ;
  8399. is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2' ), 'imap2_folder_name: S1/S2 -> INBOX' ) ;
  8400. is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/' ), 'imap2_folder_name: S1/S2/ -> INBOX' ) ;
  8401. note( 'Leaving tests_imap2_folder_name()' ) ;
  8402. return ;
  8403. }
  8404. # Global variables to remove:
  8405. # None?
  8406. sub imap2_folder_name
  8407. {
  8408. my $mysync = shift @ARG ;
  8409. my ( $h1_fold ) = shift @ARG ;
  8410. my ( $h2_fold ) ;
  8411. if ( $mysync->{f1f2h}{ $h1_fold } ) {
  8412. $h2_fold = $mysync->{f1f2h}{ $h1_fold } ;
  8413. ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "f1f2 [$h1_fold] -> [$h2_fold]\n" ) ;
  8414. return( $h2_fold ) ;
  8415. }
  8416. if ( $mysync->{f1f2auto}{ $h1_fold } ) {
  8417. $h2_fold = $mysync->{f1f2auto}{ $h1_fold } ;
  8418. ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "automap [$h1_fold] -> [$h2_fold]\n" ) ;
  8419. return( $h2_fold ) ;
  8420. }
  8421. if ( $mysync->{ subfolder1 } )
  8422. {
  8423. my $esc_h1_sep = "\\" . $mysync->{ h1_sep } ;
  8424. # case where subfolder1 has the sep1 at the end, then remove it
  8425. my $part_to_removed = remove_last_char_if_is( $mysync->{ subfolder1 }, $mysync->{ h1_sep } ) ;
  8426. # remove the subfolder1 part and the sep1 if present after
  8427. $h1_fold =~ s{$part_to_removed($esc_h1_sep)?}{} ;
  8428. #myprint( "h1_fold=$h1_fold\n" ) ;
  8429. }
  8430. if ( ( q{} eq $h1_fold ) or ( $mysync->{ h1_prefix } eq $h1_fold ) )
  8431. {
  8432. $h1_fold = 'INBOX' ;
  8433. }
  8434. $h2_fold = prefix_seperator_invertion( $mysync, $h1_fold ) ;
  8435. $h2_fold = regextrans2( $mysync, $h2_fold ) ;
  8436. return( $h2_fold ) ;
  8437. }
  8438. sub tests_remove_last_char_if_is
  8439. {
  8440. note( 'Entering tests_remove_last_char_if_is()' ) ;
  8441. is( undef, remove_last_char_if_is( ), 'remove_last_char_if_is: no args => undef' ) ;
  8442. is( q{}, remove_last_char_if_is( q{} ), 'remove_last_char_if_is: empty => empty' ) ;
  8443. is( q{}, remove_last_char_if_is( q{}, 'Z' ), 'remove_last_char_if_is: empty Z => empty' ) ;
  8444. is( q{}, remove_last_char_if_is( 'Z', 'Z' ), 'remove_last_char_if_is: Z Z => empty' ) ;
  8445. is( 'abc', remove_last_char_if_is( 'abcZ', 'Z' ), 'remove_last_char_if_is: abcZ Z => abc' ) ;
  8446. is( 'abcY', remove_last_char_if_is( 'abcY', 'Z' ), 'remove_last_char_if_is: abcY Z => abcY' ) ;
  8447. note( 'Leaving tests_remove_last_char_if_is()' ) ;
  8448. return ;
  8449. }
  8450. sub remove_last_char_if_is
  8451. {
  8452. my $string = shift @ARG ;
  8453. my $char = shift @ARG ;
  8454. if ( ! defined $string )
  8455. {
  8456. return ;
  8457. }
  8458. if ( ! defined $char )
  8459. {
  8460. return $string ;
  8461. }
  8462. my $last_char = substr $string, -1 ;
  8463. if ( $char eq $last_char )
  8464. {
  8465. chop $string ;
  8466. return $string ;
  8467. }
  8468. else
  8469. {
  8470. return $string ;
  8471. }
  8472. }
  8473. sub tests_prefix_seperator_invertion
  8474. {
  8475. note( 'Entering tests_prefix_seperator_invertion()' ) ;
  8476. is( undef, prefix_seperator_invertion( ), 'prefix_seperator_invertion: no args => undef' ) ;
  8477. is( q{}, prefix_seperator_invertion( undef, q{} ), 'prefix_seperator_invertion: empty string => empty string' ) ;
  8478. is( 'lalala', prefix_seperator_invertion( undef, 'lalala' ), 'prefix_seperator_invertion: lalala => lalala' ) ;
  8479. is( 'lal/ala', prefix_seperator_invertion( undef, 'lal/ala' ), 'prefix_seperator_invertion: lal/ala => lal/ala' ) ;
  8480. is( 'lal.ala', prefix_seperator_invertion( undef, 'lal.ala' ), 'prefix_seperator_invertion: lal.ala => lal.ala' ) ;
  8481. is( '////', prefix_seperator_invertion( undef, '////' ), 'prefix_seperator_invertion: //// => ////' ) ;
  8482. is( '.....', prefix_seperator_invertion( undef, '.....' ), 'prefix_seperator_invertion: ..... => .....' ) ;
  8483. my $mysync = {
  8484. h1_prefix => q{},
  8485. h2_prefix => q{},
  8486. h1_sep => '/',
  8487. h2_sep => '/',
  8488. } ;
  8489. is( q{}, prefix_seperator_invertion( $mysync, q{} ), 'prefix_seperator_invertion: $mysync empty string => empty string' ) ;
  8490. is( 'lalala', prefix_seperator_invertion( $mysync, 'lalala' ), 'prefix_seperator_invertion: $mysync lalala => lalala' ) ;
  8491. is( 'lal/ala', prefix_seperator_invertion( $mysync, 'lal/ala' ), 'prefix_seperator_invertion: $mysync lal/ala => lal/ala' ) ;
  8492. is( 'lal.ala', prefix_seperator_invertion( $mysync, 'lal.ala' ), 'prefix_seperator_invertion: $mysync lal.ala => lal.ala' ) ;
  8493. is( '////', prefix_seperator_invertion( $mysync, '////' ), 'prefix_seperator_invertion: $mysync //// => ////' ) ;
  8494. is( '.....', prefix_seperator_invertion( $mysync, '.....' ), 'prefix_seperator_invertion: $mysync ..... => .....' ) ;
  8495. $mysync = {
  8496. h1_prefix => 'PPP',
  8497. h2_prefix => 'QQQ',
  8498. h1_sep => 's',
  8499. h2_sep => 't',
  8500. } ;
  8501. is( q{QQQ}, prefix_seperator_invertion( $mysync, q{} ), 'prefix_seperator_invertion: PPPQQQst empty string => QQQ' ) ;
  8502. is( 'QQQlalala', prefix_seperator_invertion( $mysync, 'lalala' ), 'prefix_seperator_invertion: PPPQQQst lalala => QQQlalala' ) ;
  8503. is( 'QQQlal/ala', prefix_seperator_invertion( $mysync, 'lal/ala' ), 'prefix_seperator_invertion: PPPQQQst lal/ala => QQQlal/ala' ) ;
  8504. is( 'QQQlal.ala', prefix_seperator_invertion( $mysync, 'lal.ala' ), 'prefix_seperator_invertion: PPPQQQst lal.ala => QQQlal.ala' ) ;
  8505. is( 'QQQ////', prefix_seperator_invertion( $mysync, '////' ), 'prefix_seperator_invertion: PPPQQQst //// => QQQ////' ) ;
  8506. is( 'QQQ.....', prefix_seperator_invertion( $mysync, '.....' ), 'prefix_seperator_invertion: PPPQQQst ..... => QQQ.....' ) ;
  8507. is( 'QQQPlalala', prefix_seperator_invertion( $mysync, 'PPPPlalala' ), 'prefix_seperator_invertion: PPPQQQst PPPPlalala => QQQPlalala' ) ;
  8508. is( 'QQQ', prefix_seperator_invertion( $mysync, 'PPP' ), 'prefix_seperator_invertion: PPPQQQst PPP => QQQ' ) ;
  8509. is( 'QQQttt', prefix_seperator_invertion( $mysync, 'sss' ), 'prefix_seperator_invertion: PPPQQQst sss => QQQttt' ) ;
  8510. is( 'QQQt', prefix_seperator_invertion( $mysync, 's' ), 'prefix_seperator_invertion: PPPQQQst s => QQQt' ) ;
  8511. is( 'QQQtAAAtBBB', prefix_seperator_invertion( $mysync, 'PPPsAAAsBBB' ), 'prefix_seperator_invertion: PPPQQQst PPPsAAAsBBB => QQQtAAAtBBB' ) ;
  8512. note( 'Leaving tests_prefix_seperator_invertion()' ) ;
  8513. return ;
  8514. }
  8515. # Global variables to remove:
  8516. sub prefix_seperator_invertion
  8517. {
  8518. my $mysync = shift @ARG ;
  8519. my $h1_fold = shift @ARG ;
  8520. my $h2_fold ;
  8521. if ( not defined $h1_fold ) { return ; }
  8522. my $my_h1_prefix = $mysync->{ h1_prefix } || q{} ;
  8523. my $my_h2_prefix = $mysync->{ h2_prefix } || q{} ;
  8524. my $my_h1_sep = $mysync->{ h1_sep } || '/' ;
  8525. my $my_h2_sep = $mysync->{ h2_sep } || '/' ;
  8526. # first we remove the prefix
  8527. $h1_fold =~ s/^\Q$my_h1_prefix\E//x ;
  8528. ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "removed host1 prefix: [$h1_fold]\n" ) ;
  8529. $h2_fold = separator_invert( $mysync, $h1_fold, $my_h1_sep, $my_h2_sep ) ;
  8530. ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "inverted separators: [$h2_fold]\n" ) ;
  8531. # Adding the prefix supplied by namespace or the --prefix2 option
  8532. # except for INBOX or Inbox
  8533. if ( $h2_fold !~ m/^INBOX$/xi )
  8534. {
  8535. $h2_fold = $my_h2_prefix . $h2_fold ;
  8536. }
  8537. ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "added host2 prefix: [$h2_fold]\n" ) ;
  8538. return( $h2_fold ) ;
  8539. }
  8540. sub tests_separator_invert
  8541. {
  8542. note( 'Entering tests_separator_invert()' ) ;
  8543. my $mysync = {} ;
  8544. $mysync->{ fixslash2 } = 0 ;
  8545. ok( not( defined separator_invert( ) ), 'separator_invert: no args' ) ;
  8546. ok( not( defined separator_invert( q{} ) ), 'separator_invert: not enough args' ) ;
  8547. ok( not( defined separator_invert( q{}, q{} ) ), 'separator_invert: not enough args' ) ;
  8548. ok( q{} eq separator_invert( $mysync, q{}, q{}, q{} ), 'separator_invert: 3 empty strings' ) ;
  8549. ok( 'lalala' eq separator_invert( $mysync, 'lalala', q{}, q{} ), 'separator_invert: empty separator' ) ;
  8550. ok( 'lalala' eq separator_invert( $mysync, 'lalala', '/', '/' ), 'separator_invert: same separator /' ) ;
  8551. ok( 'lal/ala' eq separator_invert( $mysync, 'lal/ala', '/', '/' ), 'separator_invert: same separator / 2' ) ;
  8552. ok( 'lal.ala' eq separator_invert( $mysync, 'lal/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
  8553. ok( 'lal/ala' eq separator_invert( $mysync, 'lal.ala', '.', '/' ), 'separator_invert: separators ./' ) ;
  8554. ok( 'la.l/ala' eq separator_invert( $mysync, 'la/l.ala', '.', '/' ), 'separator_invert: separators ./' ) ;
  8555. ok( 'l/al.ala' eq separator_invert( $mysync, 'l.al/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
  8556. $mysync->{ fixslash2 } = 1 ;
  8557. ok( 'l_al.ala' eq separator_invert( $mysync, 'l.al/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
  8558. note( 'Leaving tests_separator_invert()' ) ;
  8559. return ;
  8560. }
  8561. # Global variables to remove:
  8562. #
  8563. sub separator_invert
  8564. {
  8565. my( $mysync, $h1_fold, $h1_separator, $h2_separator ) = @_ ;
  8566. return( undef ) if ( not all_defined( $mysync, $h1_fold, $h1_separator, $h2_separator ) ) ;
  8567. # The separator we hope we'll never encounter: 00000000 == 0x00
  8568. my $o_sep = "\000" ;
  8569. my $h2_fold = $h1_fold ;
  8570. $h2_fold =~ s,\Q$h2_separator,$o_sep,xg ;
  8571. $h2_fold =~ s,\Q$h1_separator,$h2_separator,xg ;
  8572. $h2_fold =~ s,\Q$o_sep,$h1_separator,xg ;
  8573. $h2_fold =~ s,/,_,xg if( $mysync->{ fixslash2 } and '/' ne $h2_separator and '/' eq $h1_separator ) ;
  8574. return( $h2_fold ) ;
  8575. }
  8576. sub regextrans2
  8577. {
  8578. my( $mysync, $h2_fold ) = @_ ;
  8579. # Transforming the folder name by the --regextrans2 option(s)
  8580. foreach my $regextrans2 ( @{ $mysync->{ regextrans2 } } ) {
  8581. my $h2_fold_before = $h2_fold ;
  8582. my $ret = eval "\$h2_fold =~ $regextrans2 ; 1 " ;
  8583. ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "[$h2_fold_before] -> [$h2_fold] using regextrans2 [$regextrans2]\n" ) ;
  8584. if ( not ( defined $ret ) or $EVAL_ERROR ) {
  8585. $mysync->{nb_errors}++ ;
  8586. exit_clean( $mysync, $EX_USAGE,
  8587. "error: eval regextrans2 '$regextrans2': $EVAL_ERROR\n"
  8588. ) ;
  8589. }
  8590. }
  8591. return( $h2_fold ) ;
  8592. }
  8593. sub tests_decompose_regex
  8594. {
  8595. note( 'Entering tests_decompose_regex()' ) ;
  8596. ok( 1, 'decompose_regex 1' ) ;
  8597. ok( 0 == compare_lists( [ q{}, q{} ], [ decompose_regex( q{} ) ] ), 'decompose_regex empty string' ) ;
  8598. ok( 0 == compare_lists( [ '.*', 'lala' ], [ decompose_regex( 's/.*/lala/' ) ] ), 'decompose_regex s/.*/lala/' ) ;
  8599. note( 'Leaving tests_decompose_regex()' ) ;
  8600. return ;
  8601. }
  8602. sub decompose_regex
  8603. {
  8604. my $regex = shift @ARG ;
  8605. my( $left_part, $right_part ) ;
  8606. ( $left_part, $right_part ) = $regex =~ m{^s/((?:[^/]|\\/)+)/((?:[^/]|\\/)+)/}x;
  8607. return( q{}, q{} ) if not $left_part ;
  8608. return( $left_part, $right_part ) ;
  8609. }
  8610. sub tests_timenext
  8611. {
  8612. note( 'Entering tests_timenext()' ) ;
  8613. is( undef, timenext( ), 'timenext: no args => undef' ) ;
  8614. my $mysync ;
  8615. is( undef, timenext( $mysync ), 'timenext: undef => undef' ) ;
  8616. $mysync = {} ;
  8617. ok( time - timenext( $mysync ) <= 1e-02, 'timenext: defined first time => ~ time' ) ;
  8618. ok( timenext( $mysync ) <= 1e-02, 'timenext: second time => less than 1e-02' ) ;
  8619. ok( timenext( $mysync ) <= 1e-02, 'timenext: third time => less than 1e-02' ) ;
  8620. note( 'Leaving tests_timenext()' ) ;
  8621. return ;
  8622. }
  8623. sub timenext
  8624. {
  8625. my $mysync = shift @ARG ;
  8626. if ( ! defined $mysync )
  8627. {
  8628. return ;
  8629. }
  8630. my ( $timenow, $timediff ) ;
  8631. $mysync->{ timebefore } ||= 0; # epoch...
  8632. $timenow = time ;
  8633. $timediff = $timenow - $mysync->{ timebefore } ;
  8634. $mysync->{ timebefore } = $timenow ;
  8635. # myprint( "timenext: $timediff\n" ) ;
  8636. return( $timediff ) ;
  8637. }
  8638. sub tests_timesince
  8639. {
  8640. note( 'Entering tests_timesince()' ) ;
  8641. ok( timesince( time - 1 ) - 1 <= 1e-02, 'timesince: time - 1 => <= 1 + 1e-02' ) ;
  8642. ok( timesince( time ) <= 1e-02, 'timesince: time => <= 1e-02' ) ;
  8643. ok( timesince( ) - time <= 1e-02, 'timesince: no args => <= time + 1e-02' ) ;
  8644. note( 'Leaving tests_timesince()' ) ;
  8645. return ;
  8646. }
  8647. sub timesince
  8648. {
  8649. my $timeinit = shift || 0 ;
  8650. my ( $timenow, $timediff ) ;
  8651. $timenow = time ;
  8652. $timediff = $timenow - $timeinit ;
  8653. # Often used in a division so no 0 but a nano second.
  8654. return( max( $timediff, min( 1e-09, $timediff ) ) ) ;
  8655. }
  8656. sub tests_regexflags
  8657. {
  8658. note( 'Entering tests_regexflags()' ) ;
  8659. my $mysync = {} ;
  8660. ok( q{} eq regexflags( $mysync, q{} ), 'regexflags, null string q{}' ) ;
  8661. ok( q{\Seen NonJunk $Spam} eq regexflags( $mysync, q{\Seen NonJunk $Spam} ), q{regexflags, nothing to do} ) ;
  8662. @{ $mysync->{ regexflag } } = ('I am BAD' ) ;
  8663. ok( not ( defined regexflags( $mysync, q{} ) ), 'regexflags, bad regex' ) ;
  8664. @{ $mysync->{ regexflag } } = ( 's/NonJunk//g' ) ;
  8665. ok( q{\Seen $Spam} eq regexflags( $mysync, q{\Seen NonJunk $Spam} ), q{regexflags, remove NonJunk: 's/NonJunk//g'} ) ;
  8666. @{ $mysync->{ regexflag } } = ( q{s/\$Spam//g} ) ;
  8667. ok( q{\Seen NonJunk } eq regexflags( $mysync, q{\Seen NonJunk $Spam} ), q{regexflags, remove $Spam: 's/\$Spam//g'} ) ;
  8668. @{ $mysync->{ regexflag } } = ( 's/\\\\Seen//g' ) ;
  8669. ok( q{ NonJunk $Spam} eq regexflags( $mysync, q{\Seen NonJunk $Spam} ), q{regexflags, remove \Seen: 's/\\\\\\\\Seen//g'} ) ;
  8670. @{ $mysync->{ regexflag } } = ( 's/(\s|^)[^\\\\]\w+//g' ) ;
  8671. ok( q{\Seen \Middle \End} eq regexflags( $mysync, q{\Seen NonJunk \Middle $Spam \End} ), q{regexflags: only \word among \Seen NonJunk \Middle $Spam \End} ) ;
  8672. ok( q{ \Seen \Middle \End1} eq regexflags( $mysync, q{Begin \Seen NonJunk \Middle $Spam \End1 End} ),
  8673. q{regexflags: only \word among Begin \Seen NonJunk \Middle $Spam \End1 End} ) ;
  8674. @{ $mysync->{ regexflag } } = ( q{s/.*?(Keep1|Keep2|Keep3)/$1 /g} ) ;
  8675. ok( 'Keep1 Keep2 ReB' eq regexflags( $mysync, 'ReA Keep1 REM Keep2 ReB' ), 'Keep only regex' ) ;
  8676. ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM REM Keep1 Keep2' ), 'Keep only regex' ) ;
  8677. ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 REM REM Keep2' ), 'Keep only regex' ) ;
  8678. ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM Keep1 REM REM Keep2' ), 'Keep only regex' ) ;
  8679. ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 Keep2' ), 'Keep only regex' ) ;
  8680. ok( 'Keep1 ' eq regexflags( $mysync, 'REM Keep1' ), 'Keep only regex' ) ;
  8681. @{ $mysync->{ regexflag } } = ( q{s/(Keep1|Keep2|Keep3) (?!(Keep1|Keep2|Keep3)).*/$1 /g} ) ;
  8682. ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 Keep2 ReB' ), 'Keep only regex' ) ;
  8683. ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 Keep2 REM REM REM' ), 'Keep only regex' ) ;
  8684. ok( 'Keep2 ' eq regexflags( $mysync, 'Keep2 REM REM REM' ), 'Keep only regex' ) ;
  8685. @{ $mysync->{ regexflag } } = ( q{s/.*?(Keep1|Keep2|Keep3)/$1 /g},
  8686. 's/(Keep1|Keep2|Keep3) (?!(Keep1|Keep2|Keep3)).*/$1 /g' ) ;
  8687. ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM Keep1 REM Keep2 REM' ), 'Keep only regex' ) ;
  8688. ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 REM Keep2 REM' ), 'Keep only regex' ) ;
  8689. ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM Keep1 Keep2 REM' ), 'Keep only regex' ) ;
  8690. ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM Keep1 REM Keep2' ), 'Keep only regex' ) ;
  8691. ok( 'Keep1 Keep2 Keep3 ' eq regexflags( $mysync, 'REM Keep1 REM Keep2 REM REM Keep3 REM' ), 'Keep only regex' ) ;
  8692. ok( 'Keep1 ' eq regexflags( $mysync, 'REM REM Keep1 REM REM REM ' ), 'Keep only regex' ) ;
  8693. ok( 'Keep1 Keep3 ' eq regexflags( $mysync, 'RE1 Keep1 RE2 Keep3 RE3 RE4 RE5 ' ), 'Keep only regex' ) ;
  8694. @{ $mysync->{ regexflag } } = ( 's/(.*)/$1 jrdH8u/' ) ;
  8695. ok('REM REM REM REM REM jrdH8u' eq regexflags( $mysync, 'REM REM REM REM REM' ), q{Add jrdH8u 's/(.*)/\$1 jrdH8u/'} ) ;
  8696. @{ $mysync->{ regexflag } } = ('s/jrdH8u *//' );
  8697. ok('REM REM REM REM REM ' eq regexflags( $mysync, 'REM REM REM REM REM jrdH8u' ), q{Remove jrdH8u s/jrdH8u *//} ) ;
  8698. @{ $mysync->{ regexflag } } = (
  8699. 's/.*?(?:(\\\\(?:Answered|Flagged|Deleted|Seen|Draft)\s?)|$)/defined($1)?$1:q()/eg'
  8700. );
  8701. ok( '\\Deleted \\Answered '
  8702. eq regexflags( $mysync, 'Blabla \$Junk \\Deleted machin \\Answered truc' ),
  8703. 'Keep only regex: Exchange case (Phil)' ) ;
  8704. ok( q{} eq regexflags( $mysync, q{} ), 'Keep only regex: Exchange case, null string (Phil)' ) ;
  8705. ok( q{}
  8706. eq regexflags( $mysync, 'Blabla $Junk machin truc' ),
  8707. 'Keep only regex: Exchange case, no accepted flags (Phil)' ) ;
  8708. ok('\\Deleted \\Answered \\Draft \\Flagged '
  8709. eq regexflags( $mysync, '\\Deleted \\Answered \\Draft \\Flagged ' ),
  8710. 'Keep only regex: Exchange case (Phil)' ) ;
  8711. @{ $mysync->{ regexflag } } = ( 's/\\\\Flagged//g' ) ;
  8712. is('\Deleted \Answered \Draft ',
  8713. regexflags( $mysync, '\\Deleted \\Answered \\Draft \\Flagged ' ),
  8714. 'regexflags: remove \Flagged 1' ) ;
  8715. is('\\Deleted \\Answered \\Draft',
  8716. regexflags( $mysync, '\\Deleted \\Flagged \\Answered \\Draft' ),
  8717. 'regexflags: remove \Flagged 2' ) ;
  8718. # I didn't understand why it gives \F
  8719. # https://perldoc.perl.org/perlrebackslash.html
  8720. # \F Foldcase till \E. Not in [].
  8721. # https://perldoc.perl.org/functions/fc.html
  8722. # \F Not available in old Perl so I comment the test
  8723. # @{ $mysync->{ regexflag } } = ( 's/\\Flagged/X/g' ) ;
  8724. #is('\Deleted FX \Answered \FX \Draft \FX',
  8725. #regexflags( '\Deleted Flagged \Answered \Flagged \Draft \Flagged' ),
  8726. # 'regexflags: remove \Flagged 3 mistery...' ) ;
  8727. is( '\\ \ ', '\ \\ ', 'regexflags: \\ \ is \ \\ ' ) ;
  8728. note( 'Leaving tests_regexflags()' ) ;
  8729. return ;
  8730. }
  8731. sub regexflags
  8732. {
  8733. my $mysync = shift @ARG ;
  8734. my $flags = shift @ARG ;
  8735. foreach my $regexflag ( @{ $mysync->{ regexflag } } )
  8736. {
  8737. my $flags_orig = $flags ;
  8738. $mysync->{ debugflags } and myprint( "eval \$flags =~ $regexflag\n" ) ;
  8739. my $ret = eval "\$flags =~ $regexflag ; 1 " ;
  8740. $mysync->{ debugflags } and myprint( "regexflag $regexflag [$flags_orig] -> [$flags]\n" ) ;
  8741. if( not ( defined $ret ) or $EVAL_ERROR ) {
  8742. myprint( "Error: eval regexflag '$regexflag': $EVAL_ERROR\n" ) ;
  8743. return( undef ) ;
  8744. }
  8745. }
  8746. return( $flags ) ;
  8747. }
  8748. sub tests_filterbuggyflags
  8749. {
  8750. note( 'Entering tests_regexflags()' ) ;
  8751. my $mysync = {} ;
  8752. $mysync->{ regexflag } = [ ] ;
  8753. $mysync->{ filterbuggyflags } = 1 ;
  8754. filterbuggyflags( $mysync ) ;
  8755. #
  8756. is( '\Deleted \Answered \Draft \Flagged ',
  8757. regexflags( $mysync, '\\Deleted \\Answered \\RECEIPTCHECKED \\Draft \\Indexed \\Flagged \\JUNK \\Junk' ),
  8758. 'regexflags: remove famous /X 1' ) ;
  8759. is( '\\Deleted \\Flagged \\Answered \\Draft',
  8760. regexflags( $mysync, '\\Deleted \\RECEIPTCHECKED \\Flagged \\Answered \\Indexed \\Draft' ),
  8761. 'regexflags: remove famous /X 2' ) ;
  8762. is( '\ ', '\\ ', 'regexflags: \ is \\ ' ) ;
  8763. is( '\\ ', '\\ ', 'regexflags: \\ is \\ ' ) ;
  8764. note( 'Leaving tests_regexflags()' ) ;
  8765. return ;
  8766. }
  8767. sub buggyflagsregex
  8768. {
  8769. # From /X analyse
  8770. # cut -d: -f1 Error_112_all_syncs.txt | xargs egrep -oih 'Invalid system flag [^( ]+' | sort | uniq -c | sort -g
  8771. my @buggyflagsregex = ( 's/\\\\RECEIPTCHECKED|\\\\JUNK|\\\\Indexed|\\\\X-EON-HAS-ATTACHMENT|\\\\UNSEEN|\\\\ATTACHED|\\\\X-HAS-ATTACH|\\\\FORWARDED|\\\\FORWARD|\\\\X-FORWARDED|\\\\\$FORWARDED|\\\\PRIORITY|\\\\READRCPT//gi' ) ;
  8772. return( @buggyflagsregex ) ;
  8773. }
  8774. sub filterbuggyflags
  8775. {
  8776. my $mysync = shift @ARG ;
  8777. if ( $mysync->{ filterbuggyflags } )
  8778. {
  8779. unshift @{ $mysync->{ regexflag } }, buggyflagsregex( ) ;
  8780. }
  8781. return ;
  8782. }
  8783. sub tests_remove_doublequotes_if_any
  8784. {
  8785. note( 'Entering tests_remove_doublequotes_if_any()' ) ;
  8786. # the number of tests is stupid here
  8787. is( undef, remove_doublequotes_if_any( ), 'remove_doublequotes_if_any: no args => undef' ) ;
  8788. is( q{}, remove_doublequotes_if_any( q{} ), 'remove_doublequotes_if_any: empty string => empty string' ) ;
  8789. is( q{}, remove_doublequotes_if_any( q{""} ), 'remove_doublequotes_if_any: double-quotes => empty string' ) ;
  8790. is( q{}, remove_doublequotes_if_any( q{"""} ), 'remove_doublequotes_if_any: double-quotes => empty string' ) ;
  8791. is( q{}, remove_doublequotes_if_any( q{"""} ), 'remove_doublequotes_if_any: double-quotes => empty string' ) ;
  8792. is( q{toto}, remove_doublequotes_if_any( q{"toto"} ), 'remove_doublequotes_if_any: "toto" => toto' ) ;
  8793. is( q{toto}, remove_doublequotes_if_any( q{toto} ), 'remove_doublequotes_if_any: toto => toto' ) ;
  8794. is( q{toto}, remove_doublequotes_if_any( q{to"to} ), 'remove_doublequotes_if_any: to"to => toto' ) ;
  8795. is( q{toto}, remove_doublequotes_if_any( q{toto"} ), 'remove_doublequotes_if_any: toto" => toto' ) ;
  8796. is( q{toto}, remove_doublequotes_if_any( q{"toto} ), 'remove_doublequotes_if_any: "toto => toto' ) ;
  8797. is( q{toto}, remove_doublequotes_if_any( q{"to"to} ), 'remove_doublequotes_if_any: "to"to => toto' ) ;
  8798. is( q{toto}, remove_doublequotes_if_any( q{to"to"} ), 'remove_doublequotes_if_any: to"to" => toto' ) ;
  8799. is( q{toto}, remove_doublequotes_if_any( q{to\"to} ), 'remove_doublequotes_if_any: to\"to => toto' ) ;
  8800. is( q{toto}, remove_doublequotes_if_any( q{toto\"} ), 'remove_doublequotes_if_any: toto\" => toto' ) ;
  8801. is( q{toto}, remove_doublequotes_if_any( q{\"toto} ), 'remove_doublequotes_if_any: \"toto => toto' ) ;
  8802. is( q{toto}, remove_doublequotes_if_any( q{\"to\"to} ), 'remove_doublequotes_if_any: \"to\"to => toto' ) ;
  8803. is( q{toto}, remove_doublequotes_if_any( q{to\"to\"} ), 'remove_doublequotes_if_any: to\"to" => toto' ) ;
  8804. note( 'Leaving tests_remove_doublequotes_if_any()' ) ;
  8805. return ;
  8806. }
  8807. sub remove_doublequotes_if_any
  8808. {
  8809. my $string = shift @ARG ;
  8810. if ( ! defined $string ) { return ; }
  8811. $string =~ s/\\\"//g ;
  8812. $string =~ tr/"//d ;
  8813. return $string ;
  8814. }
  8815. # No globals here
  8816. sub acls_sync
  8817. {
  8818. # https://tools.ietf.org/html/rfc4314
  8819. # Standard Rights:
  8820. # https://tools.ietf.org/html/rfc4314#section-2.1
  8821. my( $mysync, $h1_fold, $h2_fold ) = @_ ;
  8822. if ( $mysync->{ syncacls } ) {
  8823. my $h1_hash = $mysync->{imap1}->getacl($h1_fold)
  8824. or myprint( "Host1: Could not getacl for $h1_fold: $EVAL_ERROR\n" ) ;
  8825. my $h2_hash = $mysync->{imap2}->getacl($h2_fold)
  8826. or myprint( "Host2: Could not getacl for $h2_fold: $EVAL_ERROR\n" ) ;
  8827. my %users = map { ($_, 1) } ( keys %{ $h1_hash} , keys %{ $h2_hash } ) ;
  8828. foreach my $user (sort keys %users ) {
  8829. my $h1_acl = remove_doublequotes_if_any( $h1_hash->{$user} ) || '' ;
  8830. my $h2_acl = remove_doublequotes_if_any( $h2_hash->{$user} ) || '' ;
  8831. myprint( "Host1: user $user has acl [$h1_acl] on host1\n" ) ;
  8832. myprint( "Host2: user $user has acl [$h2_acl] on host2\n" ) ;
  8833. # removes surrounding double-quotes if any
  8834. my $user_no_quotes = remove_doublequotes_if_any( $user ) ;
  8835. if ( $h1_hash->{$user}
  8836. && $h2_hash->{$user}
  8837. && $h1_hash->{$user} eq $h2_hash->{$user} )
  8838. {
  8839. myprint( "Host2: user $user_no_quotes has already the same acl, no need to set it.\n" ) ;
  8840. next ;
  8841. }
  8842. myprint( "Host2: setting acl for folder $h2_fold user $user_no_quotes acl $h1_acl $mysync->{dry_message}\n" ) ;
  8843. unless ( $mysync->{dry} ) {
  8844. $mysync->{imap2}->setacl( $h2_fold, $user_no_quotes, $h1_acl )
  8845. or myprint( "Could not set acl for user $user_no_quotes on host2: $EVAL_ERROR\n" ) ;
  8846. }
  8847. }
  8848. }
  8849. return ;
  8850. }
  8851. sub tests_permanentflags
  8852. {
  8853. note( 'Entering tests_permanentflags()' ) ;
  8854. my $mysync = { } ;
  8855. ok( q{} eq permanentflags( $mysync, ' * OK [PERMANENTFLAGS (\* \Draft \Answered)] Limited' ),
  8856. 'permanentflags \*' ) ;
  8857. ok( '\Draft \Answered' eq permanentflags( $mysync, ' * OK [PERMANENTFLAGS (\Draft \Answered)] Limited' ),
  8858. 'permanentflags \Draft \Answered' ) ;
  8859. ok( '\Draft \Answered'
  8860. eq permanentflags( $mysync, 'Blabla',
  8861. ' * OK [PERMANENTFLAGS (\Draft \Answered)] Limited',
  8862. 'Blabla' ),
  8863. 'permanentflags \Draft \Answered'
  8864. ) ;
  8865. ok( q{} eq permanentflags( $mysync, 'Blabla' ), 'permanentflags nothing' ) ;
  8866. note( 'Leaving tests_permanentflags()' ) ;
  8867. return ;
  8868. }
  8869. sub permanentflags
  8870. {
  8871. my $mysync = shift @ARG ;
  8872. my @lines = @ARG ;
  8873. foreach my $line ( @lines ) {
  8874. if ( $line =~ m{\[PERMANENTFLAGS\s\(([^)]+?)\)\]}x ) {
  8875. ( $mysync->{ debugflags } or $mysync->{ debug } ) and myprint( "permanentflags: $line" ) ;
  8876. my $permanentflags = $1 ;
  8877. if ( $permanentflags =~ m{\\\*}x )
  8878. {
  8879. $permanentflags = q{} ;
  8880. }
  8881. return( $permanentflags ) ;
  8882. } ;
  8883. }
  8884. return( q{} ) ;
  8885. }
  8886. sub tests_flags_filter
  8887. {
  8888. note( 'Entering tests_flags_filter()' ) ;
  8889. ok( '\Seen' eq flags_filter('\Seen', '\Draft \Seen \Answered'), 'flags_filter ' );
  8890. ok( q{} eq flags_filter('\Seen', '\Draft \Answered'), 'flags_filter ' );
  8891. ok( '\Seen' eq flags_filter('\Seen', '\Seen'), 'flags_filter ' );
  8892. ok( '\Seen' eq flags_filter('\Seen', ' \Seen '), 'flags_filter ' );
  8893. ok( '\Seen \Draft'
  8894. eq flags_filter('\Seen \Draft', '\Draft \Seen \Answered'), 'flags_filter ' );
  8895. ok( '\Seen \Draft'
  8896. eq flags_filter('\Seen \Draft', ' \Draft \Seen \Answered '), 'flags_filter ' );
  8897. note( 'Leaving tests_flags_filter()' ) ;
  8898. return ;
  8899. }
  8900. sub flags_filter
  8901. {
  8902. my( $flags, $allowed_flags ) = @_ ;
  8903. my @flags = split /\s+/x, $flags ;
  8904. my %allowed_flags = map { $_ => 1 } split q{ }, $allowed_flags ;
  8905. my @flags_out = map { exists $allowed_flags{$_} ? $_ : () } @flags ;
  8906. my $flags_out = join q{ }, @flags_out ;
  8907. return( $flags_out ) ;
  8908. }
  8909. sub tests_flagscase
  8910. {
  8911. note( 'Entering tests_flagscase()' ) ;
  8912. ok( '\Seen' eq flagscase( '\Seen' ), 'flagscase: \Seen -> \Seen' ) ;
  8913. ok( '\Seen' eq flagscase( '\SEEN' ), 'flagscase: \SEEN -> \Seen' ) ;
  8914. ok( '\Seen \Draft' eq flagscase( '\SEEN \DRAFT' ), 'flagscase: \SEEN \DRAFT -> \Seen \Draft' ) ;
  8915. ok( '\Draft \Seen' eq flagscase( '\DRAFT \SEEN' ), 'flagscase: \DRAFT \SEEN -> \Draft \Seen' ) ;
  8916. ok( '\Draft LALA \Seen' eq flagscase( '\DRAFT LALA \SEEN' ), 'flagscase: \DRAFT LALA \SEEN -> \Draft LALA \Seen' ) ;
  8917. ok( '\Draft lala \Seen' eq flagscase( '\DRAFT lala \SEEN' ), 'flagscase: \DRAFT lala \SEEN -> \Draft lala \Seen' ) ;
  8918. note( 'Leaving tests_flagscase()' ) ;
  8919. return ;
  8920. }
  8921. sub flagscase
  8922. {
  8923. my $flags = shift @ARG ;
  8924. my @flags = split /\s+/x, $flags ;
  8925. my %rfc_flags = map { $_ => 1 } split q{ }, '\Answered \Flagged \Deleted \Seen \Draft' ;
  8926. my @flags_out = map { exists $rfc_flags{ ucsecond( lc $_ ) } ? ucsecond( lc $_ ) : $_ } @flags ;
  8927. my $flags_out = join q{ }, @flags_out ;
  8928. return( $flags_out ) ;
  8929. }
  8930. sub tests_flags_for_host2
  8931. {
  8932. note( 'Entering tests_flags_for_host2()' ) ;
  8933. is( undef, flags_for_host2( ), 'flags_for_host2: no args => undef' ) ;
  8934. my $mysync ;
  8935. is( undef, flags_for_host2( $mysync ), 'flags_for_host2: undef => undef' ) ;
  8936. $mysync = { } ;
  8937. is( undef, flags_for_host2( $mysync ), 'flags_for_host2: nothing => undef' ) ;
  8938. is( q{}, flags_for_host2( $mysync, '' ), 'flags_for_host2: no flags => empty string' ) ;
  8939. is( q{}, flags_for_host2( $mysync, '\Recent' ), 'flags_for_host2: \Recent => empty string' ) ;
  8940. is( q{\Seen}, flags_for_host2( $mysync, '\Recent \Seen' ), 'flags_for_host2: \Recent \Seen => \Seen' ) ;
  8941. is( q{\Deleted \Seen}, flags_for_host2( $mysync, '\Deleted \Recent \Seen' ), 'flags_for_host2: \Deleted \Recent \Seen => \Deleted \Seen' ) ;
  8942. $mysync->{ flagscase } = 0 ;
  8943. is( q{\DELETED \Seen}, flags_for_host2( $mysync, '\DELETED \Seen' ), 'flags_for_host2: flagscase = 0 \DELETED \Seen => \DELETED \Seen' ) ;
  8944. $mysync->{ flagscase } = 1 ;
  8945. is( q{\Deleted \Seen}, flags_for_host2( $mysync, '\DELETED \Seen' ), 'flags_for_host2: flagscase = 1 \DELETED \Seen => \Deleted \Seen' ) ;
  8946. $mysync->{ filterflags } = 0 ;
  8947. is( q{\Seen \Blabla}, flags_for_host2( $mysync, '\Seen \Blabla', '\Seen \Junk' ), 'flags_for_host2: filterflags = 0 \Seen \Blabla among \Seen \Junk => \Seen \Blabla' ) ;
  8948. $mysync->{ filterflags } = 1 ;
  8949. is( q{\Seen}, flags_for_host2( $mysync, '\Seen \Blabla', '\Seen \Junk' ), 'flags_for_host2: filterflags = 1 \Seen \Blabla among \Seen \Junk => \Seen' ) ;
  8950. $mysync->{ filterflags } = 1 ;
  8951. is( q{\Seen \Blabla}, flags_for_host2( $mysync, '\Seen \Blabla', '' ), 'flags_for_host2: filterflags = 1 \Seen \Blabla among "" => \Seen \Blabla' ) ;
  8952. note( 'Leaving tests_flags_for_host2()' ) ;
  8953. return ;
  8954. }
  8955. sub flags_for_host2
  8956. {
  8957. my $mysync = shift @ARG ;
  8958. my $h1_flags = shift @ARG ;
  8959. my $permanentflags2 = shift @ARG ;
  8960. if ( ! all_defined( $mysync, $h1_flags ) ) { return ; } ;
  8961. # RFC 2060: This flag can not be altered by any client
  8962. $h1_flags =~ s@\\Recent\s?@@xgi ;
  8963. my $h1_flags_re ;
  8964. if ( $mysync->{ regexflag } and defined( $h1_flags_re = regexflags( $mysync, $h1_flags ) ) ) {
  8965. $h1_flags = $h1_flags_re ;
  8966. }
  8967. if ( $mysync->{ flagscase } )
  8968. {
  8969. $h1_flags = flagscase( $h1_flags ) ;
  8970. }
  8971. if ( $permanentflags2 and $mysync->{ filterflags } )
  8972. {
  8973. $h1_flags = flags_filter( $h1_flags, $permanentflags2 ) ;
  8974. }
  8975. return( $h1_flags ) ;
  8976. }
  8977. sub ucsecond
  8978. {
  8979. my $string = shift @ARG ;
  8980. my $output ;
  8981. return( $string ) if ( 1 >= length $string ) ;
  8982. $output = ( substr( $string, 0, 1) ) . ( uc substr $string, 1, 1 ) . ( substr $string, 2 ) ;
  8983. #myprint( "UUU $string -> $output\n" ) ;
  8984. return( $output ) ;
  8985. }
  8986. sub tests_ucsecond
  8987. {
  8988. note( 'Entering tests_ucsecond()' ) ;
  8989. ok( 'aBcde' eq ucsecond( 'abcde' ), 'ucsecond: abcde -> aBcde' ) ;
  8990. ok( 'ABCDE' eq ucsecond( 'ABCDE' ), 'ucsecond: ABCDE -> ABCDE' ) ;
  8991. ok( 'ABCDE' eq ucsecond( 'AbCDE' ), 'ucsecond: AbCDE -> ABCDE' ) ;
  8992. ok( 'ABCde' eq ucsecond( 'AbCde' ), 'ucsecond: AbCde -> ABCde' ) ;
  8993. ok( 'A' eq ucsecond( 'A' ), 'ucsecond: A -> A' ) ;
  8994. ok( 'AB' eq ucsecond( 'Ab' ), 'ucsecond: Ab -> AB' ) ;
  8995. ok( '\B' eq ucsecond( '\b' ), 'ucsecond: \b -> \B' ) ;
  8996. ok( '\Bcde' eq ucsecond( '\bcde' ), 'ucsecond: \bcde -> \Bcde' ) ;
  8997. note( 'Leaving tests_ucsecond()' ) ;
  8998. return ;
  8999. }
  9000. sub select_msgs
  9001. {
  9002. my ( $imap, $msgs_all_hash_ref, $search_cmd, $abletosearch, $folder ) = @_ ;
  9003. my ( @msgs ) ;
  9004. if ( $abletosearch ) {
  9005. @msgs = select_msgs_by_search( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) ;
  9006. }else{
  9007. @msgs = select_msgs_by_fetch( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) ;
  9008. }
  9009. return( @msgs ) ;
  9010. }
  9011. sub select_msgs_by_search
  9012. {
  9013. my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ;
  9014. my ( @msgs, @msgs_all ) ;
  9015. # Need to have the whole list in msgs_all_hash_ref
  9016. # without calling messages() several times.
  9017. # Need all messages list to avoid deleting useful cache part
  9018. # in case of --search or --minage or --maxage
  9019. if ( ( defined $msgs_all_hash_ref and $sync->{ usecache } )
  9020. or ( not defined $maxage and not defined $minage and not defined $search_cmd )
  9021. ) {
  9022. $debugdev and myprint( "Calling messages()\n" ) ;
  9023. @msgs_all = $imap->messages( ) ;
  9024. return if ( $#msgs_all == 0 && !defined $msgs_all[0] ) ;
  9025. if ( defined $msgs_all_hash_ref ) {
  9026. @{ $msgs_all_hash_ref }{ @msgs_all } = () ;
  9027. }
  9028. # return all messages
  9029. if ( not defined $maxage and not defined $minage and not defined $search_cmd ) {
  9030. return( @msgs_all ) ;
  9031. }
  9032. }
  9033. if ( defined $search_cmd ) {
  9034. @msgs = $imap->search( $search_cmd ) ;
  9035. return( @msgs ) ;
  9036. }
  9037. # we are here only if $maxage or $minage is defined
  9038. @msgs = select_msgs_by_age( $imap ) ;
  9039. return( @msgs );
  9040. }
  9041. sub select_msgs_by_fetch
  9042. {
  9043. my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ;
  9044. my ( @msgs, @msgs_all, %fetch ) ;
  9045. # Need to have the whole list in msgs_all_hash_ref
  9046. # without calling messages() several times.
  9047. # Need all messages list to avoid deleting useful cache part
  9048. # in case of --search or --minage or --maxage
  9049. $debugdev and myprint( "Calling fetch_hash()\n" ) ;
  9050. my $fetch_hash_uids = $fetch_hash_set || "1:*" ;
  9051. %fetch = %{$imap->fetch_hash( $fetch_hash_uids, 'INTERNALDATE' ) } ;
  9052. @msgs_all = sort { $a <=> $b } keys %fetch ;
  9053. $debugdev and myprint( "Done fetch_hash()\n" ) ;
  9054. return if ( $#msgs_all == 0 && !defined $msgs_all[0] ) ;
  9055. if ( defined $msgs_all_hash_ref ) {
  9056. @{ $msgs_all_hash_ref }{ @msgs_all } = () ;
  9057. }
  9058. # return all messages
  9059. if ( not defined $maxage and not defined $minage and not defined $search_cmd ) {
  9060. return( @msgs_all ) ;
  9061. }
  9062. if ( defined $search_cmd ) {
  9063. myprint( "Warning: strange to see --search with --noabletosearch, an error can happen\n" ) ;
  9064. @msgs = $imap->search( $search_cmd ) ;
  9065. return( @msgs ) ;
  9066. }
  9067. # we are here only if $maxage or $minage is defined
  9068. my( @max, @min, $maxage_epoch, $minage_epoch ) ;
  9069. if ( defined $maxage ) { $maxage_epoch = $timestart_int - $NB_SECONDS_IN_A_DAY * $maxage ; }
  9070. if ( defined $minage ) { $minage_epoch = $timestart_int - $NB_SECONDS_IN_A_DAY * $minage ; }
  9071. foreach my $msg ( @msgs_all ) {
  9072. my $idate = $fetch{ $msg }->{'INTERNALDATE'} ;
  9073. #myprint( "$idate\n" ) ;
  9074. if ( defined $maxage and ( epoch( $idate ) >= $maxage_epoch ) ) {
  9075. push @max, $msg ;
  9076. }
  9077. if ( defined $minage and ( epoch( $idate ) <= $minage_epoch ) ) {
  9078. push @min, $msg ;
  9079. }
  9080. }
  9081. @msgs = msgs_from_maxmin( \@max, \@min ) ;
  9082. return( @msgs ) ;
  9083. }
  9084. sub select_msgs_by_age
  9085. {
  9086. my( $imap ) = @_ ;
  9087. my( @max, @min, @msgs, @inter, @union ) ;
  9088. if ( defined $maxage ) {
  9089. @max = $imap->sentsince( $timestart_int - $NB_SECONDS_IN_A_DAY * $maxage ) ;
  9090. }
  9091. if ( defined $minage ) {
  9092. @min = $imap->sentbefore( $timestart_int - $NB_SECONDS_IN_A_DAY * $minage ) ;
  9093. }
  9094. @msgs = msgs_from_maxmin( \@max, \@min ) ;
  9095. return( @msgs ) ;
  9096. }
  9097. sub msgs_from_maxmin
  9098. {
  9099. my( $max_ref, $min_ref ) = @_ ;
  9100. my( @max, @min, @msgs, @inter, @union ) ;
  9101. @max = @{ $max_ref } ;
  9102. @min = @{ $min_ref } ;
  9103. SWITCH: {
  9104. if ( not ( defined $minage or defined $maxage ) )
  9105. {
  9106. return ;
  9107. }
  9108. unless( defined $minage ) { @msgs = @max ; last SWITCH } ;
  9109. unless( defined $maxage ) { @msgs = @min ; last SWITCH } ;
  9110. my ( %union, %inter ) ;
  9111. foreach my $m ( @min, @max ) { $union{ $m }++ && $inter{ $m }++ }
  9112. @inter = sort { $a <=> $b } keys %inter ;
  9113. @union = sort { $a <=> $b } keys %union ;
  9114. # normal case
  9115. if ( $minage <= $maxage ) { @msgs = @inter ; last SWITCH } ;
  9116. # just exclude messages between
  9117. if ( $minage > $maxage ) { @msgs = @union ; last SWITCH } ;
  9118. }
  9119. return( @msgs ) ;
  9120. }
  9121. sub tests_msgs_from_maxmin
  9122. {
  9123. note( 'Entering tests_msgs_from_maxmin()' ) ;
  9124. my @msgs ;
  9125. # no maxage nor minage
  9126. @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
  9127. is_deeply( [ ], \@msgs , 'msgs_from_maxmin: no maxage nor minage => empty result' ) ;
  9128. # maxage alone
  9129. $maxage = $NUMBER_200 ;
  9130. @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
  9131. is_deeply( [ '1', '2' ], \@msgs , 'msgs_from_maxmin: maxage++' ) ;
  9132. # maxage > minage -> intersection
  9133. $minage = $NUMBER_100 ;
  9134. @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
  9135. is_deeply( [ '2' ], \@msgs , 'msgs_from_maxmin: -maxage++minage-' ) ;
  9136. # maxage < minage -> union
  9137. $minage = $NUMBER_300 ;
  9138. @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
  9139. is_deeply( [ '1', '2', '3' ], \@msgs, 'msgs_from_maxmin: ++maxage-minage++' ) ;
  9140. # minage alone
  9141. $maxage = undef ;
  9142. @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
  9143. is_deeply( [ '2', '3' ], \@msgs, 'msgs_from_maxmin: ++minage-' ) ;
  9144. note( 'Leaving tests_msgs_from_maxmin()' ) ;
  9145. return ;
  9146. }
  9147. sub tests_info_date_from_uid
  9148. {
  9149. note( 'Entering tests_info_date_from_uid()' ) ;
  9150. note( 'Leaving tests_info_date_from_uid()' ) ;
  9151. return ;
  9152. }
  9153. sub info_date_from_uid
  9154. {
  9155. #my $first_uid = $msgs_all[ 0 ] ;
  9156. #my $first_idate = $fetch{ $first_uid }->{'INTERNALDATE'} ;
  9157. #my $first_epoch = epoch( $first_idate ) ;
  9158. #my $first_days = ( $timestart_int - $first_epoch ) / $NB_SECONDS_IN_A_DAY ;
  9159. #myprint( "\nOldest msg has UID $first_uid INTERNALDATE $first_idate EPOCH $first_epoch DAYS AGO $first_days\n" ) ;
  9160. }
  9161. sub lastuid
  9162. {
  9163. my $imap = shift @ARG ;
  9164. my $folder = shift @ARG ;
  9165. my $lastuid_guess = shift @ARG ;
  9166. my $lastuid ;
  9167. # rfc3501: The only reliable way to identify recent messages is to
  9168. # look at message flags to see which have the \Recent flag
  9169. # set, or to do a SEARCH RECENT.
  9170. # SEARCH RECENT doesn't work this way on courrier.
  9171. my @recent_messages ;
  9172. # SEARCH RECENT for each transfer can be expensive with a big folder
  9173. # Call commented for now
  9174. #@recent_messages = $imap->recent( ) ;
  9175. #myprint( "Recent: @recent_messages\n" ) ;
  9176. my $max_recent ;
  9177. $max_recent = max( @recent_messages ) ;
  9178. if ( defined $max_recent and ($lastuid_guess <= $max_recent ) ) {
  9179. $lastuid = $max_recent ;
  9180. }else{
  9181. $lastuid = $lastuid_guess
  9182. }
  9183. return( $lastuid ) ;
  9184. }
  9185. sub size_filtered
  9186. {
  9187. my( $h1_size, $h1_msg, $h1_fold, $h2_fold ) = @_ ;
  9188. $h1_size = 0 if ( ! $h1_size ) ; # null if empty or undef
  9189. if ( defined $sync->{ maxsize } and $h1_size > $sync->{ maxsize } ) {
  9190. myprint( "msg $h1_fold/$h1_msg skipped ($h1_size exceeds maxsize limit $sync->{ maxsize } bytes)\n" ) ;
  9191. $sync->{ total_bytes_skipped } += $h1_size;
  9192. $sync->{ nb_msg_skipped } += 1;
  9193. return( 1 ) ;
  9194. }
  9195. if ( defined $minsize and $h1_size <= $minsize ) {
  9196. myprint( "msg $h1_fold/$h1_msg skipped ($h1_size smaller than minsize $minsize bytes)\n" ) ;
  9197. $sync->{ total_bytes_skipped } += $h1_size;
  9198. $sync->{ nb_msg_skipped } += 1;
  9199. return( 1 ) ;
  9200. }
  9201. return( 0 ) ;
  9202. }
  9203. sub message_exists
  9204. {
  9205. my( $imap, $msg ) = @_ ;
  9206. return( 1 ) if not $imap->Uid( ) ;
  9207. my $search_uid ;
  9208. ( $search_uid ) = $imap->search( "UID $msg" ) ;
  9209. #myprint( "$search ? $msg\n" ) ;
  9210. return( 1 ) if ( $search_uid eq $msg ) ;
  9211. return( 0 ) ;
  9212. }
  9213. # Globals
  9214. # $sync->{ total_bytes_skipped }
  9215. # $sync->{ nb_msg_skipped }
  9216. # $mysync->{ h1_nb_msg_processed }
  9217. sub stats_update_skip_message
  9218. {
  9219. my $mysync = shift @ARG ; # to be used
  9220. my $h1_size = shift @ARG ;
  9221. $mysync->{ total_bytes_skipped } += $h1_size ;
  9222. $mysync->{ nb_msg_skipped } += 1 ;
  9223. $mysync->{ h1_nb_msg_processed } +=1 ;
  9224. return ;
  9225. }
  9226. sub copy_message
  9227. {
  9228. # copy
  9229. my ( $mysync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $cache_dir ) = @_ ;
  9230. ( $mysync->{ debug } or $mysync->{dry} )
  9231. and myprint( "msg $h1_fold/$h1_msg copying to $h2_fold $mysync->{dry_message} " . eta( $mysync ) . "\n" ) ;
  9232. if ( $mysync->{dry1} )
  9233. {
  9234. $mysync->{ h1_nb_msg_processed } +=1 ;
  9235. $nb_msg_skipped_dry_mode += 1 ;
  9236. return ;
  9237. }
  9238. my $h1_size = $h1_fir_ref->{$h1_msg}->{'RFC822.SIZE'} || 0 ;
  9239. my $h1_flags = $h1_fir_ref->{$h1_msg}->{'FLAGS'} || q{} ;
  9240. my $h1_idate = $h1_fir_ref->{$h1_msg}->{'INTERNALDATE'} || q{} ;
  9241. if ( size_filtered( $h1_size, $h1_msg, $h1_fold, $h2_fold ) ) {
  9242. $mysync->{ h1_nb_msg_processed } +=1 ;
  9243. return ;
  9244. }
  9245. debugsleep( $mysync ) ;
  9246. myprint( "- msg $h1_fold/$h1_msg S[$h1_size] F[$h1_flags] I[$h1_idate] has RFC822.SIZE null!\n" ) if ( ! $h1_size ) ;
  9247. if ( $checkmessageexists and not message_exists( $mysync->{imap1}, $h1_msg ) ) {
  9248. stats_update_skip_message( $mysync, $h1_size ) ;
  9249. return ;
  9250. }
  9251. myprint( debugmemory( $mysync, " at C1" ) ) ;
  9252. my ( $string, $string_len ) ;
  9253. ( $string_len ) = message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, \$string ) ;
  9254. myprint( debugmemory( $mysync, " at C2" ) ) ;
  9255. # not defined or empty $string
  9256. if ( ( not $string ) or ( not $string_len ) ) {
  9257. myprint( "- msg $h1_fold/$h1_msg skipped.\n" ) ;
  9258. stats_update_skip_message( $mysync, $h1_size ) ;
  9259. return ;
  9260. }
  9261. # Lines too long (or not enough) => do no copy or fix
  9262. if ( ( defined $maxlinelength ) or ( defined $minmaxlinelength ) ) {
  9263. $string = linelengthstuff( $string, $h1_fold, $h1_msg, $string_len, $h1_size, $h1_flags, $h1_idate ) ;
  9264. if ( not defined $string ) {
  9265. stats_update_skip_message( $mysync, $h1_size ) ;
  9266. return ;
  9267. }
  9268. }
  9269. my $h1_date = date_for_host2( $h1_msg, $h1_idate ) ;
  9270. ( $mysync->{ debug } or $mysync->{ debugflags } ) and
  9271. myprint( "Host1: flags init msg $h1_fold/$h1_msg date [$h1_date] flags [$h1_flags] size [$h1_size]\n" ) ;
  9272. $h1_flags = flags_for_host2( $mysync, $h1_flags, $mysync->{ permanentflags2 } ) ;
  9273. ( $mysync->{ debug } or $mysync->{ debugflags } ) and
  9274. myprint( "Host1: flags filt msg $h1_fold/$h1_msg date [$h1_date] flags [$h1_flags] size [$h1_size]\n" ) ;
  9275. $h1_date = undef if ( $h1_date eq q{} ) ;
  9276. 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 ) ;
  9277. if ( $new_id and $syncflagsaftercopy ) {
  9278. sync_flags_after_copy( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $new_id ) ;
  9279. }
  9280. myprint( debugmemory( $mysync, " at C3" ) ) ;
  9281. return $new_id ;
  9282. }
  9283. sub linelengthstuff
  9284. {
  9285. my( $string, $h1_fold, $h1_msg, $string_len, $h1_size, $h1_flags, $h1_idate ) = @_ ;
  9286. my $maxlinelength_string = max_line_length( $string ) ;
  9287. $debugmaxlinelength and myprint( "msg $h1_fold/$h1_msg maxlinelength: $maxlinelength_string\n" ) ;
  9288. if ( ( defined $minmaxlinelength ) and ( $maxlinelength_string <= $minmaxlinelength ) ) {
  9289. my $subject = subject( $string ) ;
  9290. $debugdev and myprint( "- msg $h1_fold/$h1_msg skipped S[$h1_size] F[$h1_flags] I[$h1_idate] "
  9291. . "(Subject:[$subject]) (max line length under minmaxlinelength $minmaxlinelength bytes)\n" ) ;
  9292. return ;
  9293. }
  9294. if ( ( defined $maxlinelength ) and ( $maxlinelength_string > $maxlinelength ) ) {
  9295. my $subject = subject( $string ) ;
  9296. if ( $maxlinelengthcmd ) {
  9297. $string = pipemess( $string, $maxlinelengthcmd ) ;
  9298. # string undef means something was bad.
  9299. if ( not ( defined $string ) ) {
  9300. myprint( "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate] "
  9301. . "(Subject:[$subject]) could not be successfully transformed by --maxlinelengthcmd option\n" ) ;
  9302. return ;
  9303. }else{
  9304. return $string ;
  9305. }
  9306. }
  9307. myprint( "- msg $h1_fold/$h1_msg skipped S[$h1_size] F[$h1_flags] I[$h1_idate] "
  9308. . "(Subject:[$subject]) (line length exceeds maxlinelength $maxlinelength bytes)\n" ) ;
  9309. return ;
  9310. }
  9311. return $string ;
  9312. }
  9313. sub message_for_host2
  9314. {
  9315. # global variable list:
  9316. # @skipmess
  9317. # @regexmess
  9318. # @pipemess
  9319. # $debugcontent
  9320. # $debug
  9321. #
  9322. # API current
  9323. #
  9324. # at failure:
  9325. # * return nothing ( will then be undef or () )
  9326. # * $string_ref content is undef or empty
  9327. # at success:
  9328. # * return string length ($string_ref content length)
  9329. # * $string_ref content filled with message
  9330. # API future
  9331. #
  9332. #
  9333. my ( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ) = @_ ;
  9334. # abort when missing a parameter
  9335. if ( ( ! $mysync ) or ( ! $h1_msg ) or ( ! $h1_fold ) or ( ! defined $h1_size )
  9336. or ( ! defined $h1_flags) or ( ! defined $h1_idate )
  9337. or ( ! $h1_fir_ref) or ( ! $string_ref ) )
  9338. {
  9339. return ;
  9340. }
  9341. myprint( debugmemory( $mysync, " at M1" ) ) ;
  9342. my $string_ok = $mysync->{imap1}->message_to_file( $string_ref, $h1_msg ) ;
  9343. myprint( debugmemory( $mysync, " at M2" ) ) ;
  9344. my $string_len = length_ref( $string_ref ) ;
  9345. unless ( defined $string_ok and $string_len ) {
  9346. # undef or 0 length
  9347. my $error = join q{},
  9348. "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate] could not be fetched: ",
  9349. $mysync->{imap1}->LastError || q{}, "\n" ;
  9350. errors_incr( $mysync, $error ) ;
  9351. $mysync->{ h1_nb_msg_processed } +=1 ;
  9352. return ;
  9353. }
  9354. if ( @skipmess ) {
  9355. my $match = skipmess( ${ $string_ref } ) ;
  9356. # string undef means the eval regex was bad.
  9357. if ( not ( defined $match ) ) {
  9358. myprint(
  9359. "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
  9360. . " could not be skipped by --skipmess option, bad regex\n" ) ;
  9361. return ;
  9362. }
  9363. if ( $match ) {
  9364. my $subject = subject( ${ $string_ref } ) ;
  9365. myprint( "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
  9366. . " (Subject:[$subject]) skipped by --skipmess\n" ) ;
  9367. return ;
  9368. }
  9369. }
  9370. if ( @regexmess ) {
  9371. ${ $string_ref } = regexmess( ${ $string_ref } ) ;
  9372. # string undef means the eval regex was bad.
  9373. if ( not ( defined ${ $string_ref } ) ) {
  9374. myprint(
  9375. "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
  9376. . " could not be transformed by --regexmess\n" ) ;
  9377. return ;
  9378. }
  9379. }
  9380. if ( @pipemess ) {
  9381. ${ $string_ref } = pipemess( ${ $string_ref }, @pipemess ) ;
  9382. # string undef means something was bad.
  9383. if ( not ( defined ${ $string_ref } ) ) {
  9384. myprint(
  9385. "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
  9386. . " could not be successfully transformed by --pipemess option\n" ) ;
  9387. return ;
  9388. }
  9389. }
  9390. if ( $mysync->{addheader} and defined $h1_fir_ref->{$h1_msg}->{'NO_HEADER'} ) {
  9391. my $header = add_header( $h1_msg ) ;
  9392. $mysync->{ debug } and myprint( "msg $h1_fold/$h1_msg adding custom header [$header]\n" ) ;
  9393. ${ $string_ref } = $header . "\r\n" . ${ $string_ref } ;
  9394. }
  9395. if ( ( defined $mysync->{ truncmess } ) and is_integer( $mysync->{ truncmess } ) )
  9396. {
  9397. ${ $string_ref } = truncmess( ${ $string_ref }, $mysync->{ truncmess } ) ;
  9398. }
  9399. $string_len = length_ref( $string_ref ) ;
  9400. $mysync->{ debugcontent } and myprint( debugcontent( $mysync, $string_ref ) ) ;
  9401. myprint( debugmemory( $mysync, " at M3" ) ) ;
  9402. return $string_len ;
  9403. }
  9404. sub tests_debugcontent
  9405. {
  9406. note( 'Entering tests_debugcontent()' ) ;
  9407. is( undef, debugcontent( ), 'debugcontent: no args => undef' ) ;
  9408. my $mysync = { } ;
  9409. is( undef, debugcontent( $mysync ), 'debugcontent: undef => undef' ) ;
  9410. is( undef, debugcontent( $mysync, 'mm' ), 'debugcontent: undef, mm => undef' ) ;
  9411. #my $string_ref = \'zztop' ;
  9412. my $string = '================================================================================
  9413. F message content begin next line (2 characters long)
  9414. mm
  9415. F message content ended on previous line
  9416. ================================================================================
  9417. ' ;
  9418. is( $string, debugcontent( $mysync, \'mm' ), 'debugcontent: undef, mm => mm' ) ;
  9419. note( 'Leaving tests_debugcontent()' ) ;
  9420. return ;
  9421. }
  9422. sub debugcontent
  9423. {
  9424. my $mysync = shift @ARG ;
  9425. if ( ! defined $mysync ) { return ; }
  9426. my $string_ref = shift @ARG ;
  9427. if ( ! defined $string_ref ) { return ; }
  9428. if ( 'SCALAR' ne ref( $string_ref ) ) { return ; }
  9429. my $string_len = length_ref( $string_ref ) ;
  9430. my $string = join( '',
  9431. q{=} x $STD_CHAR_PER_LINE, "\n",
  9432. "F message content begin next line ($string_len characters long)\n",
  9433. ${ $string_ref },
  9434. "\nF message content ended on previous line\n", q{=} x $STD_CHAR_PER_LINE, "\n",
  9435. ) ;
  9436. return $string ;
  9437. }
  9438. sub tests_truncmess
  9439. {
  9440. note( 'Entering tests_truncmess()' ) ;
  9441. is( undef, truncmess( ), 'truncmess: no args => undef' ) ;
  9442. is( 'abc', truncmess( 'abc' ), 'truncmess: abc => abc' ) ;
  9443. is( 'ab', truncmess( 'abc', 2 ), 'truncmess: abc 2 => ab' ) ;
  9444. is( 'abc', truncmess( 'abc', 3 ), 'truncmess: abc 3 => abc' ) ;
  9445. is( 'abc', truncmess( 'abc', 4 ), 'truncmess: abc 4 => abc' ) ;
  9446. is( '12345', truncmess( "123456789\n", 5 ), 'truncmess: "123456789\n", 5 => 12345' ) ;
  9447. is( "123456789\n" x 5000, truncmess( "123456789\n" x 100000, 50000 ), 'truncmess: "123456789\n" x 100000, 50000 => "123456789\n" x 5000' ) ;
  9448. note( 'Leaving tests_truncmess()' ) ;
  9449. return ;
  9450. }
  9451. sub truncmess
  9452. {
  9453. my $string = shift @ARG ;
  9454. my $length = shift @ARG ;
  9455. if ( not defined $string ) { return ; }
  9456. if ( not defined $length ) { return $string ; }
  9457. $string = substr $string, 0, $length ;
  9458. return $string ;
  9459. }
  9460. sub tests_message_for_host2
  9461. {
  9462. note( 'Entering tests_message_for_host2()' ) ;
  9463. my ( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ) ;
  9464. is( undef, message_for_host2( ), q{message_for_host2: no args} ) ;
  9465. 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} ) ;
  9466. require_ok( "Test::MockObject" ) ;
  9467. my $imapT = Test::MockObject->new( ) ;
  9468. $mysync->{imap1} = $imapT ;
  9469. my $string ;
  9470. $h1_msg = 1 ;
  9471. $h1_fold = 'FoldFoo';
  9472. $h1_size = 9 ;
  9473. $h1_flags = q{} ;
  9474. $h1_idate = '10-Jul-2015 09:00:00 +0200' ;
  9475. $h1_fir_ref = {} ;
  9476. $string_ref = \$string ;
  9477. $imapT->mock( 'message_to_file',
  9478. sub {
  9479. my ( $imap, $mystring_ref, $msg ) = @_ ;
  9480. ${$mystring_ref} = 'blablabla' ;
  9481. return length ${$mystring_ref} ;
  9482. }
  9483. ) ;
  9484. is( 9, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
  9485. q{message_for_host2: msg 1 == "blablabla", length} ) ;
  9486. is( 'blablabla', $string, q{message_for_host2: msg 1 == "blablabla", value} ) ;
  9487. # so far so good
  9488. # now the --pipemess stuff
  9489. SKIP: {
  9490. Readonly my $NB_WIN_tests_message_for_host2 => 0 ;
  9491. skip( 'Not on MSWin32', $NB_WIN_tests_message_for_host2 ) if ('MSWin32' ne $OSNAME) ;
  9492. # Windows
  9493. # "type" command does not accept redirection of STDIN with <
  9494. # "sort" does
  9495. } ;
  9496. SKIP: {
  9497. Readonly my $NB_UNX_tests_message_for_host2 => 6 ;
  9498. skip( 'Not on Unix', $NB_UNX_tests_message_for_host2 ) if ('MSWin32' eq $OSNAME) ;
  9499. # Unix
  9500. # no change by cat
  9501. @pipemess = ( 'cat' ) ;
  9502. is( 9, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
  9503. q{message_for_host2: --pipemess 'cat', length} ) ;
  9504. is( 'blablabla', $string, q{message_for_host2: --pipemess 'cat', value} ) ;
  9505. # failure by false
  9506. @pipemess = ( 'false' ) ;
  9507. is( undef, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
  9508. q{message_for_host2: --pipemess 'false', length} ) ;
  9509. is( undef, $string, q{message_for_host2: --pipemess 'false', value} ) ;
  9510. # failure by true since no output
  9511. @pipemess = ( 'true' ) ;
  9512. is( undef, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
  9513. q{message_for_host2: --pipemess 'true', length} ) ;
  9514. is( undef, $string, q{message_for_host2: --pipemess 'true', value} ) ;
  9515. }
  9516. undef @pipemess ;
  9517. note( 'Leaving tests_message_for_host2()' ) ;
  9518. return ;
  9519. }
  9520. sub tests_labels_remove_subfolder1
  9521. {
  9522. note( 'Entering tests_labels_remove_subfolder1()' ) ;
  9523. is( undef, labels_remove_subfolder1( ), 'labels_remove_subfolder1: no parameters => undef' ) ;
  9524. is( 'Blabla', labels_remove_subfolder1( 'Blabla' ), 'labels_remove_subfolder1: one parameter Blabla => Blabla' ) ;
  9525. is( 'Blan blue', labels_remove_subfolder1( 'Blan blue' ), 'labels_remove_subfolder1: one parameter Blan blue => Blan blue' ) ;
  9526. is( '\Bla "Blan blan" Blabla', labels_remove_subfolder1( '\Bla "Blan blan" Blabla' ),
  9527. 'labels_remove_subfolder1: one parameter \Bla "Blan blan" Blabla => \Bla "Blan blan" Blabla' ) ;
  9528. is( 'Bla', labels_remove_subfolder1( 'Subf/Bla', 'Subf' ), 'labels_remove_subfolder1: Subf/Bla Subf => "Bla"' ) ;
  9529. is( '"\\\\Bla"', labels_remove_subfolder1( '"\\\\Bla"', 'Subf' ), 'labels_remove_subfolder1: "\\\\Bla" Subf => "\\\\Bla"' ) ;
  9530. is( 'Bla Kii', labels_remove_subfolder1( 'Subf/Bla Subf/Kii', 'Subf' ),
  9531. 'labels_remove_subfolder1: Subf/Bla Subf/Kii, Subf => "Bla" "Kii"' ) ;
  9532. is( '"\\\\Bla" Kii', labels_remove_subfolder1( '"\\\\Bla" Subf/Kii', 'Subf' ),
  9533. 'labels_remove_subfolder1: "\\\\Bla" Subf/Kii Subf => "\\\\Bla" Kii' ) ;
  9534. is( '"Blan blan"', labels_remove_subfolder1( '"Subf/Blan blan"', 'Subf' ),
  9535. 'labels_remove_subfolder1: "Subf/Blan blan" Subf => "Blan blan"' ) ;
  9536. is( '"\\\\Loo" "Blan blan" Kii', labels_remove_subfolder1( '"\\\\Loo" "Subf/Blan blan" Subf/Kii', 'Subf' ),
  9537. 'labels_remove_subfolder1: "\\\\Loo" "Subf/Blan blan" Subf/Kii + Subf => "\\\\Loo" "Blan blan" Kii' ) ;
  9538. is( '"\\\\Inbox"', labels_remove_subfolder1( 'Subf/INBOX', 'Subf' ),
  9539. 'labels_remove_subfolder1: Subf/INBOX + Subf => "\\\\Inbox"' ) ;
  9540. is( '"\\\\Loo" "Blan blan" Kii "\\\\Inbox"', labels_remove_subfolder1( '"\\\\Loo" "Subf/Blan blan" Subf/Kii Subf/INBOX', 'Subf' ),
  9541. 'labels_remove_subfolder1: "\\\\Loo" "Subf/Blan blan" Subf/Kii Subf/INBOX + Subf => "\\\\Loo" "Blan blan" Kii "\\\\Inbox"' ) ;
  9542. note( 'Leaving tests_labels_remove_subfolder1()' ) ;
  9543. return ;
  9544. }
  9545. sub labels_remove_subfolder1
  9546. {
  9547. my $labels = shift @ARG ;
  9548. my $subfolder1 = shift @ARG ;
  9549. if ( not defined $labels ) { return ; }
  9550. if ( not defined $subfolder1 ) { return $labels ; }
  9551. my @labels = quotewords('\s+', 1, $labels ) ;
  9552. #myprint( "@labels\n" ) ;
  9553. my @labels_subfolder2 ;
  9554. foreach my $label ( @labels )
  9555. {
  9556. if ( $label =~ m{zzzzzzzzzz} )
  9557. {
  9558. # \Seen \Deleted ... stay the same
  9559. push @labels_subfolder2, $label ;
  9560. }
  9561. else
  9562. {
  9563. # Remove surrounding quotes if any, to add them again in case of space
  9564. $label = join( q{}, quotewords('\s+', 0, $label ) ) ;
  9565. $label =~ s{$subfolder1/?}{} ;
  9566. if ( 'INBOX' eq $label )
  9567. {
  9568. push @labels_subfolder2, q{"\\\\Inbox"} ;
  9569. }
  9570. elsif ( $label =~ m{\\} )
  9571. {
  9572. push @labels_subfolder2, qq{"\\$label"} ;
  9573. }
  9574. elsif ( $label =~ m{ } )
  9575. {
  9576. push @labels_subfolder2, qq{"$label"} ;
  9577. }
  9578. else
  9579. {
  9580. push @labels_subfolder2, $label ;
  9581. }
  9582. }
  9583. }
  9584. my $labels_subfolder2 = join( ' ', sort uniq( @labels_subfolder2 ) ) ;
  9585. return $labels_subfolder2 ;
  9586. }
  9587. sub tests_labels_remove_special
  9588. {
  9589. note( 'Entering tests_labels_remove_special()' ) ;
  9590. is( undef, labels_remove_special( ), 'labels_remove_special: no parameters => undef' ) ;
  9591. is( q{}, labels_remove_special( q{} ), 'labels_remove_special: empty string => empty string' ) ;
  9592. is( q{}, labels_remove_special( '"\\\\Inbox"' ), 'labels_remove_special:"\\\\Inbox" => empty string' ) ;
  9593. is( q{}, labels_remove_special( '"\\\\Inbox" "\\\\Starred"' ), 'labels_remove_special:"\\\\Inbox" "\\\\Starred" => empty string' ) ;
  9594. is( 'Bar Foo', labels_remove_special( 'Foo Bar' ), 'labels_remove_special:Foo Bar => Bar Foo' ) ;
  9595. is( 'Bar Foo', labels_remove_special( 'Foo Bar "\\\\Inbox"' ), 'labels_remove_special:Foo Bar "\\\\Inbox" => Bar Foo' ) ;
  9596. note( 'Leaving tests_labels_remove_special()' ) ;
  9597. return ;
  9598. }
  9599. sub labels_remove_special
  9600. {
  9601. my $labels = shift @ARG ;
  9602. if ( not defined $labels ) { return ; }
  9603. my @labels = quotewords('\s+', 1, $labels ) ;
  9604. myprint( "labels before remove_non_folded: @labels\n" ) ;
  9605. my @labels_remove_special ;
  9606. foreach my $label ( @labels )
  9607. {
  9608. if ( $label =~ m{^\"\\\\} )
  9609. {
  9610. # not kept
  9611. }
  9612. else
  9613. {
  9614. push @labels_remove_special, $label ;
  9615. }
  9616. }
  9617. my $labels_remove_special = join( ' ', sort @labels_remove_special ) ;
  9618. return $labels_remove_special ;
  9619. }
  9620. sub tests_labels_add_subfolder2
  9621. {
  9622. note( 'Entering tests_labels_add_subfolder2()' ) ;
  9623. is( undef, labels_add_subfolder2( ), 'labels_add_subfolder2: no parameters => undef' ) ;
  9624. is( 'Blabla', labels_add_subfolder2( 'Blabla' ), 'labels_add_subfolder2: one parameter Blabla => Blabla' ) ;
  9625. is( 'Blan blue', labels_add_subfolder2( 'Blan blue' ), 'labels_add_subfolder2: one parameter Blan blue => Blan blue' ) ;
  9626. is( '\Bla "Blan blan" Blabla', labels_add_subfolder2( '\Bla "Blan blan" Blabla' ),
  9627. 'labels_add_subfolder2: one parameter \Bla "Blan blan" Blabla => \Bla "Blan blan" Blabla' ) ;
  9628. is( 'Subf/Bla', labels_add_subfolder2( 'Bla', 'Subf' ), 'labels_add_subfolder2: Bla Subf => "Subf/Bla"' ) ;
  9629. is( 'Subf/\Bla', labels_add_subfolder2( '\\\\Bla', 'Subf' ), 'labels_add_subfolder2: \Bla Subf => \Bla' ) ;
  9630. is( 'Subf/Bla Subf/Kii', labels_add_subfolder2( 'Bla Kii', 'Subf' ),
  9631. 'labels_add_subfolder2: Bla Kii Subf => "Subf/Bla" "Subf/Kii"' ) ;
  9632. is( 'Subf/Kii Subf/\Bla', labels_add_subfolder2( '\\\\Bla Kii', 'Subf' ),
  9633. 'labels_add_subfolder2: \Bla Kii Subf => \Bla Subf/Kii' ) ;
  9634. is( '"Subf/Blan blan"', labels_add_subfolder2( '"Blan blan"', 'Subf' ),
  9635. 'labels_add_subfolder2: "Blan blan" Subf => "Subf/Blan blan"' ) ;
  9636. is( '"Subf/Blan blan" Subf/Kii Subf/\Loo', labels_add_subfolder2( '\\\\Loo "Blan blan" Kii', 'Subf' ),
  9637. 'labels_add_subfolder2: \Loo "Blan blan" Kii + Subf => "Subf/Blan blan" Subf/Kii Subf/\Loo' ) ;
  9638. # "\\Inbox" is special, add to subfolder INBOX also because Gmail will but ...
  9639. is( '"Subf/\\\\Inbox" Subf/INBOX', labels_add_subfolder2( '"\\\\Inbox"', 'Subf' ),
  9640. 'labels_add_subfolder2: "\\\\Inbox" Subf => "Subf/\\\\Inbox" Subf/INBOX' ) ;
  9641. # but not with INBOX folder
  9642. is( '"Subf/\\\\Inbox"', labels_add_subfolder2( '"\\\\Inbox"', 'Subf', 'INBOX' ),
  9643. 'labels_add_subfolder2: "\\\\Inbox" Subf INBOX => "Subf/\\\\Inbox"' ) ;
  9644. # two times => one time
  9645. is( '"Subf/\\\\Inbox" Subf/INBOX', labels_add_subfolder2( '"\\\\Inbox" "\\\\Inbox"', 'Subf' ),
  9646. 'labels_add_subfolder2: "\\\\Inbox" "\\\\Inbox" Subf => "Subf/\\\\Inbox"' ) ;
  9647. is( '"Subf/\\\\Starred"', labels_add_subfolder2( '"\\\\Starred"', 'Subf' ),
  9648. 'labels_add_subfolder2: "\\\\Starred" Subf => "Subf/\\\\Starred"' ) ;
  9649. note( 'Leaving tests_labels_add_subfolder2()' ) ;
  9650. return ;
  9651. }
  9652. sub labels_add_subfolder2
  9653. {
  9654. my $labels = shift @ARG ;
  9655. my $subfolder2 = shift @ARG ;
  9656. my $h1_folder = shift || q{} ;
  9657. if ( not defined $labels ) { return ; }
  9658. if ( not defined $subfolder2 ) { return $labels ; }
  9659. # Isn't it messy?
  9660. if ( 'INBOX' eq $h1_folder )
  9661. {
  9662. $labels .= ' "\\\\Inbox"' ;
  9663. }
  9664. my @labels = uniq( quotewords('\s+', 1, $labels ) ) ;
  9665. myprint( "labels before subfolder2: @labels\n" ) ;
  9666. my @labels_subfolder2 ;
  9667. foreach my $label ( @labels )
  9668. {
  9669. # Isn't it more messy?
  9670. if ( ( q{"\\\\Inbox"} eq $label ) and ( 'INBOX' ne $h1_folder ) )
  9671. {
  9672. if ( $subfolder2 =~ m{ } )
  9673. {
  9674. push @labels_subfolder2, qq{"$subfolder2/INBOX"} ;
  9675. }
  9676. else
  9677. {
  9678. push @labels_subfolder2, "$subfolder2/INBOX" ;
  9679. }
  9680. }
  9681. if ( $label =~ m{^\"\\\\} )
  9682. {
  9683. # \Seen \Deleted ... stay the same
  9684. #push @labels_subfolder2, $label ;
  9685. # Remove surrounding quotes if any, to add them again
  9686. $label = join( q{}, quotewords('\s+', 0, $label ) ) ;
  9687. push @labels_subfolder2, qq{"$subfolder2/\\$label"} ;
  9688. }
  9689. else
  9690. {
  9691. # Remove surrounding quotes if any, to add them again in case of space
  9692. $label = join( q{}, quotewords('\s+', 0, $label ) ) ;
  9693. if ( $label =~ m{ } )
  9694. {
  9695. push @labels_subfolder2, qq{"$subfolder2/$label"} ;
  9696. }
  9697. else
  9698. {
  9699. push @labels_subfolder2, "$subfolder2/$label" ;
  9700. }
  9701. }
  9702. }
  9703. my $labels_subfolder2 = join( ' ', sort @labels_subfolder2 ) ;
  9704. return $labels_subfolder2 ;
  9705. }
  9706. sub tests_labels
  9707. {
  9708. note( 'Entering tests_labels()' ) ;
  9709. is( undef, labels( ), 'labels: no parameters => undef' ) ;
  9710. is( undef, labels( undef ), 'labels: undef => undef' ) ;
  9711. require_ok( "Test::MockObject" ) ;
  9712. my $myimap = Test::MockObject->new( ) ;
  9713. $myimap->mock( 'fetch_hash',
  9714. sub {
  9715. return(
  9716. { '1' => {
  9717. 'X-GM-LABELS' => '\Seen Blabla'
  9718. }
  9719. }
  9720. ) ;
  9721. }
  9722. ) ;
  9723. $myimap->mock( 'Debug' , sub { } ) ;
  9724. $myimap->mock( 'Unescape', sub { return Mail::IMAPClient::Unescape( @_ ) } ) ; # real one
  9725. is( undef, labels( $myimap ), 'labels: one parameter => undef' ) ;
  9726. is( '\Seen Blabla', labels( $myimap, '1' ), 'labels: $mysync UID_1 => \Seen Blabla' ) ;
  9727. note( 'Leaving tests_labels()' ) ;
  9728. return ;
  9729. }
  9730. sub labels
  9731. {
  9732. my ( $myimap, $uid ) = @ARG ;
  9733. if ( not all_defined( $myimap, $uid ) ) {
  9734. return ;
  9735. }
  9736. my $hash = $myimap->fetch_hash( [ $uid ], 'X-GM-LABELS' ) ;
  9737. my $labels = $hash->{ $uid }->{ 'X-GM-LABELS' } ;
  9738. #$labels = $myimap->Unescape( $labels ) ;
  9739. return $labels ;
  9740. }
  9741. sub tests_synclabels
  9742. {
  9743. note( 'Entering tests_synclabels()' ) ;
  9744. is( undef, synclabels( ), 'synclabels: no parameters => undef' ) ;
  9745. is( undef, synclabels( undef ), 'synclabels: undef => undef' ) ;
  9746. my $mysync ;
  9747. is( undef, synclabels( $mysync ), 'synclabels: var undef => undef' ) ;
  9748. require_ok( "Test::MockObject" ) ;
  9749. $mysync = {} ;
  9750. my $myimap1 = Test::MockObject->new( ) ;
  9751. $myimap1->mock( 'fetch_hash',
  9752. sub {
  9753. return(
  9754. { '1' => {
  9755. 'X-GM-LABELS' => '\Seen Blabla'
  9756. }
  9757. }
  9758. ) ;
  9759. }
  9760. ) ;
  9761. $myimap1->mock( 'Debug', sub { } ) ;
  9762. $myimap1->mock( 'Unescape', sub { return Mail::IMAPClient::Unescape( @_ ) } ) ; # real one
  9763. my $myimap2 = Test::MockObject->new( ) ;
  9764. $myimap2->mock( 'store',
  9765. sub {
  9766. return 1 ;
  9767. }
  9768. ) ;
  9769. $mysync->{imap1} = $myimap1 ;
  9770. $mysync->{imap2} = $myimap2 ;
  9771. is( undef, synclabels( $mysync ), 'synclabels: fresh $mysync => undef' ) ;
  9772. is( undef, synclabels( $mysync, '1' ), 'synclabels: $mysync UID_1 alone => undef' ) ;
  9773. is( 1, synclabels( $mysync, '1', '2' ), 'synclabels: $mysync UID_1 UID_2 => 1' ) ;
  9774. note( 'Leaving tests_synclabels()' ) ;
  9775. return ;
  9776. }
  9777. sub synclabels
  9778. {
  9779. my( $mysync, $uid1, $uid2 ) = @ARG ;
  9780. if ( not all_defined( $mysync, $uid1, $uid2 ) ) {
  9781. return ;
  9782. }
  9783. my $myimap1 = $mysync->{ 'imap1' } || return ;
  9784. my $myimap2 = $mysync->{ 'imap2' } || return ;
  9785. $mysync->{debuglabels} and $myimap1->Debug( 1 ) ;
  9786. my $labels1 = labels( $myimap1, $uid1 ) ;
  9787. $mysync->{debuglabels} and $myimap1->Debug( 0 ) ;
  9788. $mysync->{debuglabels} and myprint( "Host1 labels: $labels1\n" ) ;
  9789. if ( $mysync->{ subfolder1 } and $labels1 )
  9790. {
  9791. $labels1 = labels_remove_subfolder1( $labels1, $mysync->{ subfolder1 } ) ;
  9792. $mysync->{debuglabels} and myprint( "Host1 labels with subfolder1: $labels1\n" ) ;
  9793. }
  9794. if ( $mysync->{ subfolder2 } and $labels1 )
  9795. {
  9796. $labels1 = labels_add_subfolder2( $labels1, $mysync->{ subfolder2 } ) ;
  9797. $mysync->{debuglabels} and myprint( "Host1 labels with subfolder2: $labels1\n" ) ;
  9798. }
  9799. my $store ;
  9800. if ( $labels1 and not $mysync->{ dry } )
  9801. {
  9802. $mysync->{ debuglabels } and $myimap2->Debug( 1 ) ;
  9803. $store = $myimap2->store( $uid2, "X-GM-LABELS ($labels1)" ) ;
  9804. $mysync->{ debuglabels } and $myimap2->Debug( 0 ) ;
  9805. }
  9806. return $store ;
  9807. }
  9808. sub tests_resynclabels
  9809. {
  9810. note( 'Entering tests_resynclabels()' ) ;
  9811. is( undef, resynclabels( ), 'resynclabels: no parameters => undef' ) ;
  9812. is( undef, resynclabels( undef ), 'resynclabels: undef => undef' ) ;
  9813. my $mysync ;
  9814. is( undef, resynclabels( $mysync ), 'resynclabels: var undef => undef' ) ;
  9815. my ( $h1_fir_ref, $h2_fir_ref ) ;
  9816. $mysync->{ debuglabels } = 1 ;
  9817. $h1_fir_ref->{ 11 }->{ 'X-GM-LABELS' } = '\Seen Baa Kii' ;
  9818. $h2_fir_ref->{ 22 }->{ 'X-GM-LABELS' } = '\Seen Baa Kii' ;
  9819. # labels are equal
  9820. is( 1, resynclabels( $mysync, 11, 22, $h1_fir_ref, $h2_fir_ref ),
  9821. 'resynclabels: $mysync UID_1 UID_2 labels are equal => 1' ) ;
  9822. # labels are different
  9823. $h2_fir_ref->{ 22 }->{ 'X-GM-LABELS' } = '\Seen Zuu' ;
  9824. require_ok( "Test::MockObject" ) ;
  9825. my $myimap2 = Test::MockObject->new( ) ;
  9826. $myimap2->mock( 'store',
  9827. sub {
  9828. return 1 ;
  9829. }
  9830. ) ;
  9831. $myimap2->mock( 'Debug', sub { } ) ;
  9832. $mysync->{imap2} = $myimap2 ;
  9833. is( 1, resynclabels( $mysync, 11, 22, $h1_fir_ref, $h2_fir_ref ),
  9834. 'resynclabels: $mysync UID_1 UID_2 labels are not equal => store => 1' ) ;
  9835. note( 'Leaving tests_resynclabels()' ) ;
  9836. return ;
  9837. }
  9838. sub resynclabels
  9839. {
  9840. my( $mysync, $uid1, $uid2, $h1_fir_ref, $h2_fir_ref, $h1_folder ) = @ARG ;
  9841. if ( not all_defined( $mysync, $uid1, $uid2, $h1_fir_ref, $h2_fir_ref ) ) {
  9842. return ;
  9843. }
  9844. my $labels1 = $h1_fir_ref->{ $uid1 }->{ 'X-GM-LABELS' } || q{} ;
  9845. my $labels2 = $h2_fir_ref->{ $uid2 }->{ 'X-GM-LABELS' } || q{} ;
  9846. if ( $mysync->{ subfolder1 } and $labels1 )
  9847. {
  9848. $labels1 = labels_remove_subfolder1( $labels1, $mysync->{ subfolder1 } ) ;
  9849. }
  9850. if ( $mysync->{ subfolder2 } and $labels1 )
  9851. {
  9852. $labels1 = labels_add_subfolder2( $labels1, $mysync->{ subfolder2 }, $h1_folder ) ;
  9853. $labels2 = labels_remove_special( $labels2 ) ;
  9854. }
  9855. $mysync->{ debuglabels } and myprint( "Host1 labels fixed: $labels1\n" ) ;
  9856. $mysync->{ debuglabels } and myprint( "Host2 labels : $labels2\n" ) ;
  9857. my $store ;
  9858. if ( $labels1 eq $labels2 )
  9859. {
  9860. # no sync needed
  9861. $mysync->{ debuglabels } and myprint( "Labels are already equal\n" ) ;
  9862. return 1 ;
  9863. }
  9864. elsif ( not $mysync->{ dry } )
  9865. {
  9866. # sync needed
  9867. $mysync->{debuglabels} and $mysync->{imap2}->Debug( 1 ) ;
  9868. $store = $mysync->{imap2}->store( $uid2, "X-GM-LABELS ($labels1)" ) ;
  9869. $mysync->{debuglabels} and $mysync->{imap2}->Debug( 0 ) ;
  9870. }
  9871. return $store ;
  9872. }
  9873. sub tests_uniq
  9874. {
  9875. note( 'Entering tests_uniq()' ) ;
  9876. is( 0, uniq( ), 'uniq: undef => 0' ) ;
  9877. is_deeply( [ 'one' ], [ uniq( 'one' ) ], 'uniq: one => one' ) ;
  9878. is_deeply( [ 'one' ], [ uniq( 'one', 'one' ) ], 'uniq: one one => one' ) ;
  9879. is_deeply( [ 'one', 'two' ], [ uniq( 'one', 'one', 'two', 'one', 'two' ) ], 'uniq: one one two one two => one two' ) ;
  9880. note( 'Leaving tests_uniq()' ) ;
  9881. return ;
  9882. }
  9883. sub uniq
  9884. {
  9885. my @list = @ARG ;
  9886. my %seen = ( ) ;
  9887. my @uniq = ( ) ;
  9888. foreach my $item ( @list ) {
  9889. if ( ! $seen{ $item } ) {
  9890. $seen{ $item } = 1 ;
  9891. push( @uniq, $item ) ;
  9892. }
  9893. }
  9894. return @uniq ;
  9895. }
  9896. sub length_ref
  9897. {
  9898. my $string_ref = shift @ARG ;
  9899. my $string_len = defined ${ $string_ref } ? length( ${ $string_ref } ) : q{} ; # length or empty string
  9900. return $string_len ;
  9901. }
  9902. sub tests_length_ref
  9903. {
  9904. note( 'Entering tests_length_ref()' ) ;
  9905. my $notdefined ;
  9906. is( q{}, length_ref( \$notdefined ), q{length_ref: value not defined} ) ;
  9907. my $notref ;
  9908. is( q{}, length_ref( $notref ), q{length_ref: param not a ref} ) ;
  9909. my $lala = 'lala' ;
  9910. is( 4, length_ref( \$lala ), q{length_ref: lala length == 4} ) ;
  9911. is( 4, length_ref( \'lili' ), q{length_ref: lili length == 4} ) ;
  9912. note( 'Leaving tests_length_ref()' ) ;
  9913. return ;
  9914. }
  9915. sub date_for_host2
  9916. {
  9917. my( $h1_msg, $h1_idate ) = @_ ;
  9918. my $h1_date = q{} ;
  9919. if ( $syncinternaldates ) {
  9920. $h1_date = $h1_idate ;
  9921. $sync->{ debug } and myprint( "internal date from host1: [$h1_date]\n" ) ;
  9922. $h1_date = good_date( $h1_date ) ;
  9923. $sync->{ debug } and myprint( "internal date from host1: [$h1_date] (fixed)\n" ) ;
  9924. }
  9925. if ( $idatefromheader ) {
  9926. $h1_date = $sync->{imap1}->get_header( $h1_msg, 'Date' ) ;
  9927. $sync->{ debug } and myprint( "header date from host1: [$h1_date]\n" ) ;
  9928. $h1_date = good_date( $h1_date ) ;
  9929. $sync->{ debug } and myprint( "header date from host1: [$h1_date] (fixed)\n" ) ;
  9930. }
  9931. return( $h1_date ) ;
  9932. }
  9933. sub subject
  9934. {
  9935. my $string = shift @ARG ;
  9936. my $subject = q{} ;
  9937. my $header = extract_header( $string ) ;
  9938. if( $header =~ m/^Subject:[ \t]*([^\n\r]*)\r?$/msx ) {
  9939. #myprint( "MMM[$1]\n" ) ;
  9940. $subject = $1 ;
  9941. }
  9942. return( $subject ) ;
  9943. }
  9944. sub tests_subject
  9945. {
  9946. note( 'Entering tests_subject()' ) ;
  9947. ok( q{} eq subject( q{} ), 'subject: null') ;
  9948. is( '', subject( 'Subject:' ), 'Subject:') ;
  9949. is( '', subject( "Subject:\r\n" ), 'Subject:\r\n') ;
  9950. ok( 'toto le hero' eq subject( 'Subject: toto le hero' ), 'Subject: toto le hero') ;
  9951. ok( 'toto le hero' eq subject( 'Subject:toto le hero' ), 'Subject:toto le hero') ;
  9952. ok( 'toto le hero' eq subject( "Subject:toto le hero\r\n" ), 'Subject: toto le hero\r\n') ;
  9953. my $MESS ;
  9954. $MESS = <<'EOF';
  9955. From: lalala
  9956. Subject: toto le hero
  9957. Date: zzzzzz
  9958. Boogie boogie
  9959. EOF
  9960. ok( 'toto le hero' eq subject( $MESS ), 'subject: toto le hero 2') ;
  9961. $MESS = <<'EOF';
  9962. Subject: toto le hero
  9963. From: lalala
  9964. Date: zzzzzz
  9965. Boogie boogie
  9966. EOF
  9967. ok( 'toto le hero' eq subject( $MESS ), 'subject: toto le hero 3') ;
  9968. $MESS = <<'EOF';
  9969. From: lalala
  9970. Subject: cuicui
  9971. Date: zzzzzz
  9972. Subject: toto le hero
  9973. EOF
  9974. ok( 'cuicui' eq subject( $MESS ), 'subject: cuicui') ;
  9975. $MESS = <<'EOF';
  9976. From: lalala
  9977. Date: zzzzzz
  9978. Subject: toto le hero
  9979. EOF
  9980. ok( q{} eq subject( $MESS ), 'subject: null but body could') ;
  9981. $MESS = <<'EOF';
  9982. From: lalala
  9983. Subject:
  9984. Date: zzzzzz
  9985. Subject: toto le hero
  9986. EOF
  9987. is( '', subject( $MESS ), 'Subject:') ;
  9988. note( 'Leaving tests_subject()' ) ;
  9989. return ;
  9990. }
  9991. # GlobVar
  9992. # $h2_uidguess
  9993. # ...
  9994. #
  9995. #
  9996. sub append_message_on_host2
  9997. {
  9998. my( $mysync, $string_ref, $h1_fold, $h1_msg, $string_len, $h2_fold, $h1_size, $h1_flags, $h1_date, $cache_dir ) = @_ ;
  9999. myprint( debugmemory( $mysync, " at A1" ) ) ;
  10000. my $new_id ;
  10001. if ( ! $mysync->{dry} ) {
  10002. $new_id = $mysync->{imap2}->append_string( $h2_fold, ${ $string_ref }, $h1_flags, $h1_date ) ;
  10003. myprint( debugmemory( $mysync, " at A2" ) ) ;
  10004. if ( ! defined $new_id ){
  10005. my $subject = subject( ${ $string_ref } ) ;
  10006. my $error_imap = $mysync->{imap2}->LastError || q{} ;
  10007. 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" ;
  10008. errors_incr( $mysync, $error ) ;
  10009. $mysync->{ h1_nb_msg_processed } +=1 ;
  10010. return ;
  10011. }
  10012. else{
  10013. # good
  10014. # $new_id is an id if the IMAP server has the
  10015. # UIDPLUS capability else just a ref
  10016. if ( $new_id !~ m{^\d+$}x ) {
  10017. $new_id = lastuid( $mysync->{imap2}, $h2_fold, $h2_uidguess ) ;
  10018. }
  10019. if ( $mysync->{ synclabels } ) { synclabels( $mysync, $h1_msg, $new_id ) }
  10020. $h2_uidguess += 1 ;
  10021. $mysync->{ total_bytes_transferred } += $string_len ;
  10022. $mysync->{ nb_msg_transferred } += 1 ;
  10023. $mysync->{ h1_nb_msg_processed } +=1 ;
  10024. $mysync->{ biggest_message_transferred } = max( $string_len, $mysync->{ biggest_message_transferred } ) ;
  10025. my $time_spent = timesince( $mysync->{begin_transfer_time} ) ;
  10026. my $rate = bytes_display_string_bin( $mysync->{total_bytes_transferred} / $time_spent ) ;
  10027. my $eta = eta( $mysync ) ;
  10028. my $amount_transferred = bytes_display_string_bin( $mysync->{total_bytes_transferred} ) ;
  10029. myprintf( "msg %s/%-19s copied to %s/%-10s %.2f msgs/s %s/s %s copied %s\n",
  10030. $h1_fold, "$h1_msg {$string_len}", $h2_fold, $new_id, $mysync->{nb_msg_transferred}/$time_spent, $rate,
  10031. $amount_transferred,
  10032. $eta );
  10033. sleep_if_needed( $mysync ) ;
  10034. if ( $sync->{ usecache } and $cacheaftercopy and $new_id =~ m{^\d+$}x ) {
  10035. $debugcache and myprint( "touch $cache_dir/${h1_msg}_$new_id\n" ) ;
  10036. touch( "$cache_dir/${h1_msg}_$new_id" )
  10037. or croak( "Couldn't touch $cache_dir/${h1_msg}_$new_id" ) ;
  10038. }
  10039. if ( $mysync->{ delete1 } ) {
  10040. delete_message_on_host1( $mysync, $h1_fold, $mysync->{ expungeaftereach }, $h1_msg ) ;
  10041. }
  10042. #myprint( "PRESS ENTER" ) and my $a = <> ;
  10043. return( $new_id ) ;
  10044. }
  10045. }
  10046. else{
  10047. $nb_msg_skipped_dry_mode += 1 ;
  10048. $mysync->{ h1_nb_msg_processed } += 1 ;
  10049. }
  10050. return ;
  10051. }
  10052. sub tests_sleep_if_needed
  10053. {
  10054. note( 'Entering tests_sleep_if_needed()' ) ;
  10055. is( undef, sleep_if_needed( ), 'sleep_if_needed: no args => undef' ) ;
  10056. my $mysync ;
  10057. is( undef, sleep_if_needed( $mysync ), 'sleep_if_needed: arg undef => undef' ) ;
  10058. $mysync->{maxbytespersecond} = 1000 ;
  10059. is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: maxbytespersecond only => no sleep => 0' ) ;
  10060. $mysync->{begin_transfer_time} = time ; # now
  10061. is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: begin_transfer_time now => no sleep => 0' ) ;
  10062. $mysync->{begin_transfer_time} = time - 2 ; # 2 s before
  10063. is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 0 => no sleep => 0' ) ;
  10064. $mysync->{total_bytes_transferred} = 2200 ;
  10065. $mysync->{begin_transfer_time} = time - 2 ; # 2 s before
  10066. is( '0.20', sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 2200 since 2s => sleep 0.2s' ) ;
  10067. is( '0', sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 2200 since 2+2 == 4s => no sleep' ) ;
  10068. $mysync->{maxsleep} = 0.1 ;
  10069. $mysync->{begin_transfer_time} = time - 2 ; # 2 s before again
  10070. is( '0.10', sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 4000 since 2s but maxsleep 0.1s => sleep 0.1s' ) ;
  10071. $mysync->{maxbytesafter} = 4000 ;
  10072. $mysync->{begin_transfer_time} = time - 2 ; # 2 s before again
  10073. is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: maxbytesafter == total_bytes_transferred => no sleep => 0' ) ;
  10074. note( 'Leaving tests_sleep_if_needed()' ) ;
  10075. return ;
  10076. }
  10077. sub sleep_if_needed
  10078. {
  10079. my( $mysync ) = shift @ARG ;
  10080. if ( ! $mysync ) {
  10081. return ;
  10082. }
  10083. # No need to go further if there is no limit set
  10084. if (
  10085. not (
  10086. $mysync->{maxmessagespersecond}
  10087. or $mysync->{maxbytespersecond}
  10088. )
  10089. ) {
  10090. return ;
  10091. }
  10092. $mysync->{maxsleep} = defined $mysync->{maxsleep} ? $mysync->{maxsleep} : $MAX_SLEEP ;
  10093. # Must be positive
  10094. $mysync->{maxsleep} = max( 0, $mysync->{maxsleep} ) ;
  10095. my $time_spent = timesince( $mysync->{begin_transfer_time} ) ;
  10096. my $sleep_max_messages = sleep_max_messages( $mysync->{nb_msg_transferred}, $time_spent, $mysync->{maxmessagespersecond} ) ;
  10097. my $maxbytesafter = $mysync->{maxbytesafter} || 0 ;
  10098. my $total_bytes_transferred = $mysync->{total_bytes_transferred} || 0 ;
  10099. my $total_bytes_to_consider = $total_bytes_transferred - $maxbytesafter ;
  10100. #myprint( "maxbytesafter:$maxbytesafter\n" ) ;
  10101. #myprint( "total_bytes_to_consider:$total_bytes_to_consider\n" ) ;
  10102. my $sleep_max_bytes = sleep_max_bytes( $total_bytes_to_consider, $time_spent, $mysync->{maxbytespersecond} ) ;
  10103. my $sleep_max = min( $mysync->{maxsleep}, max( $sleep_max_messages, $sleep_max_bytes ) ) ;
  10104. $sleep_max = mysprintf( "%.2f", $sleep_max ) ; # round with 2 decimals.
  10105. if ( $sleep_max > 0 ) {
  10106. myprint( "sleeping $sleep_max s\n" ) ;
  10107. sleep $sleep_max ;
  10108. # Slept
  10109. return $sleep_max ;
  10110. }
  10111. # No sleep
  10112. return 0 ;
  10113. }
  10114. sub sleep_max_messages
  10115. {
  10116. # how long we have to sleep to go under max_messages_per_second
  10117. my( $nb_msg_transferred, $time_spent, $maxmessagespersecond ) = @_ ;
  10118. if ( ( not defined $maxmessagespersecond ) or $maxmessagespersecond <= 0 ) { return( 0 ) } ;
  10119. my $sleep = ( $nb_msg_transferred / $maxmessagespersecond ) - $time_spent ;
  10120. # the sleep must be positive
  10121. return( max( 0, $sleep ) ) ;
  10122. }
  10123. sub tests_sleep_max_messages
  10124. {
  10125. note( 'Entering tests_sleep_max_messages()' ) ;
  10126. ok( 0 == sleep_max_messages( 4, 2, undef ), 'sleep_max_messages: maxmessagespersecond = undef') ;
  10127. ok( 0 == sleep_max_messages( 4, 2, 0 ), 'sleep_max_messages: maxmessagespersecond = 0') ;
  10128. ok( 0 == sleep_max_messages( 4, 2, $MINUS_ONE ), 'sleep_max_messages: maxmessagespersecond = -1') ;
  10129. ok( 0 == sleep_max_messages( 4, 2, 2 ), 'sleep_max_messages: maxmessagespersecond = 2 max reached') ;
  10130. ok( 2 == sleep_max_messages( 8, 2, 2 ), 'sleep_max_messages: maxmessagespersecond = 2 max over') ;
  10131. ok( 0 == sleep_max_messages( 2, 2, 2 ), 'sleep_max_messages: maxmessagespersecond = 2 max not reached') ;
  10132. note( 'Leaving tests_sleep_max_messages()' ) ;
  10133. return ;
  10134. }
  10135. sub sleep_max_bytes
  10136. {
  10137. # how long we have to sleep to go under max_bytes_per_second
  10138. my( $total_bytes_to_consider, $time_spent, $maxbytespersecond ) = @_ ;
  10139. $total_bytes_to_consider ||= 0 ;
  10140. $time_spent ||= 0 ;
  10141. if ( ( not defined $maxbytespersecond ) or $maxbytespersecond <= 0 ) { return( 0 ) } ;
  10142. #myprint( "total_bytes_to_consider:$total_bytes_to_consider\n" ) ;
  10143. my $sleep = ( $total_bytes_to_consider / $maxbytespersecond ) - $time_spent ;
  10144. # the sleep must be positive
  10145. return( max( 0, $sleep ) ) ;
  10146. }
  10147. sub tests_sleep_max_bytes
  10148. {
  10149. note( 'Entering tests_sleep_max_bytes()' ) ;
  10150. ok( 0 == sleep_max_bytes( 4000, 2, undef ), 'sleep_max_bytes: maxbytespersecond == undef => sleep 0' ) ;
  10151. ok( 0 == sleep_max_bytes( 4000, 2, 0 ), 'sleep_max_bytes: maxbytespersecond = 0 => sleep 0') ;
  10152. ok( 0 == sleep_max_bytes( 4000, 2, $MINUS_ONE ), 'sleep_max_bytes: maxbytespersecond = -1 => sleep 0') ;
  10153. ok( 0 == sleep_max_bytes( 4000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max reached sharp => sleep 0') ;
  10154. ok( 2 == sleep_max_bytes( 8000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max over => sleep a little') ;
  10155. ok( 0 == sleep_max_bytes( -8000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max not reached => sleep 0') ;
  10156. ok( 0 == sleep_max_bytes( 2000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max not reached => sleep 0') ;
  10157. ok( 0 == sleep_max_bytes( -2000, 2, 1000 ), 'sleep_max_bytes: maxbytespersecond = 1k max not reached => sleep 0') ;
  10158. note( 'Leaving tests_sleep_max_bytes()' ) ;
  10159. return ;
  10160. }
  10161. sub delete_message_on_host1
  10162. {
  10163. my( $mysync, $h1_fold, $expunge, @h1_msg ) = @_ ;
  10164. if ( ! $mysync->{ delete1 } ) { return ; }
  10165. if ( ! @h1_msg ) { return ; }
  10166. delete_messages_on_any(
  10167. $mysync,
  10168. $mysync->{ acc1 },
  10169. $mysync->{ imap1 },
  10170. "Host1: $h1_fold",
  10171. $expunge,
  10172. $split1,
  10173. @h1_msg ) ;
  10174. return ;
  10175. }
  10176. sub tests_operators_and_exclam_precedence
  10177. {
  10178. note( 'Entering tests_operators_and_exclam_precedence()' ) ;
  10179. is( 1, ! 0, 'tests_operators_and_exclam_precedence: ! 0 => 1' ) ;
  10180. is( "", ! 1, 'tests_operators_and_exclam_precedence: ! 1 => ""' ) ;
  10181. is( 1, not( 0 ), 'tests_operators_and_exclam_precedence: not( 0 ) => 1' ) ;
  10182. is( "", not( 1 ), 'tests_operators_and_exclam_precedence: not( 1 ) => ""' ) ;
  10183. # I wrote those tests to avoid perlcrit "Mixed high and low-precedence booleans"
  10184. # and change sub delete_messages_on_any() but got 4 more warnings... So now commented.
  10185. #is( 0, ( ! 0 and 0 ), 'tests_operators_and_exclam_precedence: ! 0 and 0 ) => 0' ) ;
  10186. #is( 1, ( ! 0 and 1 ), 'tests_operators_and_exclam_precedence: ! 0 and 1 ) => 1' ) ;
  10187. #is( "", ( ! 1 and 0 ), 'tests_operators_and_exclam_precedence: ! 1 and 0 ) => ""' ) ;
  10188. #is( "", ( ! 1 and 1 ), 'tests_operators_and_exclam_precedence: ! 1 and 1 ) => ""' ) ;
  10189. is( 0, ( ! 0 && 0 ), 'tests_operators_and_exclam_precedence: ! 0 && 0 ) => 0' ) ;
  10190. is( 1, ( ! 0 && 1 ), 'tests_operators_and_exclam_precedence: ! 0 && 1 ) => 1' ) ;
  10191. is( "", ( ! 1 && 0 ), 'tests_operators_and_exclam_precedence: ! 1 && 0 ) => ""' ) ;
  10192. is( "", ( ! 1 && 1 ), 'tests_operators_and_exclam_precedence: ! 1 && 1 ) => ""' ) ;
  10193. is( 2, ( ! 0 && 2 ), 'tests_operators_and_exclam_precedence: ! 0 && 2 ) => 1' ) ;
  10194. note( 'Leaving tests_operators_and_exclam_precedence()' ) ;
  10195. return ;
  10196. }
  10197. sub delete_messages_on_any
  10198. {
  10199. # $acc is not used yet,
  10200. #
  10201. my( $mysync, $acc, $imap, $hostX_folder, $expunge, $split, @messages ) = @_ ;
  10202. my $expunge_message = q{} ;
  10203. my $dry_message = $mysync->{ dry_message } ;
  10204. $expunge_message = 'and expunged' if ( $expunge ) ;
  10205. # "Host1: msg "
  10206. # $imap->Debug( 1 ) ;
  10207. my @messages_to_mark_deleted = @messages ;
  10208. while ( my @messages_part = splice @messages_to_mark_deleted, 0, $split )
  10209. {
  10210. foreach my $message ( @messages_part )
  10211. {
  10212. myprint( "$hostX_folder/$message marking deleted $expunge_message $dry_message\n" ) ;
  10213. }
  10214. if ( ! $mysync->{dry} && @messages_part )
  10215. {
  10216. my $nb_deleted = $imap->delete_message( $imap->Range( @messages_part ) ) ;
  10217. if ( defined $nb_deleted )
  10218. {
  10219. # $nb_deleted is not accurate
  10220. $acc->{ nb_msg_deleted } += scalar @messages_part ;
  10221. }
  10222. else
  10223. {
  10224. my $error_imap = $imap->LastError || q{} ;
  10225. my $error = join( q{}, "$hostX_folder folder, could not delete ",
  10226. scalar @messages_part, ' messages: ', $error_imap, "\n" ) ;
  10227. errors_incr( $mysync, $error ) ;
  10228. }
  10229. }
  10230. }
  10231. if ( $expunge ) {
  10232. uidexpunge_or_expunge( $mysync, $imap, @messages ) ;
  10233. }
  10234. #$imap->Debug( 0 ) ;
  10235. return ;
  10236. }
  10237. sub tests_uidexpunge_or_expunge
  10238. {
  10239. note( 'Entering tests_uidexpunge_or_expunge()' ) ;
  10240. is( undef, uidexpunge_or_expunge( ), 'uidexpunge_or_expunge: no args => undef' ) ;
  10241. my $mysync ;
  10242. is( undef, uidexpunge_or_expunge( $mysync ), 'uidexpunge_or_expunge: undef args => undef' ) ;
  10243. $mysync = {} ;
  10244. is( undef, uidexpunge_or_expunge( $mysync ), 'uidexpunge_or_expunge: arg empty => undef' ) ;
  10245. my $imap ;
  10246. is( undef, uidexpunge_or_expunge( $mysync, $imap ), 'uidexpunge_or_expunge: undef Mail-IMAPClient instance => undef' ) ;
  10247. require_ok( "Test::MockObject" ) ;
  10248. $imap = Test::MockObject->new( ) ;
  10249. is( undef, uidexpunge_or_expunge( $mysync, $imap ), 'uidexpunge_or_expunge: no message (1) to uidexpunge => undef' ) ;
  10250. my @messages = ( ) ;
  10251. is( undef, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: no message (2) to uidexpunge => undef' ) ;
  10252. @messages = ( '2', '1' ) ;
  10253. $imap->mock( 'uidexpunge', sub { return ; } ) ;
  10254. $imap->mock( 'expunge', sub { return ; } ) ;
  10255. is( undef, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: uidexpunge failure => expunge failure => undef' ) ;
  10256. $imap->mock( 'expunge', sub { return 1 ; } ) ;
  10257. is( 1, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: uidexpunge failure => expunge ok => 1' ) ;
  10258. $imap->mock( 'uidexpunge', sub { return 1 ; } ) ;
  10259. is( 1, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: messages to uidexpunge ok => 1' ) ;
  10260. note( 'Leaving tests_uidexpunge_or_expunge()' ) ;
  10261. return ;
  10262. }
  10263. sub uidexpunge_or_expunge
  10264. {
  10265. my $mysync = shift @ARG ;
  10266. my $imap = shift @ARG ;
  10267. my @messages = @ARG ;
  10268. if ( ! $imap ) { return ; } ;
  10269. if ( ! @messages ) { return ; } ;
  10270. # Doing uidexpunge
  10271. my @uidexpunge_result = $imap->uidexpunge( @messages ) ;
  10272. if ( @uidexpunge_result ) {
  10273. return 1 ;
  10274. }
  10275. # Failure so doing expunge
  10276. my $expunge_result = $imap->expunge( ) ;
  10277. if ( $expunge_result ) {
  10278. return 1 ;
  10279. }
  10280. # bad trip
  10281. return ;
  10282. }
  10283. sub eta_print
  10284. {
  10285. my $mysync = shift @ARG ;
  10286. if ( my $eta = eta( $mysync ) )
  10287. {
  10288. myprint( "$eta\n" ) ;
  10289. }
  10290. return ;
  10291. }
  10292. sub tests_eta
  10293. {
  10294. note( 'Entering tests_eta()' ) ;
  10295. is( q{}, eta( ), 'eta: no args => ""' ) ;
  10296. is( q{}, eta( undef ), 'eta: undef => ""' ) ;
  10297. my $mysync = {} ;
  10298. # No foldersizes
  10299. is( q{}, eta( $mysync ), 'eta: No foldersizes => ""' ) ;
  10300. $mysync->{ foldersizes } = 1 ;
  10301. $mysync->{ begin_transfer_time } = time ; # Now
  10302. $mysync->{ h1_nb_msg_processed } = 0 ;
  10303. is( "ETA: " . localtimez( time ) . " 0 s 0/0 msgs left",
  10304. eta( $mysync ),
  10305. 'eta: no args => ETA: "Now" 0 s 0/0 msgs left' ) ;
  10306. $mysync->{ h1_nb_msg_processed } = 1 ;
  10307. $mysync->{ h1_nb_msg_start } = 2 ;
  10308. is( "ETA: " . localtimez( time ) . " 0 s 1/2 msgs left",
  10309. eta( $mysync ),
  10310. 'eta: 1, 1, 2 => ETA: "Now" 0 s 1/2 msgs left' ) ;
  10311. note( 'Leaving tests_eta()' ) ;
  10312. return ;
  10313. }
  10314. sub eta
  10315. {
  10316. my( $mysync ) = shift @ARG ;
  10317. if ( ! $mysync )
  10318. {
  10319. return q{} ;
  10320. }
  10321. return( q{} ) if not $mysync->{ foldersizes } ;
  10322. my $h1_nb_msg_start = $mysync->{ h1_nb_msg_start } ;
  10323. my $h1_nb_processed = $mysync->{ h1_nb_msg_processed } ;
  10324. my $nb_msg_transferred = ( $mysync->{dry} ) ? $mysync->{ h1_nb_msg_processed } : $mysync->{ nb_msg_transferred } ;
  10325. my $time_spent = timesince( $mysync->{ begin_transfer_time } ) ;
  10326. $h1_nb_processed ||= 0 ;
  10327. $h1_nb_msg_start ||= 0 ;
  10328. $time_spent ||= 0 ;
  10329. my $time_remaining = time_remaining( $time_spent, $h1_nb_processed, $h1_nb_msg_start, $nb_msg_transferred ) ;
  10330. $mysync->{ debug } and myprint( "time_spent: $time_spent time_remaining: $time_remaining\n" ) ;
  10331. my $nb_msg_remaining = $h1_nb_msg_start - $h1_nb_processed ;
  10332. my $eta_date = localtimez( time + $time_remaining ) ;
  10333. return( mysprintf( 'ETA: %s %1.0f s %s/%s msgs left',
  10334. $eta_date, $time_remaining, $nb_msg_remaining, $h1_nb_msg_start ) ) ;
  10335. }
  10336. sub time_remaining
  10337. {
  10338. my( $my_time_spent, $h1_nb_processed, $h1_nb_msg_start, $nb_transferred ) = @_ ;
  10339. $nb_transferred ||= 1 ; # At least one is done (no division by zero)
  10340. $h1_nb_processed ||= 0 ;
  10341. $h1_nb_msg_start ||= $h1_nb_processed ;
  10342. $my_time_spent ||= 0 ;
  10343. my $time_remaining = ( $my_time_spent / $nb_transferred ) * ( $h1_nb_msg_start - $h1_nb_processed ) ;
  10344. return( $time_remaining ) ;
  10345. }
  10346. sub tests_time_remaining
  10347. {
  10348. note( 'Entering tests_time_remaining()' ) ;
  10349. # time_spent, nb_processed, nb_to_do_total, nb_transferred
  10350. is( 0, time_remaining( ), 'time_remaining: no args -> 0' ) ;
  10351. is( 0, time_remaining( 0, 0, 0, 0 ), 'time_remaining: 0, 0, 0, 0 -> 0' ) ;
  10352. is( 1, time_remaining( 1, 1, 2, 1 ), 'time_remaining: 1, 1, 2, 1 -> 1' ) ;
  10353. is( 1, time_remaining( 9, 9, 10, 9 ), 'time_remaining: 9, 9, 10, 9 -> 1' ) ;
  10354. is( 9, time_remaining( 1, 1, 10, 1 ), 'time_remaining: 1, 1, 10, 1 -> 9' ) ;
  10355. is( 5, time_remaining( 5, 5, 10, 5 ), 'time_remaining: 5, 5, 10, 5 -> 5' ) ;
  10356. is( 25, time_remaining( 5, 5, 10, 0 ), 'time_remaining: 5, 5, 10, 0 -> ( 5 / 1 ) * ( 10 - 5) = 25' ) ;
  10357. is( 25, time_remaining( 5, 5, 10, 1 ), 'time_remaining: 5, 5, 10, 1 -> ( 5 / 1 ) * ( 10 - 5) = 25' ) ;
  10358. note( 'Leaving tests_time_remaining()' ) ;
  10359. return ;
  10360. }
  10361. sub tests_usecache_and_skipcrossduplicates
  10362. {
  10363. note( 'Entering tests_usecache_and_skipcrossduplicates()' ) ;
  10364. is( undef, usecache_and_skipcrossduplicates( ), 'usecache_and_skipcrossduplicates: no args => undef' ) ;
  10365. my $mysync = { } ;
  10366. is( undef, usecache_and_skipcrossduplicates( $mysync ), 'usecache_and_skipcrossduplicates: undef => undef' ) ;
  10367. $mysync->{ usecache } = $mysync->{ skipcrossduplicates } = 1 ;
  10368. is( 1, usecache_and_skipcrossduplicates( $mysync ), 'usecache_and_skipcrossduplicates: usecache=skipcrossduplicates=1 => wrong' ) ;
  10369. note( 'Leaving tests_usecache_and_skipcrossduplicates()' ) ;
  10370. return ;
  10371. }
  10372. sub usecache_and_skipcrossduplicates
  10373. {
  10374. my $mysync = shift @ARG ;
  10375. if ( ! defined $mysync ) { return ; }
  10376. if ( ( $mysync->{ usecache } ) and ( $mysync->{ skipcrossduplicates } ) )
  10377. {
  10378. return 1 ;
  10379. }
  10380. return ;
  10381. }
  10382. sub cache_map
  10383. {
  10384. my ( $cache_files_ref, $h1_msgs_ref, $h2_msgs_ref ) = @_;
  10385. my ( %map1_2, %map2_1, %done2 ) ;
  10386. my $h1_msgs_hash_ref = { } ;
  10387. my $h2_msgs_hash_ref = { } ;
  10388. @{ $h1_msgs_hash_ref }{ @{ $h1_msgs_ref } } = ( ) ;
  10389. @{ $h2_msgs_hash_ref }{ @{ $h2_msgs_ref } } = ( ) ;
  10390. foreach my $file ( sort @{ $cache_files_ref } ) {
  10391. $debugcache and myprint( "C12: $file\n" ) ;
  10392. ( $uid1, $uid2 ) = match_a_cache_file( $file ) ;
  10393. if ( exists( $h1_msgs_hash_ref->{ defined $uid1 ? $uid1 : q{} } )
  10394. and exists( $h2_msgs_hash_ref->{ defined $uid2 ? $uid2 : q{} } ) ) {
  10395. # keep only the greatest uid2
  10396. # 130_2301 and
  10397. # 130_231 => keep only 130 -> 2301
  10398. # keep only the greatest uid1
  10399. # 1601_260 and
  10400. # 161_260 => keep only 1601 -> 260
  10401. my $max_uid2 = max( $uid2, $map1_2{ $uid1 } || $MINUS_ONE ) ;
  10402. if ( exists $done2{ $max_uid2 } ) {
  10403. if ( $done2{ $max_uid2 } < $uid1 ) {
  10404. $map1_2{ $uid1 } = $max_uid2 ;
  10405. delete $map1_2{ $done2{ $max_uid2 } } ;
  10406. $done2{ $max_uid2 } = $uid1 ;
  10407. }
  10408. }else{
  10409. $map1_2{ $uid1 } = $max_uid2 ;
  10410. $done2{ $max_uid2 } = $uid1 ;
  10411. }
  10412. };
  10413. }
  10414. %map2_1 = reverse %map1_2 ;
  10415. return( \%map1_2, \%map2_1) ;
  10416. }
  10417. sub tests_cache_map
  10418. {
  10419. note( 'Entering tests_cache_map()' ) ;
  10420. #$debugcache = 1 ;
  10421. my @cache_files = qw (
  10422. 100_200
  10423. 101_201
  10424. 120_220
  10425. 142_242
  10426. 143_243
  10427. 177_277
  10428. 177_278
  10429. 177_279
  10430. 155_255
  10431. 180_280
  10432. 181_280
  10433. 182_280
  10434. 130_231
  10435. 130_2301
  10436. 161_260
  10437. 1601_260
  10438. ) ;
  10439. my $msgs_1 = [120, 142, 143, 144, 161, 1601, 177, 182, 130 ];
  10440. my $msgs_2 = [ 242, 243, 260, 299, 377, 279, 255, 280, 231, 2301 ];
  10441. my( $c12, $c21 ) ;
  10442. ok( ( $c12, $c21 ) = cache_map( \@cache_files, $msgs_1, $msgs_2 ), 'cache_map: 02' );
  10443. my $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
  10444. my $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
  10445. ok( 0 == compare_lists( [ 130, 142, 143, 177, 182, 1601 ], $a1 ), 'cache_map: 03' );
  10446. ok( 0 == compare_lists( [ 242, 243, 260, 279, 280, 2301 ], $a2 ), 'cache_map: 04' );
  10447. ok( ! $c12->{161}, 'cache_map: ! 161 -> 260' );
  10448. ok( 260 == $c12->{1601}, 'cache_map: 1601 -> 260' );
  10449. ok( 2301 == $c12->{130}, 'cache_map: 130 -> 2301' );
  10450. #myprint( $c12->{1601}, "\n" ) ;
  10451. note( 'Leaving tests_cache_map()' ) ;
  10452. return ;
  10453. }
  10454. sub cache_dir_fix
  10455. {
  10456. my $cache_dir = shift @ARG ;
  10457. $cache_dir =~ s/([;<>\*\|`&\$!#\(\)\[\]\{\}:'"\\])/\\$1/xg ;
  10458. #myprint( "cache_dir_fix: $cache_dir\n" ) ;
  10459. return( $cache_dir ) ;
  10460. }
  10461. sub tests_cache_dir_fix
  10462. {
  10463. note( 'Entering tests_cache_dir_fix()' ) ;
  10464. ok( 'lalala' eq cache_dir_fix('lalala'), 'cache_dir_fix: lalala -> lalala' );
  10465. ok( 'ii\\\\ii' eq cache_dir_fix('ii\ii'), 'cache_dir_fix: ii\ii -> ii\\\\ii' );
  10466. ok( 'ii@ii' eq cache_dir_fix('ii@ii'), 'cache_dir_fix: ii@ii -> ii@ii' );
  10467. ok( 'ii@ii\\:ii' eq cache_dir_fix('ii@ii:ii'), 'cache_dir_fix: ii@ii:ii -> ii@ii\\:ii' );
  10468. ok( 'i\\\\i\\\\ii' eq cache_dir_fix('i\i\ii'), 'cache_dir_fix: i\i\ii -> i\\\\i\\\\ii' );
  10469. ok( 'i\\\\ii' eq cache_dir_fix('i\\ii'), 'cache_dir_fix: i\\ii -> i\\\\\\\\ii' );
  10470. ok( '\\\\ ' eq cache_dir_fix('\\ '), 'cache_dir_fix: \\ -> \\\\\ ' );
  10471. ok( '\\\\ ' eq cache_dir_fix('\ '), 'cache_dir_fix: \ -> \\\\\ ' );
  10472. ok( '\[bracket\]' eq cache_dir_fix('[bracket]'), 'cache_dir_fix: [bracket] -> \[bracket\]' );
  10473. note( 'Leaving tests_cache_dir_fix()' ) ;
  10474. return ;
  10475. }
  10476. sub cache_dir_fix_win
  10477. {
  10478. my $cache_dir = shift @ARG ;
  10479. $cache_dir =~ s/(\[|\])/[$1]/xg ;
  10480. #myprint( "cache_dir_fix_win: $cache_dir\n" ) ;
  10481. return( $cache_dir ) ;
  10482. }
  10483. sub tests_cache_dir_fix_win
  10484. {
  10485. note( 'Entering tests_cache_dir_fix_win()' ) ;
  10486. ok( 'lalala' eq cache_dir_fix_win('lalala'), 'cache_dir_fix_win: lalala -> lalala' );
  10487. ok( '[[]bracket[]]' eq cache_dir_fix_win('[bracket]'), 'cache_dir_fix_win: [bracket] -> [[]bracket[]]' );
  10488. note( 'Leaving tests_cache_dir_fix_win()' ) ;
  10489. return ;
  10490. }
  10491. sub get_cache
  10492. {
  10493. my ( $cache_dir, $h1_msgs_ref, $h2_msgs_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) = @_;
  10494. $debugcache and myprint( "Entering get_cache\n" ) ;
  10495. -d $cache_dir or return( undef ); # exit if cache directory doesn't exist
  10496. $debugcache and myprint( "cache_dir : $cache_dir\n" ) ;
  10497. if ( 'MSWin32' ne $OSNAME ) {
  10498. $cache_dir = cache_dir_fix( $cache_dir ) ;
  10499. }else{
  10500. $cache_dir = cache_dir_fix_win( $cache_dir ) ;
  10501. }
  10502. $debugcache and myprint( "cache_dir_fix: $cache_dir\n" ) ;
  10503. my @cache_files = bsd_glob( "$cache_dir/*" ) ;
  10504. #$debugcache and myprint( "cache_files: [@cache_files]\n" ) ;
  10505. $debugcache and myprint( 'cache_files: ', scalar @cache_files , " files found\n" ) ;
  10506. my( $cache_1_2_ref, $cache_2_1_ref )
  10507. = cache_map( \@cache_files, $h1_msgs_ref, $h2_msgs_ref ) ;
  10508. clean_cache( \@cache_files, $cache_1_2_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) ;
  10509. $debugcache and myprint( "Exiting get_cache\n" ) ;
  10510. return( $cache_1_2_ref, $cache_2_1_ref ) ;
  10511. }
  10512. sub tests_get_cache
  10513. {
  10514. note( 'Entering tests_get_cache()' ) ;
  10515. ok( not( get_cache('/cache_no_exist') ), 'get_cache: /cache_no_exist' );
  10516. ok( ( not -d 'W/tmp/cache/F1/F2' or rmtree( 'W/tmp/cache/F1/F2' ) ), 'get_cache: rmtree W/tmp/cache/F1/F2' ) ;
  10517. ok( mkpath( 'W/tmp/cache/F1/F2' ), 'get_cache: mkpath W/tmp/cache/F1/F2' ) ;
  10518. my @test_files_cache = ( qw(
  10519. W/tmp/cache/F1/F2/100_200
  10520. W/tmp/cache/F1/F2/101_201
  10521. W/tmp/cache/F1/F2/120_220
  10522. W/tmp/cache/F1/F2/142_242
  10523. W/tmp/cache/F1/F2/143_243
  10524. W/tmp/cache/F1/F2/177_277
  10525. W/tmp/cache/F1/F2/177_377
  10526. W/tmp/cache/F1/F2/177_777
  10527. W/tmp/cache/F1/F2/155_255
  10528. ) ) ;
  10529. ok( touch( @test_files_cache ), 'get_cache: touch W/tmp/cache/F1/F2/...' ) ;
  10530. # on cache: 100_200 101_201 142_242 143_243 177_277 177_377 177_777 155_255
  10531. # on live:
  10532. my $msgs_1 = [120, 142, 143, 144, 177 ];
  10533. my $msgs_2 = [ 242, 243, 299, 377, 777, 255 ];
  10534. my $msgs_all_1 = { 120 => 0, 142 => 0, 143 => 0, 144 => 0, 177 => 0 } ;
  10535. my $msgs_all_2 = { 242 => 0, 243 => 0, 299 => 0, 377 => 0, 777 => 0, 255 => 0 } ;
  10536. my( $c12, $c21 ) ;
  10537. ok( ( $c12, $c21 ) = get_cache( 'W/tmp/cache/F1/F2', $msgs_1, $msgs_2, $msgs_all_1, $msgs_all_2 ), 'get_cache: 02' );
  10538. my $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
  10539. my $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
  10540. ok( 0 == compare_lists( [ 142, 143, 177 ], $a1 ), 'get_cache: 03' );
  10541. ok( 0 == compare_lists( [ 242, 243, 777 ], $a2 ), 'get_cache: 04' );
  10542. ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 142_242');
  10543. ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 143_243');
  10544. ok( ! -f 'W/tmp/cache/F1/F2/100_200', 'get_cache: file removed 100_200');
  10545. ok( ! -f 'W/tmp/cache/F1/F2/101_201', 'get_cache: file removed 101_201');
  10546. # test clean_cache executed
  10547. $maxage = 2 ;
  10548. ok( touch(@test_files_cache), 'get_cache: touch W/tmp/cache/F1/F2/...' ) ;
  10549. ok( ( $c12, $c21 ) = get_cache('W/tmp/cache/F1/F2', $msgs_1, $msgs_2, $msgs_all_1, $msgs_all_2 ), 'get_cache: 02' );
  10550. ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 142_242');
  10551. ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 143_243');
  10552. ok( ! -f 'W/tmp/cache/F1/F2/100_200', 'get_cache: file NOT removed 100_200');
  10553. ok( ! -f 'W/tmp/cache/F1/F2/101_201', 'get_cache: file NOT removed 101_201');
  10554. # strange files
  10555. #$debugcache = 1 ;
  10556. $maxage = undef ;
  10557. ok( ( not -d 'W/tmp/cache/rr\uee' or rmtree( 'W/tmp/cache/rr\uee' )), 'get_cache: rmtree W/tmp/cache/rr\uee' ) ;
  10558. ok( mkpath( 'W/tmp/cache/rr\uee' ), 'get_cache: mkpath W/tmp/cache/rr\uee' ) ;
  10559. @test_files_cache = ( qw(
  10560. W/tmp/cache/rr\uee/100_200
  10561. W/tmp/cache/rr\uee/101_201
  10562. W/tmp/cache/rr\uee/120_220
  10563. W/tmp/cache/rr\uee/142_242
  10564. W/tmp/cache/rr\uee/143_243
  10565. W/tmp/cache/rr\uee/177_277
  10566. W/tmp/cache/rr\uee/177_377
  10567. W/tmp/cache/rr\uee/177_777
  10568. W/tmp/cache/rr\uee/155_255
  10569. ) ) ;
  10570. ok( touch(@test_files_cache), 'get_cache: touch strange W/tmp/cache/...' ) ;
  10571. # on cache: 100_200 101_201 142_242 143_243 177_277 177_377 177_777 155_255
  10572. # on live:
  10573. $msgs_1 = [120, 142, 143, 144, 177 ] ;
  10574. $msgs_2 = [ 242, 243, 299, 377, 777, 255 ] ;
  10575. $msgs_all_1 = { 120 => q{}, 142 => q{}, 143 => q{}, 144 => q{}, 177 => q{} } ;
  10576. $msgs_all_2 = { 242 => q{}, 243 => q{}, 299 => q{}, 377 => q{}, 777 => q{}, 255 => q{} } ;
  10577. 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' );
  10578. $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
  10579. $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
  10580. ok( 0 == compare_lists( [ 142, 143, 177 ], $a1 ), 'get_cache: strange path 03' );
  10581. ok( 0 == compare_lists( [ 242, 243, 777 ], $a2 ), 'get_cache: strange path 04' );
  10582. ok( -f 'W/tmp/cache/rr\uee/142_242', 'get_cache: strange path file kept 142_242');
  10583. ok( -f 'W/tmp/cache/rr\uee/142_242', 'get_cache: strange path file kept 143_243');
  10584. ok( ! -f 'W/tmp/cache/rr\uee/100_200', 'get_cache: strange path file removed 100_200');
  10585. ok( ! -f 'W/tmp/cache/rr\uee/101_201', 'get_cache: strange path file removed 101_201');
  10586. note( 'Leaving tests_get_cache()' ) ;
  10587. return ;
  10588. }
  10589. sub match_a_cache_file
  10590. {
  10591. my $file = shift @ARG ;
  10592. my ( $cache_uid1, $cache_uid2 ) ;
  10593. return( ( undef, undef ) ) if ( ! $file ) ;
  10594. if ( $file =~ m{(?:^|/)(\d+)_(\d+)$}x ) {
  10595. $cache_uid1 = $1 ;
  10596. $cache_uid2 = $2 ;
  10597. }
  10598. return( $cache_uid1, $cache_uid2 ) ;
  10599. }
  10600. sub tests_match_a_cache_file
  10601. {
  10602. note( 'Entering tests_match_a_cache_file()' ) ;
  10603. my ( $tuid1, $tuid2 ) ;
  10604. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( ), 'match_a_cache_file: no arg' ) ;
  10605. ok( ! defined $tuid1 , 'match_a_cache_file: no arg 1' ) ;
  10606. ok( ! defined $tuid2 , 'match_a_cache_file: no arg 2' ) ;
  10607. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( q{} ), 'match_a_cache_file: empty arg' ) ;
  10608. ok( ! defined $tuid1 , 'match_a_cache_file: empty arg 1' ) ;
  10609. ok( ! defined $tuid2 , 'match_a_cache_file: empty arg 2' ) ;
  10610. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '000_000' ), 'match_a_cache_file: 000_000' ) ;
  10611. ok( '000' eq $tuid1, 'match_a_cache_file: 000_000 1' ) ;
  10612. ok( '000' eq $tuid2, 'match_a_cache_file: 000_000 2' ) ;
  10613. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '123_456' ), 'match_a_cache_file: 123_456' ) ;
  10614. ok( '123' eq $tuid1, 'match_a_cache_file: 123_456 1' ) ;
  10615. ok( '456' eq $tuid2, 'match_a_cache_file: 123_456 2' ) ;
  10616. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '/tmp/truc/123_456' ), 'match_a_cache_file: /tmp/truc/123_456' ) ;
  10617. ok( '123' eq $tuid1, 'match_a_cache_file: /tmp/truc/123_456 1' ) ;
  10618. ok( '456' eq $tuid2, 'match_a_cache_file: /tmp/truc/123_456 2' ) ;
  10619. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '/lala123_456' ), 'match_a_cache_file: NO /lala123_456' ) ;
  10620. ok( ! $tuid1, 'match_a_cache_file: /lala123_456 1' ) ;
  10621. ok( ! $tuid2, 'match_a_cache_file: /lala123_456 2' ) ;
  10622. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( 'la123_456' ), 'match_a_cache_file: NO la123_456' ) ;
  10623. ok( ! $tuid1, 'match_a_cache_file: la123_456 1' ) ;
  10624. ok( ! $tuid2, 'match_a_cache_file: la123_456 2' ) ;
  10625. note( 'Leaving tests_match_a_cache_file()' ) ;
  10626. return ;
  10627. }
  10628. sub clean_cache
  10629. {
  10630. my ( $cache_files_ref, $cache_1_2_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) = @_ ;
  10631. $debugcache and myprint( "Entering clean_cache\n" ) ;
  10632. $debugcache and myprint( map { "$_ -> " . $cache_1_2_ref->{ $_ } . "\n" } keys %{ $cache_1_2_ref } ) ;
  10633. foreach my $file ( @{ $cache_files_ref } ) {
  10634. $debugcache and myprint( "$file\n" ) ;
  10635. my ( $cache_uid1, $cache_uid2 ) = match_a_cache_file( $file ) ;
  10636. $debugcache and myprint( "u1: $cache_uid1 u2: $cache_uid2 c12: ", $cache_1_2_ref->{ $cache_uid1 } || q{}, "\n") ;
  10637. # or ( ! exists( $cache_1_2_ref->{ $cache_uid1 } ) )
  10638. # or ( ! ( $cache_uid2 == $cache_1_2_ref->{ $cache_uid1 } ) )
  10639. if ( ( not defined $cache_uid1 )
  10640. or ( not defined $cache_uid2 )
  10641. or ( not exists $h1_msgs_all_hash_ref->{ $cache_uid1 } )
  10642. or ( not exists $h2_msgs_all_hash_ref->{ $cache_uid2 } )
  10643. ) {
  10644. $debugcache and myprint( "remove $file\n" ) ;
  10645. unlink $file or myprint( "$OS_ERROR" ) ;
  10646. }
  10647. }
  10648. $debugcache and myprint( "Exiting clean_cache\n" ) ;
  10649. return( 1 ) ;
  10650. }
  10651. sub tests_clean_cache
  10652. {
  10653. note( 'Entering tests_clean_cache()' ) ;
  10654. ok( ( not -d 'W/tmp/cache/G1/G2' or rmtree( 'W/tmp/cache/G1/G2' )), 'clean_cache: rmtree W/tmp/cache/G1/G2' ) ;
  10655. ok( mkpath( 'W/tmp/cache/G1/G2' ), 'clean_cache: mkpath W/tmp/cache/G1/G2' ) ;
  10656. my @test_files_cache = ( qw(
  10657. W/tmp/cache/G1/G2/100_200
  10658. W/tmp/cache/G1/G2/101_201
  10659. W/tmp/cache/G1/G2/120_220
  10660. W/tmp/cache/G1/G2/142_242
  10661. W/tmp/cache/G1/G2/143_243
  10662. W/tmp/cache/G1/G2/177_277
  10663. W/tmp/cache/G1/G2/177_377
  10664. W/tmp/cache/G1/G2/177_777
  10665. W/tmp/cache/G1/G2/155_255
  10666. ) ) ;
  10667. ok( touch(@test_files_cache), 'clean_cache: touch W/tmp/cache/G1/G2/...' ) ;
  10668. ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache: 100_200 before' );
  10669. ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache: 142_242 before' );
  10670. ok( -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache: 177_277 before' );
  10671. ok( -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache: 177_377 before' );
  10672. ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache: 177_777 before' );
  10673. ok( -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache: 155_255 before' );
  10674. my $cache = {
  10675. 142 => 242,
  10676. 177 => 777,
  10677. } ;
  10678. my $all_1 = {
  10679. 142 => q{},
  10680. 177 => q{},
  10681. } ;
  10682. my $all_2 = {
  10683. 200 => q{},
  10684. 242 => q{},
  10685. 777 => q{},
  10686. } ;
  10687. ok( clean_cache( \@test_files_cache, $cache, $all_1, $all_2 ), 'clean_cache: ' ) ;
  10688. ok( ! -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache: 100_200 after' );
  10689. ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache: 142_242 after' );
  10690. ok( ! -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache: 177_277 after' );
  10691. ok( ! -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache: 177_377 after' );
  10692. ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache: 177_777 after' );
  10693. ok( ! -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache: 155_255 after' );
  10694. note( 'Leaving tests_clean_cache()' ) ;
  10695. return ;
  10696. }
  10697. sub tests_clean_cache_2
  10698. {
  10699. note( 'Entering tests_clean_cache_2()' ) ;
  10700. ok( ( not -d 'W/tmp/cache/G1/G2' or rmtree( 'W/tmp/cache/G1/G2' )), 'clean_cache_2: rmtree W/tmp/cache/G1/G2' ) ;
  10701. ok( mkpath( 'W/tmp/cache/G1/G2' ), 'clean_cache_2: mkpath W/tmp/cache/G1/G2' ) ;
  10702. my @test_files_cache = ( qw(
  10703. W/tmp/cache/G1/G2/100_200
  10704. W/tmp/cache/G1/G2/101_201
  10705. W/tmp/cache/G1/G2/120_220
  10706. W/tmp/cache/G1/G2/142_242
  10707. W/tmp/cache/G1/G2/143_243
  10708. W/tmp/cache/G1/G2/177_277
  10709. W/tmp/cache/G1/G2/177_377
  10710. W/tmp/cache/G1/G2/177_777
  10711. W/tmp/cache/G1/G2/155_255
  10712. ) ) ;
  10713. ok( touch(@test_files_cache), 'clean_cache_2: touch W/tmp/cache/G1/G2/...' ) ;
  10714. ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache_2: 100_200 before' );
  10715. ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache_2: 142_242 before' );
  10716. ok( -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache_2: 177_277 before' );
  10717. ok( -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache_2: 177_377 before' );
  10718. ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache_2: 177_777 before' );
  10719. ok( -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache_2: 155_255 before' );
  10720. my $cache = {
  10721. 142 => 242,
  10722. 177 => 777,
  10723. } ;
  10724. my $all_1 = {
  10725. $NUMBER_100 => q{},
  10726. 142 => q{},
  10727. 177 => q{},
  10728. } ;
  10729. my $all_2 = {
  10730. 200 => q{},
  10731. 242 => q{},
  10732. 777 => q{},
  10733. } ;
  10734. ok( clean_cache( \@test_files_cache, $cache, $all_1, $all_2 ), 'clean_cache_2: ' ) ;
  10735. ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache_2: 100_200 after' );
  10736. ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache_2: 142_242 after' );
  10737. ok( ! -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache_2: 177_277 after' );
  10738. ok( ! -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache_2: 177_377 after' );
  10739. ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache_2: 177_777 after' );
  10740. ok( ! -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache_2: 155_255 after' );
  10741. note( 'Leaving tests_clean_cache_2()' ) ;
  10742. return ;
  10743. }
  10744. sub tests_mkpath
  10745. {
  10746. note( 'Entering tests_mkpath()' ) ;
  10747. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' )), 'mkpath: mkpath W/tmp/tests/' ) ;
  10748. SKIP: {
  10749. skip( 'Tests only for Unix', 10 ) if ( 'MSWin32' eq $OSNAME ) ;
  10750. my $long_path_unix = '123456789/' x 30 ;
  10751. ok( ( -d "W/tmp/tests/long/$long_path_unix" or mkpath( "W/tmp/tests/long/$long_path_unix" ) ), 'mkpath: mkpath 300 char' ) ;
  10752. ok( -d "W/tmp/tests/long/$long_path_unix", 'mkpath: mkpath > 300 char verified' ) ;
  10753. ok( ( -d "W/tmp/tests/long/$long_path_unix" and rmtree( 'W/tmp/tests/long/' ) ), 'mkpath: rmtree 300 char' ) ;
  10754. ok( ! -d "W/tmp/tests/long/$long_path_unix", 'mkpath: rmtree 300 char verified' ) ;
  10755. ok( ( -d 'W/tmp/tests/trailing_dots...' or mkpath( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: mkpath trailing_dots...' ) ;
  10756. ok( -d 'W/tmp/tests/trailing_dots...', 'mkpath: mkpath trailing_dots... verified' ) ;
  10757. ok( ( -d 'W/tmp/tests/trailing_dots...' and rmtree( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: rmtree trailing_dots...' ) ;
  10758. ok( ! -d 'W/tmp/tests/trailing_dots...', 'mkpath: rmtree trailing_dots... verified' ) ;
  10759. eval { ok( 1 / 0, 'mkpath: divide by 0' ) ; } or ok( 1, 'mkpath: can not divide by 0' ) ;
  10760. ok( 1, 'mkpath: still alive' ) ;
  10761. } ;
  10762. SKIP: {
  10763. skip( 'Tests only for MSWin32', 13 ) if ( 'MSWin32' ne $OSNAME ) ;
  10764. my $long_path_2_prefix = ".\\imapsync_tests" || '\\\?\\E:\\TEMP\\imapsync_tests' ;
  10765. myprint( "long_path_2_prefix: $long_path_2_prefix\n" ) ;
  10766. my $long_path_100 = $long_path_2_prefix . '\\' . '123456789\\' x 10 . 'END' ;
  10767. my $long_path_300 = $long_path_2_prefix . '\\' . '123456789\\' x 30 . 'END' ;
  10768. #myprint( "$long_path_100\n" ) ;
  10769. ok( ( -d $long_path_2_prefix or mkpath( $long_path_2_prefix ) ), 'mkpath: -d mkpath small path' ) ;
  10770. ok( ( -d $long_path_2_prefix ), 'mkpath: -d mkpath small path done' ) ;
  10771. ok( ( -d $long_path_100 or mkpath( $long_path_100 ) ), 'mkpath: mkpath > 100 char' ) ;
  10772. ok( ( -d $long_path_100 ), 'mkpath: -d mkpath > 200 char done' ) ;
  10773. ok( ( -d $long_path_2_prefix and rmtree( $long_path_2_prefix ) ), 'mkpath: rmtree > 100 char' ) ;
  10774. ok( (! -d $long_path_2_prefix ), 'mkpath: ! -d rmtree done' ) ;
  10775. # Without the eval the following mkpath 300 just kill the whole process without a whisper
  10776. #myprint( "$long_path_300\n" ) ;
  10777. eval { ok( ( -d $long_path_300 or mkpath( $long_path_300 ) ), 'mkpath: create a path with 300 characters' ) ; }
  10778. or ok( 1, 'mkpath: can not create a path with 300 characters' ) ;
  10779. ok( ( ( ! -d $long_path_300 ) or -d $long_path_300 and rmtree( $long_path_300 ) ), 'mkpath: rmtree the 300 character path' ) ;
  10780. ok( 1, 'mkpath: still alive' ) ;
  10781. ok( ( -d 'W/tmp/tests/trailing_dots...' or mkpath( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: mkpath trailing_dots...' ) ;
  10782. ok( -d 'W/tmp/tests/trailing_dots...', 'mkpath: mkpath trailing_dots... verified' ) ;
  10783. ok( ( -d 'W/tmp/tests/trailing_dots...' and rmtree( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: rmtree trailing_dots...' ) ;
  10784. ok( ! -d 'W/tmp/tests/trailing_dots...', 'mkpath: rmtree trailing_dots... verified' ) ;
  10785. } ;
  10786. note( 'Leaving tests_mkpath()' ) ;
  10787. # Keep this because of the eval used by the caller (failed badly?)
  10788. return 1 ;
  10789. }
  10790. sub tests_touch
  10791. {
  10792. note( 'Entering tests_touch()' ) ;
  10793. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' )), 'touch: mkpath W/tmp/tests/' ) ;
  10794. ok( 1 == touch( 'W/tmp/tests/lala'), 'touch: W/tmp/tests/lala') ;
  10795. ok( 1 == touch( 'W/tmp/tests/\y'), 'touch: W/tmp/tests/\y') ;
  10796. ok( 0 == touch( '/no/no/no/aaa'), 'touch: not /aaa') ;
  10797. ok( 1 == touch( 'W/tmp/tests/lili', 'W/tmp/tests/lolo'), 'touch: 2 files') ;
  10798. ok( 0 == touch( 'W/tmp/tests/\y', '/no/no/aaa'), 'touch: 2 files, 1 fails' ) ;
  10799. note( 'Leaving tests_touch()' ) ;
  10800. return ;
  10801. }
  10802. sub touch
  10803. {
  10804. my @files = @_ ;
  10805. my $failures = 0 ;
  10806. foreach my $file ( @files ) {
  10807. my $fh = IO::File->new ;
  10808. if ( $fh->open(">> $file" ) ) {
  10809. $fh->close ;
  10810. }else{
  10811. myprint( "Could not open file $file in write/append mode\n" ) ;
  10812. $failures++ ;
  10813. }
  10814. }
  10815. return( ! $failures );
  10816. }
  10817. sub tests_tmpdir_has_colon_bug
  10818. {
  10819. note( 'Entering tests_tmpdir_has_colon_bug()' ) ;
  10820. ok( 0 == tmpdir_has_colon_bug( q{} ), 'tmpdir_has_colon_bug: ' ) ;
  10821. ok( 0 == tmpdir_has_colon_bug( '/tmp' ), 'tmpdir_has_colon_bug: /tmp' ) ;
  10822. ok( 1 == tmpdir_has_colon_bug( 'C:' ), 'tmpdir_has_colon_bug: C:' ) ;
  10823. ok( 1 == tmpdir_has_colon_bug( 'C:\temp' ), 'tmpdir_has_colon_bug: C:\temp' ) ;
  10824. note( 'Leaving tests_tmpdir_has_colon_bug()' ) ;
  10825. return ;
  10826. }
  10827. sub tmpdir_has_colon_bug
  10828. {
  10829. my $path = shift @ARG ;
  10830. my $path_filtered = filter_forbidden_characters( $path ) ;
  10831. if ( $path_filtered ne $path ) {
  10832. ( -d $path_filtered ) and myprint( "Path $path was previously mistakely changed to $path_filtered\n" ) ;
  10833. return( 1 ) ;
  10834. }
  10835. return( 0 ) ;
  10836. }
  10837. sub tmpdir_fix_colon_bug
  10838. {
  10839. my $mysync = shift @ARG ;
  10840. my $err = 0 ;
  10841. if ( not (-d $mysync->{ tmpdir } and -r _ and -w _) ) {
  10842. myprint( "tmpdir $mysync->{ tmpdir } is not valid\n" ) ;
  10843. return( 0 ) ;
  10844. }
  10845. my $cachedir_new = "$mysync->{ tmpdir }/imapsync_cache" ;
  10846. if ( not tmpdir_has_colon_bug( $cachedir_new ) ) { return( 0 ) } ;
  10847. # check if old cache directory already exists
  10848. my $cachedir_old = filter_forbidden_characters( $cachedir_new ) ;
  10849. if ( not ( -d $cachedir_old ) ) {
  10850. myprint( "Old cache directory $cachedir_new no exists, nothing to do\n" ) ;
  10851. return( 1 ) ;
  10852. }
  10853. # check if new cache directory already exists
  10854. if ( -d $cachedir_new ) {
  10855. myprint( "New fixed cache directory $cachedir_new already exists, not moving the old one $cachedir_old. Fix this manually.\n" ) ;
  10856. return( 0 ) ;
  10857. }else{
  10858. # move the old one to the new place
  10859. myprint( "Moving $cachedir_old to $cachedir_new Do not interrupt this task.\n" ) ;
  10860. File::Copy::Recursive::rmove( $cachedir_old, $cachedir_new )
  10861. or do {
  10862. myprint( "Could not move $cachedir_old to $cachedir_new\n" ) ;
  10863. $err++ ;
  10864. } ;
  10865. # check it succeeded
  10866. if ( -d $cachedir_new and -r _ and -w _ ) {
  10867. myprint( "New fixed cache directory $cachedir_new ok\n" ) ;
  10868. }else{
  10869. myprint( "New fixed cache directory $cachedir_new does not exist\n" ) ;
  10870. $err++ ;
  10871. }
  10872. if ( -d $cachedir_old ) {
  10873. myprint( "Old cache directory $cachedir_old still exists\n" ) ;
  10874. $err++ ;
  10875. }else{
  10876. myprint( "Old cache directory $cachedir_old successfully moved\n" ) ;
  10877. }
  10878. }
  10879. return( not $err ) ;
  10880. }
  10881. sub tests_cache_folder
  10882. {
  10883. note( 'Entering tests_cache_folder()' ) ;
  10884. ok( '/path/fold1/fold2' eq cache_folder( q{}, '/path', 'fold1', 'fold2'), 'cache_folder: /path, fold1, fold2 -> /path/fold1/fold2' ) ;
  10885. ok( '/pa_th/fold1/fold2' eq cache_folder( q{}, '/pa*th', 'fold1', 'fold2'), 'cache_folder: /pa*th, fold1, fold2 -> /path/fold1/fold2' ) ;
  10886. 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' ) ;
  10887. ok( 'D:/path/fold1/fold2' eq cache_folder( 'D:', '/path', 'fold1', 'fold2'), 'cache_folder: /path, fold1, fold2 -> /path/fold1/fold2' ) ;
  10888. ok( 'D:/pa_th/fold1/fold2' eq cache_folder( 'D:', '/pa*th', 'fold1', 'fold2'), 'cache_folder: /pa*th, fold1, fold2 -> /path/fold1/fold2' ) ;
  10889. 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' ) ;
  10890. ok( '//' eq cache_folder( q{}, q{}, q{}, q{}), 'cache_folder: -> //' ) ;
  10891. ok( '//_______' eq cache_folder( q{}, q{}, q{}, '*|?:"<>'), 'cache_folder: *|?:"<> -> //_______' ) ;
  10892. note( 'Leaving tests_cache_folder()' ) ;
  10893. return ;
  10894. }
  10895. sub cache_folder
  10896. {
  10897. my( $cache_base, $cache_dir, $h1_fold, $h2_fold ) = @_ ;
  10898. my $sep_1 = $sync->{ h1_sep } || '/';
  10899. my $sep_2 = $sync->{ h2_sep } || '/';
  10900. #myprint( "$cache_dir h1_fold $h1_fold sep1 $sep_1 h2_fold $h2_fold sep2 $sep_2\n" ) ;
  10901. $h1_fold = convert_sep_to_slash( $h1_fold, $sep_1 ) ;
  10902. $h2_fold = convert_sep_to_slash( $h2_fold, $sep_2 ) ;
  10903. my $cache_folder = "$cache_base" . filter_forbidden_characters( "$cache_dir/$h1_fold/$h2_fold" ) ;
  10904. #myprint( "cache_folder [$cache_folder]\n" ) ;
  10905. return( $cache_folder ) ;
  10906. }
  10907. sub tests_filter_forbidden_characters
  10908. {
  10909. note( 'Entering tests_filter_forbidden_characters()' ) ;
  10910. is( undef , filter_forbidden_characters( ), 'filter_forbidden_characters: no args -> undef' ) ;
  10911. is( '' , filter_forbidden_characters( '' ), 'filter_forbidden_characters: empty string -> empty string' ) ;
  10912. is( 'a_b' , filter_forbidden_characters( 'a_b' ), 'filter_forbidden_characters: a_b -> a_b' ) ;
  10913. is( 'a_b' , filter_forbidden_characters( 'a*b' ), 'filter_forbidden_characters: a*b -> a_b' ) ;
  10914. is( 'a_b' , filter_forbidden_characters( 'a|b' ), 'filter_forbidden_characters: a|b -> a_b' ) ;
  10915. is( 'a_b' , filter_forbidden_characters( 'a?b' ), 'filter_forbidden_characters: a?b -> a_b' ) ;
  10916. is( 'a________b', filter_forbidden_characters( q{a*|?:"<>'b} ), q{filter_forbidden_characters: a*|?:"<>'b -> a________b} ) ;
  10917. is( 'a_b_' , filter_forbidden_characters( 'a b ' ), 'filter_forbidden_characters: "a b " -> "a_b_"' ) ;
  10918. is( 'a_b' , filter_forbidden_characters( "a\tb" ), 'filter_forbidden_characters: a\tb -> a_b' ) ;
  10919. is( "a_b" , filter_forbidden_characters( "a\rb" ), 'filter_forbidden_characters: a\rb -> a_b' ) ;
  10920. is( "a_b" , filter_forbidden_characters( "a\nb" ), 'filter_forbidden_characters: a\nb -> a_b' ) ;
  10921. is( "a_b" , filter_forbidden_characters( "a\\b" ), 'filter_forbidden_characters: a\b -> a_b' ) ;
  10922. is( 'a-b' , filter_forbidden_characters( 'a-b' ), 'filter_forbidden_characters: a-b -> a-b' ) ;
  10923. is( 'a__-__-__-__-__b' , filter_forbidden_characters( 'aé-è-à-ç-Öb' ), 'filter_forbidden_characters: aé-è-à-ç-Öb -> a__-__-__-__-__b' ) ;
  10924. is( 'abcdABCDwxyzWXYZ012789' , filter_forbidden_characters( 'abcdABCDwxyzWXYZ012789' ),
  10925. 'filter_forbidden_characters: abcdABCDwxyzWXYZ012789 -> abcdABCDwxyzWXYZ012789' ) ;
  10926. note( 'Leaving tests_filter_forbidden_characters()' ) ;
  10927. return ;
  10928. }
  10929. sub filter_forbidden_characters
  10930. {
  10931. my $string = shift @ARG ;
  10932. if ( ! defined $string ) { return ; }
  10933. $string =~ s{[\Q*|?:"<>' \E\t\r\n\\]}{_}xg ;
  10934. # replace all non-ascii and control characters by _
  10935. $string =~ s/[[:^ascii:][:cntrl:]]/_/xg ;
  10936. #myprint( "[$string]\n" ) ;
  10937. return( $string ) ;
  10938. }
  10939. sub tests_convert_sep_to_slash
  10940. {
  10941. note( 'Entering tests_convert_sep_to_slash()' ) ;
  10942. ok(q{} eq convert_sep_to_slash(q{}, '/'), 'convert_sep_to_slash: no folder');
  10943. ok('INBOX' eq convert_sep_to_slash('INBOX', '/'), 'convert_sep_to_slash: INBOX');
  10944. ok('INBOX/foo' eq convert_sep_to_slash('INBOX/foo', '/'), 'convert_sep_to_slash: INBOX/foo');
  10945. ok('INBOX/foo' eq convert_sep_to_slash('INBOX_foo', '_'), 'convert_sep_to_slash: INBOX_foo');
  10946. ok('INBOX/foo/zob' eq convert_sep_to_slash('INBOX_foo_zob', '_'), 'convert_sep_to_slash: INBOX_foo_zob');
  10947. ok('INBOX/foo' eq convert_sep_to_slash('INBOX.foo', '.'), 'convert_sep_to_slash: INBOX.foo');
  10948. ok('INBOX/foo/hi' eq convert_sep_to_slash('INBOX.foo.hi', '.'), 'convert_sep_to_slash: INBOX.foo.hi');
  10949. note( 'Leaving tests_convert_sep_to_slash()' ) ;
  10950. return ;
  10951. }
  10952. sub convert_sep_to_slash
  10953. {
  10954. my ( $folder, $sep ) = @_ ;
  10955. $folder =~ s{\Q$sep\E}{/}xg ;
  10956. return( $folder ) ;
  10957. }
  10958. sub tests_regexmess
  10959. {
  10960. note( 'Entering tests_regexmess()' ) ;
  10961. ok( 'blabla' eq regexmess( 'blabla' ), 'regexmess: no regexmess, nothing to do' ) ;
  10962. @regexmess = ( 'lalala' ) ;
  10963. ok( not( defined regexmess( 'popopo' ) ), 'regexmess: bad regex lalala' ) ;
  10964. @regexmess = ( 's/p/Z/g' ) ;
  10965. ok( 'ZoZoZo' eq regexmess( 'popopo' ), 'regexmess: s/p/Z/g' ) ;
  10966. @regexmess = ( 's{c}{C}gxms' ) ;
  10967. ok("H1: abC\nH2: Cde\n\nBody abC"
  10968. eq regexmess( "H1: abc\nH2: cde\n\nBody abc"),
  10969. 'regexmess: c->C');
  10970. @regexmess = ( 's{\AFrom\ }{From:}gxms' ) ;
  10971. ok( q{}
  10972. eq regexmess(q{}),
  10973. 'regexmess: From mbox 1 add colon blank');
  10974. ok( 'From:<tartanpion@machin.truc>'
  10975. eq regexmess('From <tartanpion@machin.truc>'),
  10976. 'regexmess: From mbox 2 add colo');
  10977. ok( "\n" . 'From <tartanpion@machin.truc>'
  10978. eq regexmess("\n" . 'From <tartanpion@machin.truc>'),
  10979. 'regexmess: From mbox 3 add colo') ;
  10980. ok( "From: zzz\n" . 'From <tartanpion@machin.truc>'
  10981. eq regexmess("From zzz\n" . 'From <tartanpion@machin.truc>'),
  10982. 'regexmess: From mbox 4 add colo') ;
  10983. @regexmess = ( 's{\AFrom\ [^\n]*(\n)?}{}gxms' ) ;
  10984. ok( q{}
  10985. eq regexmess(q{}),
  10986. 'regexmess: From mbox 1 remove, blank');
  10987. ok( q{}
  10988. eq regexmess('From <tartanpion@machin.truc>'),
  10989. 'regexmess: From mbox 2 remove');
  10990. ok( "\n" . 'From <tartanpion@machin.truc>'
  10991. eq regexmess("\n" . 'From <tartanpion@machin.truc>'),
  10992. 'regexmess: From mbox 3 remove');
  10993. #myprint( "[", regexmess("From zzz\n" . 'From <tartanpion@machin.truc>'), "]" ) ;
  10994. ok( q{} . 'From <tartanpion@machin.truc>'
  10995. eq regexmess("From zzz\n" . 'From <tartanpion@machin.truc>'),
  10996. 'regexmess: From mbox 4 remove');
  10997. is(
  10998. <<'EOM'
  10999. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11000. From:<tartanpion@machin.truc>
  11001. Hello,
  11002. Bye.
  11003. EOM
  11004. , regexmess(
  11005. <<'EOM'
  11006. From zzz
  11007. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11008. From:<tartanpion@machin.truc>
  11009. Hello,
  11010. Bye.
  11011. EOM
  11012. ), 'regexmess: From mbox 5 remove');
  11013. @regexmess = ( 's{\A((?:[^\n]+\n)+|)^Disposition-Notification-To:[^\n]*\n(\r?\n|.*\n\r?\n)}{$1$2}xms' ) ; # SUPER SUPER BEST!
  11014. ok(
  11015. <<'EOM'
  11016. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11017. From:<tartanpion@machin.truc>
  11018. Hello,
  11019. Bye.
  11020. EOM
  11021. eq regexmess(
  11022. <<'EOM'
  11023. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11024. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11025. From:<tartanpion@machin.truc>
  11026. Hello,
  11027. Bye.
  11028. EOM
  11029. ),
  11030. 'regexmess: 1 Delete header Disposition-Notification-To:');
  11031. ok(
  11032. <<'EOM'
  11033. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11034. From:<tartanpion@machin.truc>
  11035. Hello,
  11036. Bye.
  11037. EOM
  11038. eq regexmess(
  11039. <<'EOM'
  11040. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11041. From:<tartanpion@machin.truc>
  11042. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11043. Hello,
  11044. Bye.
  11045. EOM
  11046. ),
  11047. 'regexmess: 2 Delete header Disposition-Notification-To:');
  11048. ok(
  11049. <<'EOM'
  11050. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11051. From:<tartanpion@machin.truc>
  11052. Hello,
  11053. Bye.
  11054. EOM
  11055. eq regexmess(
  11056. <<'EOM'
  11057. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11058. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11059. From:<tartanpion@machin.truc>
  11060. Hello,
  11061. Bye.
  11062. EOM
  11063. ),
  11064. 'regexmess: 3 Delete header Disposition-Notification-To:');
  11065. ok(
  11066. <<'EOM'
  11067. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11068. From:<tartanpion@machin.truc>
  11069. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11070. Bye.
  11071. EOM
  11072. eq regexmess(
  11073. <<'EOM'
  11074. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11075. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11076. From:<tartanpion@machin.truc>
  11077. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11078. Bye.
  11079. EOM
  11080. ),
  11081. 'regexmess: 4 Delete header Disposition-Notification-To:');
  11082. ok(
  11083. <<'EOM'
  11084. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11085. From:<tartanpion@machin.truc>
  11086. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11087. Bye.
  11088. EOM
  11089. eq regexmess(
  11090. <<'EOM'
  11091. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11092. From:<tartanpion@machin.truc>
  11093. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11094. Bye.
  11095. EOM
  11096. ),
  11097. 'regexmess: 5 Delete header Disposition-Notification-To:');
  11098. ok(
  11099. <<'EOM'
  11100. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11101. From:<tartanpion@machin.truc>
  11102. Hello,
  11103. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11104. Bye.
  11105. EOM
  11106. eq regexmess(
  11107. <<'EOM'
  11108. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11109. From:<tartanpion@machin.truc>
  11110. Hello,
  11111. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11112. Bye.
  11113. EOM
  11114. ),
  11115. 'regexmess: 6 Delete header Disposition-Notification-To:');
  11116. ok(
  11117. <<'EOM'
  11118. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11119. From:<tartanpion@machin.truc>
  11120. Hello,
  11121. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11122. Bye.
  11123. EOM
  11124. eq regexmess(
  11125. <<'EOM'
  11126. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11127. From:<tartanpion@machin.truc>
  11128. Hello,
  11129. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11130. Bye.
  11131. EOM
  11132. ),
  11133. 'regexmess: 7 Delete header Disposition-Notification-To:');
  11134. ok(
  11135. <<'EOM'
  11136. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11137. From:<tartanpion@machin.truc>
  11138. Hello,
  11139. Bye.
  11140. EOM
  11141. eq regexmess(
  11142. <<'EOM'
  11143. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11144. From:<tartanpion@machin.truc>
  11145. Hello,
  11146. Bye.
  11147. EOM
  11148. ),
  11149. 'regexmess: 8 Delete header Disposition-Notification-To:');
  11150. ok(
  11151. <<'EOM'
  11152. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11153. From:<tartanpion@machin.truc>
  11154. Hello,
  11155. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11156. Bye.
  11157. EOM
  11158. eq regexmess(
  11159. <<'EOM'
  11160. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11161. From:<tartanpion@machin.truc>
  11162. Hello,
  11163. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11164. Bye.
  11165. EOM
  11166. ),
  11167. 'regexmess: 9 Delete header Disposition-Notification-To:');
  11168. ok(
  11169. <<'EOM'
  11170. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11171. From:<tartanpion@machin.truc>
  11172. Hello,
  11173. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11174. Bye.
  11175. EOM
  11176. eq regexmess(
  11177. <<'EOM'
  11178. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11179. From:<tartanpion@machin.truc>
  11180. Hello,
  11181. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11182. Bye.
  11183. EOM
  11184. ),
  11185. 'regexmess: 10 Delete header Disposition-Notification-To:');
  11186. ok(
  11187. <<'EOM'
  11188. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11189. From:<tartanpion@machin.truc>
  11190. Hello,
  11191. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11192. Bye.
  11193. EOM
  11194. eq regexmess(
  11195. <<'EOM'
  11196. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11197. From:<tartanpion@machin.truc>
  11198. Hello,
  11199. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11200. Bye.
  11201. EOM
  11202. ),
  11203. 'regexmess: 11 Delete header Disposition-Notification-To:');
  11204. ok(
  11205. <<'EOM'
  11206. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11207. From:<tartanpion@machin.truc>
  11208. Hello,
  11209. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11210. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11211. Bye.
  11212. EOM
  11213. eq regexmess(
  11214. <<'EOM'
  11215. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11216. From:<tartanpion@machin.truc>
  11217. Hello,
  11218. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11219. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11220. Bye.
  11221. EOM
  11222. ),
  11223. 'regexmess: 12 Delete header Disposition-Notification-To:');
  11224. @regexmess = ( 's{\A(.*?(?! ^$))^Disposition-Notification-To:(.*?)$}{$1X-Disposition-Notification-To:$2}igxms' ) ; # BAD!
  11225. @regexmess = ( 's{\A((?:[^\n]+\n)+|)(^Disposition-Notification-To:[^\n]*\n)(\r?\n|.*\n\r?\n)}{$1X-$2$3}ims' ) ;
  11226. ok(
  11227. <<'EOM'
  11228. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11229. From:<tartanpion@machin.truc>
  11230. Hello,
  11231. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11232. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11233. Bye.
  11234. EOM
  11235. eq regexmess(
  11236. <<'EOM'
  11237. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11238. From:<tartanpion@machin.truc>
  11239. Hello,
  11240. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11241. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11242. Bye.
  11243. EOM
  11244. ),
  11245. 'regexmess: 13 Delete header Disposition-Notification-To:');
  11246. ok(
  11247. <<'EOM'
  11248. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11249. X-Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11250. From:<tartanpion@machin.truc>
  11251. Hello,
  11252. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11253. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11254. Bye.
  11255. EOM
  11256. eq regexmess(
  11257. <<'EOM'
  11258. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11259. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11260. From:<tartanpion@machin.truc>
  11261. Hello,
  11262. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11263. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11264. Bye.
  11265. EOM
  11266. ),
  11267. 'regexmess: 14 Delete header Disposition-Notification-To:');
  11268. ok(
  11269. <<'EOM'
  11270. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11271. X-Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11272. From:<tartanpion@machin.truc>
  11273. Hello,
  11274. Bye.
  11275. EOM
  11276. eq regexmess(
  11277. <<'EOM'
  11278. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11279. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11280. From:<tartanpion@machin.truc>
  11281. Hello,
  11282. Bye.
  11283. EOM
  11284. ),
  11285. 'regexmess: 15 Delete header Disposition-Notification-To:');
  11286. ok(
  11287. <<'EOM'
  11288. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11289. From:<tartanpion@machin.truc>
  11290. X-Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11291. Hello,
  11292. Bye.
  11293. EOM
  11294. eq regexmess(
  11295. <<'EOM'
  11296. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11297. From:<tartanpion@machin.truc>
  11298. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11299. Hello,
  11300. Bye.
  11301. EOM
  11302. ),
  11303. 'regexmess: 16 Delete header Disposition-Notification-To:');
  11304. ok(
  11305. <<'EOM'
  11306. X-Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11307. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11308. From:<tartanpion@machin.truc>
  11309. Hello,
  11310. Bye.
  11311. EOM
  11312. eq regexmess(
  11313. <<'EOM'
  11314. Disposition-Notification-To: Gilles LAMIRAL <gilles@lamiral.info>
  11315. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11316. From:<tartanpion@machin.truc>
  11317. Hello,
  11318. Bye.
  11319. EOM
  11320. ),
  11321. 'regexmess: 17 Delete header Disposition-Notification-To:');
  11322. @regexmess = ( 's/.{11}\K.*//gs' ) ;
  11323. is( "0123456789\n", regexmess( "0123456789\n" x 100 ), 'regexmess: truncate whole message after 11 characters' ) ;
  11324. is( "0123456789\n", regexmess( "0123456789\n" x 100_000 ), 'regexmess: truncate whole message after 11 characters ~ 1MB' ) ;
  11325. @regexmess = ( 's/.{10000}\K.*//gs' ) ;
  11326. is( "123456789\n" x 1000, regexmess( "123456789\n" x 100_000 ), 'regexmess: truncate whole message after 10000 characters ~ 1MB' ) ;
  11327. @regexmess = ( 's/^(X-Ham-Report.*?\n)^X-/X-/sm' ) ;
  11328. is(
  11329. <<'EOM'
  11330. X-Spam-Score: -1
  11331. X-Spam-Bar: /
  11332. X-Spam-Flag: NO
  11333. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11334. From:<tartanpion@machin.truc>
  11335. Hello,
  11336. Bye.
  11337. EOM
  11338. ,
  11339. regexmess(
  11340. <<'EOM'
  11341. X-Spam-Score: -1
  11342. X-Spam-Bar: /
  11343. X-Ham-Report: =?utf-8?Q?Spam_detection_software=2C_running?=
  11344. =?utf-8?Q?_on_the_system_=22ohp-ag006.int200?=
  11345. _has_NOT_identified_thi?=
  11346. =?utf-8?Q?s_incoming_email_as_spam.__The_o?=
  11347. _message_has_been_attac?=
  11348. =?utf-8?Q?hed_to_this_so_you_can_view_it_o?=
  11349. ___________________________?=
  11350. =?utf-8?Q?__author's_domain
  11351. X-Spam-Flag: NO
  11352. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11353. From:<tartanpion@machin.truc>
  11354. Hello,
  11355. Bye.
  11356. EOM
  11357. ),
  11358. 'regexmess: Delete header X-Ham-Report:');
  11359. # regex to play with Date: from the FAQ
  11360. #@regexmess = 's{\A(.*?(?! ^$))^Date:(.*?)$}{$1Date:$2\nX-Date:$2}gxms'
  11361. # Change 8bit characters in whole email to X characters
  11362. @regexmess = ( 's{[\x80-\xff]}{X}gxms' ) ;
  11363. is( 'X-8bit: kaka 1 XX kiki', regexmess('X-8bit: kaka 1 ¤ kiki'), 'regexmess: 1 Change 8bit characters in whole email to X characters');
  11364. # Same change but using tr
  11365. @regexmess = ( 'tr [\x80-\xff] [X]' ) ;
  11366. 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');
  11367. # Add a final \r\n if missing
  11368. @regexmess = ( 's{(?<![\n])\z}{\r\n}gxms' ) ;
  11369. is( "\r\n", regexmess(""), 'regexmess: 1. Add a final \r\n if missing. Missing' ) ;
  11370. is( "abc\r\n", regexmess("abc"), 'regexmess: 2. Add a final \r\n if missing. Missing' ) ;
  11371. is( "abc\ndef\r\n", regexmess("abc\ndef"), 'regexmess: 3. Add a final \r\n if missing. Missing' ) ;
  11372. is( "abc\r\ndef\r\n", regexmess("abc\r\ndef"), 'regexmess: 3. Add a final \r\n if missing. Missing' ) ;
  11373. is( "\r\n", regexmess("\r\n"), 'regexmess: 3. Add a final \r\n if missing. Not missing' ) ;
  11374. is( "abc\n", regexmess("abc\n"), 'regexmess: 4. Add a final \r\n if missing. Not missing' ) ;
  11375. is( "abc\r\n", regexmess("abc\r\n"), 'regexmess: 4. Add a final \r\n if missing. Not missing' ) ;
  11376. is( "abc\ndef\n", regexmess("abc\ndef\n"), 'regexmess: 4. Add a final \r\n if missing. Not missing' ) ;
  11377. is( "abc\r\ndef\r\n", regexmess("abc\r\ndef\r\n"), 'regexmess: 4. Add a final \r\n if missing. Not missing' ) ;
  11378. # Remove the fucking buggy X-Spam-Report: a bad header on several lines that can even begin without a space!
  11379. @regexmess = ( 's{X-Spam-Report:.*?\n(^[^\n]+:|^\r?\n)}{$1}xms' ) ;
  11380. # Damien regexes:
  11381. #@regexmess = ( 's{X-Spam-Report:.*?\n(^[a-zA-Z0-9\-]+:)}{$1}xms' ) ;
  11382. #@regexmess = ( 's{X-Spam-Report:.*?\n(^[a-zA-Z0-9\-]+:|^\r?\n)}{$1}xms' ) ;
  11383. is(
  11384. <<'EOM'
  11385. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11386. From:<tartanpion@machin.truc>
  11387. LaSuite: super
  11388. Hello,
  11389. Bye.
  11390. EOM
  11391. , regexmess(
  11392. <<'EOM'
  11393. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11394. From:<tartanpion@machin.truc>
  11395. X-Spam-Report: caca
  11396. caca
  11397. caca
  11398. caca
  11399. LaSuite: super
  11400. Hello,
  11401. Bye.
  11402. EOM
  11403. ), 'regexmess: 1 remove buggy X-Spam-Report: across several lines, not the final header');
  11404. is(
  11405. <<'EOM'
  11406. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11407. From:<tartanpion@machin.truc>
  11408. LaSuite: super
  11409. LaSuite2: super 2
  11410. Hello,
  11411. Bye.
  11412. EOM
  11413. , regexmess(
  11414. <<'EOM'
  11415. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11416. From:<tartanpion@machin.truc>
  11417. X-Spam-Report: caca
  11418. caca
  11419. caca
  11420. caca
  11421. LaSuite: super
  11422. LaSuite2: super 2
  11423. Hello,
  11424. Bye.
  11425. EOM
  11426. ), 'regexmess: 2 remove buggy X-Spam-Report: across several lines, not the final header');
  11427. is(
  11428. <<'EOM'
  11429. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11430. From:<tartanpion@machin.truc>
  11431. LaSuite: super
  11432. LaSuite2: super 2
  11433. Hello,
  11434. Bye.
  11435. EOM
  11436. , regexmess(
  11437. <<'EOM'
  11438. X-Spam-Report: caca
  11439. caca
  11440. caca
  11441. caca
  11442. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11443. From:<tartanpion@machin.truc>
  11444. LaSuite: super
  11445. LaSuite2: super 2
  11446. Hello,
  11447. Bye.
  11448. EOM
  11449. ), 'regexmess: 3 remove buggy X-Spam-Report: across several lines, first header');
  11450. is(
  11451. <<'EOM'
  11452. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11453. From:<tartanpion@machin.truc>
  11454. Hello,
  11455. Bye.
  11456. EOM
  11457. , regexmess(
  11458. <<'EOM'
  11459. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11460. From:<tartanpion@machin.truc>
  11461. X-Spam-Report: caca
  11462. caca
  11463. caca
  11464. caca
  11465. Hello,
  11466. Bye.
  11467. EOM
  11468. ), 'regexmess: 4 remove buggy X-Spam-Report: across several lines, final header');
  11469. is(
  11470. <<'EOM'
  11471. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11472. From:<tartanpion@machin.truc>
  11473. Hello,
  11474. Bye.
  11475. EOM
  11476. , regexmess(
  11477. <<'EOM'
  11478. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11479. From:<tartanpion@machin.truc>
  11480. Hello,
  11481. Bye.
  11482. EOM
  11483. ), 'regexmess: 5 remove buggy X-Spam-Report: not there at all');
  11484. is(
  11485. <<"EOM"
  11486. Date: Sat, 10 Jul 2010 05:34:45 -0700\r
  11487. From:<tartanpion>\r
  11488. LaSuite: super\r
  11489. LaSuite2: super 2\r
  11490. \r
  11491. Hello,\r
  11492. Bye.\r
  11493. EOM
  11494. , regexmess(
  11495. <<"EOM"
  11496. X-Spam-Report: caca\r
  11497. caca\r
  11498. caca\r
  11499. caca\r
  11500. Date: Sat, 10 Jul 2010 05:34:45 -0700\r
  11501. From:<tartanpion>\r
  11502. LaSuite: super\r
  11503. LaSuite2: super 2\r
  11504. \r
  11505. Hello,\r
  11506. Bye.\r
  11507. EOM
  11508. ), 'regexmess: 6 remove buggy X-Spam-Report: across several lines, first header, with \r');
  11509. is(
  11510. <<"EOM"
  11511. Date: Sat, 10 Jul 2010 05:34:45 -0700\r
  11512. From:<tartanpion>\r
  11513. LaSuite: super\r
  11514. LaSuite2: super 2\r
  11515. \r
  11516. Hello,\r
  11517. Bye.\r
  11518. EOM
  11519. , regexmess(
  11520. <<"EOM"
  11521. Date: Sat, 10 Jul 2010 05:34:45 -0700\r
  11522. From:<tartanpion>\r
  11523. X-Spam-Report: caca\r
  11524. caca\r
  11525. caca\r
  11526. caca\r
  11527. LaSuite: super\r
  11528. LaSuite2: super 2\r
  11529. \r
  11530. Hello,\r
  11531. Bye.\r
  11532. EOM
  11533. ), 'regexmess: 7 remove buggy X-Spam-Report: across several lines, middle header, with \r');
  11534. is(
  11535. <<"EOM"
  11536. Date: Sat, 10 Jul 2010 05:34:45 -0700\r
  11537. From:<tartanpion>\r
  11538. \r
  11539. Hello,\r
  11540. Bye.\r
  11541. EOM
  11542. , regexmess(
  11543. <<"EOM"
  11544. Date: Sat, 10 Jul 2010 05:34:45 -0700\r
  11545. From:<tartanpion>\r
  11546. X-Spam-Report: caca\r
  11547. caca\r
  11548. caca\r
  11549. caca\r
  11550. \r
  11551. Hello,\r
  11552. Bye.\r
  11553. EOM
  11554. ), 'regexmess: 8 remove buggy X-Spam-Report: across several lines, final header, with \r');
  11555. undef @regexmess ;
  11556. note( 'Leaving tests_regexmess()' ) ;
  11557. return ;
  11558. }
  11559. sub regexmess
  11560. {
  11561. my ( $string ) = @_ ;
  11562. foreach my $regexmess ( @regexmess ) {
  11563. $sync->{ debug } and myprint( "eval \$string =~ $regexmess\n" ) ;
  11564. my $ret = eval "\$string =~ $regexmess ; 1" ;
  11565. #myprint( "eval [$ret]\n" ) ;
  11566. if ( ( not $ret ) or $EVAL_ERROR ) {
  11567. myprint( "Error: eval regexmess '$regexmess': $EVAL_ERROR" ) ;
  11568. return( undef ) ;
  11569. }
  11570. }
  11571. $sync->{ debug } and myprint( "$string\n" ) ;
  11572. return( $string ) ;
  11573. }
  11574. sub tests_skipmess
  11575. {
  11576. note( 'Entering tests_skipmess()' ) ;
  11577. ok( not( defined skipmess( 'blabla' ) ), 'skipmess, no skipmess, no skip' ) ;
  11578. @skipmess = ('[') ;
  11579. ok( not( defined skipmess( 'popopo' ) ), 'skipmess, bad regex [' ) ;
  11580. @skipmess = ('lalala') ;
  11581. ok( not( defined skipmess( 'popopo' ) ), 'skipmess, bad regex lalala' ) ;
  11582. @skipmess = ('/popopo/') ;
  11583. ok( 1 == skipmess( 'popopo' ), 'skipmess, popopo match regex /popopo/' ) ;
  11584. @skipmess = ('/popopo/') ;
  11585. ok( 0 == skipmess( 'rrrrrr' ), 'skipmess, rrrrrr does not match regex /popopo/' ) ;
  11586. @skipmess = ('m{^$}') ;
  11587. ok( 1 == skipmess( q{} ), 'skipmess: empty string yes' ) ;
  11588. ok( 0 == skipmess( 'Hi!' ), 'skipmess: empty string no' ) ;
  11589. @skipmess = ('m{i}') ;
  11590. ok( 1 == skipmess( 'Hi!' ), 'skipmess: i string yes' ) ;
  11591. ok( 0 == skipmess( 'Bye!' ), 'skipmess: i string no' ) ;
  11592. @skipmess = ('m{[\x80-\xff]}') ;
  11593. ok( 0 == skipmess( 'Hi!' ), 'skipmess: i 8bit no' ) ;
  11594. ok( 1 == skipmess( "\xff" ), 'skipmess: \xff 8bit yes' ) ;
  11595. @skipmess = ('m{A}', 'm{B}') ;
  11596. ok( 0 == skipmess( 'Hi!' ), 'skipmess: A or B no' ) ;
  11597. ok( 0 == skipmess( 'lala' ), 'skipmess: A or B no' ) ;
  11598. ok( 0 == skipmess( "\xff" ), 'skipmess: A or B no' ) ;
  11599. ok( 1 == skipmess( 'AB' ), 'skipmess: A or B yes' ) ;
  11600. ok( 1 == skipmess( 'BA' ), 'skipmess: A or B yes' ) ;
  11601. ok( 1 == skipmess( 'AA' ), 'skipmess: A or B yes' ) ;
  11602. ok( 1 == skipmess( 'Ok Bye' ), 'skipmess: A or B yes' ) ;
  11603. @skipmess = ( 'm#\A((?:[^\n]+\n)+|)^Content-Type: Message/Partial;[^\n]*\n(?:\n|.*\n\n)#ism' ) ; # SUPER BEST!
  11604. ok( 1 == skipmess(
  11605. <<'EOM'
  11606. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11607. Content-Type: Message/Partial; blabla
  11608. From:<tartanpion@machin.truc>
  11609. Hello!
  11610. Bye.
  11611. EOM
  11612. ),
  11613. 'skipmess: 1 match Content-Type: Message/Partial' ) ;
  11614. ok( 0 == skipmess(
  11615. <<'EOM'
  11616. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11617. From:<tartanpion@machin.truc>
  11618. Hello!
  11619. Bye.
  11620. EOM
  11621. ),
  11622. 'skipmess: 2 not match Content-Type: Message/Partial' ) ;
  11623. ok( 1 == skipmess(
  11624. <<'EOM'
  11625. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11626. From:<tartanpion@machin.truc>
  11627. Content-Type: Message/Partial; blabla
  11628. Hello!
  11629. Bye.
  11630. EOM
  11631. ),
  11632. 'skipmess: 3 match Content-Type: Message/Partial' ) ;
  11633. ok( 0 == skipmess(
  11634. <<'EOM'
  11635. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11636. From:<tartanpion@machin.truc>
  11637. Hello!
  11638. Content-Type: Message/Partial; blabla
  11639. Bye.
  11640. EOM
  11641. ),
  11642. 'skipmess: 4 not match Content-Type: Message/Partial' ) ;
  11643. ok( 0 == skipmess(
  11644. <<'EOM'
  11645. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11646. From:<tartanpion@machin.truc>
  11647. Hello!
  11648. Content-Type: Message/Partial; blabla
  11649. Bye.
  11650. EOM
  11651. ),
  11652. 'skipmess: 5 not match Content-Type: Message/Partial' ) ;
  11653. ok( 1 == skipmess(
  11654. <<'EOM'
  11655. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11656. Content-Type: Message/Partial; blabla
  11657. From:<tartanpion@machin.truc>
  11658. Hello!
  11659. Content-Type: Message/Partial; blabla
  11660. Bye.
  11661. EOM
  11662. ),
  11663. 'skipmess: 6 match Content-Type: Message/Partial' ) ;
  11664. ok( 1 == skipmess(
  11665. <<'EOM'
  11666. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11667. Content-Type: Message/Partial;
  11668. From:<tartanpion@machin.truc>
  11669. Hello!
  11670. Bye.
  11671. EOM
  11672. ),
  11673. 'skipmess: 7 match Content-Type: Message/Partial' ) ;
  11674. ok( 1 == skipmess(
  11675. <<'EOM'
  11676. Date: Wed, 2 Jul 2014 02:26:40 +0000
  11677. MIME-Version: 1.0
  11678. Content-Type: message/partial;
  11679. id="TAN_U_P<1404267997.00007489ed17>";
  11680. number=3;
  11681. total=3
  11682. 6HQ6Hh3CdXj77qEGixerQ6zHx0OnQ/Cf5On4W0Y6vtU2crABZQtD46Hx1EOh8dDz4+OnTr1G
  11683. Hello!
  11684. Bye.
  11685. EOM
  11686. ),
  11687. 'skipmess: 8 match Content-Type: Message/Partial' ) ;
  11688. ok( 1 == skipmess(
  11689. <<'EOM'
  11690. Return-Path: <gilles@lamiral.info>
  11691. Received: by lamiral.info (Postfix, from userid 1000)
  11692. id 21EB12443BF; Mon, 2 Mar 2015 15:38:35 +0100 (CET)
  11693. Subject: test: aethaecohngiexao
  11694. To: <tata@petite.lamiral.info>
  11695. X-Mailer: mail (GNU Mailutils 2.2)
  11696. Message-Id: <20150302143835.21EB12443BF@lamiral.info>
  11697. Content-Type: message/partial;
  11698. id="TAN_U_P<1404267997.00007489ed17>";
  11699. number=3;
  11700. total=3
  11701. Date: Mon, 2 Mar 2015 15:38:34 +0100 (CET)
  11702. From: gilles@lamiral.info (Gilles LAMIRAL)
  11703. test: aethaecohngiexao
  11704. EOM
  11705. ),
  11706. 'skipmess: 9 match Content-Type: Message/Partial' ) ;
  11707. ok( 1 == skipmess(
  11708. <<'EOM'
  11709. Date: Mon, 2 Mar 2015 15:38:34 +0100 (CET)
  11710. From: gilles@lamiral.info (Gilles LAMIRAL)
  11711. Content-Type: message/partial;
  11712. id="TAN_U_P<1404267997.00007489ed17>";
  11713. number=3;
  11714. total=3
  11715. test: aethaecohngiexao
  11716. EOM
  11717. . "lalala\n" x 3_000_000
  11718. ),
  11719. 'skipmess: 10 match Content-Type: Message/Partial' ) ;
  11720. ok( 0 == skipmess(
  11721. <<'EOM'
  11722. Date: Mon, 2 Mar 2015 15:38:34 +0100 (CET)
  11723. From: gilles@lamiral.info (Gilles LAMIRAL)
  11724. test: aethaecohngiexao
  11725. EOM
  11726. . "lalala\n" x 3_000_000
  11727. ),
  11728. 'skipmess: 11 match Content-Type: Message/Partial' ) ;
  11729. ok( 0 == skipmess(
  11730. <<"EOM"
  11731. From: fff\r
  11732. To: fff\r
  11733. Subject: Testing imapsync --skipmess\r
  11734. Date: Mon, 22 Aug 2011 08:40:20 +0800\r
  11735. Mime-Version: 1.0\r
  11736. Content-Type: text/plain; charset=iso-8859-1\r
  11737. Content-Transfer-Encoding: 7bit\r
  11738. \r
  11739. EOM
  11740. . qq{!#"d%&'()*+,-./0123456789:;<=>?\@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefg\r\n } x 32_730
  11741. ),
  11742. 'skipmess: 12 not match Content-Type: Message/Partial' ) ;
  11743. # Complex regular subexpression recursion limit (32766) exceeded with more lines
  11744. # exit;
  11745. undef @skipmess ;
  11746. note( 'Leaving tests_skipmess()' ) ;
  11747. return ;
  11748. }
  11749. sub tests_skipmess_neg
  11750. {
  11751. note( 'Entering tests_skipmess_neg()' ) ;
  11752. @skipmess = ('m{i}') ;
  11753. ok( 1 == skipmess( 'Hi!' ), 'skipmess: i string yes' ) ;
  11754. ok( 0 == skipmess( 'Ho!' ), 'skipmess: i string no' ) ;
  11755. @skipmess = ('m{\A(?!.*i)}') ;
  11756. ok( 0 == skipmess( 'Hi!' ), 'skipmess: not i string no' ) ;
  11757. ok( 1 == skipmess( 'Ho!' ), 'skipmess: not i string yes' ) ;
  11758. @skipmess = ('m{\A(?!.*^From:[^\n]*tartanpion\@machin\.truc)}xms') ;
  11759. ok( 0 == skipmess(
  11760. <<'EOM'
  11761. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11762. From: <tartanpion@machin.truc>
  11763. Bye.
  11764. EOM
  11765. ),
  11766. 'skipmess: 1 not From tartanpion@machin.truc' ) ;
  11767. ok( 1 == skipmess(
  11768. <<'EOM'
  11769. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11770. From: <kikiki@machin.truc>
  11771. Bye.
  11772. EOM
  11773. ),
  11774. 'skipmess: 2 not From tartanpion@machin.truc' ) ;
  11775. ok( 0 == skipmess(
  11776. <<'EOM'
  11777. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11778. From: <tartanpion@machin.truc>
  11779. From: <tartanpion@machin.truc>
  11780. Bye.
  11781. EOM
  11782. ),
  11783. 'skipmess: 3 not From tartanpion@machin.truc' ) ;
  11784. ok( 1 == skipmess(
  11785. <<'EOM'
  11786. Date: Sat, 10 Jul 2010 05:34:45 -0700
  11787. From: <kikiki@machin.truc>
  11788. From: <tartanpion@machin.truc>
  11789. Bye.
  11790. EOM
  11791. ),
  11792. 'skipmess: 4 not From tartanpion@machin.truc' ) ;
  11793. undef @skipmess ;
  11794. note( 'Leaving tests_skipmess_neg()' ) ;
  11795. return ;
  11796. }
  11797. sub skipmess
  11798. {
  11799. my ( $string ) = @_ ;
  11800. my $match ;
  11801. #myprint( "$string\n" ) ;
  11802. foreach my $skipmess ( @skipmess ) {
  11803. $sync->{ debug } and myprint( "eval \$match = \$string =~ $skipmess\n" ) ;
  11804. my $ret = eval "\$match = \$string =~ $skipmess ; 1" ;
  11805. #myprint( "eval [$ret]\n" ) ;
  11806. $sync->{ debug } and myprint( "match [$match]\n" ) ;
  11807. if ( ( not $ret ) or $EVAL_ERROR ) {
  11808. myprint( "Error: eval skipmess '$skipmess': $EVAL_ERROR" ) ;
  11809. return( undef ) ;
  11810. }
  11811. return( $match ) if ( $match ) ;
  11812. }
  11813. return( $match ) ;
  11814. }
  11815. sub tests_bytes_display_string_bin
  11816. {
  11817. note( 'Entering tests_bytes_display_string_bin()' ) ;
  11818. is( 'NA', bytes_display_string_bin( ), 'bytes_display_string_bin: no args => NA' ) ;
  11819. is( 'NA', bytes_display_string_bin( undef ), 'bytes_display_string_bin: undef => NA' ) ;
  11820. is( 'NA', bytes_display_string_bin( 'blabla' ), 'bytes_display_string_bin: blabla => NA' ) ;
  11821. is( '0.000 KiB', bytes_display_string_bin( 0 ), 'bytes_display_string_bin: 0 => 0.000 KiB' ) ;
  11822. is( '0.001 KiB', bytes_display_string_bin( 1 ), 'bytes_display_string_bin: 1 => 0.001 KiB' ) ;
  11823. is( '0.010 KiB', bytes_display_string_bin( 10 ), 'bytes_display_string_bin: 10 => 0.010 KiB' ) ;
  11824. is( '0.976 KiB', bytes_display_string_bin( 999 ), 'bytes_display_string_bin: 999 => 0.976 KiB' ) ;
  11825. note( bytes_display_string_bin( 999 ) ) ;
  11826. is( '0.999 KiB', bytes_display_string_bin( 1023 ), 'bytes_display_string_bin: 1023 => 0.999 KiB' ) ;
  11827. note( bytes_display_string_bin( 1023 ) ) ;
  11828. is( '1.000 KiB', bytes_display_string_bin( 1024 ), 'bytes_display_string_bin: 1024 => 1.000 KiB' ) ;
  11829. note( bytes_display_string_bin( 1024 ) ) ;
  11830. is( '1.001 KiB', bytes_display_string_bin( 1025 ), 'bytes_display_string_bin: 1025 => 1.001 KiB' ) ;
  11831. is( '9.999 KiB', bytes_display_string_bin( 10_239 ), 'bytes_display_string_bin: 10_239 => 9.999 KiB' ) ;
  11832. note( bytes_display_string_bin( 10_239 ) ) ;
  11833. is( '10.000 KiB', bytes_display_string_bin( 10_240 ), 'bytes_display_string_bin: 10_240 => 10.000 KiB' ) ;
  11834. note( bytes_display_string_bin( 10_240 ) ) ;
  11835. is( '999.999 KiB', bytes_display_string_bin( 1_023_999 ), 'bytes_display_string_bin: 1_023_999 => 999.999 KiB' ) ;
  11836. note( bytes_display_string_bin( 1_023_999 ) ) ;
  11837. is( '0.977 MiB', bytes_display_string_bin( 1_024_000 ), 'bytes_display_string_bin: 1_024_000 => 0.977 MiB' ) ;
  11838. note( bytes_display_string_bin( 1_024_000 ) ) ;
  11839. is( '0.999 MiB', bytes_display_string_bin( 1_047_527 ), 'bytes_display_string_bin: 1_047_527 => 0.999 MiB' ) ;
  11840. note( bytes_display_string_bin( 1_047_527 ) ) ;
  11841. is( '0.999 MiB', bytes_display_string_bin( 1_048_051 ), 'bytes_display_string_bin: 1_048_051 => 0.999 MiB' ) ;
  11842. note( bytes_display_string_bin( 1_048_051 ) ) ;
  11843. is( '1.000 MiB', bytes_display_string_bin( 1_048_052 ), 'bytes_display_string_bin: 1_048_052 => 1.000 MiB' ) ;
  11844. note( bytes_display_string_bin( 1_048_052 ) ) ;
  11845. is( '1.000 MiB', bytes_display_string_bin( 1_048_575 ), 'bytes_display_string_bin: 1_048_575 => 1.000 MiB' ) ;
  11846. is( '1.000 MiB', bytes_display_string_bin( 1_048_576 ), 'bytes_display_string_bin: 1_048_576 => 1.000 MiB' ) ;
  11847. is( '1.000 GiB', bytes_display_string_bin( 1_073_741_823 ), 'bytes_display_string_bin: 1_073_741_823 => 1.000 GiB' ) ;
  11848. is( '1.000 GiB', bytes_display_string_bin( 1_073_741_824 ), 'bytes_display_string_bin: 1_073_741_824 => 1.000 GiB' ) ;
  11849. 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' ) ;
  11850. 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' ) ;
  11851. 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' ) ;
  11852. 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' ) ;
  11853. 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' ) ;
  11854. 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' ) ;
  11855. 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' ) ;
  11856. note( bytes_display_string_bin( 1_180_591_620_717_411_303_424 ) ) ;
  11857. note( bytes_display_string_bin( 3_000_000_000 ) ) ;
  11858. note( 'Leaving tests_bytes_display_string_bin()' ) ;
  11859. return ;
  11860. }
  11861. sub bytes_display_string_bin
  11862. {
  11863. my ( $bytes ) = @_ ;
  11864. my $readable_value = q{} ;
  11865. if ( ! defined( $bytes ) ) {
  11866. return( 'NA' ) ;
  11867. }
  11868. if ( not match_number( $bytes ) ) {
  11869. return( 'NA' ) ;
  11870. }
  11871. SWITCH: {
  11872. if ( abs( $bytes ) < ( 1000 * $KIBI ) ) {
  11873. $readable_value = mysprintf( '%.3f KiB', $bytes / $KIBI) ;
  11874. last SWITCH ;
  11875. }
  11876. if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI ) ) {
  11877. $readable_value = mysprintf( '%.3f MiB', $bytes / ($KIBI * $KIBI) ) ;
  11878. last SWITCH ;
  11879. }
  11880. if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI * $KIBI) ) {
  11881. $readable_value = mysprintf( '%.3f GiB', $bytes / ($KIBI * $KIBI * $KIBI) ) ;
  11882. last SWITCH ;
  11883. }
  11884. if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI * $KIBI * $KIBI) ) {
  11885. $readable_value = mysprintf( '%.3f TiB', $bytes / ($KIBI * $KIBI * $KIBI * $KIBI) ) ;
  11886. last SWITCH ;
  11887. } else {
  11888. $readable_value = mysprintf( '%.3f PiB', $bytes / ($KIBI * $KIBI * $KIBI * $KIBI * $KIBI) ) ;
  11889. }
  11890. # if you have exabytes (EiB) of email to transfer, you have too much email!
  11891. }
  11892. #myprint( "$bytes = $readable_value\n" ) ;
  11893. return( $readable_value ) ;
  11894. }
  11895. sub tests_bytes_display_string_dec
  11896. {
  11897. note( 'Entering tests_bytes_display_string_dec()' ) ;
  11898. is( 'NA', bytes_display_string_dec( ), 'bytes_display_string_dec: no args => NA' ) ;
  11899. is( 'NA', bytes_display_string_dec( undef ), 'bytes_display_string_dec: undef => NA' ) ;
  11900. is( 'NA', bytes_display_string_dec( 'blabla' ), 'bytes_display_string_dec: blabla => NA' ) ;
  11901. is( '0 bytes', bytes_display_string_dec( 0 ), 'bytes_display_string_dec: 0 => 0 bytes' ) ;
  11902. is( '1 bytes', bytes_display_string_dec( 1 ), 'bytes_display_string_dec: 1 => 1 bytes' ) ;
  11903. is( '10 bytes', bytes_display_string_dec( 10 ), 'bytes_display_string_dec: 10 => 10 bytes' ) ;
  11904. is( '999 bytes', bytes_display_string_dec( 999 ), 'bytes_display_string_dec: 999 => 999 bytes' ) ;
  11905. is( '1.000 KB', bytes_display_string_dec( 1000 ), 'bytes_display_string_dec: 1000 => 1.000 KB' ) ;
  11906. is( '1.001 KB', bytes_display_string_dec( 1001 ), 'bytes_display_string_dec: 1000 => 1.1001 KB' ) ;
  11907. is( '999.999 KB', bytes_display_string_dec( 999_999 ), 'bytes_display_string_dec: 999_999 => 999.999 KB' ) ;
  11908. is( '1.000 MB', bytes_display_string_dec( 1_000_000 ), 'bytes_display_string_dec: 1_000_000 => 1.000 MB' ) ;
  11909. is( '1.000 MB', bytes_display_string_dec( 1_000_500 ), 'bytes_display_string_dec: 1_000_500 => 1.000 MB' ) ;
  11910. is( '1.001 MB', bytes_display_string_dec( 1_000_501 ), 'bytes_display_string_dec: 1_000_501 => 1.001 MB' ) ;
  11911. is( '999.999 MB', bytes_display_string_dec( 999_999_000 ), 'bytes_display_string_dec: 999_999_000 => 999.999 MB' ) ;
  11912. is( '999.999 MB', bytes_display_string_dec( 999_999_499 ), 'bytes_display_string_dec: 999_999_499 => 999.999 MB' ) ;
  11913. is( '1.000 GB', bytes_display_string_dec( 999_999_500 ), 'bytes_display_string_dec: 999_999_500 => 1.000 GB' ) ;
  11914. is( '1.000 GB', bytes_display_string_dec( 1_000_000_000 ), 'bytes_display_string_dec: 1_000_000_000 => 1.000 GB' ) ;
  11915. is( '1.000 GB', bytes_display_string_dec( 1_000_500_000 ), 'bytes_display_string_dec: 1_000_500_000 => 1.000 GB' ) ;
  11916. is( '1.001 GB', bytes_display_string_dec( 1_000_500_001 ), 'bytes_display_string_dec: 1_000_501_000 => 1.001 GB' ) ;
  11917. is( '999.999 GB', bytes_display_string_dec( 999_999_000_000 ), 'bytes_display_string_dec: 999_999_000_000 => 999.999 GB' ) ;
  11918. is( '999.999 GB', bytes_display_string_dec( 999_999_499_999 ), 'bytes_display_string_dec: 999_999_499_999 => 999.999 GB' ) ;
  11919. is( '1.000 TB', bytes_display_string_dec( 999_999_500_000 ), 'bytes_display_string_dec: 999_999_500_000 => 1.000 TB' ) ;
  11920. 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' ) ;
  11921. 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' ) ;
  11922. 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' ) ;
  11923. 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' ) ;
  11924. 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' ) ;
  11925. 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' ) ;
  11926. is( '3.000 GB', bytes_display_string_dec( 3_000_000_000 ), 'bytes_display_string_dec: 3_000_000_000 => 3.000 GB' ) ;
  11927. note( 'Leaving tests_bytes_display_string_dec()' ) ;
  11928. return ;
  11929. }
  11930. sub bytes_display_string_dec
  11931. {
  11932. my ( $bytes ) = @_ ;
  11933. my $readable_value = q{} ;
  11934. if ( ! defined( $bytes ) ) {
  11935. return( 'NA' ) ;
  11936. }
  11937. if ( not match_number( $bytes ) ) {
  11938. return( 'NA' ) ;
  11939. }
  11940. SWITCH: {
  11941. if ( abs( $bytes ) < ( 1000 ) ) {
  11942. $readable_value = mysprintf( '%.0f bytes', $bytes ) ;
  11943. last SWITCH ;
  11944. }
  11945. if ( abs( $bytes ) < ( 1000**2 ) ) {
  11946. $readable_value = mysprintf( '%.3f KB', $bytes / 1000 ) ;
  11947. last SWITCH ;
  11948. }
  11949. if ( abs( $bytes ) < ( 999_999_500 ) ) {
  11950. $readable_value = mysprintf( '%.3f MB', $bytes / ( 1000**2 ) ) ;
  11951. last SWITCH ;
  11952. }
  11953. if ( abs( $bytes ) < ( 999_999_500_000 ) ) {
  11954. $readable_value = mysprintf( '%.3f GB', $bytes / ( 1000**3 ) ) ;
  11955. last SWITCH ;
  11956. }
  11957. if ( abs( $bytes ) < ( 999_999_500_000_000 ) ) {
  11958. $readable_value = mysprintf( '%.3f TB', $bytes / ( 1000**4 ) ) ;
  11959. last SWITCH ;
  11960. } else {
  11961. $readable_value = mysprintf( '%.3f PB', $bytes / ( 1000**5 ) ) ;
  11962. }
  11963. # if you have exabytes (EiB) of email to transfer, you have too much email!
  11964. }
  11965. #myprint( "$bytes = $readable_value\n" ) ;
  11966. return( $readable_value ) ;
  11967. }
  11968. sub tests_useheader_suggestion
  11969. {
  11970. note( 'Entering tests_useheader_suggestion()' ) ;
  11971. is( undef, useheader_suggestion( ), 'useheader_suggestion: no args => undef' ) ;
  11972. my $mysync = {} ;
  11973. $mysync->{ h1_nb_msg_noheader } = 0 ;
  11974. is( q{}, useheader_suggestion( $mysync ), 'useheader_suggestion: h1_nb_msg_noheader count null => no suggestion' ) ;
  11975. $mysync->{ h1_nb_msg_noheader } = 2 ;
  11976. is( q{in order to sync those 2 unidentified messages, add option --addheader}, useheader_suggestion( $mysync ),
  11977. 'useheader_suggestion: h1_nb_msg_noheader count 2 => suggestion of --addheader' ) ;
  11978. note( 'Leaving tests_useheader_suggestion()' ) ;
  11979. return ;
  11980. }
  11981. sub useheader_suggestion
  11982. {
  11983. my $mysync = shift @ARG ;
  11984. if ( ! defined $mysync->{ h1_nb_msg_noheader } )
  11985. {
  11986. return ;
  11987. }
  11988. elsif ( 1 <= $mysync->{ h1_nb_msg_noheader } )
  11989. {
  11990. return qq{in order to sync those $mysync->{ h1_nb_msg_noheader } unidentified messages, add option --addheader} ;
  11991. }
  11992. else
  11993. {
  11994. return q{} ;
  11995. }
  11996. return ;
  11997. }
  11998. sub do_and_print_stats
  11999. {
  12000. my $mysync = shift @ARG ;
  12001. if ( ! $mysync->{can_do_stats} ) {
  12002. return ;
  12003. }
  12004. my $timeend = time ;
  12005. my $timediff = $timeend - $mysync->{timestart} ;
  12006. my $timeend_str = localtimez( $timeend ) ;
  12007. my $cpu_time = cpu_time( $mysync ) ;
  12008. my $cpu_percent = cpu_percent( $mysync, $cpu_time, $timediff ) ;
  12009. my $cpu_percent_global = cpu_percent_global( $mysync, $cpu_percent ) ;
  12010. my $memory_consumption_at_end = memory_consumption_of_myself( ) || 0 ;
  12011. my $memory_consumption_at_start = $mysync->{ memory_consumption_at_start } || 0 ;
  12012. my $memory_ratio = ( $mysync->{ biggest_message_transferred } ) ?
  12013. mysprintf( '%.1f', $memory_consumption_at_end / $mysync->{ biggest_message_transferred } ) : 'NA' ;
  12014. # my $useheader_suggestion = useheader_suggestion( $mysync ) ;
  12015. myprint( "++++ Statistics\n" ) ;
  12016. myprint( "Transfer started on : $mysync->{ timestart_str }\n" ) ;
  12017. myprint( "Transfer ended on : $timeend_str\n" ) ;
  12018. myprintf( "Transfer time : %.1f sec\n", $timediff ) ;
  12019. myprint( "Folders synced : $h1_folders_wanted_ct/$h1_folders_wanted_nb synced\n" ) ;
  12020. myprint( "Messages transferred : $mysync->{ nb_msg_transferred } " ) ;
  12021. myprint( "(could be $nb_msg_skipped_dry_mode without dry mode)" ) if ( $mysync->{dry} ) ;
  12022. myprint( "\n" ) ;
  12023. myprint( "Messages skipped : $mysync->{ nb_msg_skipped }\n" ) ;
  12024. myprint( "Messages found duplicate on host1 : $mysync->{ acc1 }->{ nb_msg_duplicate }\n" ) ;
  12025. myprint( "Messages found duplicate on host2 : $mysync->{ acc2 }->{ nb_msg_duplicate }\n" ) ;
  12026. myprint( "Messages found crossduplicate on host2 : $mysync->{ h2_nb_msg_crossdup }\n" ) ;
  12027. myprint( "Messages void (noheader) on host1 : $mysync->{ h1_nb_msg_noheader } ", useheader_suggestion( $mysync ), "\n" ) ;
  12028. myprint( "Messages void (noheader) on host2 : $h2_nb_msg_noheader\n" ) ;
  12029. nb_messages_in_1_not_in_2( $mysync ) ;
  12030. nb_messages_in_2_not_in_1( $mysync ) ;
  12031. myprintf( "Messages found in host1 not in host2 : %s messages\n", $mysync->{ nb_messages_in_1_not_in_2 } ) ;
  12032. myprintf( "Messages found in host2 not in host1 : %s messages\n", $mysync->{ nb_messages_in_2_not_in_1 } ) ;
  12033. myprint( "Messages deleted on host1 : $mysync->{ acc1 }->{ nb_msg_deleted }\n" ) ;
  12034. myprint( "Messages deleted on host2 : $mysync->{ acc2 }->{ nb_msg_deleted }\n" ) ;
  12035. myprintf( "Total bytes transferred : %s (%s)\n",
  12036. $mysync->{total_bytes_transferred},
  12037. bytes_display_string_bin( $mysync->{total_bytes_transferred} ) ) ;
  12038. myprintf( "Total bytes skipped : %s (%s)\n",
  12039. $mysync->{ total_bytes_skipped },
  12040. bytes_display_string_bin( $mysync->{ total_bytes_skipped } ) ) ;
  12041. $timediff ||= 1 ; # No division per 0
  12042. myprintf("Message rate : %.1f messages/s\n", $mysync->{nb_msg_transferred} / $timediff ) ;
  12043. myprintf("Average bandwidth rate : %.1f KiB/s\n", $mysync->{total_bytes_transferred} / $KIBI / $timediff ) ;
  12044. myprint( "Reconnections to host1 : $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT}\n" ) ;
  12045. myprint( "Reconnections to host2 : $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT}\n" ) ;
  12046. myprintf("Memory consumption at the end : %.1f MiB (*time %.1f MiB*h) (started with %.1f MiB)\n",
  12047. $memory_consumption_at_end / $KIBI / $KIBI,
  12048. memory_consumption_surface( $mysync, $memory_consumption_at_end, $timediff ),
  12049. $memory_consumption_at_start / $KIBI / $KIBI ) ;
  12050. myprint( "Load end is : " . ( join( q{ }, loadavg( ) ) || 'unknown' ), " on $mysync->{cpu_number} cores\n" ) ;
  12051. myprint( "CPU time and %cpu : $cpu_time sec $cpu_percent %cpu $cpu_percent_global %allcpus\n" ) ;
  12052. myprintf("Biggest message transferred : %s bytes (%s)\n",
  12053. $mysync->{ biggest_message_transferred },
  12054. bytes_display_string_bin( $mysync->{ biggest_message_transferred } ) ) ;
  12055. myprint( "Memory/biggest message ratio : $memory_ratio\n" ) ;
  12056. if ( $mysync->{ foldersizesatend } and $mysync->{ foldersizes } ) {
  12057. my $nb_msg_start_diff = diff_or_NA( $mysync->{ h2_nb_msg_start }, $mysync->{ h1_nb_msg_start } ) ;
  12058. my $bytes_start_diff = diff_or_NA( $mysync->{ h2_bytes_start }, $mysync->{ h1_bytes_start } ) ;
  12059. myprintf("Start difference host2 - host1 : %s messages, %s bytes (%s)\n", $nb_msg_start_diff,
  12060. $bytes_start_diff,
  12061. bytes_display_string_bin( $bytes_start_diff ) ) ;
  12062. my $nb_msg_end_diff = diff_or_NA( $h2_nb_msg_end, $h1_nb_msg_end ) ;
  12063. my $bytes_end_diff = diff_or_NA( $h2_bytes_end, $h1_bytes_end ) ;
  12064. myprintf("Final difference host2 - host1 : %s messages, %s bytes (%s)\n", $nb_msg_end_diff,
  12065. $bytes_end_diff,
  12066. bytes_display_string_bin( $bytes_end_diff ) ) ;
  12067. }
  12068. comment_on_final_diff_in_1_not_in_2( $mysync ) ;
  12069. comment_on_final_diff_in_2_not_in_1( $mysync ) ;
  12070. myprint( "Detected $mysync->{nb_errors} errors\n" ) ;
  12071. myprint( $mysync->{ warn_release }, "\n" ) ;
  12072. myprint( homepage( ), "\n" ) ;
  12073. return ;
  12074. }
  12075. sub tests_memory_consumption_surface
  12076. {
  12077. note( 'Entering tests_memory_consumption_surface()' ) ;
  12078. is( undef, memory_consumption_surface( ), 'memory_consumption_surface: no args => undef' ) ;
  12079. my $mysync = { } ;
  12080. is( undef, memory_consumption_surface( $mysync ), 'memory_consumption_surface: { } => undef' ) ;
  12081. is( "1.0", memory_consumption_surface( $mysync, 1_048_576, 3600 ), 'memory_consumption_surface: 1 MB 1 hour => 1' ) ;
  12082. is( "238.4", memory_consumption_surface( $mysync, 500_000_000, 1800 ), 'memory_consumption_surface: 500 MB 30 min => 238' ) ;
  12083. note( 'Leaving tests_memory_consumption_surface()' ) ;
  12084. return ;
  12085. }
  12086. sub memory_consumption_surface
  12087. {
  12088. my ( $mysync, $memory_consumption_at_end, $timediff ) = @ARG ;
  12089. if ( ! all_defined( $mysync, $memory_consumption_at_end, $timediff ) ) { return } ;
  12090. my $memory_consumption_surface = sprintf( "%.1f", $timediff * $memory_consumption_at_end / $KIBI / $KIBI / 3600 ) ;
  12091. return $memory_consumption_surface ;
  12092. }
  12093. sub tests_email_report_message_id
  12094. {
  12095. note( 'Entering tests_email_report_message_id()' ) ;
  12096. local $ENV{TZ} = 'GMT' ;
  12097. like( email_report_message_id( ),
  12098. qr{^...._.._.._.._.._.._...__\@imapsync.tk$}xms,
  12099. 'email_report_message_id: no args => ...._.._.._.._.._.._...__@imapsync.tk' ) ;
  12100. my $mysync = { } ;
  12101. like( email_report_message_id( $mysync ),
  12102. qr{^...._.._.._.._.._.._...__\@imapsync.tk$}xms,
  12103. 'email_report_message_id: undef => ...._.._.._.._.._.._...__@imapsync.tk' ) ;
  12104. $mysync->{ timestart } = 1357902468.531 ;
  12105. like(
  12106. email_report_message_id( $mysync ),
  12107. qr{^2013_01_11_\d\d_07_48_530__\@imapsync.tk$}xms,
  12108. 'email_report_message_id: 1357902468.531 => 2013_01_11_\d\d_07_48_530__@imapsync.tk' ) ;
  12109. $mysync->{ user1 } = 'sarah' ;
  12110. $mysync->{ user2 } = 'haras' ;
  12111. $mysync->{ timestart } = 1357902468.531 ;
  12112. like(
  12113. email_report_message_id( $mysync ),
  12114. qr{^2013_01_11_\d\d_07_48_530_sarah_haras\@imapsync.tk$}xms,
  12115. 'email_report_message_id: 1357902468.531 sarah haras => 2013_01_11_\d\d_07_48_530_sarah_haras@imapsync.tk' ) ;
  12116. $mysync->{ user1 } = 'sar@ah' ;
  12117. $mysync->{ user2 } = 'har@as' ;
  12118. $mysync->{ timestart } = 1357902468.531 ;
  12119. like(
  12120. email_report_message_id( $mysync ),
  12121. qr{2013_01_11_\d\d_07_48_530_sar_ah_har_as\@imapsync.tk},
  12122. 'email_report_message_id: 1357902468.531 sar@ah har@as => 2013_01_11_\d\d_07_48_530_sar_ah_har_as@imapsync.tk' ) ;
  12123. note( 'Leaving tests_email_report_message_id()' ) ;
  12124. return ;
  12125. }
  12126. sub email_report_message_id
  12127. {
  12128. my $mysync = shift @ARG ;
  12129. my $time = $mysync->{ timestart } || time ;
  12130. my $user1_filtered = $mysync->{ user1 } || '' ;
  12131. my $user2_filtered = $mysync->{ user2 } || '' ;
  12132. # Nothing but alphanumeric characters and underscores to replace the others
  12133. $user1_filtered =~ s/[^a-zA-Z0-9]/_/g ;
  12134. $user2_filtered =~ s/[^a-zA-Z0-9]/_/g ;
  12135. my $message_id = join( '',
  12136. year_month_day_hour_min_sec_ms( $time ),
  12137. '_',
  12138. $user1_filtered,
  12139. '_',
  12140. $user2_filtered,
  12141. '@imapsync.tk',
  12142. ) ;
  12143. return $message_id ;
  12144. }
  12145. sub tests_date_rfc822
  12146. {
  12147. note( 'Entering tests_email_report_date()' ) ;
  12148. ok( date_rfc822( ), 'date_rfc822: no args => now: ' . date_rfc822( ) ) ;
  12149. if ( 'MSWin32' eq $OSNAME )
  12150. {
  12151. like( date_rfc822( 1671706800 ), qr{Thu, 22 Dec 2022 \d\d:00:00 \+0000}, 'date_rfc822: 1671706800 => Thu, 22 Dec 2022 \d\d:00:00 \+0000' ) ;
  12152. like( date_rfc822( 1671534000 ), qr{Tue, 20 Dec 2022 \d\d:00:00 \+0000}, 'date_rfc822: 1671534000 => Tue, 20 Dec 2022 \d\d:00:00 \+0000' ) ;
  12153. }
  12154. else
  12155. {
  12156. local $ENV{TZ} = 'GMT' ;
  12157. is( 'Thu, 01 Jan 1970 00:00:00 +0000', date_rfc822( 0 ), 'date_rfc822: 0 => Thu, 01 Jan 1970 00:00:00 +0000' ) ;
  12158. is( 'Thu, 22 Dec 2022 11:00:00 +0000', date_rfc822( 1671706800 ), 'date_rfc822: 1671706800 => Thu, 22 Dec 2022 11:00:00 +0000' ) ;
  12159. }
  12160. note( 'Leaving tests_email_report_date()' ) ;
  12161. return ;
  12162. }
  12163. sub date_rfc822
  12164. {
  12165. # Later I found Mail::IMAPCLient::Rfc822_date()
  12166. # https://metacpan.org/pod/Mail::IMAPClient#Rfc822_date
  12167. my $time = shift @ARG ;
  12168. $time = defined( $time ) ? $time : time ;
  12169. my $old_locale = POSIX::setlocale( POSIX::LC_TIME, "C" ) ;
  12170. my $date_rfc822 ;
  12171. if ( 'MSWin32' eq $OSNAME )
  12172. {
  12173. $date_rfc822 = POSIX::strftime( "%a, %d %b %Y %H:%M:%S +0000", localtime( $time ) ) ;
  12174. }
  12175. else
  12176. {
  12177. $date_rfc822 = POSIX::strftime( "%a, %d %b %Y %H:%M:%S %z", localtime( $time ) ) ;
  12178. }
  12179. POSIX::setlocale( POSIX::LC_TIME, $old_locale ) ;
  12180. return $date_rfc822 ;
  12181. }
  12182. sub tests_email_report_from
  12183. {
  12184. note( 'Entering tests_email_report_from()' ) ;
  12185. is( 'help@imapsync.tk', email_report_from( ), 'email_report_from: no args => help@imapsync.tk' ) ;
  12186. my $mysync = { } ;
  12187. is( 'help@imapsync.tk', email_report_from( $mysync ), 'email_report_from: undef => help@imapsync.tk' ) ;
  12188. $mysync->{ email_report_from } = 'foo@example.com' ;
  12189. is( 'foo@example.com', email_report_from( $mysync ), 'email_report_from: foo@example.com => foo@example.com' ) ;
  12190. note( 'Leaving tests_email_report_from()' ) ;
  12191. return ;
  12192. }
  12193. sub email_report_from
  12194. {
  12195. my $mysync = shift @ARG ;
  12196. my $email_report_from = defined( $mysync->{ email_report_from } )
  12197. ? $mysync->{ email_report_from }
  12198. : 'help@imapsync.tk' ;
  12199. return $email_report_from ;
  12200. }
  12201. sub tests_email_report_to
  12202. {
  12203. note( 'Entering tests_email_report_from()' ) ;
  12204. is( 'unknown@imapsync.tk', email_report_to( ), 'email_report_to: no args => help@imapsync.tk' ) ;
  12205. my $mysync = { } ;
  12206. is( 'unknown@imapsync.tk', email_report_to( $mysync ), 'email_report_to: undef => help@imapsync.tk' ) ;
  12207. $mysync->{ user2 } = 'foo@example.com' ;
  12208. is( 'foo@example.com', email_report_to( $mysync ), 'email_report_to: foo@example.com => foo@example.com' ) ;
  12209. note( 'Leaving tests_email_report_from()' ) ;
  12210. return ;
  12211. }
  12212. sub email_report_to
  12213. {
  12214. my $mysync = shift @ARG ;
  12215. my $email_report_to = defined( $mysync->{ user2 } )
  12216. ? $mysync->{ user2 }
  12217. : 'unknown@imapsync.tk' ;
  12218. return $email_report_to ;
  12219. }
  12220. sub email_report_subject
  12221. {
  12222. my $mysync = shift @ARG ;
  12223. my $email_report_subject = 'Imapsync transferred your account' ;
  12224. return $email_report_subject ;
  12225. }
  12226. sub tests_email_report_body_base
  12227. {
  12228. note( 'Entering tests_email_report_body_base()' ) ;
  12229. is( '', email_report_body_base( ), 'email_report_body_base: no args => empty string' ) ;
  12230. my $mysync = { } ;
  12231. is( '', email_report_body_base( $mysync ), 'email_report_body_base: undef => empty string' ) ;
  12232. $mysync->{ user1 } = 'user1@example.com' ;
  12233. $mysync->{ user2 } = 'user2@example.com' ;
  12234. note( email_report_body_base( $mysync ) ) ;
  12235. note( 'Leaving tests_email_report_body_base()' ) ;
  12236. return ;
  12237. }
  12238. sub email_report_body_base
  12239. {
  12240. my $mysync = shift @ARG ;
  12241. if( ! $mysync ) { return '' ; }
  12242. if( ! all_defined(
  12243. $mysync->{ user1 },
  12244. $mysync->{ user2 },
  12245. ) ) { return '' ; }
  12246. my $email_report_body_base = <<"EOM";
  12247. <!DOCTYPE html>\r
  12248. <html lang="en">\r
  12249. <head>\r
  12250. <title>Imapsync transfer from $mysync->{ user1 } to $mysync->{ user2 }</title>\r
  12251. </head>\r
  12252. <body>\r
  12253. <p>Hello!</p>\r
  12254. <p>\r
  12255. Imapsync just ended the synchronization from the imap account <b>$mysync->{ user1 }</b> to the imap account <b>$mysync->{ user2 }</b>.<br />\r
  12256. </p>\r
  12257. EOM
  12258. return $email_report_body_base ;
  12259. }
  12260. sub email_report_body_extra1
  12261. {
  12262. my $image = '<a href="https://imapsync.lamiral.info/">'
  12263. .'<img src="https://imapsync.lamiral.info/S/images/logo_imapsync_s1.png" alt="imapsync website" />'
  12264. .'</a>'
  12265. . "\r\n"
  12266. ;
  12267. return $image ;
  12268. }
  12269. sub email_report_body_extra2
  12270. {
  12271. my $image = '<a href="https://imapsync.lamiral.info/">'
  12272. .'<img src="https://imapsync.lamiral.info/S/images/logo_imapsync_s2.png" alt="imapsync website" />'
  12273. .'</a>'
  12274. . "\r\n"
  12275. ;
  12276. return $image ;
  12277. }
  12278. sub email_report_html_begin
  12279. {
  12280. return '<html>'
  12281. . "\r\n"
  12282. ;
  12283. }
  12284. sub email_report_html_end
  12285. {
  12286. return '</html>'
  12287. . "\r\n"
  12288. ;
  12289. }
  12290. sub email_report_body_begin
  12291. {
  12292. return '<body>'
  12293. . "\r\n"
  12294. ;
  12295. }
  12296. sub email_report_body_end
  12297. {
  12298. return '</body>'
  12299. . "\r\n"
  12300. ;
  12301. }
  12302. sub email_report_header
  12303. {
  12304. my $mysync = shift @ARG ;
  12305. my @header ;
  12306. push( @header, join( "\r\n",
  12307. 'Message-Id: ' . email_report_message_id( $mysync ),
  12308. 'Date: ' . date_rfc822( time ),
  12309. 'From: ' . email_report_from( $mysync ),
  12310. 'To: ' . email_report_to( $mysync ),
  12311. 'Subject: ' . email_report_subject( $mysync ),
  12312. 'Content-Type: text/html',
  12313. "\r\n",
  12314. )
  12315. ) ;
  12316. return @header ;
  12317. }
  12318. sub tests_email_report
  12319. {
  12320. note( 'Entering tests_email_report()' ) ;
  12321. is( '', email_report( ), 'email_report: undef => empty string' ) ;
  12322. my $mysync = { } ;
  12323. $mysync->{ user1 } = 'user1@example.com' ;
  12324. $mysync->{ user2 } = 'user2@example.com' ;
  12325. note( email_report( $mysync, "c'est extra !\r\n" ) ) ;
  12326. note( 'Leaving tests_email_report()' ) ;
  12327. return ;
  12328. }
  12329. sub email_report
  12330. {
  12331. my $mysync = shift @ARG ;
  12332. my $extra = shift @ARG ;
  12333. if ( ! defined $mysync ) { return '' ; }
  12334. my @email_report = ( ) ;
  12335. push( @email_report, email_report_header( $mysync ),
  12336. email_report_html_begin( ),
  12337. email_report_body_begin( ),
  12338. email_report_body_base( $mysync ),
  12339. $extra,
  12340. email_report_body_end( ),
  12341. email_report_html_end( ),
  12342. ) ;
  12343. my $email_report = join( "", @email_report ) ;
  12344. return $email_report ;
  12345. }
  12346. sub email_report_append
  12347. {
  12348. my $acc = shift @ARG ;
  12349. my $text = shift @ARG ;
  12350. if ( $acc->{ imap }->IsAuthenticated && $text )
  12351. {
  12352. my $newuid = $acc->{ imap }->append_string( 'INBOX', $text ) ;
  12353. if ( $newuid )
  12354. {
  12355. myprint( "$acc->{ Side }: Successfully put the email final report in INBOX. Use --noemailreport" . $acc->{N} . " to avoid it.\n" ) ;
  12356. }
  12357. else
  12358. {
  12359. myprint( "$acc->{ Side }: Failed to put the email final report in INBOX. Use --noemailreport" . $acc->{N} . " to avoid it.\n" ) ;
  12360. }
  12361. return $newuid ;
  12362. }
  12363. else
  12364. {
  12365. return ;
  12366. }
  12367. }
  12368. sub final_emails_reports
  12369. {
  12370. my $mysync = shift @ARG ;
  12371. if ( condition_to_put_final_emails_reports( $mysync ) )
  12372. {
  12373. $mysync->{ emailreport2 } and email_report_append( $mysync->{ acc2 }, email_report( $mysync, email_report_body_extra2( ) ) ) ;
  12374. $mysync->{ emailreport1 } and email_report_append( $mysync->{ acc1 }, email_report( $mysync, email_report_body_extra1( ) ) ) ;
  12375. }
  12376. return ;
  12377. }
  12378. sub condition_to_put_final_emails_reports
  12379. {
  12380. my $mysync = shift @ARG ;
  12381. if ( $mysync->{ dry } ) { return 0 ; }
  12382. if ( $mysync->{ justfolders } ) { return 0 ; }
  12383. return 1 ;
  12384. }
  12385. sub diff_or_NA
  12386. {
  12387. my( $n1, $n2 ) = @ARG ;
  12388. if ( not defined $n1 or not defined $n2 ) {
  12389. return 'NA' ;
  12390. }
  12391. if ( not match_number( $n1 )
  12392. or not match_number( $n2 ) ) {
  12393. return 'NA' ;
  12394. }
  12395. return( $n1 - $n2 ) ;
  12396. }
  12397. sub match_number
  12398. {
  12399. my $n = shift @ARG ;
  12400. if ( not defined $n ) {
  12401. return 0 ;
  12402. }
  12403. if ( $n =~ /[0-9]+\.?[0-9]?/x ) {
  12404. return 1 ;
  12405. }
  12406. else {
  12407. return 0 ;
  12408. }
  12409. }
  12410. sub tests_match_number
  12411. {
  12412. note( 'Entering tests_match_number()' ) ;
  12413. is( 0, match_number( ), 'match_number: no parameters => 0' ) ;
  12414. is( 0, match_number( undef ), 'match_number: undef => 0' ) ;
  12415. is( 0, match_number( 'blabla' ), 'match_number: blabla => 0' ) ;
  12416. is( 1, match_number( 0 ), 'match_number: 0 => 1' ) ;
  12417. is( 1, match_number( 1 ), 'match_number: 1 => 1' ) ;
  12418. is( 1, match_number( 1.0 ), 'match_number: 1.0 => 1' ) ;
  12419. is( 1, match_number( 0.0 ), 'match_number: 0.0 => 1' ) ;
  12420. note( 'Leaving tests_match_number()' ) ;
  12421. return ;
  12422. }
  12423. sub tests_diff_or_NA
  12424. {
  12425. note( 'Entering tests_diff_or_NA()' ) ;
  12426. is( 'NA', diff_or_NA( ), 'diff_or_NA: no parameters => NA' ) ;
  12427. is( 'NA', diff_or_NA( undef ), 'diff_or_NA: undef => NA' ) ;
  12428. is( 'NA', diff_or_NA( undef, undef ), 'diff_or_NA: undef undef => NA' ) ;
  12429. is( 'NA', diff_or_NA( undef, 1 ), 'diff_or_NA: undef 1 => NA' ) ;
  12430. is( 'NA', diff_or_NA( 1, undef ), 'diff_or_NA: 1 undef => NA' ) ;
  12431. is( 'NA', diff_or_NA( 'blabla', 1 ), 'diff_or_NA: blabla 1 => NA' ) ;
  12432. is( 'NA', diff_or_NA( 1, 'blabla' ), 'diff_or_NA: 1 blabla => NA' ) ;
  12433. is( 0, diff_or_NA( 1, 1 ), 'diff_or_NA: 1 1 => 0' ) ;
  12434. is( 1, diff_or_NA( 1, 0 ), 'diff_or_NA: 1 0 => 1' ) ;
  12435. is( -1, diff_or_NA( 0, 1 ), 'diff_or_NA: 0 1 => -1' ) ;
  12436. is( 0, diff_or_NA( 1.0, 1 ), 'diff_or_NA: 1.0 1 => 0' ) ;
  12437. is( 1, diff_or_NA( 1.0, 0 ), 'diff_or_NA: 1.0 0 => 1' ) ;
  12438. is( -1, diff_or_NA( 0, 1.0 ), 'diff_or_NA: 0 1.0 => -1' ) ;
  12439. note( 'Leaving tests_diff_or_NA()' ) ;
  12440. return ;
  12441. }
  12442. sub homepage
  12443. {
  12444. return( 'Homepage: https://imapsync.lamiral.info/' ) ;
  12445. }
  12446. sub load_modules
  12447. {
  12448. if ( $sync->{ssl1}
  12449. or $sync->{ssl2}
  12450. or $sync->{tls1}
  12451. or $sync->{tls2}) {
  12452. if ( $sync->{inet4} ) {
  12453. IO::Socket::SSL->import( 'inet4' ) ;
  12454. }
  12455. if ( $sync->{inet6} ) {
  12456. IO::Socket::SSL->import( 'inet6' ) ;
  12457. }
  12458. }
  12459. return ;
  12460. }
  12461. # Globals: $skipsize $wholeheaderifneeded
  12462. sub parse_header_msg
  12463. {
  12464. my ( $mysync, $imap, $m_uid, $folder, $s_heads, $s_fir, $side, $s_hash ) = @_ ;
  12465. my $head = $s_heads->{$m_uid} ;
  12466. my $headnum = scalar keys %{ $head } ;
  12467. $mysync->{ debug } and myprint( "$side: folder/uid $folder/$m_uid number of headers, pass one: ", $headnum, "\n" ) ;
  12468. if ( ( ! $headnum ) and ( $wholeheaderifneeded ) ){
  12469. $mysync->{ debug } and myprint( "$side: folder/uid $folder/$m_uid no header by parse_headers so taking whole header with BODY.PEEK[HEADER]\n" ) ;
  12470. $imap->fetch($m_uid, 'BODY.PEEK[HEADER]' ) ;
  12471. my $whole_header = $imap->_transaction_literals ;
  12472. #myprint( $whole_header ) ;
  12473. $head = decompose_header( $whole_header ) ;
  12474. $headnum = scalar keys %{ $head } ;
  12475. $mysync->{ debug } and myprint( "$side: folder/uid $folder/$m_uid number of headers, pass two: ", $headnum, "\n" ) ;
  12476. }
  12477. #myprint( Data::Dumper->Dump( [ $head, \%useheader ] ) ) ;
  12478. my $headstr = header_construct( $mysync, $head, $side, $folder, $m_uid ) ;
  12479. if ( ( ! $headstr ) and ( $mysync->{addheader} ) and ( $side eq 'Host1' ) ) {
  12480. my $header = add_header( $m_uid ) ;
  12481. $mysync->{ debug } and myprint( "$side: folder/uid $folder/$m_uid no header found so adding our own [$header]\n" ) ;
  12482. $headstr .= uc $header ;
  12483. $s_fir->{$m_uid}->{NO_HEADER} = 1;
  12484. }
  12485. return if ( ! $headstr ) ;
  12486. my $size = $s_fir->{$m_uid}->{'RFC822.SIZE'} ;
  12487. my $flags = $s_fir->{$m_uid}->{'FLAGS'} ;
  12488. my $idate = $s_fir->{$m_uid}->{'INTERNALDATE'} ;
  12489. $size = length $headstr unless ( $size ) ;
  12490. my $m_md5 = md5_base64( $headstr ) ;
  12491. my $key ;
  12492. if ( $skipsize ) {
  12493. $key = "$m_md5";
  12494. }
  12495. else {
  12496. $key = "$m_md5:$size";
  12497. }
  12498. if ( exists $s_hash->{"$key"} )
  12499. {
  12500. # 0 return code is used to identify duplicate message hash
  12501. my $dup_ref = $s_hash->{"$key"}->{'U'} ;
  12502. my $num = scalar( @{ $dup_ref } ) ;
  12503. push( @{ $dup_ref }, $m_uid ) ;
  12504. my $keydup = "$key#$num" ;
  12505. $mysync->{ debug } and myprint( "$side: folder/uid $folder/$m_uid sig $keydup size $size idate $idate dup @{ $dup_ref }\n" ) ;
  12506. if ( $mysync->{ syncduplicates } )
  12507. {
  12508. $s_hash->{"$keydup"}{'5'} = $m_md5 ;
  12509. $s_hash->{"$keydup"}{'s'} = $size ;
  12510. $s_hash->{"$keydup"}{'D'} = $idate ;
  12511. $s_hash->{"$keydup"}{'F'} = $flags ;
  12512. $s_hash->{"$keydup"}{'m'} = $m_uid ;
  12513. }
  12514. return 0 ;
  12515. }
  12516. else
  12517. {
  12518. $s_hash->{"$key"}{'5'} = $m_md5 ;
  12519. $s_hash->{"$key"}{'s'} = $size ;
  12520. $s_hash->{"$key"}{'D'} = $idate ;
  12521. $s_hash->{"$key"}{'F'} = $flags ;
  12522. $s_hash->{"$key"}{'m'} = $m_uid ;
  12523. $s_hash->{"$key"}{'U'} = [ $m_uid ] ; # ? or [ ] ?
  12524. $mysync->{ debug } and myprint( "$side: folder/uid $folder/$m_uid sig $key size $size idate $idate\n" ) ;
  12525. return( 1 ) ;
  12526. }
  12527. # we should not be here
  12528. return ;
  12529. }
  12530. sub tests_header_construct
  12531. {
  12532. note( 'Entering tests_header_construct()' ) ;
  12533. is( undef, header_construct( ), 'header_construct: no args => undef' ) ;
  12534. my $mysync = {} ;
  12535. my $head = {
  12536. 'key1' => [ 'val1_key1' ]
  12537. } ;
  12538. is( undef, header_construct( $mysync, $head, 'Host1', 'INBOX', '1' ), 'header_construct: key1 val1_key1 no useheader => undef' ) ;
  12539. $mysync->{useheader}->{ 'KEY1' } = 1 ;
  12540. is( 'KEY1: VAL1_KEY1', header_construct( $mysync, $head, 'Host1', 'INBOX', '1' ), 'header_construct: key1 val1_key1 => KEY1: VAL1_KEY1' ) ;
  12541. $head = {
  12542. 'key1' => [ 'val1_key1', 'val3_key1', 'val2_key1' ]
  12543. } ;
  12544. is( 'KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1', header_construct( $mysync, $head, 'Host1', 'INBOX', '1' ),
  12545. 'header_construct: key1 val1_key1 val3_key1 val2_key1 => KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1' ) ;
  12546. $head = {
  12547. 'key1' => [ 'val1_key1', 'val3_key1', ' val2_key1' ]
  12548. } ;
  12549. is( 'KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1', header_construct( $mysync, $head, 'Host1', 'INBOX', '1' ),
  12550. 'header_construct: key1 val1_key1 val3_key1 val2_key1 => KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1' ) ;
  12551. $mysync->{useheader}->{ 'ALL' } = 1 ;
  12552. is( 'KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1', header_construct( $mysync, $head, 'Host1', 'INBOX', '1' ),
  12553. 'header_construct: key1 val1_key1 val3_key1 val2_key1 useheader ALL => KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1' ) ;
  12554. $mysync->{skipheader} = 'key1' ;
  12555. is( undef, header_construct( $mysync, $head, 'Host1', 'INBOX', '1' ),
  12556. 'header_construct: key1 val1_key1 val3_key1 val2_key1 useheader ALL => undef' ) ;
  12557. $head = {
  12558. 'key1' => [ 'val1_key1', 'val3_key1', ' val2_key1' ],
  12559. 'key2' => [ 'val1_key2', 'val3_key2', ' val2_key2' ]
  12560. } ;
  12561. is( 'KEY2: VAL1_KEY2KEY2: VAL2_KEY2KEY2: VAL3_KEY2', header_construct( $mysync, $head, 'Host1', 'INBOX', '1' ),
  12562. 'header_construct: ... useheader ALL skipheader key1 => KEY2: VAL1_KEY2KEY2: VAL2_KEY2KEY2: VAL3_KEY2' ) ;
  12563. note( 'Leaving tests_header_construct()' ) ;
  12564. return ;
  12565. }
  12566. # No global in header_construct
  12567. sub header_construct
  12568. {
  12569. my( $mysync, $head, $side, $folder, $m_uid ) = @_ ;
  12570. my @headstr ;
  12571. foreach my $h ( sort keys %{ $head } ) {
  12572. next if ( not ( exists $mysync->{useheader}->{ uc $h } )
  12573. and ( not exists $mysync->{useheader}->{ 'ALL' } )
  12574. ) ;
  12575. foreach my $val ( @{$head->{$h}} ) {
  12576. my $H = header_line_normalize( $h, $val ) ;
  12577. # show stuff in debug mode
  12578. $mysync->{ debug } and myprint( "$side: folder/uid $folder/$m_uid header [$H]", "\n" ) ;
  12579. if ( $mysync->{skipheader} and $H =~ m/$mysync->{skipheader}/xi) {
  12580. $mysync->{ debug } and myprint( "$side: folder/uid $folder/$m_uid skipping header [$H]\n" ) ;
  12581. next ;
  12582. }
  12583. push @headstr, $H ;
  12584. }
  12585. }
  12586. my $headstr = join( '', sort @headstr ) || undef ;
  12587. return( $headstr ) ;
  12588. }
  12589. sub header_line_normalize
  12590. {
  12591. my( $header_key, $header_val ) = @_ ;
  12592. # no 8-bit data in headers !
  12593. $header_val =~ s/[\x80-\xff]/X/xog;
  12594. # change tabulations to space (Gmail bug on with "Received:" on multilines)
  12595. $header_val =~ s/\t/\ /xgo ;
  12596. # remove the first blanks ( dbmail bug? )
  12597. $header_val =~ s/^\s*//xo;
  12598. # remove the last blanks ( Gmail bug )
  12599. $header_val =~ s/\s*$//xo;
  12600. # remove successive blanks ( Mailenable does it )
  12601. $header_val =~ s/\s+/ /xgo;
  12602. # remove Message-Id value domain part ( Mailenable changes it )
  12603. if ( ( $messageidnodomain ) and ( 'MESSAGE-ID' eq uc $header_key ) ) { $header_val =~ s/^([^@]+).*$/$1/xo ; }
  12604. # and uppercase header line
  12605. # (dbmail and dovecot)
  12606. my $header_line = uc "$header_key: $header_val" ;
  12607. return( $header_line ) ;
  12608. }
  12609. sub tests_header_line_normalize
  12610. {
  12611. note( 'Entering tests_header_line_normalize()' ) ;
  12612. ok( ': ' eq header_line_normalize( q{}, q{} ), 'header_line_normalize: empty args' ) ;
  12613. ok( 'HHH: VVV' eq header_line_normalize( 'hhh', 'vvv' ), 'header_line_normalize: hhh vvv ' ) ;
  12614. ok( 'HHH: VVV' eq header_line_normalize( 'hhh', ' vvv' ), 'header_line_normalize: remove first blancs' ) ;
  12615. ok( 'HHH: AA BB CCC D' eq header_line_normalize( 'hhh', 'aa bb ccc d' ), 'header_line_normalize: remove succesive blanks' ) ;
  12616. ok( 'HHH: AA BB CCC' eq header_line_normalize( 'hhh', 'aa bb ccc ' ), 'header_line_normalize: remove last blanks' ) ;
  12617. ok( 'HHH: VVV XX YY' eq header_line_normalize( 'hhh', "vvv\t\txx\tyy" ), 'header_line_normalize: tabs' ) ;
  12618. ok( 'HHH: XABX' eq header_line_normalize( 'hhh', "\x80AB\xff" ), 'header_line_normalize: 8bit' ) ;
  12619. note( 'Leaving tests_header_line_normalize()' ) ;
  12620. return ;
  12621. }
  12622. sub tests_firstline
  12623. {
  12624. note( 'Entering tests_firstline()' ) ;
  12625. is( q{}, firstline( 'W/tmp/tests/noexist.txt' ), 'firstline: getting empty string from inexisting W/tmp/tests/noexist.txt' ) ;
  12626. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'firstline: mkpath W/tmp/tests/' ) ;
  12627. is( "blabla\n" , string_to_file( "blabla\n", 'W/tmp/tests/firstline.txt' ), 'firstline: put blabla in W/tmp/tests/firstline.txt' ) ;
  12628. is( 'blabla' , firstline( 'W/tmp/tests/firstline.txt' ), 'firstline: get blabla from W/tmp/tests/firstline.txt' ) ;
  12629. is( q{} , string_to_file( q{}, 'W/tmp/tests/firstline2.txt' ), 'firstline: put empty string in W/tmp/tests/firstline2.txt' ) ;
  12630. is( q{} , firstline( 'W/tmp/tests/firstline2.txt' ), 'firstline: get empty string from W/tmp/tests/firstline2.txt' ) ;
  12631. is( "\n" , string_to_file( "\n", 'W/tmp/tests/firstline3.txt' ), 'firstline: put CR in W/tmp/tests/firstline3.txt' ) ;
  12632. is( q{} , firstline( 'W/tmp/tests/firstline3.txt' ), 'firstline: get empty string from W/tmp/tests/firstline3.txt' ) ;
  12633. 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' ) ;
  12634. is( 'blabla' , firstline( 'W/tmp/tests/firstline4.txt' ), 'firstline: get blabla from W/tmp/tests/firstline4.txt' ) ;
  12635. note( 'Leaving tests_firstline()' ) ;
  12636. return ;
  12637. }
  12638. sub firstline
  12639. {
  12640. # extract the first line of a file (without \n)
  12641. # return empty string if error or empty string
  12642. my $file = shift @ARG ;
  12643. my $line ;
  12644. $line = nthline( $file, 1 ) ;
  12645. return $line ;
  12646. }
  12647. sub tests_secondline
  12648. {
  12649. note( 'Entering tests_secondline()' ) ;
  12650. is( q{}, secondline( 'W/tmp/tests/noexist.txt' ), 'secondline: getting empty string from inexisting W/tmp/tests/noexist.txt' ) ;
  12651. is( q{}, secondline( 'W/tmp/tests/noexist.txt', 2 ), 'secondline: 2nd getting empty string from inexisting W/tmp/tests/noexist.txt' ) ;
  12652. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'secondline: mkpath W/tmp/tests/' ) ;
  12653. 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' ) ;
  12654. is( 'L2' , secondline( 'W/tmp/tests/secondline.txt' ), 'secondline: get L2 from W/tmp/tests/secondline.txt' ) ;
  12655. note( 'Leaving tests_secondline()' ) ;
  12656. return ;
  12657. }
  12658. sub secondline
  12659. {
  12660. # extract the second line of a file (without \n)
  12661. # return empty string if error or empty string
  12662. my $file = shift @ARG ;
  12663. my $line ;
  12664. $line = nthline( $file, 2 ) ;
  12665. return $line ;
  12666. }
  12667. sub tests_nthline
  12668. {
  12669. note( 'Entering tests_nthline()' ) ;
  12670. is( q{}, nthline( 'W/tmp/tests/noexist.txt' ), 'nthline: getting empty string from inexisting W/tmp/tests/noexist.txt' ) ;
  12671. is( q{}, nthline( 'W/tmp/tests/noexist.txt', 2 ), 'nthline: 2nd getting empty string from inexisting W/tmp/tests/noexist.txt' ) ;
  12672. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'nthline: mkpath W/tmp/tests/' ) ;
  12673. 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' ) ;
  12674. is( 'L3' , nthline( 'W/tmp/tests/nthline.txt', 3 ), 'nthline: get L3 from W/tmp/tests/nthline.txt' ) ;
  12675. note( 'Leaving tests_nthline()' ) ;
  12676. return ;
  12677. }
  12678. sub nthline
  12679. {
  12680. # extract the nth line of a file (without \n)
  12681. # return empty string if error or empty string
  12682. my $file = shift @ARG ;
  12683. my $num = shift @ARG ;
  12684. if ( ! all_defined( $file, $num ) ) { return q{} ; }
  12685. my $line ;
  12686. $line = ( file_to_array( $file ) )[$num - 1] ;
  12687. if ( ! defined $line )
  12688. {
  12689. return q{} ;
  12690. }
  12691. else
  12692. {
  12693. chomp $line ;
  12694. return $line ;
  12695. }
  12696. }
  12697. sub tests_file_to_array
  12698. {
  12699. note( 'Entering tests_file_to_array()' ) ;
  12700. is( undef, file_to_array( ), 'file_to_array: no args => undef' ) ;
  12701. is( undef, file_to_array( '/noexist' ), 'file_to_array: /noexist => undef' ) ;
  12702. is( undef, file_to_array( '/' ), 'file_to_array: reading a directory => undef' ) ;
  12703. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'file_to_array: mkpath W/tmp/tests/' ) ;
  12704. 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' ) ;
  12705. 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' ) ;
  12706. note( 'Leaving tests_file_to_array()' ) ;
  12707. return ;
  12708. }
  12709. sub file_to_array
  12710. {
  12711. my( $file ) = shift @ARG ;
  12712. if ( ! $file ) { return ; }
  12713. if ( ! -e $file ) { return ; }
  12714. if ( ! -f $file ) { return ; }
  12715. if ( ! -r $file ) { return ; }
  12716. my @string ;
  12717. if ( open my $FILE, '<', $file )
  12718. {
  12719. @string = <$FILE> ;
  12720. close $FILE ;
  12721. return( @string ) ;
  12722. }
  12723. else
  12724. {
  12725. myprint( "Error reading file $file : $OS_ERROR\n" ) ;
  12726. return ;
  12727. }
  12728. }
  12729. sub tests_file_to_string
  12730. {
  12731. note( 'Entering tests_file_to_string()' ) ;
  12732. is( undef, file_to_string( ), 'file_to_string: no args => undef' ) ;
  12733. is( undef, file_to_string( '/noexist' ), 'file_to_string: /noexist => undef' ) ;
  12734. is( undef, file_to_string( '/' ), 'file_to_string: reading a directory => undef' ) ;
  12735. ok( file_to_string( $PROGRAM_NAME ), 'file_to_string: reading myself' ) ;
  12736. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'file_to_string: mkpath W/tmp/tests/' ) ;
  12737. is( 'lilili', string_to_file( 'lilili', 'W/tmp/tests/canbewritten' ), 'file_to_string: string_to_file filling W/tmp/tests/canbewritten with lilili' ) ;
  12738. is( 'lilili', file_to_string( 'W/tmp/tests/canbewritten' ), 'file_to_string: reading W/tmp/tests/canbewritten is lilili' ) ;
  12739. is( q{}, string_to_file( q{}, 'W/tmp/tests/empty' ), 'file_to_string: string_to_file filling W/tmp/tests/empty with empty string' ) ;
  12740. is( q{}, file_to_string( 'W/tmp/tests/empty' ), 'file_to_string: reading W/tmp/tests/empty is empty' ) ;
  12741. note( 'Leaving tests_file_to_string()' ) ;
  12742. return ;
  12743. }
  12744. sub file_to_string
  12745. {
  12746. my $file = shift @ARG ;
  12747. if ( ! $file ) { return ; }
  12748. if ( ! -e $file ) { return ; }
  12749. if ( ! -f $file ) { return ; }
  12750. if ( ! -r $file ) { return ; }
  12751. return( join q{}, file_to_array( $file ) ) ;
  12752. }
  12753. sub tests_string_to_file
  12754. {
  12755. note( 'Entering tests_string_to_file()' ) ;
  12756. is( undef, string_to_file( ), 'string_to_file: no args => undef' ) ;
  12757. is( undef, string_to_file( 'lalala' ), 'string_to_file: one arg => undef' ) ;
  12758. is( undef, string_to_file( 'lalala', '.' ), 'string_to_file: writing a directory => undef' ) ;
  12759. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'string_to_file: mkpath W/tmp/tests/' ) ;
  12760. is( 'lalala', string_to_file( 'lalala', 'W/tmp/tests/canbewritten' ), 'string_to_file: W/tmp/tests/canbewritten with lalala' ) ;
  12761. is( q{}, string_to_file( q{}, 'W/tmp/tests/empty' ), 'string_to_file: W/tmp/tests/empty with empty string' ) ;
  12762. SKIP: {
  12763. Readonly my $NB_UNX_tests_string_to_file => 1 ;
  12764. skip( 'Not on Unix non-root', $NB_UNX_tests_string_to_file ) if ('MSWin32' eq $OSNAME or '0' eq $EFFECTIVE_USER_ID ) ;
  12765. is( undef, string_to_file( 'lalala', '/cantouch' ), 'string_to_file: /cantouch denied => undef' ) ;
  12766. }
  12767. note( 'Leaving tests_string_to_file()' ) ;
  12768. return ;
  12769. }
  12770. sub string_to_file
  12771. {
  12772. my( $string, $file ) = @_ ;
  12773. if( ! defined $string ) { return ; }
  12774. if( ! defined $file ) { return ; }
  12775. if ( ! -e $file && ! -w dirname( $file ) ) {
  12776. myprint( "string_to_file: directory of $file is not writable\n" ) ;
  12777. return ;
  12778. }
  12779. if ( ! sysopen( FILE, $file, O_WRONLY|O_TRUNC|O_CREAT, 0600) ) {
  12780. myprint( "string_to_file: failure writing to $file with error: $OS_ERROR\n" ) ;
  12781. return ;
  12782. }
  12783. print FILE $string ;
  12784. close FILE ;
  12785. return $string ;
  12786. }
  12787. 0 and <<'MULTILINE_COMMENT' ;
  12788. This is a multiline comment.
  12789. Based on David Carter discussion, to do:
  12790. * Call parameters stay the same.
  12791. * Now always "return( $string, $error )". Descriptions below.
  12792. OK * Still capture STDOUT via "1> $output_tmpfile" to finish in $string and "return( $string, $error )"
  12793. OK * Now also capture STDERR via "2> $error_tmpfile" to finish in $error and "return( $string, $error )"
  12794. OK * in case of CHILD_ERROR, return( undef, $error )
  12795. and print $error, with folder/UID/maybeSubject context,
  12796. on console and at the end with the final error listing. Count this as a sync error.
  12797. * in case of good command, take final $string as is, unless void. In case $error with value then print it.
  12798. * in case of good command and final $string empty, consider it like CHILD_ERROR =>
  12799. return( undef, $error ) and print $error, with folder/UID/maybeSubject context,
  12800. on console and at the end with the final error listing. Count this as a sync error.
  12801. MULTILINE_COMMENT
  12802. # End of multiline comment.
  12803. sub pipemess
  12804. {
  12805. my ( $string, @commands ) = @_ ;
  12806. my $error = q{} ;
  12807. foreach my $command ( @commands ) {
  12808. my $input_tmpfile = "$sync->{ tmpdir }/imapsync_tmp_file.$PROCESS_ID.inp.txt" ;
  12809. my $output_tmpfile = "$sync->{ tmpdir }/imapsync_tmp_file.$PROCESS_ID.out.txt" ;
  12810. my $error_tmpfile = "$sync->{ tmpdir }/imapsync_tmp_file.$PROCESS_ID.err.txt" ;
  12811. string_to_file( $string, $input_tmpfile ) ;
  12812. ` $command < $input_tmpfile 1> $output_tmpfile 2> $error_tmpfile ` ;
  12813. my $is_command_ko = $CHILD_ERROR ;
  12814. my $error_cmd = file_to_string( $error_tmpfile ) ;
  12815. chomp( $error_cmd ) ;
  12816. $string = file_to_string( $output_tmpfile ) ;
  12817. my $string_len = length( $string ) ;
  12818. unlink $input_tmpfile, $output_tmpfile, $error_tmpfile ;
  12819. if ( $is_command_ko or ( ! $string_len ) ) {
  12820. my $cmd_exit_value = $CHILD_ERROR >> 8 ;
  12821. my $cmd_end_signal = $CHILD_ERROR & 127 ;
  12822. my $signal_log = ( $cmd_end_signal ) ? " signal $cmd_end_signal and" : q{} ;
  12823. 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} ;
  12824. myprint( $error_log ) ;
  12825. if ( wantarray ) {
  12826. return @{ [ undef, $error_log ] }
  12827. }else{
  12828. return ;
  12829. }
  12830. }
  12831. if ( $error_cmd ) {
  12832. $error .= qq{STDERR of --pipemess "$command": $error_cmd\n} ;
  12833. myprint( qq{STDERR of --pipemess "$command": $error_cmd\n} ) ;
  12834. }
  12835. }
  12836. #myprint( "[$string]\n" ) ;
  12837. if ( wantarray ) {
  12838. return ( $string, $error ) ;
  12839. }else{
  12840. return $string ;
  12841. }
  12842. }
  12843. sub tests_pipemess
  12844. {
  12845. note( 'Entering tests_pipemess()' ) ;
  12846. SKIP: {
  12847. Readonly my $NB_WIN_tests_pipemess => 3 ;
  12848. skip( 'Not on MSWin32', $NB_WIN_tests_pipemess ) if ('MSWin32' ne $OSNAME) ;
  12849. # Windows
  12850. # "type" command does not accept redirection of STDIN with <
  12851. # "sort" does
  12852. ok( "nochange\n" eq pipemess( 'nochange', 'sort' ), 'pipemess: nearly no change by sort' ) ;
  12853. ok( "nochange2\n" eq pipemess( 'nochange2', qw( sort sort ) ), 'pipemess: nearly no change by sort,sort' ) ;
  12854. # command not found
  12855. #diag( 'Warning and failure about cacaprout are on purpose' ) ;
  12856. ok( ! defined( pipemess( q{}, 'cacaprout' ) ), 'pipemess: command not found' ) ;
  12857. } ;
  12858. my ( $stringT, $errorT ) ;
  12859. SKIP: {
  12860. Readonly my $NB_UNX_tests_pipemess => 25 ;
  12861. skip( 'Not on Unix', $NB_UNX_tests_pipemess ) if ('MSWin32' eq $OSNAME) ;
  12862. # Unix
  12863. ok( 'nochange' eq pipemess( 'nochange', 'cat' ), 'pipemess: no change by cat' ) ;
  12864. ok( 'nochange2' eq pipemess( 'nochange2', 'cat', 'cat' ), 'pipemess: no change by cat,cat' ) ;
  12865. ok( " 1\tnumberize\n" eq pipemess( "numberize\n", 'cat -n' ), 'pipemess: numberize by cat -n' ) ;
  12866. ok( " 1\tnumberize\n 2\tnumberize\n" eq pipemess( "numberize\nnumberize\n", 'cat -n' ), 'pipemess: numberize by cat -n' ) ;
  12867. ok( "A\nB\nC\n" eq pipemess( "A\nC\nB\n", 'sort' ), 'pipemess: sort' ) ;
  12868. # command not found
  12869. #diag( 'Warning and failure about cacaprout are on purpose' ) ;
  12870. is( undef, pipemess( q{}, 'cacaprout' ), 'pipemess: command not found' ) ;
  12871. # success with true but no output at all
  12872. is( undef, pipemess( q{blabla}, 'true' ), 'pipemess: true but no output' ) ;
  12873. # failure with false and no output at all
  12874. is( undef, pipemess( q{blabla}, 'false' ), 'pipemess: false and no output' ) ;
  12875. # Failure since pipemess is not a real pipe, so first cat wait for standard input
  12876. is( q{blabla}, pipemess( q{blabla}, '( cat|cat ) ' ), 'pipemess: ok by ( cat|cat )' ) ;
  12877. ( $stringT, $errorT ) = pipemess( 'nochange', 'cat' ) ;
  12878. is( $stringT, 'nochange', 'pipemess: list context, no change by cat, string' ) ;
  12879. is( $errorT, q{}, 'pipemess: list context, no change by cat, no error' ) ;
  12880. ( $stringT, $errorT ) = pipemess( 'dontcare', 'true' ) ;
  12881. is( $stringT, undef, 'pipemess: list context, true but no output, string' ) ;
  12882. 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' ) ;
  12883. ( $stringT, $errorT ) = pipemess( 'dontcare', 'false' ) ;
  12884. is( $stringT, undef, 'pipemess: list context, false and no output, string' ) ;
  12885. like( $errorT, qr{\QFailure: --pipemess command "false" ended with "0" characters exit value "1" and STDERR ""\E}xm,
  12886. 'pipemess: list context, false and no output, error' ) ;
  12887. ( $stringT, $errorT ) = pipemess( 'dontcare', '/bin/echo -n blablabla' ) ;
  12888. is( $stringT, q{blablabla}, 'pipemess: list context, "echo -n blablabla", string' ) ;
  12889. is( $errorT, q{}, 'pipemess: list context, "echo blablabla", error' ) ;
  12890. ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo -n blablabla 3>&1 1>&2 2>&3 )' ) ;
  12891. is( $stringT, undef, 'pipemess: list context, "no output STDERR blablabla", string' ) ;
  12892. like( $errorT, qr{blablabla"}xm, 'pipemess: list context, "no output STDERR blablabla", error' ) ;
  12893. ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo -n blablabla 3>&1 1>&2 2>&3 )', 'false' ) ;
  12894. is( $stringT, undef, 'pipemess: list context, "no output STDERR blablabla then false", string' ) ;
  12895. like( $errorT, qr{blablabla"}xm, 'pipemess: list context, "no output STDERR blablabla then false", error' ) ;
  12896. ( $stringT, $errorT ) = pipemess( 'dontcare', 'false', '( echo -n blablabla 3>&1 1>&2 2>&3 )' ) ;
  12897. is( $stringT, undef, 'pipemess: list context, "false then STDERR blablabla", string' ) ;
  12898. like( $errorT, qr{\QFailure: --pipemess command "false" ended with "0" characters exit value "1" and STDERR ""\E}xm,
  12899. 'pipemess: list context, "false then STDERR blablabla", error' ) ;
  12900. ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo rrrrr ; echo -n error_blablabla 3>&1 1>&2 2>&3 )' ) ;
  12901. like( $stringT, qr{rrrrr}xm, 'pipemess: list context, "STDOUT rrrrr STDERR error_blablabla", string' ) ;
  12902. like( $errorT, qr{STDERR.*error_blablabla}xm, 'pipemess: list context, "STDOUT rrrrr STDERR error_blablabla", error' ) ;
  12903. }
  12904. ( $stringT, $errorT ) = pipemess( 'dontcare', 'cacaprout' ) ;
  12905. is( $stringT, undef, 'pipemess: list context, cacaprout not found, string' ) ;
  12906. like( $errorT, qr{\QFailure: --pipemess command "cacaprout" ended with "0" characters exit value\E}xm,
  12907. 'pipemess: list context, cacaprout not found, error' ) ;
  12908. note( 'Leaving tests_pipemess()' ) ;
  12909. return ;
  12910. }
  12911. sub tests_is_a_release_number
  12912. {
  12913. note( 'Entering tests_is_a_release_number()' ) ;
  12914. is( undef, is_a_release_number( ), 'is_a_release_number: no args => undef' ) ;
  12915. ok( is_a_release_number( $RELEASE_NUMBER_EXAMPLE_1 ), 'is_a_release_number 1.351' ) ;
  12916. ok( is_a_release_number( $RELEASE_NUMBER_EXAMPLE_2 ), 'is_a_release_number 42.4242' ) ;
  12917. ok( is_a_release_number( imapsync_version( $sync ) ), 'is_a_release_number imapsync_version( )' ) ;
  12918. ok( ! is_a_release_number( 'blabla' ), '! is_a_release_number blabla' ) ;
  12919. note( 'Leaving tests_is_a_release_number()' ) ;
  12920. return ;
  12921. }
  12922. sub is_a_release_number
  12923. {
  12924. my $number = shift @ARG ;
  12925. if ( ! defined $number ) { return ; }
  12926. return( $number =~ m{^\d+\.\d+$}xo ) ;
  12927. }
  12928. sub imapsync_version_public
  12929. {
  12930. my $local_version = imapsync_version( $sync ) ;
  12931. my $imapsync_basename = imapsync_basename( ) ;
  12932. my $context = imapsync_context( ) ;
  12933. my $agent_info = "$OSNAME system, perl "
  12934. . mysprintf( '%vd', $PERL_VERSION)
  12935. . ", Mail::IMAPClient $Mail::IMAPClient::VERSION"
  12936. . " $imapsync_basename"
  12937. . " $context" ;
  12938. my $sock = IO::Socket::INET->new(
  12939. PeerAddr => 'imapsync.lamiral.info',
  12940. PeerPort => 80,
  12941. Proto => 'tcp',
  12942. ) ;
  12943. return( 'unknown' ) if not $sock ;
  12944. print $sock
  12945. "GET /prj/imapsync/VERSION HTTP/1.0\r\n",
  12946. "User-Agent: imapsync/$local_version ($agent_info)\r\n",
  12947. "Host: ks.lamiral.info\r\n\r\n" ;
  12948. my @line = <$sock> ;
  12949. close $sock ;
  12950. my $last_release = $line[$LAST] ;
  12951. chomp $last_release ;
  12952. return( $last_release ) ;
  12953. }
  12954. sub not_long_imapsync_version_public
  12955. {
  12956. #myprint( "Entering not_long_imapsync_version_public\n" ) ;
  12957. my $fake = shift @ARG ;
  12958. if ( $fake ) { return $fake }
  12959. my $val ;
  12960. # Doesn't work with gethostbyname (see perlipc)
  12961. #local $SIG{ALRM} = sub { die "alarm\n" } ;
  12962. if ('MSWin32' eq $OSNAME) {
  12963. local $SIG{ALRM} = sub { die "alarm\n" } ;
  12964. }else{
  12965. POSIX::sigaction(SIGALRM,
  12966. POSIX::SigAction->new(sub { croak 'alarm' } ) )
  12967. or myprint( "Error setting SIGALRM handler: $OS_ERROR\n" ) ;
  12968. }
  12969. my $ret = eval {
  12970. alarm 3 ;
  12971. {
  12972. $val = imapsync_version_public( ) ;
  12973. #sleep 4 ;
  12974. #myprint( "End of imapsync_version_public\n" ) ;
  12975. }
  12976. alarm 0 ;
  12977. 1 ;
  12978. } ;
  12979. #myprint( "eval [$ret]\n" ) ;
  12980. if ( ( not $ret ) or $EVAL_ERROR ) {
  12981. #myprint( "$EVAL_ERROR" ) ;
  12982. if ($EVAL_ERROR =~ /alarm/) {
  12983. # timed out
  12984. return('timeout') ;
  12985. }else{
  12986. alarm 0 ;
  12987. return( 'unknown' ) ; # propagate unexpected errors
  12988. }
  12989. }else {
  12990. # Good!
  12991. return( $val ) ;
  12992. }
  12993. }
  12994. sub tests_not_long_imapsync_version_public
  12995. {
  12996. note( 'Entering tests_not_long_imapsync_version_public()' ) ;
  12997. is( 1, is_a_release_number( not_long_imapsync_version_public( ) ),
  12998. 'not_long_imapsync_version_public: public release is a number' ) ;
  12999. note( 'Leaving tests_not_long_imapsync_version_public()' ) ;
  13000. return ;
  13001. }
  13002. sub check_last_release
  13003. {
  13004. my $fake = shift @ARG ;
  13005. my $public_release = not_long_imapsync_version_public( $fake ) ;
  13006. $sync->{ debug } and myprint( "check_last_release: [$public_release]\n" ) ;
  13007. my $inline_help_when_on = '( Use --noreleasecheck to avoid this release check. )' ;
  13008. if ( $public_release eq 'unknown' ) {
  13009. return( 'Imapsync public release is unknown.' . $inline_help_when_on ) ;
  13010. }
  13011. if ( $public_release eq 'timeout' ) {
  13012. return( 'Imapsync public release is unknown (timeout).' . $inline_help_when_on ) ;
  13013. }
  13014. if ( ! is_a_release_number( $public_release ) ) {
  13015. return( "Imapsync public release is unknown ($public_release)." . $inline_help_when_on ) ;
  13016. }
  13017. my $imapsync_here = imapsync_version( $sync ) ;
  13018. if ( $public_release > $imapsync_here ) {
  13019. return( 'This imapsync is not up to date. ' . "( local $imapsync_here < official $public_release )" . $inline_help_when_on ) ;
  13020. }else{
  13021. return( 'This imapsync is up to date. ' . "( local $imapsync_here >= official $public_release )" . $inline_help_when_on ) ;
  13022. }
  13023. return( 'really unknown' ) ; # Should never arrive here
  13024. }
  13025. sub tests_check_last_release
  13026. {
  13027. note( 'Entering tests_check_last_release()' ) ;
  13028. diag( check_last_release( 1.1 ) ) ;
  13029. # \Q \E here to avoid putting \ before each space
  13030. like( check_last_release( 1.1 ), qr/\Qis up to date\E/mxs, 'check_last_release: up to date' ) ;
  13031. like( check_last_release( 1.1 ), qr/1\.1/mxs, 'check_last_release: up to date, include number' ) ;
  13032. diag( check_last_release( 999.999 ) ) ;
  13033. like( check_last_release( 999.999 ), qr/\Qnot up to date\E/mxs, 'check_last_release: not up to date' ) ;
  13034. like( check_last_release( 999.999 ), qr/999\.999/mxs, 'check_last_release: not up to date, include number' ) ;
  13035. like( check_last_release( 'unknown' ), qr/\QImapsync public release is unknown\E/mxs, 'check_last_release: unknown' ) ;
  13036. like( check_last_release( 'timeout' ), qr/\QImapsync public release is unknown (timeout)\E/mxs, 'check_last_release: timeout' ) ;
  13037. like( check_last_release( 'lalala' ), qr/\QImapsync public release is unknown (lalala)\E/mxs, 'check_last_release: lalala' ) ;
  13038. diag( check_last_release( ) ) ;
  13039. note( 'Leaving tests_check_last_release()' ) ;
  13040. return ;
  13041. }
  13042. sub tests_imapsync_context
  13043. {
  13044. note( 'Entering tests_imapsync_context()' ) ;
  13045. like( imapsync_context( ), qr/^CGI|^Docker|^DockerCGI|^Standard/, 'imapsync_context: CGI or Docker or DockerCGI or Standard' ) ;
  13046. note( 'Leaving tests_imapsync_context()' ) ;
  13047. return ;
  13048. }
  13049. sub imapsync_context
  13050. {
  13051. my $mysync = shift @ARG ;
  13052. my $context = q{} ;
  13053. if ( under_docker_context( $mysync ) && under_cgi_context( $mysync ) )
  13054. {
  13055. $context = 'DockerCGI' ;
  13056. }
  13057. elsif ( under_docker_context( $mysync ) )
  13058. {
  13059. $context = 'Docker' ;
  13060. }
  13061. elsif ( under_cgi_context( $mysync ) )
  13062. {
  13063. $context = 'CGI' ;
  13064. }
  13065. else
  13066. {
  13067. $context = 'Standard' ;
  13068. }
  13069. return $context ;
  13070. }
  13071. sub imapsync_version
  13072. {
  13073. my $mysync = shift @ARG ;
  13074. my $rcs = $mysync->{rcs} ;
  13075. my $version ;
  13076. $version = version_from_rcs( $rcs ) ;
  13077. return( $version ) ;
  13078. }
  13079. sub tests_version_from_rcs
  13080. {
  13081. note( 'Entering tests_version_from_rcs()' ) ;
  13082. is( undef, version_from_rcs( ), 'version_from_rcs: no args => undef' ) ;
  13083. 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' ) ;
  13084. is( 'UNKNOWN', version_from_rcs( 1.831 ), 'version_from_rcs: 1.831 => UNKNOWN' ) ;
  13085. note( 'Leaving tests_version_from_rcs()' ) ;
  13086. return ;
  13087. }
  13088. sub version_from_rcs
  13089. {
  13090. my $rcs = shift @ARG ;
  13091. if ( ! $rcs ) { return ; }
  13092. my $version = 'UNKNOWN' ;
  13093. if ( $rcs =~ m{,v\s+(\d+\.\d+)}mxso ) {
  13094. $version = $1
  13095. }
  13096. return( $version ) ;
  13097. }
  13098. sub tests_imapsync_basename
  13099. {
  13100. note( 'Entering tests_imapsync_basename()' ) ;
  13101. ok( imapsync_basename() =~ m/imapsync/, 'imapsync_basename: match imapsync');
  13102. ok( 'blabla' ne imapsync_basename(), 'imapsync_basename: do not equal blabla');
  13103. note( 'Leaving tests_imapsync_basename()' ) ;
  13104. return ;
  13105. }
  13106. sub imapsync_basename
  13107. {
  13108. return basename( $PROGRAM_NAME ) ;
  13109. }
  13110. sub localhost_info
  13111. {
  13112. my $mysync = shift @ARG ;
  13113. my( $infos ) = join( q{},
  13114. "Here is imapsync ", imapsync_version( $mysync ),
  13115. " on host " . hostname(),
  13116. ", a $OSNAME system with ",
  13117. ram_memory_info( $mysync ),
  13118. "\n",
  13119. 'with Perl ',
  13120. mysprintf( '%vd ', $PERL_VERSION),
  13121. "and Mail::IMAPClient $Mail::IMAPClient::VERSION",
  13122. ) ;
  13123. return( $infos ) ;
  13124. }
  13125. sub tests_cpu_number
  13126. {
  13127. note( 'Entering tests_cpu_number()' ) ;
  13128. is( 1, is_integer( cpu_number( ) ), "cpu_number: is_integer" ) ;
  13129. ok( 1 <= cpu_number( ), "cpu_number: 1 or more" ) ;
  13130. is( 1, cpu_number( 1 ), "cpu_number: 1 => 1" ) ;
  13131. is( 1, cpu_number( $MINUS_ONE ), "cpu_number: -1 => 1" ) ;
  13132. is( 1, cpu_number( 'lalala' ), "cpu_number: lalala => 1" ) ;
  13133. is( $NUMBER_42, cpu_number( $NUMBER_42 ), "cpu_number: $NUMBER_42 => $NUMBER_42" ) ;
  13134. note( "cpu_number = " . cpu_number( ) . "\n" ) ;
  13135. note( "hostname = " . hostname( ) . "\n" ) ;
  13136. SKIP: {
  13137. if ( ! ( 'i005' eq hostname() ) )
  13138. {
  13139. skip( 'cpu_number on host != i005 (FreeBSD)', 1 ) ;
  13140. }
  13141. is( 4, cpu_number( ), "cpu_number: on i005 (FreeBSD) => 4" ) ;
  13142. } ;
  13143. SKIP: {
  13144. if ( ! ( 'petite' eq hostname() ) )
  13145. {
  13146. skip( 'cpu_number on host != petite (Linux)', 1 ) ;
  13147. }
  13148. is( 2, cpu_number( ), "cpu_number: on petite (Linux) => 2" ) ;
  13149. #is( 1, cpu_number( ), "cpu_number: on petite (Linux) => 2" ) ;
  13150. } ;
  13151. SKIP: {
  13152. if ( ! ( skip_macosx( ) ) )
  13153. {
  13154. skip( 'cpu_number on host != polarhome macosx (Darwin MacOS X 10.7.5 Lion)', 1 ) ;
  13155. }
  13156. is( 2, cpu_number( ), "cpu_number: on polarhome macosx (Darwin MacOS X 10.7.5 Lion) => 2" ) ;
  13157. } ;
  13158. SKIP: {
  13159. if ( ! ( 'pcHPDV7-HP' eq hostname() ) )
  13160. {
  13161. skip( 'cpu_number on host != pcHPDV7-HP (Windows 7, 64bits)', 1 ) ;
  13162. }
  13163. is( 2, cpu_number( ), "cpu_number: on pcHPDV7-HP (Windows 7, 64bits) => 2" ) ;
  13164. } ;
  13165. SKIP: {
  13166. if ( ! ( 'CUILLERE' eq hostname() ) )
  13167. {
  13168. skip( 'cpu_number on host != CUILLERE (Windows XP, 32bits)', 1 ) ;
  13169. }
  13170. is( 1, cpu_number( ), "cpu_number: on CUILLERE (Windows XP, 32bits) => 1" ) ;
  13171. } ;
  13172. note( 'Leaving tests_cpu_number()' ) ;
  13173. return ;
  13174. }
  13175. sub cpu_number {
  13176. my $cpu_number_forced = shift @ARG ;
  13177. # Well, here 1 is better than 0 or undef
  13178. my $cpu_number = 1 ; # Default value, erased if better found
  13179. my @cpuinfo ;
  13180. if ( $ENV{"NUMBER_OF_PROCESSORS"} )
  13181. {
  13182. # might be under a Windows system
  13183. $cpu_number = $ENV{"NUMBER_OF_PROCESSORS"} ;
  13184. #myprint( "Number of processors found by env var NUMBER_OF_PROCESSORS: $cpu_number\n" ) ;
  13185. }
  13186. if ( 'darwin' eq $OSNAME )
  13187. {
  13188. $cpu_number = backtick( "sysctl -n hw.ncpu" ) ;
  13189. chomp( $cpu_number ) ;
  13190. #myprint( "Number of processors found by cmd 'sysctl -n hw.ncpu': $cpu_number\n" ) ;
  13191. }
  13192. if ( 'freebsd' eq $OSNAME )
  13193. {
  13194. $cpu_number = backtick( "sysctl -n kern.smp.cpus" ) ;
  13195. chomp( $cpu_number ) ;
  13196. #myprint( "Number of processors found by cmd 'sysctl -n kern.smp.cpus': $cpu_number\n" ) ;
  13197. }
  13198. if ( 'linux' eq $OSNAME && -e '/proc/cpuinfo' )
  13199. {
  13200. @cpuinfo = file_to_array( '/proc/cpuinfo' ) ;
  13201. $cpu_number = grep { /^processor/mxs } @cpuinfo ;
  13202. #myprint( "Number of processors found via /proc/cpuinfo: $cpu_number\n" ) ;
  13203. }
  13204. if ( defined $cpu_number_forced )
  13205. {
  13206. $cpu_number = $cpu_number_forced ;
  13207. }
  13208. return( integer_or_1( $cpu_number ) ) ;
  13209. }
  13210. sub tests_integer_or_1
  13211. {
  13212. note( 'Entering tests_integer_or_1()' ) ;
  13213. is( 1, integer_or_1( ), 'integer_or_1: no args => 1' ) ;
  13214. is( 1, integer_or_1( undef ), 'integer_or_1: undef => 1' ) ;
  13215. is( $NUMBER_10, integer_or_1( $NUMBER_10 ), 'integer_or_1: 10 => 10' ) ;
  13216. is( 1, integer_or_1( q{} ), 'integer_or_1: empty string => 1' ) ;
  13217. is( 1, integer_or_1( 'lalala' ), 'integer_or_1: lalala => 1' ) ;
  13218. note( 'Leaving tests_integer_or_1()' ) ;
  13219. return ;
  13220. }
  13221. sub integer_or_1
  13222. {
  13223. my $number = shift @ARG ;
  13224. if ( is_integer( $number ) ) {
  13225. return $number ;
  13226. }
  13227. # else
  13228. return 1 ;
  13229. }
  13230. sub tests_is_integer
  13231. {
  13232. note( 'Entering tests_is_integer()' ) ;
  13233. is( undef, is_integer( ), 'is_integer: no args => undef ' ) ;
  13234. ok( is_integer( 1 ), 'is_integer: 1 => yes ') ;
  13235. ok( is_integer( $NUMBER_42 ), 'is_integer: 42 => yes ') ;
  13236. ok( is_integer( "$NUMBER_42" ), 'is_integer: "$NUMBER_42" => yes ') ;
  13237. ok( is_integer( '42' ), 'is_integer: "42" => yes ') ;
  13238. ok( is_integer( $NUMBER_104_857_600 ), 'is_integer: 104_857_600 => yes') ;
  13239. ok( is_integer( "$NUMBER_104_857_600" ), 'is_integer: "$NUMBER_104_857_600" => yes') ;
  13240. ok( is_integer( '104857600' ), 'is_integer: 104857600 => yes') ;
  13241. ok( ! is_integer( 'blabla' ), 'is_integer: blabla => no' ) ;
  13242. ok( ! is_integer( q{} ), 'is_integer: empty string => no' ) ;
  13243. note( 'Leaving tests_is_integer()' ) ;
  13244. return ;
  13245. }
  13246. sub is_integer
  13247. {
  13248. my $number = shift @ARG ;
  13249. if ( ! defined $number ) { return ; }
  13250. return( $number =~ m{^\d+$}xo ) ;
  13251. }
  13252. sub tests_loadavg
  13253. {
  13254. note( 'Entering tests_loadavg()' ) ;
  13255. SKIP: {
  13256. skip( 'Tests for darwin', 3 ) if ('darwin' ne $OSNAME) ;
  13257. is( undef, loadavg( '/noexist' ), 'loadavg: /noexist => undef' ) ;
  13258. is_deeply(
  13259. [ '0.11', '0.22', '0.33' ],
  13260. [ loadavg( 'vm.loadavg: { 0.11 0.22 0.33 }' ) ],
  13261. 'loadavg: "vm.loadavg: { 0.11 0.22 0.33 }" => 0.11 0.22 0.33'
  13262. ) ;
  13263. note( join( " ", "loadavg:", loadavg( ) ) ) ;
  13264. is( 3, scalar( my @loadavg = loadavg( ) ), 'loadavg: 3 values' ) ;
  13265. } ;
  13266. SKIP: {
  13267. skip( 'Tests for linux', 3 ) if ('linux' ne $OSNAME) ;
  13268. is( undef, loadavg( '/noexist' ), 'loadavg: /noexist => undef' ) ;
  13269. ok( loadavg( ), 'loadavg: no args' ) ;
  13270. is_deeply( [ '0.39', '0.30', '0.37', '1/602' ],
  13271. [ loadavg( '0.39 0.30 0.37 1/602 6073' ) ],
  13272. 'loadavg 0.39 0.30 0.37 1/602 6073 => [0.39, 0.30, 0.37, 1/602]' ) ;
  13273. } ;
  13274. SKIP: {
  13275. skip( 'Tests for Windows', 1 ) if ('MSWin32' ne $OSNAME) ;
  13276. is_deeply( [ 0 ],
  13277. [ loadavg( ) ],
  13278. 'loadavg on MSWin32 => 0' ) ;
  13279. } ;
  13280. note( 'Leaving tests_loadavg()' ) ;
  13281. return ;
  13282. }
  13283. sub loadavg
  13284. {
  13285. if ( 'linux' eq $OSNAME ) {
  13286. return ( loadavg_linux( @ARG ) ) ;
  13287. }
  13288. if ( 'freebsd' eq $OSNAME ) {
  13289. return ( loadavg_freebsd( @ARG ) ) ;
  13290. }
  13291. if ( 'darwin' eq $OSNAME ) {
  13292. return ( loadavg_darwin( @ARG ) ) ;
  13293. }
  13294. if ( 'MSWin32' eq $OSNAME ) {
  13295. return ( loadavg_windows( @ARG ) ) ;
  13296. }
  13297. return( 'unknown' ) ;
  13298. }
  13299. sub loadavg_linux
  13300. {
  13301. my $line = shift @ARG ;
  13302. if ( ! $line ) {
  13303. $line = firstline( '/proc/loadavg' ) or return ;
  13304. }
  13305. my ( $avg_1_min, $avg_5_min, $avg_15_min, $current_runs ) = split /\s/mxs, $line ;
  13306. if ( all_defined( $avg_1_min, $avg_5_min, $avg_15_min ) ) {
  13307. $sync->{ debug } and myprint( "System load: $avg_1_min $avg_5_min $avg_15_min $current_runs\n" ) ;
  13308. return ( $avg_1_min, $avg_5_min, $avg_15_min, $current_runs ) ;
  13309. }
  13310. return ;
  13311. }
  13312. sub loadavg_freebsd
  13313. {
  13314. my $file = shift @ARG ;
  13315. # Example of output of command "sysctl vm.loadavg":
  13316. # vm.loadavg: { 0.15 0.08 0.08 }
  13317. my $loadavg ;
  13318. if ( ! defined $file ) {
  13319. eval {
  13320. $loadavg = `LANG=C /sbin/sysctl vm.loadavg` ;
  13321. #myprint( "LOADAVG FREEBSD: $loadavg\n" ) ;
  13322. } ;
  13323. if ( $EVAL_ERROR ) { myprint( "[$EVAL_ERROR]\n" ) ; return ; }
  13324. }else{
  13325. $loadavg = firstline( $file ) or return ;
  13326. }
  13327. my ( $avg_1_min, $avg_5_min, $avg_15_min )
  13328. = $loadavg =~ /vm\.loadavg\s*[:=]\s*\{?\s*(\d+\.?\d*)\s+(\d+\.?\d*)\s+(\d+\.?\d*)/mxs ;
  13329. $sync->{ debug } and myprint( "System load: $avg_1_min $avg_5_min $avg_15_min\n" ) ;
  13330. return ( $avg_1_min, $avg_5_min, $avg_15_min ) ;
  13331. }
  13332. sub loadavg_darwin
  13333. {
  13334. my $line = shift @ARG ;
  13335. # Example of output of command "sysctl vm.loadavg":
  13336. # vm.loadavg: { 0.15 0.08 0.08 }
  13337. my $loadavg ;
  13338. if ( ! defined $line ) {
  13339. eval {
  13340. # $loadavg = `/usr/sbin/sysctl vm.loadavg` ;
  13341. $loadavg = `LANG=C /usr/sbin/sysctl vm.loadavg` ;
  13342. #myprint( "LOADAVG DARWIN: $loadavg\n" ) ;
  13343. } ;
  13344. if ( $EVAL_ERROR ) { myprint( "[$EVAL_ERROR]\n" ) ; return ; }
  13345. }else{
  13346. $loadavg = $line ;
  13347. }
  13348. my ( $avg_1_min, $avg_5_min, $avg_15_min )
  13349. = $loadavg =~ /vm\.loadavg\s*[:=]\s*\{?\s*(\d+\.?\d*)\s+(\d+\.?\d*)\s+(\d+\.?\d*)/mxs ;
  13350. #$sync->{ debug } and myprint( "System load: $avg_1_min $avg_5_min $avg_15_min\n" ) ;
  13351. return ( $avg_1_min, $avg_5_min, $avg_15_min ) ;
  13352. }
  13353. sub loadavg_windows
  13354. {
  13355. my $file = shift @ARG ;
  13356. # Example of output of command "wmic cpu get loadpercentage":
  13357. # LoadPercentage
  13358. # 12
  13359. my $loadavg ;
  13360. if ( ! defined $file ) {
  13361. eval {
  13362. #$loadavg = `CMD wmic cpu get loadpercentage` ;
  13363. $loadavg = "LoadPercentage\n0\n" ;
  13364. #myprint( "LOADAVG WIN: $loadavg\n" ) ;
  13365. } ;
  13366. if ( $EVAL_ERROR ) { myprint( "[$EVAL_ERROR]\n" ) ; return ; }
  13367. }else{
  13368. $loadavg = file_to_string( $file ) or return ;
  13369. #myprint( "$loadavg" ) ;
  13370. }
  13371. $loadavg =~ /LoadPercentage\n(\d+)/xms ;
  13372. my $num = $1 ;
  13373. $num /= 100 ;
  13374. $sync->{ debug } and myprint( "System load: $num\n" ) ;
  13375. return ( $num ) ;
  13376. }
  13377. sub tests_load_and_delay
  13378. {
  13379. note( 'Entering tests_load_and_delay()' ) ;
  13380. is( undef, load_and_delay( ), 'load_and_delay: no args => undef ' ) ;
  13381. is( undef, load_and_delay( 1, 2 ), 'load_and_delay: not 3 args => undef ' ) ;
  13382. is( undef, load_and_delay( 2, 0, 1, 1, 1 ), 'load_and_delay: division per 0 => undef ' ) ;
  13383. # ( $max_load_per_core, $cpu_num, $avg_1_min, $avg_5_min, $avg_15_min )
  13384. # load max = 1 per cpu
  13385. is( 0, load_and_delay( 1, 1, 0 ), 'load_and_delay: max=1, one core, loads is 0 => ok ' ) ;
  13386. is( 0, load_and_delay( 1, 1, 0, 0, 0 ), 'load_and_delay: max=1, one core, loads are all 0 => ok ' ) ;
  13387. is( 0, load_and_delay( 3, 1, 1, 1, 1, 1 ), 'load_and_delay: six arguments => ok' ) ;
  13388. is( 0, load_and_delay( 1, 2, 1, 1, 1 ), 'load_and_delay: max=1, two core, loads are all 1 => ok ' ) ;
  13389. is( 0, load_and_delay( 1, 2, 1, 4, 5 ), 'load_and_delay: max=1, two core, load1m is 1 => ok ' ) ;
  13390. is( 0, load_and_delay( 1, 1, 0, 0, 0 ), 'load_and_delay: max=1, one core, load1m=0 load5m=0 load15m=0 => 0 ' ) ;
  13391. is( 0, load_and_delay( 1, 1, 0, 0, 2 ), 'load_and_delay: max=1, one core, load1m=0 load5m=0 load15m=2 => 0 ' ) ;
  13392. is( 0, load_and_delay( 1, 1, 0, 2, 0 ), 'load_and_delay: max=1, one core, load1m=0 load5m=2 load15m=0 => 0 ' ) ;
  13393. is( 0, load_and_delay( 1, 1, 0, 2, 2 ), 'load_and_delay: max=1, one core, load1m=0 load5m=2 load15m=2 => 0 ' ) ;
  13394. is( 0, load_and_delay( 1, 1, 0, 3, 3 ), 'load_and_delay: max=1, one core, load1m=0 load5m=3 load15m=3 => 0 ' ) ;
  13395. is( 0, load_and_delay( 1, 1, 0, 4, 4 ), 'load_and_delay: max=1, one core, load1m=0 load5m=3 load15m=3 => 0 ' ) ;
  13396. is( 0, load_and_delay( 1, 1, .2, 0, 0 ), 'load_and_delay: max=1, one core, load1m=.2 load5m=0 load15m=0 => 0 ' ) ;
  13397. is( 0, load_and_delay( 1, 1, .2, 0, .2 ), 'load_and_delay: max=1, one core, load1m=.2 load5m=0 load15m=.2 => 0 ' ) ;
  13398. is( 0, load_and_delay( 1, 1, .2, .2, 0 ), 'load_and_delay: max=1, one core, load1m=2 load5m=2 load15m=0 => 0 ' ) ;
  13399. is( 0, load_and_delay( 1, 1, .2, .2, .2 ), 'load_and_delay: max=1, one core, load1m=.2 load5m=.2 load15m=.2 => 0 ' ) ;
  13400. is( 1, load_and_delay( 1, 1, 1, 0, 0 ), 'load_and_delay: max=1, one core, load1m=3 load5m=0 load15m=0 => 0 ' ) ;
  13401. is( 1, load_and_delay( 1, 1, 1, .9, .9 ), 'load_and_delay: max=1, one core, load1m=3 load5m=.9 load15m=.9 => 0 ' ) ;
  13402. is( 5, load_and_delay( 1, 1, 1, 1, .9 ), 'load_and_delay: max=1, one core, load1m=3 load5m=3 load15m=.9 => 5 ' ) ;
  13403. is( 15, load_and_delay( 1, 1, 1, 1, 1 ), 'load_and_delay: max=1, one core, load1m=3 load5m=3 load15m=3 => 15 ' ) ;
  13404. is( 0, load_and_delay( 1, 1, .9, .9, .9 ), 'load_and_delay: max=1, one core, load1m=.9 load5m=.9 load15m=.9 => 0 ' ) ;
  13405. # load max = 3 per cpu
  13406. is( 0, load_and_delay( 3, 1, 1, 1, 1 ), 'load_and_delay: max=3, one core, loads are all 1 => ok ' ) ;
  13407. is( 0, load_and_delay( 3, 2, 2, 2, 2 ), 'load_and_delay: max=3, two core, loads are all 2 => ok ' ) ;
  13408. is( 0, load_and_delay( 3, 2, 2, 4, 5 ), 'load_and_delay: max=3, two core, load1m is 2 => ok ' ) ;
  13409. is( 0, load_and_delay( 3, 1, 0, 0, 0 ), 'load_and_delay: max=3, one core, load1m=0 load5m=0 load15m=0 => 0 ' ) ;
  13410. is( 0, load_and_delay( 3, 1, 0, 0, 2 ), 'load_and_delay: max=3, one core, load1m=0 load5m=0 load15m=2 => 0 ' ) ;
  13411. is( 0, load_and_delay( 3, 1, 0, 2, 0 ), 'load_and_delay: max=3, one core, load1m=0 load5m=2 load15m=0 => 0 ' ) ;
  13412. is( 0, load_and_delay( 3, 1, 0, 2, 2 ), 'load_and_delay: max=3, one core, load1m=0 load5m=2 load15m=2 => 0 ' ) ;
  13413. is( 0, load_and_delay( 3, 1, 0, 3, 3 ), 'load_and_delay: max=3, one core, load1m=0 load5m=3 load15m=3 => 0 ' ) ;
  13414. is( 0, load_and_delay( 3, 1, 0, 4, 4 ), 'load_and_delay: max=3, one core, load1m=0 load5m=3 load15m=3 => 0 ' ) ;
  13415. is( 0, load_and_delay( 3, 1, 2, 0, 0 ), 'load_and_delay: max=3, one core, load1m=2 load5m=0 load15m=0 => 0 ' ) ;
  13416. is( 0, load_and_delay( 3, 1, 2, 0, 2 ), 'load_and_delay: max=3, one core, load1m=2 load5m=0 load15m=2 => 0 ' ) ;
  13417. is( 0, load_and_delay( 3, 1, 2, 2, 0 ), 'load_and_delay: max=3, one core, load1m=2 load5m=2 load15m=0 => 0 ' ) ;
  13418. is( 0, load_and_delay( 3, 1, 2, 2, 2 ), 'load_and_delay: max=3, one core, load1m=2 load5m=2 load15m=2 => 0 ' ) ;
  13419. is( 1, load_and_delay( 3, 1, 3, 0, 0 ), 'load_and_delay: max=3, one core, load1m=3 load5m=0 load15m=0 => 0 ' ) ;
  13420. is( 1, load_and_delay( 3, 1, 3, 2.9, 2.9 ), 'load_and_delay: max=3, one core, load1m=3 load5m=2.9 load15m=2.9 => 0 ' ) ;
  13421. is( 5, load_and_delay( 3, 1, 3, 3, 2.9 ), 'load_and_delay: max=3, one core, load1m=3 load5m=3 load15m=2.9 => 0 ' ) ;
  13422. is( 15, load_and_delay( 3, 1, 3, 3, 3 ), 'load_and_delay: max=3, one core, load1m=3 load5m=3 load15m=3 => 0 ' ) ;
  13423. is( 0, load_and_delay( 3, 1, 2.9, 2.9, 2.9 ), 'load_and_delay: max=3, one core, load1m=2.9 load5m=2.9 load15m=2.9 => 0 ' ) ;
  13424. is( 1, load_and_delay( 6, 1, 6, 0, 0 ), 'load_and_delay: max=6, one core, load1m=6 load5m=0 load15m=0 => 1 ' ) ;
  13425. is( 1, load_and_delay( 6, 1, 6, 5.9, 5.9 ), 'load_and_delay: max=6, one core, load1m=6 load5m=5.9 load15m=5.9 => 1 ' ) ;
  13426. is( 5, load_and_delay( 6, 1, 6, 6, 5.9 ), 'load_and_delay: max=6, one core, load1m=6 load5m=6 load15m=5.9 => 5 ' ) ;
  13427. is( 15, load_and_delay( 6, 1, 6, 6, 6 ), 'load_and_delay: max=6, one core, load1m=6 load5m=6 load15m=6 => 15 ' ) ;
  13428. is( 0, load_and_delay( 6, 1, 5.9, 5.9, 5.9 ), 'load_and_delay: max=6, one core, load1m=5.9 load5m=5.9 load15m=5.9 => 1 ' ) ;
  13429. note( 'Leaving tests_load_and_delay()' ) ;
  13430. return ;
  13431. }
  13432. sub load_and_delay
  13433. {
  13434. # Basically return 0 if load is not heavy, ie load 1 min <= $max_load_per_core
  13435. # 5 arguments at least (loadavg return 4 arguments like "0.19 0.16 0.17 3/444" )
  13436. if ( 3 > scalar @ARG ) { return ; }
  13437. my ( $max_load_per_core, $cpu_num, $avg_1_min, $avg_5_min, $avg_15_min ) = @ARG ;
  13438. if ( 0 == $cpu_num ) { return ; }
  13439. # No avg_5_min nor avg_15_min on Windows
  13440. $avg_5_min ||= 0 ;
  13441. $avg_15_min ||= 0 ;
  13442. # Let divide by number of cores
  13443. ( $avg_1_min, $avg_5_min, $avg_15_min ) = map { $_ / $cpu_num } ( $avg_1_min, $avg_5_min, $avg_15_min ) ;
  13444. # If one period 1m is ok => ok ( 0 ) else Nok ( return > 0, 1 or 5 or 15 )
  13445. if ( $avg_1_min < $max_load_per_core ) { return 0 ; }
  13446. if ( $avg_5_min < $max_load_per_core ) { return 1 ; } # Message: retry in 1 minute
  13447. if ( $avg_15_min < $max_load_per_core ) { return 5 ; } # Message: retry in 5 minutes
  13448. return 15 ; # Message: retry in 15 minutes
  13449. }
  13450. sub tests_cpu_time
  13451. {
  13452. note( 'Entering tests_cpu_time()' ) ;
  13453. ok( is_number( cpu_time( ) ), 'cpu_time: no args => a number' ) ;
  13454. my $mysync = { } ;
  13455. $mysync->{ debug } = 1 ;
  13456. ok( is_number( cpu_time( $mysync ) ), 'cpu_time: {} => a number' ) ;
  13457. note( 'Leaving tests_cpu_time()' ) ;
  13458. return ;
  13459. }
  13460. sub cpu_time
  13461. {
  13462. my $mysync = shift @ARG ;
  13463. my @cpu_times = times ;
  13464. if ( ! @cpu_times ) { return ; }
  13465. my $cpu_time = 0 ;
  13466. # last element is the sum of all elements
  13467. $cpu_time = ( map { $cpu_time += $_ } @cpu_times )[ -1 ] ;
  13468. my $cpu_time_rounded = mysprintf( '%.2f', $cpu_time ) ;
  13469. $mysync->{ debug } and myprint( join(' + ', @cpu_times), " = $cpu_time ~ $cpu_time_rounded\n" ) ;
  13470. return $cpu_time ;
  13471. }
  13472. sub tests_cpu_percent
  13473. {
  13474. note( 'Entering tests_cpu_percent()' ) ;
  13475. is( '0.0', cpu_percent( ), 'cpu_percent: no args => 0.0' ) ;
  13476. my $mysync = { } ;
  13477. $mysync->{ debug } = 1 ;
  13478. is( '0.0', cpu_percent( $mysync ), 'cpu_percent: {} => 0.0' ) ;
  13479. is( '0.0', cpu_percent( $mysync, 0 ), 'cpu_percent: {} 0 => 0.0' ) ;
  13480. is( '300.0', cpu_percent( $mysync, 3 ), 'cpu_percent: {} 3 => 300.0' ) ;
  13481. is( '30.0', cpu_percent( $mysync, 3, 10 ), 'cpu_percent: {} 3 10 => 30.0' ) ;
  13482. is( '0.0', cpu_percent( $mysync, 0, 10 ), 'cpu_percent: {} 0 10 => 0.0' ) ;
  13483. note( 'Leaving tests_cpu_percent()' ) ;
  13484. return ;
  13485. }
  13486. sub cpu_percent
  13487. {
  13488. my $mysync = shift @ARG ;
  13489. my $cpu_time = shift || 0 ;
  13490. my $timediff = shift || 1 ; # no division by 0
  13491. if ( $cpu_time > $timediff )
  13492. {
  13493. myprint( "Strange: cpu_time $cpu_time > timediff $timediff\n" ) ;
  13494. }
  13495. my $cpu_percent = 0 ;
  13496. $cpu_percent = mysprintf( '%.1f', 100 * $cpu_time / $timediff ) ;
  13497. $mysync->{ debug } and myprint( "cpu_percent: $cpu_percent \n" ) ;
  13498. return $cpu_percent ;
  13499. }
  13500. sub tests_cpu_percent_global
  13501. {
  13502. note( 'Entering tests_cpu_percent_global()' ) ;
  13503. is( '0.0', cpu_percent_global( ), 'cpu_percent_global: no args => 0' ) ;
  13504. my $mysync = { } ;
  13505. $mysync->{ debug } = 1 ;
  13506. is( '0.0', cpu_percent_global( $mysync ), 'cpu_percent_global: {} => 0' ) ;
  13507. is( '0.0', cpu_percent_global( $mysync, 0 ), 'cpu_percent_global: {} 0 => 0' ) ;
  13508. SKIP: {
  13509. if ( ! ( 'i005' eq hostname() ) )
  13510. {
  13511. skip( 'cpu_percent_global on host != i005', 1 ) ;
  13512. }
  13513. is( '25.0', cpu_percent_global( $mysync, 100 ), 'cpu_percent_global: {} 100 => 25 on host i005' ) ;
  13514. } ;
  13515. SKIP: {
  13516. if ( ! ( 'petite' eq hostname() ) )
  13517. {
  13518. skip( 'cpu_percent_global on host != petite', 1 ) ;
  13519. }
  13520. is( '50.0', cpu_percent_global( $mysync, 100 ), 'cpu_percent_global: {} 100 => 50 on host petite' ) ;
  13521. #is( '100.0', cpu_percent_global( $mysync, 100 ), 'cpu_percent_global: {} 100 => 100 on host petite' ) ;
  13522. } ;
  13523. note( 'Leaving tests_cpu_percent_global()' ) ;
  13524. return ;
  13525. }
  13526. sub cpu_percent_global
  13527. {
  13528. my $mysync = shift @ARG ;
  13529. my $cpu_percent = shift || 0 ;
  13530. my $cpu_number = cpu_number( ) ;
  13531. my $cpu_percent_global ;
  13532. $cpu_percent_global = mysprintf( '%.1f', $cpu_percent / $cpu_number ) ;
  13533. $mysync->{ debug } and myprint( "cpu_percent_global: $cpu_percent_global \n" ) ;
  13534. return( $cpu_percent_global ) ;
  13535. }
  13536. sub tests_ram_memory_info
  13537. {
  13538. note( 'Entering tests_ram_memory_info()' ) ;
  13539. note( "ram_memory_info:", ram_memory_info( ) ) ;
  13540. note( "total_ram_memory_bytes_sys_meminfo:", total_ram_memory_bytes_sys_meminfo( ) ) ;
  13541. #note( "total_ram_memory_bytes_hw_memsize:", total_ram_memory_bytes_hw_memsize( ) ) ;
  13542. ok( ram_memory_info( ), 'ram_memory_info: => some text' ) ;
  13543. note( 'Leaving tests_ram_memory_info()' ) ;
  13544. return ;
  13545. }
  13546. sub total_ram_memory_bytes
  13547. {
  13548. my $total_ram_memory_bytes ;
  13549. # Found Sys::MemInfo::get( "totalmem" )
  13550. # wrong on macOS 11.5.2 (20G95)
  13551. # good on Mac OS X 10.7.5 (11G63)
  13552. # ( commands "sw_vers" or "system_profiler SPSoftwareDataType" )
  13553. if ( 'darwin' eq $OSNAME )
  13554. {
  13555. $total_ram_memory_bytes = total_ram_memory_bytes_hw_memsize( ) ;
  13556. }
  13557. else
  13558. {
  13559. $total_ram_memory_bytes = total_ram_memory_bytes_sys_meminfo( ) ;
  13560. }
  13561. }
  13562. sub total_ram_memory_bytes_hw_memsize
  13563. {
  13564. my $total_ram_memory_bytes = `sysctl -n hw.memsize` ;
  13565. chomp $total_ram_memory_bytes ;
  13566. return $total_ram_memory_bytes ;
  13567. }
  13568. sub total_ram_memory_bytes_sys_meminfo
  13569. {
  13570. my $total_ram_memory_bytes = Sys::MemInfo::get( "totalmem" ) ;
  13571. return $total_ram_memory_bytes ;
  13572. }
  13573. sub ram_memory_info
  13574. {
  13575. my $mysync = shift @ARG ;
  13576. # In GigaBytes so division by 1024 * 1024 * 1024
  13577. #
  13578. my $ram_memory_info = sprintf(
  13579. "%.1f/%.1f free GiB of RAM",
  13580. Sys::MemInfo::get("freemem") / ( $KIBI ** 3 ),
  13581. total_ram_memory_bytes( ) / ( $KIBI ** 3 ),
  13582. ) ;
  13583. my $memory_consumption_all_pids_percent = memory_consumption_all_pids_percent( $mysync ) || 0 ;
  13584. if ( $memory_consumption_all_pids_percent )
  13585. {
  13586. $ram_memory_info .= ", $memory_consumption_all_pids_percent% used by processes."
  13587. }
  13588. return $ram_memory_info ;
  13589. }
  13590. sub tests_memory_stress
  13591. {
  13592. note( 'Entering tests_memory_stress()' ) ;
  13593. my $mysync = { } ;
  13594. sig_install( $mysync, 'catch_ignore', ( 'QUIT', 'TERM', 'INT' ) ) ;
  13595. is( undef, memory_stress( ), 'memory_stress: => undef' ) ;
  13596. note( 'Leaving tests_memory_stress()' ) ;
  13597. return ;
  13598. }
  13599. sub memory_stress
  13600. {
  13601. my $total_ram_in_MB = total_ram_memory_bytes( ) / ( $KIBI * $KIBI ) ;
  13602. my $i = 1 ;
  13603. myprintf("Stress memory consumption before: %.1f MiB of %.1f MiB\n", memory_consumption_of_myself( ) / $KIBI / $KIBI, $total_ram_in_MB ) ;
  13604. while ( $i < $total_ram_in_MB / 1.7 )
  13605. {
  13606. $a .= "A" x 1000_000;
  13607. myprintf( "$i " ) ;
  13608. $i++ ;
  13609. } ;
  13610. myprintf( "\nStress memory consumption after: %.1f MiB of %.1f MiB\n", memory_consumption_of_myself( ) / $KIBI / $KIBI, $total_ram_in_MB ) ;
  13611. return ;
  13612. }
  13613. sub tests_memory_consumption_of_myself
  13614. {
  13615. note( 'Entering tests_memory_consumption_of_myself()' ) ;
  13616. note( "memory_consumption_of_myself: " . memory_consumption_of_myself( ) . " bytes aka " . bytes_display_string_dec( memory_consumption_of_myself( ) ) ) ;
  13617. like( memory_consumption_of_myself( ), qr{\d+}xms,'tests_memory_consumption_of_myself no args') ;
  13618. like( memory_consumption_of_myself( 1 ), qr{\d+}xms,'tests_memory_consumption_of_myself 1') ;
  13619. like( memory_consumption_of_myself( $PROCESS_ID ), qr{\d+}xms,"tests_memory_consumption_of_myself $PROCESS_ID") ;
  13620. note( 'Leaving tests_memory_consumption_of_myself()' ) ;
  13621. return ;
  13622. }
  13623. sub memory_consumption_of_myself
  13624. {
  13625. # memory consumed by imapsync until now in bytes
  13626. return( ( memory_consumption_of_pids( ) )[0] );
  13627. }
  13628. sub debugmemory
  13629. {
  13630. my $mysync = shift @ARG ;
  13631. if ( ! $mysync->{ debugmemory } ) { return q{} ; }
  13632. my $precision = shift @ARG ;
  13633. return( mysprintf( "Memory consumption$precision: %.1f MiB\n", memory_consumption_of_myself( ) / $KIBI / $KIBI ) ) ;
  13634. }
  13635. sub memory_consumption_of_pids
  13636. {
  13637. my @pid = @ARG ;
  13638. # No pid in ARG means find myself memory
  13639. @pid = ( @pid ) ? @pid : ( $PROCESS_ID ) ;
  13640. #$sync->{ debug } and myprint( "memory_consumption_of_pids PIDs: @pid\n" ) ;
  13641. my @val ;
  13642. if ( ( 'MSWin32' eq $OSNAME ) or ( 'cygwin' eq $OSNAME ) )
  13643. {
  13644. @val = memory_consumption_of_pids_win32( @pid ) ;
  13645. }
  13646. else
  13647. {
  13648. # Unix, Mac OS X included
  13649. @val = memory_consumption_of_pids_unix( @pid ) ;
  13650. }
  13651. return( @val ) ;
  13652. }
  13653. sub memory_consumption_of_pids_unix
  13654. {
  13655. my @pid = @_ ;
  13656. # Use IPC::Open3 from perlcrit -3
  13657. # But it stalls on Darwin, I don't understand why!
  13658. #my @ps = backtick( "ps -o rss -p @pid" ) ;
  13659. #myprint( "ps: @ps" ) ;
  13660. my @ps = qx{ ps -o rss -p @pid } ;
  13661. shift @ps ; # First line is the column name "RSS"
  13662. chomp @ps ;
  13663. # convert to octets
  13664. my @val = map { $_ * $KIBI } @ps ;
  13665. return( @val ) ;
  13666. }
  13667. sub tests_memory_consumption_of_all_pids
  13668. {
  13669. note( 'Entering tests_memory_consumption_of_all_pids()' ) ;
  13670. note( "memory_consumption_of_all_pids: " . memory_consumption_of_all_pids( ) . " bytes aka " . bytes_display_string_dec( memory_consumption_of_all_pids( ) ) ) ;
  13671. like( memory_consumption_of_all_pids( ), qr{\d+}xms, 'tests_memory_consumption_of_all_pids no args') ;
  13672. note( 'Leaving tests_memory_consumption_of_all_pids()' ) ;
  13673. return ;
  13674. }
  13675. sub memory_consumption_of_all_pids
  13676. {
  13677. my @all_pids = all_pids( ) ;
  13678. my @memory_consumption_of_all_pids = memory_consumption_of_pids( @all_pids ) ;
  13679. my $memory_consumption_of_all_pids = add( @memory_consumption_of_all_pids ) ;
  13680. return $memory_consumption_of_all_pids ;
  13681. }
  13682. sub tests_all_pids
  13683. {
  13684. note( 'Entering tests_all_pids()' ) ;
  13685. note( 'all_pids', join( ' ', all_pids( ) ) ) ;
  13686. ok( all_pids( ), 'tests_all_pids: no args => list of pids' ) ;
  13687. note( 'Leaving tests_all_pids()' ) ;
  13688. return ;
  13689. }
  13690. sub all_pids
  13691. {
  13692. my @all_pids ;
  13693. if ( ( 'MSWin32' eq $OSNAME ) or ( 'cygwin' eq $OSNAME ) ) {
  13694. @all_pids = all_pids_win32( ) ;
  13695. }
  13696. else
  13697. {
  13698. # Unix
  13699. @all_pids = all_pids_unix( ) ;
  13700. }
  13701. return( @all_pids ) ;
  13702. }
  13703. sub all_pids_unix
  13704. {
  13705. my @ps = qx{ ps -e -o pid } ;
  13706. shift @ps ; # First line is the column name "PID"
  13707. chomp @ps ;
  13708. return @ps ;
  13709. }
  13710. sub tests_memory_consumption_of_pids_win32
  13711. {
  13712. note( 'Entering tests_memory_consumption_of_pids_win32()' ) ;
  13713. note( memory_consumption_of_pids_win32( $PROCESS_ID ) ) ;
  13714. #ok( memory_consumption_of_pids_win32( $PROCESS_ID ), 'tests_memory_consumption_of_pids_win32: no args => ' ) ;
  13715. note( 'Leaving tests_memory_consumption_of_pids_win32()' ) ;
  13716. return ;
  13717. }
  13718. sub memory_consumption_of_pids_win32
  13719. {
  13720. # Windows
  13721. my @PID = @_;
  13722. my %PID;
  13723. # hash of pids as key values
  13724. map { $PID{$_}++ } @PID;
  13725. # Does not work but should work reading the tasklist documentation
  13726. #@ps = qx{ tasklist /FI "PID eq @PID" };
  13727. my @ps = qx{ tasklist /NH /FO TABLE } ;
  13728. #my @ps = backtick( 'tasklist /NH /FO TABLE' ) ;
  13729. #myprint( "-" x $STD_CHAR_PER_LINE, "\n", @ps, "-" x $STD_CHAR_PER_LINE, "\n" ) ;
  13730. my @val ;
  13731. #myprint( @ps ) ;
  13732. foreach my $line ( @ps ) {
  13733. my( $name, $pid, $mem ) = ( split / +/, $line )[ 0, 1, 4 ] ;
  13734. next if (! $pid);
  13735. #myprint( "[$name][$pid][$mem]\n" ) ;
  13736. $pid = remove_qq( $pid ) ;
  13737. if ( $PID{ $pid } )
  13738. {
  13739. #myprint( "MATCH $pid\n" ) ;
  13740. chomp $mem ;
  13741. $mem = remove_qq( $mem ) ;
  13742. $mem = remove_not_num( $mem ) ;
  13743. if ( is_number( $mem ) )
  13744. {
  13745. #myprint( "[$mem] ", $mem * $KIBI, "\n" ) ;
  13746. push @val, $mem * $KIBI ;
  13747. }
  13748. }
  13749. }
  13750. return( @val ) ;
  13751. }
  13752. sub all_pids_win32
  13753. {
  13754. my @ps = qx{ tasklist /NH /FO CSV } ;
  13755. my @pids ;
  13756. foreach my $line ( @ps )
  13757. {
  13758. my( $name, $pid, $mem ) = ( split ',', $line )[ 0, 1, 4 ] ;
  13759. next if ( ! $pid ) ;
  13760. #myprint( "[$name][$pid][$mem]\n" ) ;
  13761. $pid = remove_qq( $pid ) ;
  13762. push @pids, $pid ;
  13763. }
  13764. return( @pids ) ;
  13765. }
  13766. sub tests_memory_consumption_all_pids_percent
  13767. {
  13768. note( 'Entering tests_memory_consumption_all_pids_percent()' ) ;
  13769. note( memory_consumption_all_pids_percent( ) . " (%)" ) ;
  13770. like( memory_consumption_all_pids_percent( ), qr{^0|^(\d+\.\d+)$}xms, 'tests_memory_consumption_all_pids_percent: no args => like 12.34' ) ;
  13771. ok( 0 <= memory_consumption_all_pids_percent( ), 'tests_memory_consumption_all_pids_percent: > 0' ) ;
  13772. ok( 100 >= memory_consumption_all_pids_percent( ), 'tests_memory_consumption_all_pids_percent: <= 100' ) ;
  13773. note( 'Leaving tests_memory_consumption_all_pids_percent()' ) ;
  13774. return ;
  13775. }
  13776. sub memory_consumption_all_pids_percent
  13777. {
  13778. my $percent ;
  13779. my $memory_consumption_of_all_pids = memory_consumption_of_all_pids( ) ;
  13780. my $total_memory_bytes = total_ram_memory_bytes( ) || return 0 ;
  13781. $percent = sprintf( "%.2f", 100 * $memory_consumption_of_all_pids / $total_memory_bytes ) ;
  13782. return $percent ;
  13783. }
  13784. sub tests_memory_consumption_all_pids_percent_Proc_ProcessTable
  13785. {
  13786. note( 'Entering tests_memory_consumption_all_pids_percent_Proc_ProcessTable()' ) ;
  13787. require_ok( 'Proc::ProcessTable' ) ;
  13788. note( memory_consumption_all_pids_percent_Proc_ProcessTable( ) . " (%)" ) ;
  13789. like( memory_consumption_all_pids_percent_Proc_ProcessTable( ), qr{^0|^(\d+\.\d+)$}xms, 'tests_memory_consumption_all_pids_percent_Proc_ProcessTable: no args => like 12.34' ) ;
  13790. my $mysync = { } ;
  13791. like( memory_consumption_all_pids_percent_Proc_ProcessTable( $mysync ), qr{^0|^(\d+\.\d+)$}xms, 'tests_memory_consumption_all_pids_percent_Proc_ProcessTable: { } => like 12.34' ) ;
  13792. note( 'Leaving tests_memory_consumption_all_pids_percent_Proc_ProcessTable()' ) ;
  13793. return ;
  13794. }
  13795. # Proc::ProcessTable pctmem does NOT work for Win32 Darwin
  13796. # Negative values and negative zeros for Darwin
  13797. # zeros for Win32
  13798. #
  13799. sub memory_consumption_all_pids_percent_Proc_ProcessTable
  13800. {
  13801. my $mysync = shift @ARG ;
  13802. my $percent ;
  13803. if ( eval { require Proc::ProcessTable } )
  13804. {
  13805. my $table_all_processes = Proc::ProcessTable->new( ) ;
  13806. if ( pctmem_available( $table_all_processes ) )
  13807. {
  13808. #pctmem is memory percentage of a process
  13809. foreach my $process ( @{ $table_all_processes->table( ) } )
  13810. {
  13811. #myprint( "pctmem: ", $process->pctmem( ), "\n" ) ;
  13812. #
  13813. $percent += max( 0, $process->pctmem( ) ) ;
  13814. }
  13815. }
  13816. else
  13817. {
  13818. $percent = 0 ;
  13819. }
  13820. }
  13821. else
  13822. {
  13823. $percent = 0 ;
  13824. }
  13825. $percent = sprintf( "%.2f", $percent ) ;
  13826. return $percent ;
  13827. }
  13828. sub tests_pctmem_available
  13829. {
  13830. note( 'Entering tests_pctmem_available()' ) ;
  13831. is( undef, pctmem_available( ), 'pctmem_available: no args => undef' ) ;
  13832. SKIP: {
  13833. skip( 'no Proc::ProcessTable', 1 ) if ( ! eval { require Proc::ProcessTable } ) ;
  13834. my $table_all_processes = Proc::ProcessTable->new( ) ;
  13835. like( pctmem_available( $table_all_processes ), qr{^(0|1)$}xms, 'pctmem_available: => 0 or 1' ) ;
  13836. }
  13837. note( 'Leaving tests_pctmem_available()' ) ;
  13838. return ;
  13839. }
  13840. sub pctmem_available
  13841. {
  13842. my $table_all_processes = shift @ARG ;
  13843. if ( ! defined $table_all_processes ) { return ; } ;
  13844. my @fields = $table_all_processes->fields( ) ;
  13845. my %fields = map { defined( $_ ) ? ( $_ => 1 ) : ( ) } @fields ;
  13846. if ( exists( $fields{ pctmem } ) )
  13847. {
  13848. return 1 ;
  13849. }
  13850. else
  13851. {
  13852. return 0 ;
  13853. }
  13854. return ;
  13855. }
  13856. sub tests_backtick
  13857. {
  13858. note( 'Entering tests_backtick()' ) ;
  13859. is( undef, backtick( ), 'backtick: no args' ) ;
  13860. is( undef, backtick( q{} ), 'backtick: empty command' ) ;
  13861. SKIP: {
  13862. skip( 'test for MSWin32', 5 ) if ('MSWin32' ne $OSNAME) ;
  13863. my @output ;
  13864. @output = backtick( 'echo Hello World!' ) ;
  13865. # Add \r on Windows.
  13866. ok( "Hello World!\r\n" eq $output[0], 'backtick: echo Hello World!' ) ;
  13867. $sync->{ debug } and myprint( "[@output]" ) ;
  13868. @output = backtick( 'echo Hello & echo World!' ) ;
  13869. ok( "Hello \r\n" eq $output[0], 'backtick: echo Hello & echo World! line 1' ) ;
  13870. ok( "World!\r\n" eq $output[1], 'backtick: echo Hello & echo World! line 2' ) ;
  13871. $sync->{ debug } and myprint( "[@output][$output[0]][$output[1]]" ) ;
  13872. # Scalar context
  13873. ok( "Hello World!\r\n" eq backtick( 'echo Hello World!' ),
  13874. 'backtick: echo Hello World! scalar' ) ;
  13875. ok( "Hello \r\nWorld!\r\n" eq backtick( 'echo Hello & echo World!' ),
  13876. 'backtick: echo Hello & echo World! scalar 2 lines' ) ;
  13877. } ;
  13878. SKIP: {
  13879. skip( 'test for Unix', 7 ) if ('MSWin32' eq $OSNAME) ;
  13880. is( undef, backtick( 'aaaarrrg' ), 'backtick: aaaarrrg command not found' ) ;
  13881. # Array context
  13882. my @output ;
  13883. @output = backtick( 'echo Hello World!' ) ;
  13884. ok( "Hello World!\n" eq $output[0], 'backtick: echo Hello World!' ) ;
  13885. $sync->{ debug } and myprint( "[@output]" ) ;
  13886. @output = backtick( "echo Hello\necho World!" ) ;
  13887. ok( "Hello\n" eq $output[0], 'backtick: echo Hello; echo World! line 1' ) ;
  13888. ok( "World!\n" eq $output[1], 'backtick: echo Hello; echo World! line 2' ) ;
  13889. $sync->{ debug } and myprint( "[@output]" ) ;
  13890. # Scalar context
  13891. ok( "Hello World!\n" eq backtick( 'echo Hello World!' ),
  13892. 'backtick: echo Hello World! scalar' ) ;
  13893. ok( "Hello\nWorld!\n" eq backtick( "echo Hello\necho World!" ),
  13894. 'backtick: echo Hello; echo World! scalar 2 lines' ) ;
  13895. # Return error positive value, that's ok
  13896. is( undef, backtick( 'false' ), 'backtick: false returns no output' ) ;
  13897. my $mem = backtick( "ps -o vsz -p $PROCESS_ID" ) ;
  13898. $sync->{ debug } and myprint( "MEM=$mem\n" ) ;
  13899. }
  13900. note( 'Leaving tests_backtick()' ) ;
  13901. return ;
  13902. }
  13903. sub backtick
  13904. {
  13905. my $command = shift @ARG ;
  13906. if ( ! $command ) { return ; }
  13907. my ( $writer, $reader, $err ) ;
  13908. my @output ;
  13909. my $pid ;
  13910. my $eval = eval {
  13911. $pid = IPC::Open3::open3( $writer, $reader, $err, $command ) ;
  13912. } ;
  13913. if ( $EVAL_ERROR ) {
  13914. myprint( $EVAL_ERROR ) ;
  13915. return ;
  13916. }
  13917. if ( ! $eval ) { return ; }
  13918. if ( ! $pid ) { return ; }
  13919. waitpid( $pid, 0 ) ;
  13920. @output = <$reader>; # Output here
  13921. #
  13922. #my @errors = <$err>; #Errors here, instead of the console
  13923. if ( not @output ) { return ; }
  13924. #myprint( @output ) ;
  13925. if ( $output[0] =~ /\Qopen3: exec of $command failed\E/mxs ) { return ; }
  13926. if ( wantarray ) {
  13927. return( @output ) ;
  13928. } else {
  13929. return( join( q{}, @output) ) ;
  13930. }
  13931. }
  13932. sub tests_check_binary_embed_all_dyn_libs
  13933. {
  13934. note( 'Entering tests_check_binary_embed_all_dyn_libs()' ) ;
  13935. is( 1, check_binary_embed_all_dyn_libs( ), 'check_binary_embed_all_dyn_libs: no args => 1' ) ;
  13936. note( 'Leaving tests_check_binary_embed_all_dyn_libs()' ) ;
  13937. return ;
  13938. }
  13939. sub check_binary_embed_all_dyn_libs
  13940. {
  13941. my @search_dyn_lib_locale = search_dyn_lib_locale( ) ;
  13942. if ( @search_dyn_lib_locale )
  13943. {
  13944. myprint( "Found myself $PROGRAM_NAME pid $PROCESS_ID using locale dynamic libraries that seems out of myself:\n" ) ;
  13945. myprint( @search_dyn_lib_locale ) ;
  13946. if ( $PROGRAM_NAME =~ m{imapsync_bin_Darwin} )
  13947. {
  13948. return 0 ;
  13949. }
  13950. elsif ( $PROGRAM_NAME =~ m{imapsync.*\.exe} )
  13951. {
  13952. return 0 ;
  13953. }
  13954. else
  13955. {
  13956. # is always ok for non binary
  13957. return 1 ;
  13958. }
  13959. }
  13960. else
  13961. {
  13962. # Found only embedded dynamic lib
  13963. myprint( "Found only embedded dynamic lib. Good!\n" ) ;
  13964. return 1 ;
  13965. }
  13966. }
  13967. sub search_dyn_lib_locale
  13968. {
  13969. if ( 'darwin' eq $OSNAME )
  13970. {
  13971. return search_dyn_lib_locale_darwin( ) ;
  13972. }
  13973. if ( 'linux' eq $OSNAME )
  13974. {
  13975. return search_dyn_lib_locale_linux( ) ;
  13976. }
  13977. if ( 'MSWin32' eq $OSNAME )
  13978. {
  13979. return search_dyn_lib_locale_MSWin32( ) ;
  13980. }
  13981. }
  13982. sub search_dyn_lib_locale_darwin
  13983. {
  13984. my $command = qq{ lsof -p $PROCESS_ID | grep ' REG ' | grep .dylib | grep -v '/par-' } ;
  13985. myprint( "Search non embeded dynamic libs with the command: $command\n" ) ;
  13986. return backtick( $command ) ;
  13987. }
  13988. sub search_dyn_lib_locale_linux
  13989. {
  13990. my $command = qq{ lsof -p $PROCESS_ID | grep ' REG ' | grep -v '/tmp/par-' | grep '\.so' } ;
  13991. myprint( "Search non embeded dynamic libs with the command: $command\n" ) ;
  13992. return backtick( $command ) ;
  13993. }
  13994. sub search_dyn_lib_locale_MSWin32
  13995. {
  13996. my $command = qq{ Listdlls.exe $PROCESS_ID|findstr Strawberry } ;
  13997. # $command = qq{ Listdlls.exe $PROCESS_ID|findstr Strawberry } ;
  13998. myprint( "Search non embeded dynamic libs with the command: $command\n" ) ;
  13999. return qx( $command ) ;
  14000. }
  14001. sub remove_not_num
  14002. {
  14003. my $string = shift @ARG ;
  14004. $string =~ tr/0-9//cd ;
  14005. #myprint( "tr [$string]\n" ) ;
  14006. return( $string ) ;
  14007. }
  14008. sub tests_remove_not_num
  14009. {
  14010. note( 'Entering tests_remove_not_num()' ) ;
  14011. is( '123', remove_not_num( 123 ), 'remove_not_num( 123 )' ) ;
  14012. is( '123', remove_not_num( '123' ), q{remove_not_num( '123' )} ) ;
  14013. is( '123', remove_not_num( '12 3' ), q{remove_not_num( '12 3' )} ) ;
  14014. is( '123', remove_not_num( 'a 12 3 Ko' ), q{remove_not_num( 'a 12 3 Ko' )} ) ;
  14015. is( '123', remove_not_num( 'a 12 3 K' ), q{remove_not_num( 'a 12 3 K' )} ) ;
  14016. is( '123', remove_not_num( 'a 12,3 K' ), q{remove_not_num( 'a 12, 3 K' )} ) ;
  14017. is( '173892', remove_not_num( 'a 173,892 K' ), q{remove_not_num( 'a 173,892 K' )} ) ;
  14018. note( 'Leaving tests_remove_not_num()' ) ;
  14019. return ;
  14020. }
  14021. sub tests_remove_qq
  14022. {
  14023. note( 'Entering tests_remove_qq()' ) ;
  14024. is( undef, remove_qq( ), 'tests_remove_qq: no args => undef' ) ;
  14025. is( '', remove_qq( '' ), 'tests_remove_qq: empty => empty' ) ;
  14026. is( 'ABC', remove_qq( 'ABC' ), 'tests_remove_qq: ABC => ABC' ) ;
  14027. is( 'ABC', remove_qq( '"ABC"' ), 'tests_remove_qq: "ABC" => ABC' ) ;
  14028. is( '"ABC', remove_qq( '""ABC"' ), 'tests_remove_qq: ""ABC" => "ABC' ) ;
  14029. is( 'ABC"', remove_qq( '"ABC""' ), 'tests_remove_qq: "ABC"" => ABC"' ) ;
  14030. is( '"ABC"', remove_qq( '""ABC""' ), 'tests_remove_qq: ""ABC"" => "ABC"' ) ;
  14031. note( 'Leaving tests_remove_qq()' ) ;
  14032. return ;
  14033. }
  14034. sub remove_qq
  14035. {
  14036. my $string = shift ;
  14037. if ( ! defined $string ) { return ; }
  14038. #myprint( "$string\n" ) ;
  14039. if ( $string =~ /^"(.*)"$/xo )
  14040. {
  14041. return( $1 ) ;
  14042. }else{
  14043. return( $string ) ;
  14044. }
  14045. }
  14046. sub date_from_rcs
  14047. {
  14048. my $d = shift @ARG ;
  14049. 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 ) ;
  14050. if ($d =~ m{(\d{4})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) {
  14051. # Handles the following format
  14052. # 2015/07/10 11:05:59 -- Generated by RCS Date tag.
  14053. #myprint( "$d\n" ) ;
  14054. #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
  14055. my ($year, $month, $day, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6) ;
  14056. $month = $num2mon{$month} ;
  14057. $d = "$day-$month-$year $hour:$min:$sec +0000" ;
  14058. #myprint( "$d\n" ) ;
  14059. }
  14060. return( $d ) ;
  14061. }
  14062. sub tests_date_from_rcs
  14063. {
  14064. note( 'Entering tests_date_from_rcs()' ) ;
  14065. ok('19-Sep-2015 16:11:07 +0000'
  14066. eq date_from_rcs('Date: 2015/09/19 16:11:07 '), 'date_from_rcs from RCS date' ) ;
  14067. note( 'Leaving tests_date_from_rcs()' ) ;
  14068. return ;
  14069. }
  14070. sub good_date
  14071. {
  14072. # two incoming formats:
  14073. # header Tue, 24 Aug 2010 16:00:00 +0200
  14074. # internal 24-Aug-2010 16:00:00 +0200
  14075. # outgoing format: internal date format
  14076. # 24-Aug-2010 16:00:00 +0200
  14077. my $d = shift @ARG ;
  14078. return(q{}) if not defined $d;
  14079. SWITCH: {
  14080. if ( $d =~ m{(\d?)(\d-...-\d{4})(\s\d{2}:\d{2}:\d{2})(\s(?:\+|-)\d{4})?}xo ) {
  14081. #myprint( "internal: [$1][$2][$3][$4]\n" ) ;
  14082. my ($day_1, $date_rest, $hour, $zone) = ($1,$2,$3,$4) ;
  14083. $day_1 = '0' if ($day_1 eq q{}) ;
  14084. $zone = ' +0000' if not defined $zone ;
  14085. $d = $day_1 . $date_rest . $hour . $zone ;
  14086. last SWITCH ;
  14087. }
  14088. 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 ) {
  14089. # Handles any combination of following formats
  14090. # Tue, 24 Aug 2010 16:00:00 +0200 -- Standard
  14091. # 24 Aug 2010 16:00:00 +0200 -- Missing Day of Week
  14092. # Tue, 24 Aug 97 16:00:00 +0200 -- Two digit year
  14093. # Tue, 24 Aug 1997 16.00.00 +0200 -- Periods instead of colons
  14094. # Tue, 24 Aug 1997 16:00:00 +0200 -- Extra whitespace between year and hour
  14095. # Tue, 24 Aug 1997 6:5:2 +0200 -- Single digit hour, min, or second
  14096. # Tue, 24, Aug 1997 16:00:00 +0200 -- Extra comma
  14097. #myprint( "header: [$1][$2][$3][$4][$5][$6][$7][$8]\n" ) ;
  14098. my ($day, $month, $year, $hour, $min, $sec, $zone) = ($1,$2,$3,$4,$5,$6,$7,$8);
  14099. $year = '19' . $year if length($year) == 2 && $year =~ m/^[789]/xo;
  14100. $year = '20' . $year if length($year) == 2;
  14101. $month = substr $month, 0, 3 if length($month) > 4;
  14102. $day = mysprintf( '%02d', $day);
  14103. $hour = mysprintf( '%02d', $hour);
  14104. $min = mysprintf( '%02d', $min);
  14105. $sec = '00' if not defined $sec ;
  14106. $sec = mysprintf( '%02d', $sec ) ;
  14107. $zone = '+0000' if not defined $zone ;
  14108. $d = "$day-$month-$year $hour:$min:$sec $zone" ;
  14109. last SWITCH ;
  14110. }
  14111. 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 ) {
  14112. # Handles any combination of following formats
  14113. # Sun Aug 20 11:55:09 2006
  14114. # Wed Jan 24 11:58:38 MST 2007
  14115. # Wed Jan 2 08:40:57 2008
  14116. #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
  14117. my ($month, $day, $hour, $min, $sec, $year) = ($1,$2,$3,$4,$5,$6);
  14118. $day = mysprintf( '%02d', $day ) ;
  14119. $hour = mysprintf( '%02d', $hour ) ;
  14120. $min = mysprintf( '%02d', $min ) ;
  14121. $sec = mysprintf( '%02d', $sec ) ;
  14122. $d = "$day-$month-$year $hour:$min:$sec +0000" ;
  14123. last SWITCH ;
  14124. }
  14125. 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 ) ;
  14126. if ($d =~ m{(\d{4})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) {
  14127. # Handles the following format
  14128. # 2015/07/10 11:05:59 -- Generated by RCS Date tag.
  14129. #myprint( "$d\n" ) ;
  14130. #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
  14131. my ($year, $month, $day, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6) ;
  14132. $month = $num2mon{$month} ;
  14133. $d = "$day-$month-$year $hour:$min:$sec +0000" ;
  14134. #myprint( "$d\n" ) ;
  14135. last SWITCH ;
  14136. }
  14137. if ($d =~ m{(\d{2})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) {
  14138. # Handles the following format
  14139. # 02/06/09 22:18:08 -- Generated by AVTECH TemPageR devices
  14140. #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
  14141. my ($month, $day, $year, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6);
  14142. $year = '20' . $year;
  14143. $month = $num2mon{$month};
  14144. $d = "$day-$month-$year $hour:$min:$sec +0000";
  14145. last SWITCH ;
  14146. }
  14147. 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 ) {
  14148. # Handles the following format
  14149. # Saturday, December 14, 2002 05:00 PM - KBtoys.com order confirmations
  14150. my ($month, $day, $year, $hour, $min, $apm) = ($1,$2,$3,$4,$5,$6);
  14151. $hour += 12 if $apm eq 'PM' ;
  14152. $day = mysprintf( '%02d', $day ) ;
  14153. $d = "$day-$month-$year $hour:$min:00 +0000" ;
  14154. last SWITCH ;
  14155. }
  14156. if ($d =~ m{(\w{3})\s(\d{1,2})\s(\d{4})\s(\d{2}):(\d{2}):(\d{2})\s((?:\+|-)\d{4})}xo ) {
  14157. # Handles the following format
  14158. # Saturday, December 14, 2002 05:00 PM - jr.com order confirmations
  14159. my ($month, $day, $year, $hour, $min, $sec, $zone) = ($1,$2,$3,$4,$5,$6,$7);
  14160. $day = mysprintf( '%02d', $day ) ;
  14161. $d = "$day-$month-$year $hour:$min:$sec $zone";
  14162. last SWITCH ;
  14163. }
  14164. if ($d =~ m{(\d{1,2})-(\w{3})-(\d{4})}xo ) {
  14165. # Handles the following format
  14166. # 21-Jun-2001 - register.com domain transfer email circa 2001
  14167. my ($day, $month, $year) = ($1,$2,$3);
  14168. $day = mysprintf( '%02d', $day);
  14169. $d = "$day-$month-$year 11:11:11 +0000";
  14170. last SWITCH ;
  14171. }
  14172. # unknown or unmatch => return same string
  14173. return($d);
  14174. }
  14175. $d = qq("$d") ;
  14176. return( $d ) ;
  14177. }
  14178. sub tests_good_date
  14179. {
  14180. note( 'Entering tests_good_date()' ) ;
  14181. ok(q{} eq good_date(), 'good_date no arg');
  14182. ok('"24-Aug-2010 16:00:00 +0200"' eq good_date('24-Aug-2010 16:00:00 +0200'), 'good_date internal 2digit zone');
  14183. ok('"24-Aug-2010 16:00:00 +0000"' eq good_date('24-Aug-2010 16:00:00'), 'good_date internal 2digit no zone');
  14184. ok('"01-Sep-2010 16:00:00 +0200"' eq good_date( '1-Sep-2010 16:00:00 +0200'), 'good_date internal SP 1digit');
  14185. ok('"24-Aug-2010 16:00:00 +0200"' eq good_date('Tue, 24 Aug 2010 16:00:00 +0200'), 'good_date header 2digit zone');
  14186. ok('"01-Sep-2010 16:00:00 +0000"' eq good_date('Wed, 1 Sep 2010 16:00:00'), 'good_date header SP 1digit zone');
  14187. 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');
  14188. 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');
  14189. ok('"06-Feb-2009 22:18:08 +0000"' eq good_date('02/06/09 22:18:08'), 'good_date header TemPageR');
  14190. 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');
  14191. 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');
  14192. 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');
  14193. 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');
  14194. ok('"24-Aug-2067 16:00:00 +0200"' eq good_date('Tue, 24 Aug 67 16:00:00 +0200'), 'good_date header 2digit year');
  14195. ok('"24-Aug-1977 16:00:00 +0200"' eq good_date('Tue, 24 Aug 77 16:00:00 +0200'), 'good_date header 2digit year');
  14196. ok('"24-Aug-1987 16:00:00 +0200"' eq good_date('Tue, 24 Aug 87 16:00:00 +0200'), 'good_date header 2digit year');
  14197. ok('"24-Aug-1997 16:00:00 +0200"' eq good_date('Tue, 24 Aug 97 16:00:00 +0200'), 'good_date header 2digit year');
  14198. ok('"24-Aug-2004 16:00:00 +0200"' eq good_date('Tue, 24 Aug 04 16:00:00 +0200'), 'good_date header 2digit year');
  14199. 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');
  14200. 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');
  14201. 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');
  14202. ok('"24-Aug-1997 05:06:02 +0200"' eq good_date('Tue, 24, Aug 1997 05:06:02 +0200'), 'good_date header extra commas');
  14203. ok('"01-Oct-2003 12:45:24 +0000"' eq good_date('Wednesday, 01 October 2003 12:45:24 CDT'), 'good_date header no abbrev');
  14204. ok('"11-Jan-2005 17:58:27 -0500"' eq good_date('Tue, 11 Jan 2005 17:58:27 -0500'), 'good_date extra white space');
  14205. ok('"18-Dec-2002 15:07:00 +0000"' eq good_date('Wednesday, December 18, 2002 03:07 PM'), 'good_date kbtoys.com orders');
  14206. ok('"16-Dec-2004 02:01:49 -0500"' eq good_date('Dec 16 2004 02:01:49 -0500'), 'good_date jr.com orders');
  14207. ok('"21-Jun-2001 11:11:11 +0000"' eq good_date('21-Jun-2001'), 'good_date register.com domain transfer');
  14208. 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)');
  14209. ok('"19-Sep-2015 16:11:07 +0000"' eq good_date('Date: 2015/09/19 16:11:07 '), 'good_date from RCS date' ) ;
  14210. note( 'Leaving tests_good_date()' ) ;
  14211. return ;
  14212. }
  14213. sub tests_list_keys_in_2_not_in_1
  14214. {
  14215. note( 'Entering tests_list_keys_in_2_not_in_1()' ) ;
  14216. my @list;
  14217. ok( ! list_keys_in_2_not_in_1( {}, {}), 'list_keys_in_2_not_in_1: {} {}');
  14218. ok( 0 == compare_lists( [], [ list_keys_in_2_not_in_1( {}, {} ) ] ), 'list_keys_in_2_not_in_1: {} {}');
  14219. 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}');
  14220. 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}');
  14221. 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}');
  14222. 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}');
  14223. 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}');
  14224. note( 'Leaving tests_list_keys_in_2_not_in_1()' ) ;
  14225. return ;
  14226. }
  14227. sub list_keys_in_2_not_in_1
  14228. {
  14229. my $hash_1_ref = shift;
  14230. my $hash_2_ref = shift;
  14231. my @list;
  14232. foreach my $key ( sort keys %{ $hash_2_ref } ) {
  14233. #$sync->{ debug } and print "$key\n" ;
  14234. if ( exists $hash_1_ref->{$key} )
  14235. {
  14236. next ;
  14237. }
  14238. #$sync->{ debug } and print "list_keys_in_2_not_in_1: $key\n" ;
  14239. push @list, $key ;
  14240. }
  14241. #$sync->{ debug } and print "@list\n" ;
  14242. return( @list ) ;
  14243. }
  14244. sub list_folders_in_2_not_in_1
  14245. {
  14246. my ( @h2_folders_not_in_h1, %h2_folders_not_in_h1 ) ;
  14247. @h2_folders_not_in_h1 = list_keys_in_2_not_in_1( \%h1_folders_all, \%h2_folders_all ) ;
  14248. map { $h2_folders_not_in_h1{$_} = 1} @h2_folders_not_in_h1 ;
  14249. @h2_folders_not_in_h1 = list_keys_in_2_not_in_1( \%h2_folders_from_1_all, \%h2_folders_not_in_h1 ) ;
  14250. #$sync->{ debug } and print "h2_folders_not_in_h1: @h2_folders_not_in_h1\n" ;
  14251. return( reverse @h2_folders_not_in_h1 ) ;
  14252. }
  14253. sub tests_nb_messages_in_2_not_in_1
  14254. {
  14255. note( 'Entering tests_stats_across_folders()' ) ;
  14256. is( undef, nb_messages_in_2_not_in_1( ), 'nb_messages_in_2_not_in_1: no args => undef' ) ;
  14257. my $mysync->{ h1_folders_of_md5 }->{ 'some_id_01' }->{ 'some_folder_01' } = 1 ;
  14258. is( 0, nb_messages_in_2_not_in_1( $mysync ), 'nb_messages_in_2_not_in_1: no messages in 2 => 0' ) ;
  14259. $mysync->{ h1_folders_of_md5 }->{ 'some_id_in_1_and_2' }->{ 'some_folder_01' } = 2 ;
  14260. $mysync->{ h2_folders_of_md5 }->{ 'some_id_in_1_and_2' }->{ 'some_folder_02' } = 4 ;
  14261. is( 0, nb_messages_in_2_not_in_1( $mysync ), 'nb_messages_in_2_not_in_1: a common message => 0' ) ;
  14262. $mysync->{ h2_folders_of_md5 }->{ 'some_id_in_2_not_in_1' }->{ 'some_folder_02' } = 1 ;
  14263. 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' ) ;
  14264. $mysync->{ h2_folders_of_md5 }->{ 'some_other_id_in_2_not_in_1' }->{ 'some_folder_02' } = 3 ;
  14265. 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' ) ;
  14266. note( 'Leaving tests_stats_across_folders()' ) ;
  14267. return ;
  14268. }
  14269. sub nb_messages_in_2_not_in_1
  14270. {
  14271. my $mysync = shift @ARG ;
  14272. if ( not defined $mysync ) { return ; }
  14273. $mysync->{ nb_messages_in_2_not_in_1 } = scalar(
  14274. list_keys_in_2_not_in_1(
  14275. $mysync->{ h1_folders_of_md5 },
  14276. $mysync->{ h2_folders_of_md5 } ) ) ;
  14277. return $mysync->{ nb_messages_in_2_not_in_1 } ;
  14278. }
  14279. sub nb_messages_in_1_not_in_2
  14280. {
  14281. my $mysync = shift @ARG ;
  14282. if ( not defined $mysync ) { return ; }
  14283. $mysync->{ nb_messages_in_1_not_in_2 } = scalar(
  14284. list_keys_in_2_not_in_1(
  14285. $mysync->{ h2_folders_of_md5 },
  14286. $mysync->{ h1_folders_of_md5 } ) ) ;
  14287. return $mysync->{ nb_messages_in_1_not_in_2 } ;
  14288. }
  14289. sub comment_on_final_diff_in_1_not_in_2
  14290. {
  14291. my $mysync = shift @ARG ;
  14292. if ( not defined $mysync
  14293. or $mysync->{ justfolders }
  14294. or $mysync->{ useuid }
  14295. )
  14296. {
  14297. return ;
  14298. }
  14299. my $nb_identified_h1_messages = scalar( keys %{ $mysync->{ h1_folders_of_md5 } } ) ;
  14300. my $nb_identified_h2_messages = scalar( keys %{ $mysync->{ h2_folders_of_md5 } } ) ;
  14301. $mysync->{ debug } and myprint( "nb_keys h1_folders_of_md5 $nb_identified_h1_messages\n" ) ;
  14302. $mysync->{ debug } and myprint( "nb_keys h2_folders_of_md5 $nb_identified_h2_messages\n" ) ;
  14303. if ( 0 == $nb_identified_h1_messages ) { return ; }
  14304. # Calculate if not yet done
  14305. if ( not defined $mysync->{ nb_messages_in_1_not_in_2 } )
  14306. {
  14307. nb_messages_in_1_not_in_2( $mysync ) ;
  14308. }
  14309. if ( 0 == $mysync->{ nb_messages_in_1_not_in_2 } )
  14310. {
  14311. myprint( "The sync looks good, all ",
  14312. $nb_identified_h1_messages,
  14313. " identified messages in host1 are on host2.\n" ) ;
  14314. }
  14315. else
  14316. {
  14317. myprint( "The sync is not finished, there are ",
  14318. $mysync->{ nb_messages_in_1_not_in_2 },
  14319. " among ",
  14320. $nb_identified_h1_messages,
  14321. " identified messages in host1 that are not on host2.\n" ) ;
  14322. }
  14323. if ( 1 <= $mysync->{ h1_nb_msg_noheader } )
  14324. {
  14325. myprint( "There are ",
  14326. $mysync->{ h1_nb_msg_noheader },
  14327. " unidentified messages (usually Sent or Draft messages).",
  14328. " To sync them add option --addheader\n" ) ;
  14329. }
  14330. else
  14331. {
  14332. myprint( "There is no unidentified message on host1.\n" ) ;
  14333. }
  14334. return ;
  14335. }
  14336. sub comment_on_final_diff_in_2_not_in_1
  14337. {
  14338. my $mysync = shift @ARG ;
  14339. if ( not defined $mysync
  14340. or $mysync->{ justfolders }
  14341. or $mysync->{ useuid }
  14342. )
  14343. {
  14344. return ;
  14345. }
  14346. my $nb_identified_h2_messages = scalar( keys %{ $mysync->{ h2_folders_of_md5 } } ) ;
  14347. # Calculate if not done yet
  14348. if ( not defined $mysync->{ nb_messages_in_2_not_in_1 } )
  14349. {
  14350. nb_messages_in_2_not_in_1( $mysync ) ;
  14351. }
  14352. if ( 0 == $mysync->{ nb_messages_in_2_not_in_1 } )
  14353. {
  14354. myprint( "The sync is strict, all ",
  14355. $nb_identified_h2_messages,
  14356. " identified messages in host2 are on host1.\n" ) ;
  14357. }
  14358. else
  14359. {
  14360. myprint( "The sync is not strict, there are ",
  14361. $mysync->{ nb_messages_in_2_not_in_1 },
  14362. " among ",
  14363. $nb_identified_h2_messages,
  14364. " identified messages in host2 that are not on host1.",
  14365. " Use --delete2 and sync again to delete them and have a strict sync.\n"
  14366. ) ;
  14367. }
  14368. return ;
  14369. }
  14370. sub tests_match
  14371. {
  14372. note( 'Entering tests_match()' ) ;
  14373. # undef serie
  14374. is( undef, match( ), 'match: no args => undef' ) ;
  14375. is( undef, match( 'lalala' ), 'match: one args => undef' ) ;
  14376. # This one gives 0 under a binary made by pp
  14377. # but 1 under "normal" Perl interpreter. So a PAR bug?
  14378. #is( 1, match( q{}, q{} ), 'match: q{} =~ q{} => 1' ) ;
  14379. is( 'lalala', match( 'lalala', 'lalala' ), 'match: lalala =~ lalala => lalala' ) ;
  14380. is( 'lalala', match( 'lalala', '^lalala' ), 'match: lalala =~ ^lalala => lalala' ) ;
  14381. is( 'lalala', match( 'lalala', 'lalala$' ), 'match: lalala =~ lalala$ => lalala' ) ;
  14382. is( 'lalala', match( 'lalala', '^lalala$' ), 'match: lalala =~ ^lalala$ => lalala' ) ;
  14383. is( '_lalala_', match( '_lalala_', 'lalala' ), 'match: _lalala_ =~ lalala => _lalala_' ) ;
  14384. is( 'lalala', match( 'lalala', '.*' ), 'match: lalala =~ .* => lalala' ) ;
  14385. is( 'lalala', match( 'lalala', '.' ), 'match: lalala =~ . => lalala' ) ;
  14386. is( '/lalala/', match( '/lalala/', '/lalala/' ), 'match: /lalala/ =~ /lalala/ => /lalala/' ) ;
  14387. is( 0, match( 'foo', 's/foo/bar/g' ), 'match: foo =~ s/foo/bar/g => 0' ) ;
  14388. 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' ) ;
  14389. is( 0, match( 'lalala', 'ooo' ), 'match: lalala =~ ooo => 0' ) ;
  14390. is( 0, match( 'lalala', 'lal_ala' ), 'match: lalala =~ lal_ala => 0' ) ;
  14391. is( 0, match( 'lalala', '\.' ), 'match: lalala =~ \. => 0' ) ;
  14392. is( 0, match( 'lalalaX', '^lalala$' ), 'match: lalalaX =~ ^lalala$ => 0' ) ;
  14393. is( 0, match( 'lalala', '/lalala/' ), 'match: lalala =~ /lalala/ => 0' ) ;
  14394. is( 'LALALA', match( 'LALALA', '(?i:lalala)' ), 'match: LALALA =~ (?i:lalala) => 1' ) ;
  14395. is( undef, match( 'LALALA', '(?{`ls /`})' ), 'match: LALALA =~ (?{`ls /`}) => undef' ) ;
  14396. is( undef, match( 'LALALA', '(?{print "CACA"})' ), 'match: LALALA =~ (?{print "CACA"}) => undef' ) ;
  14397. is( undef, match( 'CACA', '(??{print "CACA"})' ), 'match: CACA =~ (??{print "CACA"}) => undef' ) ;
  14398. note( 'Leaving tests_match()' ) ;
  14399. return ;
  14400. }
  14401. sub match
  14402. {
  14403. my( $var, $regex ) = @ARG ;
  14404. # undef cases
  14405. if ( ( ! defined $var ) or ( ! defined $regex ) ) { return ; }
  14406. # normal cases
  14407. if ( eval { $var =~ qr{$regex} } ) {
  14408. return $var ;
  14409. }elsif ( $EVAL_ERROR ) {
  14410. myprint( "Fatal regex $regex\n" ) ;
  14411. return ;
  14412. } else {
  14413. return 0 ;
  14414. }
  14415. return ;
  14416. }
  14417. sub tests_notmatch
  14418. {
  14419. note( 'Entering tests_notmatch()' ) ;
  14420. # undef serie
  14421. is( undef, notmatch( ), 'notmatch: no args => undef' ) ;
  14422. is( undef, notmatch( 'lalala' ), 'notmatch: one args => undef' ) ;
  14423. is( 1, notmatch( 'lalala', '/lalala/' ), 'notmatch: lalala !~ /lalala/ => 1' ) ;
  14424. is( 0, notmatch( '/lalala/', '/lalala/' ), 'notmatch: /lalala/ !~ /lalala/ => 0' ) ;
  14425. is( 1, notmatch( 'lalala', '/ooo/' ), 'notmatch: lalala !~ /ooo/ => 1' ) ;
  14426. # This one gives 1 under a binary made by pp
  14427. # but 0 under "normal" Perl interpreter. So a PAR bug, same in tests_match .
  14428. #is( 0, notmatch( q{}, q{} ), 'notmatch: q{} !~ q{} => 0' ) ;
  14429. is( 0, notmatch( 'lalala', 'lalala' ), 'notmatch: lalala !~ lalala => 0' ) ;
  14430. is( 0, notmatch( 'lalala', '^lalala' ), 'notmatch: lalala !~ ^lalala => 0' ) ;
  14431. is( 0, notmatch( 'lalala', 'lalala$' ), 'notmatch: lalala !~ lalala$ => 0' ) ;
  14432. is( 0, notmatch( 'lalala', '^lalala$' ), 'notmatch: lalala !~ ^lalala$ => 0' ) ;
  14433. is( 0, notmatch( '_lalala_', 'lalala' ), 'notmatch: _lalala_ !~ lalala => 0' ) ;
  14434. is( 0, notmatch( 'lalala', '.*' ), 'notmatch: lalala !~ .* => 0' ) ;
  14435. is( 0, notmatch( 'lalala', '.' ), 'notmatch: lalala !~ . => 0' ) ;
  14436. is( 1, notmatch( 'lalala', 'ooo' ), 'notmatch: does not match regex => 1' ) ;
  14437. is( 1, notmatch( 'lalala', 'lal_ala' ), 'notmatch: does not match regex => 1' ) ;
  14438. is( 1, notmatch( 'lalala', '\.' ), 'notmatch: matches regex => 0' ) ;
  14439. is( 1, notmatch( 'lalalaX', '^lalala$' ), 'notmatch: does not match regex => 1' ) ;
  14440. note( 'Leaving tests_notmatch()' ) ;
  14441. return ;
  14442. }
  14443. sub notmatch
  14444. {
  14445. my( $var, $regex ) = @ARG ;
  14446. # undef cases
  14447. if ( ( ! defined $var ) or ( ! defined $regex ) ) { return ; }
  14448. # normal cases
  14449. if ( eval { $var !~ $regex } ) {
  14450. return 1 ;
  14451. }elsif ( $EVAL_ERROR ) {
  14452. myprint( "Fatal regex $regex\n" ) ;
  14453. return ;
  14454. }else{
  14455. return 0 ;
  14456. }
  14457. return ;
  14458. }
  14459. sub delete_folders_in_2_not_in_1
  14460. {
  14461. foreach my $folder ( @h2_folders_not_in_1 ) {
  14462. if ( defined $delete2foldersonly and eval "\$folder !~ $delete2foldersonly" ) {
  14463. myprint( "Not deleting $folder because of --delete2foldersonly $delete2foldersonly\n" ) ;
  14464. next ;
  14465. }
  14466. if ( defined $delete2foldersbutnot and eval "\$folder =~ $delete2foldersbutnot" ) {
  14467. myprint( "Not deleting $folder because of --delete2foldersbutnot $delete2foldersbutnot\n" ) ;
  14468. next ;
  14469. }
  14470. my $res = $sync->{dry} ; # always success in dry mode!
  14471. $sync->{imap2}->unsubscribe( $folder ) if ( ! $sync->{dry} ) ;
  14472. $res = $sync->{imap2}->delete( $folder ) if ( ! $sync->{dry} ) ;
  14473. if ( $res ) {
  14474. myprint( "Deleted $folder", "$sync->{dry_message}", "\n" ) ;
  14475. }else{
  14476. myprint( "Deleting $folder failed", "\n" ) ;
  14477. }
  14478. }
  14479. return ;
  14480. }
  14481. sub delete_folder
  14482. {
  14483. my ( $mysync, $imap, $folder, $Side ) = @_ ;
  14484. if ( ! $mysync ) { return ; }
  14485. if ( ! $imap ) { return ; }
  14486. if ( ! $folder ) { return ; }
  14487. $Side ||= 'HostX' ;
  14488. my $res = $mysync->{dry} ; # always success in dry mode!
  14489. if ( ! $mysync->{dry} ) {
  14490. $imap->unsubscribe( $folder ) ;
  14491. $res = $imap->delete( $folder ) ;
  14492. }
  14493. if ( $res ) {
  14494. myprint( "$Side deleted $folder", $mysync->{dry_message}, "\n" ) ;
  14495. return 1 ;
  14496. }else{
  14497. myprint( "$Side deleting $folder failed", "\n" ) ;
  14498. return ;
  14499. }
  14500. }
  14501. sub delete1emptyfolders
  14502. {
  14503. my $mysync = shift @ARG ;
  14504. if ( ! $mysync ) { return ; } # abort if no parameter
  14505. if ( ! $mysync->{delete1emptyfolders} ) { return ; } # abort if --delete1emptyfolders off
  14506. my $imap = $mysync->{imap1} ;
  14507. if ( ! $imap ) { return ; } # abort if no imap
  14508. if ( $imap->IsUnconnected( ) ) { return ; } # abort if disconnected
  14509. my %folders_kept ;
  14510. myprint( qq{Host1 deleting empty folders\n} ) ;
  14511. foreach my $folder ( reverse sort @{ $mysync->{h1_folders_wanted} } ) {
  14512. my $parenthood = $imap->is_parent( $folder ) ;
  14513. if ( defined $parenthood and $parenthood ) {
  14514. myprint( "Host1: folder $folder has subfolders\n" ) ;
  14515. $folders_kept{ $folder }++ ;
  14516. next ;
  14517. }
  14518. my $nb_messages_select = examine_folder_and_count( $mysync, $imap, $folder, 'Host1' ) ;
  14519. if ( ! defined $nb_messages_select ) { next ; } # Select failed => Neither continue nor keep this folder }
  14520. my $nb_messages_search = scalar( @{ $imap->messages( ) } ) ;
  14521. if ( 0 != $nb_messages_select and 0 != $nb_messages_search ) {
  14522. myprint( "Host1: folder $folder has messages: $nb_messages_search (search) $nb_messages_select (select)\n" ) ;
  14523. $folders_kept{ $folder }++ ;
  14524. next ;
  14525. }
  14526. if ( 0 != $nb_messages_select + $nb_messages_search ) {
  14527. myprint( "Host1: folder $folder odd messages count: $nb_messages_search (search) $nb_messages_select (select)\n" ) ;
  14528. $folders_kept{ $folder }++ ;
  14529. next ;
  14530. }
  14531. # Here we must have 0 messages by messages() aka "SEARCH ALL" and also "EXAMINE"
  14532. if ( uc $folder eq 'INBOX' ) {
  14533. myprint( "Host1: Not deleting $folder\n" ) ;
  14534. $folders_kept{ $folder }++ ;
  14535. next ;
  14536. }
  14537. myprint( "Host1: deleting empty folder $folder\n" ) ;
  14538. # can not delete a SELECTed or EXAMINEd folder so closing it
  14539. # could changed be SELECT INBOX
  14540. $imap->close( ) ; # close after examine does not expunge; anyway expunging an empty folder...
  14541. if ( delete_folder( $mysync, $imap, $folder, 'Host1' ) ) {
  14542. next ; # Deleted, good!
  14543. }else{
  14544. $folders_kept{ $folder }++ ;
  14545. next ; # Not deleted, bad!
  14546. }
  14547. }
  14548. remove_deleted_folders_from_wanted_list( $mysync, %folders_kept ) ;
  14549. myprint( qq{Host1 ended deleting empty folders\n} ) ;
  14550. return ;
  14551. }
  14552. sub remove_deleted_folders_from_wanted_list
  14553. {
  14554. my ( $mysync, %folders_kept ) = @ARG ;
  14555. my @h1_folders_wanted_init = @{ $mysync->{h1_folders_wanted} } ;
  14556. my @h1_folders_wanted_last ;
  14557. foreach my $folder ( @h1_folders_wanted_init ) {
  14558. if ( $folders_kept{ $folder } ) {
  14559. push @h1_folders_wanted_last, $folder ;
  14560. }
  14561. }
  14562. @{ $mysync->{h1_folders_wanted} } = @h1_folders_wanted_last ;
  14563. return ;
  14564. }
  14565. sub examine_folder_and_count
  14566. {
  14567. my ( $mysync, $imap, $folder, $Side ) = @_ ;
  14568. $Side ||= 'HostX' ;
  14569. if ( ! examine_folder( $mysync, $imap, $folder, $Side ) ) {
  14570. return ;
  14571. }
  14572. my $nb_messages_select = count_from_select( $imap->History ) ;
  14573. return $nb_messages_select ;
  14574. }
  14575. sub tests_delete1emptyfolders
  14576. {
  14577. note( 'Entering tests_delete1emptyfolders()' ) ;
  14578. is( undef, delete1emptyfolders( ), q{delete1emptyfolders: undef} ) ;
  14579. my $syncT ;
  14580. is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: undef 2} ) ;
  14581. my $imapT ;
  14582. $syncT->{imap1} = $imapT ;
  14583. is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: undef imap} ) ;
  14584. require_ok( "Test::MockObject" ) ;
  14585. $imapT = Test::MockObject->new( ) ;
  14586. $syncT->{imap1} = $imapT ;
  14587. $imapT->set_true( 'IsUnconnected' ) ;
  14588. is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: Unconnected imap} ) ;
  14589. # Now connected tests
  14590. $imapT->set_false( 'IsUnconnected' ) ;
  14591. $imapT->mock( 'LastError', sub { q{LastError mocked} } ) ;
  14592. $syncT->{delete1emptyfolders} = 0 ;
  14593. tests_delete1emptyfolders_unit(
  14594. $syncT,
  14595. [ qw{ INBOX DELME1 DELME2 } ],
  14596. [ qw{ INBOX DELME1 DELME2 } ],
  14597. q{tests_delete1emptyfolders: --delete1emptyfolders OFF}
  14598. ) ;
  14599. # All are parents => no deletion at all
  14600. $imapT->set_true( 'is_parent' ) ;
  14601. $syncT->{delete1emptyfolders} = 1 ;
  14602. tests_delete1emptyfolders_unit(
  14603. $syncT,
  14604. [ qw{ INBOX DELME1 DELME2 } ],
  14605. [ qw{ INBOX DELME1 DELME2 } ],
  14606. q{tests_delete1emptyfolders: --delete1emptyfolders ON}
  14607. ) ;
  14608. # No parents but examine false for all => skip all
  14609. $imapT->set_false( 'is_parent', 'examine' ) ;
  14610. tests_delete1emptyfolders_unit(
  14611. $syncT,
  14612. [ qw{ INBOX DELME1 DELME2 } ],
  14613. [ ],
  14614. q{tests_delete1emptyfolders: EXAMINE fails}
  14615. ) ;
  14616. # examine ok for all but History bad => skip all
  14617. $imapT->set_true( 'examine' ) ;
  14618. $imapT->mock( 'History', sub { ( q{History badly mocked} ) } ) ;
  14619. tests_delete1emptyfolders_unit(
  14620. $syncT,
  14621. [ qw{ INBOX DELME1 DELME2 } ],
  14622. [ ],
  14623. q{tests_delete1emptyfolders: examine ok but History badly mocked so count messages fails}
  14624. ) ;
  14625. # History good but some messages EXISTS == messages() => no deletion
  14626. $imapT->mock( 'History', sub { ( q{* 2 EXISTS} ) } ) ;
  14627. $imapT->mock( 'messages', sub { [ qw{ UID_1 UID_2 } ] } ) ;
  14628. tests_delete1emptyfolders_unit(
  14629. $syncT,
  14630. [ qw{ INBOX DELME1 DELME2 } ],
  14631. [ qw{ INBOX DELME1 DELME2 } ],
  14632. q{tests_delete1emptyfolders: History EXAMINE ok, several messages}
  14633. ) ;
  14634. # 0 EXISTS but != messages() => no deletion
  14635. $imapT->mock( 'History', sub { ( q{* 0 EXISTS} ) } ) ;
  14636. $imapT->mock( 'messages', sub { [ qw{ UID_1 UID_2 } ] } ) ;
  14637. tests_delete1emptyfolders_unit(
  14638. $syncT,
  14639. [ qw{ INBOX DELME1 DELME2 } ],
  14640. [ qw{ INBOX DELME1 DELME2 } ],
  14641. q{tests_delete1emptyfolders: 0 EXISTS but 2 by messages()}
  14642. ) ;
  14643. # 1 EXISTS but != 0 == messages() => no deletion
  14644. $imapT->mock( 'History', sub { ( q{* 1 EXISTS} ) } ) ;
  14645. $imapT->mock( 'messages', sub { [ ] } ) ;
  14646. tests_delete1emptyfolders_unit(
  14647. $syncT,
  14648. [ qw{ INBOX DELME1 DELME2 } ],
  14649. [ qw{ INBOX DELME1 DELME2 } ],
  14650. q{tests_delete1emptyfolders: 1 EXISTS but 0 by messages()}
  14651. ) ;
  14652. # 0 EXISTS and 0 == messages() => deletion except INBOX
  14653. $imapT->mock( 'History', sub { ( q{* 0 EXISTS} ) } ) ;
  14654. $imapT->mock( 'messages', sub { [ ] } ) ;
  14655. $imapT->set_true( qw{ delete close unsubscribe } ) ;
  14656. $syncT->{dry_message} = q{ (not really since in a mocked test)} ;
  14657. tests_delete1emptyfolders_unit(
  14658. $syncT,
  14659. [ qw{ INBOX DELME1 DELME2 } ],
  14660. [ qw{ INBOX } ],
  14661. q{tests_delete1emptyfolders: 0 EXISTS 0 by messages() delete folders, keep INBOX}
  14662. ) ;
  14663. note( 'Leaving tests_delete1emptyfolders()' ) ;
  14664. return ;
  14665. }
  14666. sub tests_delete1emptyfolders_unit
  14667. {
  14668. note( 'Entering tests_delete1emptyfolders_unit()' ) ;
  14669. my $syncT = shift @ARG ;
  14670. my $folders1wanted_init_ref = shift @ARG ;
  14671. my $folders1wanted_after_ref = shift @ARG ;
  14672. my $comment = shift || q{delete1emptyfolders:} ;
  14673. my @folders1wanted_init = @{ $folders1wanted_init_ref } ;
  14674. my @folders1wanted_after = @{ $folders1wanted_after_ref } ;
  14675. @{ $syncT->{h1_folders_wanted} } = @folders1wanted_init ;
  14676. is_deeply( $syncT->{h1_folders_wanted}, \@folders1wanted_init, qq{$comment, init check} ) ;
  14677. delete1emptyfolders( $syncT ) ;
  14678. is_deeply( $syncT->{h1_folders_wanted}, \@folders1wanted_after, qq{$comment, after check} ) ;
  14679. note( 'Leaving tests_delete1emptyfolders_unit()' ) ;
  14680. return ;
  14681. }
  14682. sub extract_header
  14683. {
  14684. my $string = shift @ARG ;
  14685. my ( $header ) = split /\n\n/x, $string ;
  14686. if ( ! $header ) { return( q{} ) ; }
  14687. #myprint( "[$header]\n" ) ;
  14688. return( $header ) ;
  14689. }
  14690. sub tests_extract_header
  14691. {
  14692. note( 'Entering tests_extract_header()' ) ;
  14693. my $h = <<'EOM';
  14694. Message-Id: <20100428101817.A66CB162474E@plume.est.belle>
  14695. Date: Wed, 28 Apr 2010 12:18:17 +0200 (CEST)
  14696. From: gilles@louloutte.dyndns.org (Gilles LAMIRAL)
  14697. EOM
  14698. chomp $h ;
  14699. ok( $h eq extract_header(
  14700. <<'EOM'
  14701. Message-Id: <20100428101817.A66CB162474E@plume.est.belle>
  14702. Date: Wed, 28 Apr 2010 12:18:17 +0200 (CEST)
  14703. From: gilles@louloutte.dyndns.org (Gilles LAMIRAL)
  14704. body
  14705. lalala
  14706. EOM
  14707. ), 'extract_header: 1') ;
  14708. note( 'Leaving tests_extract_header()' ) ;
  14709. return ;
  14710. }
  14711. sub decompose_header{
  14712. my $string = shift @ARG ;
  14713. # a hash, for a keyword header KEY value are list of strings [VAL1, VAL1_other, etc]
  14714. # Think of multiple "Received:" header lines.
  14715. my $header = { } ;
  14716. my ($key, $val ) ;
  14717. my @line = split /\n|\r\n/x, $string ;
  14718. foreach my $line ( @line ) {
  14719. #myprint( "DDD $line\n" ) ;
  14720. # End of header
  14721. last if ( $line =~ m{^$}xo ) ;
  14722. # Key: value
  14723. if ( $line =~ m/(^[^:]+):\s(.*)/xo ) {
  14724. $key = $1 ;
  14725. $val = $2 ;
  14726. $debugdev and myprint( "DDD KV [$key] [$val]\n" ) ;
  14727. push @{ $header->{ $key } }, $val ;
  14728. # blanc and value => value from previous line continues
  14729. }elsif( $line =~ m/^(\s+)(.*)/xo ) {
  14730. $val = $2 ;
  14731. $debugdev and myprint( "DDD V [$val]\n" ) ;
  14732. @{ $header->{ $key } }[ $LAST ] .= " $val" if $key ;
  14733. # dirty line?
  14734. }else{
  14735. next ;
  14736. }
  14737. }
  14738. #myprint( Data::Dumper->Dump( [ $header ] ) ) ;
  14739. return( $header ) ;
  14740. }
  14741. sub tests_decompose_header{
  14742. note( 'Entering tests_decompose_header()' ) ;
  14743. my $header_dec ;
  14744. $header_dec = decompose_header(
  14745. <<'EOH'
  14746. KEY_1: VAL_1
  14747. KEY_2: VAL_2
  14748. VAL_2_+
  14749. VAL_2_++
  14750. KEY_3: VAL_3
  14751. KEY_1: VAL_1_other
  14752. KEY_4: VAL_4
  14753. VAL_4_+
  14754. KEY_5 BLANC: VAL_5
  14755. KEY_6_BAD_BODY: VAL_6
  14756. EOH
  14757. ) ;
  14758. ok( 'VAL_3'
  14759. eq $header_dec->{ 'KEY_3' }[0], 'decompose_header: VAL_3' ) ;
  14760. ok( 'VAL_1'
  14761. eq $header_dec->{ 'KEY_1' }[0], 'decompose_header: VAL_1' ) ;
  14762. ok( 'VAL_1_other'
  14763. eq $header_dec->{ 'KEY_1' }[1], 'decompose_header: VAL_1_other' ) ;
  14764. ok( 'VAL_2 VAL_2_+ VAL_2_++'
  14765. eq $header_dec->{ 'KEY_2' }[0], 'decompose_header: VAL_2 VAL_2_+ VAL_2_++' ) ;
  14766. ok( 'VAL_4 VAL_4_+'
  14767. eq $header_dec->{ 'KEY_4' }[0], 'decompose_header: VAL_4 VAL_4_+' ) ;
  14768. ok( ' VAL_5'
  14769. eq $header_dec->{ 'KEY_5 BLANC' }[0], 'decompose_header: KEY_5 BLANC' ) ;
  14770. ok( not( defined $header_dec->{ 'KEY_6_BAD_BODY' }[0] ), 'decompose_header: KEY_6_BAD_BODY' ) ;
  14771. $header_dec = decompose_header(
  14772. <<'EOH'
  14773. Message-Id: <20100428101817.A66CB162474E@plume.est.belle>
  14774. Date: Wed, 28 Apr 2010 12:18:17 +0200 (CEST)
  14775. From: gilles@louloutte.dyndns.org (Gilles LAMIRAL)
  14776. EOH
  14777. ) ;
  14778. ok( '<20100428101817.A66CB162474E@plume.est.belle>'
  14779. eq $header_dec->{ 'Message-Id' }[0], 'decompose_header: 1' ) ;
  14780. $header_dec = decompose_header(
  14781. <<'EOH'
  14782. Return-Path: <gilles@louloutte.dyndns.org>
  14783. Received: by plume.est.belle (Postfix, from userid 1000)
  14784. id 120A71624742; Wed, 28 Apr 2010 01:46:40 +0200 (CEST)
  14785. Subject: test:eekahceishukohpe
  14786. EOH
  14787. ) ;
  14788. ok(
  14789. 'by plume.est.belle (Postfix, from userid 1000) id 120A71624742; Wed, 28 Apr 2010 01:46:40 +0200 (CEST)'
  14790. eq $header_dec->{ 'Received' }[0], 'decompose_header: 2' ) ;
  14791. $header_dec = decompose_header(
  14792. <<'EOH'
  14793. Received: from plume (localhost [127.0.0.1])
  14794. by plume.est.belle (Postfix) with ESMTP id C6EB73F6C9
  14795. for <gilles@localhost>; Mon, 26 Nov 2007 10:39:06 +0100 (CET)
  14796. Received: from plume [192.168.68.7]
  14797. by plume with POP3 (fetchmail-6.3.6)
  14798. for <gilles@localhost> (single-drop); Mon, 26 Nov 2007 10:39:06 +0100 (CET)
  14799. EOH
  14800. ) ;
  14801. ok(
  14802. '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)'
  14803. eq $header_dec->{ 'Received' }[0], 'decompose_header: 3' ) ;
  14804. ok(
  14805. '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)'
  14806. eq $header_dec->{ 'Received' }[1], 'decompose_header: 3' ) ;
  14807. # Bad header beginning with a blank character
  14808. $header_dec = decompose_header(
  14809. <<'EOH'
  14810. KEY_1: VAL_1
  14811. KEY_2: VAL_2
  14812. VAL_2_+
  14813. VAL_2_++
  14814. KEY_3: VAL_3
  14815. KEY_1: VAL_1_other
  14816. EOH
  14817. ) ;
  14818. ok( 'VAL_3'
  14819. eq $header_dec->{ 'KEY_3' }[0], 'decompose_header: Bad header VAL_3' ) ;
  14820. ok( 'VAL_1_other'
  14821. eq $header_dec->{ 'KEY_1' }[0], 'decompose_header: Bad header VAL_1_other' ) ;
  14822. ok( 'VAL_2 VAL_2_+ VAL_2_++'
  14823. eq $header_dec->{ 'KEY_2' }[0], 'decompose_header: Bad header VAL_2 VAL_2_+ VAL_2_++' ) ;
  14824. note( 'Leaving tests_decompose_header()' ) ;
  14825. return ;
  14826. }
  14827. sub tests_epoch
  14828. {
  14829. note( 'Entering tests_epoch()' ) ;
  14830. ok( '1282658400' eq epoch( '24-Aug-2010 16:00:00 +0200' ), 'epoch 24-Aug-2010 16:00:00 +0200 -> 1282658400' ) ;
  14831. ok( '1282658400' eq epoch( '24-Aug-2010 14:00:00 +0000' ), 'epoch 24-Aug-2010 14:00:00 +0000 -> 1282658400' ) ;
  14832. ok( '1282658400' eq epoch( '24-Aug-2010 12:00:00 -0200' ), 'epoch 24-Aug-2010 12:00:00 -0200 -> 1282658400' ) ;
  14833. ok( '1282658400' eq epoch( '24-Aug-2010 16:01:00 +0201' ), 'epoch 24-Aug-2010 16:01:00 +0201 -> 1282658400' ) ;
  14834. ok( '1282658400' eq epoch( '24-Aug-2010 14:01:00 +0001' ), 'epoch 24-Aug-2010 14:01:00 +0001 -> 1282658400' ) ;
  14835. ok( '1280671200' eq epoch( '1-Aug-2010 16:00:00 +0200' ), 'epoch 1-Aug-2010 16:00:00 +0200 -> 1280671200' ) ;
  14836. ok( '1280671200' eq epoch( '1-Aug-2010 14:00:00 +0000' ), 'epoch 1-Aug-2010 14:00:00 +0000 -> 1280671200' ) ;
  14837. ok( '1280671200' eq epoch( '1-Aug-2010 12:00:00 -0200' ), 'epoch 1-Aug-2010 12:00:00 -0200 -> 1280671200' ) ;
  14838. ok( '1280671200' eq epoch( '1-Aug-2010 16:01:00 +0201' ), 'epoch 1-Aug-2010 16:01:00 +0201 -> 1280671200' ) ;
  14839. ok( '1280671200' eq epoch( '1-Aug-2010 14:01:00 +0001' ), 'epoch 1-Aug-2010 14:01:00 +0001 -> 1280671200' ) ;
  14840. is( '1280671200', epoch( '1-Aug-2010 14:01:00 +0001' ), 'epoch 1-Aug-2010 14:01:00 +0001 -> 1280671200' ) ;
  14841. is( '946684800', epoch( '00-Jan-0000 00:00:00 +0000' ), 'epoch 1-Aug-2010 14:01:00 +0001 -> 1280671200' ) ;
  14842. note( 'Leaving tests_epoch()' ) ;
  14843. return ;
  14844. }
  14845. sub epoch
  14846. {
  14847. # incoming format:
  14848. # internal date 24-Aug-2010 16:00:00 +0200
  14849. # outgoing format: epoch
  14850. my $d = shift @ARG ;
  14851. return(q{}) if not defined $d;
  14852. my ( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m ) ;
  14853. my $time ;
  14854. 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 ) {
  14855. #myprint( "internal: [$1][$2][$3][$4][$5][$6][$7][$8][$9]\n" ) ;
  14856. ( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m )
  14857. = ( $1, $2, $3, $4, $5, $6, $7, $8, $9 ) ;
  14858. #myprint( "( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m )\n" ) ;
  14859. $sign = +1 if ( '+' eq $sign ) ;
  14860. $sign = $MINUS_ONE if ( '-' eq $sign ) ;
  14861. if ( 0 == $mday ) {
  14862. myprint( "buggy day in $d. Fixed to 01\n" ) ;
  14863. $mday = '01' ;
  14864. }
  14865. $time = timegm( $sec, $min, $hour, $mday, $month_abrev{$month}, $year )
  14866. - $sign * ( 3600 * $zone_h + 60 * $zone_m ) ;
  14867. #myprint( "$time ", scalar localtime($time), "\n");
  14868. }
  14869. return( $time ) ;
  14870. }
  14871. sub tests_add_header
  14872. {
  14873. note( 'Entering tests_add_header()' ) ;
  14874. ok( 'Message-Id: <mistake@imapsync>' eq add_header(), 'add_header no arg' ) ;
  14875. ok( 'Message-Id: <123456789@imapsync>' eq add_header( '123456789' ), 'add_header 123456789' ) ;
  14876. note( 'Leaving tests_add_header()' ) ;
  14877. return ;
  14878. }
  14879. sub add_header
  14880. {
  14881. my $header_uid = shift || 'mistake' ;
  14882. my $header_Message_Id = 'Message-Id: <' . $header_uid . '@imapsync>' ;
  14883. return( $header_Message_Id ) ;
  14884. }
  14885. sub tests_max_line_length
  14886. {
  14887. note( 'Entering tests_max_line_length()' ) ;
  14888. ok( 0 == max_line_length( q{} ), 'max_line_length: 0 == null string' ) ;
  14889. ok( 1 == max_line_length( "\n" ), 'max_line_length: 1 == \n' ) ;
  14890. ok( 1 == max_line_length( "\n\n" ), 'max_line_length: 1 == \n\n' ) ;
  14891. ok( 1 == max_line_length( "\n" x 500 ), 'max_line_length: 1 == 500 \n' ) ;
  14892. ok( 1 == max_line_length( 'a' ), 'max_line_length: 1 == a' ) ;
  14893. ok( 2 == max_line_length( "a\na" ), 'max_line_length: 2 == a\na' ) ;
  14894. ok( 2 == max_line_length( "a\na\n" ), 'max_line_length: 2 == a\na\n' ) ;
  14895. ok( 3 == max_line_length( "a\nab\n" ), 'max_line_length: 3 == a\nab\n' ) ;
  14896. ok( 3 == max_line_length( "a\nab\n" x 1_000 ), 'max_line_length: 3 == 1_000 a\nab\n' ) ;
  14897. ok( 3 == max_line_length( "a\nab\nabc" ), 'max_line_length: 3 == a\nab\nabc' ) ;
  14898. ok( 4 == max_line_length( "a\nab\nabc\n" ), 'max_line_length: 4 == a\nab\nabc\n' ) ;
  14899. ok( 5 == max_line_length( "a\nabcd\nabc\n" ), 'max_line_length: 5 == a\nabcd\nabc\n' ) ;
  14900. 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' ) ;
  14901. note( 'Leaving tests_max_line_length()' ) ;
  14902. return ;
  14903. }
  14904. sub max_line_length
  14905. {
  14906. my $string = shift @ARG ;
  14907. my $max = 0 ;
  14908. while ( $string =~ m/([^\n]*\n?)/msxg ) {
  14909. $max = max( $max, length $1 ) ;
  14910. }
  14911. return( $max ) ;
  14912. }
  14913. sub set_checknoabletosearch
  14914. {
  14915. my $mysync = shift @ARG ;
  14916. if ( defined $mysync->{ checknoabletosearch } )
  14917. {
  14918. return ;
  14919. }
  14920. elsif ( $mysync->{ justfolders } )
  14921. {
  14922. $mysync->{ checknoabletosearch } = 0 ;
  14923. }
  14924. else
  14925. {
  14926. $mysync->{ checknoabletosearch } = 1 ;
  14927. }
  14928. return ;
  14929. }
  14930. sub tests_setlogfile
  14931. {
  14932. note( 'Entering tests_setlogfile()' ) ;
  14933. my $mysync = {} ;
  14934. $mysync->{ logdir } = 'vallogdir' ;
  14935. is( 'vallogdir/vallogfile.txt', setlogfile( $mysync, 'vallogfile.txt' ),
  14936. 'setlogfile: logdir vallogdir, vallogfile.txt => vallogdir/vallogfile.txt' ) ;
  14937. SKIP: {
  14938. skip( 'Too hard to have a well known timezone on Windows', 9 ) if ( 'MSWin32' eq $OSNAME ) ;
  14939. local $ENV{TZ} = 'GMT' ;
  14940. $mysync = {
  14941. timestart => 2,
  14942. } ;
  14943. is( '1970_01_01_00_00_02_000__.txt', setlogfile( $mysync ),
  14944. 'setlogfile: default is like 1970_01_01_00_00_02_000__.txt' ) ;
  14945. $mysync = {
  14946. timestart => 2,
  14947. user1 => 'user1',
  14948. user2 => 'user2',
  14949. abort => 1,
  14950. } ;
  14951. is( '1970_01_01_00_00_02_000_user1_user2_abort.txt', setlogfile( $mysync ),
  14952. 'setlogfile: default abort is like 1970_01_01_00_00_02_000_user1_user2_abort.txt' ) ;
  14953. $mysync = {
  14954. timestart => 2,
  14955. user1 => 'user1',
  14956. user2 => 'user2',
  14957. } ;
  14958. is( '1970_01_01_00_00_02_000_user1_user2_remote.txt', setlogfile( $mysync, undef, '_remote' ),
  14959. 'setlogfile: default with _remote is like 1970_01_01_00_00_02_000_user1_user2_remote.txt' ) ;
  14960. $mysync = {
  14961. timestart => 2,
  14962. user1 => 'user1',
  14963. user2 => 'user2',
  14964. abort => 1,
  14965. } ;
  14966. is( '1970_01_01_00_00_02_000_user1_user2_remote_abort.txt', setlogfile( $mysync, undef, '_remote' ),
  14967. 'setlogfile: default abort with _remote is like 1970_01_01_00_00_02_000_user1_user2_remote_abort.txt' ) ;
  14968. $mysync = {
  14969. timestart => 2,
  14970. user1 => 'user1',
  14971. user2 => 'user2',
  14972. } ;
  14973. is( '1970_01_01_00_00_02_000_user1_user2.txt', setlogfile( $mysync ),
  14974. 'setlogfile: default is like 1970_01_01_00_00_02_000_user1_user2.txt' ) ;
  14975. $mysync->{logdir} = undef ;
  14976. $mysync->{logfile} = undef ;
  14977. is( '1970_01_01_00_00_02_000_user1_user2.txt', setlogfile( $mysync ),
  14978. 'setlogfile: logdir undef, 1970_01_01_00_00_02_000_user1_user2.txt' ) ;
  14979. $mysync->{logdir} = q{} ;
  14980. $mysync->{logfile} = undef ;
  14981. is( '1970_01_01_00_00_02_000_user1_user2.txt', setlogfile( $mysync ),
  14982. 'setlogfile: logdir empty, 1970_01_01_00_00_02_000_user1_user2.txt' ) ;
  14983. $mysync->{logdir} = 'vallogdir' ;
  14984. $mysync->{logfile} = undef ;
  14985. is( 'vallogdir/1970_01_01_00_00_02_000_user1_user2.txt', setlogfile( $mysync ),
  14986. 'setlogfile: logdir vallogdir, vallogdir/1970_01_01_00_00_02_000_user1_user2.txt' ) ;
  14987. $mysync = {
  14988. user1 => 'us/er1a*|?:"<>b',
  14989. user2 => 'u/ser2a*|?:"<>b',
  14990. } ;
  14991. is( '1970_01_01_00_00_00_000_us_er1a_______b_u_ser2a_______b.txt', setlogfile( $mysync ),
  14992. 'setlogfile: logdir undef, 1970_01_01_00_00_00_000_us_er1a_______b_u_ser2a_______b.txt' ) ;
  14993. } ;
  14994. note( 'Leaving tests_setlogfile()' ) ;
  14995. return ;
  14996. }
  14997. sub setlogfile
  14998. {
  14999. my $mysync = shift @ARG ;
  15000. my $given = shift @ARG ;
  15001. my $supplement = shift @ARG || '' ;
  15002. # When aborting another process the log file name finishes with "_abort.txt"
  15003. my $abort_suffix = ( $mysync->{ abort } ) ? '_abort' : q{} ;
  15004. my $suffix = logfilesuffix( $mysync, $supplement . $abort_suffix ) ;
  15005. my $logdir = $mysync->{ logdir } || '' ;
  15006. my $logfile ;
  15007. if ( defined $given )
  15008. {
  15009. if ( $logdir )
  15010. {
  15011. $logfile = "$logdir/$given" ;
  15012. }
  15013. else
  15014. {
  15015. $logfile = "$given" ;
  15016. }
  15017. }
  15018. else
  15019. {
  15020. $logfile = logfile( $mysync->{ timestart }, $suffix, $logdir ) ;
  15021. }
  15022. return( $logfile ) ;
  15023. }
  15024. sub tests_logfilesuffix
  15025. {
  15026. note( 'Entering tests_logfilesuffix()' ) ;
  15027. is( '_', logfilesuffix( ), 'logfilesuffix: no args => _' ) ;
  15028. my $mysync = { } ;
  15029. is( '_', logfilesuffix( $mysync ), 'logfilesuffix: undef => _' ) ;
  15030. $mysync->{ user1 } = 'valuser1' ;
  15031. $mysync->{ user2 } = 'valuser2' ;
  15032. is( 'valuser1_valuser2', logfilesuffix( $mysync ), 'logfilesuffix: valuser1 valuser2 => valuser1_valuser2' ) ;
  15033. is( 'valuser1_valuser2_suppl', logfilesuffix( $mysync, '_suppl' ), 'logfilesuffix: valuser1 valuser2 _suppl => valuser1_valuser2_suppl' ) ;
  15034. note( 'Leaving tests_logfilesuffix()' ) ;
  15035. return ;
  15036. }
  15037. sub logfilesuffix
  15038. {
  15039. my $mysync = shift @ARG ;
  15040. my $supplement = shift @ARG || '' ;
  15041. my $suffix = (
  15042. filter_forbidden_characters( slash_to_underscore( $mysync->{ user1 } ) ) || q{} )
  15043. . '_'
  15044. . ( filter_forbidden_characters( slash_to_underscore( $mysync->{ user2 } ) ) || q{} )
  15045. . $supplement ;
  15046. return $suffix ;
  15047. }
  15048. sub tests_setlogdir
  15049. {
  15050. note( 'Entering tests_setlogdir()' ) ;
  15051. is( $DEFAULT_LOGDIR, setlogdir( ), "setlogdir: no args => $DEFAULT_LOGDIR" ) ;
  15052. my $mysync = { } ;
  15053. is( $DEFAULT_LOGDIR, setlogdir( $mysync ), "setlogdir: no args => $DEFAULT_LOGDIR" ) ;
  15054. $mysync->{ logdir } = '' ;
  15055. is( '', setlogdir( $mysync ), "setlogdir: logdir empty string => empty string" ) ;
  15056. is( '', $mysync->{ logdir }, "setlogdir: logdir empty string unchanged" ) ;
  15057. $mysync->{ logdir } = 'vallogdir' ;
  15058. is( 'vallogdir', setlogdir( $mysync ), "setlogdir: logdir vallogdir => vallogdir" ) ;
  15059. is( 'vallogdir', $mysync->{ logdir }, "setlogdir: logdir vallogdir unchanged" ) ;
  15060. # Does a second call hurt?
  15061. is( 'vallogdir', setlogdir( $mysync ), "setlogdir: logdir vallogdir => vallogdir" ) ;
  15062. is( 'vallogdir', $mysync->{ logdir }, "setlogdir: logdir vallogdir unchanged" ) ;
  15063. note( 'Leaving tests_setlogdir()' ) ;
  15064. return ;
  15065. }
  15066. sub setlogdir
  15067. {
  15068. my $mysync = shift @ARG ;
  15069. my $logdir = defined $mysync->{ logdir } ? $mysync->{ logdir } : $DEFAULT_LOGDIR ;
  15070. return $logdir ;
  15071. }
  15072. sub tests_logfile
  15073. {
  15074. note( 'Entering tests_logfile()' ) ;
  15075. SKIP: {
  15076. # Too hard to have a well known timezone on Windows
  15077. skip( 'Too hard to have a well known timezone on Windows', 10 ) if ( 'MSWin32' eq $OSNAME ) ;
  15078. local $ENV{TZ} = 'GMT' ;
  15079. {
  15080. POSIX::tzset unless ('MSWin32' eq $OSNAME) ;
  15081. is( '1970_01_01_00_00_00_000.txt', logfile( ), 'logfile: no args => 1970_01_01_00_00_00_000.txt' ) ;
  15082. is( '1970_01_01_00_00_00_000.txt', logfile( 0 ), 'logfile: 0 => 1970_01_01_00_00_00_000.txt' ) ;
  15083. is( '1970_01_01_00_01_01_000.txt', logfile( 61 ), 'logfile: 0 => 1970_01_01_00_01_01_000.txt' ) ;
  15084. is( '1970_01_01_00_01_01_234.txt', logfile( 61.234 ), 'logfile: 0 => 1970_01_01_00_01_01_234.txt' ) ;
  15085. 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_000.txt' ) ;
  15086. 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_000.txt' ) ;
  15087. 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_000_poupinette.txt' ) ;
  15088. 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.txt' ) ;
  15089. is( '2010_08_24_14_01_01_234_poup.txt', logfile( 1_282_658_461.2347, 'poup' ),
  15090. 'logfile: 1_282_658_461.2347 poup => 2010_08_24_14_01_01_234_poup.txt' ) ;
  15091. is( 'dirdir/2010_08_24_14_01_01_234_poup.txt', logfile( 1_282_658_461.2347, 'poup', 'dirdir' ),
  15092. 'logfile: 1_282_658_461.2347 poup dirdir => dirdir/2010_08_24_14_01_01_234_poup.txt' ) ;
  15093. }
  15094. POSIX::tzset unless ('MSWin32' eq $OSNAME) ;
  15095. } ;
  15096. note( 'Leaving tests_logfile()' ) ;
  15097. return ;
  15098. }
  15099. sub logfile
  15100. {
  15101. my ( $time, $suffix, $dir ) = @_ ;
  15102. $time ||= 0 ;
  15103. $suffix ||= q{} ;
  15104. $suffix =~ tr/ //ds ;
  15105. my $sep_suffix = ( $suffix ) ? '_' : q{} ;
  15106. $dir ||= q{} ;
  15107. my $sep_dir = ( $dir ) ? '/' : q{} ;
  15108. my $date_str = year_month_day_hour_min_sec_ms( $time ) ;
  15109. my $logfile = "${dir}${sep_dir}${date_str}${sep_suffix}${suffix}.txt" ;
  15110. return( $logfile ) ;
  15111. }
  15112. sub tests_year_month_day_hour_min_sec_ms
  15113. {
  15114. note( 'Entering tests_date_year_month_day_hour_min_sec_ms()' ) ;
  15115. if ( 'MSWin32' eq $OSNAME )
  15116. {
  15117. # Can not use $ENV{TZ} nor POSIX::tzset
  15118. like( year_month_day_hour_min_sec_ms( ), qr'1970_01_01_\d\d_00_00_000', 'year_month_day_hour_min_sec_ms: no args => match 1970_01_01_\d\d_00_00_000' ) ;
  15119. like( year_month_day_hour_min_sec_ms( 0 ), qr'1970_01_01_\d\d_00_00_000', 'year_month_day_hour_min_sec_ms: 0 => match 1970_01_01_\d\d_00_00_000' ) ;
  15120. like( year_month_day_hour_min_sec_ms( 1671706800.123 ), qr'2022_12_22_\d\d_00_00_122', 'year_month_day_hour_min_sec_ms: 123456789.123 => match 2022_12_22_\d\d_00_00_122' ) ;
  15121. like( year_month_day_hour_min_sec_ms( -1 ), qr'19\d\d_\d\d_\d\d_\d\d_59_59_000', 'year_month_day_hour_min_sec_ms: -1 => 19\d\d_\d\d_\d\d_\d\d_59_59_000' ) ;
  15122. like( year_month_day_hour_min_sec_ms( -0.246 ), qr'19\d\d_\d\d_\d\d_\d\d_59_59_754', 'year_month_day_hour_min_sec_ms: -1 => 19\d\d_\d\d_\d\d_\d\d_59_59_754' ) ;
  15123. like( year_month_day_hour_min_sec_ms( -32360400.135 ), qr'1968_12_22_\d\d_59_59_864', 'year_month_day_hour_min_sec_ms: -1 => 1968_12_22_\d\d_59_59_864' ) ;
  15124. }
  15125. else
  15126. {
  15127. local $ENV{TZ} = 'GMT' ;
  15128. {
  15129. POSIX::tzset unless ('MSWin32' eq $OSNAME) ;
  15130. is( '1970_01_01_00_00_00_000', year_month_day_hour_min_sec_ms( ),
  15131. 'year_month_day_hour_min_sec_ms: no args => 1970_01_01_00_00_00_000 GMT' ) ;
  15132. is( '1970_01_01_00_00_00_000', year_month_day_hour_min_sec_ms( 0 ),
  15133. 'year_month_day_hour_min_sec_ms: 0 => 1970_01_01_00_00_00_000 GMT' ) ;
  15134. is( '1973_11_29_21_33_09_122', year_month_day_hour_min_sec_ms( 123456789.123 ),
  15135. 'year_month_day_hour_min_sec_ms: 123456789.123 => 1973_11_29_21_33_09_122 GMT' ) ;
  15136. is( '1969_12_31_23_59_59_000', year_month_day_hour_min_sec_ms( -1 ),
  15137. 'year_month_day_hour_min_sec_ms: -1 => 1969_12_31_23_59_59_000 GMT' ) ;
  15138. is( '1969_12_31_23_59_59_754', year_month_day_hour_min_sec_ms( -0.246 ),
  15139. 'year_month_day_hour_min_sec_ms: -0.246 => 1969_12_31_23_59_59_754 GMT' ) ;
  15140. is( '1966_02_02_02_26_50_864', year_month_day_hour_min_sec_ms( -123456789.135 ),
  15141. 'year_month_day_hour_min_sec_ms: -123456789.135 => 1966_02_02_02_26_50_864 GMT' ) ;
  15142. }
  15143. POSIX::tzset unless ('MSWin32' eq $OSNAME) ;
  15144. }
  15145. note( 'Leaving tests_year_month_day_hour_min_sec_ms()' ) ;
  15146. return ;
  15147. }
  15148. sub year_month_day_hour_min_sec_ms
  15149. {
  15150. my $time = shift @ARG || 0 ;
  15151. my $date_str = POSIX::strftime( '%Y_%m_%d_%H_%M_%S', localtime $time ) ;
  15152. # Because of ab tests or web accesses, more than one sync withing one second is possible
  15153. # so we add also milliseconds
  15154. $date_str .= sprintf "_%03d", fractional_of_floor( $time ) * 1000 ;
  15155. return $date_str ;
  15156. }
  15157. sub tests_fractional_of_floor
  15158. {
  15159. note( 'Entering tests_fractional_of_floor()' ) ;
  15160. is( 0, fractional_of_floor( ), 'fractional_of_floor: no args => 0' ) ;
  15161. is( 0, fractional_of_floor( 0 ), 'fractional_of_floor: 0 => 0' ) ;
  15162. is( 0, fractional_of_floor( '0' ), 'fractional_of_floor: 0 => 0' ) ;
  15163. is( 0, fractional_of_floor( 1 ), 'fractional_of_floor: 1 => 0' ) ;
  15164. is( 0, fractional_of_floor( '1' ), 'fractional_of_floor: 1 => 0' ) ;
  15165. is( 0, fractional_of_floor( -1 ), 'fractional_of_floor: -1 => 0' ) ;
  15166. is( 0, fractional_of_floor( '-1' ), 'fractional_of_floor: -1 => 0' ) ;
  15167. is( 0.234, fractional_of_floor( 1.234 ), 'fractional_of_floor: 1.234 => 0.234' ) ;
  15168. is( 0.234, fractional_of_floor( '1.234' ), 'fractional_of_floor: 1.234 => 0.234' ) ;
  15169. is( 0.766, fractional_of_floor( -1.234 ), 'fractional_of_floor: -1.234 => 0.766' ) ;
  15170. is( 0.766, fractional_of_floor( '-1.234' ), 'fractional_of_floor: -1.234 => 0.766' ) ;
  15171. is( 0.234, fractional_of_floor( 10.234 ), 'fractional_of_floor: 10.234 => 0.234' ) ;
  15172. is( 0.766, fractional_of_floor( -10.234 ), 'fractional_of_floor: -10.234 => 0.766' ) ;
  15173. note( 'Leaving tests_fractional_of_floor()' ) ;
  15174. return ;
  15175. }
  15176. sub fractional_of_floor
  15177. {
  15178. my $float = shift @ARG || 0 ;
  15179. if ( $float - int( $float ) >= 0 )
  15180. {
  15181. return( $float - int( $float ) ) ;
  15182. }
  15183. else
  15184. {
  15185. return( 1 - ( int( $float ) - $float ) ) ;
  15186. }
  15187. }
  15188. sub tests_localtimez
  15189. {
  15190. note( 'Entering tests_localtimez()' ) ;
  15191. note( "localtimez: " . localtimez( ) ) ;
  15192. if ( 'MSWin32' eq $OSNAME )
  15193. {
  15194. like( localtimez( 0 ), qr'1970-01-01 \d\d:\d\d:\d\d', 'localtimez: 0 => match 1970-01-01 \d\d:\d\d:\d\d' ) ;
  15195. }
  15196. else
  15197. {
  15198. local $ENV{TZ} = 'GMT' ;
  15199. like( localtimez( 0 ), qr'1970-01-01 00:00:00 \+0000 (GMT|UTC)', 'localtimez: 0 => match 1970-01-01 00:00:00 +0000 GMT or UTC' ) ;
  15200. }
  15201. is( localtimez( ), localtimez( time ), 'localtimez: undef => equals currrent' ) ;
  15202. note( 'Leaving tests_localtimez()' ) ;
  15203. return ;
  15204. }
  15205. sub localtimez
  15206. {
  15207. my $time = shift @ARG ;
  15208. $time = defined( $time ) ? $time : time ;
  15209. my $datetimestr ;
  15210. if ( 'MSWin32' eq $OSNAME )
  15211. {
  15212. $datetimestr = POSIX::strftime( '%A %d %B %Y-%m-%d %H:%M:%S %z', localtime( $time ) ) ;
  15213. }
  15214. else
  15215. {
  15216. $datetimestr = POSIX::strftime( '%A %d %B %Y-%m-%d %H:%M:%S %z %Z', localtime( $time ) ) ;
  15217. }
  15218. #myprint( "$datetimestr\n" ) ;
  15219. return $datetimestr ;
  15220. }
  15221. sub tests_slash_to_underscore
  15222. {
  15223. note( 'Entering tests_slash_to_underscore()' ) ;
  15224. is( undef, slash_to_underscore( ), 'slash_to_underscore: no parameters => undef' ) ;
  15225. is( '_', slash_to_underscore( '/' ), 'slash_to_underscore: / => _' ) ;
  15226. is( '_abc_def_', slash_to_underscore( '/abc/def/' ), 'slash_to_underscore: /abc/def/ => _abc_def_' ) ;
  15227. note( 'Leaving tests_slash_to_underscore()' ) ;
  15228. return ;
  15229. }
  15230. sub slash_to_underscore
  15231. {
  15232. my $string = shift @ARG ;
  15233. if ( ! defined $string ) { return ; }
  15234. $string =~ tr{/}{_} ;
  15235. return( $string ) ;
  15236. }
  15237. sub tests_million_folders_baby_2
  15238. {
  15239. note( 'Entering tests_million_folders_baby_2()' ) ;
  15240. my %long ;
  15241. @long{ 1 .. 900_000 } = (1) x 900_000 ;
  15242. #myprint( %long, "\n" ) ;
  15243. my $pasglop = 0 ;
  15244. foreach my $elem ( 1 .. 900_000 ) {
  15245. #$debug and myprint( "$elem " ) ;
  15246. if ( not exists $long{ $elem } ) {
  15247. $pasglop++ ;
  15248. }
  15249. }
  15250. ok( 0 == $pasglop, 'tests_million_folders_baby_2: search among 900_000' ) ;
  15251. # myprint( "$pasglop\n" ) ;
  15252. note( 'Leaving tests_million_folders_baby_2()' ) ;
  15253. return ;
  15254. }
  15255. sub tests_logfileprepa
  15256. {
  15257. note( 'Entering tests_logfileprepa()' ) ;
  15258. is( undef, logfileprepa( ), 'logfileprepa: no args => undef' ) ;
  15259. my $logfile = 'W/tmp/tests/tests_logfileprepa.txt' ;
  15260. is( 1, logfileprepa( $logfile ), 'logfileprepa: W/tmp/tests/tests_logfileprepa.txt => 1' ) ;
  15261. note( 'Leaving tests_logfileprepa()' ) ;
  15262. return ;
  15263. }
  15264. sub logfileprepa
  15265. {
  15266. my $logfile = shift @ARG ;
  15267. if ( ! defined( $logfile ) )
  15268. {
  15269. return ;
  15270. }else
  15271. {
  15272. #myprint( "[$logfile]\n" ) ;
  15273. my $dirname = dirname( $logfile ) ;
  15274. do_valid_directory( $dirname ) || return( 0 ) ;
  15275. return( 1 ) ;
  15276. }
  15277. }
  15278. sub tests_teelaunch
  15279. {
  15280. note( 'Entering tests_teelaunch()' ) ;
  15281. is( undef, teelaunch( ), 'teelaunch: no args => undef' ) ;
  15282. my $mysync = {} ;
  15283. is( undef, teelaunch( $mysync ), 'teelaunch: arg empty {} => undef' ) ;
  15284. is( undef, teelaunch( $mysync, '' ), 'teelaunch: empty string => undef' ) ;
  15285. # First time, learning IO::Tee intrasics
  15286. my $tee = teelaunch( $mysync, 'W/tmp/tests/tests_teelaunch.txt' ) ;
  15287. isa_ok( $tee, 'IO::Tee', 'teelaunch: logfile W/tmp/tests/tests_teelaunch.txt' ) ;
  15288. is( 1, print( $tee "Hi!\n" ), 'teelaunch: write Hi!') ;
  15289. is( "Hi!\n", file_to_string( 'W/tmp/tests/tests_teelaunch.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch.txt is Hi!\n' ) ;
  15290. is( 1, print( $tee "Hoo\n" ), 'teelaunch: write Hoo') ;
  15291. 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' ) ;
  15292. # closing file handle so tee won't be happy
  15293. ($tee->handles)[0]->close ;
  15294. is( undef, print( $tee "Argh1\n" ), 'teelaunch: write Argh1') ;
  15295. is( undef, print( $tee "Argh2\n" ), 'teelaunch: write Argh2') ;
  15296. # write not done
  15297. 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' ) ;
  15298. print join( ' ', $tee->handles ), "\n";
  15299. is( 2, scalar $tee->handles, 'teelaunch: 2 handles') ;
  15300. shift @{*{$tee}};
  15301. print join(' ', $tee->handles), "\n" ;
  15302. is( 1, scalar $tee->handles, 'teelaunch: 1 handle') ;
  15303. is( 1, print( $tee "Argh3\n" ), 'teelaunch: write Argh3 yeah') ;
  15304. shift @{*{$tee}};
  15305. # will not print anything now
  15306. is( 0, scalar $tee->handles, 'teelaunch: 0 handle') ;
  15307. is( 1, print( $tee "Argh 4\n" ), 'teelaunch: write Argh4 no') ;
  15308. # Second time, lesson learnt IO::Tee
  15309. $tee = teelaunch( $mysync, 'W/tmp/tests/tests_teelaunch2.txt' ) ;
  15310. isa_ok( $tee, 'IO::Tee' , 'teelaunch: W/tmp/tests/tests_teelaunch2.txt' ) ;
  15311. is( 1, print( $tee "Hi!\n" ), 'teelaunch: write Hi!') ;
  15312. is( "Hi!\n", file_to_string( 'W/tmp/tests/tests_teelaunch2.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch2.txt is Hi!\n' ) ;
  15313. is( 1, print( $tee "Hoo\n" ), 'teelaunch: write Hoo') ;
  15314. 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' ) ;
  15315. is( 1, teefinish( $tee ), 'teefinish: return 1') ;
  15316. is( 1, print( $tee "Argh1\n" ), 'teelaunch: write Argh1') ;
  15317. is( 1, print( $tee "Argh2\n" ), 'teelaunch: write Argh2') ;
  15318. 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' ) ;
  15319. is( 1, teefinish( $tee ), 'teefinish: still return 1') ;
  15320. note( 'Leaving tests_teelaunch()' ) ;
  15321. return ;
  15322. }
  15323. sub teelaunch
  15324. {
  15325. my $mysync = shift @ARG ;
  15326. my $logfile = shift @ARG ;
  15327. if ( ! defined( $mysync ) )
  15328. {
  15329. return ;
  15330. }
  15331. if ( ! $logfile )
  15332. {
  15333. return ;
  15334. }
  15335. logfileprepa( $logfile ) || croak "Error no valid directory to write log file $logfile : $OS_ERROR" ;
  15336. # To honor -T tainted mode. Could do better...
  15337. ( $logfile ) = $logfile =~ m{(.*)}x ;
  15338. # This is a log file opened during the whole sync
  15339. ## no critic (InputOutput::RequireBriefOpen)
  15340. if ( ! open my $logfile_handle, '>', $logfile )
  15341. {
  15342. carp( "Can not open $logfile for write: $OS_ERROR" ) ;
  15343. return ;
  15344. }
  15345. else
  15346. {
  15347. binmode $logfile_handle, ":encoding(UTF-8)" ;
  15348. my $tee = IO::Tee->new( $logfile_handle, \*STDOUT ) ;
  15349. $tee->autoflush( 1 ) ;
  15350. return $tee ;
  15351. }
  15352. }
  15353. sub teefinish
  15354. {
  15355. my $tee = shift @ARG ;
  15356. if ( ! defined( $tee ) ) { return ; }
  15357. if ( 2 == scalar $tee->handles )
  15358. {
  15359. my $handle = shift @{*{$tee}} ;
  15360. $handle->close ;
  15361. }
  15362. else
  15363. {
  15364. # nothing
  15365. }
  15366. return scalar $tee->handles ;
  15367. }
  15368. sub getpwuid_any_os
  15369. {
  15370. my $uid = shift @ARG ;
  15371. return( scalar getlogin ) if ( 'MSWin32' eq $OSNAME ) ; # Windows system
  15372. return( scalar getpwuid $uid ) ; # Unix system
  15373. }
  15374. sub abortifneeded
  15375. {
  15376. my $mysync = shift @ARG ;
  15377. if ( -e $mysync->{ abortfile } )
  15378. {
  15379. myprint( "Asked to terminate by file $mysync->{ abortfile }\n" ) ;
  15380. do_and_print_stats( $mysync ) ;
  15381. myprint( "You should resynchronize those accounts by running a sync again,\n",
  15382. "since some messages and entire folders might still be missing on host2.\n"
  15383. ) ;
  15384. exit_clean( $mysync, $EXIT_BY_FILE ) ;
  15385. return ;
  15386. }
  15387. else
  15388. {
  15389. return ;
  15390. }
  15391. }
  15392. sub simulong
  15393. {
  15394. my $mysync = shift @ARG ;
  15395. my $max_seconds = $mysync->{ simulong } ;
  15396. if ( ! $max_seconds ) { return ; }
  15397. my $division = 5 ;
  15398. my $last_count = int( $division * $max_seconds ) ;
  15399. $mysync->{ debug } and myprint "last_count $last_count = int( division $division * max_seconds $max_seconds)\n" ;
  15400. foreach my $i ( 1 .. ( $last_count ) ) {
  15401. myprint( "Are you still here ETA: " . ( $last_count - $i ) . "/$last_count msgs left\n" ) ;
  15402. #this one is for testing huge page behavior
  15403. #myprint( "Are you still here ETA: " . ($last_count - $i) . "/$last_count msgs left\n" . ( "Ah" x 40 . "\n") x 4000 ) ;
  15404. sleep( 1 / $division ) ;
  15405. abortifneeded( $mysync ) ;
  15406. }
  15407. return ;
  15408. }
  15409. sub printenv
  15410. {
  15411. myprint( "Environment variables listing:\n",
  15412. ( map { "$_ => $ENV{$_}\n" } sort keys %ENV),
  15413. "Environment variables listing end\n" ) ;
  15414. return ;
  15415. }
  15416. sub unittestssuite
  15417. {
  15418. my $mysync = shift @ARG ;
  15419. if ( ! ( $mysync->{ tests } or $mysync->{ testsdebug } or $mysync->{ testsunit } ) ) {
  15420. return ;
  15421. }
  15422. my $test_builder = Test::More->builder ;
  15423. tests( $mysync ) ;
  15424. testsdebug( $mysync ) ;
  15425. testunitsession( $mysync ) ;
  15426. cleanup_mess_from_tests( ) ;
  15427. my @summary = $test_builder->summary() ;
  15428. my @details = $test_builder->details() ;
  15429. my $nb_tests_run = scalar( @summary ) ;
  15430. my $nb_tests_expected = $test_builder->expected_tests() ;
  15431. my $nb_tests_failed = count_0s( @summary ) ;
  15432. my $tests_failed = report_failures( @details ) ;
  15433. if ( $nb_tests_failed or ( $nb_tests_run != $nb_tests_expected ) ) {
  15434. #$test_builder->reset( ) ;
  15435. myprint( "Summary of tests: failed $nb_tests_failed tests, run $nb_tests_run tests, expected to run $nb_tests_expected tests.\n",
  15436. "List of failed tests:\n", $tests_failed ) ;
  15437. return $EXIT_TESTS_FAILED ;
  15438. }
  15439. return 0 ;
  15440. }
  15441. sub cleanup_mess_from_tests
  15442. {
  15443. undef @pipemess ;
  15444. undef @include ;
  15445. undef @exclude ;
  15446. undef @folderrec ;
  15447. undef @folderfirst ;
  15448. undef @folderlast ;
  15449. undef @h1_folders_all ;
  15450. undef %h1_folders_all ;
  15451. undef @h2_folders_all ;
  15452. undef %h2_folders_all ;
  15453. undef @h2_folders_from_1_wanted ;
  15454. undef %h2_folders_from_1_all ;
  15455. undef %requested_folder ;
  15456. return ;
  15457. }
  15458. sub after_get_options
  15459. {
  15460. my $mysync = shift @ARG ;
  15461. my $numopt = shift @ARG ;
  15462. # exit with --help option or no option at all
  15463. $mysync->{ debug } and myprint( "numopt:$numopt\n" ) ;
  15464. if ( $help or not $numopt ) {
  15465. myprint( usage( $mysync ) ) ;
  15466. exit ;
  15467. }
  15468. return ;
  15469. }
  15470. sub tests_remove_edging_blanks
  15471. {
  15472. note( 'Entering tests_remove_edging_blanks()' ) ;
  15473. is( undef, remove_edging_blanks( ), 'remove_edging_blanks: no args => undef' ) ;
  15474. is( 'abcd', remove_edging_blanks( 'abcd' ), 'remove_edging_blanks: abcd => abcd' ) ;
  15475. is( 'ab cd', remove_edging_blanks( ' ab cd ' ), 'remove_edging_blanks: " ab cd " => "ab cd"' ) ;
  15476. note( 'Leaving tests_remove_edging_blanks()' ) ;
  15477. return ;
  15478. }
  15479. sub remove_edging_blanks
  15480. {
  15481. my $string = shift @ARG ;
  15482. if ( ! defined $string )
  15483. {
  15484. return ;
  15485. }
  15486. $string =~ s,^ +| +$,,g ;
  15487. return $string ;
  15488. }
  15489. sub tests_sanitize
  15490. {
  15491. note( 'Entering tests_remove_edging_blanks()' ) ;
  15492. is( undef, sanitize( ), 'sanitize: no args => undef' ) ;
  15493. my $mysync = {} ;
  15494. $mysync->{ host1 } = ' example.com ' ;
  15495. $mysync->{ user1 } = ' to to ' ;
  15496. $mysync->{ password1 } = ' sex is good! ' ;
  15497. is( undef, sanitize( $mysync ), 'sanitize: => undef' ) ;
  15498. is( 'example.com', $mysync->{ host1 }, 'sanitize: host1 " example.com " => "example.com"' ) ;
  15499. is( 'to to', $mysync->{ user1 }, 'sanitize: user1 " to to " => "to to"' ) ;
  15500. is( 'sex is good!', $mysync->{ password1 }, 'sanitize: password1 " sex is good! " => "sex is good!"' ) ;
  15501. note( 'Leaving tests_remove_edging_blanks()' ) ;
  15502. return ;
  15503. }
  15504. sub sanitize
  15505. {
  15506. my $mysync = shift @ARG ;
  15507. if ( ! defined $mysync )
  15508. {
  15509. return ;
  15510. }
  15511. foreach my $parameter ( qw( host1 host2 user1 user2 password1 password2 ) )
  15512. {
  15513. $mysync->{ $parameter } = remove_edging_blanks( $mysync->{ $parameter } ) ;
  15514. }
  15515. return ;
  15516. }
  15517. sub easyany
  15518. {
  15519. my $mysync = shift @ARG ;
  15520. # Gmail
  15521. if ( $mysync->{gmail1} and $mysync->{gmail2} ) {
  15522. $mysync->{ debug } and myprint( "gmail1 gmail2\n") ;
  15523. gmail12( $mysync ) ;
  15524. return ;
  15525. }
  15526. if ( $mysync->{gmail1} ) {
  15527. $mysync->{ debug } and myprint( "gmail1\n" ) ;
  15528. gmail1( $mysync ) ;
  15529. }
  15530. if ( $mysync->{gmail2} ) {
  15531. $mysync->{ debug } and myprint( "gmail2\n" ) ;
  15532. gmail2( $mysync ) ;
  15533. }
  15534. # Office 365
  15535. if ( $mysync->{office1} ) {
  15536. office1( $mysync ) ;
  15537. }
  15538. if ( $mysync->{office2} ) {
  15539. office2( $mysync ) ;
  15540. }
  15541. # Exchange
  15542. if ( $mysync->{exchange1} ) {
  15543. exchange1( $mysync ) ;
  15544. }
  15545. if ( $mysync->{exchange2} ) {
  15546. exchange2( $mysync ) ;
  15547. }
  15548. # Domino
  15549. if ( $mysync->{domino1} ) {
  15550. domino1( $mysync ) ;
  15551. }
  15552. if ( $mysync->{domino2} ) {
  15553. domino2( $mysync ) ;
  15554. }
  15555. return ;
  15556. }
  15557. # From and for https://imapsync.lamiral.info/FAQ.d/FAQ.Gmail.txt
  15558. sub gmail12
  15559. {
  15560. my $mysync = shift @ARG ;
  15561. # Gmail at host1 and host2
  15562. $mysync->{host1} ||= 'imap.gmail.com' ;
  15563. $mysync->{ssl1} = ( defined $mysync->{ssl1} ) ? $mysync->{ssl1} : 1 ;
  15564. $mysync->{host2} ||= 'imap.gmail.com' ;
  15565. $mysync->{ssl2} = ( defined $mysync->{ssl2} ) ? $mysync->{ssl2} : 1 ;
  15566. $mysync->{maxbytespersecond} ||= 20_000 ; # should be less than 10_000 when computed from Gmail documentation
  15567. $mysync->{maxbytesafter} ||= 1_000_000_000 ; # In fact it is documented as half: 500_000_000
  15568. $mysync->{automap} = ( defined $mysync->{automap} ) ? $mysync->{automap} : 1 ;
  15569. $mysync->{maxsleep} = ( defined $mysync->{maxsleep} ) ? $mysync->{maxsleep} : $MAX_SLEEP ; ;
  15570. $mysync->{ skipcrossduplicates } = ( defined $mysync->{ skipcrossduplicates } ) ? $mysync->{ skipcrossduplicates } : 0 ;
  15571. $mysync->{ synclabels } = ( defined $mysync->{ synclabels } ) ? $mysync->{ synclabels } : 1 ;
  15572. $mysync->{ resynclabels } = ( defined $mysync->{ resynclabels } ) ? $mysync->{ resynclabels } : 1 ;
  15573. push @useheader, 'X-Gmail-Received', 'Message-Id' ;
  15574. push @exclude, '\[Gmail\]$' ;
  15575. push @folderlast, '[Gmail]/All Mail' ;
  15576. return ;
  15577. }
  15578. sub gmail1
  15579. {
  15580. my $mysync = shift @ARG ;
  15581. # Gmail at host2
  15582. $mysync->{host1} ||= 'imap.gmail.com' ;
  15583. $mysync->{ssl1} = ( defined $mysync->{ssl1} ) ? $mysync->{ssl1} : 1 ;
  15584. $mysync->{maxbytespersecond} ||= 40_000 ; # should be 30_000 computed from by Gmail documentation
  15585. $mysync->{maxbytesafter} ||= 3_000_000_000 ; #
  15586. $mysync->{automap} = ( defined $mysync->{automap} ) ? $mysync->{automap} : 1 ;
  15587. $mysync->{maxsleep} = ( defined $mysync->{maxsleep} ) ? $mysync->{maxsleep} : $MAX_SLEEP ; ;
  15588. $mysync->{ skipcrossduplicates } = ( defined $mysync->{ skipcrossduplicates } ) ? $mysync->{ skipcrossduplicates } : 1 ;
  15589. push @useheader, 'X-Gmail-Received', 'Message-Id' ;
  15590. push @{ $mysync->{ regextrans2 } }, 's,\[Gmail\].,,' ;
  15591. push @folderlast, '[Gmail]/All Mail' ;
  15592. return ;
  15593. }
  15594. sub gmail2
  15595. {
  15596. my $mysync = shift @ARG ;
  15597. # Gmail at host2
  15598. $mysync->{ host2 } ||= 'imap.gmail.com' ;
  15599. $mysync->{ ssl2 } = ( defined $mysync->{ ssl2 } ) ? $mysync->{ ssl2 } : 1 ;
  15600. $mysync->{ maxbytespersecond } ||= 20_000 ; # should be less than 10_000 computed from by Gmail documentation
  15601. $mysync->{ maxbytesafter } ||= 1_000_000_000 ; # In fact it is documented as half: 500_000_000
  15602. $mysync->{ automap } = ( defined $mysync->{ automap } ) ? $mysync->{ automap } : 1 ;
  15603. $mysync->{ expunge1 } = ( defined $mysync->{ expunge1 } ) ? $mysync->{ expunge1 } : 1 ;
  15604. $mysync->{ addheader } = ( defined $mysync->{ addheader } ) ? $mysync->{ addheader } : 1 ;
  15605. $mysync->{ maxsleep } = ( defined $mysync->{ maxsleep } ) ? $mysync->{ maxsleep } : $MAX_SLEEP ; ;
  15606. #$mysync->{ maxsize } = ( defined $mysync->{ maxsize } ) ? $mysync->{ maxsize } : $GMAIL_MAXSIZE ;
  15607. if ( ! $mysync->{ noexclude } ) {
  15608. push @exclude, '\[Gmail\]$' ;
  15609. }
  15610. push @useheader, 'Message-Id' ;
  15611. push @{ $mysync->{ regextrans2 } }, 's,\[Gmail\].,,' ;
  15612. # push @{ $mysync->{ regextrans2 } }, 's/[ ]+/_/g' ; # is now replaced
  15613. # by the two more specific following regexes,
  15614. # they remove just the beginning and trailing blanks, not all.
  15615. push @{ $mysync->{ regextrans2 } }, 's,^ +| +$,,g' ;
  15616. push @{ $mysync->{ regextrans2 } }, 's,/ +| +/,/,g' ;
  15617. #
  15618. push @{ $mysync->{ regextrans2 } }, q{s/['\\^"]/_/g} ; # Verified this
  15619. push @folderlast, '[Gmail]/All Mail' ;
  15620. return ;
  15621. }
  15622. # From https://imapsync.lamiral.info/FAQ.d/FAQ.Exchange.txt
  15623. sub office1
  15624. {
  15625. # Office 365 at host1
  15626. my $mysync = shift @ARG ;
  15627. output( $mysync, q{Option --office1 is like: --host1 outlook.office365.com --ssl1 --exclude "^Files$"} . "\n" ) ;
  15628. output( $mysync, "Option --office1 (cont) : unless overrided with --host1 otherhost --nossl1 --noexclude\n" ) ;
  15629. $mysync->{host1} ||= 'outlook.office365.com' ;
  15630. $mysync->{ssl1} = ( defined $mysync->{ssl1} ) ? $mysync->{ssl1} : 1 ;
  15631. if ( ! $mysync->{noexclude} ) {
  15632. push @exclude, '^Files$' ;
  15633. }
  15634. return ;
  15635. }
  15636. sub office2
  15637. {
  15638. # Office 365 at host2
  15639. my $mysync = shift @ARG ;
  15640. output( $mysync, qq{Option --office2 is like: --host2 outlook.office365.com --ssl2 --maxsize 45_000_000 --maxmessagespersecond 4\n} ) ;
  15641. output( $mysync, qq{Option --office2 (cont) : --disarmreadreceipts --regexmess "wrap 10500" --f1f2 "Files=Files_renamed_by_imapsync"\n} ) ;
  15642. output( $mysync, qq{Option --office2 (cont) : unless overrided with --host2 otherhost --nossl2 ... --nodisarmreadreceipts --noregexmess\n} ) ;
  15643. output( $mysync, qq{Option --office2 (cont) : and --nof1f2 to avoid Files folder renamed to Files_renamed_by_imapsync\n} ) ;
  15644. $mysync->{host2} ||= 'outlook.office365.com' ;
  15645. $mysync->{ssl2} = ( defined $mysync->{ssl2} ) ? $mysync->{ssl2} : 1 ;
  15646. $mysync->{ maxsize } ||= 45_000_000 ;
  15647. $mysync->{maxmessagespersecond} ||= 4 ;
  15648. #push @{ $mysync->{ regexflag } }, 's/\\\\Flagged//g' ; # No problem without! tested 2018_09_10
  15649. $disarmreadreceipts = ( defined $disarmreadreceipts ) ? $disarmreadreceipts : 1 ;
  15650. # I dislike double negation but here is one
  15651. if ( ! $mysync->{noregexmess} )
  15652. {
  15653. push @regexmess, 's,(.{10239}),$1\r\n,g' ;
  15654. }
  15655. # and another...
  15656. if ( ! $mysync->{nof1f2} )
  15657. {
  15658. push @{ $mysync->{f1f2} }, 'Files=Files_renamed_by_imapsync' ;
  15659. }
  15660. return ;
  15661. }
  15662. sub exchange1
  15663. {
  15664. # Exchange 2010/2013 at host1
  15665. my $mysync = shift @ARG ;
  15666. output( $mysync, "Option --exchange1 does nothing (except printing this line...)\n" ) ;
  15667. # Well nothing to do so far
  15668. return ;
  15669. }
  15670. sub exchange2
  15671. {
  15672. # Exchange 2010/2013 at host2
  15673. my $mysync = shift @ARG ;
  15674. output( $mysync, "Option --exchange2 is like: --maxsize 10_000_000 --maxmessagespersecond 4 --disarmreadreceipts\n" ) ;
  15675. output( $mysync, "Option --exchange2 (cont) : --regexflag del Flagged --regexmess wrap 10500\n" ) ;
  15676. output( $mysync, "Option --exchange2 (cont) : unless overrided with --maxsize xxx --nodisarmreadreceipts --noregexflag --noregexmess\n" ) ;
  15677. $mysync->{ maxsize } ||= 10_000_000 ;
  15678. $mysync->{maxmessagespersecond} ||= 4 ;
  15679. $disarmreadreceipts = ( defined $disarmreadreceipts ) ? $disarmreadreceipts : 1 ;
  15680. # I dislike double negation but here are two
  15681. if ( ! $mysync->{noregexflag} ) {
  15682. push @{ $mysync->{ regexflag } }, 's/\\\\Flagged//g' ;
  15683. }
  15684. if ( ! $mysync->{noregexmess} ) {
  15685. push @regexmess, 's,(.{10239}),$1\r\n,g' ;
  15686. }
  15687. return ;
  15688. }
  15689. sub domino1
  15690. {
  15691. # Domino at host1
  15692. my $mysync = shift @ARG ;
  15693. $mysync->{ sep1 } = q{\\} ;
  15694. $prefix1 = q{} ;
  15695. $messageidnodomain = ( defined $messageidnodomain ) ? $messageidnodomain : 1 ;
  15696. return ;
  15697. }
  15698. sub domino2
  15699. {
  15700. # Domino at host1
  15701. my $mysync = shift @ARG ;
  15702. $mysync->{ sep2 } = q{\\} ;
  15703. $prefix2 = q{} ;
  15704. $messageidnodomain = ( defined $messageidnodomain ) ? $messageidnodomain : 1 ;
  15705. push @{ $mysync->{ regextrans2 } }, 's,^Inbox\\\\(.*),$1,i' ;
  15706. return ;
  15707. }
  15708. sub tests_resolv
  15709. {
  15710. note( 'Entering tests_resolv()' ) ;
  15711. # is( , resolv( ), 'resolv: => ' ) ;
  15712. is( undef, resolv( ), 'resolv: no args => undef' ) ;
  15713. is( undef, resolv( q{} ), 'resolv: empty string => undef' ) ;
  15714. is( undef, resolv( 'hostnotexist' ), 'resolv: hostnotexist => undef' ) ;
  15715. is( '127.0.0.1', resolv( '127.0.0.1' ), 'resolv: 127.0.0.1 => 127.0.0.1' ) ;
  15716. is( '127.0.0.1', resolv( 'localhost' ), 'resolv: localhost => 127.0.0.1' ) ;
  15717. is( '2001:41d0:2:84e0::1', resolv( 'imapsync.lamiral.info' ), 'resolv: imapsync.lamiral.info => 2001:41d0:2:84e0::1' ) ;
  15718. # ip6-localhost ( in /etc/hosts )
  15719. is( '::1', resolv( 'ip6-localhost' ), 'resolv: ip6-localhost => ::1' ) ;
  15720. is( '::1', resolv( '::1' ), 'resolv: ::1 => ::1' ) ;
  15721. # ks2ipv6 now has CNAME ks6ipv6
  15722. is( '2001:41d0:8:d8b6::1', resolv( '2001:41d0:8:d8b6::1' ), 'resolv: 2001:41d0:8:d8b6::1 => 2001:41d0:8:d8b6::1' ) ;
  15723. is( '2001:41d0:8:9951::1', resolv( 'ks6ipv6.lamiral.info' ), 'resolv: ks6ipv6.lamiral.info => 2001:41d0:8:9951::1' ) ;
  15724. # ks6
  15725. is( '2001:41d0:8:9951::1', resolv( '2001:41d0:8:9951::1' ), 'resolv: 2001:41d0:8:9951::1 => 2001:41d0:8:9951::1' ) ;
  15726. is( '2001:41d0:8:9951::1', resolv( 'ks6ipv6.lamiral.info' ), 'resolv: ks6ipv6.lamiral.info => 2001:41d0:8:9951::1' ) ;
  15727. # ks3
  15728. is( '2001:41d0:8:bebd::1', resolv( '2001:41d0:8:bebd::1' ), 'resolv: 2001:41d0:8:bebd::1 => 2001:41d0:8:bebd::1' ) ;
  15729. is( '2001:41d0:8:bebd::1', resolv( 'ks3ipv6.lamiral.info' ), 'resolv: ks3ipv6.lamiral.info => 2001:41d0:8:bebd::1' ) ;
  15730. note( 'Leaving tests_resolv()' ) ;
  15731. return ;
  15732. }
  15733. sub resolv
  15734. {
  15735. my $host = shift @ARG ;
  15736. if ( ! $host ) { return ; }
  15737. my $addr ;
  15738. if ( defined &Socket::getaddrinfo ) {
  15739. $addr = resolv_with_getaddrinfo( $host ) ;
  15740. return( $addr ) ;
  15741. }
  15742. my $iaddr = inet_aton( $host ) ;
  15743. if ( ! $iaddr ) { return ; }
  15744. $addr = inet_ntoa( $iaddr ) ;
  15745. return $addr ;
  15746. }
  15747. sub resolv_with_getaddrinfo
  15748. {
  15749. my $host = shift @ARG ;
  15750. $sync->{ debug } and myprint( "Entering resolv_with_getaddrinfo( $host )\n" ) ;
  15751. if ( ! $host ) { return ; }
  15752. my ( $err_getaddrinfo, @res ) = Socket::getaddrinfo( $host, "", { socktype => Socket::SOCK_RAW } ) ;
  15753. if ( $err_getaddrinfo ) {
  15754. myprint( "Cannot getaddrinfo of $host: $err_getaddrinfo\n" ) ;
  15755. return ;
  15756. }
  15757. my @addr ;
  15758. while( my $ai = shift @res ) {
  15759. my ( $err_getnameinfo, $ipaddr ) = Socket::getnameinfo( $ai->{addr}, Socket::NI_NUMERICHOST(), Socket::NIx_NOSERV() ) ;
  15760. if ( $err_getnameinfo ) {
  15761. myprint( "Cannot getnameinfo of $host: $err_getnameinfo\n" ) ;
  15762. return ;
  15763. }else{
  15764. $sync->{ debug } and myprint( "$host => $ipaddr\n" ) ;
  15765. push @addr, $ipaddr ;
  15766. my $reverse ;
  15767. ( $err_getnameinfo, $reverse ) = Socket::getnameinfo( $ai->{addr}, 0, Socket::NIx_NOSERV() ) ;
  15768. $sync->{ debug } and myprint( "$host => $ipaddr => $reverse\n" ) ;
  15769. }
  15770. $sync->{ debug } and myprint( "$host => $ipaddr\n" ) ;
  15771. }
  15772. $sync->{ debug } and myprint( "Leaving resolv_with_getaddrinfo( $host => $addr[0])\n" ) ;
  15773. return $addr[0] ;
  15774. }
  15775. sub tests_resolvrev
  15776. {
  15777. note( 'Entering tests_resolvrev()' ) ;
  15778. # is( , resolvrev( ), 'resolvrev: => ' ) ;
  15779. is( undef, resolvrev( ), 'resolvrev: no args => undef' ) ;
  15780. is( undef, resolvrev( q{} ), 'resolvrev: empty string => undef' ) ;
  15781. is( undef, resolvrev( 'hostnotexist' ), 'resolvrev: hostnotexist => undef' ) ;
  15782. is( 'localhost', resolvrev( '127.0.0.1' ), 'resolvrev: 127.0.0.1 => localhost' ) ;
  15783. is( 'localhost', resolvrev( 'localhost' ), 'resolvrev: localhost => localhost' ) ;
  15784. is( 'ks.lamiral.info', resolvrev( 'imapsync.lamiral.info' ), 'resolvrev: imapsync.lamiral.info => ks.lamiral.info' ) ;
  15785. # ip6-localhost ( in /etc/hosts )
  15786. is( 'ip6-localhost', resolvrev( 'ip6-localhost' ), 'resolvrev: ip6-localhost => ip6-localhost' ) ;
  15787. is( 'ip6-localhost', resolvrev( '::1' ), 'resolvrev: ::1 => ip6-localhost' ) ;
  15788. # ks2
  15789. is( 'ks6ipv6.lamiral.info', resolvrev( '2001:41d0:8:d8b6::1' ), 'resolvrev: 2001:41d0:8:d8b6::1 => ks6ipv6.lamiral.info' ) ;
  15790. is( 'ks6ipv6.lamiral.info', resolvrev( 'ks6ipv6.lamiral.info' ), 'resolvrev: ks6ipv6.lamiral.info => ks6ipv6.lamiral.info' ) ;
  15791. # ks3
  15792. is( 'ks3ipv6.lamiral.info', resolvrev( '2001:41d0:8:bebd::1' ), 'resolvrev: 2001:41d0:8:bebd::1 => ks3ipv6.lamiral.info' ) ;
  15793. is( 'ks3ipv6.lamiral.info', resolvrev( 'ks3ipv6.lamiral.info' ), 'resolvrev: ks3ipv6.lamiral.info => ks3ipv6.lamiral.info' ) ;
  15794. note( 'Leaving tests_resolvrev()' ) ;
  15795. return ;
  15796. }
  15797. sub resolvrev
  15798. {
  15799. my $host = shift @ARG ;
  15800. if ( ! $host ) { return ; }
  15801. if ( defined &Socket::getaddrinfo ) {
  15802. my $name = resolvrev_with_getaddrinfo( $host ) ;
  15803. return( $name ) ;
  15804. }
  15805. return ;
  15806. }
  15807. sub resolvrev_with_getaddrinfo
  15808. {
  15809. my $host = shift @ARG ;
  15810. if ( ! $host ) { return ; }
  15811. my ( $err, @res ) = Socket::getaddrinfo( $host, "", { socktype => Socket::SOCK_RAW } ) ;
  15812. if ( $err ) {
  15813. myprint( "Cannot getaddrinfo of $host: $err\n" ) ;
  15814. return ;
  15815. }
  15816. my @name ;
  15817. while( my $ai = shift @res ) {
  15818. my ( $err, $reverse ) = Socket::getnameinfo( $ai->{addr}, 0, Socket::NIx_NOSERV() ) ;
  15819. if ( $err ) {
  15820. myprint( "Cannot getnameinfo of $host: $err\n" ) ;
  15821. return ;
  15822. }
  15823. $sync->{ debug } and myprint( "$host => $reverse\n" ) ;
  15824. push @name, $reverse ;
  15825. }
  15826. return $name[0] ;
  15827. }
  15828. sub tests_imapsping
  15829. {
  15830. note( 'Entering tests_imapsping()' ) ;
  15831. is( undef, imapsping( ), 'imapsping: no args => undef' ) ;
  15832. is( undef, imapsping( 'hostnotexist' ), 'imapsping: hostnotexist => undef' ) ;
  15833. is( 1, imapsping( 'imapsync.lamiral.info' ), 'imapsping: imapsync.lamiral.info => 1' ) ;
  15834. is( 1, imapsping( 'ks6ipv6.lamiral.info' ), 'imapsping: ks6ipv6.lamiral.info => 1' ) ;
  15835. note( 'Leaving tests_imapsping()' ) ;
  15836. return ;
  15837. }
  15838. sub imapsping
  15839. {
  15840. my $host = shift @ARG ;
  15841. return tcpping( $host, $IMAP_SSL_PORT ) ;
  15842. }
  15843. sub tests_tcpping
  15844. {
  15845. note( 'Entering tests_tcpping()' ) ;
  15846. is( undef, tcpping( ), 'tcpping: no args => undef' ) ;
  15847. is( undef, tcpping( 'hostnotexist' ), 'tcpping: one arg => undef' ) ;
  15848. is( undef, tcpping( undef, 888 ), 'tcpping: arg undef, port => undef' ) ;
  15849. is( undef, tcpping( 'hostnotexist', 993 ), 'tcpping: hostnotexist 993 => undef' ) ;
  15850. is( undef, tcpping( 'hostnotexist', 888 ), 'tcpping: hostnotexist 888 => undef' ) ;
  15851. is( 1, tcpping( 'imapsync.lamiral.info', 993 ), 'tcpping: imapsync.lamiral.info 993 => 1' ) ;
  15852. is( 0, tcpping( 'imapsync.lamiral.info', 888 ), 'tcpping: imapsync.lamiral.info 888 => 0' ) ;
  15853. is( 1, tcpping( '5.135.158.182', 993 ), 'tcpping: 5.135.158.182 993 => 1' ) ;
  15854. is( 0, tcpping( '5.135.158.182', 888 ), 'tcpping: 5.135.158.182 888 => 0' ) ;
  15855. # Net::Ping supports ipv6 only after release 1.50
  15856. # http://cpansearch.perl.org/src/RURBAN/Net-Ping-2.59/Changes
  15857. # Anyway I plan to avoid Net-Ping for that too long standing feature
  15858. # Net-Ping is integrated in Perl itself, who knows ipv6 for a long time
  15859. is( 1, tcpping( '2001:41d0:8:d8b6::1', 993 ), 'tcpping: 2001:41d0:8:d8b6::1 993 => 1' ) ;
  15860. is( 0, tcpping( '2001:41d0:8:d8b6::1', 888 ), 'tcpping: 2001:41d0:8:d8b6::1 888 => 0' ) ;
  15861. note( 'Leaving tests_tcpping()' ) ;
  15862. return ;
  15863. }
  15864. sub tcpping
  15865. {
  15866. if ( 2 != scalar( @ARG ) ) {
  15867. return ;
  15868. }
  15869. my ( $host, $port ) = @ARG ;
  15870. if ( ! $host ) { return ; }
  15871. if ( ! $port ) { return ; }
  15872. my $mytimeout = $TCP_PING_TIMEOUT ;
  15873. require Net::Ping ;
  15874. #my $p = Net::Ping->new( 'tcp' ) ;
  15875. my $p = Net::Ping->new( ) ;
  15876. $p->{port_num} = $port ;
  15877. $p->service_check( 1 ) ;
  15878. $p->hires( 1 ) ;
  15879. my ($ping_ok, $rtt, $ip ) = $p->ping( $host, $mytimeout ) ;
  15880. if ( ! defined $ping_ok ) { return ; }
  15881. my $rtt_approx = sprintf( "%.3f", $rtt ) ;
  15882. $sync->{ debug } and myprint( "Host $host timeout $mytimeout port $port ok $ping_ok ip $ip acked in $rtt_approx s\n" ) ;
  15883. $p->close( ) ;
  15884. if( $ping_ok ) {
  15885. return 1 ;
  15886. }else{
  15887. return 0 ;
  15888. }
  15889. }
  15890. sub tests_sslcheck
  15891. {
  15892. note( 'Entering tests_sslcheck()' ) ;
  15893. my $mysync ;
  15894. is( undef, sslcheck( $mysync ), 'sslcheck: no sslcheck => undef' ) ;
  15895. $mysync = {
  15896. sslcheck => 1,
  15897. } ;
  15898. is( 0, sslcheck( $mysync ), 'sslcheck: no host => 0' ) ;
  15899. $mysync = {
  15900. sslcheck => 1,
  15901. host1 => 'test1.lamiral.info',
  15902. tls1 => 1,
  15903. } ;
  15904. is( 0, sslcheck( $mysync ), 'sslcheck: tls1 => 0' ) ;
  15905. $mysync = {
  15906. sslcheck => 1,
  15907. host1 => 'test1.lamiral.info',
  15908. } ;
  15909. is( 1, sslcheck( $mysync ), 'sslcheck: test1.lamiral.info => 1' ) ;
  15910. is( 1, $mysync->{ssl1}, 'sslcheck: test1.lamiral.info => ssl1 1' ) ;
  15911. $mysync->{sslcheck} = 0 ;
  15912. is( undef, sslcheck( $mysync ), 'sslcheck: sslcheck off => undef' ) ;
  15913. $mysync = {
  15914. sslcheck => 1,
  15915. host1 => 'test1.lamiral.info',
  15916. host2 => 'test2.lamiral.info',
  15917. } ;
  15918. is( 2, sslcheck( $mysync ), 'sslcheck: test1.lamiral.info + test2.lamiral.info => 2' ) ;
  15919. $mysync = {
  15920. sslcheck => 1,
  15921. host1 => 'test1.lamiral.info',
  15922. host2 => 'test2.lamiral.info',
  15923. tls1 => 1,
  15924. } ;
  15925. is( 1, sslcheck( $mysync ), 'sslcheck: test1.lamiral.info + test2.lamiral.info + tls1 => 1' ) ;
  15926. note( 'Leaving tests_sslcheck()' ) ;
  15927. return ;
  15928. }
  15929. sub sslcheck
  15930. {
  15931. my $mysync = shift @ARG ;
  15932. if ( ! $mysync->{sslcheck} ) {
  15933. return ;
  15934. }
  15935. my $nb_on = 0 ;
  15936. $mysync->{ debug } and myprint( "sslcheck\n" ) ;
  15937. if (
  15938. ( ! defined $mysync->{port1} )
  15939. and
  15940. ( ! defined $mysync->{tls1} )
  15941. and
  15942. ( ! defined $mysync->{ssl1} )
  15943. and
  15944. ( defined $mysync->{host1} )
  15945. ) {
  15946. myprint( "Host1: probing ssl on port $IMAP_SSL_PORT ( use --nosslcheck to avoid this ssl probe ) \n" ) ;
  15947. if ( probe_imapssl( $mysync->{host1} ) ) {
  15948. $mysync->{ssl1} = 1 ;
  15949. 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" ) ;
  15950. $nb_on++ ;
  15951. }else{
  15952. myprint( "Host1: sslcheck did not detected open ssl port $IMAP_SSL_PORT. Will use standard $IMAP_PORT port.\n" ) ;
  15953. }
  15954. }
  15955. if (
  15956. ( ! defined $mysync->{port2} )
  15957. and
  15958. ( ! defined $mysync->{tls2} )
  15959. and
  15960. ( ! defined $mysync->{ssl2} )
  15961. and
  15962. ( defined $mysync->{host2} )
  15963. ) {
  15964. myprint( "Host2: probing ssl on port $IMAP_SSL_PORT ( use --nosslcheck to avoid this ssl probe ) \n" ) ;
  15965. if ( probe_imapssl( $mysync->{host2} ) ) {
  15966. $mysync->{ssl2} = 1 ;
  15967. 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" ) ;
  15968. $nb_on++ ;
  15969. }else{
  15970. myprint( "Host2: sslcheck did not detected open ssl port $IMAP_SSL_PORT. Will use standard $IMAP_PORT port.\n" ) ;
  15971. }
  15972. }
  15973. return $nb_on ;
  15974. }
  15975. sub testslive_init
  15976. {
  15977. my $mysync = shift @ARG ;
  15978. $mysync->{host1} ||= 'test1.lamiral.info' ;
  15979. $mysync->{user1} ||= 'test1' ;
  15980. $mysync->{password1} ||= 'secret1' ;
  15981. $mysync->{host2} ||= 'test2.lamiral.info' ;
  15982. $mysync->{user2} ||= 'test2' ;
  15983. $mysync->{password2} ||= 'secret2' ;
  15984. return ;
  15985. }
  15986. sub testslive6_init
  15987. {
  15988. my $mysync = shift @ARG ;
  15989. $mysync->{host1} ||= 'ks6ipv6.lamiral.info' ;
  15990. $mysync->{user1} ||= 'test1' ;
  15991. $mysync->{password1} ||= 'secret1' ;
  15992. $mysync->{host2} ||= 'ks6ipv6.lamiral.info' ;
  15993. $mysync->{user2} ||= 'test2' ;
  15994. $mysync->{password2} ||= 'secret2' ;
  15995. return ;
  15996. }
  15997. sub tests_backslash_caret
  15998. {
  15999. note( 'Entering tests_backslash_caret()' ) ;
  16000. is( "lalala", backslash_caret( "lalala" ), 'backslash_caret: lalala => lalala' ) ;
  16001. is( "lalala\n", backslash_caret( "lalala\n" ), 'backslash_caret: lalala => lalala 2nd' ) ;
  16002. is( '^', backslash_caret( '\\' ), 'backslash_caret: \\ => ^' ) ;
  16003. is( "^\n", backslash_caret( "\\\n" ), 'backslash_caret: \\ => ^' ) ;
  16004. is( "\\lalala", backslash_caret( "\\lalala" ), 'backslash_caret: \\lalala => \\lalala' ) ;
  16005. is( "\\lal\\ala", backslash_caret( "\\lal\\ala" ), 'backslash_caret: \\lal\\ala => \\lal\\ala' ) ;
  16006. is( "\\lalala\n", backslash_caret( "\\lalala\n" ), 'backslash_caret: \\lalala => \\lalala 2nd' ) ;
  16007. is( "lalala^\n", backslash_caret( "lalala\\\n" ), 'backslash_caret: lalala\\\n => lalala^\n' ) ;
  16008. is( "lalala^\nlalala^\n", backslash_caret( "lalala\\\nlalala\\\n" ), 'backslash_caret: lalala\\\nlalala\\\n => lalala^\nlalala^\n' ) ;
  16009. is( "lal\\ala^\nlalala^\n", backslash_caret( "lal\\ala\\\nlalala\\\n" ), 'backslash_caret: lal\\ala\\\nlalala\\\n => lal\\ala^\nlalala^\n' ) ;
  16010. note( 'Leaving tests_backslash_caret()' ) ;
  16011. return ;
  16012. }
  16013. sub backslash_caret
  16014. {
  16015. my $string = shift @ARG ;
  16016. $string =~ s{\\ $ }{^}gxms ;
  16017. return $string ;
  16018. }
  16019. sub tests_split_around_equal
  16020. {
  16021. note( 'Entering tests_split_around_equal()' ) ;
  16022. is( undef, split_around_equal( ), 'split_around_equal: no args => undef' ) ;
  16023. is_deeply( { toto => 'titi' }, { split_around_equal( 'toto=titi' ) }, 'split_around_equal: toto=titi => toto => titi' ) ;
  16024. is_deeply( { toto => undef }, { split_around_equal( 'toto' ) }, 'split_around_equal: tototiti => toto => undef' ) ;
  16025. is_deeply( { toto => '' }, { split_around_equal( 'toto=' ) }, 'split_around_equal: tototiti => toto= => empty' ) ;
  16026. is_deeply( { A => 'B', C => 'D' }, { split_around_equal( 'A=B', 'C=D' ) }, 'split_around_equal: A=B C=D => A => B, C=>D' ) ;
  16027. is_deeply( { A => 'B', C => 'D', E => 'F' }, { split_around_equal( 'A=B', 'C=D', 'E=F' ) }, 'split_around_equal: A=B C=D => A => B, C=>D' ) ;
  16028. is_deeply( { A => 'B=C' }, { split_around_equal( 'A=B=C' ) }, 'split_around_equal: A=B=C => A => B=C' ) ;
  16029. is_deeply( { A => 'B=C=D' }, { split_around_equal( 'A=B=C=D' ) }, 'split_around_equal: A=B=C=D => A => B=C=D' ) ;
  16030. note( 'Leaving tests_split_around_equal()' ) ;
  16031. return ;
  16032. }
  16033. sub split_around_equal
  16034. {
  16035. if ( ! @ARG ) { return ; } ;
  16036. return map { split( /=/mxs, $_, 2 ) } @ARG ;
  16037. }
  16038. sub tests_sig_install
  16039. {
  16040. note( 'Entering tests_sig_install()' ) ;
  16041. my $mysync ;
  16042. is( undef, sig_install( ), 'sig_install: no args => undef' ) ;
  16043. is( undef, sig_install( $mysync ), 'sig_install: arg undef => undef' ) ;
  16044. $mysync = { } ;
  16045. is( undef, sig_install( $mysync ), 'sig_install: empty hash => undef' ) ;
  16046. SKIP: {
  16047. Readonly my $SKIP_15 => 15 ;
  16048. if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests only for Unix', $SKIP_15 ) ; }
  16049. # Default to ignore USR1 USR2 in case future install fails
  16050. local $SIG{ USR1 } = local $SIG{ USR2 } = sub { } ;
  16051. kill( 'USR1', $PROCESS_ID ) ;
  16052. $mysync->{ debugsig } = 1 ;
  16053. # Assign USR1 to call sub tototo
  16054. # Surely a better value than undef should be returned when doing real signal stuff
  16055. is( undef, sig_install( $mysync, 'tototo', 'USR1' ), 'sig_install: USR1 tototo' ) ;
  16056. is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 1' ) ;
  16057. is( 1, $mysync->{ tototo_calls }, 'sig_install: tototo call nb 1' ) ;
  16058. #return ;
  16059. # Assign USR2 to call sub tototo
  16060. is( undef, sig_install( $mysync, 'tototo', 'USR2' ), 'sig_install: USR2 tototo' ) ;
  16061. is( 1, kill( 'USR2', $PROCESS_ID ), 'sig_install: kill USR2 myself 1' ) ;
  16062. is( 2, $mysync->{ tototo_calls }, 'sig_install: tototo call nb 2' ) ;
  16063. is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 2' ) ;
  16064. is( 3, $mysync->{ tototo_calls }, 'sig_install: tototo call nb 3' ) ;
  16065. local $SIG{ USR1 } = local $SIG{ USR2 } = sub { } ;
  16066. is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 3' ) ;
  16067. is( 3, $mysync->{ tototo_calls }, 'sig_install: tototo call still nb 3' ) ;
  16068. # Assign USR1 + USR2 to call sub tototo
  16069. is( undef, sig_install( $mysync, 'tototo', 'USR1', 'USR2' ), 'sig_install: USR1 USR2 tototo' ) ;
  16070. is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 4' ) ;
  16071. is( 4, $mysync->{ tototo_calls }, 'sig_install: tototo call now nb 4' ) ;
  16072. is( 1, kill( 'USR2', $PROCESS_ID ), 'sig_install: kill USR1 myself 2' ) ;
  16073. is( 5, $mysync->{ tototo_calls }, 'sig_install: tototo call now nb 5' ) ;
  16074. }
  16075. note( 'Leaving tests_sig_install()' ) ;
  16076. return ;
  16077. }
  16078. #
  16079. sub sig_install
  16080. {
  16081. my $mysync = shift @ARG ;
  16082. if ( ! $mysync ) { return ; }
  16083. my $mysubname = shift @ARG ;
  16084. if ( ! $mysubname ) { return ; }
  16085. if ( ! @ARG ) { return ; }
  16086. my @signals = @ARG ;
  16087. my $mysub = \&$mysubname ;
  16088. #$mysync->{ debugsig } = 1 ;
  16089. $mysync->{ debugsig } and myprint( "In sig_install with sub $mysubname and signal @ARG\n" ) ;
  16090. my $subsignal = sub {
  16091. my $signame = shift @ARG ;
  16092. $mysync->{ debugsig } and myprint( "In subsignal with $signame and $mysubname\n" ) ;
  16093. &$mysub( $mysync, $signame ) ;
  16094. } ;
  16095. foreach my $signal ( @signals ) {
  16096. $mysync->{ debugsig } and myprint( "Installing signal $signal to call sub $mysubname\n") ;
  16097. output( $mysync, "kill -$signal $PROCESS_ID # special behavior: call to sub $mysubname\n" ) ;
  16098. ## no critic (RequireLocalizedPunctuationVars)
  16099. $SIG{ $signal } = $subsignal ;
  16100. }
  16101. return ;
  16102. }
  16103. sub tototo
  16104. {
  16105. my $mysync = shift @ARG ;
  16106. myprint("In tototo with @ARG\n" ) ;
  16107. $mysync->{ tototo_calls } += 1 ;
  16108. return ;
  16109. }
  16110. sub mygetppid
  16111. {
  16112. if ( 'MSWin32' eq $OSNAME ) {
  16113. return( 'unknown under MSWin32 (too complicated)' ) ;
  16114. } else {
  16115. # Unix
  16116. return( getppid( ) ) ;
  16117. }
  16118. }
  16119. sub tests_toggle_sleep
  16120. {
  16121. note( 'Entering tests_toggle_sleep()' ) ;
  16122. is( undef, toggle_sleep( ), 'toggle_sleep: no args => undef' ) ;
  16123. my $mysync ;
  16124. is( undef, toggle_sleep( $mysync ), 'toggle_sleep: undef => undef' ) ;
  16125. $mysync = { } ;
  16126. is( undef, toggle_sleep( $mysync ), 'toggle_sleep: no maxsleep => undef' ) ;
  16127. $mysync->{maxsleep} = 3 ;
  16128. is( 0, toggle_sleep( $mysync ), 'toggle_sleep: 3 => 0' ) ;
  16129. is( $MAX_SLEEP, toggle_sleep( $mysync ), "toggle_sleep: 0 => $MAX_SLEEP" ) ;
  16130. is( 0, toggle_sleep( $mysync ), "toggle_sleep: $MAX_SLEEP => 0" ) ;
  16131. is( $MAX_SLEEP, toggle_sleep( $mysync ), "toggle_sleep: 0 => $MAX_SLEEP" ) ;
  16132. is( 0, toggle_sleep( $mysync ), "toggle_sleep: $MAX_SLEEP => 0" ) ;
  16133. SKIP: {
  16134. Readonly my $SKIP_9 => 9 ;
  16135. if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests only for Unix', $SKIP_9 ) ; }
  16136. # Default to ignore USR1 USR2 in case future install fails
  16137. local $SIG{ USR1 } = sub { } ;
  16138. kill( 'USR1', $PROCESS_ID ) ;
  16139. $mysync->{ debugsig } = 1 ;
  16140. # Assign USR1 to call sub toggle_sleep
  16141. is( undef, sig_install( $mysync, \&toggle_sleep, 'USR1' ), 'toggle_sleep: install USR1 toggle_sleep' ) ;
  16142. $mysync->{maxsleep} = 4 ;
  16143. is( 1, kill( 'USR1', $PROCESS_ID ), 'toggle_sleep: kill USR1 myself' ) ;
  16144. is( 0, $mysync->{ maxsleep }, 'toggle_sleep: toggle_sleep called => sleeps are 0s' ) ;
  16145. is( 1, kill( 'USR1', $PROCESS_ID ), 'toggle_sleep: kill USR1 myself again' ) ;
  16146. is( $MAX_SLEEP, $mysync->{ maxsleep }, "toggle_sleep: toggle_sleep called => sleeps are ${MAX_SLEEP}s" ) ;
  16147. is( 1, kill( 'USR1', $PROCESS_ID ), 'toggle_sleep: kill USR1 myself' ) ;
  16148. is( 0, $mysync->{ maxsleep }, 'toggle_sleep: toggle_sleep called => sleeps are 0s' ) ;
  16149. is( 1, kill( 'USR1', $PROCESS_ID ), 'toggle_sleep: kill USR1 myself again' ) ;
  16150. is( $MAX_SLEEP, $mysync->{ maxsleep }, "toggle_sleep: toggle_sleep called => sleeps are ${MAX_SLEEP}s" ) ;
  16151. }
  16152. note( 'Leaving tests_toggle_sleep()' ) ;
  16153. return ;
  16154. }
  16155. sub toggle_sleep
  16156. {
  16157. my $mysync = shift @ARG ;
  16158. myprint("In toggle_sleep with @ARG\n" ) ;
  16159. if ( !defined( $mysync ) ) { return ; }
  16160. if ( !defined( $mysync->{maxsleep} ) ) { return ; }
  16161. $mysync->{ maxsleep } = max( 0, $MAX_SLEEP - $mysync->{maxsleep} ) ;
  16162. myprint("Resetting maxsleep to ", $mysync->{maxsleep}, "s\n" ) ;
  16163. return $mysync->{maxsleep} ;
  16164. }
  16165. sub mypod2usage
  16166. {
  16167. my $fh_pod2usage = shift @ARG ;
  16168. pod2usage(
  16169. -exitval => 'NOEXIT',
  16170. -noperldoc => 1,
  16171. -verbose => 99,
  16172. -sections => [ qw(NAME VERSION USAGE OPTIONS) ],
  16173. -indent => 1,
  16174. -loose => 1,
  16175. -output => $fh_pod2usage,
  16176. ) ;
  16177. return ;
  16178. }
  16179. sub usage
  16180. {
  16181. my $mysync = shift @ARG ;
  16182. if ( ! defined $mysync ) { return ; }
  16183. my $usage = q{} ;
  16184. my $usage_from_pod ;
  16185. my $usage_footer = usage_footer( $mysync ) ;
  16186. # pod2usage writes on a filehandle only and I want a variable
  16187. open my $fh_pod2usage, ">", \$usage_from_pod
  16188. or do { warn $OS_ERROR ; return ; } ;
  16189. mypod2usage( $fh_pod2usage ) ;
  16190. close $fh_pod2usage ;
  16191. if ( 'MSWin32' eq $OSNAME ) {
  16192. $usage_from_pod = backslash_caret( $usage_from_pod ) ;
  16193. }
  16194. $usage = join( q{}, $usage_from_pod, $usage_footer ) ;
  16195. return( $usage ) ;
  16196. }
  16197. sub tests_usage
  16198. {
  16199. note( 'Entering tests_usage()' ) ;
  16200. my $usage ;
  16201. like( $usage = usage( $sync ), qr/Name:/, 'usage: contains Name:' ) ;
  16202. myprint( $usage ) ;
  16203. like( $usage, qr/Version:/, 'usage: contains Version:' ) ;
  16204. like( $usage, qr/Usage:/, 'usage: contains Usage:' ) ;
  16205. like( $usage, qr/imapsync/, 'usage: contains imapsync' ) ;
  16206. is( undef, usage( ), 'usage: no args => undef' ) ;
  16207. note( 'Leaving tests_usage()' ) ;
  16208. return ;
  16209. }
  16210. sub usage_footer
  16211. {
  16212. my $mysync = shift @ARG ;
  16213. my $footer = q{} ;
  16214. my $localhost_info = localhost_info( $mysync ) ;
  16215. my $rcs = $mysync->{rcs} ;
  16216. my $homepage = homepage( ) ;
  16217. my $imapsync_release = $STR_use_releasecheck ;
  16218. if ( $mysync->{releasecheck} ) {
  16219. $imapsync_release = check_last_release( ) ;
  16220. }
  16221. $footer = qq{$localhost_info
  16222. $rcs
  16223. $imapsync_release
  16224. $homepage
  16225. } ;
  16226. return( $footer ) ;
  16227. }
  16228. sub usage_complete
  16229. {
  16230. # Unused, I guess this function could be deleted
  16231. my $usage = <<'EOF' ;
  16232. --skipheader reg : Don't take into account header keyword
  16233. matching reg ex: --skipheader 'X.*'
  16234. --skipsize : Don't take message size into account to compare
  16235. messages on both sides. On by default.
  16236. Use --no-skipsize for using size comparaison.
  16237. --allowsizemismatch : allow RFC822.SIZE != fetched msg size
  16238. consider also --skipsize to avoid duplicate messages
  16239. when running syncs more than one time per mailbox
  16240. --reconnectretry1 int : reconnect to host1 if connection is lost up to
  16241. int times per imap command (default is 3)
  16242. --reconnectretry2 int : same as --reconnectretry1 but for host2
  16243. --split1 int : split the requests in several parts on host1.
  16244. int is the number of messages handled per request.
  16245. default is like --split1 100.
  16246. --split2 int : same thing on host2.
  16247. --nofixInboxINBOX : Don't fix Inbox INBOX mapping.
  16248. EOF
  16249. return( $usage ) ;
  16250. }
  16251. sub setvalfromcgikey
  16252. {
  16253. my ( $mysync, $mycgi, $key, $val ) = @ARG ;
  16254. my $badthings = 0 ;
  16255. my ( $name, $type, $struct ) ;
  16256. if ( $key !~ m/^([\w\d\|]+)([=:][isf])?([\+!\@\%])?$/mxs )
  16257. {
  16258. $badthings++ ;
  16259. next ; # Unknown item
  16260. }
  16261. else
  16262. {
  16263. $name = [ split '|', $1, 1 ]->[0] ; # option name ab|cd|ef => keep only ab
  16264. $type = $2 ; # = or : followed by i or s or f
  16265. $struct = $3 ; # + or ! or @ or %
  16266. }
  16267. if ( ( $struct || q{} ) eq '+' )
  16268. {
  16269. ${$val} = $mycgi->param( $name ) ; # "Incremental" integer
  16270. }
  16271. elsif ( $type )
  16272. {
  16273. my @values = $mycgi->multi_param( $name ) ;
  16274. #myprint( "type[$type]values[@values]\$struct[", $struct || q{}, "]val[$val]ref(val)[", ref($val), "]\n" ) ;
  16275. if ( ( $struct || q{} ) eq '%' or ref( $val ) eq 'HASH' )
  16276. {
  16277. setvalfromhash( $val, $type, @values ) ;
  16278. }
  16279. else
  16280. {
  16281. setvalfromlist( $mysync, $val, $name, $type, $struct, @values ) ;
  16282. }
  16283. }
  16284. else
  16285. {
  16286. setvalfromcheckbox( $mysync, $mycgi, $key, $name, $val ) ;
  16287. }
  16288. return $badthings ;
  16289. }
  16290. sub setvalfromlist
  16291. {
  16292. my ( $mysync, $val, $name, $type, $struct, @values ) = @ARG ;
  16293. if ( $type =~ m/i$/mxs )
  16294. {
  16295. @values = map { q{} ne $_ ? int $_ : undef } @values ;
  16296. }
  16297. elsif ( $type =~ m/f$/mxs )
  16298. {
  16299. @values = map { 0 + $_ } @values ;
  16300. }
  16301. if ( ( $struct || q{} ) eq '@' )
  16302. {
  16303. @{ ${$val} } = @values ;
  16304. my @option = map { +( "--$name", "$_" ) } @values ;
  16305. push @{ $mysync->{ cmdcgi } }, @option ;
  16306. }
  16307. elsif ( ref( $val ) eq 'ARRAY' )
  16308. {
  16309. @{$val} = @values ;
  16310. }
  16311. elsif ( my $value = $values[0] )
  16312. {
  16313. ${$val} = $value ;
  16314. push @{ $mysync->{ cmdcgi } }, "--$name", $value ;
  16315. }
  16316. else
  16317. {
  16318. }
  16319. return ;
  16320. }
  16321. sub setvalfromhash
  16322. {
  16323. my ( $val, $type, @values ) = @ARG ;
  16324. my %values = map { split /=/mxs, $_ } @values ;
  16325. if ( $type =~ m/i$/mxs )
  16326. {
  16327. foreach my $k ( keys %values )
  16328. {
  16329. $values{$k} = int $values{$k} ;
  16330. }
  16331. }
  16332. elsif ( $type =~ m/f$/mxs )
  16333. {
  16334. foreach my $k ( keys %values ) {
  16335. $values{$k} = 0 + $values{$k};
  16336. }
  16337. }
  16338. if ( 'REF' eq ref $val )
  16339. {
  16340. %{ ${$val} } = %values ;
  16341. }
  16342. else
  16343. {
  16344. %{$val} = %values ;
  16345. }
  16346. return ;
  16347. }
  16348. sub setvalfromcheckbox
  16349. {
  16350. my ( $mysync, $mycgi, $key, $name, $val ) = @ARG ;
  16351. # Checkbox
  16352. # --noname is set by name=0 or name=
  16353. my $value = $mycgi->param( $name ) ;
  16354. if ( defined $value )
  16355. {
  16356. ${$val} = $value ;
  16357. if ( $value )
  16358. {
  16359. push @{ $mysync->{ cmdcgi } }, "--$name" ;
  16360. }
  16361. else
  16362. {
  16363. push @{ $mysync->{ cmdcgi } }, "--no$name" ;
  16364. }
  16365. }
  16366. else
  16367. {
  16368. ${$val} = undef ;
  16369. }
  16370. return ;
  16371. }
  16372. sub myGetOptions
  16373. {
  16374. # Started as a copy of Luke Ross Getopt::Long::CGI
  16375. # https://metacpan.org/release/Getopt-Long-CGI
  16376. # So this sub function is under the same license as Getopt-Long-CGI Luke Ross wants it,
  16377. # which was Perl 5.6 or later licenses at the date of the copy.
  16378. # It also applies for the sub functions called from this one.
  16379. my $mysync = shift @ARG ;
  16380. my $arguments_ref = shift @ARG ;
  16381. my %options = @ARG ;
  16382. my $mycgi = $mysync->{cgi} ;
  16383. if ( not under_cgi_context() ) {
  16384. # Not CGI - pass upstream for normal command line handling
  16385. return Getopt::Long::GetOptionsFromArray( $arguments_ref, %options ) ;
  16386. }
  16387. # We must be in CGI context now
  16388. if ( ! defined( $mycgi ) ) { return ; }
  16389. my $badthings = 0 ;
  16390. foreach my $key ( sort keys %options ) {
  16391. my $val = $options{$key} ;
  16392. $badthings += setvalfromcgikey( $mysync, $mycgi, $key, $val ) ;
  16393. }
  16394. if ( $badthings ) {
  16395. return ; # undef or ()
  16396. }
  16397. else {
  16398. return ( 1 ) ;
  16399. }
  16400. }
  16401. sub tests_get_options_extra
  16402. {
  16403. note( 'Entering tests_get_options_extra()' ) ;
  16404. is( undef, get_options_extra( ), 'get_options_extra: no args => undef' ) ;
  16405. my $mysync = { } ;
  16406. is( undef, get_options_extra( $mysync ), 'get_options_extra: undef => undef' ) ;
  16407. my $cwd_save = getcwd( ) ;
  16408. ok( (-d 'W/tmp/tests/options_extra/' or mkpath( 'W/tmp/tests/options_extra/' )), 'get_options_extra: mkpath W/tmp/tests/options_extra/' ) ;
  16409. chdir 'W/tmp/tests/options_extra/' ;
  16410. is( '--debugimap1', string_to_file( '--debugimap1', 'options_extra.txt' ), 'get_options_extra: string_to_file filling options_extra.txt with --debugimap1' ) ;
  16411. is( '--debugimap1', file_to_string( 'options_extra.txt' ), 'get_options_extra: reading options_extra.txt is --debugimap1' ) ;
  16412. is( '', get_options_extra( $mysync ), 'get_options_extra: --debugimap1 in options_extra.txt => nothing left, empty string return' ) ;
  16413. is( 1, $mysync->{ acc1 }->{ debugimap }, 'get_options_extra: --debugimap1 in options_extra.txt => ok, acc1->debugimap = 1' ) ;
  16414. is( '--tls1 proutcaca', string_to_file( '--tls1 proutcaca', 'options_extra.txt' ), 'get_options_extra: string_to_file filling options_extra.txt with --tls1 proutcaca' ) ;
  16415. is( 'proutcaca', get_options_extra( $mysync ), 'get_options_extra: --tls1 proutcaca in options_extra.txt => proutcaca left, proutcaca return' ) ;
  16416. chdir $cwd_save ;
  16417. note( 'Leaving tests_get_options_extra()' ) ;
  16418. return ;
  16419. }
  16420. sub get_options_extra
  16421. {
  16422. my $mysync = shift @ARG ;
  16423. if ( ! defined $mysync ) { return ; }
  16424. if ( -f -r 'options_extra.txt' )
  16425. {
  16426. my $cwd = getcwd( ) ;
  16427. my $string = firstline( 'options_extra.txt' ) ;
  16428. my $rest = get_options_from_string( $mysync, $string ) ;
  16429. output( $mysync, "Reading extra options from file options_extra.txt (cwd: $cwd) : $string\n" ) ;
  16430. return $rest ;
  16431. }
  16432. else
  16433. {
  16434. return ;
  16435. }
  16436. }
  16437. sub tests_get_options_from_string
  16438. {
  16439. note( 'Entering tests_get_options_from_string()' ) ;
  16440. is( undef, get_options_from_string( ), 'get_options_from_string: no args => undef' ) ;
  16441. my $mysync = { } ;
  16442. is( undef, get_options_from_string( $mysync ), 'get_options_from_string: undef => undef' ) ;
  16443. is( '', get_options_from_string( $mysync, '--debugimap1' ),
  16444. 'get_options_from_string: --debugimap1 => ok, nothing left, empty string return' ) ;
  16445. is( 1, $mysync->{ acc1 }->{ debugimap }, 'get_options_from_string: --debugimap1 => ok, acc1->debugimap = 1' ) ;
  16446. $mysync = { } ; # reset
  16447. is( 'caca', get_options_from_string( $mysync, '--debugimap1 caca' ),
  16448. 'get_options_from_string: --debugimap1 caca => ok, caca left, caca return' ) ;
  16449. is( 1, $mysync->{ acc1 }->{ debugimap }, 'get_options_from_string: --debugimap1 => ok, acc1->debugimap = 1' ) ;
  16450. is( 'popo roro', get_options_from_string( $mysync, '--debugimap2 popo roro' ),
  16451. 'get_options_from_string: --debugimap1 popo roro => ok, popo roro left, popo roro return' ) ;
  16452. is( 1, $mysync->{ acc2 }->{ debugimap }, 'get_options_from_string: --debugimap2 popo roro => ok, acc2->debugimap = 1' ) ;
  16453. is( 1, $mysync->{ acc1 }->{ debugimap }, 'get_options_from_string: acc1->debugimap = 1 still' ) ;
  16454. is( '', get_options_from_string( $mysync, '--nodebugimap1 --debugflags --errorsmax 2' ),
  16455. 'get_options_from_string: --nodebugimap1 --debugflags --errorsmax 2 => ok, empty string return' ) ;
  16456. is( 0, $mysync->{ acc1 }->{ debugimap }, 'get_options_from_string: acc1->debugimap = 0 now' ) ;
  16457. is( 1, $mysync->{ debugflags }, 'get_options_from_string: debugflags = 1 now' ) ;
  16458. is( 2, $mysync->{ errorsmax }, 'get_options_from_string: mysync->errorsmax = 2 now' ) ;
  16459. is( '', get_options_from_string( $mysync, '--folder "IN BOX" --folder JOE' ),
  16460. 'get_options_from_string: --folder "IN BOX" --folder JOE => ok, empty string return' ) ;
  16461. is_deeply( [ 'IN BOX', 'JOE' ], [@{$mysync->{ folder }}], 'get_options_from_string: "IN BOX" "JOE"' ) ;
  16462. is( '', get_options_from_string( $mysync, '--debugflags --koko' ),
  16463. 'get_options_from_string: --debugflags --koko => ok, empty string return, with "Unknown option: koko" on STDERR' ) ;
  16464. note( 'Leaving tests_get_options_from_string()' ) ;
  16465. return ;
  16466. }
  16467. sub get_options_from_string
  16468. {
  16469. my $mysync = shift @ARG ;
  16470. my $mystring = shift @ARG ;
  16471. if ( ! defined $mystring ) { return ; }
  16472. my ( $ret, $args ) = Getopt::Long::GetOptionsFromString( $mystring,
  16473. 'debugimap!' => \$mysync->{ debugimap },
  16474. 'debugimap1!' => \$mysync->{ acc1 }->{ debugimap },
  16475. 'debugimap2!' => \$mysync->{ acc2 }->{ debugimap },
  16476. 'debugflags!' => \$mysync->{ debugflags },
  16477. 'debugsleep=f' => \$mysync->{ debugsleep },
  16478. 'errorsmax=i' => \$mysync->{ errorsmax },
  16479. 'folder=s@' => \$mysync->{ folder },
  16480. 'timeout=f' => \$mysync->{ timeout },
  16481. 'timeout1=f' => \$mysync->{ acc1 }->{ timeout },
  16482. 'timeout2=f' => \$mysync->{ acc2 }->{ timeout },
  16483. 'keepalive1!' => \$mysync->{ acc1 }->{ keepalive },
  16484. 'keepalive2!' => \$mysync->{ acc2 }->{ keepalive },
  16485. 'reconnectretry1=i' => \$mysync->{ acc1 }->{ reconnectretry },
  16486. 'reconnectretry2=i' => \$mysync->{ acc2 }->{ reconnectretry },
  16487. 'ssl1!' => \$mysync->{ ssl1 },
  16488. 'ssl2!' => \$mysync->{ ssl2 },
  16489. 'tls1!' => \$mysync->{ tls1 },
  16490. 'tls2!' => \$mysync->{ tls2 },
  16491. 'compress1!' => \$mysync->{ acc1 }->{ compress },
  16492. 'compress2!' => \$mysync->{ acc2 }->{ compress },
  16493. ) ;
  16494. my $left = join( ' ', @$args ) ;
  16495. return $left ;
  16496. }
  16497. sub tests_get_options_cgi_context
  16498. {
  16499. note( 'Entering tests_get_options_cgi_context()' ) ;
  16500. # Temporary, have to think harder about testing CGI context in command line --tests
  16501. # API:
  16502. # * input arguments: two ways, command line or CGI
  16503. # * the program arguments
  16504. # * QUERY_STRING env variable
  16505. # * return
  16506. # * QUERY_STRING length
  16507. # CGI context
  16508. local $ENV{SERVER_SOFTWARE} = 'Votre serviteur' ;
  16509. # Real full test
  16510. # = 'host1=test1.lamiral.info&user1=test1&password1=secret1&host2=test2.lamiral.info&user2=test2&password2=secret2&debugenv=on'
  16511. my $mysync ;
  16512. is( undef, get_options( $mysync ), 'get_options cgi context: no CGI module => undef' ) ;
  16513. # skip all next tests if the CGI module is not available
  16514. SKIP: {
  16515. if ( ! eval { require CGI ; } ) {
  16516. skip( "CGI Perl module is not installed", 19 ) ;
  16517. }
  16518. CGI->import( qw( -no_debug -utf8 ) ) ;
  16519. is( undef, get_options( $mysync ), 'get_options cgi context: no CGI param => undef' ) ;
  16520. # Testing boolean
  16521. $mysync->{cgi} = CGI->new( 'version=on&debugenv=on' ) ;
  16522. local $ENV{'QUERY_STRING'} = 'version=on&debugenv=on' ;
  16523. is( 22, get_options( $mysync ), 'get_options cgi context: QUERY_STRING => 22' ) ;
  16524. is( 'on', $mysync->{ version }, 'get_options cgi context: --version => on' ) ;
  16525. # debugenv is not allowed in cgi context
  16526. is( undef, $mysync->{debugenv}, 'get_options cgi context: $mysync->{debugenv} => undef' ) ;
  16527. # QUERY_STRING in this test is only for return value of get_options
  16528. # Have to think harder, GET/POST context, is this return value a good thing?
  16529. local $ENV{'QUERY_STRING'} = 'host1=test1.lamiral.info&user1=test1' ;
  16530. $mysync->{cgi} = CGI->new( 'host1=test1.lamiral.info&user1=test1' ) ;
  16531. is( 36, get_options( $mysync, ), 'get_options cgi context: QUERY_STRING => 36' ) ;
  16532. is( 'test1', $mysync->{user1}, 'get_options cgi context: $mysync->{user1} => test1' ) ;
  16533. #local $ENV{'QUERY_STRING'} = undef ;
  16534. # Testing s@ as ref
  16535. $mysync->{cgi} = CGI->new( 'folder=fd1' ) ;
  16536. get_options( $mysync ) ;
  16537. is_deeply( [ 'fd1' ], $mysync->{ folder }, 'get_options cgi context: $mysync->{ folder } => fd1' ) ;
  16538. $mysync->{cgi} = CGI->new( 'folder=fd1&folder=fd2' ) ;
  16539. get_options( $mysync ) ;
  16540. is_deeply( [ 'fd1', 'fd2' ], $mysync->{ folder }, 'get_options cgi context: $mysync->{ folder } => fd1, fd2' ) ;
  16541. # Testing %
  16542. $mysync->{cgi} = CGI->new( 'f1f2h=s1=d1&f1f2h=s2=d2&f1f2h=s3=d3' ) ;
  16543. get_options( $mysync ) ;
  16544. is_deeply( { 's1' => 'd1', 's2' => 'd2', 's3' => 'd3' },
  16545. $mysync->{f1f2h}, 'get_options cgi context: f1f2h => s1=d1 s2=d2 s3=d3' ) ;
  16546. # Testing boolean ! with --noxxx, doesnot work
  16547. $mysync->{cgi} = CGI->new( 'nodry=on' ) ;
  16548. get_options( $mysync ) ;
  16549. is( undef, $mysync->{dry}, 'get_options cgi context: --nodry => $mysync->{dry} => undef' ) ;
  16550. $mysync->{cgi} = CGI->new( 'host1=example.com' ) ;
  16551. get_options( $mysync ) ;
  16552. is( 'example.com', $mysync->{host1}, 'get_options cgi context: --host1=example.com => $mysync->{host1} => example.com' ) ;
  16553. #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
  16554. $mysync->{cgi} = CGI->new( 'simulong=' ) ;
  16555. get_options( $mysync ) ;
  16556. is( undef, $mysync->{simulong}, 'get_options cgi context: --simulong= => $mysync->{simulong} => undef' ) ;
  16557. $mysync->{cgi} = CGI->new( 'simulong' ) ;
  16558. get_options( $mysync ) ;
  16559. is( undef, $mysync->{simulong}, 'get_options cgi context: --simulong => $mysync->{simulong} => undef' ) ;
  16560. $mysync->{cgi} = CGI->new( 'simulong=4' ) ;
  16561. get_options( $mysync ) ;
  16562. is( 4, $mysync->{simulong}, 'get_options cgi context: --simulong=4 => $mysync->{simulong} => 4' ) ;
  16563. is( undef, $mysync->{ folder }, 'get_options cgi context: --simulong=4 => $mysync->{ folder } => undef' ) ;
  16564. #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
  16565. $mysync ={} ;
  16566. $mysync->{cgi} = CGI->new( 'testslive=on' ) ;
  16567. get_options( $mysync ) ;
  16568. is( 'on', $mysync->{ testslive }, 'get_options cgi context: --testslive=on => testslive => on' ) ;
  16569. #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
  16570. $mysync ={} ;
  16571. $mysync->{cgi} = CGI->new( 'log=0' ) ;
  16572. get_options( $mysync ) ;
  16573. is( 0, $mysync->{ log }, 'get_options cgi context: --log=0 => log => 0' ) ;
  16574. #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
  16575. # What is this fucked up indentation?
  16576. }
  16577. note( 'Leaving tests_get_options_cgi_context()' ) ;
  16578. return ;
  16579. }
  16580. sub get_options_cgi
  16581. {
  16582. # In CGI context arguments are not in @ARGV but in QUERY_STRING variable (with GET).
  16583. my $mysync = shift @ARG ;
  16584. $mysync->{cgi} || return ;
  16585. my @arguments = @ARG ;
  16586. # final 0 is used to print usage when no option is given
  16587. my $numopt = length $ENV{'QUERY_STRING'} || 1 ;
  16588. $mysync->{f1f2h} = {} ;
  16589. my $opt_ret = myGetOptions(
  16590. $mysync,
  16591. \@arguments,
  16592. 'abort' => \$mysync->{ abort },
  16593. 'abortbyfile' => \$mysync->{ abortbyfile },
  16594. 'host1=s' => \$mysync->{ host1 },
  16595. 'host2=s' => \$mysync->{ host2 },
  16596. 'user1=s' => \$mysync->{ user1 },
  16597. 'user2=s' => \$mysync->{ user2 },
  16598. 'password1=s' => \$mysync->{ password1 },
  16599. 'password2=s' => \$mysync->{ password2 },
  16600. 'dry!' => \$mysync->{ dry },
  16601. 'dry1!' => \$mysync->{ dry1 },
  16602. 'version' => \$mysync->{ version },
  16603. 'ssl1!' => \$mysync->{ ssl1 },
  16604. 'ssl2!' => \$mysync->{ ssl2 },
  16605. 'tls1!' => \$mysync->{ tls1 },
  16606. 'tls2!' => \$mysync->{ tls2 },
  16607. 'compress1!' => \$mysync->{ acc1 }->{ compress },
  16608. 'compress2!' => \$mysync->{ acc2 }->{ compress },
  16609. 'justbanner!' => \$mysync->{ justbanner },
  16610. 'justlogin!' => \$mysync->{ justlogin },
  16611. 'justconnect!' => \$mysync->{ justconnect },
  16612. 'addheader!' => \$mysync->{ addheader },
  16613. 'automap!' => \$mysync->{ automap },
  16614. 'justautomap!' => \$mysync->{ justautomap },
  16615. 'gmail1' => \$mysync->{ gmail1 },
  16616. 'gmail2' => \$mysync->{ gmail2 },
  16617. 'office1' => \$mysync->{ office1 },
  16618. 'office2' => \$mysync->{ office2 },
  16619. 'exchange1' => \$mysync->{ exchange1 },
  16620. 'exchange2' => \$mysync->{ exchange2 },
  16621. 'domino1' => \$mysync->{ domino1 },
  16622. 'domino2' => \$mysync->{ domino2 },
  16623. 'f1f2=s@' => \$mysync->{ f1f2 },
  16624. 'f1f2h=s%' => \$mysync->{ f1f2h },
  16625. 'folder=s@' => \$mysync->{ folder },
  16626. 'testslive!' => \$mysync->{ testslive },
  16627. 'testslive6!' => \$mysync->{ testslive6 },
  16628. 'releasecheck!' => \$mysync->{ releasecheck },
  16629. 'simulong=f' => \$mysync->{ simulong },
  16630. 'debugsleep=f' => \$mysync->{ debugsleep },
  16631. 'subfolder1=s' => \$mysync->{ subfolder1 },
  16632. 'subfolder2=s' => \$mysync->{ subfolder2 },
  16633. 'justfolders!' => \$mysync->{ justfolders },
  16634. 'justfoldersizes!' => \$mysync->{ justfoldersizes },
  16635. 'delete1!' => \$mysync->{ delete1 },
  16636. 'delete2!' => \$mysync->{ delete2 },
  16637. 'delete2duplicates!' => \$mysync->{ delete2duplicates },
  16638. 'tail!' => \$mysync->{ tail },
  16639. 'tmphash=s' => \$mysync->{ tmphash },
  16640. 'exitwhenover=i' => \$mysync->{ exitwhenover },
  16641. 'exitonload!' => \$mysync->{ exitonload },
  16642. 'syncduplicates!' => \$mysync->{ syncduplicates },
  16643. 'skipcrossduplicates!' => \$mysync->{ skipcrossduplicates },
  16644. 'debugcrossduplicates!'=> \$mysync->{ debugcrossduplicates },
  16645. 'log!' => \$mysync->{ log },
  16646. 'loglogfile!' => \$mysync->{ loglogfile },
  16647. 'emailreportfrom=s' => \$mysync->{ email_report_from },
  16648. 'emailreport1!' => \$mysync->{ emailreport1 },
  16649. 'emailreport2!' => \$mysync->{ emailreport2 },
  16650. 'var=s@' => \$mysync->{ var },
  16651. # f1f2h=s% could be removed but
  16652. # tests_get_options_cgi() should be split before
  16653. # with a sub tests_myGetOptions()
  16654. ) ;
  16655. $mysync->{ debug } and output( $mysync, "get options: [$opt_ret][$numopt]\n" ) ;
  16656. if ( ! $opt_ret ) {
  16657. return ;
  16658. }
  16659. return $numopt ;
  16660. }
  16661. sub get_options_cmd
  16662. {
  16663. my $mysync = shift @ARG ;
  16664. my @arguments = @ARG ;
  16665. my $mycgi = $mysync->{cgi} ;
  16666. # final 0 is used to print usage when no option is given on command line
  16667. my $numopt = scalar @arguments || 0 ;
  16668. my $argv = join "\x00", @arguments ;
  16669. if ( $argv =~ m/-delete\x002/x ) {
  16670. output( $mysync, "May be you mean --delete2 instead of --delete 2\n" ) ;
  16671. return ;
  16672. }
  16673. $mysync->{f1f2h} = {} ;
  16674. my $opt_ret = myGetOptions(
  16675. $mysync,
  16676. \@arguments,
  16677. 'debug!' => \$mysync->{ debug },
  16678. 'debuglist!' => \$mysync->{ debuglist },
  16679. 'debugcontent!' => \$mysync->{ debugcontent },
  16680. 'debugsleep=f' => \$mysync->{ debugsleep },
  16681. 'debugflags!' => \$mysync->{ debugflags },
  16682. 'debugimap!' => \$mysync->{ debugimap },
  16683. 'debugimap1!' => \$mysync->{ acc1 }->{ debugimap },
  16684. 'debugimap2!' => \$mysync->{ acc2 }->{ debugimap },
  16685. 'debugdev!' => \$debugdev,
  16686. 'debugmemory!' => \$mysync->{debugmemory},
  16687. 'debugfolders!' => \$mysync->{debugfolders},
  16688. 'debugssl=i' => \$mysync->{debugssl},
  16689. 'debugcgi!' => \$debugcgi,
  16690. 'debugenv!' => \$mysync->{debugenv},
  16691. 'debugsig!' => \$mysync->{debugsig},
  16692. 'debuglabels!' => \$mysync->{debuglabels},
  16693. 'simulong=f' => \$mysync->{simulong},
  16694. 'abort' => \$mysync->{abort},
  16695. 'abortbyfile' => \$mysync->{abortbyfile},
  16696. 'host1=s' => \$mysync->{ host1 },
  16697. 'host2=s' => \$mysync->{ host2 },
  16698. 'port1=i' => \$mysync->{ port1 },
  16699. 'port2=i' => \$mysync->{ port2 },
  16700. 'inet4|ipv4' => \$mysync->{ inet4 },
  16701. 'inet6|ipv6' => \$mysync->{ inet6 },
  16702. 'user1=s' => \$mysync->{ user1 },
  16703. 'user2=s' => \$mysync->{ user2 },
  16704. 'gmail1' => \$mysync->{gmail1},
  16705. 'gmail2' => \$mysync->{gmail2},
  16706. 'office1' => \$mysync->{office1},
  16707. 'office2' => \$mysync->{office2},
  16708. 'exchange1' => \$mysync->{exchange1},
  16709. 'exchange2' => \$mysync->{exchange2},
  16710. 'domino1' => \$mysync->{domino1},
  16711. 'domino2' => \$mysync->{domino2},
  16712. 'domain1=s' => \$mysync->{ acc1 }->{ domain },
  16713. 'domain2=s' => \$mysync->{ acc2 }->{ domain },
  16714. 'password1=s' => \$mysync->{password1},
  16715. 'password2=s' => \$mysync->{password2},
  16716. 'passfile1=s' => \$mysync->{ passfile1 },
  16717. 'passfile2=s' => \$mysync->{ passfile2 },
  16718. 'authmd5!' => \$authmd5,
  16719. 'authmd51!' => \$authmd51,
  16720. 'authmd52!' => \$authmd52,
  16721. 'trylogin!' => \$mysync->{ trylogin },
  16722. 'oauthdirect1=s' => \$mysync->{ acc1 }->{ oauthdirect },
  16723. 'oauthdirect2=s' => \$mysync->{ acc2 }->{ oauthdirect },
  16724. 'oauthaccesstoken1=s' => \$mysync->{ acc1 }->{ oauthaccesstoken },
  16725. 'oauthaccesstoken2=s' => \$mysync->{ acc2 }->{ oauthaccesstoken },
  16726. 'sep1=s' => \$mysync->{ sep1 },
  16727. 'sep2=s' => \$mysync->{ sep2 },
  16728. 'sanitize!' => \$mysync->{ sanitize },
  16729. 'folder=s@' => \$mysync->{ folder },
  16730. 'folderrec=s' => \@folderrec,
  16731. 'include=s' => \@include,
  16732. 'exclude=s' => \@exclude,
  16733. 'noexclude' => \$mysync->{noexclude},
  16734. 'folderfirst=s' => \@folderfirst,
  16735. 'folderlast=s' => \@folderlast,
  16736. 'prefix1=s' => \$prefix1,
  16737. 'prefix2=s' => \$prefix2,
  16738. 'subfolder1=s' => \$mysync->{ subfolder1 },
  16739. 'subfolder2=s' => \$mysync->{ subfolder2 },
  16740. 'fixslash2!' => \$mysync->{ fixslash2 },
  16741. 'fixInboxINBOX!' => \$fixInboxINBOX,
  16742. 'regextrans2=s@' => \$mysync->{ regextrans2 },
  16743. 'mixfolders!' => \$mixfolders,
  16744. 'skipemptyfolders!' => \$mysync->{ skipemptyfolders },
  16745. 'regexmess=s' => \@regexmess,
  16746. 'noregexmess' => \$mysync->{noregexmess},
  16747. 'skipmess=s' => \@skipmess,
  16748. 'pipemess=s' => \@pipemess,
  16749. 'pipemesscheck!' => \$pipemesscheck,
  16750. 'disarmreadreceipts!' => \$disarmreadreceipts,
  16751. 'regexflag=s@' => \$mysync->{ regexflag },
  16752. 'noregexflag' => \$mysync->{ noregexflag },
  16753. 'filterflags!' => \$mysync->{ filterflags },
  16754. 'filterbuggyflags!' => \$mysync->{ filterbuggyflags },
  16755. 'flagscase!' => \$mysync->{ flagscase },
  16756. 'syncflagsaftercopy!' => \$syncflagsaftercopy,
  16757. 'resyncflags!' => \$mysync->{ resyncflags },
  16758. 'synclabels!' => \$mysync->{ synclabels },
  16759. 'resynclabels!' => \$mysync->{ resynclabels },
  16760. 'delete|delete1!' => \$mysync->{ delete1 },
  16761. 'delete2!' => \$mysync->{ delete2 },
  16762. 'delete2duplicates!' => \$mysync->{ delete2duplicates },
  16763. 'delete2folders!' => \$delete2folders,
  16764. 'delete2foldersonly=s' => \$delete2foldersonly,
  16765. 'delete2foldersbutnot=s' => \$delete2foldersbutnot,
  16766. 'syncinternaldates!' => \$syncinternaldates,
  16767. 'idatefromheader!' => \$idatefromheader,
  16768. 'syncacls!' => \$mysync->{ syncacls },
  16769. 'maxsize=i' => \$mysync->{ maxsize },
  16770. 'appendlimit=i' => \$mysync->{ appendlimit },
  16771. 'truncmess=i' => \$mysync->{ truncmess },
  16772. 'minsize=i' => \$minsize,
  16773. 'maxage=f' => \$maxage,
  16774. 'minage=f' => \$minage,
  16775. 'search=s' => \$search,
  16776. 'search1=s' => \$mysync->{ search1 },
  16777. 'search2=s' => \$mysync->{ search2 },
  16778. 'foldersizes!' => \$mysync->{ foldersizes },
  16779. 'foldersizesatend!' => \$mysync->{ foldersizesatend },
  16780. 'dry!' => \$mysync->{dry},
  16781. 'dry1!' => \$mysync->{dry1},
  16782. 'expunge1|expunge!' => \$mysync->{ expunge1 },
  16783. 'expunge2!' => \$mysync->{ expunge2 },
  16784. 'uidexpunge2!' => \$mysync->{ uidexpunge2 },
  16785. 'subscribed' => \$subscribed,
  16786. 'subscribe!' => \$subscribe,
  16787. 'subscribeall|subscribe_all!' => \$subscribeall,
  16788. 'justbanner!' => \$mysync->{ justbanner },
  16789. 'justfolders!'=> \$mysync->{ justfolders },
  16790. 'justfoldersizes!' => \$mysync->{ justfoldersizes },
  16791. 'version' => \$mysync->{version},
  16792. 'help' => \$help,
  16793. 'timeout=f' => \$mysync->{timeout},
  16794. 'timeout1=f' => \$mysync->{ acc1 }->{timeout},
  16795. 'timeout2=f' => \$mysync->{ acc2 }->{timeout},
  16796. 'skipheader=s' => \$mysync->{ skipheader },
  16797. 'useheader=s' => \@useheader,
  16798. 'wholeheaderifneeded!' => \$wholeheaderifneeded,
  16799. 'messageidnodomain!' => \$messageidnodomain,
  16800. 'skipsize!' => \$skipsize,
  16801. 'allowsizemismatch!' => \$allowsizemismatch,
  16802. 'fastio1!' => \$mysync->{ acc1 }->{ fastio },
  16803. 'fastio2!' => \$mysync->{ acc2 }->{ fastio },
  16804. 'sslcheck!' => \$mysync->{sslcheck},
  16805. 'ssl1!' => \$mysync->{ssl1},
  16806. 'ssl2!' => \$mysync->{ssl2},
  16807. 'ssl1_ssl_version=s' => \$mysync->{ acc1 }->{sslargs}->{SSL_version},
  16808. 'ssl2_ssl_version=s' => \$mysync->{ acc2 }->{sslargs}->{SSL_version},
  16809. 'sslargs1=s%' => \$mysync->{ acc1 }->{sslargs},
  16810. 'sslargs2=s%' => \$mysync->{ acc2 }->{sslargs},
  16811. 'tls1!' => \$mysync->{tls1},
  16812. 'tls2!' => \$mysync->{tls2},
  16813. 'uid1!' => \$uid1,
  16814. 'uid2!' => \$uid2,
  16815. 'authmech1=s' => \$mysync->{ acc1 }->{ authmech },
  16816. 'authmech2=s' => \$mysync->{ acc2 }->{ authmech },
  16817. 'authuser1=s' => \$mysync->{ acc1 }->{ authuser },
  16818. 'authuser2=s' => \$mysync->{ acc2 }->{ authuser },
  16819. 'proxyauth1' => \$mysync->{ acc1 }->{ proxyauth },
  16820. 'proxyauth2' => \$mysync->{ acc2 }->{ proxyauth },
  16821. 'compress1!' => \$mysync->{ acc1 }->{ compress },
  16822. 'compress2!' => \$mysync->{ acc2 }->{ compress },
  16823. 'keepalive1!' => \$mysync->{ acc1 }->{ keepalive },
  16824. 'keepalive2!' => \$mysync->{ acc2 }->{ keepalive },
  16825. 'split1=i' => \$split1,
  16826. 'split2=i' => \$split2,
  16827. 'buffersize=i' => \$buffersize,
  16828. 'reconnectretry1=i' => \$mysync->{ acc1 }->{ reconnectretry },
  16829. 'reconnectretry2=i' => \$mysync->{ acc2 }->{ reconnectretry },
  16830. 'tests!' => \$mysync->{ tests },
  16831. 'testsdebug|tests_debug!' => \$mysync->{ testsdebug },
  16832. 'testsunit=s@' => \$mysync->{testsunit},
  16833. 'testslive!' => \$mysync->{testslive},
  16834. 'testslive6!' => \$mysync->{testslive6},
  16835. 'justlogin!' => \$mysync->{justlogin},
  16836. 'justconnect!' => \$mysync->{justconnect},
  16837. 'tmpdir=s' => \$mysync->{ tmpdir },
  16838. 'pidfile=s' => \$mysync->{pidfile},
  16839. 'pidfilelocking!' => \$mysync->{pidfilelocking},
  16840. 'sigexit=s@' => \$mysync->{ sigexit },
  16841. 'sigreconnect=s@' => \$mysync->{ sigreconnect },
  16842. 'sigignore=s@' => \$mysync->{ sigignore },
  16843. 'releasecheck!' => \$mysync->{releasecheck},
  16844. 'modulesversion|modules_version!' => \$modulesversion,
  16845. 'usecache!' => \$mysync->{ usecache },
  16846. 'cacheaftercopy!' => \$cacheaftercopy,
  16847. 'debugcache!' => \$debugcache,
  16848. 'useuid!' => \$useuid,
  16849. 'addheader!' => \$mysync->{addheader},
  16850. 'exitwhenover=i' => \$mysync->{ exitwhenover },
  16851. 'exitonload!' => \$mysync->{ exitonload },
  16852. 'checkselectable!' => \$mysync->{ checkselectable },
  16853. 'checkfoldersexist!' => \$mysync->{ checkfoldersexist },
  16854. 'checkmessageexists!' => \$checkmessageexists,
  16855. 'expungeaftereach!' => \$mysync->{ expungeaftereach },
  16856. 'abletosearch!' => \$mysync->{abletosearch},
  16857. 'abletosearch1!' => \$mysync->{abletosearch1},
  16858. 'abletosearch2!' => \$mysync->{abletosearch2},
  16859. 'showpasswords!' => \$mysync->{showpasswords},
  16860. 'maxlinelength=i' => \$maxlinelength,
  16861. 'maxlinelengthcmd=s' => \$maxlinelengthcmd,
  16862. 'minmaxlinelength=i' => \$minmaxlinelength,
  16863. 'debugmaxlinelength!' => \$debugmaxlinelength,
  16864. 'fixcolonbug!' => \$fixcolonbug,
  16865. 'create_folder_old!' => \$create_folder_old,
  16866. 'maxmessagespersecond=f' => \$mysync->{maxmessagespersecond},
  16867. 'maxbytespersecond=i' => \$mysync->{maxbytespersecond},
  16868. 'maxbytesafter=i' => \$mysync->{maxbytesafter},
  16869. 'maxsleep=f' => \$mysync->{maxsleep},
  16870. 'syncduplicates!' => \$mysync->{ syncduplicates },
  16871. 'skipcrossduplicates!' => \$mysync->{ skipcrossduplicates },
  16872. 'debugcrossduplicates!' => \$mysync->{ debugcrossduplicates },
  16873. 'log!' => \$mysync->{log},
  16874. 'tail!' => \$mysync->{tail},
  16875. 'logfile=s' => \$mysync->{logfile},
  16876. 'logdir=s' => \$mysync->{logdir},
  16877. 'errorsmax=i' => \$mysync->{errorsmax},
  16878. 'errorsdump!' => \$mysync->{ errorsdump },
  16879. 'fetch_hash_set=s' => \$fetch_hash_set,
  16880. 'automap!' => \$mysync->{automap},
  16881. 'justautomap!' => \$mysync->{justautomap},
  16882. 'id!' => \$mysync->{id},
  16883. 'f1f2=s@' => \$mysync->{f1f2},
  16884. 'nof1f2' => \$mysync->{nof1f2},
  16885. 'f1f2h=s%' => \$mysync->{f1f2h},
  16886. 'justfolderlists!' => \$mysync->{justfolderlists},
  16887. 'delete1emptyfolders' => \$mysync->{delete1emptyfolders},
  16888. 'checknoabletosearch!' => \$mysync->{checknoabletosearch},
  16889. 'dockercontext!' => \$mysync->{ dockercontext },
  16890. 'emailreportfrom=s' => \$mysync->{ email_report_from },
  16891. 'emailreport1!' => \$mysync->{ emailreport1 },
  16892. 'emailreport2!' => \$mysync->{ emailreport2 },
  16893. 'var=s@' => \$mysync->{ var },
  16894. 'memorystress!' => \$mysync->{ memorystress },
  16895. ) ;
  16896. #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
  16897. $mysync->{ debug } and output( $mysync, "get options: [$opt_ret][$numopt]\n" ) ;
  16898. my $numopt_after = scalar @arguments ;
  16899. #myprint( "get options: [$opt_ret][$numopt][$numopt_after]\n" ) ;
  16900. # The $arguments[0] test is just because parallel adds "" when it is
  16901. # used with {=7=} in sync_parallel_unix.sh
  16902. if ( $numopt_after and $arguments[0] ) {
  16903. myprint(
  16904. "Found ", scalar( @arguments ), " extra arguments : [@arguments]\n",
  16905. "It usually means a quoting issue in the command line ",
  16906. "or some misspelling options.\n",
  16907. ) ;
  16908. return ;
  16909. }
  16910. if ( ! $opt_ret ) {
  16911. return ;
  16912. }
  16913. return $numopt ;
  16914. }
  16915. sub tests_get_options
  16916. {
  16917. note( 'Entering tests_get_options()' ) ;
  16918. # CAVEAT: still setting global variables, be careful
  16919. # with tests, the context increases! $debug stays on for example.
  16920. # API:
  16921. # * input arguments: two ways, command line or CGI
  16922. # * the program arguments
  16923. # * QUERY_STRING env variable
  16924. # * return
  16925. # * undef if bad things happened like
  16926. # * options not known
  16927. # * --delete 2 input
  16928. # * number of arguments or QUERY_STRING length
  16929. my $mysync = { } ;
  16930. is( undef, get_options( $mysync, qw( --noexist ) ), 'get_options: --noexist => undef' ) ;
  16931. is( undef, $mysync->{ noexist }, 'get_options: --noexist => undef' ) ;
  16932. $mysync = { } ;
  16933. is( undef, get_options( $mysync, qw( --lalala --noexist --version ) ), 'get_options: --lalala --noexist --version => undef' ) ;
  16934. is( 1, $mysync->{ version }, 'get_options: --version => 1' ) ;
  16935. is( undef, $mysync->{ noexist }, 'get_options: --noexist => undef' ) ;
  16936. $mysync = { } ;
  16937. is( 1, get_options( $mysync, qw( --delete2 ) ), 'get_options: --delete2 => 1' ) ;
  16938. is( 1, $mysync->{ delete2 }, 'get_options: --delete2 => var delete2 = 1' ) ;
  16939. $mysync = { } ;
  16940. is( undef, get_options( $mysync, qw( --delete 2 ) ), 'get_options: --delete 2 => var undef' ) ;
  16941. is( undef, $mysync->{ delete1 }, 'get_options: --delete 2 => var still undef ; good!' ) ;
  16942. $mysync = { } ;
  16943. is( undef, get_options( $mysync, "--delete 2" ), 'get_options: --delete 2 => undef' ) ;
  16944. is( 1, get_options( $mysync, "--version" ), 'get_options: --version => 1' ) ;
  16945. is( 1, get_options( $mysync, "--help" ), 'get_options: --help => 1' ) ;
  16946. is( undef, get_options( $mysync, qw( --noexist --version ) ), 'get_options: --debug --noexist --version => undef' ) ;
  16947. is( 1, get_options( $mysync, qw( --version ) ), 'get_options: --version => 1' ) ;
  16948. is( undef, get_options( $mysync, qw( extra ) ), 'get_options: extra => undef' ) ;
  16949. is( undef, get_options( $mysync, qw( extra1 --version extra2 ) ), 'get_options: extra1 --version extra2 => undef' ) ;
  16950. $mysync = { } ;
  16951. is( 2, get_options( $mysync, qw( --host1 HOST_01) ), 'get_options: --host1 HOST_01 => 1' ) ;
  16952. is( 'HOST_01', $mysync->{ host1 }, 'get_options: --host1 HOST_01 => HOST_01' ) ;
  16953. #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ;
  16954. note( 'Leaving tests_get_options()' ) ;
  16955. return ;
  16956. }
  16957. sub get_options
  16958. {
  16959. my $mysync = shift @ARG ;
  16960. my @arguments = @ARG ;
  16961. #myprint( "1 mysync: ", Data::Dumper->Dump( [ $mysync ] ) ) ;
  16962. my $ret ;
  16963. if ( under_cgi_context( ) ) {
  16964. # CGI context
  16965. $ret = get_options_cgi( $mysync, @arguments ) ;
  16966. }else{
  16967. # Command line context ;
  16968. $ret = get_options_cmd( $mysync, @arguments ) ;
  16969. } ;
  16970. #myprint( "2 mysync: ", Data::Dumper->Dump( [ $mysync ] ) ) ;
  16971. foreach my $key ( sort keys %{ $mysync } ) {
  16972. if ( ! defined $mysync->{$key} ) {
  16973. delete $mysync->{$key} ;
  16974. next ;
  16975. }
  16976. if ( 'ARRAY' eq ref( $mysync->{$key} )
  16977. and 0 == scalar( @{ $mysync->{$key} } ) ) {
  16978. delete $mysync->{$key} ;
  16979. }
  16980. }
  16981. return $ret ;
  16982. }
  16983. sub tests_infos
  16984. {
  16985. note( 'Entering tests_infos()' ) ;
  16986. note( "OSNAME=$OSNAME" ) ;
  16987. note( "hostname=". hostname( ) ) ;
  16988. note( "cwd=" . getcwd( ) ) ;
  16989. note( "PROGRAM_NAME=$PROGRAM_NAME" ) ;
  16990. my $stat = stat( "$PROGRAM_NAME" ) ;
  16991. my $perms = sprintf( "%04o\n", $stat->mode & oct( $PERMISSION_FILTER ) ) ;
  16992. note( "permissions=$perms" ) ;
  16993. note( "PROCESS_ID=$PROCESS_ID" ) ;
  16994. note( "REAL_USER_ID=$REAL_USER_ID" ) ;
  16995. note( "EFFECTIVE_USER_ID=$EFFECTIVE_USER_ID" ) ;
  16996. note( "context: " . imapsync_context( $sync ) ) ;
  16997. note( "memory_consumption_of_myself: " . memory_consumption_of_myself( ) . " bytes aka " . bytes_display_string_dec( memory_consumption_of_myself( ) ) ) ;
  16998. note( "cpu_number: " . cpu_number( ) ) ;
  16999. note( $sync->{ rcs } ) ;
  17000. note( 'Leaving tests_infos()' ) ;
  17001. return ;
  17002. }
  17003. sub condition_to_leave_after_tests
  17004. {
  17005. my $mysync = shift @ARG ;
  17006. if ( $mysync->{ testslive } or $mysync->{ testslive6 } )
  17007. {
  17008. return 0 ;
  17009. }
  17010. if ( $mysync->{ tests }
  17011. or $mysync->{ testsdebug }
  17012. or $mysync->{ testsunit }
  17013. )
  17014. {
  17015. return 1 ;
  17016. }
  17017. }
  17018. sub testunitsession
  17019. {
  17020. my $mysync = shift @ARG ;
  17021. if ( ! $mysync ) { return ; }
  17022. if ( ! $mysync->{ testsunit } ) { return ; }
  17023. my @functions = @{ $mysync->{ testsunit } } ;
  17024. if ( ! @functions ) { return ; }
  17025. SKIP: {
  17026. if ( ! @functions ) { skip 'No test in normal run' ; }
  17027. testsunit( @functions ) ;
  17028. done_testing( ) ;
  17029. }
  17030. return ;
  17031. }
  17032. sub tests_count_0s
  17033. {
  17034. note( 'Entering tests_count_zeros()' ) ;
  17035. is( 0, count_0s( ), 'count_0s: no parameters => 0' ) ;
  17036. is( 1, count_0s( 0 ), 'count_0s: 0 => 1' ) ;
  17037. is( 0, count_0s( 1 ), 'count_0s: 1 => 0' ) ;
  17038. is( 1, count_0s( 1, 0, 1 ), 'count_0s: 1, 0, 1 => 1' ) ;
  17039. is( 2, count_0s( 1, 0, 1, 0 ), 'count_0s: 1, 0, 1, 0 => 2' ) ;
  17040. note( 'Leaving tests_count_zeros()' ) ;
  17041. return ;
  17042. }
  17043. sub count_0s
  17044. {
  17045. my @array = @ARG ;
  17046. if ( ! @array ) { return 0 ; }
  17047. my $nb_zeros = 0 ;
  17048. map { $_ == 0 and $nb_zeros += 1 } @array ;
  17049. return $nb_zeros ;
  17050. }
  17051. sub tests_report_failures
  17052. {
  17053. note( 'Entering tests_report_failures()' ) ;
  17054. is( undef, report_failures( ), 'report_failures: no parameters => undef' ) ;
  17055. is( "nb 1 - first\n", report_failures( ({'ok' => 0, name => 'first'}) ), 'report_failures: "first" failed => nb 1 - first' ) ;
  17056. is( q{}, report_failures( ( {'ok' => 1, name => 'first'} ) ), 'report_failures: "first" success =>' ) ;
  17057. is( "nb 2 - second\n", report_failures( ( {'ok' => 1, name => 'second'}, {'ok' => 0, name => 'second'} ) ), 'report_failures: "second" failed => nb 2 - second' ) ;
  17058. 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' ) ;
  17059. note( 'Leaving tests_report_failures()' ) ;
  17060. return ;
  17061. }
  17062. sub report_failures
  17063. {
  17064. my @details = @ARG ;
  17065. if ( ! @details ) { return ; }
  17066. my $counter = 1 ;
  17067. my $report = q{} ;
  17068. foreach my $details ( @details ) {
  17069. if ( ! $details->{ 'ok' } ) {
  17070. my $name = $details->{ 'name' } || 'NONAME' ;
  17071. $report .= "nb $counter - $name\n" ;
  17072. }
  17073. $counter += 1 ;
  17074. }
  17075. return $report ;
  17076. }
  17077. sub tests_true
  17078. {
  17079. note( 'Entering tests_true()' ) ;
  17080. is( 1, 1, 'true: 1 is 1' ) ;
  17081. is( 'A', 'A', 'true: A is A' ) ;
  17082. note( 'Leaving tests_true()' ) ;
  17083. return ;
  17084. }
  17085. sub tests_always_fail
  17086. {
  17087. note( 'Entering tests_always_fail()' ) ;
  17088. is( 0, 1, 'always_fail: 0 is 1' ) ;
  17089. isnt( 'A', 'A', 'always_fail: A is A' ) ;
  17090. note( 'Leaving tests_always_fail()' ) ;
  17091. return ;
  17092. }
  17093. sub tests_testsunit
  17094. {
  17095. note( 'Entering tests_testunit()' ) ;
  17096. is( undef, testsunit( ), 'testsunit: no parameters => undef' ) ;
  17097. is( undef, testsunit( undef ), 'testsunit: an undef parameter => undef' ) ;
  17098. is( undef, testsunit( q{} ), 'testsunit: an empty parameter => undef' ) ;
  17099. is( undef, testsunit( 'idonotexist' ), 'testsunit: a do not exist function as parameter => undef' ) ;
  17100. is( undef, testsunit( 'tests_true' ), 'testsunit: tests_true => undef' ) ;
  17101. note( 'Leaving tests_testunit()' ) ;
  17102. return ;
  17103. }
  17104. sub testsunit
  17105. {
  17106. my @functions = @ARG ;
  17107. if ( ! @functions ) { #
  17108. myprint( "testsunit warning: no argument given\n" ) ;
  17109. return ;
  17110. }
  17111. foreach my $function ( @functions ) {
  17112. if ( ! $function ) {
  17113. myprint( "testsunit warning: argument is empty\n" ) ;
  17114. next ;
  17115. }
  17116. if ( ! exists &$function ) {
  17117. myprint( "testsunit warning: function $function does not exist\n" ) ;
  17118. next ;
  17119. }
  17120. if ( ! defined &$function ) {
  17121. myprint( "testsunit warning: function $function is not defined\n" ) ;
  17122. next ;
  17123. }
  17124. my $function_ref = \&{ $function } ;
  17125. &$function_ref() ;
  17126. }
  17127. return ;
  17128. }
  17129. sub testsdebug
  17130. {
  17131. # Now a little obsolete since there is
  17132. # imapsync ... --testsunit "anyfunction"
  17133. my $mysync = shift @ARG ;
  17134. if ( ! $mysync->{ testsdebug } ) { return ; }
  17135. SKIP: {
  17136. if ( ! $mysync->{ testsdebug } ) {
  17137. skip 'No test in normal run' ;
  17138. }
  17139. note( 'Entering testsdebug()' ) ;
  17140. #ok( ( ( not -d 'W/tmp/tests' ) or rmtree( 'W/tmp/tests/' ) ), 'testsdebug: rmtree W/tmp/tests' ) ;
  17141. #tests_check_binary_embed_all_dyn_libs( ) ;
  17142. #tests_killpid_by_parent( ) ;
  17143. #tests_killpid_by_brother( ) ;
  17144. #tests_kill_zero( ) ;
  17145. #tests_connect_socket( ) ;
  17146. #tests_probe_imapssl( ) ;
  17147. #tests_mailimapclient_connect( ) ;
  17148. #tests_always_fail( ) ;
  17149. #tests_localtimez( ) ;
  17150. #tests_year_month_day_hour_min_sec_ms( ) ;
  17151. #tests_date_rfc822( ) ;
  17152. #tests_email_report_message_id( ) ;
  17153. tests_all_pids( ) ;
  17154. tests_memory_consumption_of_myself( ) ;
  17155. tests_ram_memory_info( ) ;
  17156. tests_memory_consumption_of_all_pids( ) ;
  17157. tests_memory_consumption_all_pids_percent( ) ;
  17158. #tests_memory_stress( ) ;
  17159. tests_memory_consumption_of_pids_win32( ) ;
  17160. #tests_load_and_delay( ) ;
  17161. #tests_cpu_number( ) ;
  17162. #tests_loadavg( ) ;
  17163. #tests_load_per_cpu( ) ;
  17164. #tests_all_pids( ) ;
  17165. #tests_remove_qq( ) ;
  17166. #tests_remove_not_num( ) ;
  17167. tests_infos( ) ;
  17168. note( 'Leaving testsdebug()' ) ;
  17169. done_testing( ) ;
  17170. }
  17171. return ;
  17172. }
  17173. sub tests
  17174. {
  17175. my $mysync = shift @ARG ;
  17176. if ( ! $mysync->{ tests } ) { return ; }
  17177. SKIP: {
  17178. skip 'No test in normal run' if ( ! $mysync->{ tests } ) ;
  17179. note( 'Entering tests()' ) ;
  17180. tests_folder_routines( ) ;
  17181. tests_compare_lists( ) ;
  17182. tests_regexmess( ) ;
  17183. tests_skipmess( ) ;
  17184. tests_regexflags( );
  17185. tests_ucsecond( ) ;
  17186. tests_permanentflags();
  17187. tests_flags_filter( ) ;
  17188. tests_separator_invert( ) ;
  17189. tests_imap2_folder_name( ) ;
  17190. tests_command_line_nopassword( ) ;
  17191. tests_good_date( ) ;
  17192. tests_max( ) ;
  17193. tests_remove_not_num();
  17194. tests_memory_consumption_of_myself( ) ;
  17195. tests_is_a_release_number();
  17196. tests_imapsync_basename();
  17197. tests_list_keys_in_2_not_in_1();
  17198. tests_convert_sep_to_slash( ) ;
  17199. tests_match_a_cache_file( ) ;
  17200. tests_cache_map( ) ;
  17201. tests_get_cache( ) ;
  17202. tests_clean_cache( ) ;
  17203. tests_clean_cache_2( ) ;
  17204. tests_touch( ) ;
  17205. tests_flagscase( ) ;
  17206. tests_mkpath( ) ;
  17207. tests_extract_header( ) ;
  17208. tests_decompose_header( ) ;
  17209. tests_epoch( ) ;
  17210. tests_add_header( ) ;
  17211. tests_cache_dir_fix( ) ;
  17212. tests_cache_dir_fix_win( ) ;
  17213. tests_filter_forbidden_characters( ) ;
  17214. tests_cache_folder( ) ;
  17215. tests_time_remaining( ) ;
  17216. tests_decompose_regex( ) ;
  17217. tests_backtick( ) ;
  17218. tests_bytes_display_string_bin( ) ;
  17219. tests_bytes_display_string_dec( ) ;
  17220. tests_header_line_normalize( ) ;
  17221. tests_fix_Inbox_INBOX_mapping( ) ;
  17222. tests_max_line_length( ) ;
  17223. tests_subject( ) ;
  17224. tests_msgs_from_maxmin( ) ;
  17225. tests_tmpdir_has_colon_bug( ) ;
  17226. tests_sleep_max_messages( ) ;
  17227. tests_sleep_max_bytes( ) ;
  17228. tests_logfile( ) ;
  17229. tests_setlogfile( ) ;
  17230. tests_jux_utf8_old( ) ;
  17231. tests_jux_utf8( ) ;
  17232. tests_pipemess( ) ;
  17233. tests_jux_utf8_list( ) ;
  17234. tests_guess_prefix( ) ;
  17235. tests_guess_separator( ) ;
  17236. tests_format_for_imap_arg( ) ;
  17237. tests_imapsync_id( ) ;
  17238. tests_date_from_rcs( ) ;
  17239. tests_quota_extract_storage_limit_in_bytes( ) ;
  17240. tests_quota_extract_storage_current_in_bytes( ) ;
  17241. tests_guess_special( ) ;
  17242. tests_do_valid_directory( ) ;
  17243. tests_delete1emptyfolders( ) ;
  17244. tests_message_for_host2( ) ;
  17245. tests_length_ref( ) ;
  17246. tests_firstline( ) ;
  17247. tests_diff_or_NA( ) ;
  17248. tests_match_number( ) ;
  17249. tests_all_defined( ) ;
  17250. tests_special_from_folders_hash( ) ;
  17251. tests_notmatch( ) ;
  17252. tests_match( ) ;
  17253. tests_get_options( ) ;
  17254. tests_get_options_cgi_context( ) ;
  17255. tests_rand32( ) ;
  17256. tests_hashsynclocal( ) ;
  17257. tests_hashsync( ) ;
  17258. tests_output( ) ;
  17259. tests_output_reset_with( ) ;
  17260. tests_output_start( ) ;
  17261. tests_check_last_release( ) ;
  17262. tests_loadavg( ) ;
  17263. tests_cpu_number( ) ;
  17264. tests_load_and_delay( ) ;
  17265. #tests_imapsping( ) ;
  17266. #tests_tcpping( ) ;
  17267. tests_sslcheck( ) ;
  17268. tests_not_long_imapsync_version_public( ) ;
  17269. tests_reconnect_if_needed( ) ;
  17270. tests_reconnect_12_if_needed( ) ;
  17271. tests_sleep_if_needed( ) ;
  17272. tests_string_to_file( ) ;
  17273. tests_file_to_string( ) ;
  17274. tests_under_cgi_context( ) ;
  17275. tests_umask( ) ;
  17276. tests_umask_str( ) ;
  17277. tests_set_umask( ) ;
  17278. tests_createhashfileifneeded( ) ;
  17279. tests_slash_to_underscore( ) ;
  17280. tests_testsunit( ) ;
  17281. tests_count_0s( ) ;
  17282. tests_report_failures( ) ;
  17283. tests_min( ) ;
  17284. #tests_connect_socket( ) ;
  17285. #tests_resolvrev( ) ;
  17286. tests_usage( ) ;
  17287. tests_version_from_rcs( ) ;
  17288. tests_backslash_caret( ) ;
  17289. #tests_mailimapclient_connect_bug( ) ; # it fails with Mail-IMAPClient <= 3.39
  17290. tests_write_pidfile( ) ;
  17291. tests_remove_pidfile_not_running( ) ;
  17292. tests_match_a_pid_number( ) ;
  17293. tests_prefix_seperator_invertion( ) ;
  17294. tests_is_integer( ) ;
  17295. tests_integer_or_1( ) ;
  17296. tests_is_number( ) ;
  17297. tests_sig_install( ) ;
  17298. tests_template( ) ;
  17299. tests_split_around_equal( ) ;
  17300. tests_toggle_sleep( ) ;
  17301. tests_labels( ) ;
  17302. tests_synclabels( ) ;
  17303. tests_uidexpunge_or_expunge( ) ;
  17304. tests_appendlimit_from_capability( ) ;
  17305. tests_maxsize_setting( ) ;
  17306. tests_mock_capability( ) ;
  17307. tests_appendlimit( ) ;
  17308. tests_capability_of( ) ;
  17309. tests_search_in_array( ) ;
  17310. tests_operators_and_exclam_precedence( ) ;
  17311. tests_teelaunch( ) ;
  17312. tests_logfileprepa( ) ;
  17313. tests_useheader_suggestion( ) ;
  17314. tests_nb_messages_in_2_not_in_1( ) ;
  17315. tests_labels_add_subfolder2( ) ;
  17316. tests_labels_remove_subfolder1( ) ;
  17317. tests_resynclabels( ) ;
  17318. tests_labels_remove_special( ) ;
  17319. tests_uniq( ) ;
  17320. tests_remove_from_requested_folders( ) ;
  17321. tests_errors_log( ) ;
  17322. tests_add_subfolder1_to_folderrec( ) ;
  17323. tests_sanitize_subfolder( ) ;
  17324. tests_remove_edging_blanks( ) ;
  17325. tests_sanitize( ) ;
  17326. tests_remove_last_char_if_is( ) ;
  17327. tests_check_binary_embed_all_dyn_libs( ) ;
  17328. tests_nthline( ) ;
  17329. tests_secondline( ) ;
  17330. tests_tail( ) ;
  17331. tests_truncmess( ) ;
  17332. tests_eta( ) ;
  17333. tests_timesince( ) ;
  17334. tests_timenext( ) ;
  17335. tests_imapsync_context( ) ;
  17336. tests_abort( ) ;
  17337. tests_probe_imapssl( ) ;
  17338. tests_mailimapclient_connect( ) ;
  17339. tests_checknoabletosearch( ) ;
  17340. tests_errorsdump( ) ;
  17341. tests_errorsanalyse( ) ;
  17342. tests_most_common_error( ) ;
  17343. tests_errorclassify( ) ;
  17344. tests_error_type( ) ;
  17345. tests_sanitize_host( ) ;
  17346. tests_hmac_sha1_hex( ) ;
  17347. tests_total_bytes_max_reached( ) ;
  17348. tests_header_construct( ) ;
  17349. tests_remove_doublequotes_if_any( ) ;
  17350. tests_login_imap( ) ;
  17351. tests_login_imap_oauth( ) ;
  17352. tests_skipmess_neg( ) ;
  17353. tests_localtimez( ) ;
  17354. tests_file_to_array( ) ;
  17355. tests_cpu_time( ) ;
  17356. tests_cpu_percent( ) ;
  17357. tests_cpu_percent_global( ) ;
  17358. tests_flags_for_host2( ) ;
  17359. tests_under_docker_context( ) ;
  17360. tests_exit_value( ) ;
  17361. tests_comment_of_error_type( ) ;
  17362. tests_debugcontent( ) ;
  17363. tests_compress_ssl( ) ;
  17364. tests_compress( ) ;
  17365. tests_get_options_extra( ) ;
  17366. tests_get_options_from_string( ) ;
  17367. tests_email_report_message_id( ) ;
  17368. tests_year_month_day_hour_min_sec_ms( ) ;
  17369. tests_fractional_of_floor( ) ;
  17370. tests_date_rfc822( ) ;
  17371. tests_email_report_from( ) ;
  17372. tests_email_report_to( ) ;
  17373. tests_email_report_body_base( ) ;
  17374. tests_email_report( ) ;
  17375. tests_setlogdir( ) ;
  17376. tests_logfilesuffix( ) ;
  17377. tests_cgienvcontext( ) ;
  17378. tests_usecache_and_skipcrossduplicates( ) ;
  17379. tests_loglogfile( ) ;
  17380. tests_heavy_load_reached( ) ;
  17381. tests_heavy_load_percent_threshold( ) ;
  17382. tests_pctmem_available( ) ;
  17383. tests_filterbuggyflags( ) ;
  17384. tests_heavy_load_reached_by_memory( ) ;
  17385. tests_heavy_load_reached_by_cpu( ) ;
  17386. tests_load_per_cpu( ) ;
  17387. tests_memory_consumption_surface( ) ;
  17388. tests_add( ) ;
  17389. tests_all_pids( ) ;
  17390. tests_memory_consumption_of_all_pids( ) ;
  17391. tests_remove_qq( ) ;
  17392. tests_memory_consumption_all_pids_percent( ) ;
  17393. tests_ram_memory_info( ) ;
  17394. tests_infos( ) ;
  17395. #tests_resolv( ) ;
  17396. # Those three are for later use, when webserver will be inside imapsync
  17397. # or will be deleted them if I abandon the project.
  17398. #tests_killpid_by_parent( ) ;
  17399. #tests_killpid_by_brother( ) ;
  17400. #tests_kill_zero( ) ;
  17401. #tests_always_fail( ) ;
  17402. done_testing( 1992 ) ;
  17403. note( 'Leaving tests()' ) ;
  17404. }
  17405. return ;
  17406. }
  17407. sub tests_template
  17408. {
  17409. note( 'Entering tests_template()' ) ;
  17410. is( undef, template( ), 'tests_template: no args => undef' ) ;
  17411. my $mysync = { } ;
  17412. is( undef, template( $mysync ), 'tests_template: { } => undef' ) ;
  17413. is_deeply( {}, {}, 'tests_template: a hash is a hash' ) ;
  17414. is_deeply( [], [], 'tests_template: an array is an array' ) ;
  17415. note( 'Leaving tests_template()' ) ;
  17416. return ;
  17417. }
  17418. sub template
  17419. {
  17420. my $mysync = shift @ARG ;
  17421. return ;
  17422. }