kopano-migration-imap 326 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124
  1. #!/usr/bin/perl
  2. # structure
  3. # pod documentation
  4. # pragmas
  5. # main program
  6. # global variables initialisation
  7. # get_options( ) ;
  8. # default values
  9. # folder loop
  10. # subroutines
  11. # sub usage {
  12. # IMAPClient 3.xx ads
  13. # pod documentation
  14. =pod
  15. =head1 NAME
  16. kopano-migration-imap - Email IMAP tool for syncing, copying and migrating email mailboxes.
  17. The kopano-migration-imap command synchronises mailboxes between two imap servers.
  18. More than 69 different IMAP server softwares supported with success,
  19. few failures.
  20. $Revision: 1.727 $
  21. =head1 SYNOPSIS
  22. To synchronize the source imap account
  23. "test1" on server "test1.kopano.com" with password "secret1"
  24. to the destination imap account
  25. "test2" on server "test2.kopano.com" with password "secret2"
  26. do:
  27. kopano-migration-imap \
  28. --host1 test1.kopano.com --user1 test1 --password1 secret1 \
  29. --host2 test2.kopano.com --user2 test2 --password2 secret2
  30. =head1 REQUIRED ARGUMENTS
  31. The required argmuments are the six values, three on each sides,
  32. needed to login into the IMAP servers,
  33. a host, a username, and a password, two times.
  34. =head1 INSTALL
  35. kopano-migration-imap is provided by Kopano as RPM or DEB package
  36. with dependencies to make it easily installable on all supported
  37. platforms. As soon as you can read this by installation of a
  38. package, you have all the package dependencies installed successfully.
  39. kopano-migration-imap is based on imapsync from Gilles Lamiral
  40. =head1 CONFIGURATION
  41. There is no specific configuration file for kopano-migration-imap,
  42. everything is specified by the command line parameteres
  43. and the default behavior.
  44. =head1 USAGE
  45. To get a description of each option just run kopano-migration-imap
  46. with no argument, like this:
  47. kopano-migration-imap
  48. This description of options are reproduced here:
  49. usage: ./kopano-migration-imap [options]
  50. Several options are mandatory.
  51. str means string
  52. int means integer
  53. reg means regular expression
  54. cmd means command
  55. --dry : Makes kopano-migration-imap doing nothing, just print what would
  56. be done without --dry.
  57. --host1 str : Source or "from" imap server. Mandatory.
  58. --port1 int : Port to connect on host1. Default is 143, 993 if --ssl1
  59. --user1 str : User to login on host1. Mandatory.
  60. --showpasswords : Shows passwords on output instead of "MASKED".
  61. Useful to restart a complete run by just reading the log.
  62. --password1 str : Password for the user1.
  63. --host2 str : "destination" imap server. Mandatory.
  64. --port2 int : Port to connect on host2. Default is 143, 993 if --ssl2
  65. --user2 str : User to login on host2. Mandatory.
  66. --password2 str : Password for the user2.
  67. --passfile1 str : Password file for the user1. It must contain the
  68. password on the first line. This option avoids to show
  69. the password on the command line like --password1 does.
  70. --passfile2 str : Password file for the user2. Contains the password.
  71. --ssl1 : Use a SSL connection on host1.
  72. --ssl2 : Use a SSL connection on host2.
  73. --tls1 : Use a TLS connection on host1.
  74. --tls2 : Use a TLS connection on host2.
  75. --debugssl int : SSL debug mode from 0 to 4.
  76. --sslargs1 str : Pass any ssl parameter for host1 ssl or tls connection. Example:
  77. --sslargs1 SSL_verify_mode=1 --sslargs1 SSL_version=SSLv3
  78. See all possibilities in the new() method of IO::Socket::SSL
  79. http://search.cpan.org/perldoc?IO::Socket::SSL#Description_Of_Methods
  80. --sslargs2 str : Pass any ssl parameter for host2 ssl or tls connection.
  81. See --sslargs1
  82. --timeout1 int : Connection timeout in seconds for host1.
  83. Default is 120 and 0 means no timeout at all.
  84. --timeout2 int : Connection timeout in seconds for host2.
  85. Default is 120 and 0 means no timeout at all.
  86. --authmech1 str : Auth mechanism to use with host1:
  87. PLAIN, LOGIN, CRAM-MD5 etc. Use UPPERCASE.
  88. --authmech2 str : Auth mechanism to use with host2. See --authmech1
  89. --authuser1 str : User to auth with on host1 (admin user).
  90. Avoid using --authmech1 SOMETHING with --authuser1.
  91. --authuser2 str : User to auth with on host2 (admin user).
  92. --proxyauth1 : Use proxyauth on host1. Requires --authuser1.
  93. Required by Sun/iPlanet/Netscape IMAP servers to
  94. be able to use an administrative user.
  95. --proxyauth2 : Use proxyauth on host2. Requires --authuser2.
  96. --authmd51 : Use MD5 authentification for host1.
  97. --authmd52 : Use MD5 authentification for host2.
  98. --domain1 str : Domain on host1 (NTLM authentication).
  99. --domain2 str : Domain on host2 (NTLM authentication).
  100. --folder str : Sync this folder.
  101. --folder str : and this one, etc.
  102. --folderrec str : Sync this folder recursively.
  103. --folderrec str : and this one, etc.
  104. --folderfirst str : Sync this folder first. --folderfirst "Work"
  105. --folderfirst str : then this one, etc.
  106. --folderlast str : Sync this folder last. --folderlast "[Gmail]/All Mail"
  107. --folderlast str : then this one, etc.
  108. --nomixfolders : Do not merge folders when host1 is case sensitive
  109. while host2 is not (like Exchange). Only the first
  110. similar folder is synced (ex: Sent SENT sent -> Sent).
  111. --skipemptyfolders : Empty host1 folders are not created on host2.
  112. --f1f2 str1=str2 : Force folder str1 to be synced to str2.
  113. --include reg : Sync folders matching this regular expression
  114. --include reg : or this one, etc.
  115. in case both --include --exclude options are
  116. use, include is done before.
  117. --exclude reg : Skips folders matching this regular expression
  118. Several folders to avoid:
  119. --exclude 'fold1|fold2|f3' skips fold1, fold2 and f3.
  120. --exclude reg : or this one, etc.
  121. --subfolder2 str : Move whole host1 folders hierarchy under this
  122. host2 folder str .
  123. It does it by adding two --regextrans2 options before
  124. all others. Add --debug to see what's really going on.
  125. --regextrans2 reg : Apply the whole regex to each destination folders.
  126. --regextrans2 reg : and this one. etc.
  127. When you play with the --regextrans2 option, first
  128. add also the safe options --dry --justfolders
  129. Then, when happy, remove --dry, remove --justfolders.
  130. Have in mind that --regextrans2 is applied after prefix
  131. and separator inversion.
  132. --tmpdir str : Where to store temporary files and subdirectories.
  133. Will be created if it doesn't exist.
  134. Default is system specific, Unix is /tmp but
  135. it's often small and deleted at reboot.
  136. --tmpdir /var/tmp should be better.
  137. --pidfile str : The file where kopano-migration-imap pid is written.
  138. --pidfilelocking : Abort if pidfile already exists. Usefull to avoid
  139. concurrent transfers on the same mailbox.
  140. --nolog : Turn off logging on file
  141. --logfile str : Change the default log filename (can be dirname/filename).
  142. --logdir str : Change the default log directory. Default is LOG_kopano-migration-imap
  143. --prefix1 str : Remove prefix to all destination folders
  144. (usually INBOX. or INBOX/ or an empty string "")
  145. you have to use --prefix1 if host1 imap server
  146. does not have NAMESPACE capability, so kopano-migration-imap
  147. suggests to use it. All other cases are bad.
  148. --prefix2 str : Add prefix to all host2 folders. See --prefix1
  149. --sep1 str : Host1 separator in case NAMESPACE is not supported.
  150. --sep2 str : Host2 separator in case NAMESPACE is not supported.
  151. --skipmess reg : Skips messages maching the regex.
  152. Example: 'm/[\x80-ff]/' # to avoid 8bits messages.
  153. --skipmess is applied before --regexmess
  154. --skipmess reg : or this one, etc.
  155. --pipemess cmd : Apply this cmd command to each message content
  156. before the copy.
  157. --pipemess cmd : and this one, etc.
  158. --disarmreadreceipts : Disarms read receipts (host2 Exchange issue)
  159. --regexmess reg : Apply the whole regex to each message before transfer.
  160. Example: 's/\000/ /g' # to replace null by space.
  161. --regexmess reg : and this one, etc.
  162. --regexflag reg : Apply the whole regex to each flags list.
  163. Example: 's/"Junk"//g' # to remove "Junk" flag.
  164. --regexflag reg : and this one, etc.
  165. --delete : Deletes messages on host1 server after a successful
  166. transfer. Option --delete has the following behavior:
  167. it marks messages as deleted with the IMAP flag
  168. \Deleted, then messages are really deleted with an
  169. EXPUNGE IMAP command.
  170. --delete2 : Delete messages in host2 that are not in
  171. host1 server. Useful for backup or pre-sync.
  172. --delete2duplicates : Delete messages in host2 that are duplicates.
  173. Works only without --useuid since duplicates are
  174. detected with a header part of each message.
  175. --delete2folders : Delete folders in host2 that are not in host1 server.
  176. For safety, first try it like this (it is safe):
  177. --delete2folders --dry --justfolders --nofoldersizes
  178. --delete2foldersonly reg : Deleted only folders matching regex.
  179. Example: --delete2foldersonly "/^Junk$|^INBOX.Junk$/"
  180. --delete2foldersbutnot reg : Do not delete folders matching regex.
  181. Example: --delete2foldersbutnot "/Tasks$|Contacts$|Foo$/"
  182. --noexpunge : Do not expunge messages on host1.
  183. Expunge really deletes messages marked deleted.
  184. Expunge is made at the beginning, on host1 only.
  185. Newly transferred messages are also expunged if
  186. option --delete is given.
  187. No expunge is done on host2 account (unless --expunge2)
  188. --expunge1 : Expunge messages on host1 after messages transfer.
  189. --expunge2 : Expunge messages on host2 after messages transfer.
  190. --uidexpunge2 : uidexpunge messages on the host2 account
  191. that are not on the host1 account, requires --delete2
  192. --nomixfolders : Avoid merging folders that are considered different on
  193. host1 but the same on destination host2 because of
  194. case sensitivities and insensitivities.
  195. --syncinternaldates : Sets the internal dates on host2 same as host1.
  196. Turned on by default. Internal date is the date
  197. a message arrived on a host (mtime).
  198. --idatefromheader : Sets the internal dates on host2 same as the
  199. "Date:" headers.
  200. --maxsize int : Skip messages larger (or equal) than int bytes
  201. --minsize int : Skip messages smaller (or equal) than int bytes
  202. --maxage int : Skip messages older than int days.
  203. final stats (skipped) don't count older messages
  204. see also --minage
  205. --minage int : Skip messages newer than int days.
  206. final stats (skipped) don't count newer messages
  207. You can do (+ are the messages selected):
  208. past|----maxage+++++++++++++++>now
  209. past|+++++++++++++++minage---->now
  210. past|----maxage+++++minage---->now (intersection)
  211. past|++++minage-----maxage++++>now (union)
  212. --search str : Selects only messages returned by this IMAP SEARCH
  213. command. Applied on both sides.
  214. --search1 str : Same as --search for selecting host1 messages only.
  215. --search2 str : Same as --search for selecting host2 messages only.
  216. --search CRIT equals --search1 CRIT --search2 CRIT
  217. --exitwhenover int : Stop syncing when total bytes transferred reached.
  218. Gmail per day allows
  219. 2500000000 = 2.5 GB downloaded from Gmail as host2
  220. 500000000 = 500 MB uploaded to Gmail as host1.
  221. --maxlinelength int : skip messages with a line length longer than int bytes.
  222. RFC 2822 says it must be no more than 1000 bytes.
  223. --useheader str : Use this header to compare messages on both sides.
  224. Ex: Message-ID or Subject or Date.
  225. --useheader str and this one, etc.
  226. --subscribed : Transfers subscribed folders.
  227. --subscribe : Subscribe to the folders transferred on the
  228. host2 that are subscribed on host1. On by default.
  229. --subscribeall : Subscribe to the folders transferred on the
  230. host2 even if they are not subscribed on host1.
  231. --nofoldersizes : Do not calculate the size of each folder in bytes
  232. and message counts. Default is to calculate them.
  233. --nofoldersizesatend: Do not calculate the size of each folder in bytes
  234. and message counts at the end. Default is on.
  235. --justfoldersizes : Exit after having printed the folder sizes.
  236. --syncacls : Synchronises acls (Access Control Lists).
  237. --nosyncacls : Does not synchronize acls. This is the default.
  238. Acls in IMAP are not standardized, be careful.
  239. --usecache : Use cache to speedup.
  240. --nousecache : Do not use cache. Caveat: --useuid --nousecache creates
  241. duplicates on multiple runs.
  242. --useuid : Use uid instead of header as a criterium to recognize
  243. messages. Option --usecache is then implied unless
  244. --nousecache is used.
  245. --debug : Debug mode.
  246. --debugfolders : Debug mode for the folders part only.
  247. --debugcontent : Debug content of the messages transfered. Huge ouput.
  248. --debugflags : Debug mode for flags.
  249. --debugimap1 : IMAP debug mode for host1. Very verbose.
  250. --debugimap2 : IMAP debug mode for host2. Very verbose.
  251. --debugimap : IMAP debug mode for host1 and host2.
  252. --debugmemory : Debug mode showing memory consumption after each copy.
  253. --errorsmax int : Exit when int number of errors is reached. Default is 50.
  254. --tests : Run local non-regression tests. Exit code 0 means all ok.
  255. --testslive : Run a live test with test1.kopano.com imap server.
  256. Useful to check the basics. Needs internet connexion.
  257. --version : Print only software version.
  258. --noid : Do not send/receive ID command to imap servers.
  259. --justconnect : Just connect to both servers and print useful
  260. information. Need only --host1 and --host2 options.
  261. --justlogin : Just login to both host1 and host2 with users
  262. credentials, then exit.
  263. --justfolders : Do only things about folders (ignore messages).
  264. --help : print this help.
  265. Example:
  266. To synchronize the source imap account
  267. "test1" on server "test1.kopano.com" with password "secret1"
  268. to the destination imap account
  269. "test2" on server "test2.kopano.com" with password "secret2"
  270. do:
  271. kopano-migration-imap \
  272. --host1 test1.kopano.com --user1 test1 --password1 secret1 \
  273. --host2 test2.kopano.com --user2 test2 --password2 secret2
  274. =cut
  275. # comment
  276. =pod
  277. =head1 DESCRIPTION
  278. Imapsync command is a tool allowing incremental and
  279. recursive imap transfers from one mailbox to another.
  280. By default all folders are transferred, recursively, all
  281. possible flags (\Seen \Answered \Flagged etc.) are synced too.
  282. We sometimes need to transfer mailboxes from one imap server to
  283. another. This is called migration.
  284. Imapsync reduces the amount
  285. of data transferred by not transferring a given message
  286. if it resides already on both sides. Same specific headers
  287. and the transfer is done only once; taken into account are by default
  288. Message-Id and Received header lines.
  289. All flags are
  290. preserved, unread will stay unread, read will stay read,
  291. deleted will stay deleted. You can stop the transfer at any
  292. time and restart it later, kopano-migration-imap works well with bad
  293. connections and interruptions.
  294. You can decide to delete the messages from the source mailbox
  295. after a successful transfer, it can be a good feature when migrating
  296. live mailboxes since messages will be only on one side.
  297. In that case, use the --delete option. Option --delete implies
  298. also option --expunge so all messages marked deleted on host1
  299. will be really deleted.
  300. (you can use --noexpunge to avoid this but I don't see any
  301. good real world scenario for the combination --delete --noexpunge).
  302. A different scenario is synchronizing a mailbox B from another mailbox A
  303. in case you just want to keep a "live" copy of A in B.
  304. In that case --delete2 has to be used, it deletes messages in host2
  305. folder B that are not in host1 folder A. If you also need to destroy
  306. host2 folders that are not in host1 then use --delete2folders (see also
  307. --delete2foldersonly and --delete2foldersbutnot).
  308. kopano-migration-imap is not adequate for maintaining two active imap accounts
  309. in synchronization when the user plays independently on both sides.
  310. =head1 OPTIONS
  311. To get a description of each option just invoke:
  312. kopano-migration-imap
  313. or read the previous section named USAGE,
  314. =head1 EXAMPLE
  315. While working on kopano-migration-imap parameters please run kopano-migration-imap in
  316. dry mode (no modification induced) with the --dry
  317. option. Nothing bad can be done this way.
  318. To synchronize the imap account "buddy" (with password "secret1")
  319. on host "src.kopano.com" to the imap account "max" (with password "secret2")
  320. on host "dst.kopano.com":
  321. kopano-migration-imap --host1 src.kopano.com --user1 buddy --password1 secret1 \
  322. --host2 dst.kopano.com --user2 max --password2 secret2
  323. Then you will have max's mailbox updated from buddy's
  324. mailbox.
  325. =head1 SECURITY
  326. You can use --passfile1 instead of --password1 to give the
  327. password since it is safer. With --password1 option any user
  328. on your host can see the password by using the 'ps auxwwww'
  329. command. Using a variable (like $PASSWORD1) is also
  330. dangerous because of the 'ps auxwwwwe' command. So, saving
  331. the password in a well protected file (600 or rw-------) is
  332. the best solution.
  333. imasync is not totally protected against sniffers on the
  334. network since passwords may be transferred in plain text
  335. if CRAM-MD5 is not supported by your imap servers. Use
  336. --ssl1 (or --tls1) and --ssl2 (or --tls2) to enable
  337. encryption on host1 and host2.
  338. You may authenticate as one user (typically an admin user),
  339. but be authorized as someone else, which means you don't
  340. need to know every user's personal password. Specify
  341. --authuser1 "adminuser" to enable this on host1. In this
  342. case, --authmech1 PLAIN will be used by default since it
  343. is the only way to go for now. So don't use --authmech1 SOMETHING
  344. with --authuser1 "adminuser", it will not work.
  345. Same behavior with the --authuser2 option.
  346. Authenticate with an admin account must be supported by your
  347. imap server to work with kopano-migration-imap.
  348. When working on Sun/iPlanet/Netscape IMAP servers you must use
  349. --proxyauth1 to enable administrative user to masquerade as another user.
  350. Can also be used on destination server with --proxyauth2
  351. You can authenticate with OAUTH when transfering from Google Apps.
  352. The consumer key will be the domain part of the --user, and the
  353. --password will be used as the consumer secret. It does not work
  354. with Google Apps free edition.
  355. =head1 EXIT STATUS
  356. kopano-migration-imap will exit with a 0 status (return code) if everything went good.
  357. Otherwise, it exits with a non-zero status.
  358. So if you have an unreliable internet connection, you can use this loop
  359. in a Bourne shell:
  360. while ! kopano-migration-imap ...; do
  361. echo kopano-migration-imap not complete
  362. done
  363. =head1 LICENSE AND COPYRIGHT
  364. kopano-migration-imap is free, open, public but not always gratis software
  365. covered by the NOLIMIT Public License.
  366. The license text is as follows:
  367. "No limit to do anything with this work and this license."
  368. =head1 HUGE MIGRATION
  369. Pay special attention to options
  370. --subscribed
  371. --subscribe
  372. --delete
  373. --delete2
  374. --delete2folders
  375. --maxage
  376. --minage
  377. --maxsize
  378. --useuid
  379. --usecache
  380. If you have many mailboxes to migrate think about a little
  381. shell program. Write a file called file.txt (for example)
  382. containing users and passwords.
  383. The separator used in this example is ';'
  384. The file.txt file contains:
  385. user001_1;password001_1;user001_2;password001_2
  386. user002_1;password002_1;user002_2;password002_2
  387. user003_1;password003_1;user003_2;password003_2
  388. user004_1;password004_1;user004_2;password004_2
  389. user005_1;password005_1;user005_2;password005_2
  390. ...
  391. On Unix the shell program can be:
  392. { while IFS=';' read u1 p1 u2 p2; do
  393. kopano-migration-imap --host1 src.kopano.com --user1 "$u1" --password1 "$p1" \
  394. --host2 dst.kopano.com --user2 "$u2" --password2 "$p2" ...
  395. done ; } < file.txt
  396. The ... have to be replaced by nothing or any kopano-migration-imap option.
  397. =cut
  398. # pragmas
  399. use strict ;
  400. use warnings ;
  401. ++$| ;
  402. use Carp ;
  403. use Data::Dumper ;
  404. use Digest::HMAC_SHA1 qw( hmac_sha1 ) ;
  405. use Digest::MD5 qw( md5 md5_hex md5_base64 ) ;
  406. use English qw( -no_match_vars ) ;
  407. use Errno qw(EAGAIN EPIPE ECONNRESET) ;
  408. use Fcntl ;
  409. use File::Basename ;
  410. use File::Copy::Recursive ;
  411. use File::Glob qw( :glob ) ;
  412. use File::Path qw( mkpath rmtree ) ;
  413. use File::Spec ;
  414. use File::stat ;
  415. #use Imapsync::Getopt::Long ;
  416. use IO::File ;
  417. use IO::Socket qw(:crlf SOL_SOCKET SO_KEEPALIVE) ;
  418. #use IO::Socket::SSL ;
  419. use IO::Tee ;
  420. use IPC::Open3 'open3' ;
  421. use Mail::IMAPClient 3.30 ;
  422. use MIME::Base64 ;
  423. use POSIX qw(uname SIGALRM) ;
  424. use Term::ReadKey ;
  425. use Test::More ;
  426. use Time::HiRes qw( time sleep ) ;
  427. use Time::Local ;
  428. use Unicode::String ;
  429. use Cwd ;
  430. use Readonly ;
  431. # constants
  432. # Let us do like sysexits.h
  433. # /usr/include/sysexits.h
  434. Readonly my $EX_OK => 0 ; #/* successful termination */
  435. Readonly my $EX_USAGE => 64 ; #/* command line usage error */
  436. #Readonly my $EX_DATAERR => 65 ; #/* data format error */
  437. #Readonly my $EX_NOINPUT => 66 ; #/* cannot open input */
  438. #Readonly my $EX_NOUSER => 67 ; #/* addressee unknown */
  439. #Readonly my $EX_NOHOST => 68 ; #/* host name unknown */
  440. #Readonly my $EX_UNAVAILABLE => 69 ; #/* service unavailable */
  441. Readonly my $EX_SOFTWARE => 70 ; #/* internal software error */
  442. #Readonly my $EX_OSERR => 71 ; #/* system error (e.g., can't fork) */
  443. #Readonly my $EX_OSFILE => 72 ; #/* critical OS file missing */
  444. #Readonly my $EX_CANTCREAT => 73 ; #/* can't create (user) output file */
  445. #Readonly my $EX_IOERR => 74 ; #/* input/output error */
  446. #Readonly my $EX_TEMPFAIL => 75 ; #/* temp failure; user is invited to retry */
  447. #Readonly my $EX_PROTOCOL => 76 ; #/* remote error in protocol */
  448. #Readonly my $EX_NOPERM => 77 ; #/* permission denied */
  449. #Readonly my $EX_CONFIG => 78 ; #/* configuration error */
  450. # Mine
  451. Readonly my $EXIT_BY_SIGNAL => 6 ;
  452. Readonly my $EXIT_PID_FILE_ALREADY_EXIST => 8 ;
  453. Readonly my $EXIT_WITH_ERRORS => 111 ;
  454. Readonly my $EXIT_WITH_ERRORS_MAX => 112 ;
  455. Readonly my $EXIT_UNKNOWN => 126 ;
  456. Readonly my $ERRORS_MAX => 50 ; # exit after 50 errors.
  457. Readonly my $INTERVAL_TO_EXIT => 2 ; # interval max to exit instead of reconnect
  458. Readonly my $SPLIT => 100 ; # By default, 100 at a time, not more.
  459. Readonly my $SPLIT_FACTOR => 10 ; # init_imap() calls Maxcommandlength( $SPLIT_FACTOR * $split )
  460. # which means default Maxcommandlength is 10*100 = 1000 characters ;
  461. Readonly my $IMAP_PORT => 143 ; # Well know port for IMAP
  462. Readonly my $IMAP_SSL_PORT => 993 ; # Well know port for IMAP over SSL
  463. Readonly my $LAST => -1 ;
  464. Readonly my $MINUS_ONE => -1 ;
  465. Readonly my $RELEASE_NUMBER_EXAMPLE_1 => '1.351' ;
  466. Readonly my $RELEASE_NUMBER_EXAMPLE_2 => 42.4242 ;
  467. Readonly my $DEFAULT_TIMEOUT => 120 ;
  468. Readonly my $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND => 3 ;
  469. Readonly my $DEFAULT_UIDNEXT => 999999 ;
  470. Readonly my $DEFAULT_BUFFER_SIZE => 4096 ;
  471. Readonly my $DEFAULT_EXPIRATION_TIME_OAUTH2_PK12 => 3600 ;
  472. Readonly my $PERMISSION_FILTER => 7777 ;
  473. Readonly my $KIBI => 1024 ;
  474. Readonly my $NUMBER_10 => 10 ;
  475. Readonly my $NUMBER_42 => 42 ;
  476. Readonly my $NUMBER_100 => 100 ;
  477. Readonly my $NUMBER_200 => 200 ;
  478. Readonly my $NUMBER_300 => 300 ;
  479. Readonly my $NUMBER_20_000 => 20_000 ;
  480. Readonly my $QUOTA_PERCENT_LIMIT => 90 ;
  481. Readonly my $NUMBER_104857600 => 104857600 ;
  482. Readonly my $SIZE_MAX_STR => 64 ;
  483. Readonly my $NB_SECONDS_IN_A_DAY => 86400 ;
  484. Readonly my $STD_CHAR_PER_LINE => 80 ;
  485. Readonly my $TRUE => 1 ;
  486. Readonly my $FALSE => 0 ;
  487. Readonly my $LAST_RESSORT_SEPARATOR => q{/} ;
  488. # global variables
  489. my(
  490. $sync,
  491. $rcs,
  492. $debug, $debugimap, $debugimap1, $debugimap2, $debugcontent, $debugflags,
  493. $debuglist, $debugdev, $debugmaxlinelength, @debugbasket, $debugcgi,
  494. $host1, $host2, $port1, $port2,
  495. $user1, $user2, $domain1, $domain2,
  496. $password1, $password2, $passfile1, $passfile2,
  497. @folder, @include, @exclude, @folderrec,
  498. @folderfirst, @folderlast,
  499. $prefix1, $prefix2,
  500. $subfolder2,
  501. @regextrans2, @regexmess, @regexflag, @skipmess, @pipemess, $pipemesscheck,
  502. $flagscase, $filterflags, $syncflagsaftercopy,
  503. $sep1, $sep2,
  504. $syncinternaldates,
  505. $idatefromheader,
  506. $syncacls,
  507. $fastio1, $fastio2,
  508. $maxsize, $minsize, $maxage, $minage,
  509. $exitwhenover,
  510. $search, $search1, $search2,
  511. $skipheader, @useheader,
  512. $skipsize, $allowsizemismatch, $foldersizes, $foldersizesatend, $buffersize,
  513. $delete, $delete2, $delete2duplicates,
  514. $expunge, $expunge1, $expunge2, $uidexpunge2, $dry,
  515. $justfoldersizes,
  516. $authmd5, $authmd51, $authmd52,
  517. $subscribed, $subscribe, $subscribeall,
  518. $version, $help,
  519. $justconnect, $justfolders, $justbanner,
  520. $fast,
  521. $total_bytes_transferred,
  522. $total_bytes_skipped,
  523. $total_bytes_error,
  524. $nb_msg_transferred,
  525. $nb_msg_skipped,
  526. $nb_msg_skipped_dry_mode,
  527. $h1_nb_msg_duplicate,
  528. $h2_nb_msg_duplicate,
  529. $h1_nb_msg_noheader,
  530. $h2_nb_msg_noheader,
  531. $h1_total_bytes_duplicate,
  532. $h2_total_bytes_duplicate,
  533. $h1_nb_msg_deleted,
  534. $h2_nb_msg_deleted,
  535. $h1_bytes_processed,
  536. $h1_nb_msg_processed,
  537. $h1_nb_msg_start, $h1_bytes_start,
  538. $h2_nb_msg_start, $h2_bytes_start,
  539. $h1_nb_msg_end, $h1_bytes_end,
  540. $h2_nb_msg_end, $h2_bytes_end,
  541. $timeout,
  542. $timestart_int, $timeend,
  543. $timebefore,
  544. $ssl1, $ssl2,
  545. $ssl1_ssl_version, $ssl2_ssl_version,
  546. $tls1, $tls2,
  547. $uid1, $uid2,
  548. $authuser1, $authuser2,
  549. $proxyauth1, $proxyauth2,
  550. $authmech1, $authmech2,
  551. $split1, $split2,
  552. $reconnectretry1, $reconnectretry2,
  553. $tests, $test_builder, $testsdebug, $testslive,
  554. $justlogin,
  555. $tmpdir,
  556. $max_msg_size_in_bytes,
  557. $modulesversion,
  558. $delete2folders, $delete2foldersonly, $delete2foldersbutnot,
  559. $usecache, $debugcache, $cacheaftercopy,
  560. $wholeheaderifneeded, %h1_msgs_copy_by_uid, $useuid, $h2_uidguess,
  561. $addheader,
  562. %h1, %h2,
  563. $checkselectable, $checkmessageexists,
  564. $expungeaftereach,
  565. $abletosearch,
  566. $showpasswords,
  567. $fixslash2,
  568. $messageidnodomain,
  569. $fixInboxINBOX,
  570. $maxlinelength, $maxlinelengthcmd,
  571. $minmaxlinelength,
  572. $uidnext_default,
  573. $fixcolonbug,
  574. $create_folder_old,
  575. $maxmessagespersecond,
  576. $maxbytespersecond,
  577. $skipcrossduplicates, $debugcrossduplicates,
  578. $disarmreadreceipts,
  579. $mixfolders, $skipemptyfolders,
  580. $fetch_hash_set,
  581. );
  582. # main program
  583. # global variables initialisation
  584. $rcs = q{$Id$};
  585. $total_bytes_transferred = 0;
  586. $total_bytes_skipped = 0;
  587. $total_bytes_error = 0;
  588. $nb_msg_transferred = 0;
  589. $nb_msg_skipped = $nb_msg_skipped_dry_mode = 0;
  590. $h1_nb_msg_deleted = $h2_nb_msg_deleted = 0;
  591. $h1_nb_msg_duplicate = $h2_nb_msg_duplicate = 0;
  592. $h1_nb_msg_noheader = $h2_nb_msg_noheader = 0;
  593. $h1_total_bytes_duplicate = $h2_total_bytes_duplicate = 0;
  594. $h1_nb_msg_start = $h1_bytes_start = 0 ;
  595. $h2_nb_msg_start = $h2_bytes_start = 0 ;
  596. $h1_nb_msg_processed = $h1_bytes_processed = 0 ;
  597. #$h1_nb_msg_end = $h1_bytes_end = 0 ;
  598. #$h2_nb_msg_end = $h2_bytes_end = 0 ;
  599. $sync->{nb_errors} = 0;
  600. $max_msg_size_in_bytes = 0;
  601. my %month_abrev = (
  602. Jan => '00',
  603. Feb => '01',
  604. Mar => '02',
  605. Apr => '03',
  606. May => '04',
  607. Jun => '05',
  608. Jul => '06',
  609. Aug => '07',
  610. Sep => '08',
  611. Oct => '09',
  612. Nov => '10',
  613. Dec => '11',
  614. );
  615. # @ARGV will be eat by get_options()
  616. my @argv_copy = @ARGV;
  617. my $cgi_dir = '/var/tmp/kopano-migration-imap_cgi' ;
  618. # Under CGI environment
  619. if ( $ENV{SERVER_SOFTWARE} ) {
  620. myprint( "\n" ) ;
  621. myprint( "<pre>\n" ) ;
  622. -d $cgi_dir or mkpath $cgi_dir or die "Can not create $cgi_dir: $!\n" ;
  623. chdir $cgi_dir or die "Can not cd to $cgi_dir: $!\n" ;
  624. }
  625. get_options( ) ;
  626. unsetunsafe( ) if ( $ENV{SERVER_SOFTWARE} ) ;
  627. # Under CGI environment
  628. if ( $ENV{SERVER_SOFTWARE} ) {
  629. myprint( 'Current directory is ' . getcwd( ) . "\n" ) ;
  630. myprint( 'Real user id is ' . getpwuid_any_os( $REAL_USER_ID ) . " (uid $REAL_USER_ID)\n" ) ;
  631. myprint( 'Effective user id is ' . getpwuid_any_os( $EFFECTIVE_USER_ID ). " (euid $EFFECTIVE_USER_ID)\n" ) ;
  632. }
  633. local $SIG{ INT } = sub {
  634. my $signame = shift ;
  635. catch_reconnect( $sync, $signame ) ;
  636. } ;
  637. local $SIG{ QUIT } = local $SIG{ TERM } = sub {
  638. my $signame = shift ;
  639. catch_exit( $sync, $signame ) ;
  640. } ;
  641. $sync->{timestart} = $BASETIME ; # Never too let reading books and perlvar
  642. $sync->{log} = defined $sync->{log} ? $sync->{log} : 1 ;
  643. $sync->{errorsdump} = defined $sync->{errorsdump} ? $sync->{errorsdump} : 1 ;
  644. $sync->{errorsmax} = defined $sync->{errorsmax} ? $sync->{errorsmax} : $ERRORS_MAX ;
  645. $sync->{user2} = $user2 ;
  646. if ( $sync->{log} ) {
  647. setlogfile( $sync ) ;
  648. teelaunch( $sync ) ;
  649. }
  650. $timestart_int = int( $sync->{timestart} ) ;
  651. $timebefore = $sync->{timestart} ;
  652. my $timestart_str = localtime( $sync->{timestart} ) ;
  653. myprint( "Transfer started at $timestart_str\n" ) ;
  654. myprint( "PID is $PROCESS_ID\n" ) ;
  655. myprint( "Log file is $sync->{logfile} ( to change it, use --logfile path ; or use --nolog to turn off logging )\n" ) if ( $sync->{log} ) ;
  656. $modulesversion = defined $modulesversion ? $modulesversion : 1 ;
  657. # default values
  658. $sync->{pidfile} = defined $sync->{pidfile} ? $sync->{pidfile} : $tmpdir . '/kopano-migration-imap.pid' ;
  659. $sync->{pidfilelocking} = defined $sync->{pidfilelocking} ? $sync->{pidfilelocking} : 0 ;
  660. $wholeheaderifneeded = defined $wholeheaderifneeded ? $wholeheaderifneeded : 1;
  661. # turn on RFC standard flags correction like \SEEN -> \Seen
  662. $flagscase = defined $flagscase ? $flagscase : 1 ;
  663. # Use PERMANENTFLAGS if available
  664. $filterflags = defined $filterflags ? $filterflags : 1 ;
  665. # sync flags just after an APPEND, some servers ignore the flags given in the APPEND
  666. # like MailEnable IMAP server.
  667. # Off by default since it takes time.
  668. $syncflagsaftercopy = defined $syncflagsaftercopy ? $syncflagsaftercopy : 0 ;
  669. # Activate --usecache if --useuid is set and no --nousecache
  670. $usecache = 1 if ( $useuid and ( ! defined $usecache ) ) ;
  671. $cacheaftercopy = 1 if ( $usecache and ( ! defined $cacheaftercopy ) ) ;
  672. $checkselectable = defined $checkselectable ? $checkselectable : 1 ;
  673. $checkmessageexists = defined $checkmessageexists ? $checkmessageexists : 0 ;
  674. $expungeaftereach = defined $expungeaftereach ? $expungeaftereach : 1 ;
  675. $abletosearch = defined $abletosearch ? $abletosearch : 1 ;
  676. $checkmessageexists = 0 if ( not $abletosearch ) ;
  677. $showpasswords = defined $showpasswords ? $showpasswords : 0 ;
  678. $fixslash2 = defined $fixslash2 ? $fixslash2 : 1 ;
  679. $fixInboxINBOX = defined $fixInboxINBOX ? $fixInboxINBOX : 1 ;
  680. $create_folder_old = defined $create_folder_old ? $create_folder_old : 0 ;
  681. $mixfolders = defined $mixfolders ? $mixfolders : 1 ;
  682. $sync->{automap} = defined $sync->{automap} ? $sync->{automap} : 0 ;
  683. $delete2duplicates = 1 if ( $delete2 and ( ! defined $delete2duplicates ) ) ;
  684. $maxmessagespersecond = defined $maxmessagespersecond ? $maxmessagespersecond : 0 ;
  685. $maxbytespersecond = defined $maxbytespersecond ? $maxbytespersecond : 0 ;
  686. myprint( banner_imapsync( @argv_copy ) ) ;
  687. myprint( "Temp directory is $tmpdir ( to change it use --tmpdir dirpath )\n") ;
  688. is_valid_directory( $tmpdir ) || croak "Error creating tmpdir $tmpdir : $!" ;
  689. if ( $sync->{pidfile} ) {
  690. write_pidfile( $sync->{pidfile}, $sync->{pidfilelocking} ) ;
  691. }
  692. $fixcolonbug = defined $fixcolonbug ? $fixcolonbug : 1 ;
  693. if ( $usecache and $fixcolonbug ) { tmpdir_fix_colon_bug( ) } ;
  694. $modulesversion and myprint( "Modules version list:\n", modulesversion(), "( use --no-modulesversion to turn off printing this Perl modules list )\n" ) ;
  695. my $DEFAULT_SSL_VERIFY ;
  696. my %SSL_VERIFY_STR ;
  697. if ( $ssl1 or $ssl2 or $tls1 or $tls2) {
  698. Readonly $DEFAULT_SSL_VERIFY => IO::Socket::SSL::SSL_VERIFY_NONE( ) ;
  699. Readonly %SSL_VERIFY_STR => (
  700. IO::Socket::SSL::SSL_VERIFY_NONE( ) => 'SSL_VERIFY_NONE' ,
  701. IO::Socket::SSL::SSL_VERIFY_PEER( ) => 'SSL_VERIFY_PEER' ,
  702. ) ;
  703. $IO::Socket::SSL::DEBUG = $sync->{debugssl} || 1 ;
  704. 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" ) ;
  705. }
  706. if ( $ssl1 ) {
  707. myprint( 'Host1: SSL default mode is like --sslargs1 SSL_verify_mode=' . $DEFAULT_SSL_VERIFY . " meaning $SSL_VERIFY_STR{$DEFAULT_SSL_VERIFY} on host1 (do not check the certificate server)\n" ) ;
  708. myprint( 'Host1: Use --sslargs1 SSL_verify_mode=' . IO::Socket::SSL::SSL_VERIFY_PEER( ) . " for $SSL_VERIFY_STR{IO::Socket::SSL::SSL_VERIFY_PEER( )} on host1\n" ) ;
  709. }
  710. if ( $ssl2 ) {
  711. myprint( 'Host2: SSL default mode is like --sslargs2 SSL_verify_mode=' . $DEFAULT_SSL_VERIFY . " meaning $SSL_VERIFY_STR{$DEFAULT_SSL_VERIFY} on host2 (do not check the certificate server)\n" ) ;
  712. myprint( 'Host2: Use --sslargs2 SSL_verify_mode=' . IO::Socket::SSL::SSL_VERIFY_PEER( ) . " for $SSL_VERIFY_STR{IO::Socket::SSL::SSL_VERIFY_PEER( )} on host2\n" ) ;
  713. }
  714. check_lib_version( ) or
  715. croak "kopano-migration-imap needs perl lib Mail::IMAPClient release 3.30 or superior.\n";
  716. exit_clean( $sync, $EX_OK ) if ( $justbanner ) ;
  717. $split1 ||= $SPLIT ;
  718. $split2 ||= $SPLIT ;
  719. $host1 || missing_option( '--host1' ) ;
  720. $port1 ||= ( $ssl1 ) ? $IMAP_SSL_PORT : $IMAP_PORT ;
  721. $host2 || missing_option( '--host2' ) ;
  722. $port2 ||= ( $ssl2 ) ? $IMAP_SSL_PORT : $IMAP_PORT ;
  723. $debugimap1 = $debugimap2 = 1 if ( $debugimap ) ;
  724. $debug = 1 if ( $debugimap1 or $debugimap2 ) ;
  725. # By default, don't take size to compare
  726. $skipsize = (defined $skipsize) ? $skipsize : 1;
  727. $uid1 = defined $uid1 ? $uid1 : 1;
  728. $uid2 = defined $uid2 ? $uid2 : 1;
  729. $subscribe = defined $subscribe ? $subscribe : 1;
  730. # Allow size mismatch by default
  731. $allowsizemismatch = defined $allowsizemismatch ? $allowsizemismatch : 1;
  732. $delete2folders = 1
  733. if ( defined $delete2foldersbutnot or defined $delete2foldersonly ) ;
  734. if ( $justconnect ) {
  735. justconnect( ) ;
  736. exit_clean( $sync, $EX_OK ) ;
  737. }
  738. $user1 || missing_option( '--user1' ) ;
  739. $user2 || missing_option( '--user2' ) ;
  740. $syncinternaldates = defined $syncinternaldates ? $syncinternaldates : 1;
  741. # Turn on expunge if there is not explicit option --noexpunge and option
  742. # --delete is given.
  743. # Done because --delete --noexpunge is very dangerous on the second run:
  744. # the Deleted flag is then synced to all previously transfered messages.
  745. # So --delete implies --expunge is a better usability default behaviour.
  746. if ( $delete ) {
  747. if ( ! defined $expunge ) {
  748. myprint( "Info: turning on --expunge1 because --delete --noexpunge1 is very dangerous on the second run.\n" ) ;
  749. $expunge = 1 ;
  750. }
  751. myprint( "Info: if expunging after each message slows down too much the sync then use --noexpungeaftereach to speed up\n" ) ;
  752. }
  753. if ( $uidexpunge2 and not Mail::IMAPClient->can( 'uidexpunge' ) ) {
  754. myprint( "Failure: uidexpunge not supported (IMAPClient release < 3.17), use --expunge2 instead\n" ) ;
  755. exit_clean( $sync, $EX_SOFTWARE ) ;
  756. }
  757. if ( ( $delete2 or $delete2duplicates ) and not defined $uidexpunge2 ) {
  758. if ( Mail::IMAPClient->can( 'uidexpunge' ) ) {
  759. myprint( "Info: will act as --uidexpunge2\n" ) ;
  760. $uidexpunge2 = 1 ;
  761. }elsif ( not defined $expunge2 ) {
  762. myprint( "Info: will act as --expunge2 (no uidexpunge support)\n" ) ;
  763. $expunge2 = 1 ;
  764. }
  765. }
  766. if ( $delete and $delete2 ) {
  767. myprint( "Warning: using --delete and --delete2 together is almost always a bad idea, exiting kopano-migration-imap\n" ) ;
  768. exit_clean( $sync, $EX_USAGE ) ;
  769. }
  770. if ( $idatefromheader ) {
  771. myprint( 'Turned ON idatefromheader, ',
  772. "will set the internal dates on host2 from the 'Date:' header line.\n" ) ;
  773. $syncinternaldates = 0 ;
  774. }
  775. if ( $syncinternaldates ) {
  776. myprint( 'Info: turned ON syncinternaldates, ',
  777. "will set the internal dates (arrival dates) on host2 same as host1.\n" ) ;
  778. }else{
  779. myprint( "Info: turned OFF syncinternaldates\n" ) ;
  780. }
  781. if ( defined $authmd5 and $authmd5 ) {
  782. $authmd51 = 1 ;
  783. $authmd52 = 1 ;
  784. }
  785. if ( defined $authmd51 and $authmd51 ) {
  786. $authmech1 ||= 'CRAM-MD5';
  787. }
  788. else{
  789. $authmech1 ||= $authuser1 ? 'PLAIN' : 'LOGIN';
  790. }
  791. if ( defined $authmd52 and $authmd52 ) {
  792. $authmech2 ||= 'CRAM-MD5';
  793. }
  794. else{
  795. $authmech2 ||= $authuser2 ? 'PLAIN' : 'LOGIN';
  796. }
  797. $authmech1 = uc $authmech1;
  798. $authmech2 = uc $authmech2;
  799. if (defined $proxyauth1 && !$authuser1) {
  800. missing_option( 'With --proxyauth1, --authuser1' ) ;
  801. }
  802. if (defined $proxyauth2 && !$authuser2) {
  803. missing_option( 'With --proxyauth2, --authuser2' ) ;
  804. }
  805. $authuser1 ||= $user1;
  806. $authuser2 ||= $user2;
  807. myprint( "Host1: will try to use $authmech1 authentication on host1\n") ;
  808. myprint( "Host2: will try to use $authmech2 authentication on host2\n") ;
  809. $timeout = defined $timeout ? $timeout : $DEFAULT_TIMEOUT ;
  810. $sync->{h1}->{timeout} = defined $sync->{h1}->{timeout} ? $sync->{h1}->{timeout} : $timeout ;
  811. myprint( "Host1: imap connexion timeout is $sync->{h1}->{timeout} seconds\n") ;
  812. $sync->{h2}->{timeout} = defined $sync->{h2}->{timeout} ? $sync->{h2}->{timeout} : $timeout ;
  813. myprint( "Host2: imap connexion timeout is $sync->{h2}->{timeout} seconds\n" ) ;
  814. $syncacls = defined $syncacls ? $syncacls : 0 ;
  815. # No folders sizes if --justfolders, unless really wanted.
  816. if ( $justfolders and not defined $foldersizes ) { $foldersizes = 0 ; }
  817. $foldersizes = ( defined $foldersizes ) ? $foldersizes : 1 ;
  818. $foldersizesatend = ( defined $foldersizesatend ) ? $foldersizesatend : $foldersizes ;
  819. $fastio1 = defined $fastio1 ? $fastio1 : 0 ;
  820. $fastio2 = defined $fastio2 ? $fastio2 : 0 ;
  821. $reconnectretry1 = defined $reconnectretry1 ? $reconnectretry1 : $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ;
  822. $reconnectretry2 = defined $reconnectretry2 ? $reconnectretry2 : $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ;
  823. # Since select_msgs() returns no messages when uidnext does not return something
  824. # then $uidnext_default is never used. So I have to remove it.
  825. $uidnext_default = $DEFAULT_UIDNEXT ;
  826. @useheader = qw( Message-Id Received ) unless ( @useheader ) ;
  827. my %useheader ;
  828. # Make a hash %useheader of each --useheader 'key' in uppercase
  829. for ( @useheader ) { $useheader{ uc $_ } = undef } ;
  830. #myprint( Data::Dumper->Dump( [ \%useheader ] ) ) ;
  831. #exit ;
  832. myprint( "Host1: IMAP server [$host1] port [$port1] user [$user1]\n" ) ;
  833. myprint( "Host2: IMAP server [$host2] port [$port2] user [$user2]\n" ) ;
  834. $password1 || $passfile1 || 'PREAUTH' eq $authmech1 || 'EXTERNAL' eq $authmech1 || do {
  835. myprint( << 'FIN_PASSFILE' ) ;
  836. If you are afraid of giving password on the command line arguments, you can put the
  837. password of user1 in a file named file1 and use "--passfile1 file1" instead of typing it.
  838. Then give this file restrictive permissions with the command "chmod 600 file1".
  839. FIN_PASSFILE
  840. $password1 = ask_for_password( $authuser1 || $user1, $host1 ) ;
  841. } ;
  842. $password1 = ( defined $passfile1 ) ? firstline ( $passfile1 ) : $password1 ;
  843. $password2 || $passfile2 || 'PREAUTH' eq $authmech2 || 'EXTERNAL' eq $authmech2 || do {
  844. myprint( << 'FIN_PASSFILE' ) ;
  845. If you are afraid of giving password on the command line arguments, you can put the
  846. password of user2 in a file named file2 and use "--passfile2 file2" instead of typing it.
  847. Then give this file restrictive permissions with the command "chmod 600 file2".
  848. FIN_PASSFILE
  849. $password2 = ask_for_password( $authuser2 || $user2, $host2 ) ;
  850. } ;
  851. $password2 = ( defined $passfile2 ) ? firstline ( $passfile2 ) : $password2 ;
  852. # need clean up => write methods dry() and dry_message()
  853. $sync->{dry} = $dry ;
  854. my $dry_message = q{} ;
  855. if( $sync->{dry} ) {
  856. $dry_message = "\t(not really since --dry mode)" ;
  857. }
  858. $sync->{dry_message} = $dry_message ;
  859. $search1 ||= $search if ( $search ) ;
  860. $search2 ||= $search if ( $search ) ;
  861. if ( $disarmreadreceipts ) {
  862. push @regexmess, q{s{\A((?:[^\n]+\r\n)+|)(^Disposition-Notification-To:[^\n]*\n)(\r?\n|.*\n\r?\n)}{$1X-$2$3}ims} ;
  863. }
  864. $pipemesscheck = ( defined $pipemesscheck ) ? $pipemesscheck : 1 ;
  865. if ( @pipemess and $pipemesscheck ) {
  866. myprint( 'Checking each --pipemess command, '
  867. . join( q{, }, @pipemess )
  868. . ", with a space string. ( Can avoid this check with --nopipemesscheck )\n" ) ;
  869. my $string = pipemess( q{ }, @pipemess ) ;
  870. # string undef means something was bad.
  871. if ( not ( defined $string ) ) {
  872. die_clean( "Error: one of --pipemess command is bad, check it\n" ) ;
  873. }
  874. myprint( "Ok with each --pipemess @pipemess\n" ) ;
  875. }
  876. if ( $maxlinelengthcmd ) {
  877. myprint( "Checking --maxlinelengthcmd command, $maxlinelengthcmd, with a space string.\n" ) ;
  878. my $string = pipemess( q{ }, $maxlinelengthcmd ) ;
  879. # string undef means something was bad.
  880. if ( not ( defined $string ) ) {
  881. die_clean( "Error: --maxlinelengthcmd command is bad, check it\n" ) ;
  882. }
  883. myprint( "Ok with --maxlinelengthcmd $maxlinelengthcmd\n" ) ;
  884. }
  885. if ( @regexmess ) {
  886. my $string = regexmess( q{ } ) ;
  887. myprint( "Checking each --regexmess command with a space string.\n" ) ;
  888. # string undef means one of the eval regex was bad.
  889. if ( not ( defined $string ) ) {
  890. die_clean( 'Error: one of --regexmess option is bad, check it' ) ;
  891. }
  892. myprint( "Ok with each --regexmess\n" ) ;
  893. }
  894. if ( @skipmess ) {
  895. myprint( "Checking each --skipmess command with a space string.\n" ) ;
  896. my $match = skipmess( q{ } ) ;
  897. # match undef means one of the eval regex was bad.
  898. if ( not ( defined $match ) ) {
  899. die_clean( 'Error: one of --skipmess option is bad, check it' ) ;
  900. }
  901. myprint( "Ok with each --skipmess\n" ) ;
  902. }
  903. if ( @regexflag ) {
  904. myprint( "Checking each --regexflag command with a space string.\n" ) ;
  905. my $string = flags_regex( q{ } ) ;
  906. # string undef means one of the eval regex was bad.
  907. if ( not ( defined $string ) ) {
  908. die_clean( 'Error: one of --regexflag option is bad, check it' ) ;
  909. }
  910. myprint( "Ok with each --regexflag\n" ) ;
  911. }
  912. $sync->{imap1} = my $imap1 = login_imap($host1, $port1, $user1, $domain1, $password1,
  913. $debugimap1, $sync->{h1}->{timeout}, $fastio1, $ssl1, $tls1,
  914. $authmech1, $authuser1, $reconnectretry1,
  915. $proxyauth1, $uid1, $split1, 'Host1', $sync->{h1} ) ;
  916. $sync->{imap2} = my $imap2 = login_imap($host2, $port2, $user2, $domain2, $password2,
  917. $debugimap2, $sync->{h2}->{timeout}, $fastio2, $ssl2, $tls2,
  918. $authmech2, $authuser2, $reconnectretry2,
  919. $proxyauth2, $uid2, $split2, 'Host2', $sync->{h2} ) ;
  920. $debug and myprint( 'Host1 Buffer I/O: ', $imap1->Buffer(), "\n" ) ;
  921. $debug and myprint( 'Host2 Buffer I/O: ', $imap2->Buffer(), "\n" ) ;
  922. die_clean( 'Not authenticated on host1' ) unless $imap1->IsAuthenticated( ) ;
  923. myprint( "Host1: state Authenticated\n" ) ;
  924. die_clean( 'Not authenticated on host2' ) unless $imap2->IsAuthenticated( ) ;
  925. myprint( "Host2: state Authenticated\n" ) ;
  926. myprint( 'Host1 capability: ', join(q{ }, @{ $imap1->capability_update() || [] }), "\n" ) ;
  927. myprint( 'Host2 capability: ', join(q{ }, @{ $imap2->capability_update() || [] }), "\n" ) ;
  928. imap_id_stuff( $sync ) ;
  929. #quota( $imap1, 'host1' ) ; # quota on host1 is useless and pollute host2 output.
  930. quota( $imap2, 'host2', $sync ) ;
  931. if ( $justlogin ) {
  932. $imap1->logout( ) ;
  933. $imap2->logout( ) ;
  934. exit_clean( $sync, $EX_OK ) ;
  935. }
  936. #
  937. # Folder stuff
  938. #
  939. my (
  940. @h1_folders_all , %h1_folders_all , @h1_folders_wanted , %requested_folder ,
  941. %h1_subscribed_folder , %h2_subscribed_folder ,
  942. @h2_folders_all , %h2_folders_all , %h2_folders_all_UPPER ,
  943. @h2_folders_from_1_wanted , %h2_folders_from_1_wanted ,
  944. %h2_folders_from_1_several ,
  945. %h2_folders_from_1_all ,
  946. ) ;
  947. my $h1_folders_wanted_nb = 0 ;
  948. my $h1_folders_wanted_ct = 0 ; # counter of folders done.
  949. # All folders on host1 and host2
  950. @h1_folders_all = sort $imap1->folders( ) ;
  951. @h2_folders_all = sort $imap2->folders( ) ;
  952. myprint( 'Host1: found ', scalar @h1_folders_all , " folders.\n" ) ;
  953. myprint( 'Host2: found ', scalar @h2_folders_all , " folders.\n" ) ;
  954. for ( @h1_folders_all ) { $h1_folders_all{ $_ } = 1 } ;
  955. for ( @h2_folders_all ) {
  956. $h2_folders_all{ $_ } = 1 ;
  957. $h2_folders_all_UPPER{ uc $_ } = 1 ;
  958. } ;
  959. $sync->{h1_folders_all} = \%h1_folders_all ;
  960. $sync->{h2_folders_all} = \%h2_folders_all ;
  961. $sync->{h2_folders_all_UPPER} = \%h2_folders_all_UPPER ;
  962. # Make a hash of subscribed folders in both servers.
  963. for ( $imap1->subscribed( ) ) { $h1_subscribed_folder{ $_ } = 1 } ;
  964. for ( $imap2->subscribed( ) ) { $h2_subscribed_folder{ $_ } = 1 } ;
  965. if ( defined $subfolder2 ) {
  966. unshift @regextrans2,
  967. q's,^${h2_prefix}(.*),${h2_prefix}${subfolder2}${h2_sep}$1,',
  968. q's,^INBOX$,${h2_prefix}${subfolder2}${h2_sep}INBOX,' ;
  969. }
  970. if ( $fixInboxINBOX and ( my $reg = fix_Inbox_INBOX_mapping( \%h1_folders_all, \%h2_folders_all ) ) ) {
  971. push @regextrans2, $reg ;
  972. }
  973. if (scalar @folder or $subscribed or scalar @folderrec) {
  974. # folders given by option --folder
  975. if (scalar @folder) {
  976. add_to_requested_folders(@folder);
  977. }
  978. # option --subscribed
  979. if ( $subscribed ) {
  980. add_to_requested_folders( keys %h1_subscribed_folder ) ;
  981. }
  982. # option --folderrec
  983. if (scalar @folderrec) {
  984. foreach my $folderrec (@folderrec) {
  985. add_to_requested_folders($imap1->folders($folderrec));
  986. }
  987. }
  988. }
  989. else {
  990. # no include, no folder/subscribed/folderrec options => all folders
  991. if (not scalar @include) {
  992. 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" ) ;
  993. add_to_requested_folders(@h1_folders_all);
  994. }
  995. }
  996. # consider (optional) includes and excludes
  997. if ( scalar @include ) {
  998. foreach my $include ( @include ) {
  999. my @included_folders = grep { /$include/ } @h1_folders_all ;
  1000. add_to_requested_folders( @included_folders ) ;
  1001. myprint( "Including folders matching pattern $include\n" . jux_utf8_list( @included_folders ) . "\n" ) ;
  1002. }
  1003. }
  1004. if ( scalar @exclude ) {
  1005. foreach my $exclude ( @exclude ) {
  1006. my @requested_folder = sort keys %requested_folder ;
  1007. my @excluded_folders = grep { /$exclude/ } @requested_folder ;
  1008. remove_from_requested_folders( @excluded_folders ) ;
  1009. myprint( "Excluding folders matching pattern $exclude\n" . jux_utf8_list( @excluded_folders ) . "\n" ) ;
  1010. }
  1011. }
  1012. # sort before is not very powerful
  1013. # it adds --folderfirst and --folderlast even if they don't exist on host1
  1014. @h1_folders_wanted = sort_requested_folders( ) ;
  1015. # Remove no selectable folders
  1016. my @h1_folders_wanted_exist ;
  1017. myprint( "Host1: checking all wanted folders exist.\n" ) ;
  1018. foreach my $folder ( @h1_folders_wanted ) {
  1019. ( $debug or $sync->{debugfolders} ) and myprint( "Checking $folder exists on host1\n" ) ;
  1020. if ( ! exists $h1_folders_all{ $folder } ) {
  1021. myprint( "Host1: warning! ignoring folder $folder because it is not in host1 whole folders list.\n" ) ;
  1022. next ;
  1023. }else{
  1024. push @h1_folders_wanted_exist, $folder ;
  1025. }
  1026. }
  1027. @h1_folders_wanted = @h1_folders_wanted_exist ;
  1028. $checkselectable and do {
  1029. my @h1_folders_wanted_selectable ;
  1030. myprint( "Host1: checking all wanted folders are selectable. Use --nocheckselectable to avoid this check.\n" ) ;
  1031. foreach my $folder ( @h1_folders_wanted ) {
  1032. ( $debug or $sync->{debugfolders} ) and myprint( "Checking $folder is selectable on host1\n" ) ;
  1033. if ( ! $imap1->selectable( $folder ) ) {
  1034. myprint( "Host1: warning! ignoring folder $folder because it is not selectable\n" ) ;
  1035. }else{
  1036. push @h1_folders_wanted_selectable, $folder ;
  1037. }
  1038. }
  1039. @h1_folders_wanted = @h1_folders_wanted_selectable ;
  1040. ( $debug or $sync->{debugfolders} ) and myprint( 'Host1: checking folders took ', timenext( ), " s\n" ) ;
  1041. } ;
  1042. $sync->{h1_folders_wanted} = \@h1_folders_wanted ;
  1043. my( $h1_sep, $h2_sep ) ;
  1044. # what are the private folders separators for each server ?
  1045. ( $debug or $sync->{debugfolders} ) and myprint( "Getting separators\n" ) ;
  1046. $h1_sep = get_separator( $imap1, $sep1, '--sep1', 'Host1', \@h1_folders_all ) ;
  1047. $h2_sep = get_separator( $imap2, $sep2, '--sep2', 'Host2', \@h2_folders_all ) ;
  1048. my( $h1_prefix, $h2_prefix ) ;
  1049. $sync->{ h1_prefix } = $h1_prefix = get_prefix( $imap1, $prefix1, '--prefix1', 'Host1', \@h1_folders_all ) ;
  1050. $sync->{ h2_prefix } = $h2_prefix = get_prefix( $imap2, $prefix2, '--prefix2', 'Host2', \@h2_folders_all ) ;
  1051. myprint( "Host1 separator and prefix: [$h1_sep][$h1_prefix]\n" ) ;
  1052. myprint( "Host2 separator and prefix: [$h2_sep][$h2_prefix]\n" ) ;
  1053. automap( $sync ) ;
  1054. foreach my $h1_fold ( @h1_folders_wanted ) {
  1055. my $h2_fold ;
  1056. $h2_fold = imap2_folder_name( $h1_fold ) ;
  1057. $h2_folders_from_1_wanted{ $h2_fold }++ ;
  1058. if ( 1 < $h2_folders_from_1_wanted{ $h2_fold } ) {
  1059. $h2_folders_from_1_several{ $h2_fold }++ ;
  1060. }
  1061. }
  1062. @h2_folders_from_1_wanted = sort keys %h2_folders_from_1_wanted;
  1063. foreach my $h1_fold ( @h1_folders_all ) {
  1064. my $h2_fold ;
  1065. $h2_fold = imap2_folder_name( $h1_fold ) ;
  1066. $h2_folders_from_1_all{ $h2_fold }++ ;
  1067. }
  1068. myprint( << 'END_LISTING' ) ;
  1069. ++++ Listing folders
  1070. All foldernames are presented between brackets like [X] where X is the foldername.
  1071. When a foldername contains non-ASCII characters it is presented in the form
  1072. [X] = [Y] where
  1073. X is the imap foldername you have to use in command line options and
  1074. Y is the uft8 output just printed for convenience, to recognize it.
  1075. END_LISTING
  1076. print
  1077. "Host1 folders list:\n",
  1078. jux_utf8_list( @h1_folders_all ),
  1079. "\n",
  1080. "Host2 folders list:\n",
  1081. jux_utf8_list( @h2_folders_all ),
  1082. "\n" ;
  1083. print
  1084. 'Host1 subscribed folders list: ',
  1085. jux_utf8_list( sort keys %h1_subscribed_folder ), "\n"
  1086. if ( $subscribed ) ;
  1087. my @h2_folders_not_in_1;
  1088. @h2_folders_not_in_1 = list_folders_in_2_not_in_1( ) ;
  1089. if ( @h2_folders_not_in_1 ) {
  1090. myprint( "Folders in host2 not in host1:\n",
  1091. jux_utf8_list( @h2_folders_not_in_1 ), "\n" ) ;
  1092. }
  1093. if ( defined $sync->{f1f2auto} ) {
  1094. myprint( "Folders mapping from --automap feature (use --f1f2 to override any mapping):\n" ) ;
  1095. foreach my $h1_fold ( keys %{$sync->{f1f2auto}} ) {
  1096. my $h2_fold = $sync->{f1f2auto}{$h1_fold} ;
  1097. myprintf( "%-40s -> %-40s\n",
  1098. jux_utf8( $h1_fold ), jux_utf8( $h2_fold ) ) ;
  1099. }
  1100. myprint( "\n" ) ;
  1101. }
  1102. if ( defined $sync->{f1f2} ) {
  1103. myprint( "Folders mapping from --f1f2 options, it overrides --automap:\n" ) ;
  1104. foreach my $h1_fold ( keys %{$sync->{f1f2}} ) {
  1105. my $h2_fold = $sync->{f1f2}{$h1_fold} ;
  1106. my $warn = q{} ;
  1107. if ( not exists $h1_folders_all{ $h1_fold } ) {
  1108. $warn = "BUT $h1_fold does NOT exist on host1!" ;
  1109. }
  1110. myprintf( "%-40s -> %-40s %s\n",
  1111. jux_utf8( $h1_fold ), jux_utf8( $h2_fold ), $warn ) ;
  1112. }
  1113. myprint( "\n" ) ;
  1114. }
  1115. exit_clean( $sync, $EX_OK ) if ( $sync->{justfolderlists} ) ;
  1116. exit_clean( $sync, $EX_OK ) if ( $sync->{justautomap} ) ;
  1117. debugsleep( $sync ) ;
  1118. if ( $foldersizes ) {
  1119. foldersizes_on_h1h2( ) ;
  1120. }
  1121. exit_clean( $sync, $EX_OK ) if ( $justfoldersizes ) ;
  1122. $sync->{stats} = 1 ;
  1123. if ( $sync->{'delete1emptyfolders'} ) {
  1124. delete1emptyfolders( $sync ) ;
  1125. }
  1126. delete_folders_in_2_not_in_1( ) if $delete2folders ;
  1127. # folder loop
  1128. $h1_folders_wanted_nb = scalar @h1_folders_wanted ;
  1129. myprint( "++++ Looping on each one of $h1_folders_wanted_nb folders to sync\n" ) ;
  1130. my $begin_transfer_time = time ;
  1131. my %uid_candidate_for_deletion ;
  1132. my %uid_candidate_no_deletion ;
  1133. my %h2_folders_of_md5 = ( ) ;
  1134. FOLDER: foreach my $h1_fold ( @h1_folders_wanted ) {
  1135. last FOLDER if $imap1->IsUnconnected( ) ;
  1136. last FOLDER if $imap2->IsUnconnected( ) ;
  1137. my $h2_fold = imap2_folder_name( $h1_fold ) ;
  1138. $h1_folders_wanted_ct++ ;
  1139. myprintf( "Folder %7s %-35s -> %-35s\n", "$h1_folders_wanted_ct/$h1_folders_wanted_nb",
  1140. jux_utf8( $h1_fold ), jux_utf8( $h2_fold ) ) ;
  1141. if ( $sync->{debugmemory} ) {
  1142. myprintf("FL: Memory consumption: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI) ;
  1143. }
  1144. # host1 can not be fetched read only, select is needed because of expunge.
  1145. select_folder( $imap1, $h1_fold, 'Host1' ) or next FOLDER ;
  1146. debugsleep( $sync ) ;
  1147. my $h1_fold_nb_messages = count_from_select( $imap1->History ) ;
  1148. myprint( "Host1 folder [$h1_fold] has $h1_fold_nb_messages messages in total (mentioned by SELECT)\n" ) ;
  1149. if ( $skipemptyfolders and 0 == $h1_fold_nb_messages ) {
  1150. myprint( "Skipping empty host1 folder [$h1_fold]\n" ) ;
  1151. next FOLDER ;
  1152. }
  1153. if ( ! exists $h2_folders_all{ $h2_fold } ) {
  1154. create_folder( $imap2, $h2_fold, $h1_fold ) or next FOLDER ;
  1155. }
  1156. acls_sync( $h1_fold, $h2_fold ) ;
  1157. # Sometimes the folder on host2 is listed (it exists) but is
  1158. # not selectable but becomes selectable by a create (Gmail)
  1159. select_folder( $imap2, $h2_fold, 'Host2' )
  1160. or ( create_folder( $imap2, $h2_fold, $h1_fold )
  1161. and select_folder( $imap2, $h2_fold, 'Host2' ) )
  1162. or next FOLDER ;
  1163. my @select_results = $imap2->Results( ) ;
  1164. my $h2_fold_nb_messages = count_from_select( @select_results ) ;
  1165. myprint( "Host2 folder [$h2_fold] has $h2_fold_nb_messages messages in total (mentioned by SELECT)\n" ) ;
  1166. my $permanentflags2 = permanentflags( @select_results ) ;
  1167. ( $debug or $debugflags ) and myprint( "Host2 folder [$h2_fold] permanentflags: $permanentflags2\n" ) ;
  1168. if ( $expunge or $expunge1 ){
  1169. myprint( "Host1: Expunging $h1_fold $dry_message\n" ) ;
  1170. unless( $dry ) { $imap1->expunge( ) } ;
  1171. }
  1172. if ( ( ( $subscribe and exists $h1_subscribed_folder{ $h1_fold } ) or $subscribeall )
  1173. and not exists $h2_subscribed_folder{ $h2_fold } ) {
  1174. myprint( "Host2: Subscribing to folder $h2_fold\n" ) ;
  1175. unless( $dry ) { $imap2->subscribe( $h2_fold ) } ;
  1176. }
  1177. next FOLDER if ( $justfolders ) ;
  1178. last FOLDER if $imap1->IsUnconnected( ) ;
  1179. last FOLDER if $imap2->IsUnconnected( ) ;
  1180. my $h1_msgs_all_hash_ref = { } ;
  1181. my @h1_msgs = select_msgs( $imap1, $h1_msgs_all_hash_ref, $search1, $h1_fold );
  1182. last FOLDER if $imap1->IsUnconnected( ) ;
  1183. my $h1_msgs_nb = scalar @h1_msgs ;
  1184. $h1{ $h1_fold }{ 'messages_nb' } = $h1_msgs_nb ;
  1185. myprint( "Host1 folder [$h1_fold] considering $h1_msgs_nb messages\n" ) ;
  1186. ( $debug or $debuglist ) and myprint( "Host1 folder [$h1_fold] considering $h1_msgs_nb messages, LIST gives: @h1_msgs\n" ) ;
  1187. $debug and myprint( "Host1 selecting messages of folder [$h1_fold] took ", timenext(), " s\n" ) ;
  1188. my $h2_msgs_all_hash_ref = { } ;
  1189. my @h2_msgs = select_msgs( $imap2, $h2_msgs_all_hash_ref, $search2, $h2_fold ) ;
  1190. last FOLDER if $imap2->IsUnconnected( ) ;
  1191. my $h2_msgs_nb = scalar @h2_msgs ;
  1192. $h2{ $h2_fold }{ 'messages_nb' } = $h2_msgs_nb ;
  1193. myprint( "Host2 folder [$h2_fold] considering $h2_msgs_nb messages\n" ) ;
  1194. ( $debug or $debuglist ) and myprint( "Host2 folder [$h2_fold] considering $h2_msgs_nb messages, LIST gives: @h2_msgs\n" ) ;
  1195. $debug and myprint( "Host2 selecting messages of folder [$h2_fold] took ", timenext(), " s\n" ) ;
  1196. my $cache_base = "$tmpdir/kopano-migration-imap_cache/" ;
  1197. my $cache_dir = cache_folder( $cache_base, "$host1/$user1/$host2/$user2", $h1_fold, $h2_fold ) ;
  1198. my ( $cache_1_2_ref, $cache_2_1_ref ) = ( {}, {} ) ;
  1199. my $h1_uidvalidity = $imap1->uidvalidity( ) || q{} ;
  1200. my $h2_uidvalidity = $imap2->uidvalidity( ) || q{} ;
  1201. last FOLDER if $imap1->IsUnconnected( ) ;
  1202. last FOLDER if $imap2->IsUnconnected( ) ;
  1203. if ( $usecache ) {
  1204. myprint( "cache directory: $cache_dir\n" ) ;
  1205. mkpath( "$cache_dir" ) ;
  1206. ( $cache_1_2_ref, $cache_2_1_ref )
  1207. = get_cache( $cache_dir, \@h1_msgs, \@h2_msgs, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) ;
  1208. myprint( 'CACHE h1 h2: ', scalar keys %{ $cache_1_2_ref } , " files\n" ) ;
  1209. $debug and myprint( '[',
  1210. map ( { "$_->$cache_1_2_ref->{$_} " } keys %{ $cache_1_2_ref } ), " ]\n" ) ;
  1211. }
  1212. my %h1_hash = () ;
  1213. my %h2_hash = () ;
  1214. my ( %h1_msgs, %h2_msgs ) ;
  1215. @h1_msgs{ @h1_msgs } = ();
  1216. @h2_msgs{ @h2_msgs } = ();
  1217. my @h1_msgs_in_cache = sort { $a <=> $b } keys %{ $cache_1_2_ref } ;
  1218. my @h2_msgs_in_cache = keys %{ $cache_2_1_ref } ;
  1219. my ( %h1_msgs_not_in_cache, %h2_msgs_not_in_cache ) ;
  1220. %h1_msgs_not_in_cache = %h1_msgs ;
  1221. %h2_msgs_not_in_cache = %h2_msgs ;
  1222. delete @h1_msgs_not_in_cache{ @h1_msgs_in_cache } ;
  1223. delete @h2_msgs_not_in_cache{ @h2_msgs_in_cache } ;
  1224. my @h1_msgs_not_in_cache = keys %h1_msgs_not_in_cache ;
  1225. #myprint( "h1_msgs_not_in_cache: [@h1_msgs_not_in_cache]\n" ) ;
  1226. my @h2_msgs_not_in_cache = keys %h2_msgs_not_in_cache ;
  1227. my @h2_msgs_delete2_not_in_cache = () ;
  1228. %h1_msgs_copy_by_uid = ( ) ;
  1229. if ( $useuid ) {
  1230. # use uid so we have to avoid getting header
  1231. @h1_msgs_copy_by_uid{ @h1_msgs_not_in_cache } = ( ) ;
  1232. @h2_msgs_delete2_not_in_cache = @h2_msgs_not_in_cache if $usecache ;
  1233. @h1_msgs_not_in_cache = ( ) ;
  1234. @h2_msgs_not_in_cache = ( ) ;
  1235. #myprint( "delete2: @h2_msgs_delete2_not_in_cache\n" ) ;
  1236. }
  1237. $debug and myprint( "Host1 parsing headers of folder [$h1_fold]\n" ) ;
  1238. my ($h1_heads_ref, $h1_fir_ref) = ({}, {});
  1239. $h1_heads_ref = $imap1->parse_headers([@h1_msgs_not_in_cache], @useheader) if (@h1_msgs_not_in_cache);
  1240. $debug and myprint( "Host1 parsing headers of folder [$h1_fold] took ", timenext(), " s\n" ) ;
  1241. @{ $h1_fir_ref }{@h1_msgs} = ( undef ) ;
  1242. $debug and myprint( "Host1 getting flags idate and sizes of folder [$h1_fold]\n" ) ;
  1243. if ( $abletosearch ) {
  1244. $h1_fir_ref = $imap1->fetch_hash( \@h1_msgs, 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE', $h1_fir_ref )
  1245. if ( @h1_msgs ) ;
  1246. }else{
  1247. my $uidnext = $imap1->uidnext( $h1_fold ) || $uidnext_default ;
  1248. my $fetch_hash_uids = $fetch_hash_set || "1:$uidnext" ;
  1249. $h1_fir_ref = $imap1->fetch_hash( $fetch_hash_uids, 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE', $h1_fir_ref )
  1250. if ( @h1_msgs ) ;
  1251. }
  1252. $debug and myprint( "Host1 getting flags idate and sizes of folder [$h1_fold] took ", timenext(), " s\n" ) ;
  1253. unless ($h1_fir_ref) {
  1254. my $error = join( q{}, "Host1 folder $h1_fold: Could not fetch_hash ",
  1255. scalar @h1_msgs, ' msgs: ', $imap1->LastError || q{}, "\n" ) ;
  1256. errors_incr( $sync, $error ) ;
  1257. next FOLDER ;
  1258. }
  1259. my @h1_msgs_duplicate;
  1260. foreach my $m (@h1_msgs_not_in_cache) {
  1261. my $rc = parse_header_msg($imap1, $m, $h1_heads_ref, $h1_fir_ref, 'Host1', \%h1_hash);
  1262. if (! defined $rc) {
  1263. my $h1_size = $h1_fir_ref->{$m}->{'RFC822.SIZE'} || 0;
  1264. myprint( "Host1 $h1_fold/$m size $h1_size ignored (no wanted headers so we ignore this message. To solve this: use --addheader)\n" ) ;
  1265. $total_bytes_skipped += $h1_size;
  1266. $nb_msg_skipped += 1;
  1267. $h1_nb_msg_noheader +=1;
  1268. $h1_nb_msg_processed +=1 ;
  1269. } elsif(0 == $rc) {
  1270. # duplicate
  1271. push @h1_msgs_duplicate, $m;
  1272. # duplicate, same id same size?
  1273. my $h1_size = $h1_fir_ref->{$m}->{'RFC822.SIZE'} || 0;
  1274. $nb_msg_skipped += 1;
  1275. $h1_total_bytes_duplicate += $h1_size;
  1276. $h1_nb_msg_duplicate += 1;
  1277. $h1_nb_msg_processed +=1 ;
  1278. }
  1279. }
  1280. my $h1_msgs_duplicate_nb = scalar @h1_msgs_duplicate ;
  1281. $h1{ $h1_fold }{ 'duplicates_nb' } = $h1_msgs_duplicate_nb ;
  1282. $debug and myprint( "Host1 selected: $h1_msgs_nb duplicates: $h1_msgs_duplicate_nb\n" ) ;
  1283. $debug and myprint( 'Host1 whole time parsing headers took ', timenext(), " s\n" ) ;
  1284. $debug and myprint( "Host2 parsing headers of folder [$h2_fold]\n" ) ;
  1285. my ($h2_heads_ref, $h2_fir_ref) = ( {}, {} );
  1286. $h2_heads_ref = $imap2->parse_headers([@h2_msgs_not_in_cache], @useheader) if (@h2_msgs_not_in_cache);
  1287. $debug and myprint( "Host2 parsing headers of folder [$h2_fold] took ", timenext(), " s\n" ) ;
  1288. $debug and myprint( "Host2 getting flags idate and sizes of folder [$h2_fold]\n" ) ;
  1289. @{ $h2_fir_ref }{@h2_msgs} = ( ); # fetch_hash can select by uid with last arg as ref
  1290. if ( $abletosearch ) {
  1291. $h2_fir_ref = $imap2->fetch_hash( \@h2_msgs, 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE', $h2_fir_ref)
  1292. if (@h2_msgs) ;
  1293. }else{
  1294. my $uidnext = $imap2->uidnext( $h2_fold ) || $uidnext_default ;
  1295. my $fetch_hash_uids = $fetch_hash_set || "1:$uidnext" ;
  1296. $h2_fir_ref = $imap2->fetch_hash( $fetch_hash_uids, 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE', $h2_fir_ref )
  1297. if ( @h2_msgs ) ;
  1298. }
  1299. $debug and myprint( "Host2 getting flags idate and sizes of folder [$h2_fold] took ", timenext(), " s\n" ) ;
  1300. my @h2_msgs_duplicate;
  1301. foreach my $m (@h2_msgs_not_in_cache) {
  1302. my $rc = parse_header_msg($imap2, $m, $h2_heads_ref, $h2_fir_ref, 'Host2', \%h2_hash) ;
  1303. my $h2_size = $h2_fir_ref->{$m}->{'RFC822.SIZE'} || 0 ;
  1304. if (! defined $rc ) {
  1305. myprint( "Host2 $h2_fold/$m size $h2_size ignored (no wanted headers so we ignore this message)\n" ) ;
  1306. $h2_nb_msg_noheader += 1 ;
  1307. } elsif( 0 == $rc ) {
  1308. # duplicate
  1309. $h2_nb_msg_duplicate += 1 ;
  1310. $h2_total_bytes_duplicate += $h2_size ;
  1311. push @h2_msgs_duplicate, $m ;
  1312. }
  1313. }
  1314. # %h2_folders_of_md5
  1315. foreach my $md5 ( keys %h2_hash ) {
  1316. $h2_folders_of_md5{ $md5 }->{ $h2_fold } ++ ;
  1317. }
  1318. my $h2_msgs_duplicate_nb = scalar @h2_msgs_duplicate ;
  1319. $h2{ $h2_fold }{ 'duplicates_nb' } = $h2_msgs_duplicate_nb ;
  1320. myprint( "Host2 folder $h2_fold selected: $h2_msgs_nb messages, duplicates: $h2_msgs_duplicate_nb\n" )
  1321. if ( $debug or $delete2duplicates or $h2_msgs_duplicate_nb ) ;
  1322. $debug and myprint( 'Host2 whole time parsing headers took ', timenext( ), " s\n" ) ;
  1323. $debug and myprint( "++++ Verifying [$h1_fold] -> [$h2_fold]\n" ) ;
  1324. # messages in host1 that are not in host2
  1325. my @h1_hash_keys_sorted_by_uid
  1326. = sort {$h1_hash{$a}{'m'} <=> $h1_hash{$b}{'m'}} keys %h1_hash;
  1327. #myprint( map { $h1_hash{$_}{'m'} . q{ }} @h1_hash_keys_sorted_by_uid ) ;
  1328. my @h2_hash_keys_sorted_by_uid
  1329. = sort {$h2_hash{$a}{'m'} <=> $h2_hash{$b}{'m'}} keys %h2_hash;
  1330. if( $delete2duplicates and not exists $h2_folders_from_1_several{ $h2_fold } ) {
  1331. my @h2_expunge ;
  1332. foreach my $h2_msg ( @h2_msgs_duplicate ) {
  1333. myprint( "msg $h2_fold/$h2_msg marked \\Deleted [duplicate] on host2 $dry_message\n" ) ;
  1334. push @h2_expunge, $h2_msg if $uidexpunge2 ;
  1335. unless ( $dry ) {
  1336. $imap2->delete_message( $h2_msg ) ;
  1337. $h2_nb_msg_deleted += 1 ;
  1338. }
  1339. }
  1340. my $cnt = scalar @h2_expunge ;
  1341. if( @h2_expunge and not $expunge2 ) {
  1342. myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $dry_message\n" ) ;
  1343. $imap2->uidexpunge( \@h2_expunge ) if ! $dry ;
  1344. }
  1345. if ( $expunge2 ){
  1346. myprint( "Host2: Expunging folder $h2_fold $dry_message\n" ) ;
  1347. $imap2->expunge( ) if ! $dry ;
  1348. }
  1349. }
  1350. if( $delete2 and not exists $h2_folders_from_1_several{ $h2_fold } ) {
  1351. # No host1 folders f1a f1b ... going all to same f2 (via --regextrans2)
  1352. my @h2_expunge;
  1353. foreach my $m_id (@h2_hash_keys_sorted_by_uid) {
  1354. #myprint( "$m_id " ) ;
  1355. unless (exists $h1_hash{$m_id}) {
  1356. my $h2_msg = $h2_hash{$m_id}{'m'};
  1357. my $h2_flags = $h2_hash{$m_id}{'F'} || q{};
  1358. my $isdel = $h2_flags =~ /\B\\Deleted\b/x ? 1 : 0;
  1359. myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted on host2 [$m_id] $dry_message\n" )
  1360. if ! $isdel;
  1361. push @h2_expunge, $h2_msg if $uidexpunge2;
  1362. unless ($dry or $isdel) {
  1363. $imap2->delete_message($h2_msg);
  1364. $h2_nb_msg_deleted += 1;
  1365. }
  1366. }
  1367. }
  1368. foreach my $h2_msg ( @h2_msgs_delete2_not_in_cache ) {
  1369. myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted [not in cache] on host2 $dry_message\n" ) ;
  1370. push @h2_expunge, $h2_msg if $uidexpunge2;
  1371. unless ($dry) {
  1372. $imap2->delete_message($h2_msg);
  1373. $h2_nb_msg_deleted += 1;
  1374. }
  1375. }
  1376. my $cnt = scalar @h2_expunge ;
  1377. if( @h2_expunge and not $expunge2 ) {
  1378. myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $dry_message\n" ) ;
  1379. $imap2->uidexpunge( \@h2_expunge ) if ! $dry ;
  1380. }
  1381. if ( $expunge2 ) {
  1382. myprint( "Host2: Expunging folder $h2_fold $dry_message\n" ) ;
  1383. $imap2->expunge( ) if ! $dry ;
  1384. }
  1385. }
  1386. if( $delete2 and exists $h2_folders_from_1_several{ $h2_fold } ) {
  1387. myprint( "Host2 folder $h2_fold $h2_folders_from_1_several{ $h2_fold } folders left to sync there\n" ) ;
  1388. my @h2_expunge;
  1389. foreach my $m_id ( @h2_hash_keys_sorted_by_uid ) {
  1390. my $h2_msg = $h2_hash{ $m_id }{ 'm' } ;
  1391. unless ( exists $h1_hash{ $m_id } ) {
  1392. my $h2_flags = $h2_hash{ $m_id }{ 'F' } || q{} ;
  1393. my $isdel = $h2_flags =~ /\B\\Deleted\b/x ? 1 : 0 ;
  1394. unless ( $isdel ) {
  1395. $debug and myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion [$m_id]\n" ) ;
  1396. $uid_candidate_for_deletion{ $h2_fold }{ $h2_msg }++ ;
  1397. }
  1398. }else{
  1399. $debug and myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [$m_id]\n" ) ;
  1400. $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
  1401. }
  1402. }
  1403. foreach my $h2_msg ( @h2_msgs_delete2_not_in_cache ) {
  1404. myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion [not in cache]\n" ) ;
  1405. $uid_candidate_for_deletion{ $h2_fold }{ $h2_msg }++ ;
  1406. }
  1407. foreach my $h2_msg ( @h2_msgs_in_cache ) {
  1408. myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [in cache]\n" ) ;
  1409. $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
  1410. }
  1411. if ( 0 == $h2_folders_from_1_several{ $h2_fold } ) {
  1412. # last host1 folder going to $h2_fold
  1413. myprint( "Last host1 folder going to $h2_fold\n" ) ;
  1414. foreach my $h2_msg ( keys %{ $uid_candidate_for_deletion{ $h2_fold } } ) {
  1415. $debug and myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion\n" ) ;
  1416. if ( exists $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg } ) {
  1417. $debug and myprint( "Host2: msg $h2_fold/$h2_msg canceled deletion\n" ) ;
  1418. }else{
  1419. myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted $dry_message\n" ) ;
  1420. push @h2_expunge, $h2_msg if $uidexpunge2 ;
  1421. unless ( $dry ) {
  1422. $imap2->delete_message( $h2_msg ) ;
  1423. $h2_nb_msg_deleted += 1 ;
  1424. }
  1425. }
  1426. }
  1427. }
  1428. my $cnt = scalar @h2_expunge ;
  1429. if( @h2_expunge and not $expunge2 ) {
  1430. myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $dry_message\n" ) ;
  1431. $imap2->uidexpunge( \@h2_expunge ) if ! $dry ;
  1432. }
  1433. if ( $expunge2 ) {
  1434. myprint( "Host2: Expunging host2 folder $h2_fold $dry_message\n" ) ;
  1435. $imap2->expunge( ) if ! $dry ;
  1436. }
  1437. $h2_folders_from_1_several{ $h2_fold }-- ;
  1438. }
  1439. my $h2_uidnext = $imap2->uidnext( $h2_fold ) ;
  1440. $debug and myprint( "Host2 uidnext: $h2_uidnext\n" ) ;
  1441. $h2_uidguess = $h2_uidnext ;
  1442. MESS: foreach my $m_id (@h1_hash_keys_sorted_by_uid) {
  1443. last FOLDER if $imap1->IsUnconnected( ) ;
  1444. last FOLDER if $imap2->IsUnconnected( ) ;
  1445. #myprint( "h1_nb_msg_processed: $h1_nb_msg_processed\n" ) ;
  1446. my $h1_size = $h1_hash{$m_id}{'s'};
  1447. my $h1_msg = $h1_hash{$m_id}{'m'};
  1448. my $h1_idate = $h1_hash{$m_id}{'D'};
  1449. if ( ( not exists $h2_hash{ $m_id } )
  1450. and ( not ( exists $h2_folders_of_md5{ $m_id } )
  1451. or not $skipcrossduplicates ) ) {
  1452. # copy
  1453. my $h2_msg = copy_message( $sync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) ;
  1454. $h2_folders_of_md5{ $m_id }->{ $h2_fold } ++ ;
  1455. if( $delete2 and ( exists $h2_folders_from_1_several{ $h2_fold } ) and $h2_msg ) {
  1456. myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [fresh copy] on host2\n" ) ;
  1457. $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
  1458. }
  1459. last FOLDER if total_bytes_max_reached( ) ;
  1460. next MESS;
  1461. }
  1462. else{
  1463. # already on host2
  1464. if ( exists $h2_hash{ $m_id } ) {
  1465. my $h2_msg = $h2_hash{$m_id}{'m'} ;
  1466. $debug and myprint( "Host1 found msg $h1_fold/$h1_msg equals Host2 $h2_fold/$h2_msg\n" ) ;
  1467. if ( $usecache ) {
  1468. $debugcache and myprint( "touch $cache_dir/${h1_msg}_$h2_msg\n" ) ;
  1469. touch( "$cache_dir/${h1_msg}_$h2_msg" )
  1470. or croak( "Couldn't touch $cache_dir/${h1_msg}_$h2_msg" ) ;
  1471. }
  1472. }elsif( exists $h2_folders_of_md5{ $m_id } ) {
  1473. my @folders_dup = keys %{ $h2_folders_of_md5{ $m_id } } ;
  1474. ( $debug or $debugcrossduplicates ) and myprint( "Host1 found msg $h1_fold/$h1_msg is also in Host2 folders @folders_dup\n" ) ;
  1475. }
  1476. $total_bytes_skipped += $h1_size ;
  1477. $nb_msg_skipped += 1 ;
  1478. $h1_nb_msg_processed +=1 ;
  1479. }
  1480. if ( exists $h2_hash{ $m_id } ) {
  1481. #$debug and myprint( "MESSAGE $m_id\n" ) ;
  1482. my $h2_msg = $h2_hash{$m_id}{'m'};
  1483. sync_flags_fir( $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) ;
  1484. # Good
  1485. my $h2_size = $h2_hash{$m_id}{'s'};
  1486. $debug and myprint(
  1487. "Host1 size msg $h1_fold/$h1_msg = $h1_size <> $h2_size = Host2 $h2_fold/$h2_msg\n" ) ;
  1488. }
  1489. last FOLDER if $imap2->IsUnconnected( ) ;
  1490. if ( $delete ) {
  1491. delete_message_on_host1( $h1_msg, $h1_fold ) ;
  1492. }
  1493. }
  1494. # END MESS: loop
  1495. last FOLDER if $imap1->IsUnconnected( ) ;
  1496. last FOLDER if $imap2->IsUnconnected( ) ;
  1497. MESS_IN_CACHE: foreach my $h1_msg ( @h1_msgs_in_cache ) {
  1498. my $h2_msg = $cache_1_2_ref->{ $h1_msg } ;
  1499. $debugcache and myprint( "cache messages update flags $h1_msg->$h2_msg\n" ) ;
  1500. sync_flags_fir( $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) ;
  1501. my $h1_size = $h1_fir_ref->{ $h1_msg }->{ 'RFC822.SIZE' } || 0 ;
  1502. $total_bytes_skipped += $h1_size;
  1503. $nb_msg_skipped += 1;
  1504. $h1_nb_msg_processed +=1 ;
  1505. last FOLDER if $imap2->IsUnconnected( ) ;
  1506. }
  1507. #myprint( "Messages by uid: ", map { "$_ " } keys %h1_msgs_copy_by_uid, "\n" ) ;
  1508. MESS_BY_UID: foreach my $h1_msg ( sort { $a <=> $b } keys %h1_msgs_copy_by_uid ) {
  1509. #
  1510. $debug and myprint( "Copy by uid $h1_fold/$h1_msg\n" ) ;
  1511. last FOLDER if $imap1->IsUnconnected( ) ;
  1512. last FOLDER if $imap2->IsUnconnected( ) ;
  1513. my $h2_msg = copy_message( $sync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) ;
  1514. if( $delete2 and exists $h2_folders_from_1_several{ $h2_fold } and $h2_msg ) {
  1515. myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [fresh copy] on host2\n" ) ;
  1516. $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
  1517. }
  1518. last FOLDER if total_bytes_max_reached( ) ;
  1519. }
  1520. if ( $expunge or $expunge1 ){
  1521. myprint( "Host1: Expunging folder $h1_fold $dry_message\n" ) ;
  1522. unless( $dry ) { $imap1->expunge( ) } ;
  1523. }
  1524. if ( $expunge2 ){
  1525. myprint( "Host2: Expunging folder $h2_fold $dry_message\n" ) ;
  1526. unless( $dry ) { $imap2->expunge( ) } ;
  1527. }
  1528. $debug and myprint( 'Time: ', timenext( ), " s\n" ) ;
  1529. }
  1530. sub total_bytes_max_reached {
  1531. return( 0 ) if not $exitwhenover ;
  1532. if ( $total_bytes_transferred >= $exitwhenover ) {
  1533. myprint( "Maximum bytes transferred reached, $total_bytes_transferred >= $exitwhenover, ending sync\n" ) ;
  1534. return( 1 ) ;
  1535. }
  1536. }
  1537. myprint( "++++ End looping on each folder\n" ) ;
  1538. ( $debug or $sync->{debugfolders} ) and myprint( 'Time: ', timenext( ), " s\n" ) ;
  1539. if ( $foldersizesatend ) {
  1540. myprint( << 'END_SIZE' ) ;
  1541. Folders sizes after the synchronization.
  1542. You can remove this foldersizes listing by using "--nofoldersizesatend"
  1543. END_SIZE
  1544. foldersizesatend( ) ;
  1545. }
  1546. $imap1->logout( ) unless lost_connection( $imap1, "for host1 [$host1]" ) ;
  1547. $imap2->logout( ) unless lost_connection( $imap2, "for host2 [$host2]" ) ;
  1548. stats( $sync ) ;
  1549. myprint( errorsdump( $sync->{nb_errors}, errors_log( $sync ) ) ) if ( $sync->{errorsdump} ) ;
  1550. tests_live_result( $sync->{nb_errors} ) if ( $testslive ) ;
  1551. exit_clean( $sync, $EXIT_WITH_ERRORS ) if ( $sync->{nb_errors} ) ;
  1552. exit_clean( $sync, $EX_OK ) ;
  1553. # END of main program
  1554. # subroutines
  1555. sub myprint { return print @ARG ; }
  1556. sub myprintf { return printf @ARG ; }
  1557. sub mysprintf {
  1558. my( $format, @list ) = @ARG ;
  1559. return sprintf $format, @list ;
  1560. }
  1561. sub unsetunsafe {
  1562. # Remove all content in unsafe evalued options
  1563. @regextrans2 = ( ) ;
  1564. @regexflag = ( ) ;
  1565. @regexmess = ( ) ;
  1566. @skipmess = ( ) ;
  1567. @pipemess = ( ) ;
  1568. $delete2foldersonly = undef ;
  1569. $delete2foldersbutnot = undef ;
  1570. return ;
  1571. }
  1572. sub debugsleep {
  1573. my $mysync = shift ;
  1574. if ( defined $mysync->{debugsleep} ) {
  1575. myprint( "Info: sleeping $mysync->{debugsleep}s\n" ) ;
  1576. sleep $mysync->{debugsleep} ;
  1577. }
  1578. return ;
  1579. }
  1580. sub foldersizes_on_h1h2 {
  1581. myprint( << 'END_SIZE' ) ;
  1582. Folders sizes before the synchronization.
  1583. You can remove foldersizes listings by using "--nofoldersizes" and "--nofoldersizesatend"
  1584. but then you will also loose the ETA (Estimation Time of Arrival) given after each message copy.
  1585. END_SIZE
  1586. ( $h1_nb_msg_start, $h1_bytes_start ) = foldersizes( 'Host1', $imap1, $search1, @h1_folders_wanted ) ;
  1587. ( $h2_nb_msg_start, $h2_bytes_start ) = foldersizes( 'Host2', $imap2, $search2, @h2_folders_from_1_wanted ) ;
  1588. if ( not all_defined( $h1_nb_msg_start, $h1_bytes_start, $h2_nb_msg_start, $h2_bytes_start ) ) {
  1589. my $error = "Failure getting foldersizes, ETA and final diff will not be displayed\n" ;
  1590. errors_incr( $sync, $error ) ;
  1591. $foldersizes = 0 ;
  1592. $foldersizesatend = 0 ;
  1593. return ;
  1594. }
  1595. my $h2_bytes_limit = $sync->{host2}->{quota_limit_bytes} || 0 ;
  1596. if ( $h2_bytes_limit and ( $h2_bytes_limit < $h1_bytes_start ) ) {
  1597. my $quota_percent = mysprintf( '%.0f', $h1_bytes_start/$h2_bytes_limit ) ;
  1598. my $error = "Host2: Quota limit will be exceeded! Over $quota_percent % ( $h1_bytes_start bytes / $h2_bytes_limit bytes )\n" ;
  1599. errors_incr( $sync, $error ) ;
  1600. }
  1601. return ;
  1602. }
  1603. sub all_defined {
  1604. if ( not @ARG ) {
  1605. return 0 ;
  1606. }
  1607. foreach my $elem ( @ARG ) {
  1608. if ( not defined $elem ) {
  1609. return 0 ;
  1610. }
  1611. }
  1612. return 1 ;
  1613. }
  1614. sub tests_all_defined {
  1615. is( 0, all_defined( ), 'all_defined: no param => 0' ) ;
  1616. is( 0, all_defined( () ), 'all_defined: void list => 0' ) ;
  1617. is( 0, all_defined( undef ), 'all_defined: undef => 0' ) ;
  1618. is( 0, all_defined( undef, undef ), 'all_defined: undef => 0' ) ;
  1619. is( 0, all_defined( 1, undef ), 'all_defined: 1 undef => 0' ) ;
  1620. is( 0, all_defined( undef, 1 ), 'all_defined: undef 1 => 0' ) ;
  1621. is( 1, all_defined( 1, 1 ), 'all_defined: 1 1 => 1' ) ;
  1622. is( 1, all_defined( (1, 1) ), 'all_defined: (1 1) => 1' ) ;
  1623. return ;
  1624. }
  1625. sub imap_id_stuff {
  1626. my $sync = shift ;
  1627. if ( not $sync->{id} ) { return ; } ;
  1628. $sync->{h1_imap_id} = imap_id( $sync->{imap1}, 'Host1' ) ;
  1629. #myprint( 'Host1: ' . $sync->{h1_imap_id} ) ;
  1630. $sync->{h2_imap_id} = imap_id( $sync->{imap2}, 'Host2' ) ;
  1631. #myprint( 'Host2: ' . $sync->{h2_imap_id} ) ;
  1632. return ;
  1633. }
  1634. sub imap_id {
  1635. my ( $imap, $Side ) = @_ ;
  1636. $Side ||= q{} ;
  1637. my $imap_id_response = q{} ;
  1638. if ( not $imap->has_capability( 'ID' ) ) {
  1639. $imap_id_response = 'No ID capability' ;
  1640. myprint( "$Side: No ID capability\n" ) ;
  1641. }else{
  1642. my $id_inp = imapsync_id( { side => lc $Side } ) ;
  1643. myprint( "\n$Side: found ID capability. Sending/receiving ID, presented in raw IMAP for now.\n"
  1644. . "In order to avoid sending/receiving ID, use option --noid\n" ) ;
  1645. my $debug_before = $imap->Debug( ) ;
  1646. $imap->Debug( 1 ) ;
  1647. my $id_out = $imap->tag_and_run( 'ID ' . $id_inp ) ;
  1648. #my $id_out = $imap->tag_and_run( 'ID NIL' ) ;
  1649. myprint( "\n" ) ;
  1650. $imap->Debug( $debug_before ) ;
  1651. #$imap_id_response = Data::Dumper->Dump( [ $id_out ], [ 'IMAP_ID' ] ) ;
  1652. }
  1653. return( $imap_id_response ) ;
  1654. }
  1655. sub imapsync_id {
  1656. my $overhashref = shift ;
  1657. # See http://tools.ietf.org/html/rfc2971.html
  1658. my $imapsync_id = { } ;
  1659. my $imapsync_id_kopano = {
  1660. name => 'kopano-migration-imap',
  1661. version => kopano_migration_imap_version( ),
  1662. os => $OSNAME,
  1663. vendor => 'Kopano',
  1664. 'support-url' => 'https://kopano.com',
  1665. # Example of date-time: 19-Sep-2015 08:56:07
  1666. date => date_from_rcs( q{$Date: 2016/08/19 10:30:36 $ } ),
  1667. } ;
  1668. $imapsync_id = $imapsync_id_kopano ;
  1669. my %mix = ( %{ $imapsync_id }, %{ $overhashref } ) ;
  1670. my $imapsync_id_str = format_for_imap_arg( \%mix ) ;
  1671. #myprint( "$imapsync_id_str\n" ) ;
  1672. return( $imapsync_id_str ) ;
  1673. }
  1674. sub tests_imapsync_id {
  1675. ok( '("name" "kopano-migration-imap" "version" "111" "os" "beurk" "vendor" "Kopano" "support-url" "https://kopano.com" "date" "22-12-1968" "side" "host1")'
  1676. eq imapsync_id( {
  1677. version => 111,
  1678. os => 'beurk',
  1679. date => '22-12-1968',
  1680. side => 'host1' } ),
  1681. 'tests_imapsync_id override' ) ;
  1682. return ;
  1683. }
  1684. sub format_for_imap_arg {
  1685. my $ref = shift ;
  1686. my $string = q{} ;
  1687. my %terms = %{ $ref } ;
  1688. my @terms = ( ) ;
  1689. if ( not ( %terms ) ) { return( 'NIL' ) } ;
  1690. # sort like in RFC then add extra key/values
  1691. foreach my $key ( qw( name version os os-version vendor support-url address date command arguments environment) ) {
  1692. if ( $terms{ $key } ) {
  1693. push @terms, $key, $terms{ $key } ;
  1694. delete $terms{ $key } ;
  1695. }
  1696. }
  1697. push @terms, %terms ;
  1698. $string = '(' . ( join q{ }, map { '"' . $_ . '"' } @terms ) . ')' ;
  1699. return( $string ) ;
  1700. }
  1701. sub tests_format_for_imap_arg {
  1702. ok( 'NIL' eq format_for_imap_arg( { } ), 'format_for_imap_arg empty hash ref' ) ;
  1703. ok( '("name" "toto")' eq format_for_imap_arg( { name => 'toto' } ), 'format_for_imap_arg { name => toto }' ) ;
  1704. ok( '("name" "toto" "key" "val")' eq format_for_imap_arg( { name => 'toto', key => 'val' } ), 'format_for_imap_arg 2 x key val' ) ;
  1705. return ;
  1706. }
  1707. sub quota {
  1708. my ( $imap, $side, $sync ) = @_ ;
  1709. my $Side = ucfirst $side ;
  1710. my $debug_before = $imap->Debug( ) ;
  1711. $imap->Debug( 1 ) ;
  1712. if ( not $imap->has_capability( 'QUOTA' ) ) {
  1713. $imap->Debug( $debug_before ) ;
  1714. return ;
  1715. } ;
  1716. myprint( "\n$Side: found quota, presented in raw IMAP\n" ) ;
  1717. my $getquotaroot = $imap->getquotaroot( 'INBOX' ) ;
  1718. # Gmail INBOX quotaroot is "" but with it Mail::IMAPClient does a literal GETQUOTA {2} \n ""
  1719. #$imap->quota( 'ROOT' ) ;
  1720. #$imap->quota( '""' ) ;
  1721. myprint( "\n" ) ;
  1722. $imap->Debug( $debug_before ) ;
  1723. my $quota_limit_bytes = quota_extract_storage_limit_in_bytes( $getquotaroot ) ;
  1724. my $quota_current_bytes = quota_extract_storage_current_in_bytes( $getquotaroot ) ;
  1725. $sync->{$side}->{quota_limit_bytes} = $quota_limit_bytes ;
  1726. $sync->{$side}->{quota_current_bytes} = $quota_current_bytes ;
  1727. my $quota_percent ;
  1728. if ( $quota_limit_bytes > 0 ) {
  1729. $quota_percent = mysprintf( '%.2f', $NUMBER_100 * $quota_current_bytes / $quota_limit_bytes ) ;
  1730. }else{
  1731. $quota_percent = 0 ;
  1732. }
  1733. myprint( "$Side: Quota current storage is $quota_current_bytes bytes. Limit is $quota_limit_bytes bytes. So $quota_percent % full\n" ) ;
  1734. if ( $QUOTA_PERCENT_LIMIT < $quota_percent ) {
  1735. my $error = "$Side: $quota_percent % full: it is time to find a bigger place! ( $quota_current_bytes bytes / $quota_limit_bytes bytes )\n" ;
  1736. errors_incr( $sync, $error ) ;
  1737. }
  1738. return ;
  1739. }
  1740. sub tests_quota_extract_storage_limit_in_bytes {
  1741. my $imap_output = [
  1742. '* QUOTAROOT "INBOX" "Storage quota" "Messages quota"',
  1743. '* QUOTA "Storage quota" (STORAGE 1 104857600)',
  1744. '* QUOTA "Messages quota" (MESSAGE 2 100000)',
  1745. '5 OK Getquotaroot completed.'
  1746. ] ;
  1747. ok( $NUMBER_104857600 * $KIBI == quota_extract_storage_limit_in_bytes( $imap_output ), 'quota_extract_storage_limit_in_bytes ') ;
  1748. return ;
  1749. }
  1750. sub quota_extract_storage_limit_in_bytes {
  1751. my $imap_output = shift ;
  1752. my $limit_kb ;
  1753. $limit_kb = ( map { /.*\(\s*STORAGE\s+\d+\s+(\d+)\s*\)/ ? $1 : () } @{ $imap_output } )[0] ;
  1754. $limit_kb ||= 0 ;
  1755. $debug and myprint( "storage_limit_kb = $limit_kb\n" ) ;
  1756. return( $KIBI * $limit_kb ) ;
  1757. }
  1758. sub tests_quota_extract_storage_current_in_bytes {
  1759. my $imap_output = [
  1760. '* QUOTAROOT "INBOX" "Storage quota" "Messages quota"',
  1761. '* QUOTA "Storage quota" (STORAGE 1 104857600)',
  1762. '* QUOTA "Messages quota" (MESSAGE 2 100000)',
  1763. '5 OK Getquotaroot completed.'
  1764. ] ;
  1765. ok( 1*$KIBI == quota_extract_storage_current_in_bytes( $imap_output ), 'quota_extract_storage_current_in_bytes: 1 => 1024 ') ;
  1766. return ;
  1767. }
  1768. sub quota_extract_storage_current_in_bytes {
  1769. my $imap_output = shift ;
  1770. my $current_kb ;
  1771. $current_kb = ( map { /.*\(\s*STORAGE\s+(\d+)\s+\d+\s*\)/ ? $1 : () } @{ $imap_output } )[0] ;
  1772. $current_kb ||= 0 ;
  1773. $debug and myprint( "storage_current_kb = $current_kb\n" ) ;
  1774. return( $KIBI * $current_kb ) ;
  1775. }
  1776. sub automap {
  1777. my ( $sync ) = @_ ;
  1778. if ( $sync->{automap} ) {
  1779. myprint( "Turned on automapping folders ( use --noautomap to turn off automapping )\n" ) ;
  1780. }else{
  1781. myprint( "Turned off automapping folders ( use --automap to turn on automapping )\n" ) ;
  1782. return ;
  1783. }
  1784. $sync->{h1_special} = special_from_folders_hash( $sync->{imap1}, 'Host1' ) ;
  1785. $sync->{h2_special} = special_from_folders_hash( $sync->{imap2}, 'Host2' ) ;
  1786. build_possible_special( $sync ) ;
  1787. build_guess_special( $sync ) ;
  1788. build_automap( $sync ) ;
  1789. return ;
  1790. }
  1791. sub build_guess_special {
  1792. my ( $sync ) = shift ;
  1793. foreach my $h1_fold ( sort keys %{ $sync->{h1_folders_all} } ) {
  1794. my $special = guess_special( $h1_fold, $sync->{possible_special}, $sync->{h1_prefix} ) ;
  1795. if ( $special ) {
  1796. $sync->{h1_special_guessed}{$h1_fold} = $special ;
  1797. my $already_guessed = $sync->{h1_special_guessed}{$special} ;
  1798. if ( $already_guessed ) {
  1799. myprint( "Host1: $h1_fold not $special because set to $already_guessed\n" ) ;
  1800. }else{
  1801. $sync->{h1_special_guessed}{$special} = $h1_fold ;
  1802. }
  1803. }
  1804. }
  1805. foreach my $h2_fold ( sort keys %{ $sync->{h2_folders_all} } ) {
  1806. my $special = guess_special( $h2_fold, $sync->{possible_special}, $sync->{h2_prefix} ) ;
  1807. if ( $special ) {
  1808. $sync->{h2_special_guessed}{$h2_fold} = $special ;
  1809. my $already_guessed = $sync->{h2_special_guessed}{$special} ;
  1810. if ( $already_guessed ) {
  1811. myprint( "Host2: $h2_fold not $special because set to $already_guessed\n" ) ;
  1812. }else{
  1813. $sync->{h2_special_guessed}{$special} = $h2_fold ;
  1814. }
  1815. }
  1816. }
  1817. return ;
  1818. }
  1819. sub guess_special {
  1820. my( $folder, $possible_special_ref, $prefix ) = @_ ;
  1821. my $folder_no_prefix = $folder ;
  1822. $folder_no_prefix =~ s/${prefix}// ;
  1823. #$debug and myprint( "folder_no_prefix: $folder_no_prefix\n" ) ;
  1824. my $guess_special = $possible_special_ref->{ $folder }
  1825. || $possible_special_ref->{ $folder_no_prefix }
  1826. || q{} ;
  1827. return( $guess_special ) ;
  1828. }
  1829. sub tests_guess_special {
  1830. my $possible_special_ref = build_possible_special( my $sync ) ;
  1831. ok( '\Sent' eq guess_special( 'Sent', $possible_special_ref, q{} ) ,'guess_special: Sent => \Sent' ) ;
  1832. ok( q{} eq guess_special( 'Blabla', $possible_special_ref, q{} ) ,'guess_special: Blabla => q{}' ) ;
  1833. ok( '\Sent' eq guess_special( 'INBOX.Sent', $possible_special_ref, 'INBOX.' ) ,'guess_special: INBOX.Sent => \Sent' ) ;
  1834. return ;
  1835. }
  1836. sub build_automap {
  1837. my ( $sync ) = @_ ;
  1838. foreach my $h1_fold ( @{ $sync->{h1_folders_wanted} } ) {
  1839. my $h2_fold ;
  1840. my $h1_special = $sync->{h1_special}{$h1_fold} ;
  1841. my $h1_special_guessed = $sync->{h1_special_guessed}{$h1_fold} ;
  1842. # Case 1: special on both sides.
  1843. if ( $h1_special
  1844. and exists $sync->{h2_special}{$h1_special} ) {
  1845. $h2_fold = $sync->{h2_special}{$h1_special} ;
  1846. $sync->{f1f2auto}{ $h1_fold } = $h2_fold ;
  1847. next ;
  1848. }
  1849. # Case 2: special on host1, not on host2
  1850. if ( $h1_special
  1851. and ( not exists $sync->{h2_special}{$h1_special} )
  1852. and ( exists $sync->{h2_special_guessed}{$h1_special} )
  1853. ) {
  1854. # special_guessed on host2
  1855. $h2_fold = $sync->{h2_special_guessed}{$h1_special} ;
  1856. $sync->{f1f2auto}{ $h1_fold } = $h2_fold ;
  1857. next ;
  1858. }
  1859. # Case 3: no special on host1, special on host2
  1860. if ( ( not $h1_special )
  1861. and ( $h1_special_guessed )
  1862. and ( exists $sync->{h2_special}{$h1_special_guessed} )
  1863. ) {
  1864. $h2_fold = $sync->{h2_special}{$h1_special_guessed} ;
  1865. $sync->{f1f2auto}{ $h1_fold } = $h2_fold ;
  1866. next ;
  1867. }
  1868. # Case 4: no special on both sides.
  1869. if ( ( not $h1_special )
  1870. and ( $h1_special_guessed )
  1871. and ( not exists $sync->{h2_special}{$h1_special_guessed} )
  1872. and ( exists $sync->{h2_special_guessed}{$h1_special_guessed} )
  1873. ) {
  1874. $h2_fold = $sync->{h2_special_guessed}{$h1_special_guessed} ;
  1875. $sync->{f1f2auto}{ $h1_fold } = $h2_fold ;
  1876. next ;
  1877. }
  1878. }
  1879. return( $sync->{f1f2auto} ) ;
  1880. }
  1881. # I willll probably add what there is at:
  1882. # http://stackoverflow.com/questions/2185391/localized-gmail-imap-folders/2185548#2185548
  1883. sub build_possible_special {
  1884. my $sync = shift ;
  1885. my $possible_special = { } ;
  1886. # All|Archive|Drafts|Flagged|Junk|Sent|Trash
  1887. $possible_special->{'\All'} = [ 'All', 'All Messages', '&BBIEQQQ1-' ] ;
  1888. $possible_special->{'\Archive'} = [ 'Archive', 'Archives', '&BBAEQARFBDgEMg-' ] ;
  1889. $possible_special->{'\Drafts'} = [ 'Drafts', '&BCcENQRABD0EPgQyBDgEOgQ4-' ] ;
  1890. $possible_special->{'\Flagged'} = [ 'Flagged', 'Starred', '&BB8EPgQ8BDUERwQ1BD0EPQRLBDU-' ] ;
  1891. $possible_special->{'\Junk'} = [ 'Junk', 'Spam', '&BCEEPwQwBDw-' ] ;
  1892. $possible_special->{'\Sent'} = [ 'Sent', 'Sent Messages', 'Sent Items',
  1893. 'Gesendete Elemente', 'Gesendete Objekte',
  1894. '&AMk-l&AOk-ments envoy&AOk-s', 'Envoy&AOk-',
  1895. 'Elementos enviados',
  1896. '&kAFP4W4IMH8wojCkMMYw4A-',
  1897. '&BB4EQgQ,BEAEMAQyBDsENQQ9BD0ESwQ1-'] ;
  1898. $possible_special->{'\Trash'} = [ 'Trash', '&BCMENAQwBDsENQQ9BD0ESwQ1-', '&BBoEPgRABDcEOAQ9BDA-' ] ;
  1899. foreach my $special ( qw( \All \Archive \Drafts \Flagged \Junk \Sent \Trash ) ){
  1900. foreach my $possible_folder ( @{ $possible_special->{$special} } ) {
  1901. $possible_special->{ $possible_folder } = $special ;
  1902. } ;
  1903. }
  1904. $sync->{possible_special} = $possible_special ;
  1905. $debug and myprint( Data::Dumper->Dump( [ $possible_special ], [ 'possible_special' ] ) ) ;
  1906. return( $possible_special ) ;
  1907. }
  1908. sub special_from_folders_hash {
  1909. my ( $imap, $side ) = @_ ;
  1910. my %special = ( ) ;
  1911. if ( not( Mail::IMAPClient->can( 'folders_hash' ) ) ) {
  1912. my $error = "$side: To have automagic rfc6154 folder mapping, upgrade Mail::IMAPClient >= 3.34\n" ;
  1913. errors_incr( $sync, $error ) ;
  1914. return( \%special ) ; # empty hash ref
  1915. }
  1916. my $folders_hash = $imap->folders_hash( ) ;
  1917. foreach my $fhash (@{ $folders_hash } ) {
  1918. my @special = grep { /\\(?:All|Archive|Drafts|Flagged|Junk|Sent|Trash)/ } @{ $fhash->{attrs} } ;
  1919. if ( @special ) {
  1920. my $special = $special[0] ; # keep first one. Could be not very good.
  1921. if ( exists $special{ $special } ) {
  1922. myprintf( "%s: special %-20s = %s already asigned to %s\n",
  1923. $side, $fhash->{name}, join( q{ }, @special ), $special{ $special } ) ;
  1924. }else{
  1925. myprintf( "%s: special %-20s = %s\n",
  1926. $side, $fhash->{name}, join( q{ }, @special ) ) ;
  1927. $special{ $special } = $fhash->{name} ;
  1928. $special{ $fhash->{name} } = $special ; # double entry value => key
  1929. }
  1930. }
  1931. }
  1932. myprint( "\n" ) if ( %special ) ;
  1933. return( \%special ) ;
  1934. }
  1935. sub errors_incr {
  1936. my ( $mysync, @error ) = @ARG ;
  1937. $sync->{nb_errors}++ ;
  1938. if ( @error ) {
  1939. errors_log( $mysync, @error ) ;
  1940. myprint( @error ) ;
  1941. }
  1942. $mysync->{errorsmax} ||= $ERRORS_MAX ;
  1943. if ( $sync->{nb_errors} >= $mysync->{errorsmax} ) {
  1944. myprint( "Maximum number of errors $mysync->{errorsmax} reached ( you can change $mysync->{errorsmax} to 100 with --errorsmax 100 ). Exiting.\n" ) ;
  1945. if ( $mysync->{errorsdump} ) {
  1946. myprint( errorsdump( $sync->{nb_errors}, errors_log( $mysync ) ) ) ;
  1947. # again since errorsdump( ) can be very verbose and masq previous warning
  1948. myprint( "Maximum number of errors $mysync->{errorsmax} reached ( you can change $mysync->{errorsmax} to 100 with --errorsmax 100 ). Exiting.\n" ) ;
  1949. }
  1950. exit_clean( $mysync, $EXIT_WITH_ERRORS_MAX ) ;
  1951. }
  1952. return ;
  1953. }
  1954. sub errors_log {
  1955. my ( $mysync, @error ) = @ARG ;
  1956. if ( ! $mysync->{errors_log} ) {
  1957. $mysync->{errors_log} = [] ;
  1958. }
  1959. if ( @error ) {
  1960. push @{ $mysync->{errors_log} }, join( q{}, @error ) ;
  1961. }
  1962. if ( @{ $mysync->{errors_log} } ) {
  1963. return @{ $mysync->{errors_log} } ;
  1964. }
  1965. else {
  1966. return ;
  1967. }
  1968. }
  1969. sub tests_errors_log {
  1970. }
  1971. sub errorsdump {
  1972. my( $nb_errors, @errors_log ) = @ARG ;
  1973. my $error_num = 0 ;
  1974. my $errors_list = q{} ;
  1975. if ( @errors_log ) {
  1976. $errors_list = "++++ Listing $nb_errors errors encountered during the sync ( avoid this listing with --noerrorsdump ).\n" ;
  1977. foreach my $error ( @errors_log ) {
  1978. $error_num++ ;
  1979. $errors_list .= "Err $error_num/$nb_errors: $error" ;
  1980. }
  1981. }
  1982. return( $errors_list ) ;
  1983. }
  1984. sub tests_live_result {
  1985. my $nb_errors = shift ;
  1986. if ( $nb_errors ) {
  1987. myprint( "Live tests failed with $nb_errors errors\n" ) ;
  1988. } else {
  1989. myprint( "Live tests ended successfully\n" ) ;
  1990. }
  1991. return ;
  1992. }
  1993. sub foldersizesatend {
  1994. timenext( ) ;
  1995. return if ( $imap1->IsUnconnected( ) ) ;
  1996. return if ( $imap2->IsUnconnected( ) ) ;
  1997. # Get all folders on host2 again since new were created
  1998. @h2_folders_all = sort $imap2->folders();
  1999. for ( @h2_folders_all ) {
  2000. $h2_folders_all{ $_ } = 1 ;
  2001. $h2_folders_all_UPPER{ uc $_ } = 1 ;
  2002. } ;
  2003. ( $h1_nb_msg_end, $h1_bytes_end ) = foldersizes( 'Host1', $imap1, $search1, @h1_folders_wanted ) ;
  2004. ( $h2_nb_msg_end, $h2_bytes_end ) = foldersizes( 'Host2', $imap2, $search2, @h2_folders_from_1_wanted ) ;
  2005. if ( not all_defined( $h1_nb_msg_end, $h1_bytes_end, $h2_nb_msg_end, $h2_bytes_end ) ) {
  2006. my $error = "Failure getting foldersizes, final differences will not be calculated\n" ;
  2007. errors_incr( $sync, $error ) ;
  2008. }
  2009. return ;
  2010. }
  2011. sub size_filtered_flag {
  2012. my $h1_size = shift ;
  2013. if (defined $maxsize and $h1_size >= $maxsize) {
  2014. return( 1 ) ;
  2015. }
  2016. if (defined $minsize and $h1_size <= $minsize) {
  2017. return( 1 ) ;
  2018. }
  2019. return( 0 ) ;
  2020. }
  2021. sub sync_flags_fir {
  2022. my ( $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) = @_ ;
  2023. if ( not defined $h1_msg ) { return } ;
  2024. if ( not defined $h2_msg ) { return } ;
  2025. my $h1_size = $h1_fir_ref->{$h1_msg}->{'RFC822.SIZE'} ;
  2026. return if size_filtered_flag( $h1_size ) ;
  2027. # used cached flag values for efficiency
  2028. my $h1_flags = $h1_fir_ref->{ $h1_msg }->{ 'FLAGS' } || q{} ;
  2029. my $h2_flags = $h2_fir_ref->{ $h2_msg }->{ 'FLAGS' } || q{} ;
  2030. sync_flags( $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) ;
  2031. return ;
  2032. }
  2033. sub sync_flags_after_copy {
  2034. my( $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $permanentflags2 ) = @_ ;
  2035. my @h2_flags = $imap2->flags( $h2_msg ) ;
  2036. my $h2_flags = "@h2_flags" ;
  2037. ( $debug or $debugflags ) and myprint( "Host2 flags before resync by STORE on msg $h2_msg: $h2_flags\n" ) ;
  2038. sync_flags( $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) ;
  2039. return ;
  2040. }
  2041. sub sync_flags {
  2042. my( $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) = @_ ;
  2043. ( $debug or $debugflags ) and
  2044. myprint( "Host1: flags init msg $h1_fold/$h1_msg flags( $h1_flags ) Host2 $h2_fold/$h2_msg flags( $h2_flags )\n" ) ;
  2045. $h1_flags = flags_for_host2( $h1_flags, $permanentflags2 ) ;
  2046. $h2_flags = flagscase( $h2_flags ) ;
  2047. ( $debug or $debugflags ) and
  2048. myprint( "Host1 flags filt msg $h1_fold/$h1_msg flags( $h1_flags ) Host2 $h2_fold/$h2_msg flags( $h2_flags )\n" ) ;
  2049. # compare flags - set flags if there a difference
  2050. my @h1_flags = sort split(q{ }, $h1_flags );
  2051. my @h2_flags = sort split(q{ }, $h2_flags );
  2052. my $diff = compare_lists( \@h1_flags, \@h2_flags );
  2053. $diff and ( $debug or $debugflags )
  2054. and myprint( "Host2 flags msg $h2_fold/$h2_msg replacing h2 flags( $h2_flags ) with h1 flags( $h1_flags )\n" ) ;
  2055. # This sets flags so flags can be removed with this
  2056. # When you remove a \Seen flag on host1 you want to it
  2057. # to be removed on host2. Just add flags is not what
  2058. # we need most of the time.
  2059. if ( not $dry and $diff and not $imap2->store( $h2_msg, "FLAGS.SILENT (@h1_flags)" ) ) {
  2060. my $error_msg = join q{}, "Host2 flags msg $h2_fold/$h2_msg could not add flags [@h1_flags]: ",
  2061. $imap2->LastError || q{}, "\n" ;
  2062. errors_incr( $sync, $error_msg ) ;
  2063. }
  2064. return ;
  2065. }
  2066. sub _filter {
  2067. my $str = shift or return q{} ;
  2068. my $sz = $SIZE_MAX_STR ;
  2069. my $len = length $str ;
  2070. if ( not $debug and $len > $sz*2 ) {
  2071. my $beg = substr $str, 0, $sz ;
  2072. my $end = substr $str, -$sz, $sz ;
  2073. $str = $beg . '...' . $end ;
  2074. }
  2075. $str =~ s/\012?\015$//x ;
  2076. return "(len=$len) " . $str ;
  2077. }
  2078. sub lost_connection {
  2079. my( $imap, $error_message ) = @_;
  2080. if ( $imap->IsUnconnected( ) ) {
  2081. $sync->{nb_errors}++ ;
  2082. my $lcomm = $imap->LastIMAPCommand || q{} ;
  2083. my $einfo = $imap->LastError || @{$imap->History}[$LAST] || q{} ;
  2084. # if string is long try reduce to a more reasonable size
  2085. $lcomm = _filter( $lcomm ) ;
  2086. $einfo = _filter( $einfo ) ;
  2087. myprint( "Failure: last command: $lcomm\n") if ($debug && $lcomm) ;
  2088. myprint( "Failure: lost connection $error_message: ", $einfo, "\n") ;
  2089. return( 1 ) ;
  2090. }
  2091. else{
  2092. return( 0 ) ;
  2093. }
  2094. }
  2095. sub max {
  2096. my @list = @_ ;
  2097. return( undef ) if ( 0 == scalar @list ) ;
  2098. my @sorted = sort { $a <=> $b } @list ;
  2099. return( pop @sorted ) ;
  2100. }
  2101. sub tests_max {
  2102. ok( 0 == max( 0 ), 'max 0' ) ;
  2103. ok( 1 == max( 1 ), 'max 1' ) ;
  2104. ok( $MINUS_ONE == max( $MINUS_ONE ), 'max -1') ;
  2105. ok( not ( defined max( ) ), 'max no arg' ) ;
  2106. ok( $NUMBER_100 == max( 1, $NUMBER_100 ), 'max 1 100' ) ;
  2107. ok( $NUMBER_100 == max( $NUMBER_100, 1 ), 'max 100 1' ) ;
  2108. ok( $NUMBER_100 == max( $NUMBER_100, $NUMBER_42, 1 ), 'max 100 42 1' ) ;
  2109. ok( $NUMBER_100 == max( $NUMBER_100, '42', 1 ), 'max 100 42 1' ) ;
  2110. ok( $NUMBER_100 == max( '100', '42', 1 ), 'max 100 42 1' ) ;
  2111. #ok( 100 == max( 100, 'haha', 1 ), 'max 100 42 1') ;
  2112. return ;
  2113. }
  2114. sub check_lib_version {
  2115. $debug and myprint( "IMAPClient $Mail::IMAPClient::VERSION\n" ) ;
  2116. if ( '2.2.9' eq $Mail::IMAPClient::VERSION ) {
  2117. myprint( "kopano-migration-imap no longer supports Mail::IMAPClient 2.2.9, upgrade it\n" ) ;
  2118. return 0 ;
  2119. }
  2120. else{
  2121. # 3.x.x is no longer buggy with kopano-migration-imap.
  2122. # 3.30 or currently superior is imposed in the Perl "use Mail::IMAPClient line".
  2123. return 1 ;
  2124. }
  2125. return ;
  2126. }
  2127. sub module_version_str {
  2128. my( $module_name, $module_version ) = @_ ;
  2129. my $str = mysprintf( "%-20s %s\n", $module_name, $module_version ) ;
  2130. return( $str ) ;
  2131. }
  2132. sub modulesversion {
  2133. my @list_version;
  2134. my $v ;
  2135. eval { require Mail::IMAPClient; $v = $Mail::IMAPClient::VERSION } or $v = q{?} ;
  2136. push @list_version, module_version_str( 'Mail::IMAPClient', $v ) ;
  2137. eval { require IO::Socket; $v = $IO::Socket::VERSION } or $v = q{?} ;
  2138. push @list_version, module_version_str( 'IO::Socket', $v ) ;
  2139. eval { require IO::Socket::INET; $v = $IO::Socket::INET::VERSION } or $v = q{?} ;
  2140. push @list_version, module_version_str( 'IO::Socket::INET', $v ) ;
  2141. eval { require IO::Socket::INET6; $v = $IO::Socket::INET6::VERSION } or $v = q{?} ;
  2142. push @list_version, module_version_str( 'IO::Socket::INET6', $v ) ;
  2143. eval { require IO::Socket::SSL ; $v = $IO::Socket::SSL::VERSION } or $v = q{?} ;
  2144. push @list_version, module_version_str( 'IO::Socket::SSL ', $v ) ;
  2145. eval { require Net::SSLeay ; $v = $Net::SSLeay::VERSION } or $v = q{?} ;
  2146. push @list_version, module_version_str( 'Net::SSLeay ', $v ) ;
  2147. eval { require Compress::Zlib; $v = $Compress::Zlib::VERSION } or $v = q{?} ;
  2148. push @list_version, module_version_str( 'Compress::Zlib', $v ) ;
  2149. eval { require Digest::MD5; $v = $Digest::MD5::VERSION } or $v = q{?} ;
  2150. push @list_version, module_version_str( 'Digest::MD5', $v ) ;
  2151. eval { require Digest::HMAC_MD5; $v = $Digest::HMAC_MD5::VERSION } or $v = q{?} ;
  2152. push @list_version, module_version_str( 'Digest::HMAC_MD5', $v ) ;
  2153. eval { require Digest::HMAC_SHA1; $v = $Digest::HMAC_SHA1::VERSION } or $v = q{?} ;
  2154. push @list_version, module_version_str( 'Digest::HMAC_SHA1', $v ) ;
  2155. eval { require Term::ReadKey; $v = $Term::ReadKey::VERSION } or $v = q{?} ;
  2156. push @list_version, module_version_str( 'Term::ReadKey', $v ) ;
  2157. eval { require File::Spec; $v = $File::Spec::VERSION } or $v = q{?} ;
  2158. push @list_version, module_version_str( 'File::Spec', $v ) ;
  2159. eval { require Time::HiRes; $v = $Time::HiRes::VERSION } or $v = q{?} ;
  2160. push @list_version, module_version_str( 'Time::HiRes', $v ) ;
  2161. eval { require Unicode::String; $v = $Unicode::String::VERSION } or $v = q{?} ;
  2162. push @list_version, module_version_str( 'Unicode::String', $v ) ;
  2163. eval { require IO::Tee; $v = $IO::Tee::VERSION } or $v = q{?} ;
  2164. push @list_version, module_version_str( 'IO::Tee', $v ) ;
  2165. eval { require File::Copy::Recursive; $v = $File::Copy::Recursive::VERSION } or $v = q{?} ;
  2166. push @list_version, module_version_str( 'File::Copy::Recursive', $v ) ;
  2167. eval { require Authen::NTLM; $v = $Authen::NTLM::VERSION } or $v = q{?} ;
  2168. push @list_version, module_version_str( 'Authen::NTLM', $v ) ;
  2169. eval { require URI::Escape; $v = $URI::Escape::VERSION } or $v = q{?} ;
  2170. push @list_version, module_version_str( 'URI::Escape', $v ) ;
  2171. eval { require Data::Uniqid; $v = $Data::Uniqid::VERSION } or $v = q{?} ;
  2172. push @list_version, module_version_str( 'Data::Uniqid', $v ) ;
  2173. eval { require JSON; $v = $JSON::VERSION } or $v = q{?} ;
  2174. push @list_version, module_version_str( 'JSON', $v ) ;
  2175. eval { require JSON::WebToken; $v = $JSON::WebToken::VERSION } or $v = q{?} ;
  2176. push @list_version, module_version_str( 'JSON::WebToken', $v ) ;
  2177. eval { require Crypt::OpenSSL::RSA; $v = $Crypt::OpenSSL::RSA::VERSION } or $v = q{?} ;
  2178. push @list_version, module_version_str( 'Crypt::OpenSSL::RSA', $v ) ;
  2179. eval { require LWP; $v = $LWP::VERSION } or $v = q{?} ;
  2180. push @list_version, module_version_str( 'LWP', $v ) ;
  2181. eval { require HTML::Entities; $v = $HTML::Entities::VERSION } or $v = q{?} ;
  2182. push @list_version, module_version_str( 'HTML::Entities', $v ) ;
  2183. #eval { require Filesys::DfPortable; $v = $Filesys::DfPortable::VERSION } or $v = q{?} ;
  2184. #push @list_version, module_version_str( 'Filesys::DfPortable', $v ) ;
  2185. eval { require Getopt::Long; $v = $Getopt::Long::VERSION } or $v = q{?} ;
  2186. push @list_version, module_version_str( 'Getopt::Long', $v ) ;
  2187. eval { require Test::MockObject; $v = $Test::MockObject::VERSION } or $v = q{?} ;
  2188. push @list_version, module_version_str( 'Test::MockObject', $v ) ;
  2189. return( @list_version ) ;
  2190. }
  2191. # Construct a command line copy with passwords replaced by MASKED.
  2192. sub command_line_nopassword {
  2193. my @argv = @_ ;
  2194. my @argv_nopassword ;
  2195. return( "@argv" ) if $showpasswords ;
  2196. while ( @argv ) {
  2197. my $arg = shift @argv ; # option name or value
  2198. if ( $arg =~ m/-password[12]/x ) {
  2199. shift @argv ; # password value
  2200. push @argv_nopassword, $arg, 'MASKED' ; # option name and fake value
  2201. }else{
  2202. push @argv_nopassword, $arg ; # same option or value
  2203. }
  2204. }
  2205. return("@argv_nopassword") ;
  2206. }
  2207. sub tests_command_line_nopassword {
  2208. ok(q{} eq command_line_nopassword(), 'command_line_nopassword void');
  2209. ok('--blabla' eq command_line_nopassword('--blabla'), 'command_line_nopassword --blabla');
  2210. #myprint( command_line_nopassword((qw{ --password1 secret1 })), "\n" ) ;
  2211. ok('--password1 MASKED' eq command_line_nopassword(qw{ --password1 secret1}), 'command_line_nopassword --password1');
  2212. ok('--blabla --password1 MASKED --blibli'
  2213. eq command_line_nopassword(qw{ --blabla --password1 secret1 --blibli }), 'command_line_nopassword --password1 --blibli');
  2214. $showpasswords = 1 ;
  2215. ok(q{} eq command_line_nopassword(), 'command_line_nopassword void');
  2216. ok('--blabla' eq command_line_nopassword('--blabla'), 'command_line_nopassword --blabla');
  2217. #myprint( command_line_nopassword((qw{ --password1 secret1 })), "\n" ) ;
  2218. ok('--password1 secret1' eq command_line_nopassword(qw{ --password1 secret1}), 'command_line_nopassword --password1');
  2219. ok('--blabla --password1 secret1 --blibli'
  2220. eq command_line_nopassword(qw{ --blabla --password1 secret1 --blibli }), 'command_line_nopassword --password1 --blibli');
  2221. return ;
  2222. }
  2223. sub ask_for_password {
  2224. my ( $user, $host ) = @_ ;
  2225. myprint( "What's the password for $user" . '@' . "$host? (not visible while you type, then enter RETURN) " ) ;
  2226. Term::ReadKey::ReadMode( 2 ) ;
  2227. my $password = <> ;
  2228. chomp $password ;
  2229. myprint( "\nGot it\n" ) ;
  2230. Term::ReadKey::ReadMode( 0 ) ;
  2231. return $password ;
  2232. }
  2233. sub catch_exit {
  2234. my $mysync = shift ;
  2235. my $signame = shift ;
  2236. if ( $signame ) {
  2237. myprint( "\nGot a signal $signame\n" ) ;
  2238. }
  2239. stats( $mysync ) ;
  2240. myprint( "Ended by a signal\n" ) ;
  2241. exit_clean( $mysync, $EXIT_BY_SIGNAL ) ;
  2242. return ;
  2243. }
  2244. sub catch_reconnect {
  2245. my $mysync = shift ;
  2246. my $signame = shift ;
  2247. myprint( "\nGot a signal $signame\n",
  2248. "Hit 2 ctr-c within 2 seconds to exit the program\n",
  2249. "Hit only 1 ctr-c to reconnect to both imap servers\n",
  2250. ) ;
  2251. if ( here_twice( $mysync ) ) {
  2252. myprint( "Got two signals $signame within $INTERVAL_TO_EXIT seconds. Exiting...\n" ) ;
  2253. catch_exit( $mysync ) ;
  2254. }
  2255. else{
  2256. myprint( "For now only one signal $signame within $INTERVAL_TO_EXIT seconds.\n" ) ;
  2257. }
  2258. if ( ! defined $mysync->{imap1} ) { return ; }
  2259. if ( ! defined $mysync->{imap2} ) { return ; }
  2260. myprint( "Info: reconnecting to host1 imap server\n" ) ;
  2261. $mysync->{imap1}->State( Mail::IMAPClient::Unconnected ) ;
  2262. $mysync->{imap1}->reconnect( ) ;
  2263. myprint( "Info: reconnecting to host2 imap server\n" ) ;
  2264. $mysync->{imap2}->State( Mail::IMAPClient::Unconnected ) ;
  2265. $mysync->{imap2}->reconnect( ) ;
  2266. myprint( "Info: reconnected to both imap servers\n" ) ;
  2267. return ;
  2268. }
  2269. sub here_twice {
  2270. my $mysync = shift ;
  2271. my $now = time ;
  2272. my $previous = $mysync->{lastcatch} || 0 ;
  2273. $mysync->{lastcatch} = $now ;
  2274. if ( $INTERVAL_TO_EXIT >= $now - $previous ) {
  2275. return $TRUE ;
  2276. }else{
  2277. return $FALSE ;
  2278. }
  2279. }
  2280. sub justconnect {
  2281. $imap1 = connect_imap( $host1, $port1, $debugimap1, $ssl1, $tls1, 'Host1', $sync->{h1}->{timeout}, $sync->{h1} ) ;
  2282. myprint( 'Host1 banner: ', $imap1->Banner( ) ) ;
  2283. myprint( 'Host1 capability: ', join(q{ }, $imap1->capability( ) ), "\n" ) ;
  2284. $imap2 = connect_imap( $host2, $port2, $debugimap2, $ssl2, $tls2, 'Host2', $sync->{h2}->{timeout}, $sync->{h2} ) ;
  2285. myprint( 'Host2 banner: ', $imap2->Banner( ) ) ;
  2286. myprint( 'Host2 capability: ', join(q{ }, $imap2->capability( ) ), "\n" ) ;
  2287. $imap1->logout( ) ;
  2288. $imap2->logout( ) ;
  2289. return ;
  2290. }
  2291. sub connect_imap {
  2292. my( $host, $port, $mydebugimap, $ssl, $tls, $Side, $mytimeout, $h ) = @_ ;
  2293. my $imap = Mail::IMAPClient->new() ;
  2294. if ( $ssl ) { set_ssl( $imap, $h ) }
  2295. if ( $tls ) { $imap->Tls( 1 ) }
  2296. $imap->Server( $host ) ;
  2297. $imap->Port( $port ) ;
  2298. $imap->Debug( $mydebugimap ) ;
  2299. $imap->Timeout( $mytimeout ) ;
  2300. $imap->connect( )
  2301. or die_clean( "$Side: Can not open imap connection on [$host]: $@\n" ) ;
  2302. my $banner = $imap->Results()->[0] ;
  2303. $imap->Banner( $banner ) ;
  2304. if ( $imap->Tls( ) ) {
  2305. set_tls( $imap, $h ) ;
  2306. $imap->starttls( )
  2307. or die_clean("$Side: Can not go to tls encryption on [$host]:", $imap->LastError, "\n" ) ;
  2308. myprint( "$Side: Socket successfuly converted to SSL\n" ) ;
  2309. }
  2310. return( $imap ) ;
  2311. }
  2312. sub login_imap {
  2313. my @allargs = @_ ;
  2314. my(
  2315. $host, $port, $user, $domain, $password,
  2316. $mydebugimap, $mytimeout, $fastio,
  2317. $ssl, $tls, $authmech, $authuser, $reconnectretry,
  2318. $proxyauth, $uid, $split, $Side, $h ) = @allargs ;
  2319. my $side = lc $Side ;
  2320. myprint( "$Side: connecting and login on $side [$host] port [$port] with user [$user]\n" ) ;
  2321. my $imap = init_imap( @allargs ) ;
  2322. $imap->connect()
  2323. or die_clean("$Side failure: can not open imap connection on $side [$host] with user [$user]: $@\n") ;
  2324. my $banner = $imap->Results()->[0] ;
  2325. $imap->Banner( $banner ) ;
  2326. myprint( "$Side banner: $banner" ) ;
  2327. if ( $authmech eq 'PREAUTH' ) {
  2328. if ( $imap->IsAuthenticated( ) ) {
  2329. $imap->Socket ;
  2330. myprintf("%s: Assuming PREAUTH for %s\n", $Side, $imap->Server ) ;
  2331. }else{
  2332. die_clean( "$Side failure: error login on $side [$host] with user [$user] auth [PREAUTH]" ) ;
  2333. }
  2334. }
  2335. if ( $imap->Tls( ) ) {
  2336. set_tls( $imap, $h ) ;
  2337. $imap->starttls( )
  2338. or die_clean("$Side failure: Can not go to tls encryption on $side [$host]:", $imap->LastError, "\n" ) ;
  2339. myprint( "$Side: Socket successfuly converted to SSL\n" ) ;
  2340. }
  2341. authenticate_imap( $imap, @allargs ) ;
  2342. myprint( "$Side: success login on [$host] with user [$user] auth [$authmech]\n" ) ;
  2343. return( $imap ) ;
  2344. }
  2345. sub authenticate_imap {
  2346. my($imap,
  2347. $host, $port, $user, $domain, $password,
  2348. $mydebugimap, $mytimeout, $fastio,
  2349. $ssl, $tls, $authmech, $authuser, $reconnectretry,
  2350. $proxyauth, $uid, $split, $Side, $h ) = @_ ;
  2351. check_capability( $imap, $authmech, $Side ) ;
  2352. if ( $proxyauth ) {
  2353. $imap->Authmechanism(q{}) ;
  2354. $imap->User($authuser) ;
  2355. } else {
  2356. $imap->Authmechanism( $authmech ) unless ( $authmech eq 'LOGIN' or $authmech eq 'PREAUTH' ) ;
  2357. $imap->User($user) ;
  2358. }
  2359. $imap->Authcallback(\&xoauth) if ( 'XOAUTH' eq $authmech ) ;
  2360. $imap->Authcallback(\&xoauth2) if ( 'XOAUTH2' eq $authmech ) ;
  2361. $imap->Authcallback(\&plainauth) if ( ( 'PLAIN' eq $authmech ) or ( 'EXTERNAL' eq $authmech ) ) ;
  2362. $imap->Domain($domain) if (defined $domain) ;
  2363. $imap->Authuser($authuser) ;
  2364. $imap->Password($password) ;
  2365. unless ( $authmech eq 'PREAUTH' or $imap->login( ) ) {
  2366. my $info = "$Side failure: Error login on [$host] with user [$user] auth" ;
  2367. my $einfo = $imap->LastError || @{$imap->History}[$LAST] ;
  2368. chomp $einfo ;
  2369. my $error = "$info [$authmech]: $einfo\n" ;
  2370. if ( $authmech eq 'LOGIN' or $imap->IsUnconnected( ) or $authuser ) {
  2371. die_clean( $error ) ;
  2372. }else{
  2373. myprint( $error ) ;
  2374. }
  2375. myprint( "$Side info: trying LOGIN Auth mechanism on [$host] with user [$user]\n" ) ;
  2376. $imap->Authmechanism(q{}) ;
  2377. $imap->login() or
  2378. die_clean("$info [LOGIN]: ", $imap->LastError, "\n") ;
  2379. }
  2380. if ( $proxyauth ) {
  2381. if ( ! $imap->proxyauth( $user ) ) {
  2382. my $info = "$Side failure: Error doing proxyauth as user [$user] on [$host] using proxy-login as [$authuser]" ;
  2383. my $einfo = $imap->LastError || @{$imap->History}[$LAST] ;
  2384. chomp $einfo ;
  2385. die_clean( "$info: $einfo\n" ) ;
  2386. }
  2387. }
  2388. return ;
  2389. }
  2390. sub check_capability {
  2391. my( $imap, $authmech, $Side ) = @_ ;
  2392. if ($imap->has_capability("AUTH=$authmech")
  2393. or $imap->has_capability($authmech)
  2394. ) {
  2395. myprintf("%s: %s says it has CAPABILITY for AUTHENTICATE %s\n",
  2396. $Side, $imap->Server, $authmech);
  2397. }
  2398. else {
  2399. myprintf("%s: %s says it has NO CAPABILITY for AUTHENTICATE %s\n",
  2400. $Side, $imap->Server, $authmech);
  2401. if ($authmech eq 'PLAIN') {
  2402. myprint( "$Side: frequently PLAIN is only supported with SSL, ",
  2403. "try --ssl or --tls options\n" ) ;
  2404. }
  2405. }
  2406. return ;
  2407. }
  2408. sub set_ssl {
  2409. my ( $imap, $h ) = @_ ;
  2410. # SSL_version can be
  2411. # SSLv3 SSLv2 SSLv23 SSLv23:!SSLv2 (last one is the default in IO-Socket-SSL-1.953)
  2412. #
  2413. my $sslargs_hash = $h->{sslargs} ;
  2414. my $sslargs_default = {
  2415. SSL_verify_mode => $DEFAULT_SSL_VERIFY,
  2416. SSL_verifycn_scheme => 'imap',
  2417. } ;
  2418. # initiate with default values
  2419. my %sslargs_mix = %{ $sslargs_default } ;
  2420. # now override with passed values
  2421. @sslargs_mix{ keys %{ $sslargs_hash } } = values %{ $sslargs_hash } ;
  2422. # remove keys with undef values
  2423. foreach my $key ( keys %sslargs_mix ) {
  2424. delete $sslargs_mix{ $key } if ( not defined $sslargs_mix{ $key } ) ;
  2425. }
  2426. # back to an ARRAY
  2427. my @sslargs_mix = %sslargs_mix ;
  2428. #myprint( Data::Dumper->Dump( [ $sslargs_hash, $sslargs_default, \%sslargs_mix, \@sslargs_mix ] ) ) ;
  2429. $imap->Ssl( \@sslargs_mix ) ;
  2430. return ;
  2431. }
  2432. sub set_tls {
  2433. my ( $imap, $h ) = @_ ;
  2434. my $sslargs_hash = $h->{sslargs} ;
  2435. my $sslargs_default = {
  2436. SSL_verify_mode => $DEFAULT_SSL_VERIFY,
  2437. } ;
  2438. # initiate with default values
  2439. my %sslargs_mix = %{ $sslargs_default } ;
  2440. # now override with passed values
  2441. @sslargs_mix{ keys %{ $sslargs_hash } } = values %{ $sslargs_hash } ;
  2442. # remove keys with undef values
  2443. foreach my $key ( keys %sslargs_mix ) {
  2444. delete $sslargs_mix{ $key } if ( not defined $sslargs_mix{ $key } ) ;
  2445. }
  2446. # back to an ARRAY
  2447. my @sslargs_mix = %sslargs_mix ;
  2448. $imap->Starttls( \@sslargs_mix ) ;
  2449. return ;
  2450. }
  2451. sub init_imap {
  2452. my(
  2453. $host, $port, $user, $domain, $password,
  2454. $mydebugimap, $mytimeout, $fastio,
  2455. $ssl, $tls, $authmech, $authuser, $reconnectretry,
  2456. $proxyauth, $uid, $split, $Side, $h ) = @_ ;
  2457. my ( $imap ) ;
  2458. $imap = Mail::IMAPClient->new() ;
  2459. if ( $ssl ) { set_ssl( $imap, $h ) }
  2460. if ( $tls ) { $imap->Tls( 1 ) } # can not do set_tls() here because connect() will directly do a STARTTLS
  2461. $imap->Clear(1);
  2462. $imap->Server($host);
  2463. $imap->Port($port);
  2464. $imap->Fast_io($fastio);
  2465. $imap->Buffer($buffersize || $DEFAULT_BUFFER_SIZE);
  2466. $imap->Uid($uid);
  2467. $imap->Peek(1);
  2468. $imap->Debug($mydebugimap);
  2469. defined $mytimeout and $imap->Timeout( $mytimeout ) ;
  2470. $imap->Reconnectretry( $reconnectretry ) if ( $reconnectretry ) ;
  2471. $imap->Ignoresizeerrors( $allowsizemismatch ) ;
  2472. $split and $imap->Maxcommandlength( $SPLIT_FACTOR * $split ) ;
  2473. return( $imap ) ;
  2474. }
  2475. sub plainauth {
  2476. my $code = shift;
  2477. my $imap = shift;
  2478. my $string = mysprintf("%s\x00%s\x00%s", $imap->User,
  2479. $imap->Authuser, $imap->Password);
  2480. return encode_base64("$string", q{});
  2481. }
  2482. # Changes "use" pragmas to "require".
  2483. # The openssl system call shall be replaced by pure Perl and
  2484. # https://metacpan.org/pod/Crypt::OpenSSL::PKCS12
  2485. # Now the Joaquin Lopez code:
  2486. #
  2487. # Used this as an example: https://gist.github.com/gsainio/6322375
  2488. #
  2489. # And this as a reference: https://developers.google.com/accounts/docs/OAuth2ServiceAccount
  2490. # (note there is an http/rest tab, where the real info is hidden away... went on a witch hunt
  2491. # until I noticed that...)
  2492. #
  2493. # This is targeted at gmail to maintain compatibility after google's oauth1 service is deactivated
  2494. # on May 5th, 2015: https://developers.google.com/gmail/oauth_protocol
  2495. # If there are other oauth2 implementations out there, this would need to be modified to be
  2496. # compatible
  2497. #
  2498. # This is a good guide on setting up the google api/apps side of the equation:
  2499. # http://www.limilabs.com/blog/oauth2-gmail-imap-service-account
  2500. #
  2501. # 2016/05/27: Updated to support oauth/key data in the .json files Google now defaults to
  2502. # when creating gmail service accounts. They're easier to work with since they neither
  2503. # requiring decrypting nor specifying the oauth2 client id separately.
  2504. #
  2505. # If the password arg ends in .json, it will assume this new json method, otherwise it
  2506. # will fallback to the "oauth client id;.p12" format it was previously using.
  2507. sub xoauth2 {
  2508. require JSON::WebToken ;
  2509. require LWP::UserAgent ;
  2510. require HTML::Entities ;
  2511. require JSON ;
  2512. require JSON::WebToken::Crypt::RSA ;
  2513. require Crypt::OpenSSL::RSA ;
  2514. require Encode::Byte ;
  2515. require IO::Socket::SSL ;
  2516. my $code = shift;
  2517. my $imap = shift;
  2518. my ($iss,$key);
  2519. if( $imap->Password =~ /^(.*\.json)$/ ) {
  2520. my $json = JSON->new( ) ;
  2521. my $filename = $1;
  2522. $debug and myprint( "XOAUTH2 json file: $filename\n" ) ;
  2523. open( my $FILE, '<', $filename ) or die_clean( "error [$filename]: $! " ) ;
  2524. my $jsonfile = $json->decode( join q{}, <$FILE> ) ;
  2525. close $FILE ;
  2526. $iss = $jsonfile->{client_id};
  2527. $key = $jsonfile->{private_key};
  2528. $debug and myprint( "Service account: $iss\n");
  2529. $debug and myprint( "Private key:\n$key\n");
  2530. }
  2531. else {
  2532. # Get iss (service account address), keyfile name, and keypassword if necessary
  2533. ( $iss, my $keyfile, my $keypass ) = $imap->Password =~ /([\-\d\w\@\.]+);([a-zA-Z0-9 \_\-\.\/]+);?(.*)?/ ;
  2534. # Assume key password is google default if not provided
  2535. $keypass = 'notasecret' if not $keypass;
  2536. $debug and myprint( "Service account: $iss\nKey file: $keyfile\nKey password: $keypass\n");
  2537. # Get private key from p12 file (would be better in perl...)
  2538. $key = `openssl pkcs12 -in "$keyfile" -nodes -nocerts -passin pass:$keypass -nomacver`;
  2539. $debug and myprint( "Private key:\n$key\n");
  2540. }
  2541. # Create jwt of oauth2 request
  2542. my $time = time ;
  2543. my $jwt = JSON::WebToken->encode( {
  2544. 'iss' => $iss, # service account
  2545. 'scope' => 'https://mail.google.com/',
  2546. 'aud' => 'https://www.googleapis.com/oauth2/v3/token',
  2547. 'exp' => $time + $DEFAULT_EXPIRATION_TIME_OAUTH2_PK12,
  2548. 'iat' => $time,
  2549. 'prn' => $imap->User # user to auth as
  2550. },
  2551. $key, 'RS256', {'typ' => 'JWT'} ); # Crypt::OpenSSL::RSA needed here.
  2552. # Post oauth2 request
  2553. my $ua = LWP::UserAgent->new( ) ;
  2554. $ua->env_proxy( ) ;
  2555. my $response = $ua->post('https://www.googleapis.com/oauth2/v3/token',
  2556. { grant_type => HTML::Entities::encode_entities('urn:ietf:params:oauth:grant-type:jwt-bearer'),
  2557. assertion => $jwt } ) ;
  2558. unless( $response->is_success( ) ) {
  2559. die_clean( $response->code, "\n", $response->content, "\n" ) ;
  2560. }else{
  2561. $debug and myprint( $response->content ) ;
  2562. }
  2563. # access_token in response is what we need
  2564. my $data = JSON::decode_json( $response->content ) ;
  2565. # format as oauth2 auth data
  2566. my $xoauth2_string = encode_base64( 'user=' . $imap->User . "\1auth=Bearer " . $data->{access_token} . "\1\1", q{} ) ;
  2567. $debug and myprint( "XOAUTH2 String: $xoauth2_string\n");
  2568. return($xoauth2_string);
  2569. }
  2570. sub xoauth {
  2571. require URI::Escape ;
  2572. require Data::Uniqid ;
  2573. my $code = shift;
  2574. my $imap = shift;
  2575. # The base information needed to construct the OAUTH authentication
  2576. my $method = 'GET' ;
  2577. my $url = mysprintf( 'https://mail.google.com/mail/b/%s/imap/', $imap->User ) ;
  2578. my $urlparm = mysprintf( 'xoauth_requestor_id=%s', URI::Escape::uri_escape( $imap->User ) ) ;
  2579. # For Google Apps, the consumer key is the primary domain
  2580. # TODO: create a command line argument to define the consumer key
  2581. my @user_parts = split /@/x, $imap->User ;
  2582. $debug and myprint( "XOAUTH: consumer key: $user_parts[1]\n" ) ;
  2583. # All the parameters needed to be signed on the XOAUTH
  2584. my %hash = ();
  2585. $hash { 'xoauth_requestor_id' } = URI::Escape::uri_escape($imap->User);
  2586. $hash { 'oauth_consumer_key' } = $user_parts[1];
  2587. $hash { 'oauth_nonce' } = md5_hex(Data::Uniqid::uniqid(rand(), 1==1));
  2588. $hash { 'oauth_signature_method' } = 'HMAC-SHA1';
  2589. $hash { 'oauth_timestamp' } = time ;
  2590. $hash { 'oauth_version' } = '1.0';
  2591. # Base will hold the string to be signed
  2592. my $base = "$method&" . URI::Escape::uri_escape( $url ) . q{&} ;
  2593. # The parameters must be in dictionary order before signing
  2594. my $baseparms = q{} ;
  2595. foreach my $key ( sort keys %hash ) {
  2596. if ( length( $baseparms ) > 0 ) {
  2597. $baseparms .= q{&} ;
  2598. }
  2599. $baseparms .= "$key=$hash{$key}" ;
  2600. }
  2601. $base .= URI::Escape::uri_escape($baseparms);
  2602. $debug and myprint( "XOAUTH: base request to sign: $base\n" ) ;
  2603. # Sign it with the consumer secret, informed on the command line (password)
  2604. my $digest = hmac_sha1( $base, URI::Escape::uri_escape( $imap->Password ) . q{&} ) ;
  2605. # The parameters signed become a parameter and...
  2606. $hash { 'oauth_signature' } = URI::Escape::uri_escape( substr encode_base64( $digest ), 0, $MINUS_ONE ) ;
  2607. # ... we don't need the requestor_id anymore.
  2608. delete $hash{'xoauth_requestor_id'} ;
  2609. # Create the final authentication string
  2610. my $string = $method . q{ } . $url . q{?} . $urlparm .q{ } ;
  2611. # All the parameters must be sorted
  2612. $baseparms = q{};
  2613. foreach my $key (sort keys %hash) {
  2614. if(length($baseparms)>0) {
  2615. $baseparms .= q{,} ;
  2616. }
  2617. $baseparms .= "$key=\"$hash{$key}\"";
  2618. }
  2619. $string .= $baseparms;
  2620. $debug and myprint( "XOAUTH: authentication string: $string\n" ) ;
  2621. # It must be base64 encoded
  2622. return encode_base64("$string", q{});
  2623. }
  2624. sub server_banner {
  2625. my $imap = shift;
  2626. my $banner = $imap->Banner() || "No banner\n";
  2627. return $banner;
  2628. }
  2629. sub banner_imapsync {
  2630. my @argv = @_ ;
  2631. my $banner_imapsync = join q{},
  2632. q{$RCSfile: kopano-migration-imap,v $ },
  2633. q{$Revision: 1.727 $ },
  2634. q{$Date: 2016/08/19 10:30:36 $ },
  2635. "Command line used:\n",
  2636. "$0 ", command_line_nopassword( @argv ), "\n" ;
  2637. return( $banner_imapsync ) ;
  2638. }
  2639. sub is_valid_directory {
  2640. my $dir = shift;
  2641. # all good => return ok.
  2642. return( 1 ) if ( -d $dir and -r _ and -w _ ) ;
  2643. # exist but bad
  2644. if ( -e $dir and not -d _ ) {
  2645. myprint( "Error: $dir exists but is not a directory\n" ) ;
  2646. return( 0 ) ;
  2647. }
  2648. if ( -e $dir and not -w _ ) {
  2649. my $sb = stat $dir ;
  2650. myprintf( "Error: directory %s is not writable for user %s, permissions are %04o and owner is %s ( uid %s )\n",
  2651. $dir, getpwuid_any_os( $EFFECTIVE_USER_ID ), ($sb->mode & oct($PERMISSION_FILTER) ), getpwuid_any_os( $sb->uid ), $sb->uid( ) ) ;
  2652. return( 0 ) ;
  2653. }
  2654. # Trying to create it
  2655. myprint( "Creating directory $dir\n" ) ;
  2656. eval { mkpath( $dir ) } ;
  2657. myprint( "$@" ) if ( $@ ) ;
  2658. return( 1 ) if ( -d $dir and -r _ and -w _ ) ;
  2659. return( 0 ) ;
  2660. }
  2661. sub tests_is_valid_directory {
  2662. Readonly my $NB_UNIX_tests_is_valid_directory => 4 ;
  2663. SKIP: {
  2664. skip( 'Tests only for Unix', $NB_UNIX_tests_is_valid_directory ) if ( 'MSWin32' eq $OSNAME ) ;
  2665. ok( 1 == is_valid_directory( '.'), 'is_valid_directory: . good' ) ;
  2666. ok( 1 == is_valid_directory( './tmp/tests/valid/sub'), 'is_valid_directory: ./tmp/tests/valid/sub good' ) ;
  2667. diag( 'Error / not writable is on purpose' ) ;
  2668. ok( 0 == is_valid_directory( '/'), 'is_valid_directory: / bad' ) ;
  2669. diag( 'Error permission denied on /noway is on purpose' ) ;
  2670. ok( 0 == is_valid_directory( '/noway'), 'is_valid_directory: /noway bad' ) ;
  2671. }
  2672. return ;
  2673. }
  2674. sub write_pidfile {
  2675. my $pid_filename = shift ;
  2676. my $lock = shift ;
  2677. myprint( "PID file is $pid_filename ( to change it use --pidfile filepath ; to avoid it use --pidfile \"\" )\n" ) ;
  2678. if ( -e $pid_filename and $lock ) {
  2679. myprint( "$pid_filename already exists, another kopano-migration-imap may be curently running. Aborting kopano-migration-imap.\n" ) ;
  2680. exit $EXIT_PID_FILE_ALREADY_EXIST ;
  2681. }
  2682. if ( -e $pid_filename ) {
  2683. myprint( "$pid_filename already exists, overwriting it ( use --pidfilelocking to avoid concurrent runs )\n" ) ;
  2684. }
  2685. open my $FILE_HANDLE, '>', $pid_filename
  2686. or do {
  2687. myprint( "Could not open $pid_filename for writing. Check permissions or disk space." ) ;
  2688. return ;
  2689. } ;
  2690. myprint( "Wrinting my PID $PROCESS_ID in $pid_filename\n" ) ;
  2691. print $FILE_HANDLE $PROCESS_ID ;
  2692. close $FILE_HANDLE ;
  2693. return( $PROCESS_ID ) ;
  2694. }
  2695. sub remove_tmp_files {
  2696. my $mysync = shift ;
  2697. unlink $mysync->{pidfile} ;
  2698. return ;
  2699. }
  2700. sub exit_clean {
  2701. my $mysync = shift ;
  2702. my $status = shift ;
  2703. $status = defined $status ? $status : $EXIT_UNKNOWN ;
  2704. remove_tmp_files( $mysync ) ;
  2705. myprint( "Exiting with return value $status\n" ) ;
  2706. if ( $mysync->{log} ) {
  2707. myprint( "Log file is $mysync->{logfile} ( to change it, use --logfile filepath ; or use --nolog to turn off logging )\n" ) ;
  2708. close $mysync->{logfile_handle} ;
  2709. }
  2710. exit $status ;
  2711. }
  2712. sub die_clean {
  2713. my @messages = @_ ;
  2714. remove_tmp_files( $sync ) ;
  2715. die @messages ;
  2716. }
  2717. sub missing_option {
  2718. my ( $option ) = @_ ;
  2719. die_clean( "$option option is mandatory, for help run $0 --help\n" ) ;
  2720. return ;
  2721. }
  2722. sub fix_Inbox_INBOX_mapping {
  2723. my( $h1_all, $h2_all ) = @_ ;
  2724. my $regex = q{} ;
  2725. SWITCH: {
  2726. if ( exists $h1_all->{INBOX} and exists $h2_all->{INBOX} ) { $regex = q{} ; last SWITCH ; } ;
  2727. if ( exists $h1_all->{Inbox} and exists $h2_all->{Inbox} ) { $regex = q{} ; last SWITCH ; } ;
  2728. if ( exists $h1_all->{INBOX} and exists $h2_all->{Inbox} ) { $regex = q{s/^INBOX$/Inbox/x} ; last SWITCH ; } ;
  2729. if ( exists $h1_all->{Inbox} and exists $h2_all->{INBOX} ) { $regex = q{s/^Inbox$/INBOX/x} ; last SWITCH ; } ;
  2730. } ;
  2731. return( $regex ) ;
  2732. }
  2733. sub tests_fix_Inbox_INBOX_mapping {
  2734. my( $h1_all, $h2_all ) ;
  2735. $h1_all = { 'INBOX' => q{} } ;
  2736. $h2_all = { 'INBOX' => q{} } ;
  2737. ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX INBOX' ) ;
  2738. $h1_all = { 'Inbox' => q{} } ;
  2739. $h2_all = { 'Inbox' => q{} } ;
  2740. ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: Inbox Inbox' ) ;
  2741. $h1_all = { 'INBOX' => q{} } ;
  2742. $h2_all = { 'Inbox' => q{} } ;
  2743. ok( q{s/^INBOX$/Inbox/x} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX Inbox' ) ;
  2744. $h1_all = { 'Inbox' => q{} } ;
  2745. $h2_all = { 'INBOX' => q{} } ;
  2746. ok( q{s/^Inbox$/INBOX/x} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: Inbox INBOX' ) ;
  2747. $h1_all = { 'INBOX' => q{} } ;
  2748. $h2_all = { 'rrrrr' => q{} } ;
  2749. ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX rrrrrr' ) ;
  2750. $h1_all = { 'rrrrr' => q{} } ;
  2751. $h2_all = { 'Inbox' => q{} } ;
  2752. ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: rrrrr Inbox' ) ;
  2753. return ;
  2754. }
  2755. sub jux_utf8_list {
  2756. my @s_inp = @_ ;
  2757. my $s_out = q{} ;
  2758. foreach my $s ( @s_inp ) {
  2759. $s_out .= jux_utf8( $s ) . "\n" ;
  2760. }
  2761. return( $s_out ) ;
  2762. }
  2763. sub tests_jux_utf8_list {
  2764. ok( q{} eq jux_utf8_list( ), 'jux_utf8_list: void' ) ;
  2765. ok( "[]\n" eq jux_utf8_list( q{} ), 'jux_utf8_list: empty string' ) ;
  2766. ok( "[INBOX]\n" eq jux_utf8_list( 'INBOX' ), 'jux_utf8_list: INBOX' ) ;
  2767. ok( "[&ANY-] = [Ö]\n" eq jux_utf8_list( '&ANY-' ), 'jux_utf8_list: &ANY-' ) ;
  2768. return( 0 ) ;
  2769. }
  2770. sub jux_utf8 {
  2771. # juxtapose utf8 at the right if different
  2772. my ( $s_utf7 ) = shift ;
  2773. my ( $s_utf8 ) = imap_utf7_decode( $s_utf7 ) ;
  2774. if ( $s_utf7 eq $s_utf8 ) {
  2775. #myprint( "[$s_utf7]\n" ) ;
  2776. return( "[$s_utf7]" ) ;
  2777. }else{
  2778. #myprint( "[$s_utf7] = [$s_utf8]\n" ) ;
  2779. return( "[$s_utf7] = [$s_utf8]" ) ;
  2780. }
  2781. }
  2782. # editing utf8 can be tricky without an utf8 editor
  2783. sub tests_jux_utf8 {
  2784. ok( '[INBOX]' eq jux_utf8( 'INBOX'), 'jux_utf8: INBOX => [INBOX]' ) ;
  2785. ok( '[&ZTZO9nux-] = [收件箱]' eq jux_utf8( '&ZTZO9nux-'), 'jux_utf8: => [&ZTZO9nux-] = [收件箱]' ) ;
  2786. ok( '[&ANY-] = [Ö]' eq jux_utf8( '&ANY-'), 'jux_utf8: &ANY- => [&ANY-] = [Ö]' ) ;
  2787. ok( '[]' eq jux_utf8( q{} ), 'jux_utf8: void => []' ) ;
  2788. ok( '[+BD8EQAQ1BDQEOwQ+BDM-] = [предлог]' eq jux_utf8( '+BD8EQAQ1BDQEOwQ+BDM-' ), 'jux_utf8: => [+BD8EQAQ1BDQEOwQ+BDM-] = [предлог]' ) ;
  2789. ok( '[&BB8EQAQ+BDUEOgRC-] = [Проект]' eq jux_utf8( '&BB8EQAQ+BDUEOgRC-' ), 'jux_utf8: => [&BB8EQAQ+BDUEOgRC-] = [Проект]' ) ;
  2790. return( 0 ) ;
  2791. }
  2792. # Copied from http://cpansearch.perl.org/src/FABPOT/Unicode-IMAPUtf7-2.01/lib/Unicode/IMAPUtf7.pm
  2793. # and then fixed with
  2794. # https://rt.cpan.org/Public/Bug/Display.html?id=11172
  2795. sub imap_utf7_decode {
  2796. my ( $s ) = shift ;
  2797. # Algorithm
  2798. # On remplace , par / dans les BASE 64 (, entre & et -)
  2799. # On remplace les &, non suivi d'un - par +
  2800. # On remplace les &- par &
  2801. $s =~ s/&([^,&\-]*),([^,\-&]*)\-/&$1\/$2\-/g ;
  2802. $s =~ s/&(?!\-)/\+/g ;
  2803. $s =~ s/&\-/&/g ;
  2804. return( Unicode::String::utf7( $s )->utf8 ) ;
  2805. }
  2806. sub imap_utf7_encode {
  2807. my ( $s ) = @_ ;
  2808. $s = Unicode::String::utf8( $s )->utf7 ;
  2809. $s =~ s/\+([^\/&\-]*)\/([^\/\-&]*)\-/\+$1,$2\-/g ;
  2810. $s =~ s/&/&\-/g ;
  2811. $s =~ s/\+([^+\-]+)?\-/&$1\-/g ;
  2812. return( $s ) ;
  2813. }
  2814. sub select_folder {
  2815. my ( $imap, $folder, $hostside ) = @_ ;
  2816. if ( ! $imap->select( $folder ) ) {
  2817. my $error = join q{},
  2818. "$hostside folder $folder: Could not select: ",
  2819. $imap->LastError, "\n" ;
  2820. errors_incr( $sync, $error ) ;
  2821. return( 0 ) ;
  2822. }else{
  2823. # ok select succeeded
  2824. return( 1 ) ;
  2825. }
  2826. }
  2827. sub examine_folder {
  2828. my ( $imap, $folder, $hostside ) = @_ ;
  2829. if ( ! $imap->examine( $folder ) ) {
  2830. my $error = join q{},
  2831. "$hostside folder $folder: Could not examine: ",
  2832. $imap->LastError, "\n" ;
  2833. errors_incr( $sync, $error ) ;
  2834. return( 0 ) ;
  2835. }else{
  2836. # ok select succeeded
  2837. return( 1 ) ;
  2838. }
  2839. }
  2840. sub count_from_select {
  2841. my @lines = @_ ;
  2842. my $count ;
  2843. foreach my $line ( @lines ) {
  2844. #myprint( "line = [$line]\n" ) ;
  2845. if ( $line =~ m/^\*\s+(\d+)\s+EXISTS/ ) {
  2846. $count = $1 ;
  2847. return( $count ) ;
  2848. }
  2849. }
  2850. return( undef ) ;
  2851. }
  2852. sub create_folder_old {
  2853. my( $imap, $h2_fold, $h1_fold ) = @_ ;
  2854. myprint( "Creating (old way) folder [$h2_fold] on host2\n" ) ;
  2855. if ( ( 'INBOX' eq uc $h2_fold )
  2856. and ( $imap->exists( $h2_fold ) ) ) {
  2857. myprint( "Folder [$h2_fold] already exists\n" ) ;
  2858. return( 1 ) ;
  2859. }
  2860. if ( ! $dry ){
  2861. if ( ! $imap->create( $h2_fold ) ) {
  2862. my $error = join q{},
  2863. "Could not create folder [$h2_fold] from [$h1_fold]: ",
  2864. $imap->LastError( ), "\n" ;
  2865. errors_incr( $sync, $error ) ;
  2866. # success if folder exists ("already exists" error)
  2867. return( 1 ) if $imap->exists( $h2_fold ) ;
  2868. # failure since create failed
  2869. return( 0 ) ;
  2870. }else{
  2871. #create succeeded
  2872. myprint( "Created ( the old way ) folder [$h2_fold] on host2\n" ) ;
  2873. return( 1 ) ;
  2874. }
  2875. }else{
  2876. # dry mode, no folder so many imap will fail, assuming failure
  2877. myprint( "Created ( the old way ) folder [$h2_fold] on host2 $dry_message\n" ) ;
  2878. return( 0 ) ;
  2879. }
  2880. }
  2881. sub create_folder {
  2882. my( $imap2 , $h2_fold , $h1_fold ) = @_ ;
  2883. my( @parts , $parent ) ;
  2884. if ( $imap2->IsUnconnected( ) ) {
  2885. myprint( "Host2: Unconnected state\n" ) ;
  2886. return( 0 ) ;
  2887. }
  2888. if ( $create_folder_old ) {
  2889. return( create_folder_old( $imap2 , $h2_fold , $h1_fold ) ) ;
  2890. }
  2891. myprint( "Creating folder [$h2_fold] on host2\n" ) ;
  2892. if ( ( 'INBOX' eq uc $h2_fold )
  2893. and ( $imap2->exists( $h2_fold ) ) ) {
  2894. myprint( "Folder [$h2_fold] already exists\n" ) ;
  2895. return( 1 ) ;
  2896. }
  2897. if ( $mixfolders and $imap2->exists( $h2_fold ) ) {
  2898. myprint( "Folder [$h2_fold] already exists (--nomixfolders is not set)\n" ) ;
  2899. return( 1 ) ;
  2900. }
  2901. if ( ( not $mixfolders ) and ( $imap2->exists( $h2_fold ) ) ) {
  2902. myprint( "Folder [$h2_fold] already exists and --nomixfolders is set\n" ) ;
  2903. return( 0 ) ;
  2904. }
  2905. @parts = split /\Q$h2_sep\E/, $h2_fold ;
  2906. pop @parts ;
  2907. $parent = join $h2_sep, @parts ;
  2908. $parent =~ s/^\s+|\s+$//g ;
  2909. if ( ( $parent ne q{} ) and ( ! $imap2->exists( $parent ) ) ) {
  2910. create_folder( $imap2 , $parent , $h1_fold ) ;
  2911. }
  2912. if ( ! $dry ) {
  2913. if ( ! $imap2->create( $h2_fold ) ) {
  2914. my $error = join q{},
  2915. "Could not create folder [$h2_fold] from [$h1_fold]: " ,
  2916. $imap2->LastError( ), "\n" ;
  2917. errors_incr( $sync, $error ) ;
  2918. # success if folder exists ("already exists" error)
  2919. return( 1 ) if $imap2->exists( $h2_fold ) ;
  2920. # failure since create failed
  2921. return( 0 ) ;
  2922. }else{
  2923. #create succeeded
  2924. myprint( "Created folder [$h2_fold] on host2\n" ) ;
  2925. return( 1 ) ;
  2926. }
  2927. }else{
  2928. # dry mode, no folder so many imap will fail, assuming failure
  2929. myprint( "Created folder [$h2_fold] on host2 $dry_message\n" ) ;
  2930. if ( ! $justfolders ) {
  2931. myprint( "Since --dry mode is on and folder [$h2_fold] on host2 does not exist yet, syncing messages will not be simulated.\n"
  2932. . "To simulate message syncing, use --justfolders without --dry to first create the missing folders then rerun the --dry sync.\n" ) ;
  2933. }
  2934. return( 0 ) ;
  2935. }
  2936. }
  2937. sub tests_folder_routines {
  2938. ok( !is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 1' );
  2939. ok( add_to_requested_folders('folder_foo'), 'add_to_requested_folders folder_foo' );
  2940. ok( is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 2' );
  2941. ok( !is_requested_folder('folder_NO_EXIST'), 'is_requested_folder folder_NO_EXIST' );
  2942. ok( !remove_from_requested_folders('folder_foo'), 'removed folder_foo' );
  2943. ok( !is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 3' );
  2944. my @f ;
  2945. ok( @f = add_to_requested_folders('folder_bar', 'folder_toto'), "add result: @f" );
  2946. ok( is_requested_folder('folder_bar'), 'is_requested_folder 4' );
  2947. ok( is_requested_folder('folder_toto'), 'is_requested_folder 5' );
  2948. ok( remove_from_requested_folders('folder_toto'), 'remove_from_requested_folders: ' );
  2949. ok( !is_requested_folder('folder_toto'), 'is_requested_folder 6' );
  2950. ok( !remove_from_requested_folders('folder_bar'), 'remove_from_requested_folders: empty' ) ;
  2951. ok( 0 == compare_lists( [ sort_requested_folders( ) ], [] ), 'sort_requested_folders: all empty' ) ;
  2952. ok( add_to_requested_folders('M_55'), 'add_to_requested_folders M_55' );
  2953. ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'M_55' ] ), 'sort_requested_folders: middle' ) ;
  2954. @folderfirst = ( 'Z_11' ) ;
  2955. ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'M_55' ] ), 'sort_requested_folders: first+middle' ) ;
  2956. @folderlast = ( 'A_99' ) ;
  2957. ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'M_55', 'A_99' ] ), 'sort_requested_folders: first+middle+last 1' ) ;
  2958. ok( add_to_requested_folders('M_55', 'M_44',), 'add_to_requested_folders M_55 M_44' );
  2959. ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'M_44', 'M_55', 'A_99' ] ), 'sort_requested_folders: first+middle+last 2' ) ;
  2960. @folderfirst = qw( Z_22 Z_11 ) ;
  2961. @folderlast = qw( A_99 A_88 ) ;
  2962. 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' ) ;
  2963. return ;
  2964. }
  2965. sub sort_requested_folders {
  2966. my @requested_folders_sorted = () ;
  2967. foreach my $folder ( @folderfirst ) {
  2968. remove_from_requested_folders( $folder ) ;
  2969. }
  2970. foreach my $folder ( @folderlast ) {
  2971. remove_from_requested_folders( $folder ) ;
  2972. }
  2973. my @middle = sort keys %requested_folder ;
  2974. @requested_folders_sorted = ( @folderfirst, @middle, @folderlast ) ;
  2975. return( @requested_folders_sorted ) ;
  2976. }
  2977. sub is_requested_folder {
  2978. my ( $folder ) = @_;
  2979. return( defined $requested_folder{ $folder } ) ;
  2980. }
  2981. sub add_to_requested_folders {
  2982. my @wanted_folders = @_ ;
  2983. foreach my $folder ( @wanted_folders ) {
  2984. ++$requested_folder{ $folder } ;
  2985. }
  2986. return( keys %requested_folder ) ;
  2987. }
  2988. sub remove_from_requested_folders {
  2989. my @wanted_folders = @_ ;
  2990. foreach my $folder ( @wanted_folders ) {
  2991. delete $requested_folder{ $folder } ;
  2992. }
  2993. return( keys %requested_folder ) ;
  2994. }
  2995. sub compare_lists {
  2996. my ($list_1_ref, $list_2_ref) = @_;
  2997. return($MINUS_ONE) if ((not defined $list_1_ref) and defined $list_2_ref);
  2998. return(0) if ((not defined $list_1_ref) and not defined $list_2_ref); # end if no list
  2999. return(1) if (not defined $list_2_ref); # end if only one list
  3000. if (not ref $list_1_ref ) {$list_1_ref = [$list_1_ref]};
  3001. if (not ref $list_2_ref ) {$list_2_ref = [$list_2_ref]};
  3002. my $last_used_indice = $MINUS_ONE;
  3003. ELEMENT:
  3004. foreach my $indice ( 0 .. $#{ $list_1_ref } ) {
  3005. $last_used_indice = $indice ;
  3006. # End of list_2
  3007. return 1 if ($indice > $#{ $list_2_ref } ) ;
  3008. my $element_list_1 = $list_1_ref->[$indice] ;
  3009. my $element_list_2 = $list_2_ref->[$indice] ;
  3010. my $balance = $element_list_1 cmp $element_list_2 ;
  3011. next ELEMENT if ($balance == 0) ;
  3012. return $balance ;
  3013. }
  3014. # each element equal until last indice of list_1
  3015. return $MINUS_ONE if ($last_used_indice < $#{ $list_2_ref } ) ;
  3016. # same size, each element equal
  3017. return 0 ;
  3018. }
  3019. sub tests_compare_lists {
  3020. my $empty_list_ref = [];
  3021. ok( 0 == compare_lists() , 'compare_lists, no args');
  3022. ok( 0 == compare_lists(undef) , 'compare_lists, undef = nothing');
  3023. ok( 0 == compare_lists(undef, undef) , 'compare_lists, undef = undef');
  3024. ok($MINUS_ONE == compare_lists(undef , []) , 'compare_lists, undef < []');
  3025. ok($MINUS_ONE == compare_lists(undef , [1]) , 'compare_lists, undef < [1]');
  3026. ok($MINUS_ONE == compare_lists(undef , [0]) , 'compare_lists, undef < [0]');
  3027. ok(+1 == compare_lists([]) , 'compare_lists, [] > nothing');
  3028. ok(+1 == compare_lists([], undef) , 'compare_lists, [] > undef');
  3029. ok( 0 == compare_lists([] , []) , 'compare_lists, [] = []');
  3030. ok($MINUS_ONE == compare_lists([] , [1]) , 'compare_lists, [] < [1]');
  3031. ok(+1 == compare_lists([1] , []) , 'compare_lists, [1] > []');
  3032. ok( 0 == compare_lists([1], 1 ) , 'compare_lists, [1] = 1 ') ;
  3033. ok( 0 == compare_lists( 1 , [1]) , 'compare_lists, 1 = [1]') ;
  3034. ok( 0 == compare_lists( 1 , 1 ) , 'compare_lists, 1 = 1 ') ;
  3035. ok($MINUS_ONE == compare_lists( 0 , 1 ) , 'compare_lists, 0 < 1 ') ;
  3036. ok($MINUS_ONE == compare_lists($MINUS_ONE , 0 ) , 'compare_lists, -1 < 0 ') ;
  3037. ok($MINUS_ONE == compare_lists( 1 , 2 ) , 'compare_lists, 1 < 2 ') ;
  3038. ok(+1 == compare_lists( 2 , 1 ) , 'compare_lists, 2 > 1 ') ;
  3039. ok( 0 == compare_lists([1,2], [1,2]) , 'compare_lists, [1,2] = [1,2]' ) ;
  3040. ok($MINUS_ONE == compare_lists([1], [1,2]) , 'compare_lists, [1] < [1,2]' ) ;
  3041. ok(+1 == compare_lists([2], [1,2]) , 'compare_lists, [2] > [1,2]' ) ;
  3042. ok($MINUS_ONE == compare_lists([1], [1,1]) , 'compare_lists, [1] < [1,1]' ) ;
  3043. ok(+1 == compare_lists([1, 1], [1]) , 'compare_lists, [1, 1] > [1]' ) ;
  3044. ok( 0 == compare_lists([1 .. $NUMBER_20_000] , [1 .. $NUMBER_20_000])
  3045. , 'compare_lists, [1..20_000] = [1..20_000]' ) ;
  3046. ok($MINUS_ONE == compare_lists([1], [2]) , 'compare_lists, [1] < [2]') ;
  3047. ok( 0 == compare_lists([2], [2]) , 'compare_lists, [0] = [2]') ;
  3048. ok(+1 == compare_lists([2], [1]) , 'compare_lists, [2] > [1]') ;
  3049. ok($MINUS_ONE == compare_lists(['a'], ['b']) , 'compare_lists, ["a"] < ["b"]') ;
  3050. ok( 0 == compare_lists(['a'], ['a']) , 'compare_lists, ["a"] = ["a"]') ;
  3051. ok( 0 == compare_lists(['ab'], ['ab']) , 'compare_lists, ["ab"] = ["ab"]') ;
  3052. ok(+1 == compare_lists(['b'], ['a']) , 'compare_lists, ["b"] > ["a"]') ;
  3053. ok($MINUS_ONE == compare_lists(['a'], ['aa']) , 'compare_lists, ["a"] < ["aa"]') ;
  3054. ok($MINUS_ONE == compare_lists(['a'], ['a', 'a']), 'compare_lists, ["a"] < ["a", "a"]') ;
  3055. ok( 0 == compare_lists([split q{ }, 'a b' ], ['a', 'b']), 'compare_lists, split') ;
  3056. ok( 0 == compare_lists([sort split q{ }, 'b a' ], ['a', 'b']), 'compare_lists, sort split') ;
  3057. return ;
  3058. }
  3059. sub guess_prefix {
  3060. my @foldernames = @_ ;
  3061. return( undef ) unless ( @foldernames ) ;
  3062. my $prefix_guessed = q{} ;
  3063. foreach my $folder ( @foldernames ) {
  3064. next if ( $folder =~ m{^INBOX$}i ) ; # no guessing from INBOX
  3065. if ( $folder !~ m{^INBOX}i ) {
  3066. $prefix_guessed = q{} ; # prefix empty guessed
  3067. last ;
  3068. }
  3069. if ( $folder =~ m{^(INBOX(?:\.|\/))}i ) {
  3070. $prefix_guessed = $1 ; # prefix Inbox/ or INBOX. guessed
  3071. }
  3072. }
  3073. return( $prefix_guessed ) ;
  3074. }
  3075. sub tests_guess_prefix {
  3076. ok( not( defined guess_prefix( ) ), 'guess_prefix: no args' ) ;
  3077. ok( q{} eq guess_prefix( 'INBOX' ), 'guess_prefix: INBOX alone' ) ;
  3078. ok( q{} eq guess_prefix( 'Inbox' ), 'guess_prefix: Inbox alone' ) ;
  3079. ok( q{} eq guess_prefix( 'INBOX' ), 'guess_prefix: INBOX alone' ) ;
  3080. ok( 'INBOX/' eq guess_prefix( 'INBOX', 'INBOX/Junk' ), 'guess_prefix: INBOX INBOX/Junk' ) ;
  3081. ok( 'INBOX.' eq guess_prefix( 'INBOX', 'INBOX.Junk' ), 'guess_prefix: INBOX INBOX.Junk' ) ;
  3082. ok( 'Inbox/' eq guess_prefix( 'Inbox', 'Inbox/Junk' ), 'guess_prefix: Inbox Inbox/Junk' ) ;
  3083. ok( 'Inbox.' eq guess_prefix( 'Inbox', 'Inbox.Junk' ), 'guess_prefix: Inbox Inbox.Junk' ) ;
  3084. ok( 'INBOX/' eq guess_prefix( 'INBOX', 'INBOX/Junk', 'INBOX/rrr' ), 'guess_prefix: INBOX INBOX/Junk INBOX/rrr' ) ;
  3085. ok( q{} eq guess_prefix( 'INBOX', 'INBOX/Junk', 'INBOX/rrr', 'zzz' ), 'guess_prefix: INBOX INBOX/Junk INBOX/rrr zzz' ) ;
  3086. ok( q{} eq guess_prefix( 'INBOX', 'Junk' ), 'guess_prefix: INBOX Junk' ) ;
  3087. ok( q{} eq guess_prefix( 'INBOX', 'Junk' ), 'guess_prefix: INBOX Junk' ) ;
  3088. return ;
  3089. }
  3090. sub get_prefix {
  3091. my( $imap, $prefix_in, $prefix_opt, $Side, $folders_ref ) = @_ ;
  3092. my( $prefix_out, $prefix_guessed ) ;
  3093. ( $debug or $sync->{debugfolders} ) and myprint( "$Side: Getting prefix\n" ) ;
  3094. $prefix_guessed = guess_prefix( @{ $folders_ref } ) ;
  3095. myprint( "$Side: guessing prefix from folder listing: [$prefix_guessed]\n" ) ;
  3096. ( $debug or $sync->{debugfolders} ) and myprint( "$Side: Calling namespace capability\n" ) ;
  3097. if ( $imap->has_capability( 'namespace' ) ) {
  3098. my $r_namespace = $imap->namespace( ) ;
  3099. $prefix_out = $r_namespace->[0][0][0] ;
  3100. myprint( "$Side: prefix given by NAMESPACE: [$prefix_out]\n" ) ;
  3101. if ( defined $prefix_in ) {
  3102. myprint( "$Side: but using [$prefix_in] given by $prefix_opt\n" ) ;
  3103. $prefix_out = $prefix_in ;
  3104. return( $prefix_out ) ;
  3105. }else{
  3106. # all good
  3107. return( $prefix_out ) ;
  3108. }
  3109. }
  3110. else{
  3111. if ( defined $prefix_in ) {
  3112. myprint( "$Side: using [$prefix_in] given by $prefix_opt\n" ) ;
  3113. $prefix_out = $prefix_in ;
  3114. return( $prefix_out ) ;
  3115. }else{
  3116. myprint(
  3117. "$Side: No NAMESPACE capability so using guessed prefix [$prefix_guessed]\n",
  3118. help_to_guess_prefix( $imap, $prefix_opt ) ) ;
  3119. return( $prefix_guessed ) ;
  3120. }
  3121. }
  3122. return ;
  3123. }
  3124. sub guess_separator {
  3125. my @foldernames = @_ ;
  3126. #return( undef ) unless ( @foldernames ) ;
  3127. my $sep_guessed ;
  3128. my %counter ;
  3129. foreach my $folder ( @foldernames ) {
  3130. $counter{'/'}++ while ( $folder =~ m{/}g ) ; # count /
  3131. $counter{'.'}++ while ( $folder =~ m{\.}g ) ; # count .
  3132. $counter{'\\\\'}++ while ( $folder =~ m{(\\){2}}g ) ; # count \\
  3133. }
  3134. my @race_sorted = sort { $counter{ $b } <=> $counter{ $a } } keys %counter ;
  3135. #myprint( "@race_sorted\n" ) ;
  3136. $sep_guessed = shift @race_sorted || $LAST_RESSORT_SEPARATOR ; # / when nothing found.
  3137. return( $sep_guessed ) ;
  3138. }
  3139. sub tests_guess_separator {
  3140. ok( '/' eq guess_separator( ), 'guess_separator: no args' ) ;
  3141. ok( '/' eq guess_separator( 'abcd' ), 'guess_separator: abcd' ) ;
  3142. ok( '/' eq guess_separator( 'a/b/c.d' ), 'guess_separator: a/b/c.d' ) ;
  3143. ok( '.' eq guess_separator( 'a.b/c.d' ), 'guess_separator: a.b/c.d' ) ;
  3144. ok( '\\\\' eq guess_separator( 'a\\\\b\\\\c.c\\\\d/e/f' ), 'guess_separator: a\\\\b\\\\c.c\\\\d/e/f' ) ;
  3145. return ;
  3146. }
  3147. sub get_separator {
  3148. my( $imap, $sep_in, $sep_opt, $Side, $folders_ref ) = @_ ;
  3149. my( $sep_out, $sep_guessed ) ;
  3150. ( $debug or $sync->{debugfolders} ) and myprint( "$Side: Getting separator\n" ) ;
  3151. $sep_guessed = guess_separator( @{ $folders_ref } ) ;
  3152. myprint( "$Side: guessing separator from folder listing: [$sep_guessed]\n" ) ;
  3153. ( $debug or $sync->{debugfolders} ) and myprint( "$Side: calling namespace capability\n" ) ;
  3154. if ( $imap->has_capability( 'namespace' ) ) {
  3155. $sep_out = $imap->separator( ) ;
  3156. if ( defined $sep_out ) {
  3157. myprint( "$Side: separator given by NAMESPACE: [$sep_out]\n" ) ;
  3158. if ( defined $sep_in ) {
  3159. myprint( "$Side: but using [$sep_in] given by $sep_opt\n" ) ;
  3160. $sep_out = $sep_in ;
  3161. return( $sep_out ) ;
  3162. }else{
  3163. return( $sep_out ) ;
  3164. }
  3165. }else{
  3166. if ( defined $sep_in ) {
  3167. myprint( "$Side: NAMESPACE request failed but using [$sep_in] given by $sep_opt\n" ) ;
  3168. $sep_out = $sep_in ;
  3169. return( $sep_out ) ;
  3170. }else{
  3171. myprint(
  3172. "$Side: NAMESPACE request failed so using guessed separator [$sep_guessed]\n",
  3173. help_to_guess_sep( $imap, $sep_opt ) ) ;
  3174. return( $sep_guessed ) ;
  3175. }
  3176. }
  3177. }
  3178. else{
  3179. if ( defined $sep_in ) {
  3180. myprint( "$Side: No NAMESPACE capability but using [$sep_in] given by $sep_opt\n" ) ;
  3181. $sep_out = $sep_in ;
  3182. return( $sep_out ) ;
  3183. }else{
  3184. myprint(
  3185. "$Side: No NAMESPACE capability, so using guessed separator [$sep_guessed]\n",
  3186. help_to_guess_sep( $imap, $sep_opt ) ) ;
  3187. return( $sep_guessed ) ;
  3188. }
  3189. }
  3190. return ;
  3191. }
  3192. sub help_to_guess_sep {
  3193. my( $imap, $sep_opt ) = @_ ;
  3194. my $help_to_guess_sep = "You can set the separator character with the $sep_opt option,\n"
  3195. . "the complete listing of folders may help you to find it\n"
  3196. . folders_list_to_help( $imap ) ;
  3197. return( $help_to_guess_sep ) ;
  3198. }
  3199. sub help_to_guess_prefix {
  3200. my( $imap, $prefix_opt ) = @_ ;
  3201. my $help_to_guess_prefix = "You can set the prefix namespace with the $prefix_opt option,\n"
  3202. . "the folowing listing of folders may help you to find it:\n"
  3203. . folders_list_to_help( $imap ) ;
  3204. return( $help_to_guess_prefix ) ;
  3205. }
  3206. sub folders_list_to_help {
  3207. my($imap) = @_ ;
  3208. my @folders = $imap->folders ;
  3209. my $listing = join q{}, map { "[$_]\n" } @folders ;
  3210. return( $listing ) ;
  3211. }
  3212. sub tests_separator_invert {
  3213. $fixslash2 = 0 ;
  3214. ok( not( defined separator_invert( ) ), 'separator_invert: no args' ) ;
  3215. ok( not( defined separator_invert( q{} ) ), 'separator_invert: not enough args' ) ;
  3216. ok( not( defined separator_invert( q{}, q{} ) ), 'separator_invert: not enough args' ) ;
  3217. ok( q{} eq separator_invert( q{}, q{}, q{} ), 'separator_invert: 3 empty strings' ) ;
  3218. ok( 'lalala' eq separator_invert( 'lalala', q{}, q{} ), 'separator_invert: empty separator' ) ;
  3219. ok( 'lalala' eq separator_invert( 'lalala', '/', '/' ), 'separator_invert: same separator /' ) ;
  3220. ok( 'lal/ala' eq separator_invert( 'lal/ala', '/', '/' ), 'separator_invert: same separator / 2' ) ;
  3221. ok( 'lal.ala' eq separator_invert( 'lal/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
  3222. ok( 'lal/ala' eq separator_invert( 'lal.ala', '.', '/' ), 'separator_invert: separators ./' ) ;
  3223. ok( 'la.l/ala' eq separator_invert( 'la/l.ala', '.', '/' ), 'separator_invert: separators ./' ) ;
  3224. ok( 'l/al.ala' eq separator_invert( 'l.al/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
  3225. $fixslash2 = 1 ;
  3226. ok( 'l_al.ala' eq separator_invert( 'l.al/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
  3227. return ;
  3228. }
  3229. sub separator_invert {
  3230. my( $h1_fold, $h1_separator, $h2_separator ) = @_ ;
  3231. return( undef ) if ( not defined $h1_fold or not defined $h1_separator or not defined $h2_separator ) ;
  3232. # The separator we hope we'll never encounter: 00000000 == 0x00
  3233. my $o_sep = "\000" ;
  3234. my $h2_fold = $h1_fold ;
  3235. $h2_fold =~ s,\Q$h2_separator,$o_sep,xg ;
  3236. $h2_fold =~ s,\Q$h1_separator,$h2_separator,xg ;
  3237. $h2_fold =~ s,\Q$o_sep,$h1_separator,xg ;
  3238. $h2_fold =~ s,/,_,xg if( $fixslash2 and '/' ne $h2_separator and '/' eq $h1_separator ) ;
  3239. return( $h2_fold ) ;
  3240. }
  3241. sub tests_imap2_folder_name {
  3242. $h1_prefix = $h2_prefix = q{};
  3243. $h1_sep = '/';
  3244. $h2_sep = '.';
  3245. $debug and myprint( <<"EOS"
  3246. prefix1: [$h1_prefix]
  3247. prefix2: [$h2_prefix]
  3248. sep1:[$h1_sep]
  3249. sep2:[$h2_sep]
  3250. EOS
  3251. ) ;
  3252. $fixslash2 = 0 ;
  3253. ok(q{} eq imap2_folder_name(q{}), 'imap2_folder_name: empty string');
  3254. ok('blabla' eq imap2_folder_name('blabla'), 'imap2_folder_name: blabla');
  3255. ok('spam.spam' eq imap2_folder_name('spam/spam'), 'imap2_folder_name: spam/spam');
  3256. ok('spam/spam' eq imap2_folder_name('spam.spam'), 'imap2_folder_name: spam.spam');
  3257. ok('spam.spam/spam' eq imap2_folder_name('spam/spam.spam'), 'imap2_folder_name: spam/spam.spam');
  3258. ok('s pam.spam/sp am' eq imap2_folder_name('s pam/spam.sp am'), 'imap2_folder_name: s pam/spam.sp am');
  3259. $sync->{f1f2}{ 'auto' } = 'moto' ;
  3260. ok( 'moto' eq imap2_folder_name( 'auto' ), 'imap2_folder_name: auto' ) ;
  3261. $sync->{f1f2}{ 'auto/auto' } = 'moto x 2' ;
  3262. ok( 'moto x 2' eq imap2_folder_name( 'auto/auto' ), 'imap2_folder_name: auto/auto' ) ;
  3263. @regextrans2 = ('s,/,X,g');
  3264. ok(q{} eq imap2_folder_name(q{}), 'imap2_folder_name: empty string [s,/,X,g]');
  3265. ok('blabla' eq imap2_folder_name('blabla'), 'imap2_folder_name: blabla [s,/,X,g]');
  3266. ok('spam.spam' eq imap2_folder_name('spam/spam'), 'imap2_folder_name: spam/spam [s,/,X,g]');
  3267. ok('spamXspam' eq imap2_folder_name('spam.spam'), 'imap2_folder_name: spam.spam [s,/,X,g]');
  3268. ok('spam.spamXspam' eq imap2_folder_name('spam/spam.spam'), 'imap2_folder_name: spam/spam.spam [s,/,X,g]');
  3269. @regextrans2 = ( 's, ,_,g' ) ;
  3270. ok('blabla' eq imap2_folder_name('blabla'), 'imap2_folder_name: blabla [s, ,_,g]');
  3271. ok('bla_bla' eq imap2_folder_name('bla bla'), 'imap2_folder_name: blabla [s, ,_,g]');
  3272. @regextrans2 = ( q{s,(.*),\U$1,} ) ;
  3273. ok( 'BLABLA' eq imap2_folder_name( 'blabla' ), q{imap2_folder_name: blabla [s,\U(.*)\E,$1,]} ) ;
  3274. $fixslash2 = 1 ;
  3275. @regextrans2 = ( ) ;
  3276. ok(q{} eq imap2_folder_name(q{}), 'imap2_folder_name: empty string');
  3277. ok('blabla' eq imap2_folder_name('blabla'), 'imap2_folder_name: blabla');
  3278. ok('spam.spam' eq imap2_folder_name('spam/spam'), 'imap2_folder_name: spam/spam -> spam.spam');
  3279. ok('spam_spam' eq imap2_folder_name('spam.spam'), 'imap2_folder_name: spam.spam -> spam_spam');
  3280. ok('spam.spam_spam' eq imap2_folder_name('spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam_spam');
  3281. ok('s pam.spam_spa m' eq imap2_folder_name('s pam/spam.spa m'), 'imap2_folder_name: s pam/spam.spa m -> s pam.spam_spa m');
  3282. $h1_sep = '.';
  3283. $h2_sep = '/';
  3284. ok(q{} eq imap2_folder_name(q{}), 'imap2_folder_name: empty string');
  3285. ok('blabla' eq imap2_folder_name('blabla'), 'imap2_folder_name: blabla');
  3286. ok('spam.spam' eq imap2_folder_name('spam/spam'), 'imap2_folder_name: spam/spam -> spam.spam');
  3287. ok('spam/spam' eq imap2_folder_name('spam.spam'), 'imap2_folder_name: spam.spam -> spam/spam');
  3288. ok('spam.spam/spam' eq imap2_folder_name('spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam');
  3289. $fixslash2 = 0 ;
  3290. $h1_prefix = q{ };
  3291. ok('spam.spam/spam' eq imap2_folder_name('spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam');
  3292. ok('spam.spam/spam' eq imap2_folder_name(' spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam');
  3293. $h1_sep = '.' ;
  3294. $h2_sep = '/' ;
  3295. $h1_prefix = 'INBOX.' ;
  3296. $h2_prefix = q{} ;
  3297. @regextrans2 = ( q{s,(.*),\U$1,} ) ;
  3298. ok( 'BLABLA' eq imap2_folder_name( 'blabla' ), 'imap2_folder_name: blabla' ) ;
  3299. ok( 'TEST/TEST/TEST/TEST' eq imap2_folder_name( 'INBOX.TEST.test.Test.tesT' ), 'imap2_folder_name: INBOX.TEST.test.Test.tesT' ) ;
  3300. @regextrans2 = ( q{s,(.*),\L$1,} ) ;
  3301. ok( 'test/test/test/test' eq imap2_folder_name( 'INBOX.TEST.test.Test.tesT' ), 'imap2_folder_name: INBOX.TEST.test.Test.tesT' ) ;
  3302. return ;
  3303. }
  3304. sub imap2_folder_name {
  3305. my ( $h1_fold ) = @_ ;
  3306. my ( $h2_fold ) ;
  3307. if ( $sync->{f1f2}{ $h1_fold } ) {
  3308. $h2_fold = $sync->{f1f2}{ $h1_fold } ;
  3309. ( $debug or $sync->{debugfolders} ) and myprint( "f1f2 [$h1_fold] -> [$h2_fold]\n" ) ;
  3310. return( $h2_fold ) ;
  3311. }
  3312. if ( $sync->{f1f2auto}{ $h1_fold } ) {
  3313. $h2_fold = $sync->{f1f2auto}{ $h1_fold } ;
  3314. ( $debug or $sync->{debugfolders} ) and myprint( "automap [$h1_fold] -> [$h2_fold]\n" ) ;
  3315. return( $h2_fold ) ;
  3316. }
  3317. $h2_fold = prefix_seperator_invertion( $h1_fold ) ;
  3318. $h2_fold = regextrans2( $h2_fold ) ;
  3319. return( $h2_fold ) ;
  3320. }
  3321. sub prefix_seperator_invertion {
  3322. my ( $h1_fold ) = @_ ;
  3323. my ( $h2_fold ) ;
  3324. # first we remove the prefix
  3325. $h1_fold =~ s/^\Q$h1_prefix\E//x ;
  3326. ( $debug or $sync->{debugfolders} ) and myprint( "removed host1 prefix: [$h1_fold]\n" ) ;
  3327. $h2_fold = separator_invert( $h1_fold, $h1_sep, $h2_sep ) ;
  3328. ( $debug or $sync->{debugfolders} ) and myprint( "inverted separators: [$h2_fold]\n" ) ;
  3329. # Adding the prefix supplied by namespace or the --prefix2 option
  3330. $h2_fold = $h2_prefix . $h2_fold
  3331. unless( ( $h2_prefix eq 'INBOX' . $h2_sep ) and ( $h2_fold =~ m/^INBOX$/xi ) ) ;
  3332. ( $debug or $sync->{debugfolders} ) and myprint( "added host2 prefix: [$h2_fold]\n" ) ;
  3333. return( $h2_fold ) ;
  3334. }
  3335. sub regextrans2 {
  3336. my( $h2_fold ) = @_ ;
  3337. # Transforming the folder name by the --regextrans2 option(s)
  3338. foreach my $regextrans2 ( @regextrans2 ) {
  3339. my $h2_fold_before = $h2_fold ;
  3340. my $ret = eval "\$h2_fold =~ $regextrans2 ; 1 " ;
  3341. ( $debug or $sync->{debugfolders} ) and myprint( "[$h2_fold_before] -> [$h2_fold] using regextrans2 [$regextrans2]\n" ) ;
  3342. if ( not ( defined $ret ) or $@ ) {
  3343. die_clean( "error: eval regextrans2 '$regextrans2': $@\n" ) ;
  3344. }
  3345. }
  3346. return( $h2_fold ) ;
  3347. }
  3348. sub tests_decompose_regex {
  3349. ok( 1, 'decompose_regex 1' ) ;
  3350. ok( 0 == compare_lists( [ q{}, q{} ], [ decompose_regex( q{} ) ] ), 'decompose_regex empty string' ) ;
  3351. ok( 0 == compare_lists( [ '.*', 'lala' ], [ decompose_regex( 's/.*/lala/' ) ] ), 'decompose_regex s/.*/lala/' ) ;
  3352. return ;
  3353. }
  3354. sub decompose_regex {
  3355. my $regex = shift ;
  3356. my( $left_part, $right_part ) ;
  3357. ( $left_part, $right_part ) = $regex =~ m{^s/((?:[^/]|\\/)+)/((?:[^/]|\\/)+)/}x;
  3358. return( q{}, q{} ) if not $left_part ;
  3359. return( $left_part, $right_part ) ;
  3360. }
  3361. sub foldersizes {
  3362. my ( $side, $imap, $search_cmd, @folders ) = @_ ;
  3363. my $total_size = 0 ;
  3364. my $total_nb = 0 ;
  3365. my $biggest_in_all = 0 ;
  3366. my $nb_folders = scalar @folders ;
  3367. my $ct_folders = 0 ; # folder counter.
  3368. myprint( "++++ Calculating sizes of $nb_folders folders on $side\n" ) ;
  3369. foreach my $folder ( @folders ) {
  3370. my $stot = 0 ;
  3371. my $nb_msgs = 0 ;
  3372. $ct_folders++ ;
  3373. myprintf( "$side folder %7s %-35s", "$ct_folders/$nb_folders", jux_utf8( $folder ) ) ;
  3374. if ( 'Host2' eq $side and not exists $h2_folders_all_UPPER{ uc $folder } ) {
  3375. myprint( " does not exist yet\n") ;
  3376. next ;
  3377. }
  3378. if ( 'Host1' eq $side and not exists $h1_folders_all{ $folder } ) {
  3379. myprint( " does not exist\n" ) ;
  3380. next ;
  3381. }
  3382. last if $imap->IsUnconnected( ) ;
  3383. # FTGate is RFC buggy with EXAMINE it does not act as SELECT
  3384. #unless ( $imap->examine( $folder ) ) {
  3385. unless ( $imap->select( $folder ) ) {
  3386. my $error = join q{},
  3387. "$side Folder $folder: Could not select: ",
  3388. $imap->LastError, "\n" ;
  3389. errors_incr( $sync, $error ) ;
  3390. next ;
  3391. }
  3392. last if $imap->IsUnconnected( ) ;
  3393. my $hash_ref = { } ;
  3394. my @msgs = select_msgs( $imap, undef, $search_cmd, $folder ) ;
  3395. $nb_msgs = scalar @msgs ;
  3396. my $biggest_in_folder = 0 ;
  3397. @{ $hash_ref }{ @msgs } = ( undef ) if @msgs ;
  3398. last if $imap->IsUnconnected( ) ;
  3399. if ( $nb_msgs > 0 and @msgs ) {
  3400. if ( $abletosearch ) {
  3401. if ( ! $imap->fetch_hash( \@msgs, 'RFC822.SIZE', $hash_ref) ) {
  3402. my $error = "$side failure with fetch_hash: $@" ;
  3403. errors_incr( $sync, $error ) ;
  3404. return ;
  3405. }
  3406. }else{
  3407. my $uidnext = $imap->uidnext( $folder ) || $uidnext_default ;
  3408. my $fetch_hash_uids = $fetch_hash_set || "1:$uidnext" ;
  3409. if ( ! $imap->fetch_hash( $fetch_hash_uids, 'RFC822.SIZE', $hash_ref ) ) {
  3410. my $error = "$side failure with fetch_hash: $@" ;
  3411. errors_incr( $sync, $error ) ;
  3412. return ;
  3413. }
  3414. }
  3415. for ( keys %{ $hash_ref } ) {
  3416. my $size = $hash_ref->{ $_ }->{ 'RFC822.SIZE' } ;
  3417. $stot += $size ;
  3418. $biggest_in_folder = max( $biggest_in_folder, $size ) ;
  3419. }
  3420. }
  3421. myprintf( ' Size: %9s', $stot ) ;
  3422. myprintf( ' Messages: %5s', $nb_msgs ) ;
  3423. myprintf( " Biggest: %9s\n", $biggest_in_folder ) ;
  3424. $total_size += $stot ;
  3425. $total_nb += $nb_msgs ;
  3426. $biggest_in_all = max( $biggest_in_all, $biggest_in_folder ) ;
  3427. }
  3428. myprintf( "%s Nb folders: %11s folders\n", $side, $nb_folders ) ;
  3429. myprintf( "%s Nb messages: %11s messages\n", $side, $total_nb ) ;
  3430. myprintf( "%s Total size: %11s bytes (%s)\n", $side, $total_size, bytes_display_string( $total_size ) ) ;
  3431. myprintf( "%s Biggest message: %11s bytes (%s)\n", $side, $biggest_in_all, bytes_display_string( $biggest_in_all ) ) ;
  3432. myprintf( "%s Time spent: %11.1f seconds\n", $side, timenext( ) ) ;
  3433. return( $total_nb, $total_size ) ;
  3434. }
  3435. sub timenext {
  3436. my ( $timenow, $timediff ) ;
  3437. # $timebefore is global, beurk !
  3438. $timenow = time ;
  3439. $timediff = $timenow - $timebefore ;
  3440. $timebefore = $timenow ;
  3441. return( $timediff ) ;
  3442. }
  3443. sub timesince {
  3444. my $timeinit = shift ;
  3445. my ( $timenow, $timediff ) ;
  3446. $timenow = time ;
  3447. $timediff = $timenow - $timeinit ;
  3448. return( $timediff ) ;
  3449. }
  3450. sub tests_flags_regex {
  3451. ok( q{} eq flags_regex(q{} ), 'flags_regex, null string q{}' ) ;
  3452. ok( q'\Seen NonJunk $Spam' eq flags_regex( q'\Seen NonJunk $Spam' ), 'flags_regex, nothing to do');
  3453. @regexflag = ('I am BAD' ) ;
  3454. ok( not ( defined flags_regex( q{} ) ), 'flags_regex, bad regex' ) ;
  3455. @regexflag = ( 's/NonJunk//g' ) ;
  3456. ok( q'\Seen $Spam' eq flags_regex( q'\Seen NonJunk $Spam' ), q{flags_regex, remove NonJunk: 's/NonJunk//g'} ) ;
  3457. @regexflag = ( q's/\$Spam//g' ) ;
  3458. ok( '\Seen NonJunk ' eq flags_regex( q'\Seen NonJunk $Spam' ), q{flags_regex, remove $Spam: 's/\$Spam//g'} ) ;
  3459. @regexflag = ( 's/\\\\Seen//g' ) ;
  3460. ok( q' NonJunk $Spam' eq flags_regex( q'\Seen NonJunk $Spam' ), q{flags_regex, remove \Seen: 's/\\\\\\\\Seen//g'} ) ;
  3461. @regexflag = ( 's/(\s|^)[^\\\\]\w+//g' ) ;
  3462. ok( '\Seen \Middle \End' eq flags_regex( q'\Seen NonJunk \Middle $Spam \End' ), q{flags_regex: only \word among \Seen NonJunk \Middle $Spam \End} ) ;
  3463. ok( ' \Seen \Middle \End1' eq flags_regex( q'Begin \Seen NonJunk \Middle $Spam \End1 End' ),
  3464. q'flags_regex: only \word among Begin \Seen NonJunk \Middle $Spam \End1 End' ) ;
  3465. @regexflag = ( q's/.*?(Keep1|Keep2|Keep3)/$1 /g' ) ;
  3466. ok('Keep1 Keep2 ReB' eq flags_regex('ReA Keep1 REM Keep2 ReB'), 'Keep only regex' ) ;
  3467. ok('Keep1 Keep2 ' eq flags_regex( 'REM REM Keep1 Keep2'), 'Keep only regex' ) ;
  3468. ok('Keep1 Keep2 ' eq flags_regex( 'Keep1 REM REM Keep2'), 'Keep only regex' ) ;
  3469. ok('Keep1 Keep2 ' eq flags_regex( 'REM Keep1 REM REM Keep2'), 'Keep only regex' ) ;
  3470. ok('Keep1 Keep2 ' eq flags_regex( 'Keep1 Keep2'), 'Keep only regex' ) ;
  3471. ok('Keep1 ' eq flags_regex( 'REM Keep1'), 'Keep only regex' ) ;
  3472. @regexflag = ( q's/(Keep1|Keep2|Keep3) (?!(Keep1|Keep2|Keep3)).*/$1 /g' ) ;
  3473. ok('Keep1 Keep2 ' eq flags_regex( 'Keep1 Keep2 ReB'), 'Keep only regex' ) ;
  3474. ok('Keep1 Keep2 ' eq flags_regex( 'Keep1 Keep2 REM REM REM'), 'Keep only regex' ) ;
  3475. ok('Keep2 ' eq flags_regex('Keep2 REM REM REM'), 'Keep only regex' ) ;
  3476. @regexflag = ( q's/.*?(Keep1|Keep2|Keep3)/$1 /g',
  3477. 's/(Keep1|Keep2|Keep3) (?!(Keep1|Keep2|Keep3)).*/$1 /g');
  3478. ok('Keep1 Keep2 ' eq flags_regex('REM Keep1 REM Keep2 REM'), 'Keep only regex');
  3479. ok('Keep1 Keep2 ' eq flags_regex('Keep1 REM Keep2 REM'), 'Keep only regex');
  3480. ok('Keep1 Keep2 ' eq flags_regex('REM Keep1 Keep2 REM'), 'Keep only regex');
  3481. ok('Keep1 Keep2 ' eq flags_regex('REM Keep1 REM Keep2'), 'Keep only regex');
  3482. ok('Keep1 Keep2 Keep3 ' eq flags_regex('REM Keep1 REM Keep2 REM REM Keep3 REM'), 'Keep only regex');
  3483. ok('Keep1 ' eq flags_regex('REM REM Keep1 REM REM REM '), 'Keep only regex');
  3484. ok('Keep1 Keep3 ' eq flags_regex('RE1 Keep1 RE2 Keep3 RE3 RE4 RE5 '), 'Keep only regex');
  3485. @regexflag = ('s/(.*)/$1 jrdH8u/');
  3486. ok('REM REM REM REM REM jrdH8u' eq flags_regex('REM REM REM REM REM'), q{Keep only regex 's/(.*)/\$1 jrdH8u/'} ) ;
  3487. @regexflag = ('s/jrdH8u *//');
  3488. ok('REM REM REM REM REM ' eq flags_regex('REM REM REM REM REM jrdH8u'), q{Keep only regex s/jrdH8u *//} ) ;
  3489. @regexflag = (
  3490. 's/(.*)/$1 jrdH8u/',
  3491. 's/.*?(Keep1|Keep2|Keep3|jrdH8u)/$1 /g',
  3492. 's/(Keep1|Keep2|Keep3|jrdH8u) (?!(Keep1|Keep2|Keep3|jrdH8u)).*/$1 /g',
  3493. 's/jrdH8u *//'
  3494. );
  3495. ok('Keep1 Keep2 ' eq flags_regex('REM Keep1 REM Keep2 REM'), q{Keep only regex 'REM Keep1 REM Keep2 REM'} ) ;
  3496. ok('Keep1 Keep2 ' eq flags_regex('Keep1 REM Keep2 REM'), 'Keep only regex');
  3497. ok('Keep1 Keep2 ' eq flags_regex('REM Keep1 Keep2 REM'), 'Keep only regex');
  3498. ok('Keep1 Keep2 ' eq flags_regex('REM Keep1 REM Keep2'), 'Keep only regex');
  3499. ok('Keep1 Keep2 Keep3 ' eq flags_regex('REM Keep1 REM Keep2 REM REM Keep3 REM'), 'Keep only regex');
  3500. ok('Keep1 ' eq flags_regex('REM REM Keep1 REM REM REM '), 'Keep only regex');
  3501. ok('Keep1 Keep3 ' eq flags_regex('RE1 Keep1 RE2 Keep3 RE3 RE4 RE5 '), 'Keep only regex');
  3502. ok(q{} eq flags_regex('REM REM REM REM REM'), 'Keep only regex');
  3503. @regexflag = (
  3504. 's/(.*)/$1 jrdH8u/',
  3505. 's/.*?(\\\\Seen|\\\\Answered|\\\\Flagged|\\\\Deleted|\\\\Draft|jrdH8u)/$1 /g',
  3506. 's/(\\\\Seen|\\\\Answered|\\\\Flagged|\\\\Deleted|\\\\Draft|jrdH8u) (?!(\\\\Seen|\\\\Answered|\\\\Flagged|\\\\Deleted|\\\\Draft|jrdH8u)).*/$1 /g',
  3507. 's/jrdH8u *//'
  3508. );
  3509. ok('\\Deleted \\Answered '
  3510. eq flags_regex('Blabla $Junk \\Deleted machin \\Answered truc'), 'Keep only regex: Exchange case' ) ;
  3511. ok( q{} eq flags_regex( q{} ), 'Keep only regex: Exchange case, null string' ) ;
  3512. ok( q{}
  3513. eq flags_regex('Blabla $Junk machin truc'), 'Keep only regex: Exchange case, no accepted flags' ) ;
  3514. ok( '\\Deleted \\Answered \\Draft \\Flagged '
  3515. eq flags_regex('\\Deleted \\Answered \\Draft \\Flagged '), 'Keep only regex: Exchange case' ) ;
  3516. @regexflag = (
  3517. 's/.*?(?:(\\\\(?:Answered|Flagged|Deleted|Seen|Draft)\s?)|$)/defined($1)?$1:q()/eg'
  3518. );
  3519. ok( '\\Deleted \\Answered '
  3520. eq flags_regex('Blabla \$Junk \\Deleted machin \\Answered truc'),
  3521. 'Keep only regex: Exchange case (Phil)' ) ;
  3522. ok( q{} eq flags_regex( q{} ), 'Keep only regex: Exchange case, null string (Phil)' ) ;
  3523. ok( q{}
  3524. eq flags_regex('Blabla $Junk machin truc'),
  3525. 'Keep only regex: Exchange case, no accepted flags (Phil)' ) ;
  3526. ok('\\Deleted \\Answered \\Draft \\Flagged '
  3527. eq flags_regex('\\Deleted \\Answered \\Draft \\Flagged '),
  3528. 'Keep only regex: Exchange case (Phil)' ) ;
  3529. return ;
  3530. }
  3531. sub flags_regex {
  3532. my ( $h1_flags ) = @_ ;
  3533. foreach my $regexflag ( @regexflag ) {
  3534. my $h1_flags_orig = $h1_flags ;
  3535. $debugflags and myprint( "eval \$h1_flags =~ $regexflag\n" ) ;
  3536. my $ret = eval "\$h1_flags =~ $regexflag ; 1 " ;
  3537. $debugflags and myprint( "regexflag $regexflag [$h1_flags_orig] -> [$h1_flags]\n" ) ;
  3538. if( not ( defined $ret ) or $@ ) {
  3539. myprint( "Error: eval regexflag '$regexflag': $@\n" ) ;
  3540. return( undef ) ;
  3541. }
  3542. }
  3543. return( $h1_flags ) ;
  3544. }
  3545. sub acls_sync {
  3546. my($h1_fold, $h2_fold) = @_ ;
  3547. if ( $syncacls ) {
  3548. my $h1_hash = $imap1->getacl($h1_fold)
  3549. or myprint( "Could not getacl for $h1_fold: $@\n" ) ;
  3550. my $h2_hash = $imap2->getacl($h2_fold)
  3551. or myprint( "Could not getacl for $h2_fold: $@\n" ) ;
  3552. my %users = map { ($_, 1) } ( keys %{ $h1_hash} , keys %{ $h2_hash } ) ;
  3553. foreach my $user (sort keys %users ) {
  3554. my $acl = $h1_hash->{$user} || 'none' ;
  3555. myprint( "acl $user: [$acl]\n" ) ;
  3556. next if ($h1_hash->{$user} && $h2_hash->{$user} &&
  3557. $h1_hash->{$user} eq $h2_hash->{$user});
  3558. unless ($dry) {
  3559. myprint( "setting acl $h2_fold $user $acl\n" ) ;
  3560. $imap2->setacl($h2_fold, $user, $acl)
  3561. or myprint( "Could not set acl: $@\n" ) ;
  3562. }
  3563. }
  3564. }
  3565. return ;
  3566. }
  3567. sub tests_permanentflags {
  3568. my $string;
  3569. ok(q{} eq permanentflags(' * OK [PERMANENTFLAGS (\* \Draft \Answered)] Limited'),
  3570. 'permanentflags \*');
  3571. ok('\Draft \Answered' eq permanentflags(' * OK [PERMANENTFLAGS (\Draft \Answered)] Limited'),
  3572. 'permanentflags \Draft \Answered');
  3573. ok('\Draft \Answered'
  3574. eq permanentflags('Blabla',
  3575. ' * OK [PERMANENTFLAGS (\Draft \Answered)] Limited',
  3576. 'Blabla'),
  3577. 'permanentflags \Draft \Answered'
  3578. );
  3579. ok(q{} eq permanentflags('Blabla'), 'permanentflags nothing');
  3580. return ;
  3581. }
  3582. sub permanentflags {
  3583. my @lines = @_ ;
  3584. foreach my $line (@lines) {
  3585. if ( $line =~ m{\[PERMANENTFLAGS\s\(([^)]+?)\)\]}x ) {
  3586. ( $debugflags or $debug ) and myprint( "permanentflags: $line" ) ;
  3587. my $permanentflags = $1 ;
  3588. if ( $permanentflags =~ m{\\\*}x ) {
  3589. $permanentflags = q{} ;
  3590. }
  3591. return($permanentflags) ;
  3592. } ;
  3593. }
  3594. return( q{} ) ;
  3595. }
  3596. sub tests_flags_filter {
  3597. ok( '\Seen' eq flags_filter('\Seen', '\Draft \Seen \Answered'), 'flags_filter ' );
  3598. ok( q{} eq flags_filter('\Seen', '\Draft \Answered'), 'flags_filter ' );
  3599. ok( '\Seen' eq flags_filter('\Seen', '\Seen'), 'flags_filter ' );
  3600. ok( '\Seen' eq flags_filter('\Seen', ' \Seen '), 'flags_filter ' );
  3601. ok( '\Seen \Draft'
  3602. eq flags_filter('\Seen \Draft', '\Draft \Seen \Answered'), 'flags_filter ' );
  3603. ok( '\Seen \Draft'
  3604. eq flags_filter('\Seen \Draft', ' \Draft \Seen \Answered '), 'flags_filter ' );
  3605. return ;
  3606. }
  3607. sub flags_filter {
  3608. my( $flags, $allowed_flags ) = @_ ;
  3609. my @flags = split /\s+/x, $flags ;
  3610. my %allowed_flags = map { $_ => 1 } split q{ }, $allowed_flags ;
  3611. my @flags_out = map { exists $allowed_flags{$_} ? $_ : () } @flags ;
  3612. my $flags_out = join q{ }, @flags_out ;
  3613. return( $flags_out ) ;
  3614. }
  3615. sub flagscase {
  3616. my $flags = shift ;
  3617. my @flags = split /\s+/x, $flags ;
  3618. my %rfc_flags = map { $_ => 1 } split q{ }, '\Answered \Flagged \Deleted \Seen \Draft' ;
  3619. my @flags_out = map { exists $rfc_flags{ ucsecond( lc $_ ) } ? ucsecond( lc $_ ) : $_ } @flags ;
  3620. my $flags_out = join q{ }, @flags_out ;
  3621. return( $flags_out ) ;
  3622. }
  3623. sub tests_flagscase {
  3624. ok( '\Seen' eq flagscase( '\Seen' ), 'flagscase: \Seen -> \Seen' ) ;
  3625. ok( '\Seen' eq flagscase( '\SEEN' ), 'flagscase: \SEEN -> \Seen' ) ;
  3626. ok( '\Seen \Draft' eq flagscase( '\SEEN \DRAFT' ), 'flagscase: \SEEN \DRAFT -> \Seen \Draft' ) ;
  3627. ok( '\Draft \Seen' eq flagscase( '\DRAFT \SEEN' ), 'flagscase: \DRAFT \SEEN -> \Draft \Seen' ) ;
  3628. ok( '\Draft LALA \Seen' eq flagscase( '\DRAFT LALA \SEEN' ), 'flagscase: \DRAFT LALA \SEEN -> \Draft LALA \Seen' ) ;
  3629. ok( '\Draft lala \Seen' eq flagscase( '\DRAFT lala \SEEN' ), 'flagscase: \DRAFT lala \SEEN -> \Draft lala \Seen' ) ;
  3630. return ;
  3631. }
  3632. sub ucsecond {
  3633. my $string = shift ;
  3634. my $output ;
  3635. return( $string ) if ( 1 >= length $string ) ;
  3636. $output = ( substr( $string, 0, 1) ) . ( uc substr $string, 1, 1 ) . ( substr $string, 2 ) ;
  3637. #myprint( "UUU $string -> $output\n" ) ;
  3638. return( $output ) ;
  3639. }
  3640. sub tests_ucsecond {
  3641. ok( 'aBcde' eq ucsecond( 'abcde' ), 'ucsecond: abcde -> aBcde' ) ;
  3642. ok( 'ABCDE' eq ucsecond( 'ABCDE' ), 'ucsecond: ABCDE -> ABCDE' ) ;
  3643. ok( 'ABCDE' eq ucsecond( 'AbCDE' ), 'ucsecond: AbCDE -> ABCDE' ) ;
  3644. ok( 'ABCde' eq ucsecond( 'AbCde' ), 'ucsecond: AbCde -> ABCde' ) ;
  3645. ok( 'A' eq ucsecond( 'A' ), 'ucsecond: A -> A' ) ;
  3646. ok( 'AB' eq ucsecond( 'Ab' ), 'ucsecond: Ab -> AB' ) ;
  3647. ok( '\B' eq ucsecond( '\b' ), 'ucsecond: \b -> \B' ) ;
  3648. ok( '\Bcde' eq ucsecond( '\bcde' ), 'ucsecond: \bcde -> \Bcde' ) ;
  3649. return ;
  3650. }
  3651. sub select_msgs {
  3652. my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ;
  3653. my ( @msgs ) ;
  3654. if ( $abletosearch ) {
  3655. @msgs = select_msgs_by_search( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) ;
  3656. }else{
  3657. @msgs = select_msgs_by_fetch( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) ;
  3658. }
  3659. return( @msgs ) ;
  3660. }
  3661. sub select_msgs_by_search {
  3662. my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ;
  3663. my ( @msgs, @msgs_all ) ;
  3664. # Need to have the whole list in msgs_all_hash_ref
  3665. # without calling messages() several times.
  3666. # Need all messages list to avoid deleting useful cache part
  3667. # in case of --search or --minage or --maxage
  3668. if ( ( defined $msgs_all_hash_ref and $usecache )
  3669. or ( not defined $maxage and not defined $minage and not defined $search_cmd )
  3670. ) {
  3671. $debugdev and myprint( "Calling messages()\n" ) ;
  3672. @msgs_all = $imap->messages( ) ;
  3673. return if ( $#msgs_all == 0 && !defined $msgs_all[0] ) ;
  3674. if ( defined $msgs_all_hash_ref ) {
  3675. @{ $msgs_all_hash_ref }{ @msgs_all } = () ;
  3676. }
  3677. # return all messages
  3678. if ( not defined $maxage and not defined $minage and not defined $search_cmd ) {
  3679. return( @msgs_all ) ;
  3680. }
  3681. }
  3682. if ( defined $search_cmd ) {
  3683. @msgs = $imap->search( $search_cmd ) ;
  3684. return( @msgs ) ;
  3685. }
  3686. # we are here only if $maxage or $minage is defined
  3687. @msgs = select_msgs_by_age( $imap ) ;
  3688. return( @msgs );
  3689. }
  3690. sub select_msgs_by_fetch {
  3691. my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ;
  3692. my ( @msgs, @msgs_all, %fetch ) ;
  3693. # Need to have the whole list in msgs_all_hash_ref
  3694. # without calling messages() several times.
  3695. # Need all messages list to avoid deleting useful cache part
  3696. # in case of --search or --minage or --maxage
  3697. $debugdev and myprint( "Calling fetch_hash()\n" ) ;
  3698. my $uidnext = $imap->uidnext( $folder ) || $uidnext_default ;
  3699. my $fetch_hash_uids = $fetch_hash_set || "1:$uidnext" ;
  3700. %fetch = %{$imap->fetch_hash( $fetch_hash_uids, 'INTERNALDATE' ) } ;
  3701. @msgs_all = sort { $a <=> $b } keys %fetch ;
  3702. $debugdev and myprint( "Done fetch_hash()\n" ) ;
  3703. return if ( $#msgs_all == 0 && !defined $msgs_all[0] ) ;
  3704. if ( defined $msgs_all_hash_ref ) {
  3705. @{ $msgs_all_hash_ref }{ @msgs_all } = () ;
  3706. }
  3707. # return all messages
  3708. if ( not defined $maxage and not defined $minage and not defined $search_cmd ) {
  3709. return( @msgs_all ) ;
  3710. }
  3711. if ( defined $search_cmd ) {
  3712. myprint( "Warning: strange to see --search with --noabletosearch, an error can happen\n" ) ;
  3713. @msgs = $imap->search( $search_cmd ) ;
  3714. return( @msgs ) ;
  3715. }
  3716. # we are here only if $maxage or $minage is defined
  3717. my( @max, @min, $maxage_epoch, $minage_epoch ) ;
  3718. if ( defined $maxage ) { $maxage_epoch = $timestart_int - $NB_SECONDS_IN_A_DAY * $maxage ; }
  3719. if ( defined $minage ) { $minage_epoch = $timestart_int - $NB_SECONDS_IN_A_DAY * $minage ; }
  3720. foreach my $msg ( @msgs_all ) {
  3721. my $idate = $fetch{ $msg }->{'INTERNALDATE'} ;
  3722. #myprint( "$idate\n" ) ;
  3723. if ( defined $maxage and ( epoch( $idate ) >= $maxage_epoch ) ) {
  3724. push @max, $msg ;
  3725. }
  3726. if ( defined $minage and ( epoch( $idate ) <= $minage_epoch ) ) {
  3727. push @min, $msg ;
  3728. }
  3729. }
  3730. @msgs = msgs_from_maxmin( \@max, \@min ) ;
  3731. return( @msgs ) ;
  3732. }
  3733. sub select_msgs_by_age {
  3734. my( $imap ) = @_ ;
  3735. my( @max, @min, @msgs, @inter, @union ) ;
  3736. if ( defined $maxage ) {
  3737. @max = $imap->sentsince( $timestart_int - $NB_SECONDS_IN_A_DAY * $maxage ) ;
  3738. }
  3739. if ( defined $minage ) {
  3740. @min = $imap->sentbefore( $timestart_int - $NB_SECONDS_IN_A_DAY * $minage ) ;
  3741. }
  3742. @msgs = msgs_from_maxmin( \@max, \@min ) ;
  3743. return( @msgs ) ;
  3744. }
  3745. sub msgs_from_maxmin {
  3746. my( $max_ref, $min_ref ) = @_ ;
  3747. my( @max, @min, @msgs, @inter, @union ) ;
  3748. @max = @{ $max_ref } ;
  3749. @min = @{ $min_ref } ;
  3750. SWITCH: {
  3751. unless( defined $minage ) { @msgs = @max ; last SWITCH } ;
  3752. unless( defined $maxage ) { @msgs = @min ; last SWITCH } ;
  3753. my ( %union, %inter ) ;
  3754. foreach my $m ( @min, @max ) { $union{ $m }++ && $inter{ $m }++ }
  3755. @inter = sort { $a <=> $b } keys %inter ;
  3756. @union = sort { $a <=> $b } keys %union ;
  3757. # normal case
  3758. if ( $minage <= $maxage ) { @msgs = @inter ; last SWITCH } ;
  3759. # just exclude messages between
  3760. if ( $minage > $maxage ) { @msgs = @union ; last SWITCH } ;
  3761. }
  3762. return( @msgs ) ;
  3763. }
  3764. sub tests_msgs_from_maxmin {
  3765. my @msgs ;
  3766. $maxage = $NUMBER_200 ;
  3767. @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
  3768. ok( 0 == compare_lists( [ '1', '2' ], \@msgs ), 'msgs_from_maxmin: maxage++' ) ;
  3769. $minage = $NUMBER_100 ;
  3770. @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
  3771. ok( 0 == compare_lists( [ '2' ], \@msgs ), 'msgs_from_maxmin: -maxage++minage-' ) ;
  3772. $minage = $NUMBER_300 ;
  3773. @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
  3774. ok( 0 == compare_lists( [ '1', '2', '3' ], \@msgs ), 'msgs_from_maxmin: ++maxage-minage++' ) ;
  3775. $maxage = undef ;
  3776. @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
  3777. ok( 0 == compare_lists( [ '2', '3' ], \@msgs ), 'msgs_from_maxmin: ++minage-' ) ;
  3778. return ;
  3779. }
  3780. sub lastuid {
  3781. my $imap = shift ;
  3782. my $folder = shift ;
  3783. my $lastuid_guess = shift ;
  3784. my $lastuid ;
  3785. # rfc3501: The only reliable way to identify recent messages is to
  3786. # look at message flags to see which have the \Recent flag
  3787. # set, or to do a SEARCH RECENT.
  3788. # SEARCH RECENT doesn't work this way on courrier.
  3789. my @recent_messages ;
  3790. # SEARCH RECENT for each transfer can be expensive with a big folder
  3791. # Call commented for now
  3792. #@recent_messages = $imap->recent( ) ;
  3793. #myprint( "Recent: @recent_messages\n" ) ;
  3794. my $max_recent ;
  3795. $max_recent = max( @recent_messages ) ;
  3796. if ( defined $max_recent and ($lastuid_guess <= $max_recent ) ) {
  3797. $lastuid = $max_recent ;
  3798. }else{
  3799. $lastuid = $lastuid_guess
  3800. }
  3801. return( $lastuid ) ;
  3802. }
  3803. sub size_filtered {
  3804. my( $h1_size, $h1_msg, $h1_fold, $h2_fold ) = @_ ;
  3805. $h1_size = 0 if ( ! $h1_size ) ; # null if empty or undef
  3806. if (defined $maxsize and $h1_size > $maxsize) {
  3807. myprint( "msg $h1_fold/$h1_msg skipped ($h1_size exceeds maxsize limit $maxsize bytes)\n" ) ;
  3808. $total_bytes_skipped += $h1_size;
  3809. $nb_msg_skipped += 1;
  3810. return( 1 ) ;
  3811. }
  3812. if (defined $minsize and $h1_size <= $minsize) {
  3813. myprint( "msg $h1_fold/$h1_msg skipped ($h1_size smaller than minsize $minsize bytes)\n" ) ;
  3814. $total_bytes_skipped += $h1_size;
  3815. $nb_msg_skipped += 1;
  3816. return( 1 ) ;
  3817. }
  3818. return( 0 ) ;
  3819. }
  3820. sub message_exists {
  3821. my( $imap, $msg ) = @_ ;
  3822. return( 1 ) if not $imap->Uid( ) ;
  3823. my $search_uid ;
  3824. ( $search_uid ) = $imap->search( "UID $msg" ) ;
  3825. #myprint( "$search ? $msg\n" ) ;
  3826. return( 1 ) if ( $search_uid eq $msg ) ;
  3827. return( 0 ) ;
  3828. }
  3829. sub copy_message {
  3830. # copy
  3831. my ( $sync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) = @_ ;
  3832. ( $debug or $dry) and myprint( "msg $h1_fold/$h1_msg copying to $h2_fold $dry_message\n" ) ;
  3833. my $h1_size = $h1_fir_ref->{$h1_msg}->{'RFC822.SIZE'} || 0 ;
  3834. my $h1_flags = $h1_fir_ref->{$h1_msg}->{'FLAGS'} || q{} ;
  3835. my $h1_idate = $h1_fir_ref->{$h1_msg}->{'INTERNALDATE'} || q{} ;
  3836. if ( size_filtered( $h1_size, $h1_msg, $h1_fold, $h2_fold ) ) {
  3837. $h1_nb_msg_processed +=1 ;
  3838. return ;
  3839. }
  3840. debugsleep( $sync ) ;
  3841. myprint( "- msg $h1_fold/$h1_msg S[$h1_size] F[$h1_flags] I[$h1_idate] has RFC822.SIZE null!\n" ) if ( ! $h1_size ) ;
  3842. if ( $checkmessageexists and not message_exists( $imap1, $h1_msg ) ) {
  3843. $total_bytes_skipped += $h1_size;
  3844. $nb_msg_skipped += 1;
  3845. $h1_nb_msg_processed +=1 ;
  3846. return ;
  3847. }
  3848. if ( $sync->{debugmemory} ) {
  3849. myprintf("C1: Memory consumption: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI) ;
  3850. }
  3851. my ( $string, $string_len ) ;
  3852. ( $string_len ) = message_for_host2( $sync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, \$string ) ;
  3853. if ( $sync->{debugmemory} ) {
  3854. myprintf("C2: Memory consumption: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI) ;
  3855. }
  3856. # not defined or empty $string
  3857. if ( ( not $string ) and ( not $string_len ) ) {
  3858. myprint( "- msg $h1_fold/$h1_msg skipped.\n" ) ;
  3859. $total_bytes_skipped += $h1_size;
  3860. $nb_msg_skipped += 1;
  3861. $h1_nb_msg_processed +=1 ;
  3862. return ;
  3863. }
  3864. # Lines too long (or not enough) => do no copy or fix
  3865. if ( ( defined $maxlinelength ) or ( defined $minmaxlinelength ) ) {
  3866. $string = linelengthstuff( $string, $h1_fold, $h1_msg, $string_len, $h1_size, $h1_flags, $h1_idate ) ;
  3867. if ( not defined $string ) {
  3868. $h1_nb_msg_processed +=1 ;
  3869. $total_bytes_skipped += $h1_size ;
  3870. $nb_msg_skipped += 1 ;
  3871. return ;
  3872. }
  3873. }
  3874. my $h1_date = date_for_host2( $h1_msg, $h1_idate ) ;
  3875. ( $debug or $debugflags ) and
  3876. myprint( "Host1 flags init msg $h1_fold/$h1_msg date [$h1_date] flags [$h1_flags] size [$h1_size]\n" ) ;
  3877. $h1_flags = flags_for_host2( $h1_flags, $permanentflags2 ) ;
  3878. ( $debug or $debugflags ) and
  3879. myprint( "Host1 flags filt msg $h1_fold/$h1_msg date [$h1_date] flags [$h1_flags] size [$h1_size]\n" ) ;
  3880. $h1_date = undef if ($h1_date eq q{});
  3881. my $new_id = append_message_on_host2( \$string, $h1_fold, $h1_msg, $string_len, $h2_fold, $h1_size, $h1_flags, $h1_date, $cache_dir ) ;
  3882. if ( $new_id and $syncflagsaftercopy ) {
  3883. sync_flags_after_copy( $h1_fold, $h1_msg, $h1_flags, $h2_fold, $new_id, $permanentflags2 ) ;
  3884. }
  3885. if ( $sync->{debugmemory} ) {
  3886. myprintf("C3: Memory consumption: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI) ;
  3887. }
  3888. return $new_id ;
  3889. }
  3890. sub linelengthstuff {
  3891. my( $string, $h1_fold, $h1_msg, $string_len, $h1_size, $h1_flags, $h1_idate ) = @_ ;
  3892. my $maxlinelength_string = max_line_length( $string ) ;
  3893. $debugmaxlinelength and myprint( "msg $h1_fold/$h1_msg maxlinelength: $maxlinelength_string\n" ) ;
  3894. if ( ( defined $minmaxlinelength ) and ( $maxlinelength_string <= $minmaxlinelength ) ) {
  3895. my $subject = subject( $string ) ;
  3896. $debugdev and myprint( "- msg $h1_fold/$h1_msg skipped S[$h1_size] F[$h1_flags] I[$h1_idate] "
  3897. . "(Subject:[$subject]) (max line length under minmaxlinelength $minmaxlinelength bytes)\n" ) ;
  3898. return ;
  3899. }
  3900. if ( ( defined $maxlinelength ) and ( $maxlinelength_string > $maxlinelength ) ) {
  3901. my $subject = subject( $string ) ;
  3902. if ( $maxlinelengthcmd ) {
  3903. $string = pipemess( $string, $maxlinelengthcmd ) ;
  3904. # string undef means something was bad.
  3905. if ( not ( defined $string ) ) {
  3906. myprint( "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate] "
  3907. . "(Subject:[$subject]) could not be successfully transformed by --maxlinelengthcmd option\n" ) ;
  3908. return ;
  3909. }else{
  3910. return $string ;
  3911. }
  3912. }
  3913. myprint( "- msg $h1_fold/$h1_msg skipped S[$h1_size] F[$h1_flags] I[$h1_idate] "
  3914. . "(Subject:[$subject]) (line length exceeds maxlinelength $maxlinelength bytes)\n" ) ;
  3915. return ;
  3916. }
  3917. return $string ;
  3918. }
  3919. sub message_for_host2 {
  3920. # global variable list:
  3921. # @skipmess
  3922. # @regexmess
  3923. # @pipemess
  3924. # $addheader
  3925. # $debugcontent
  3926. # $debug
  3927. #
  3928. # API current
  3929. #
  3930. # at failure:
  3931. # * return nothing ( will then be undef or () )
  3932. # * $string_ref content is undef or empty
  3933. # at success:
  3934. # * return string length ($string_ref content length)
  3935. # * $string_ref content filled with message
  3936. # API future
  3937. #
  3938. #
  3939. my ( $sync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ) = @_ ;
  3940. # abort when missing a parameter
  3941. if ( (!$sync) or (!$h1_msg) or (!$h1_fold) or (!$h1_size) or (!defined $h1_flags) or (!$h1_idate) or (!$h1_fir_ref) or (!$string_ref) ) {
  3942. return ;
  3943. }
  3944. if ( $sync->{debugmemory} ) {
  3945. myprintf("M1: Memory consumption: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI) ;
  3946. }
  3947. my $imap1 = $sync->{imap1} ;
  3948. my $string_ok = $imap1->message_to_file( $string_ref, $h1_msg ) ;
  3949. if ( $sync->{debugmemory} ) {
  3950. myprintf("M2: Memory consumption: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI) ;
  3951. }
  3952. my $string_len = length_ref( $string_ref ) ;
  3953. unless ( defined $string_ok and $string_len ) {
  3954. # undef or 0 length
  3955. my $error = join q{},
  3956. "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate] could not be fetched: ",
  3957. $imap1->LastError || q{}, "\n" ;
  3958. errors_incr( $sync, $error ) ;
  3959. $total_bytes_error += $h1_size if ( $h1_size ) ;
  3960. $h1_nb_msg_processed +=1 ;
  3961. return ;
  3962. }
  3963. if ( @skipmess ) {
  3964. my $match = skipmess( ${ $string_ref } ) ;
  3965. # string undef means the eval regex was bad.
  3966. if ( not ( defined $match ) ) {
  3967. myprint(
  3968. "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
  3969. . " could not be skipped by --skipmess option, bad regex\n" ) ;
  3970. return ;
  3971. }
  3972. if ( $match ) {
  3973. my $subject = subject( ${ $string_ref } ) ;
  3974. myprint( "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
  3975. . " (Subject:[$subject]) skipped by --skipmess\n" ) ;
  3976. return ;
  3977. }
  3978. }
  3979. if ( @regexmess ) {
  3980. ${ $string_ref } = regexmess( ${ $string_ref } ) ;
  3981. # string undef means the eval regex was bad.
  3982. if ( not ( defined ${ $string_ref } ) ) {
  3983. myprint(
  3984. "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
  3985. . " could not be transformed by --regexmess\n" ) ;
  3986. return ;
  3987. }
  3988. }
  3989. if ( @pipemess ) {
  3990. ${ $string_ref } = pipemess( ${ $string_ref }, @pipemess ) ;
  3991. # string undef means something was bad.
  3992. if ( not ( defined ${ $string_ref } ) ) {
  3993. myprint(
  3994. "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
  3995. . " could not be successfully transformed by --pipemess option\n" ) ;
  3996. return ;
  3997. }
  3998. }
  3999. if ( $addheader and defined $h1_fir_ref->{$h1_msg}->{'NO_HEADER'} ) {
  4000. my $header = add_header( $h1_msg ) ;
  4001. $debug and myprint( "msg $h1_fold/$h1_msg adding custom header [$header]\n" ) ;
  4002. ${ $string_ref } = $header . "\r\n" . ${ $string_ref } ;
  4003. }
  4004. $string_len = length_ref( $string_ref ) ;
  4005. $debugcontent and myprint(
  4006. q{=} x $STD_CHAR_PER_LINE, "\n",
  4007. "F message content begin next line ($string_len characters long)\n",
  4008. ${ $string_ref },
  4009. "F message content ended on previous line\n", q{=} x $STD_CHAR_PER_LINE, "\n" ) ;
  4010. if ( $sync->{debugmemory} ) {
  4011. myprintf("M3: Memory consumption: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI) ;
  4012. }
  4013. return $string_len ;
  4014. }
  4015. sub tests_message_for_host2 {
  4016. my ( $sync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ) ;
  4017. is( undef, message_for_host2( ), q{message_for_host2: no args} ) ;
  4018. is( undef, message_for_host2( $sync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ), q{message_for_host2: undef args} ) ;
  4019. require Test::MockObject ;
  4020. my $imapT = Test::MockObject->new( ) ;
  4021. $sync->{imap1} = $imapT ;
  4022. my $string ;
  4023. $h1_msg = 1 ;
  4024. $h1_fold = 'FoldFoo';
  4025. $h1_size = 9 ;
  4026. $h1_flags = '' ;
  4027. $h1_idate = '10-Jul-2015 09:00:00 +0200' ;
  4028. $h1_fir_ref = {} ;
  4029. $string_ref = \$string ;
  4030. $imapT->mock( 'message_to_file',
  4031. sub {
  4032. my ( $imap, $string_ref, $msg ) = @_ ;
  4033. ${$string_ref} = 'blablabla' ;
  4034. return length ${$string_ref} ;
  4035. }
  4036. ) ;
  4037. is( 9, message_for_host2( $sync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
  4038. q{message_for_host2: msg 1 == "blablabla", length} ) ;
  4039. is( 'blablabla', $string, q{message_for_host2: msg 1 == "blablabla", value} ) ;
  4040. # so far so good
  4041. # now the --pipemess stuff
  4042. SKIP: {
  4043. Readonly my $NB_WIN_tests_message_for_host2 => 0 ;
  4044. skip( 'Not on MSWin32', $NB_WIN_tests_message_for_host2 ) if ('MSWin32' ne $OSNAME) ;
  4045. # Windows
  4046. # "type" command does not accept redirection of STDIN with <
  4047. # "sort" does
  4048. } ;
  4049. SKIP: {
  4050. Readonly my $NB_UNX_tests_message_for_host2 => 6 ;
  4051. skip( 'Not on Unix', $NB_UNX_tests_message_for_host2 ) if ('MSWin32' eq $OSNAME) ;
  4052. # Unix
  4053. # no change by cat
  4054. @pipemess = ( 'cat' ) ;
  4055. is( 9, message_for_host2( $sync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
  4056. q{message_for_host2: --pipemess 'cat', length} ) ;
  4057. is( 'blablabla', $string, q{message_for_host2: --pipemess 'cat', value} ) ;
  4058. # failure by false
  4059. @pipemess = ( 'false' ) ;
  4060. is( undef, message_for_host2( $sync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
  4061. q{message_for_host2: --pipemess 'false', length} ) ;
  4062. is( undef, $string, q{message_for_host2: --pipemess 'false', value} ) ;
  4063. # failure by true since no output
  4064. @pipemess = ( 'true' ) ;
  4065. is( undef, message_for_host2( $sync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
  4066. q{message_for_host2: --pipemess 'true', length} ) ;
  4067. is( undef, $string, q{message_for_host2: --pipemess 'true', value} ) ;
  4068. }
  4069. return ;
  4070. }
  4071. sub length_ref {
  4072. my $string_ref = shift ;
  4073. my $string_len = defined ${ $string_ref } ? length( ${ $string_ref } ) : q{} ; # length or empty string
  4074. return $string_len ;
  4075. }
  4076. sub tests_length_ref {
  4077. my $notdefined ;
  4078. is( q{}, length_ref( \$notdefined ), q{length_ref: value not defined} ) ;
  4079. my $notref ;
  4080. is( q{}, length_ref( $notref ), q{length_ref: param not a ref} ) ;
  4081. my $lala = 'lala' ;
  4082. is( 4, length_ref( \$lala ), q{length_ref: lala length == 4} ) ;
  4083. is( 4, length_ref( \'lili' ), q{length_ref: lili length == 4} ) ;
  4084. return ;
  4085. }
  4086. sub date_for_host2 {
  4087. my( $h1_msg, $h1_idate ) = @_ ;
  4088. my $h1_date = q{} ;
  4089. if ( $syncinternaldates ) {
  4090. $h1_date = $h1_idate ;
  4091. $debug and myprint( "internal date from host1: [$h1_date]\n" ) ;
  4092. $h1_date = good_date( $h1_date ) ;
  4093. $debug and myprint( "internal date from host1: [$h1_date] (fixed)\n" ) ;
  4094. }
  4095. if ( $idatefromheader ) {
  4096. $h1_date = $imap1->get_header( $h1_msg, 'Date' ) ;
  4097. $debug and myprint( "header date from host1: [$h1_date]\n" ) ;
  4098. $h1_date = good_date( $h1_date ) ;
  4099. $debug and myprint( "header date from host1: [$h1_date] (fixed)\n" ) ;
  4100. }
  4101. return( $h1_date ) ;
  4102. }
  4103. sub flags_for_host2 {
  4104. my( $h1_flags, $permanentflags2 ) = @_ ;
  4105. # RFC 2060: This flag can not be altered by any client
  4106. $h1_flags =~ s@\\Recent\s?@@xgi ;
  4107. my $h1_flags_re ;
  4108. if ( @regexflag and defined( $h1_flags_re = flags_regex( $h1_flags ) ) ) {
  4109. $h1_flags = $h1_flags_re ;
  4110. }
  4111. $h1_flags = flagscase( $h1_flags ) if $flagscase ;
  4112. $h1_flags = flags_filter( $h1_flags, $permanentflags2) if ( $permanentflags2 and $filterflags ) ;
  4113. return( $h1_flags ) ;
  4114. }
  4115. sub subject {
  4116. my $string = shift ;
  4117. my $subject = q{} ;
  4118. my $header = extract_header( $string ) ;
  4119. if( $header =~ m/^Subject:\s*([^\n\r]*)\r?$/msx ) {
  4120. #myprint( "MMM[$1]\n" ) ;
  4121. $subject = $1 ;
  4122. }
  4123. return( $subject ) ;
  4124. }
  4125. sub tests_subject {
  4126. ok( q{} eq subject( q{} ), 'subject: null') ;
  4127. ok( 'toto le hero' eq subject( 'Subject: toto le hero' ), 'subject: toto le hero') ;
  4128. ok( 'toto le hero' eq subject( 'Subject:toto le hero' ), 'subject: toto le hero blank') ;
  4129. ok( 'toto le hero' eq subject( "Subject:toto le hero\r\n" ), 'subject: toto le hero\r\n') ;
  4130. my $MESS ;
  4131. $MESS = <<'EOF';
  4132. From: lalala
  4133. Subject: toto le hero
  4134. Date: zzzzzz
  4135. Boogie boogie
  4136. EOF
  4137. ok( 'toto le hero' eq subject( $MESS ), 'subject: toto le hero 2') ;
  4138. $MESS = <<'EOF';
  4139. Subject: toto le hero
  4140. From: lalala
  4141. Date: zzzzzz
  4142. Boogie boogie
  4143. EOF
  4144. ok( 'toto le hero' eq subject( $MESS ), 'subject: toto le hero 3') ;
  4145. $MESS = <<'EOF';
  4146. From: lalala
  4147. Subject: cuicui
  4148. Date: zzzzzz
  4149. Subject: toto le hero
  4150. EOF
  4151. ok( 'cuicui' eq subject( $MESS ), 'subject: cuicui') ;
  4152. $MESS = <<'EOF';
  4153. From: lalala
  4154. Date: zzzzzz
  4155. Subject: toto le hero
  4156. EOF
  4157. ok( q{} eq subject( $MESS ), 'subject: null but body could') ;
  4158. return ;
  4159. }
  4160. # GlobVar
  4161. # $dry
  4162. # $max_msg_size_in_bytes
  4163. # $imap2
  4164. # $imap1
  4165. # $total_bytes_error
  4166. # $h1_nb_msg_processed
  4167. # $h2_uidguess
  4168. # $total_bytes_transferred
  4169. # $nb_msg_transferred
  4170. # $begin_transfer_time
  4171. # $time_spent
  4172. # ...
  4173. #
  4174. #
  4175. sub append_message_on_host2 {
  4176. my( $string_ref, $h1_fold, $h1_msg, $string_len, $h2_fold, $h1_size, $h1_flags, $h1_date, $cache_dir ) = @_ ;
  4177. if ( $sync->{debugmemory} ) {
  4178. myprintf("A1: Memory consumption: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI) ;
  4179. }
  4180. my $new_id ;
  4181. if ( ! $dry ) {
  4182. $max_msg_size_in_bytes = max( $h1_size, $max_msg_size_in_bytes ) ;
  4183. $new_id = $imap2->append_string( $h2_fold, ${ $string_ref }, $h1_flags, $h1_date ) ;
  4184. if ( $sync->{debugmemory} ) {
  4185. myprintf("A2: Memory consumption: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI) ;
  4186. }
  4187. if ( ! $new_id){
  4188. my $subject = subject( ${ $string_ref } ) ;
  4189. my $error_imap = $imap2->LastError || q{} ;
  4190. my $error = "- msg $h1_fold/$h1_msg {$string_len} couldn't append (Subject:[$subject]) to folder $h2_fold: $error_imap\n" ;
  4191. errors_incr( $sync, $error ) ;
  4192. $total_bytes_error += $h1_size;
  4193. $h1_nb_msg_processed +=1 ;
  4194. return ;
  4195. }
  4196. else{
  4197. # good
  4198. # $new_id is an id if the IMAP server has the
  4199. # UIDPLUS capability else just a ref
  4200. if ( $new_id !~ m{^\d+$}x ) {
  4201. $new_id = lastuid( $imap2, $h2_fold, $h2_uidguess ) ;
  4202. }
  4203. $h2_uidguess += 1 ;
  4204. $total_bytes_transferred += $h1_size ;
  4205. $nb_msg_transferred += 1 ;
  4206. $h1_nb_msg_processed +=1 ;
  4207. my $time_spent = timesince( $begin_transfer_time ) ;
  4208. my $rate = bytes_display_string( $total_bytes_transferred / $time_spent ) ;
  4209. my $eta = eta( $time_spent,
  4210. $h1_nb_msg_processed, $h1_nb_msg_start, $nb_msg_transferred ) ;
  4211. my $amount_transferred = bytes_display_string( $total_bytes_transferred ) ;
  4212. myprintf( "msg %s/%-19s copied to %s/%-10s %.2f msgs/s %s/s %s copied %s\n",
  4213. $h1_fold, "$h1_msg {$string_len}", $h2_fold, $new_id, $nb_msg_transferred/$time_spent, $rate,
  4214. $amount_transferred,
  4215. $eta );
  4216. sleep_if_needed( $time_spent, $total_bytes_transferred, $nb_msg_transferred ) ;
  4217. if ( $usecache and $cacheaftercopy and $new_id =~ m{^\d+$}x ) {
  4218. $debugcache and myprint( "touch $cache_dir/${h1_msg}_$new_id\n" ) ;
  4219. touch( "$cache_dir/${h1_msg}_$new_id" )
  4220. or croak( "Couldn't touch $cache_dir/${h1_msg}_$new_id" ) ;
  4221. }
  4222. if ( $delete ) {
  4223. delete_message_on_host1( $h1_msg, $h1_fold ) ;
  4224. }
  4225. #myprint( "PRESS ENTER" ) and my $a = <> ;
  4226. return( $new_id ) ;
  4227. }
  4228. }
  4229. else{
  4230. # NOOP to avoid timeout on large folders.
  4231. $imap2->noop( ) ;
  4232. $nb_msg_skipped_dry_mode += 1 ;
  4233. $h1_nb_msg_processed +=1 ;
  4234. }
  4235. return ;
  4236. }
  4237. sub sleep_if_needed {
  4238. my( $time_spent, $total_bytes_transferred, $nb_msg_transferred ) = @_ ;
  4239. my $sleep_max_messages = sleep_max_messages( $nb_msg_transferred, $time_spent, $maxmessagespersecond ) ;
  4240. my $sleep_max_bytes = sleep_max_bytes( $total_bytes_transferred, $time_spent, $maxbytespersecond ) ;
  4241. my $sleep_max = max( $sleep_max_messages, $sleep_max_bytes ) ;
  4242. if ( $sleep_max > 0 ) {
  4243. myprintf( "sleeping %.2f s\n", $sleep_max ) ;
  4244. sleep $sleep_max ;
  4245. }
  4246. return ;
  4247. }
  4248. sub sleep_max_messages {
  4249. # how long we have to sleep to go under max_messages_per_second
  4250. my( $nb_msg_transferred, $time_spent, $maxmessagespersecond ) = @_ ;
  4251. if ( ( not defined $maxmessagespersecond ) or $maxmessagespersecond <= 0 ) { return( 0 ) } ;
  4252. my $sleep = ( $nb_msg_transferred / $maxmessagespersecond ) - $time_spent ;
  4253. # the sleep must be positive
  4254. return( max( 0, $sleep ) ) ;
  4255. }
  4256. sub tests_sleep_max_messages {
  4257. ok( 0 == sleep_max_messages( 4, 2, undef ), 'sleep_max_messages: maxmessagespersecond = undef') ;
  4258. ok( 0 == sleep_max_messages( 4, 2, 0 ), 'sleep_max_messages: maxmessagespersecond = 0') ;
  4259. ok( 0 == sleep_max_messages( 4, 2, $MINUS_ONE ), 'sleep_max_messages: maxmessagespersecond = -1') ;
  4260. ok( 0 == sleep_max_messages( 4, 2, 2 ), 'sleep_max_messages: maxmessagespersecond = 2 max reached') ;
  4261. ok( 2 == sleep_max_messages( 8, 2, 2 ), 'sleep_max_messages: maxmessagespersecond = 2 max over') ;
  4262. ok( 0 == sleep_max_messages( 2, 2, 2 ), 'sleep_max_messages: maxmessagespersecond = 2 max not reached') ;
  4263. return ;
  4264. }
  4265. sub sleep_max_bytes {
  4266. # how long we have to sleep to go under max_bytes_per_second
  4267. my( $total_bytes_transferred, $time_spent, $maxbytespersecond ) = @_ ;
  4268. if ( ( not defined $maxbytespersecond ) or $maxbytespersecond <= 0 ) { return( 0 ) } ;
  4269. my $sleep = ( $total_bytes_transferred / $maxbytespersecond ) - $time_spent ;
  4270. # the sleep must be positive
  4271. return( max( 0, $sleep ) ) ;
  4272. }
  4273. sub tests_sleep_max_bytes {
  4274. ok( 0 == sleep_max_bytes( 4000, 2, undef ), 'sleep_max_bytes: maxbytespersecond = undef') ;
  4275. ok( 0 == sleep_max_bytes( 4000, 2, 0 ), 'sleep_max_bytes: maxbytespersecond = 0') ;
  4276. ok( 0 == sleep_max_bytes( 4000, 2, $MINUS_ONE ), 'sleep_max_bytes: maxbytespersecond = -1') ;
  4277. ok( 0 == sleep_max_bytes( 4000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2 max reached') ;
  4278. ok( 2 == sleep_max_bytes( 8000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2 max over') ;
  4279. ok( 0 == sleep_max_bytes( 2000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2 max not reached') ;
  4280. return ;
  4281. }
  4282. # 6 GlobVar: $dry_message $dry $imap1 $h1_nb_msg_deleted $expunge $expunge1
  4283. sub delete_message_on_host1 {
  4284. my( $h1_msg, $h1_fold ) = @_ ;
  4285. my $expunge_message = q{} ;
  4286. $expunge_message = 'and expunged' if ( $expungeaftereach and ( $expunge or $expunge1 ) ) ;
  4287. myprint( "Host1 msg $h1_fold/$h1_msg marked deleted $expunge_message $dry_message\n" ) ;
  4288. if ( ! $dry ) {
  4289. $imap1->delete_message( $h1_msg ) ;
  4290. $h1_nb_msg_deleted += 1 ;
  4291. $imap1->expunge( ) if ( $expungeaftereach and ( $expunge or $expunge1 ) ) ;
  4292. }
  4293. return ;
  4294. }
  4295. sub eta {
  4296. my( $my_time_spent, $h1_nb_processed, $h1_nb_msg_start, $nb_transferred ) = @_ ;
  4297. return( q{} ) if not $foldersizes ;
  4298. my $time_remaining = time_remaining( $my_time_spent, $h1_nb_processed, $h1_nb_msg_start, $nb_transferred ) ;
  4299. my $nb_msg_remaining = $h1_nb_msg_start - $h1_nb_processed ;
  4300. my $eta_date = localtime( time + $time_remaining ) ;
  4301. return( mysprintf( 'ETA: %s %1.0f s %s/%s msgs left', $eta_date, $time_remaining, $nb_msg_remaining, $h1_nb_msg_start ) ) ;
  4302. }
  4303. sub time_remaining {
  4304. my( $my_time_spent, $h1_nb_processed, $h1_nb_msg_start, $nb_transferred ) = @_ ;
  4305. my $time_remaining = ( $my_time_spent / $nb_transferred ) * ( $h1_nb_msg_start - $h1_nb_processed ) ;
  4306. return( $time_remaining ) ;
  4307. }
  4308. sub tests_time_remaining {
  4309. ok( 1 == time_remaining( 1, 1, 2, 1 ), 'time_remaining: 1, 1, 2, 1 -> 1' ) ;
  4310. ok( 1 == time_remaining( 9, 9, 10, 9 ), 'time_remaining: 9, 9, 10, 9 -> 1' ) ;
  4311. ok( 9 == time_remaining( 1, 1, 10, 1 ), 'time_remaining: 1, 1, 10, 1 -> 1' ) ;
  4312. return ;
  4313. }
  4314. sub cache_map {
  4315. my ( $cache_files_ref, $h1_msgs_ref, $h2_msgs_ref ) = @_;
  4316. my ( %map1_2, %map2_1, %done2 ) ;
  4317. my $h1_msgs_hash_ref = { } ;
  4318. my $h2_msgs_hash_ref = { } ;
  4319. @{ $h1_msgs_hash_ref }{ @{ $h1_msgs_ref } } = ( ) ;
  4320. @{ $h2_msgs_hash_ref }{ @{ $h2_msgs_ref } } = ( ) ;
  4321. foreach my $file ( sort @{ $cache_files_ref } ) {
  4322. $debugcache and myprint( "C12: $file\n" ) ;
  4323. ( $uid1, $uid2 ) = match_a_cache_file( $file ) ;
  4324. if ( exists( $h1_msgs_hash_ref->{ defined $uid1 ? $uid1 : q{} } )
  4325. and exists( $h2_msgs_hash_ref->{ defined $uid2 ? $uid2 : q{} } ) ) {
  4326. # keep only the greatest uid2
  4327. # 130_2301 and
  4328. # 130_231 => keep only 130 -> 2301
  4329. # keep only the greatest uid1
  4330. # 1601_260 and
  4331. # 161_260 => keep only 1601 -> 260
  4332. my $max_uid2 = max( $uid2, $map1_2{ $uid1 } || $MINUS_ONE ) ;
  4333. if ( exists $done2{ $max_uid2 } ) {
  4334. if ( $done2{ $max_uid2 } < $uid1 ) {
  4335. $map1_2{ $uid1 } = $max_uid2 ;
  4336. delete $map1_2{ $done2{ $max_uid2 } } ;
  4337. $done2{ $max_uid2 } = $uid1 ;
  4338. }
  4339. }else{
  4340. $map1_2{ $uid1 } = $max_uid2 ;
  4341. $done2{ $max_uid2 } = $uid1 ;
  4342. }
  4343. };
  4344. }
  4345. %map2_1 = reverse %map1_2 ;
  4346. return( \%map1_2, \%map2_1) ;
  4347. }
  4348. sub tests_cache_map {
  4349. #$debugcache = 1 ;
  4350. my @cache_files = qw (
  4351. 100_200
  4352. 101_201
  4353. 120_220
  4354. 142_242
  4355. 143_243
  4356. 177_277
  4357. 177_278
  4358. 177_279
  4359. 155_255
  4360. 180_280
  4361. 181_280
  4362. 182_280
  4363. 130_231
  4364. 130_2301
  4365. 161_260
  4366. 1601_260
  4367. ) ;
  4368. my $msgs_1 = [120, 142, 143, 144, 161, 1601, 177, 182, 130 ];
  4369. my $msgs_2 = [ 242, 243, 260, 299, 377, 279, 255, 280, 231, 2301 ];
  4370. my( $c12, $c21 ) ;
  4371. ok( ( $c12, $c21 ) = cache_map( \@cache_files, $msgs_1, $msgs_2 ), 'cache_map: 02' );
  4372. my $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
  4373. my $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
  4374. ok( 0 == compare_lists( [ 130, 142, 143, 177, 182, 1601 ], $a1 ), 'cache_map: 03' );
  4375. ok( 0 == compare_lists( [ 242, 243, 260, 279, 280, 2301 ], $a2 ), 'cache_map: 04' );
  4376. ok( ! $c12->{161}, 'cache_map: ! 161 -> 260' );
  4377. ok( 260 == $c12->{1601}, 'cache_map: 1601 -> 260' );
  4378. ok( 2301 == $c12->{130}, 'cache_map: 130 -> 2301' );
  4379. #myprint( $c12->{1601}, "\n" ) ;
  4380. return ;
  4381. }
  4382. sub cache_dir_fix {
  4383. my $cache_dir = shift ;
  4384. $cache_dir =~ s/([;<>\*\|`&\$!#\(\)\[\]\{\}:'"\\])/\\$1/xg ;
  4385. #myprint( "cache_dir_fix: $cache_dir\n" ) ;
  4386. return( $cache_dir ) ;
  4387. }
  4388. sub tests_cache_dir_fix {
  4389. ok( 'lalala' eq cache_dir_fix('lalala'), 'cache_dir_fix: lalala -> lalala' );
  4390. ok( 'ii\\\\ii' eq cache_dir_fix('ii\ii'), 'cache_dir_fix: ii\ii -> ii\\\\ii' );
  4391. ok( 'ii@ii' eq cache_dir_fix('ii@ii'), 'cache_dir_fix: ii@ii -> ii@ii' );
  4392. ok( 'ii@ii\\:ii' eq cache_dir_fix('ii@ii:ii'), 'cache_dir_fix: ii@ii:ii -> ii@ii\\:ii' );
  4393. ok( 'i\\\\i\\\\ii' eq cache_dir_fix('i\i\ii'), 'cache_dir_fix: i\i\ii -> i\\\\i\\\\ii' );
  4394. ok( 'i\\\\ii' eq cache_dir_fix('i\\ii'), 'cache_dir_fix: i\\ii -> i\\\\\\\\ii' );
  4395. ok( '\\\\ ' eq cache_dir_fix('\\ '), 'cache_dir_fix: \\ -> \\\\\ ' );
  4396. ok( '\\\\ ' eq cache_dir_fix('\ '), 'cache_dir_fix: \ -> \\\\\ ' );
  4397. ok( '\[bracket\]' eq cache_dir_fix('[bracket]'), 'cache_dir_fix: [bracket] -> \[bracket\]' );
  4398. return ;
  4399. }
  4400. sub cache_dir_fix_win {
  4401. my $cache_dir = shift ;
  4402. $cache_dir =~ s/(\[|\])/[$1]/xg ;
  4403. #myprint( "cache_dir_fix_win: $cache_dir\n" ) ;
  4404. return( $cache_dir ) ;
  4405. }
  4406. sub tests_cache_dir_fix_win {
  4407. ok( 'lalala' eq cache_dir_fix_win('lalala'), 'cache_dir_fix_win: lalala -> lalala' );
  4408. ok( '[[]bracket[]]' eq cache_dir_fix_win('[bracket]'), 'cache_dir_fix_win: [bracket] -> [[]bracket[]]' );
  4409. return ;
  4410. }
  4411. sub get_cache {
  4412. my ( $cache_dir, $h1_msgs_ref, $h2_msgs_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) = @_;
  4413. $debugcache and myprint( "Entering get_cache\n" ) ;
  4414. -d $cache_dir or return( undef ); # exit if cache directory doesn't exist
  4415. $debugcache and myprint( "cache_dir : $cache_dir\n" ) ;
  4416. if ( 'MSWin32' ne $OSNAME ) {
  4417. $cache_dir = cache_dir_fix( $cache_dir ) ;
  4418. }else{
  4419. $cache_dir = cache_dir_fix_win( $cache_dir ) ;
  4420. }
  4421. $debugcache and myprint( "cache_dir_fix: $cache_dir\n" ) ;
  4422. my @cache_files = bsd_glob( "$cache_dir/*" ) ;
  4423. #$debugcache and myprint( "cache_files: [@cache_files]\n" ) ;
  4424. $debugcache and myprint( 'cache_files: ', scalar @cache_files , " files found\n" ) ;
  4425. my( $cache_1_2_ref, $cache_2_1_ref )
  4426. = cache_map( \@cache_files, $h1_msgs_ref, $h2_msgs_ref ) ;
  4427. clean_cache( \@cache_files, $cache_1_2_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) ;
  4428. $debugcache and myprint( "Exiting get_cache\n" ) ;
  4429. return( $cache_1_2_ref, $cache_2_1_ref ) ;
  4430. }
  4431. sub tests_get_cache {
  4432. ok( not( get_cache('/cache_no_exist') ), 'get_cache: /cache_no_exist' );
  4433. ok( ( not -d 'W/tmp/cache/F1/F2' or rmtree( 'W/tmp/cache/F1/F2' )), 'get_cache: rmtree W/tmp/cache/F1/F2' ) ;
  4434. ok( mkpath( 'W/tmp/cache/F1/F2' ), 'get_cache: mkpath W/tmp/cache/F1/F2' ) ;
  4435. my @test_files_cache = ( qw(
  4436. W/tmp/cache/F1/F2/100_200
  4437. W/tmp/cache/F1/F2/101_201
  4438. W/tmp/cache/F1/F2/120_220
  4439. W/tmp/cache/F1/F2/142_242
  4440. W/tmp/cache/F1/F2/143_243
  4441. W/tmp/cache/F1/F2/177_277
  4442. W/tmp/cache/F1/F2/177_377
  4443. W/tmp/cache/F1/F2/177_777
  4444. W/tmp/cache/F1/F2/155_255
  4445. ) ) ;
  4446. ok( touch( @test_files_cache ), 'get_cache: touch W/tmp/cache/F1/F2/...' ) ;
  4447. # on cache: 100_200 101_201 142_242 143_243 177_277 177_377 177_777 155_255
  4448. # on live:
  4449. my $msgs_1 = [120, 142, 143, 144, 177 ];
  4450. my $msgs_2 = [ 242, 243, 299, 377, 777, 255 ];
  4451. my $msgs_all_1 = { 120 => 0, 142 => 0, 143 => 0, 144 => 0, 177 => 0 } ;
  4452. my $msgs_all_2 = { 242 => 0, 243 => 0, 299 => 0, 377 => 0, 777 => 0, 255 => 0 } ;
  4453. my( $c12, $c21 ) ;
  4454. ok( ( $c12, $c21 ) = get_cache( 'W/tmp/cache/F1/F2', $msgs_1, $msgs_2, $msgs_all_1, $msgs_all_2 ), 'get_cache: 02' );
  4455. my $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
  4456. my $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
  4457. ok( 0 == compare_lists( [ 142, 143, 177 ], $a1 ), 'get_cache: 03' );
  4458. ok( 0 == compare_lists( [ 242, 243, 777 ], $a2 ), 'get_cache: 04' );
  4459. ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 142_242');
  4460. ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 143_243');
  4461. ok( ! -f 'W/tmp/cache/F1/F2/100_200', 'get_cache: file removed 100_200');
  4462. ok( ! -f 'W/tmp/cache/F1/F2/101_201', 'get_cache: file removed 101_201');
  4463. # test clean_cache executed
  4464. $maxage = 2 ;
  4465. ok( touch(@test_files_cache), 'get_cache: touch W/tmp/cache/F1/F2/...' ) ;
  4466. ok( ( $c12, $c21 ) = get_cache('W/tmp/cache/F1/F2', $msgs_1, $msgs_2, $msgs_all_1, $msgs_all_2 ), 'get_cache: 02' );
  4467. ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 142_242');
  4468. ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 143_243');
  4469. ok( ! -f 'W/tmp/cache/F1/F2/100_200', 'get_cache: file NOT removed 100_200');
  4470. ok( ! -f 'W/tmp/cache/F1/F2/101_201', 'get_cache: file NOT removed 101_201');
  4471. # strange files
  4472. #$debugcache = 1 ;
  4473. $maxage = undef ;
  4474. ok( ( not -d 'W/tmp/cache/rr\uee' or rmtree( 'W/tmp/cache/rr\uee' )), 'get_cache: rmtree W/tmp/cache/rr\uee' ) ;
  4475. ok( mkpath( 'W/tmp/cache/rr\uee' ), 'get_cache: mkpath W/tmp/cache/rr\uee' ) ;
  4476. @test_files_cache = ( qw(
  4477. W/tmp/cache/rr\uee/100_200
  4478. W/tmp/cache/rr\uee/101_201
  4479. W/tmp/cache/rr\uee/120_220
  4480. W/tmp/cache/rr\uee/142_242
  4481. W/tmp/cache/rr\uee/143_243
  4482. W/tmp/cache/rr\uee/177_277
  4483. W/tmp/cache/rr\uee/177_377
  4484. W/tmp/cache/rr\uee/177_777
  4485. W/tmp/cache/rr\uee/155_255
  4486. ) ) ;
  4487. ok( touch(@test_files_cache), 'get_cache: touch strange W/tmp/cache/...' ) ;
  4488. # on cache: 100_200 101_201 142_242 143_243 177_277 177_377 177_777 155_255
  4489. # on live:
  4490. $msgs_1 = [120, 142, 143, 144, 177 ] ;
  4491. $msgs_2 = [ 242, 243, 299, 377, 777, 255 ] ;
  4492. $msgs_all_1 = { 120 => q{}, 142 => q{}, 143 => q{}, 144 => q{}, 177 => q{} } ;
  4493. $msgs_all_2 = { 242 => q{}, 243 => q{}, 299 => q{}, 377 => q{}, 777 => q{}, 255 => q{} } ;
  4494. 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' );
  4495. $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
  4496. $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
  4497. ok( 0 == compare_lists( [ 142, 143, 177 ], $a1 ), 'get_cache: strange path 03' );
  4498. ok( 0 == compare_lists( [ 242, 243, 777 ], $a2 ), 'get_cache: strange path 04' );
  4499. ok( -f 'W/tmp/cache/rr\uee/142_242', 'get_cache: strange path file kept 142_242');
  4500. ok( -f 'W/tmp/cache/rr\uee/142_242', 'get_cache: strange path file kept 143_243');
  4501. ok( ! -f 'W/tmp/cache/rr\uee/100_200', 'get_cache: strange path file removed 100_200');
  4502. ok( ! -f 'W/tmp/cache/rr\uee/101_201', 'get_cache: strange path file removed 101_201');
  4503. return ;
  4504. }
  4505. sub match_a_cache_file {
  4506. my $file = shift ;
  4507. my ( $cache_uid1, $cache_uid2 ) ;
  4508. return( ( undef, undef ) ) if ( ! $file ) ;
  4509. if ( $file =~ m{(?:^|/)(\d+)_(\d+)$}x ) {
  4510. $cache_uid1 = $1 ;
  4511. $cache_uid2 = $2 ;
  4512. }
  4513. return( $cache_uid1, $cache_uid2 ) ;
  4514. }
  4515. sub tests_match_a_cache_file {
  4516. my ( $tuid1, $tuid2 ) ;
  4517. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( ), 'match_a_cache_file: no arg' ) ;
  4518. ok( ! defined $tuid1 , 'match_a_cache_file: no arg 1' ) ;
  4519. ok( ! defined $tuid2 , 'match_a_cache_file: no arg 2' ) ;
  4520. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( q{} ), 'match_a_cache_file: empty arg' ) ;
  4521. ok( ! defined $tuid1 , 'match_a_cache_file: empty arg 1' ) ;
  4522. ok( ! defined $tuid2 , 'match_a_cache_file: empty arg 2' ) ;
  4523. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '000_000' ), 'match_a_cache_file: 000_000' ) ;
  4524. ok( '000' eq $tuid1, 'match_a_cache_file: 000_000 1' ) ;
  4525. ok( '000' eq $tuid2, 'match_a_cache_file: 000_000 2' ) ;
  4526. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '123_456' ), 'match_a_cache_file: 123_456' ) ;
  4527. ok( '123' eq $tuid1, 'match_a_cache_file: 123_456 1' ) ;
  4528. ok( '456' eq $tuid2, 'match_a_cache_file: 123_456 2' ) ;
  4529. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '/tmp/truc/123_456' ), 'match_a_cache_file: /tmp/truc/123_456' ) ;
  4530. ok( '123' eq $tuid1, 'match_a_cache_file: /tmp/truc/123_456 1' ) ;
  4531. ok( '456' eq $tuid2, 'match_a_cache_file: /tmp/truc/123_456 2' ) ;
  4532. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '/lala123_456' ), 'match_a_cache_file: NO /lala123_456' ) ;
  4533. ok( ! $tuid1, 'match_a_cache_file: /lala123_456 1' ) ;
  4534. ok( ! $tuid2, 'match_a_cache_file: /lala123_456 2' ) ;
  4535. ok( ( $tuid1, $tuid2 ) = match_a_cache_file( 'la123_456' ), 'match_a_cache_file: NO la123_456' ) ;
  4536. ok( ! $tuid1, 'match_a_cache_file: la123_456 1' ) ;
  4537. ok( ! $tuid2, 'match_a_cache_file: la123_456 2' ) ;
  4538. return ;
  4539. }
  4540. sub clean_cache {
  4541. my ( $cache_files_ref, $cache_1_2_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) = @_ ;
  4542. $debugcache and myprint( "Entering clean_cache\n" ) ;
  4543. $debugcache and myprint( map { "$_ -> " . $cache_1_2_ref->{ $_ } . "\n" } keys %{ $cache_1_2_ref } ) ;
  4544. foreach my $file ( @{ $cache_files_ref } ) {
  4545. $debugcache and myprint( "$file\n" ) ;
  4546. my ( $cache_uid1, $cache_uid2 ) = match_a_cache_file( $file ) ;
  4547. $debugcache and myprint( "u1: $cache_uid1 u2: $cache_uid2 c12: ", $cache_1_2_ref->{ $cache_uid1 } || q{}, "\n") ;
  4548. # or ( ! exists( $cache_1_2_ref->{ $cache_uid1 } ) )
  4549. # or ( ! ( $cache_uid2 == $cache_1_2_ref->{ $cache_uid1 } ) )
  4550. if ( ( not defined $cache_uid1 )
  4551. or ( not defined $cache_uid2 )
  4552. or ( not exists $h1_msgs_all_hash_ref->{ $cache_uid1 } )
  4553. or ( not exists $h2_msgs_all_hash_ref->{ $cache_uid2 } )
  4554. ) {
  4555. $debugcache and myprint( "remove $file\n" ) ;
  4556. unlink $file or myprint( "$!" ) ;
  4557. }
  4558. }
  4559. $debugcache and myprint( "Exiting clean_cache\n" ) ;
  4560. return( 1 ) ;
  4561. }
  4562. sub tests_clean_cache {
  4563. ok( ( not -d 'W/tmp/cache/G1/G2' or rmtree( 'W/tmp/cache/G1/G2' )), 'clean_cache: rmtree W/tmp/cache/G1/G2' ) ;
  4564. ok( mkpath( 'W/tmp/cache/G1/G2' ), 'clean_cache: mkpath W/tmp/cache/G1/G2' ) ;
  4565. my @test_files_cache = ( qw(
  4566. W/tmp/cache/G1/G2/100_200
  4567. W/tmp/cache/G1/G2/101_201
  4568. W/tmp/cache/G1/G2/120_220
  4569. W/tmp/cache/G1/G2/142_242
  4570. W/tmp/cache/G1/G2/143_243
  4571. W/tmp/cache/G1/G2/177_277
  4572. W/tmp/cache/G1/G2/177_377
  4573. W/tmp/cache/G1/G2/177_777
  4574. W/tmp/cache/G1/G2/155_255
  4575. ) ) ;
  4576. ok( touch(@test_files_cache), 'clean_cache: touch W/tmp/cache/G1/G2/...' ) ;
  4577. ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache: 100_200 before' );
  4578. ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache: 142_242 before' );
  4579. ok( -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache: 177_277 before' );
  4580. ok( -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache: 177_377 before' );
  4581. ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache: 177_777 before' );
  4582. ok( -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache: 155_255 before' );
  4583. my $cache = {
  4584. 142 => 242,
  4585. 177 => 777,
  4586. } ;
  4587. my $all_1 = {
  4588. 142 => q{},
  4589. 177 => q{},
  4590. } ;
  4591. my $all_2 = {
  4592. 200 => q{},
  4593. 242 => q{},
  4594. 777 => q{},
  4595. } ;
  4596. ok( clean_cache( \@test_files_cache, $cache, $all_1, $all_2 ), 'clean_cache: ' ) ;
  4597. ok( ! -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache: 100_200 after' );
  4598. ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache: 142_242 after' );
  4599. ok( ! -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache: 177_277 after' );
  4600. ok( ! -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache: 177_377 after' );
  4601. ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache: 177_777 after' );
  4602. ok( ! -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache: 155_255 after' );
  4603. return ;
  4604. }
  4605. sub tests_clean_cache_2 {
  4606. ok( ( not -d 'W/tmp/cache/G1/G2' or rmtree( 'W/tmp/cache/G1/G2' )), 'clean_cache_2: rmtree W/tmp/cache/G1/G2' ) ;
  4607. ok( mkpath( 'W/tmp/cache/G1/G2' ), 'clean_cache_2: mkpath W/tmp/cache/G1/G2' ) ;
  4608. my @test_files_cache = ( qw(
  4609. W/tmp/cache/G1/G2/100_200
  4610. W/tmp/cache/G1/G2/101_201
  4611. W/tmp/cache/G1/G2/120_220
  4612. W/tmp/cache/G1/G2/142_242
  4613. W/tmp/cache/G1/G2/143_243
  4614. W/tmp/cache/G1/G2/177_277
  4615. W/tmp/cache/G1/G2/177_377
  4616. W/tmp/cache/G1/G2/177_777
  4617. W/tmp/cache/G1/G2/155_255
  4618. ) ) ;
  4619. ok( touch(@test_files_cache), 'clean_cache_2: touch W/tmp/cache/G1/G2/...' ) ;
  4620. ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache_2: 100_200 before' );
  4621. ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache_2: 142_242 before' );
  4622. ok( -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache_2: 177_277 before' );
  4623. ok( -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache_2: 177_377 before' );
  4624. ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache_2: 177_777 before' );
  4625. ok( -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache_2: 155_255 before' );
  4626. my $cache = {
  4627. 142 => 242,
  4628. 177 => 777,
  4629. } ;
  4630. my $all_1 = {
  4631. $NUMBER_100 => q{},
  4632. 142 => q{},
  4633. 177 => q{},
  4634. } ;
  4635. my $all_2 = {
  4636. 200 => q{},
  4637. 242 => q{},
  4638. 777 => q{},
  4639. } ;
  4640. ok( clean_cache( \@test_files_cache, $cache, $all_1, $all_2 ), 'clean_cache_2: ' ) ;
  4641. ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache_2: 100_200 after' );
  4642. ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache_2: 142_242 after' );
  4643. ok( ! -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache_2: 177_277 after' );
  4644. ok( ! -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache_2: 177_377 after' );
  4645. ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache_2: 177_777 after' );
  4646. ok( ! -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache_2: 155_255 after' );
  4647. return ;
  4648. }
  4649. sub tests_mkpath {
  4650. ok( 1 == 1, 'tests_mkpath: 1 == 1' ) ;
  4651. SKIP: {
  4652. skip( 'Tests only for Unix', 2 ) if ( 'MSWin32' eq $OSNAME ) ;
  4653. my $long_path_unix = '123456789/' x 30 ;
  4654. ok( (-d "W/tmp/tests/long/$long_path_unix" or mkpath( "W/tmp/tests/long/$long_path_unix" ) ), 'tests_mkpath: mkpath > 300 char' ) ;
  4655. ok( (-d "W/tmp/tests/long/$long_path_unix" and rmtree( 'W/tmp/tests/long/' ) ), 'tests_mkpath: rmtree > 300 char' ) ;
  4656. } ;
  4657. SKIP: {
  4658. skip( 'Tests only for MSWin32', 6 ) if ( 'MSWin32' ne $OSNAME ) ;
  4659. my $long_path_2_prefix = "$tmpdir\\kopano-migration-imap_tests" || '\\\?\\E:\\TEMP\\kopano-migration-imap_tests' ;
  4660. myprint( "long_path_2_prefix: $long_path_2_prefix\n" ) ;
  4661. my $long_path_2 = $long_path_2_prefix . '\\' . '123456789\\' x 10 . 'END' ;
  4662. my $long_path_300 = $long_path_2_prefix . '\\' . '123456789\\' x 30 . 'END' ;
  4663. myprint( "$long_path_2\n" ) ;
  4664. #ok( ( -d $long_path_2_prefix and rmtree( $long_path_2_prefix ) ), 'tests_mkpath: rmtree > 200 char' ) ;
  4665. #ok( ( -d $long_path_2_prefix or mkpath( "\\\\\?\\E:\\\\TEMP\\kopano-migration-imap_tests" ) ), 'tests_mkpath: -d small path 1' ) ;
  4666. ok( ( -d $long_path_2_prefix or mkpath( $long_path_2_prefix ) ), 'tests_mkpath: -d mkpath small path' ) ;
  4667. ok( ( -d $long_path_2_prefix ), 'tests_mkpath: -d mkpath small path done' ) ;
  4668. ok( ( -d $long_path_2 or mkpath( $long_path_2 ) ), 'tests_mkpath: mkpath > 200 char' ) ;
  4669. ok( ( -d $long_path_2 ), 'tests_mkpath: -d mkpath > 200 char done' ) ;
  4670. ok( ( -d $long_path_2_prefix and rmtree( $long_path_2_prefix ) ), 'tests_mkpath: rmtree > 200 char' ) ;
  4671. ok( (! -d $long_path_2_prefix ), 'tests_mkpath: ! -d rmtree done' ) ;
  4672. myprint( "$long_path_300\n" ) ;
  4673. # This one just kill the whole process without a whisper:
  4674. #ok( ( -d $long_path_300 or mkpath( $long_path_300 ) ), 'tests_mkpath: mkpath fails > 300 char' ) ;
  4675. #ok( ( -d $long_path_300 and rmtree( $long_path_300 ) ), 'tests_mkpath: rmtree \ > 300 char' ) ;
  4676. } ;
  4677. return 1 ;
  4678. }
  4679. sub tests_touch {
  4680. ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' )), 'tests_touch: mkpath W/tmp/tests/' ) ;
  4681. ok( 1 == touch( 'W/tmp/tests/lala'), 'tests_touch: W/tmp/tests/lala') ;
  4682. ok( 1 == touch( 'W/tmp/tests/\y'), 'tests_touch: W/tmp/tests/\y') ;
  4683. ok( 0 == touch( '/no/no/no/aaa'), 'tests_touch: not /aaa') ;
  4684. ok( 1 == touch( 'W/tmp/tests/lili', 'W/tmp/tests/lolo'), 'tests_touch: 2 files') ;
  4685. ok( 0 == touch( 'W/tmp/tests/\y', '/no/no/aaa'), 'tests_touch: 2 files, 1 fails' ) ;
  4686. return ;
  4687. }
  4688. sub touch {
  4689. my @files = @_ ;
  4690. my $failures = 0 ;
  4691. foreach my $file ( @files ) {
  4692. my $fh = IO::File->new ;
  4693. if ( $fh->open(">> $file" ) ) {
  4694. $fh->close ;
  4695. }else{
  4696. myprint( "Could not open file $file in write/append mode\n" ) ;
  4697. $failures++ ;
  4698. }
  4699. }
  4700. return( ! $failures );
  4701. }
  4702. sub tests_tmpdir_has_colon_bug {
  4703. ok( 0 == tmpdir_has_colon_bug( q{} ), 'tmpdir_has_colon_bug: ' ) ;
  4704. ok( 0 == tmpdir_has_colon_bug( '/tmp' ), 'tmpdir_has_colon_bug: /tmp' ) ;
  4705. ok( 1 == tmpdir_has_colon_bug( 'C:' ), 'tmpdir_has_colon_bug: C:' ) ;
  4706. ok( 1 == tmpdir_has_colon_bug( 'C:\temp' ), 'tmpdir_has_colon_bug: C:\temp' ) ;
  4707. return( 0 ) ;
  4708. }
  4709. sub tmpdir_has_colon_bug {
  4710. my $path = shift ;
  4711. my $path_filtered = filter_forbidden_characters( $path ) ;
  4712. if ( $path_filtered ne $path ) {
  4713. ( -d $path_filtered ) and myprint( "Path $path was previously mistakely changed to $path_filtered\n" ) ;
  4714. return( 1 ) ;
  4715. }
  4716. return( 0 ) ;
  4717. }
  4718. sub tmpdir_fix_colon_bug {
  4719. my $err = 0 ;
  4720. if ( not (-d $tmpdir and -r _ and -w _) ) {
  4721. myprint( "tmpdir $tmpdir is not valid\n" ) ;
  4722. return( 0 ) ;
  4723. }
  4724. my $cachedir_new = "$tmpdir/kopano-migration-imap_cache" ;
  4725. if ( not tmpdir_has_colon_bug( $cachedir_new ) ) { return( 0 ) } ;
  4726. # check if old cache directory already exists
  4727. my $cachedir_old = filter_forbidden_characters( $cachedir_new ) ;
  4728. if ( not ( -d $cachedir_old ) ) {
  4729. myprint( "Old cache directory $cachedir_new no exists, nothing to do\n" ) ;
  4730. return( 1 ) ;
  4731. }
  4732. # check if new cache directory already exists
  4733. if ( -d $cachedir_new ) {
  4734. myprint( "New fixed cache directory $cachedir_new already exists, not moving the old one $cachedir_old. Fix this manually.\n" ) ;
  4735. return( 0 ) ;
  4736. }else{
  4737. # move the old one to the new place
  4738. myprint( "Moving $cachedir_old to $cachedir_new Do not interrupt this task.\n" ) ;
  4739. File::Copy::Recursive::rmove( $cachedir_old, $cachedir_new )
  4740. or do {
  4741. myprint( "Could not move $cachedir_old to $cachedir_new\n" ) ;
  4742. $err++ ;
  4743. } ;
  4744. # check it succeeded
  4745. if ( -d $cachedir_new and -r _ and -w _ ) {
  4746. myprint( "New fixed cache directory $cachedir_new ok\n" ) ;
  4747. }else{
  4748. myprint( "New fixed cache directory $cachedir_new does not exist\n" ) ;
  4749. $err++ ;
  4750. }
  4751. if ( -d $cachedir_old ) {
  4752. myprint( "Old cache directory $cachedir_old still exists\n" ) ;
  4753. $err++ ;
  4754. }else{
  4755. myprint( "Old cache directory $cachedir_old successfuly moved\n" ) ;
  4756. }
  4757. }
  4758. return( not $err ) ;
  4759. }
  4760. sub tests_cache_folder {
  4761. ok( '/path/fold1/fold2' eq cache_folder( q{}, '/path', 'fold1', 'fold2'), 'cache_folder: /path, fold1, fold2 -> /path/fold1/fold2' ) ;
  4762. ok( '/pa_th/fold1/fold2' eq cache_folder( q{}, '/pa*th', 'fold1', 'fold2'), 'cache_folder: /pa*th, fold1, fold2 -> /path/fold1/fold2' ) ;
  4763. 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' ) ;
  4764. ok( 'D:/path/fold1/fold2' eq cache_folder( 'D:', '/path', 'fold1', 'fold2'), 'cache_folder: /path, fold1, fold2 -> /path/fold1/fold2' ) ;
  4765. ok( 'D:/pa_th/fold1/fold2' eq cache_folder( 'D:', '/pa*th', 'fold1', 'fold2'), 'cache_folder: /pa*th, fold1, fold2 -> /path/fold1/fold2' ) ;
  4766. 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' ) ;
  4767. ok( '//' eq cache_folder( q{}, q{}, q{}, q{}), 'cache_folder: -> //' ) ;
  4768. ok( '//_______' eq cache_folder( q{}, q{}, q{}, '*|?:"<>'), 'cache_folder: *|?:"<> -> //_______' ) ;
  4769. return ;
  4770. }
  4771. sub cache_folder {
  4772. my( $cache_base, $cache_dir, $h1_fold, $h2_fold ) = @_ ;
  4773. my $sep_1 = $h1_sep || '/';
  4774. my $sep_2 = $h2_sep || '/';
  4775. #myprint( "$cache_dir h1_fold $h1_fold sep1 $sep_1 h2_fold $h2_fold sep2 $sep_2\n" ) ;
  4776. $h1_fold = convert_sep_to_slash( $h1_fold, $sep_1 ) ;
  4777. $h2_fold = convert_sep_to_slash( $h2_fold, $sep_2 ) ;
  4778. my $cache_folder = "$cache_base" . filter_forbidden_characters( "$cache_dir/$h1_fold/$h2_fold" ) ;
  4779. #myprint( "cache_folder [$cache_folder]\n" ) ;
  4780. return( $cache_folder ) ;
  4781. }
  4782. sub filter_forbidden_characters {
  4783. my $string = shift ;
  4784. if ( 'MSWin32' eq $OSNAME ) {
  4785. # Move trailing whitespace to _ " a b /c d " -> " a b_/c d_"
  4786. $string =~ s{\ (/|$)}{_$1}xg ;
  4787. }
  4788. $string =~ s{[\Q*|?:"<>\E]}{_}xg ;
  4789. #myprint( "[$string]\n" ) ;
  4790. return( $string ) ;
  4791. }
  4792. sub tests_filter_forbidden_characters {
  4793. ok( 'a_b' eq filter_forbidden_characters( 'a_b' ), 'filter_forbidden_characters: a_b -> a_b' ) ;
  4794. ok( 'a_b' eq filter_forbidden_characters( 'a*b' ), 'filter_forbidden_characters: a*b -> a_b' ) ;
  4795. ok( 'a_b' eq filter_forbidden_characters( 'a|b' ), 'filter_forbidden_characters: a|b -> a_b' ) ;
  4796. ok( 'a_b' eq filter_forbidden_characters( 'a?b' ), 'filter_forbidden_characters: a?b -> a_b' ) ;
  4797. ok( 'a_______b' eq filter_forbidden_characters( 'a*|?:"<>b' ), 'filter_forbidden_characters: a*|?:"<>b -> a_______b' ) ;
  4798. SKIP: {
  4799. skip( 'Not on MSWin32', 1 ) if ( 'MSWin32' eq $OSNAME ) ;
  4800. ok( ( 'a b ' eq filter_forbidden_characters( 'a b ' ) ), 'filter_forbidden_characters: "a b " -> "a b "' ) ;
  4801. } ;
  4802. SKIP: {
  4803. skip( 'Only on MSWin32', 2 ) if ( 'MSWin32' ne $OSNAME ) ;
  4804. ok( ( ' a b_' eq filter_forbidden_characters( ' a b ' ) ), 'filter_forbidden_characters: "a b " -> "a b_"' ) ;
  4805. ok( ( ' a b_/ c d_' eq filter_forbidden_characters( ' a b / c d ' ) ), 'filter_forbidden_characters: " a b / c d " -> "a b_/ c d_"' ) ;
  4806. } ;
  4807. return ;
  4808. }
  4809. sub convert_sep_to_slash {
  4810. my ( $folder, $sep ) = @_ ;
  4811. $folder =~ s{\Q$sep\E}{/}xg ;
  4812. return( $folder ) ;
  4813. }
  4814. sub tests_convert_sep_to_slash {
  4815. ok(q{} eq convert_sep_to_slash(q{}, '/'), 'convert_sep_to_slash: no folder');
  4816. ok('INBOX' eq convert_sep_to_slash('INBOX', '/'), 'convert_sep_to_slash: INBOX');
  4817. ok('INBOX/foo' eq convert_sep_to_slash('INBOX/foo', '/'), 'convert_sep_to_slash: INBOX/foo');
  4818. ok('INBOX/foo' eq convert_sep_to_slash('INBOX_foo', '_'), 'convert_sep_to_slash: INBOX_foo');
  4819. ok('INBOX/foo/zob' eq convert_sep_to_slash('INBOX_foo_zob', '_'), 'convert_sep_to_slash: INBOX_foo_zob');
  4820. ok('INBOX/foo' eq convert_sep_to_slash('INBOX.foo', '.'), 'convert_sep_to_slash: INBOX.foo');
  4821. ok('INBOX/foo/hi' eq convert_sep_to_slash('INBOX.foo.hi', '.'), 'convert_sep_to_slash: INBOX.foo.hi');
  4822. return ;
  4823. }
  4824. sub tests_regexmess {
  4825. ok( 'blabla' eq regexmess( 'blabla' ), 'regexmess, no regexmess, nothing to do' ) ;
  4826. @regexmess = ( 'lalala' ) ;
  4827. ok( not( defined regexmess( 'popopo' ) ), 'regexmess, bad regex lalala' ) ;
  4828. @regexmess = ( 's/p/Z/g' ) ;
  4829. ok( 'ZoZoZo' eq regexmess( 'popopo' ), 'regexmess, s/p/Z/g' ) ;
  4830. @regexmess = ( 's{c}{C}gxms' ) ;
  4831. ok("H1: abC\nH2: Cde\n\nBody abC"
  4832. eq regexmess( "H1: abc\nH2: cde\n\nBody abc"),
  4833. 'regexmess, c->C');
  4834. @regexmess = ( 's{\AFrom\ }{From:}gxms' ) ;
  4835. ok( q{}
  4836. eq regexmess(q{}),
  4837. 'From mbox 1 add colon blank');
  4838. ok( 'From:<tartanpion@machin.truc>'
  4839. eq regexmess('From <tartanpion@machin.truc>'),
  4840. 'From mbox 2 add colo');
  4841. ok( "\n" . 'From <tartanpion@machin.truc>'
  4842. eq regexmess("\n" . 'From <tartanpion@machin.truc>'),
  4843. 'From mbox 3 add colo') ;
  4844. ok( "From: zzz\n" . 'From <tartanpion@machin.truc>'
  4845. eq regexmess("From zzz\n" . 'From <tartanpion@machin.truc>'),
  4846. 'From mbox 4 add colo') ;
  4847. @regexmess = ( 's{\AFrom\ [^\n]*(\n)?}{}gxms' ) ;
  4848. ok( q{}
  4849. eq regexmess(q{}),
  4850. 'From mbox 1 remove, blank');
  4851. ok( q{}
  4852. eq regexmess('From <tartanpion@machin.truc>'),
  4853. 'From mbox 2 remove');
  4854. ok( "\n" . 'From <tartanpion@machin.truc>'
  4855. eq regexmess("\n" . 'From <tartanpion@machin.truc>'),
  4856. 'From mbox 3 remove');
  4857. #myprint( "[", regexmess("From zzz\n" . 'From <tartanpion@machin.truc>'), "]" ) ;
  4858. ok( q{} . 'From <tartanpion@machin.truc>'
  4859. eq regexmess("From zzz\n" . 'From <tartanpion@machin.truc>'),
  4860. 'From mbox 4 remove');
  4861. ok(
  4862. <<'EOM'
  4863. Date: Sat, 10 Jul 2010 05:34:45 -0700
  4864. From:<tartanpion@machin.truc>
  4865. Hello,
  4866. Bye.
  4867. EOM
  4868. eq regexmess(
  4869. <<'EOM'
  4870. From zzz
  4871. Date: Sat, 10 Jul 2010 05:34:45 -0700
  4872. From:<tartanpion@machin.truc>
  4873. Hello,
  4874. Bye.
  4875. EOM
  4876. ), 'From mbox 5 remove');
  4877. @regexmess = ( 's{\A((?:[^\n]+\n)+|)^Disposition-Notification-To:[^\n]*\n(\r?\n|.*\n\r?\n)}{$1$2}xms' ) ; # SUPER SUPER BEST!
  4878. ok(
  4879. <<'EOM'
  4880. Date: Sat, 10 Jul 2010 05:34:45 -0700
  4881. From:<tartanpion@machin.truc>
  4882. Hello,
  4883. Bye.
  4884. EOM
  4885. eq regexmess(
  4886. <<'EOM'
  4887. Date: Sat, 10 Jul 2010 05:34:45 -0700
  4888. Disposition-Notification-To: Kopano <development@kopano.com>
  4889. From:<tartanpion@machin.truc>
  4890. Hello,
  4891. Bye.
  4892. EOM
  4893. ),
  4894. 'regexmess: 1 Delete header Disposition-Notification-To:');
  4895. ok(
  4896. <<'EOM'
  4897. Date: Sat, 10 Jul 2010 05:34:45 -0700
  4898. From:<tartanpion@machin.truc>
  4899. Hello,
  4900. Bye.
  4901. EOM
  4902. eq regexmess(
  4903. <<'EOM'
  4904. Date: Sat, 10 Jul 2010 05:34:45 -0700
  4905. From:<tartanpion@machin.truc>
  4906. Disposition-Notification-To: Kopano <development@kopano.com>
  4907. Hello,
  4908. Bye.
  4909. EOM
  4910. ),
  4911. 'regexmess: 2 Delete header Disposition-Notification-To:');
  4912. ok(
  4913. <<'EOM'
  4914. Date: Sat, 10 Jul 2010 05:34:45 -0700
  4915. From:<tartanpion@machin.truc>
  4916. Hello,
  4917. Bye.
  4918. EOM
  4919. eq regexmess(
  4920. <<'EOM'
  4921. Disposition-Notification-To: Kopano <development@kopano.com>
  4922. Date: Sat, 10 Jul 2010 05:34:45 -0700
  4923. From:<tartanpion@machin.truc>
  4924. Hello,
  4925. Bye.
  4926. EOM
  4927. ),
  4928. 'regexmess: 3 Delete header Disposition-Notification-To:');
  4929. ok(
  4930. <<'EOM'
  4931. Date: Sat, 10 Jul 2010 05:34:45 -0700
  4932. From:<tartanpion@machin.truc>
  4933. Disposition-Notification-To: Kopano <development@kopano.com>
  4934. Bye.
  4935. EOM
  4936. eq regexmess(
  4937. <<'EOM'
  4938. Disposition-Notification-To: Kopano <development@kopano.com>
  4939. Date: Sat, 10 Jul 2010 05:34:45 -0700
  4940. From:<tartanpion@machin.truc>
  4941. Disposition-Notification-To: Kopano <development@kopano.com>
  4942. Bye.
  4943. EOM
  4944. ),
  4945. 'regexmess: 4 Delete header Disposition-Notification-To:');
  4946. ok(
  4947. <<'EOM'
  4948. Date: Sat, 10 Jul 2010 05:34:45 -0700
  4949. From:<tartanpion@machin.truc>
  4950. Disposition-Notification-To: Kopano <development@kopano.com>
  4951. Bye.
  4952. EOM
  4953. eq regexmess(
  4954. <<'EOM'
  4955. Date: Sat, 10 Jul 2010 05:34:45 -0700
  4956. From:<tartanpion@machin.truc>
  4957. Disposition-Notification-To: Kopano <development@kopano.com>
  4958. Bye.
  4959. EOM
  4960. ),
  4961. 'regexmess: 5 Delete header Disposition-Notification-To:');
  4962. ok(
  4963. <<'EOM'
  4964. Date: Sat, 10 Jul 2010 05:34:45 -0700
  4965. From:<tartanpion@machin.truc>
  4966. Hello,
  4967. Disposition-Notification-To: Kopano <development@kopano.com>
  4968. Bye.
  4969. EOM
  4970. eq regexmess(
  4971. <<'EOM'
  4972. Date: Sat, 10 Jul 2010 05:34:45 -0700
  4973. From:<tartanpion@machin.truc>
  4974. Hello,
  4975. Disposition-Notification-To: Kopano <development@kopano.com>
  4976. Bye.
  4977. EOM
  4978. ),
  4979. 'regexmess: 6 Delete header Disposition-Notification-To:');
  4980. ok(
  4981. <<'EOM'
  4982. Date: Sat, 10 Jul 2010 05:34:45 -0700
  4983. From:<tartanpion@machin.truc>
  4984. Hello,
  4985. Disposition-Notification-To: Kopano <development@kopano.com>
  4986. Bye.
  4987. EOM
  4988. eq regexmess(
  4989. <<'EOM'
  4990. Date: Sat, 10 Jul 2010 05:34:45 -0700
  4991. From:<tartanpion@machin.truc>
  4992. Hello,
  4993. Disposition-Notification-To: Kopano <development@kopano.com>
  4994. Bye.
  4995. EOM
  4996. ),
  4997. 'regexmess: 7 Delete header Disposition-Notification-To:');
  4998. ok(
  4999. <<'EOM'
  5000. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5001. From:<tartanpion@machin.truc>
  5002. Hello,
  5003. Bye.
  5004. EOM
  5005. eq regexmess(
  5006. <<'EOM'
  5007. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5008. From:<tartanpion@machin.truc>
  5009. Hello,
  5010. Bye.
  5011. EOM
  5012. ),
  5013. 'regexmess: 8 Delete header Disposition-Notification-To:');
  5014. ok(
  5015. <<'EOM'
  5016. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5017. From:<tartanpion@machin.truc>
  5018. Hello,
  5019. Disposition-Notification-To: Kopano <development@kopano.com>
  5020. Bye.
  5021. EOM
  5022. eq regexmess(
  5023. <<'EOM'
  5024. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5025. From:<tartanpion@machin.truc>
  5026. Hello,
  5027. Disposition-Notification-To: Kopano <development@kopano.com>
  5028. Bye.
  5029. EOM
  5030. ),
  5031. 'regexmess: 9 Delete header Disposition-Notification-To:');
  5032. ok(
  5033. <<'EOM'
  5034. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5035. From:<tartanpion@machin.truc>
  5036. Hello,
  5037. Disposition-Notification-To: Kopano <development@kopano.com>
  5038. Bye.
  5039. EOM
  5040. eq regexmess(
  5041. <<'EOM'
  5042. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5043. From:<tartanpion@machin.truc>
  5044. Hello,
  5045. Disposition-Notification-To: Kopano <development@kopano.com>
  5046. Bye.
  5047. EOM
  5048. ),
  5049. 'regexmess: 10 Delete header Disposition-Notification-To:');
  5050. ok(
  5051. <<'EOM'
  5052. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5053. From:<tartanpion@machin.truc>
  5054. Hello,
  5055. Disposition-Notification-To: Kopano <development@kopano.com>
  5056. Bye.
  5057. EOM
  5058. eq regexmess(
  5059. <<'EOM'
  5060. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5061. From:<tartanpion@machin.truc>
  5062. Hello,
  5063. Disposition-Notification-To: Kopano <development@kopano.com>
  5064. Bye.
  5065. EOM
  5066. ),
  5067. 'regexmess: 11 Delete header Disposition-Notification-To:');
  5068. ok(
  5069. <<'EOM'
  5070. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5071. From:<tartanpion@machin.truc>
  5072. Hello,
  5073. Disposition-Notification-To: Kopano <development@kopano.com>
  5074. Disposition-Notification-To: Kopano <development@kopano.com>
  5075. Bye.
  5076. EOM
  5077. eq regexmess(
  5078. <<'EOM'
  5079. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5080. From:<tartanpion@machin.truc>
  5081. Hello,
  5082. Disposition-Notification-To: Kopano <development@kopano.com>
  5083. Disposition-Notification-To: Kopano <development@kopano.com>
  5084. Bye.
  5085. EOM
  5086. ),
  5087. 'regexmess: 12 Delete header Disposition-Notification-To:');
  5088. @regexmess = ( 's{\A(.*?(?! ^$))^Disposition-Notification-To:(.*?)$}{$1X-Disposition-Notification-To:$2}igxms' ) ; # BAD!
  5089. @regexmess = ( 's{\A((?:[^\n]+\n)+|)(^Disposition-Notification-To:[^\n]*\n)(\r?\n|.*\n\r?\n)}{$1X-$2$3}ims' ) ;
  5090. ok(
  5091. <<'EOM'
  5092. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5093. From:<tartanpion@machin.truc>
  5094. Hello,
  5095. Disposition-Notification-To: Kopano <development@kopano.com>
  5096. Disposition-Notification-To: Kopano <development@kopano.com>
  5097. Bye.
  5098. EOM
  5099. eq regexmess(
  5100. <<'EOM'
  5101. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5102. From:<tartanpion@machin.truc>
  5103. Hello,
  5104. Disposition-Notification-To: Kopano <development@kopano.com>
  5105. Disposition-Notification-To: Kopano <development@kopano.com>
  5106. Bye.
  5107. EOM
  5108. ),
  5109. 'regexmess: 13 Delete header Disposition-Notification-To:');
  5110. ok(
  5111. <<'EOM'
  5112. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5113. X-Disposition-Notification-To: Kopano <development@kopano.com>
  5114. From:<tartanpion@machin.truc>
  5115. Hello,
  5116. Disposition-Notification-To: Kopano <development@kopano.com>
  5117. Disposition-Notification-To: Kopano <development@kopano.com>
  5118. Bye.
  5119. EOM
  5120. eq regexmess(
  5121. <<'EOM'
  5122. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5123. Disposition-Notification-To: Kopano <development@kopano.com>
  5124. From:<tartanpion@machin.truc>
  5125. Hello,
  5126. Disposition-Notification-To: Kopano <development@kopano.com>
  5127. Disposition-Notification-To: Kopano <development@kopano.com>
  5128. Bye.
  5129. EOM
  5130. ),
  5131. 'regexmess: 14 Delete header Disposition-Notification-To:');
  5132. ok(
  5133. <<'EOM'
  5134. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5135. X-Disposition-Notification-To: Kopano <development@kopano.com>
  5136. From:<tartanpion@machin.truc>
  5137. Hello,
  5138. Bye.
  5139. EOM
  5140. eq regexmess(
  5141. <<'EOM'
  5142. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5143. Disposition-Notification-To: Kopano <development@kopano.com>
  5144. From:<tartanpion@machin.truc>
  5145. Hello,
  5146. Bye.
  5147. EOM
  5148. ),
  5149. 'regexmess: 15 Delete header Disposition-Notification-To:');
  5150. ok(
  5151. <<'EOM'
  5152. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5153. From:<tartanpion@machin.truc>
  5154. X-Disposition-Notification-To: Kopano <development@kopano.com>
  5155. Hello,
  5156. Bye.
  5157. EOM
  5158. eq regexmess(
  5159. <<'EOM'
  5160. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5161. From:<tartanpion@machin.truc>
  5162. Disposition-Notification-To: Kopano <development@kopano.com>
  5163. Hello,
  5164. Bye.
  5165. EOM
  5166. ),
  5167. 'regexmess: 16 Delete header Disposition-Notification-To:');
  5168. ok(
  5169. <<'EOM'
  5170. X-Disposition-Notification-To: Kopano <development@kopano.com>
  5171. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5172. From:<tartanpion@machin.truc>
  5173. Hello,
  5174. Bye.
  5175. EOM
  5176. eq regexmess(
  5177. <<'EOM'
  5178. Disposition-Notification-To: Kopano <development@kopano.com>
  5179. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5180. From:<tartanpion@machin.truc>
  5181. Hello,
  5182. Bye.
  5183. EOM
  5184. ),
  5185. 'regexmess: 17 Delete header Disposition-Notification-To:');
  5186. # regex to play with Date: from the FAQ
  5187. #@regexmess = 's{\A(.*?(?! ^$))^Date:(.*?)$}{$1Date:$2\nX-Date:$2}gxms'
  5188. return ;
  5189. }
  5190. sub regexmess {
  5191. my ( $string ) = @_ ;
  5192. foreach my $regexmess ( @regexmess ) {
  5193. $debug and myprint( "eval \$string =~ $regexmess\n" ) ;
  5194. my $ret = eval "\$string =~ $regexmess ; 1" ;
  5195. #myprint( "eval [$ret]\n" ) ;
  5196. if ( ( not $ret ) or $@ ) {
  5197. myprint( "Error: eval regexmess '$regexmess': $@" ) ;
  5198. return( undef ) ;
  5199. }
  5200. }
  5201. $debug and myprint( "$string\n" ) ;
  5202. return( $string ) ;
  5203. }
  5204. sub tests_skipmess {
  5205. ok( not( defined skipmess( 'blabla' ) ), 'skipmess, no skipmess, no skip' ) ;
  5206. @skipmess = ('[') ;
  5207. ok( not( defined skipmess( 'popopo' ) ), 'skipmess, bad regex [' ) ;
  5208. @skipmess = ('lalala') ;
  5209. ok( not( defined skipmess( 'popopo' ) ), 'skipmess, bad regex lalala' ) ;
  5210. @skipmess = ('/popopo/') ;
  5211. ok( 1 == skipmess( 'popopo' ), 'skipmess, popopo match regex /popopo/' ) ;
  5212. @skipmess = ('/popopo/') ;
  5213. ok( 0 == skipmess( 'rrrrrr' ), 'skipmess, rrrrrr does not match regex /popopo/' ) ;
  5214. @skipmess = ('m{^$}') ;
  5215. ok( 1 == skipmess( q{} ), 'skipmess: empty string yes' ) ;
  5216. ok( 0 == skipmess( 'Hi!' ), 'skipmess: empty string no' ) ;
  5217. @skipmess = ('m{i}') ;
  5218. ok( 1 == skipmess( 'Hi!' ), 'skipmess: i string yes' ) ;
  5219. ok( 0 == skipmess( 'Bye!' ), 'skipmess: i string no' ) ;
  5220. @skipmess = ('m{[\x80-\xff]}') ;
  5221. ok( 0 == skipmess( 'Hi!' ), 'skipmess: i 8bit no' ) ;
  5222. ok( 1 == skipmess( "\xff" ), 'skipmess: \xff 8bit yes' ) ;
  5223. @skipmess = ('m{A}', 'm{B}') ;
  5224. ok( 0 == skipmess( 'Hi!' ), 'skipmess: A or B no' ) ;
  5225. ok( 0 == skipmess( 'lala' ), 'skipmess: A or B no' ) ;
  5226. ok( 0 == skipmess( "\xff" ), 'skipmess: A or B no' ) ;
  5227. ok( 1 == skipmess( 'AB' ), 'skipmess: A or B yes' ) ;
  5228. ok( 1 == skipmess( 'BA' ), 'skipmess: A or B yes' ) ;
  5229. ok( 1 == skipmess( 'AA' ), 'skipmess: A or B yes' ) ;
  5230. ok( 1 == skipmess( 'Ok Bye' ), 'skipmess: A or B yes' ) ;
  5231. @skipmess = ( 'm#\A((?:[^\n]+\n)+|)^Content-Type: Message/Partial;[^\n]*\n(?:\n|.*\n\n)#ism' ) ; # SUPER BEST!
  5232. ok( 1 == skipmess(
  5233. <<'EOM'
  5234. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5235. Content-Type: Message/Partial; blabla
  5236. From:<tartanpion@machin.truc>
  5237. Hello!
  5238. Bye.
  5239. EOM
  5240. ),
  5241. 'skipmess: 1 match Content-Type: Message/Partial' ) ;
  5242. ok( 0 == skipmess(
  5243. <<'EOM'
  5244. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5245. From:<tartanpion@machin.truc>
  5246. Hello!
  5247. Bye.
  5248. EOM
  5249. ),
  5250. 'skipmess: 2 not match Content-Type: Message/Partial' ) ;
  5251. ok( 1 == skipmess(
  5252. <<'EOM'
  5253. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5254. From:<tartanpion@machin.truc>
  5255. Content-Type: Message/Partial; blabla
  5256. Hello!
  5257. Bye.
  5258. EOM
  5259. ),
  5260. 'skipmess: 3 match Content-Type: Message/Partial' ) ;
  5261. ok( 0 == skipmess(
  5262. <<'EOM'
  5263. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5264. From:<tartanpion@machin.truc>
  5265. Hello!
  5266. Content-Type: Message/Partial; blabla
  5267. Bye.
  5268. EOM
  5269. ),
  5270. 'skipmess: 4 not match Content-Type: Message/Partial' ) ;
  5271. ok( 0 == skipmess(
  5272. <<'EOM'
  5273. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5274. From:<tartanpion@machin.truc>
  5275. Hello!
  5276. Content-Type: Message/Partial; blabla
  5277. Bye.
  5278. EOM
  5279. ),
  5280. 'skipmess: 5 not match Content-Type: Message/Partial' ) ;
  5281. ok( 1 == skipmess(
  5282. <<'EOM'
  5283. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5284. Content-Type: Message/Partial; blabla
  5285. From:<tartanpion@machin.truc>
  5286. Hello!
  5287. Content-Type: Message/Partial; blabla
  5288. Bye.
  5289. EOM
  5290. ),
  5291. 'skipmess: 6 match Content-Type: Message/Partial' ) ;
  5292. ok( 1 == skipmess(
  5293. <<'EOM'
  5294. Date: Sat, 10 Jul 2010 05:34:45 -0700
  5295. Content-Type: Message/Partial;
  5296. From:<tartanpion@machin.truc>
  5297. Hello!
  5298. Bye.
  5299. EOM
  5300. ),
  5301. 'skipmess: 7 match Content-Type: Message/Partial' ) ;
  5302. ok( 1 == skipmess(
  5303. <<'EOM'
  5304. Date: Wed, 2 Jul 2014 02:26:40 +0000
  5305. MIME-Version: 1.0
  5306. Content-Type: message/partial;
  5307. id="TAN_U_P<1404267997.00007489ed17>";
  5308. number=3;
  5309. total=3
  5310. 6HQ6Hh3CdXj77qEGixerQ6zHx0OnQ/Cf5On4W0Y6vtU2crABZQtD46Hx1EOh8dDz4+OnTr1G
  5311. Hello!
  5312. Bye.
  5313. EOM
  5314. ),
  5315. 'skipmess: 8 match Content-Type: Message/Partial' ) ;
  5316. ok( 1 == skipmess(
  5317. <<'EOM'
  5318. Return-Path: <development@kopano.com>
  5319. Received: by kopano.com (Postfix, from userid 1000)
  5320. id 21EB12443BF; Mon, 2 Mar 2015 15:38:35 +0100 (CET)
  5321. Subject: test: aethaecohngiexao
  5322. To: <tata@petite.kopano.com>
  5323. X-Mailer: mail (GNU Mailutils 2.2)
  5324. Message-Id: <20150302143835.21EB12443BF@kopano.com>
  5325. Content-Type: message/partial;
  5326. id="TAN_U_P<1404267997.00007489ed17>";
  5327. number=3;
  5328. total=3
  5329. Date: Mon, 2 Mar 2015 15:38:34 +0100 (CET)
  5330. From: development@kopano.com (Gilles LAMIRAL)
  5331. test: aethaecohngiexao
  5332. EOM
  5333. ),
  5334. 'skipmess: 9 match Content-Type: Message/Partial' ) ;
  5335. ok( 1 == skipmess(
  5336. <<'EOM'
  5337. Date: Mon, 2 Mar 2015 15:38:34 +0100 (CET)
  5338. From: development@kopano.com (Gilles LAMIRAL)
  5339. Content-Type: message/partial;
  5340. id="TAN_U_P<1404267997.00007489ed17>";
  5341. number=3;
  5342. total=3
  5343. test: aethaecohngiexao
  5344. EOM
  5345. . "lalala\n" x 3000000
  5346. ),
  5347. 'skipmess: 10 match Content-Type: Message/Partial' ) ;
  5348. ok( 0 == skipmess(
  5349. <<'EOM'
  5350. Date: Mon, 2 Mar 2015 15:38:34 +0100 (CET)
  5351. From: development@kopano.com (Gilles LAMIRAL)
  5352. test: aethaecohngiexao
  5353. EOM
  5354. . "lalala\n" x 3000000
  5355. ),
  5356. 'skipmess: 11 match Content-Type: Message/Partial' ) ;
  5357. ok( 0 == skipmess(
  5358. <<"EOM"
  5359. From: fff\r
  5360. To: fff\r
  5361. Subject: Testing kopano-migration-imap --skipmess\r
  5362. Date: Mon, 22 Aug 2011 08:40:20 +0800\r
  5363. Mime-Version: 1.0\r
  5364. Content-Type: text/plain; charset=iso-8859-1\r
  5365. Content-Transfer-Encoding: 7bit\r
  5366. \r
  5367. EOM
  5368. . qq{!#"$%&'()*+,-./0123456789:;<=>?\@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefg\r\n } x 32730
  5369. ),
  5370. 'skipmess: 12 not match Content-Type: Message/Partial' ) ;
  5371. # Complex regular subexpression recursion limit (32766) exceeded with more lines
  5372. # exit;
  5373. return ;
  5374. }
  5375. sub skipmess {
  5376. my ( $string ) = @_ ;
  5377. my $match ;
  5378. #myprint( "$string\n" ) ;
  5379. foreach my $skipmess ( @skipmess ) {
  5380. $debug and myprint( "eval \$match = \$string =~ $skipmess\n" ) ;
  5381. my $ret = eval "\$match = \$string =~ $skipmess ; 1" ;
  5382. #myprint( "eval [$ret]\n" ) ;
  5383. $debug and myprint( "match [$match]\n" ) ;
  5384. if ( ( not $ret ) or $@ ) {
  5385. myprint( "Error: eval skipmess '$skipmess': $@" ) ;
  5386. return( undef ) ;
  5387. }
  5388. return( $match ) if ( $match ) ;
  5389. }
  5390. return( $match ) ;
  5391. }
  5392. sub tests_bytes_display_string {
  5393. is( 'NA', bytes_display_string( ), 'bytes_display_string: no args => NA' ) ;
  5394. is( 'NA', bytes_display_string( undef ), 'bytes_display_string: undef => NA' ) ;
  5395. is( 'NA', bytes_display_string( 'blabla' ), 'bytes_display_string: blabla => NA' ) ;
  5396. ok( '0.000 KiB' eq bytes_display_string( 0 ), 'bytes_display_string: 0' ) ;
  5397. ok( '0.001 KiB' eq bytes_display_string( 1 ), 'bytes_display_string: 1' ) ;
  5398. ok( '0.010 KiB' eq bytes_display_string( 10 ), 'bytes_display_string: 10' ) ;
  5399. ok( '1.000 MiB' eq bytes_display_string( 1048575 ), 'bytes_display_string: 1048575' ) ;
  5400. ok( '1.000 MiB' eq bytes_display_string( 1048576 ), 'bytes_display_string: 1048576' ) ;
  5401. ok( '1.000 GiB' eq bytes_display_string( 1073741823 ), 'bytes_display_string: 1073741823 ' ) ;
  5402. ok( '1.000 GiB' eq bytes_display_string( 1073741824 ), 'bytes_display_string: 1073741824 ' ) ;
  5403. ok( '1.000 TiB' eq bytes_display_string( 1099511627775 ), 'bytes_display_string: 1099511627775' ) ;
  5404. ok( '1.000 TiB' eq bytes_display_string( 1099511627776 ), 'bytes_display_string: 1099511627776' ) ;
  5405. ok( '1.000 PiB' eq bytes_display_string( 1125899906842623 ), 'bytes_display_string: 1125899906842623' ) ;
  5406. ok( '1.000 PiB' eq bytes_display_string( 1125899906842624 ), 'bytes_display_string: 1125899906842624' ) ;
  5407. ok( '1024.000 PiB' eq bytes_display_string( 1152921504606846975 ), 'bytes_display_string: 1152921504606846975' ) ;
  5408. ok( '1024.000 PiB' eq bytes_display_string( 1152921504606846976 ), 'bytes_display_string: 1152921504606846976' ) ;
  5409. ok( '1048576.000 PiB' eq bytes_display_string( 1180591620717411303424 ), 'bytes_display_string: 1180591620717411303424' ) ;
  5410. #myprint( bytes_display_string( 1180591620717411303424 ), "\n" ) ;
  5411. return ;
  5412. }
  5413. sub bytes_display_string {
  5414. my ( $bytes ) = @_ ;
  5415. my $readable_value = q{} ;
  5416. if ( ! defined( $bytes ) ) {
  5417. return( 'NA' ) ;
  5418. }
  5419. if ( not match_number( $bytes ) ) {
  5420. return( 'NA' ) ;
  5421. }
  5422. SWITCH: {
  5423. if ( abs( $bytes ) < ( 1000 * $KIBI ) ) {
  5424. $readable_value = mysprintf( '%.3f KiB', $bytes / $KIBI) ;
  5425. last SWITCH ;
  5426. }
  5427. if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI ) ) {
  5428. $readable_value = mysprintf( '%.3f MiB', $bytes / ($KIBI * $KIBI) ) ;
  5429. last SWITCH ;
  5430. }
  5431. if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI * $KIBI) ) {
  5432. $readable_value = mysprintf( '%.3f GiB', $bytes / ($KIBI * $KIBI * $KIBI) ) ;
  5433. last SWITCH ;
  5434. }
  5435. if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI * $KIBI * $KIBI) ) {
  5436. $readable_value = mysprintf( '%.3f TiB', $bytes / ($KIBI * $KIBI * $KIBI * $KIBI) ) ;
  5437. last SWITCH ;
  5438. } else {
  5439. $readable_value = mysprintf( '%.3f PiB', $bytes / ($KIBI * $KIBI * $KIBI * $KIBI * $KIBI) ) ;
  5440. }
  5441. # if you have exabytes (EiB) of email to transfer, you have too much email!
  5442. }
  5443. #myprint( "$bytes = $readable_value\n" ) ;
  5444. return( $readable_value ) ;
  5445. }
  5446. sub stats {
  5447. my $sync_loc = shift ;
  5448. if ( ! $sync_loc->{stats} ) {
  5449. return ;
  5450. }
  5451. $timeend = time ;
  5452. my $timediff = $timeend - $sync_loc->{timestart} ;
  5453. my $timeend_str = localtime $timeend ;
  5454. my $memory_consumption = 0 ;
  5455. $memory_consumption = memory_consumption( ) || 0 ;
  5456. my $memory_ratio = ($max_msg_size_in_bytes) ?
  5457. mysprintf('%.1f', $memory_consumption / $max_msg_size_in_bytes) : 'NA' ;
  5458. my $host1_reconnect_count = $imap1->Reconnect_counter() || 0 ;
  5459. my $host2_reconnect_count = $imap2->Reconnect_counter() || 0 ;
  5460. myprint( "++++ Statistics\n" ) ;
  5461. myprint( "Transfer started on : $timestart_str\n" ) ;
  5462. myprint( "Transfer ended on : $timeend_str\n" ) ;
  5463. myprintf( "Transfer time : %.1f sec\n", $timediff ) ;
  5464. myprint( "Folders synced : $h1_folders_wanted_ct/$h1_folders_wanted_nb synced\n" ) ;
  5465. myprint( "Messages transferred : $nb_msg_transferred " ) ;
  5466. myprint( "(could be $nb_msg_skipped_dry_mode without dry mode)" ) if ( $dry ) ;
  5467. myprint( "\n" ) ;
  5468. myprint( "Messages skipped : $nb_msg_skipped\n" ) ;
  5469. myprint( "Messages found duplicate on host1 : $h1_nb_msg_duplicate\n" ) ;
  5470. myprint( "Messages found duplicate on host2 : $h2_nb_msg_duplicate\n" ) ;
  5471. myprint( "Messages void (noheader) on host1 : $h1_nb_msg_noheader\n" ) ;
  5472. myprint( "Messages void (noheader) on host2 : $h2_nb_msg_noheader\n" ) ;
  5473. myprint( "Messages deleted on host1 : $h1_nb_msg_deleted\n" ) ;
  5474. myprint( "Messages deleted on host2 : $h2_nb_msg_deleted\n" ) ;
  5475. myprintf( "Total bytes transferred : %s (%s)\n",
  5476. $total_bytes_transferred,
  5477. bytes_display_string( $total_bytes_transferred ) ) ;
  5478. myprintf( "Total bytes duplicate host1 : %s (%s)\n",
  5479. $h1_total_bytes_duplicate,
  5480. bytes_display_string( $h1_total_bytes_duplicate) ) ;
  5481. myprintf( "Total bytes duplicate host2 : %s (%s)\n",
  5482. $h2_total_bytes_duplicate,
  5483. bytes_display_string( $h2_total_bytes_duplicate) ) ;
  5484. myprintf( "Total bytes skipped : %s (%s)\n",
  5485. $total_bytes_skipped,
  5486. bytes_display_string( $total_bytes_skipped ) ) ;
  5487. myprintf( "Total bytes error : %s (%s)\n",
  5488. $total_bytes_error,
  5489. bytes_display_string( $total_bytes_error ) ) ;
  5490. $timediff ||= 1 ; # No division per 0
  5491. myprintf("Message rate : %.1f messages/s\n", $nb_msg_transferred / $timediff ) ;
  5492. myprintf("Average bandwidth rate : %.1f KiB/s\n", $total_bytes_transferred / $KIBI / $timediff ) ;
  5493. #myprint( "Reconnections to host1 : $host1_reconnect_count\n" ) ;
  5494. #myprint( "Reconnections to host2 : $host2_reconnect_count\n" ) ;
  5495. myprintf("Memory consumption : %.1f MiB\n", $memory_consumption / $KIBI / $KIBI ) ;
  5496. myprintf("Biggest message : %s bytes (%s)\n",
  5497. $max_msg_size_in_bytes,
  5498. bytes_display_string( $max_msg_size_in_bytes) ) ;
  5499. myprint( "Memory/biggest message ratio : $memory_ratio\n" ) ;
  5500. if ( $foldersizesatend and $foldersizes ) {
  5501. my $nb_msg_start_diff = diff_or_NA( $h2_nb_msg_start, $h1_nb_msg_start ) ;
  5502. my $bytes_start_diff = diff_or_NA( $h2_bytes_start, $h1_bytes_start ) ;
  5503. myprintf("Start difference host2 - host1 : %s messages, %s bytes (%s)\n", $nb_msg_start_diff,
  5504. $bytes_start_diff,
  5505. bytes_display_string( $bytes_start_diff ) ) ;
  5506. my $nb_msg_end_diff = diff_or_NA( $h2_nb_msg_end, $h1_nb_msg_end ) ;
  5507. my $bytes_end_diff = diff_or_NA( $h2_bytes_end, $h1_bytes_end ) ;
  5508. myprintf("Final difference host2 - host1 : %s messages, %s bytes (%s)\n", $nb_msg_end_diff,
  5509. $bytes_end_diff,
  5510. bytes_display_string( $bytes_end_diff ) ) ;
  5511. }
  5512. myprint( "Detected $sync->{nb_errors} errors\n\n" ) ;
  5513. return ;
  5514. }
  5515. sub diff_or_NA {
  5516. my( $n1, $n2 ) = @ARG ;
  5517. if ( not defined $n1 or not defined $n2 ) {
  5518. return 'NA' ;
  5519. }
  5520. if ( not match_number( $n1 )
  5521. or not match_number( $n2 ) ) {
  5522. return 'NA' ;
  5523. }
  5524. return( $n1 - $n2 ) ;
  5525. }
  5526. sub match_number {
  5527. my $n = shift @ARG ;
  5528. if ( not defined $n ) {
  5529. return 0 ;
  5530. }
  5531. if ( $n =~ /[0-9]+\.?[0-9]?/ ) {
  5532. return 1 ;
  5533. }
  5534. else {
  5535. return 0 ;
  5536. }
  5537. }
  5538. sub tests_match_number {
  5539. is( 0, match_number( ), 'match_number: no parameters => 0' ) ;
  5540. is( 0, match_number( undef ), 'match_number: undef => 0' ) ;
  5541. is( 0, match_number( 'blabla' ), 'match_number: blabla => 0' ) ;
  5542. is( 1, match_number( 0 ), 'match_number: 0 => 1' ) ;
  5543. is( 1, match_number( 1 ), 'match_number: 1 => 1' ) ;
  5544. is( 1, match_number( 1.0 ), 'match_number: 1.0 => 1' ) ;
  5545. is( 1, match_number( 0.0 ), 'match_number: 0.0 => 1' ) ;
  5546. return ;
  5547. }
  5548. sub tests_diff_or_NA {
  5549. is( 'NA', diff_or_NA( ), 'diff_or_NA: no parameters => NA' ) ;
  5550. is( 'NA', diff_or_NA( undef ), 'diff_or_NA: undef => NA' ) ;
  5551. is( 'NA', diff_or_NA( undef, undef ), 'diff_or_NA: undef undef => NA' ) ;
  5552. is( 'NA', diff_or_NA( undef, 1 ), 'diff_or_NA: undef 1 => NA' ) ;
  5553. is( 'NA', diff_or_NA( 1, undef ), 'diff_or_NA: 1 undef => NA' ) ;
  5554. is( 'NA', diff_or_NA( 'blabla', 1 ), 'diff_or_NA: blabla 1 => NA' ) ;
  5555. is( 'NA', diff_or_NA( 1, 'blabla' ), 'diff_or_NA: 1 blabla => NA' ) ;
  5556. is( 0, diff_or_NA( 1, 1 ), 'diff_or_NA: 1 1 => 0' ) ;
  5557. is( 1, diff_or_NA( 1, 0 ), 'diff_or_NA: 1 0 => 1' ) ;
  5558. is( -1, diff_or_NA( 0, 1 ), 'diff_or_NA: 0 1 => -1' ) ;
  5559. is( 0, diff_or_NA( 1.0, 1 ), 'diff_or_NA: 1.0 1 => 0' ) ;
  5560. is( 1, diff_or_NA( 1.0, 0 ), 'diff_or_NA: 1.0 0 => 1' ) ;
  5561. is( -1, diff_or_NA( 0, 1.0 ), 'diff_or_NA: 0 1.0 => -1' ) ;
  5562. return ;
  5563. }
  5564. sub load_modules {
  5565. if ( $ssl1 or $ssl2 or $tls1 or $tls2) {
  5566. # not yet a "use" statement
  5567. require IO::Socket::SSL ;
  5568. if ( $sync->{inet4} ) {
  5569. IO::Socket::SSL->import( 'inet4' ) ;
  5570. }
  5571. if ( $sync->{inet6} ) {
  5572. IO::Socket::SSL->import( 'inet6' ) ;
  5573. }
  5574. }
  5575. if ( ( ( not( $password1 or $passfile1 ) )
  5576. or (not ( $password2 or $passfile2 ) )
  5577. )
  5578. and ( not $help ) ) {
  5579. # now a "use" statement
  5580. #require Term::ReadKey ;
  5581. }
  5582. return ;
  5583. }
  5584. sub parse_header_msg {
  5585. my ( $imap, $m_uid, $s_heads, $s_fir, $side, $s_hash ) = @_ ;
  5586. my $head = $s_heads->{$m_uid} ;
  5587. my $headnum = scalar keys %{ $head } ;
  5588. $debug and myprint( "$side uid $m_uid head nb pass one: ", $headnum, "\n" ) ;
  5589. if ( ( ! $headnum ) and ( $wholeheaderifneeded ) ){
  5590. myprint( "$side uid $m_uid no header by parse_headers so taking whole header with BODY.PEEK[HEADER]\n" ) ;
  5591. $imap->fetch($m_uid, 'BODY.PEEK[HEADER]' ) ;
  5592. my $whole_header = $imap->_transaction_literals ;
  5593. #myprint( $whole_header ) ;
  5594. $head = decompose_header( $whole_header ) ;
  5595. $headnum = scalar keys %{ $head } ;
  5596. $debug and myprint( "$side uid $m_uid head nb pass two: ", $headnum, "\n" ) ;
  5597. }
  5598. #myprint( Data::Dumper->Dump( [ $head, \%useheader ] ) ) ;
  5599. my $headstr ;
  5600. $headstr = header_construct( $head, $side, $m_uid ) ;
  5601. if ( ( ! $headstr) and ( $addheader ) and ( $side eq 'Host1' ) ) {
  5602. my $header = add_header( $m_uid ) ;
  5603. myprint( "Host1 uid $m_uid no header found so adding our own [$header]\n" ) ;
  5604. $headstr .= uc $header ;
  5605. $s_fir->{$m_uid}->{NO_HEADER} = 1;
  5606. }
  5607. return if ( ! $headstr ) ;
  5608. my $size = $s_fir->{$m_uid}->{'RFC822.SIZE'} ;
  5609. my $flags = $s_fir->{$m_uid}->{'FLAGS'} ;
  5610. my $idate = $s_fir->{$m_uid}->{'INTERNALDATE'} ;
  5611. $size = length $headstr unless ( $size ) ;
  5612. my $m_md5 = md5_base64( $headstr ) ;
  5613. $debug and myprint( "$side uid $m_uid sig $m_md5 size $size idate $idate\n" ) ;
  5614. my $key ;
  5615. if ($skipsize) {
  5616. $key = "$m_md5";
  5617. }
  5618. else {
  5619. $key = "$m_md5:$size";
  5620. }
  5621. # 0 return code is used to identify duplicate message hash
  5622. return 0 if exists $s_hash->{"$key"};
  5623. $s_hash->{"$key"}{'5'} = $m_md5;
  5624. $s_hash->{"$key"}{'s'} = $size;
  5625. $s_hash->{"$key"}{'D'} = $idate;
  5626. $s_hash->{"$key"}{'F'} = $flags;
  5627. $s_hash->{"$key"}{'m'} = $m_uid;
  5628. return( 1 ) ;
  5629. }
  5630. sub header_construct {
  5631. my( $head, $side, $m_uid ) = @_ ;
  5632. my $headstr ;
  5633. foreach my $h ( sort keys %{ $head } ) {
  5634. next if ( not ( exists $useheader{ uc $h } )
  5635. and ( not exists $useheader{ 'ALL' } )
  5636. ) ;
  5637. foreach my $val ( sort @{$head->{$h}} ) {
  5638. my $H = header_line_normalize( $h, $val ) ;
  5639. # show stuff in debug mode
  5640. $debug and myprint( "$side uid $m_uid header [$H]", "\n" ) ;
  5641. if ($skipheader and $H =~ m/$skipheader/xi) {
  5642. $debug and myprint( "$side uid $m_uid skipping header [$H]\n" ) ;
  5643. next ;
  5644. }
  5645. $headstr .= "$H" ;
  5646. }
  5647. }
  5648. return( $headstr ) ;
  5649. }
  5650. sub header_line_normalize {
  5651. my( $header_key, $header_val ) = @_ ;
  5652. # no 8-bit data in headers !
  5653. $header_val =~ s/[\x80-\xff]/X/xog;
  5654. # change tabulations to space (Gmail bug on with "Received:" on multilines)
  5655. $header_val =~ s/\t/\ /xgo ;
  5656. # remove the first blanks ( dbmail bug? )
  5657. $header_val =~ s/^\s*//xo;
  5658. # remove the last blanks ( Gmail bug )
  5659. $header_val =~ s/\s*$//xo;
  5660. # remove successive blanks ( Mailenable does it )
  5661. $header_val =~ s/\s+/ /xgo;
  5662. # remove Message-Id value domain part ( Mailenable changes it )
  5663. if ( ( $messageidnodomain ) and ( 'MESSAGE-ID' eq uc $header_key ) ) { $header_val =~ s/^([^@]+).*$/$1/xo ; }
  5664. # and uppercase header line
  5665. # (dbmail and dovecot)
  5666. my $header_line = uc "$header_key: $header_val" ;
  5667. return( $header_line ) ;
  5668. }
  5669. sub tests_header_line_normalize {
  5670. ok( ': ' eq header_line_normalize( q{}, q{} ), 'header_line_normalize: empty args' ) ;
  5671. ok( 'HHH: VVV' eq header_line_normalize( 'hhh', 'vvv' ), 'header_line_normalize: hhh vvv ' ) ;
  5672. ok( 'HHH: VVV' eq header_line_normalize( 'hhh', ' vvv' ), 'header_line_normalize: remove first blancs' ) ;
  5673. ok( 'HHH: AA BB CCC D' eq header_line_normalize( 'hhh', 'aa bb ccc d' ), 'header_line_normalize: remove succesive blanks' ) ;
  5674. ok( 'HHH: AA BB CCC' eq header_line_normalize( 'hhh', 'aa bb ccc ' ), 'header_line_normalize: remove last blanks' ) ;
  5675. ok( 'HHH: VVV XX YY' eq header_line_normalize( 'hhh', "vvv\t\txx\tyy" ), 'header_line_normalize: tabs' ) ;
  5676. ok( 'HHH: XABX' eq header_line_normalize( 'hhh', "\x80AB\xff" ), 'header_line_normalize: 8bit' ) ;
  5677. return ;
  5678. }
  5679. sub firstline {
  5680. # extract the first line of a file (without \n)
  5681. my( $file ) = @_ ;
  5682. my $line = q{} ;
  5683. my $FILE ;
  5684. open $FILE, '<', $file or do {
  5685. myprint( "Error opening file $file : $!\n" ) ;
  5686. return ;
  5687. } ;
  5688. $line = <$FILE> || q{} ;
  5689. close $FILE ;
  5690. chomp $line ;
  5691. return $line ;
  5692. }
  5693. sub tests_firstline {
  5694. is( 1 , string_to_file( "blabla\n", 'tmp/firstline.txt' ), 'tests_firstline: put blabla in tmp/firstline.txt' ) ;
  5695. is( 'blabla' , firstline( 'tmp/firstline.txt' ), 'tests_firstline: get blabla from tmp/firstline.txt' ) ;
  5696. is( undef , firstline( 'tmp/noexist.txt' ), 'tests_firstline: get blabla from tmp/noexist.txt' ) ;
  5697. is( 1 , string_to_file( q{}, 'tmp/firstline2.txt' ), 'tests_firstline: put empty string in tmp/firstline2.txt' ) ;
  5698. is( q{} , firstline( 'tmp/firstline2.txt' ), 'tests_firstline: get empty string from tmp/firstline2.txt' ) ;
  5699. is( 1 , string_to_file( "\n", 'tmp/firstline3.txt' ), 'tests_firstline: put CR in tmp/firstline3.txt' ) ;
  5700. is( q{} , firstline( 'tmp/firstline3.txt' ), 'tests_firstline: get empty string from tmp/firstline3.txt' ) ;
  5701. return ;
  5702. }
  5703. sub file_to_string {
  5704. my( $file ) = @_ ;
  5705. my @string ;
  5706. open my $FILE, '<', $file or die_clean( "Error with file $file : $! " ) ;
  5707. @string = <$FILE> ;
  5708. close $FILE ;
  5709. return( join q{}, @string ) ;
  5710. }
  5711. sub string_to_file {
  5712. my( $string, $file ) = @_ ;
  5713. sysopen( FILE, $file, O_WRONLY|O_TRUNC|O_CREAT, 0600) or die_clean( "$! $file" ) ;
  5714. print FILE $string ;
  5715. close FILE ;
  5716. return 1 ;
  5717. }
  5718. q^
  5719. This is a multiline comment.
  5720. Based on David Carter discussion, to do:
  5721. * Call parameters stay the same.
  5722. * Now always "return( $string, $error )". Descriptions below.
  5723. OK * Still capture STDOUT via "1> $output_tmpfile" to finish in $string and "return( $string, $error )"
  5724. OK * Now also capture STDERR via "2> $error_tmpfile" to finish in $error and "return( $string, $error )"
  5725. OK * in case of CHILD_ERROR, return( undef, $error )
  5726. and print $error, with folder/UID/maybeSubject context,
  5727. on console and at the end with the final error listing. Count this as a sync error.
  5728. * in case of good command, take final $string as is, unless void. In case $error with value then print it.
  5729. * in case of good command and final $string empty, consider it like CHILD_ERROR =>
  5730. return( undef, $error ) and print $error, with folder/UID/maybeSubject context,
  5731. on console and at the end with the final error listing. Count this as a sync error.
  5732. ^ if 0 ; # End of multiline comment.
  5733. sub pipemess {
  5734. my ( $string, @commands ) = @_ ;
  5735. my $error = q{} ;
  5736. foreach my $command ( @commands ) {
  5737. my $input_tmpfile = "$tmpdir/kopano-migration-imap_tmp_file.$PROCESS_ID.inp.txt" ;
  5738. my $output_tmpfile = "$tmpdir/kopano-migration-imap_tmp_file.$PROCESS_ID.out.txt" ;
  5739. my $error_tmpfile = "$tmpdir/kopano-migration-imap_tmp_file.$PROCESS_ID.err.txt" ;
  5740. string_to_file( $string, $input_tmpfile ) ;
  5741. ` $command < $input_tmpfile 1> $output_tmpfile 2> $error_tmpfile ` ;
  5742. my $is_command_ko = $CHILD_ERROR ;
  5743. my $error_cmd = file_to_string( $error_tmpfile ) ;
  5744. chomp( $error_cmd ) ;
  5745. $string = file_to_string( $output_tmpfile ) ;
  5746. my $string_len = length( $string ) ;
  5747. unlink $input_tmpfile, $output_tmpfile, $error_tmpfile ;
  5748. if ( $is_command_ko or ( ! $string_len ) ) {
  5749. my $cmd_exit_value = $CHILD_ERROR >> 8 ;
  5750. my $cmd_end_signal = $CHILD_ERROR & 127 ;
  5751. my $signal_log = ( $cmd_end_signal ) ? " signal $cmd_end_signal and" : q{} ;
  5752. 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} ;
  5753. myprint( $error_log ) ;
  5754. if ( wantarray ) {
  5755. return @{ [ undef, $error_log ] }
  5756. }else{
  5757. return ;
  5758. }
  5759. }
  5760. if ( $error_cmd ) {
  5761. $error .= qq{STDERR of --pipemess "$command": $error_cmd\n} ;
  5762. myprint( qq{STDERR of --pipemess "$command": $error_cmd\n} ) ;
  5763. }
  5764. }
  5765. #myprint( "[$string]\n" ) ;
  5766. if ( wantarray ) {
  5767. return ( $string, $error ) ;
  5768. }else{
  5769. return $string ;
  5770. }
  5771. }
  5772. sub tests_pipemess {
  5773. SKIP: {
  5774. Readonly my $NB_WIN_tests_pipemess => 3 ;
  5775. skip( 'Not on MSWin32', $NB_WIN_tests_pipemess ) if ('MSWin32' ne $OSNAME) ;
  5776. # Windows
  5777. # "type" command does not accept redirection of STDIN with <
  5778. # "sort" does
  5779. ok( "nochange\n" eq pipemess( 'nochange', 'sort' ), 'pipemess: nearly no change by sort' ) ;
  5780. ok( "nochange2\n" eq pipemess( 'nochange2', qw( sort sort ) ), 'pipemess: nearly no change by sort,sort' ) ;
  5781. # command not found
  5782. #diag( 'Warning and failure about cacaprout are on purpose' ) ;
  5783. ok( ! defined( pipemess( q{}, 'cacaprout' ) ), 'pipemess: command not found' ) ;
  5784. } ;
  5785. my ( $stringT, $errorT ) ;
  5786. SKIP: {
  5787. Readonly my $NB_UNX_tests_pipemess => 25 ;
  5788. skip( 'Not on Unix', $NB_UNX_tests_pipemess ) if ('MSWin32' eq $OSNAME) ;
  5789. # Unix
  5790. ok( 'nochange' eq pipemess( 'nochange', 'cat' ), 'pipemess: no change by cat' ) ;
  5791. ok( 'nochange2' eq pipemess( 'nochange2', 'cat', 'cat' ), 'pipemess: no change by cat,cat' ) ;
  5792. ok( " 1\tnumberize\n" eq pipemess( "numberize\n", 'cat -n' ), 'pipemess: numberize by cat -n' ) ;
  5793. ok( " 1\tnumberize\n 2\tnumberize\n" eq pipemess( "numberize\nnumberize\n", 'cat -n' ), 'pipemess: numberize by cat -n' ) ;
  5794. ok( "A\nB\nC\n" eq pipemess( "A\nC\nB\n", 'sort' ), 'pipemess: sort' ) ;
  5795. # command not found
  5796. #diag( 'Warning and failure about cacaprout are on purpose' ) ;
  5797. is( undef, pipemess( q{}, 'cacaprout' ), 'pipemess: command not found' ) ;
  5798. # success with true but no output at all
  5799. is( undef, pipemess( q{blabla}, 'true' ), 'pipemess: true but no output' ) ;
  5800. # failure with false and no output at all
  5801. is( undef, pipemess( q{blabla}, 'false' ), 'pipemess: false and no output' ) ;
  5802. # Failure since pipemess is not a real pipe, so first cat wait for standard input
  5803. is( q{blabla}, pipemess( q{blabla}, '( cat|cat ) ' ), 'pipemess: ok by ( cat|cat )' ) ;
  5804. ( $stringT, $errorT ) = pipemess( 'nochange', 'cat' ) ;
  5805. is( $stringT, 'nochange', 'pipemess: list context, no change by cat, string' ) ;
  5806. is( $errorT, q{}, 'pipemess: list context, no change by cat, no error' ) ;
  5807. ( $stringT, $errorT ) = pipemess( 'dontcare', 'true' ) ;
  5808. is( $stringT, undef, 'pipemess: list context, true but no output, string' ) ;
  5809. like( $errorT, qr{Failure: --pipemess command "true" ended with "0" characters exit value "0" and STDERR ""}, 'pipemess: list context, true but no output, error' ) ;
  5810. ( $stringT, $errorT ) = pipemess( 'dontcare', 'false' ) ;
  5811. is( $stringT, undef, 'pipemess: list context, false and no output, string' ) ;
  5812. like( $errorT, qr{Failure: --pipemess command "false" ended with "0" characters exit value "1" and STDERR ""}, 'pipemess: list context, false and no output, error' ) ;
  5813. ( $stringT, $errorT ) = pipemess( 'dontcare', 'echo -n blablabla' ) ;
  5814. is( $stringT, q{blablabla}, 'pipemess: list context, "echo -n blablabla", string' ) ;
  5815. is( $errorT, q{}, 'pipemess: list context, "echo blablabla", error' ) ;
  5816. ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo -n blablabla 3>&1 1>&2 2>&3 )' ) ;
  5817. is( $stringT, undef, 'pipemess: list context, "no output STDERR blablabla", string' ) ;
  5818. like( $errorT, qr{blablabla"$}, 'pipemess: list context, "no output STDERR blablabla", error' ) ;
  5819. ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo -n blablabla 3>&1 1>&2 2>&3 )', 'false' ) ;
  5820. is( $stringT, undef, 'pipemess: list context, "no output STDERR blablabla then false", string' ) ;
  5821. like( $errorT, qr{blablabla"$}, 'pipemess: list context, "no output STDERR blablabla then false", error' ) ;
  5822. ( $stringT, $errorT ) = pipemess( 'dontcare', 'false', '( echo -n blablabla 3>&1 1>&2 2>&3 )' ) ;
  5823. is( $stringT, undef, 'pipemess: list context, "false then STDERR blablabla", string' ) ;
  5824. like( $errorT, qr{Failure: --pipemess command "false" ended with "0" characters exit value "1" and STDERR ""}, 'pipemess: list context, "false then STDERR blablabla", error' ) ;
  5825. ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo rrrrr ; echo -n error_blablabla 3>&1 1>&2 2>&3 )' ) ;
  5826. like( $stringT, qr{rrrrr}, 'pipemess: list context, "STDOUT rrrrr STDERR error_blablabla", string' ) ;
  5827. like( $errorT, qr{STDERR.*error_blablabla}, 'pipemess: list context, "STDOUT rrrrr STDERR error_blablabla", error' ) ;
  5828. }
  5829. ( $stringT, $errorT ) = pipemess( 'dontcare', 'cacaprout' ) ;
  5830. is( $stringT, undef, 'pipemess: list context, cacaprout not found, string' ) ;
  5831. like( $errorT, qr{Failure: --pipemess command "cacaprout" ended with "0" characters exit value.*}, 'pipemess: list context, cacaprout not found, error' ) ;
  5832. return ;
  5833. }
  5834. sub tests_is_a_release_number {
  5835. ok(is_a_release_number($RELEASE_NUMBER_EXAMPLE_1), 'is_a_release_number 1.351') ;
  5836. ok(is_a_release_number($RELEASE_NUMBER_EXAMPLE_2), 'is_a_release_number 42.4242') ;
  5837. ok(is_a_release_number(kopano_migration_imap_version()), 'is_a_release_number kopano_migration_imap_version()') ;
  5838. ok(! is_a_release_number('blabla' ), '! is_a_release_number blabla') ;
  5839. return ;
  5840. }
  5841. sub is_a_release_number {
  5842. my $number = shift;
  5843. return( $number =~ m{^\d+\.\d+$}xo ) ;
  5844. }
  5845. sub kopano_migration_imap_version {
  5846. my $rcs_imapsync = '$Id$';
  5847. my $kopano_migration_imap_version ;
  5848. if ( $rcs_imapsync =~ m{,v\s+(\d+\.\d+)}xo ) {
  5849. $kopano_migration_imap_version = $1
  5850. } else {
  5851. $kopano_migration_imap_version = 'UNKNOWN' ;
  5852. }
  5853. return( $kopano_migration_imap_version ) ;
  5854. }
  5855. sub memory_consumption {
  5856. # memory consumed by kopano-migration-imap until now in bytes
  5857. return( ( memory_consumption_of_pids( ) )[0] );
  5858. }
  5859. sub tests_memory_consumption {
  5860. like( memory_consumption( ), qr{\d+},'memory_consumption no args') ;
  5861. like( memory_consumption( 1 ), qr{\d+},'memory_consumption 1') ;
  5862. like( memory_consumption( $PROCESS_ID ), qr{\d+},"memory_consumption_of_pids $PROCESS_ID") ;
  5863. like( memory_consumption_ratio(), qr{\d+}, 'memory_consumption_ratio' ) ;
  5864. like( memory_consumption_ratio(1), qr{\d+}, 'memory_consumption_ratio 1' ) ;
  5865. like( memory_consumption_ratio(10), qr{\d+}, 'memory_consumption_ratio 10' ) ;
  5866. like( memory_consumption(), qr{\d+}, "memory_consumption\n" ) ;
  5867. return ;
  5868. }
  5869. sub memory_consumption_of_pids {
  5870. my @pid = @_;
  5871. @pid = (@pid) ? @pid : ($PROCESS_ID) ;
  5872. #myprint( "PIDs: @pid\n" ) ;
  5873. my @val;
  5874. if ('MSWin32' eq $OSNAME) {
  5875. @val = memory_consumption_of_pids_win32(@pid);
  5876. }else{
  5877. # Unix
  5878. my @ps = qx{ ps -o vsz -p @pid } ;
  5879. #myprint( @ps ) ;
  5880. #my @ps = backtick( "ps -o vsz -p @pid" ) ;
  5881. shift @ps; # First line is column name "VSZ"
  5882. chomp @ps;
  5883. # convert to octets
  5884. @val = map { $_ * $KIBI } @ps;
  5885. }
  5886. return( @val ) ;
  5887. }
  5888. sub memory_consumption_of_pids_win32 {
  5889. # Windows
  5890. my @PID = @_;
  5891. my %PID;
  5892. # hash of pids as key values
  5893. map { $PID{$_}++ } @PID;
  5894. # Does not work but should reading the tasklist documentation
  5895. #@ps = qx{ tasklist /FI "PID eq @PID" };
  5896. my @ps = qx{ tasklist /NH /FO CSV } ;
  5897. #my @ps = backtick( 'tasklist /NH /FO CSV' ) ;
  5898. #myprint( "-" x $STD_CHAR_PER_LINE, "\n", @ps, "-" x $STD_CHAR_PER_LINE, "\n" ) ;
  5899. my @val;
  5900. foreach my $line (@ps) {
  5901. my($name, $pid, $mem) = (split ',', $line )[0,1,4];
  5902. next if (! $pid);
  5903. #myprint( "[$name][$pid][$mem]" ) ;
  5904. if ($PID{remove_qq($pid)}) {
  5905. #myprint( "MATCH !\n" ) ;
  5906. chomp $mem ;
  5907. $mem = remove_qq($mem);
  5908. $mem = remove_Ko($mem);
  5909. $mem = remove_not_num($mem);
  5910. #myprint( "[$mem]\n" ) ;
  5911. push @val, $mem * $KIBI;
  5912. }
  5913. }
  5914. return(@val);
  5915. }
  5916. sub backtick {
  5917. my $command = shift ;
  5918. my ( $writer, $reader, $err ) ;
  5919. my @output ;
  5920. open3( $writer, $reader, $err, $command ) ;
  5921. @output = <$reader>; #Output here
  5922. #my @errors = <$err>; #Errors here, instead of the console
  5923. $debugdev and myprint( @output ) ;
  5924. return( @output ) ;
  5925. }
  5926. sub tests_backtick {
  5927. SKIP: {
  5928. skip( 'Tests for MSWin32', 3 ) if ('MSWin32' ne $OSNAME) ;
  5929. my @output ;
  5930. @output = backtick( 'echo Hello World!' ) ;
  5931. # Add \r on Windows.
  5932. ok( "Hello World!\r\n" eq $output[0], 'backtick: echo Hello World!' ) ;
  5933. $debug and myprint( "[@output]" ) ;
  5934. @output = backtick( 'echo Hello & echo World!' ) ;
  5935. ok( "Hello \r\n" eq $output[0], 'backtick: echo Hello & echo World!' ) ;
  5936. ok( "World!\r\n" eq $output[1], 'backtick: echo Hello & echo World!' ) ;
  5937. $debug and myprint( "[@output][$output[0]][$output[1]]" ) ;
  5938. } ;
  5939. SKIP: {
  5940. skip( 'Tests for Unix', 3 ) if ('MSWin32' eq $OSNAME) ;
  5941. my @output ;
  5942. @output = backtick( 'echo Hello World!' ) ;
  5943. ok( "Hello World!\n" eq $output[0], 'backtick: echo Hello World!' ) ;
  5944. $debug and myprint( "[@output]" ) ;
  5945. @output = backtick( "echo Hello\necho World!" ) ;
  5946. ok( "Hello\n" eq $output[0], 'backtick: echo Hello; echo World!' ) ;
  5947. ok( "World!\n" eq $output[1], 'backtick: echo Hello; echo World!' ) ;
  5948. $debug and myprint( "[@output]" ) ;
  5949. }
  5950. return ;
  5951. }
  5952. sub remove_not_num {
  5953. my $string = shift;
  5954. $string =~ tr/0-9//cd;
  5955. #myprint( "tr [$string]\n" ) ;
  5956. return($string);
  5957. }
  5958. sub tests_remove_not_num {
  5959. ok('123' eq remove_not_num(123), 'remove_not_num( 123 )' ) ;
  5960. ok('123' eq remove_not_num('123'), q{remove_not_num( '123' )} ) ;
  5961. ok('123' eq remove_not_num('12 3'), q{remove_not_num( '12 3' )} ) ;
  5962. ok('123' eq remove_not_num('a 12 3 Ko'), q{remove_not_num( 'a 12 3 Ko' )} ) ;
  5963. return ;
  5964. }
  5965. sub remove_Ko {
  5966. my $string = shift;
  5967. if ($string =~ /^(.*)\sKo$/xo) {
  5968. return($1);
  5969. }else{
  5970. return($string);
  5971. }
  5972. }
  5973. sub remove_qq {
  5974. my $string = shift;
  5975. if ($string =~ /^"(.*)"$/xo) {
  5976. return($1);
  5977. }else{
  5978. return($string);
  5979. }
  5980. }
  5981. sub memory_consumption_ratio {
  5982. my ($base) = @_;
  5983. $base ||= 1;
  5984. my $consu = memory_consumption();
  5985. return($consu / $base);
  5986. }
  5987. sub date_from_rcs {
  5988. my $d = shift ;
  5989. 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 ) ;
  5990. if ($d =~ m{(\d{4})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) {
  5991. # Handles the following format
  5992. # 2015/07/10 11:05:59 -- Generated by RCS Date tag.
  5993. #myprint( "$d\n" ) ;
  5994. #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
  5995. my ($year, $month, $day, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6) ;
  5996. $month = $num2mon{$month} ;
  5997. $d = "$day-$month-$year $hour:$min:$sec +0000" ;
  5998. #myprint( "$d\n" ) ;
  5999. }
  6000. return( $d ) ;
  6001. }
  6002. sub tests_date_from_rcs {
  6003. ok('19-Sep-2015 16:11:07 +0000'
  6004. eq date_from_rcs('Date: 2015/09/19 16:11:07 '), 'date_from_rcs from RCS date' ) ;
  6005. return ;
  6006. }
  6007. sub good_date {
  6008. # two incoming formats:
  6009. # header Tue, 24 Aug 2010 16:00:00 +0200
  6010. # internal 24-Aug-2010 16:00:00 +0200
  6011. # outgoing format: internal date format
  6012. # 24-Aug-2010 16:00:00 +0200
  6013. my $d = shift ;
  6014. return(q{}) if not defined $d;
  6015. SWITCH: {
  6016. if ( $d =~ m{(\d?)(\d-...-\d{4})(\s\d{2}:\d{2}:\d{2})(\s(?:\+|-)\d{4})?}xo ) {
  6017. #myprint( "internal: [$1][$2][$3][$4]\n" ) ;
  6018. my ($day_1, $date_rest, $hour, $zone) = ($1,$2,$3,$4) ;
  6019. $day_1 = '0' if ($day_1 eq q{}) ;
  6020. $zone = ' +0000' if not defined $zone ;
  6021. $d = $day_1 . $date_rest . $hour . $zone ;
  6022. last SWITCH ;
  6023. }
  6024. 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 ) {
  6025. # Handles any combination of following formats
  6026. # Tue, 24 Aug 2010 16:00:00 +0200 -- Standard
  6027. # 24 Aug 2010 16:00:00 +0200 -- Missing Day of Week
  6028. # Tue, 24 Aug 97 16:00:00 +0200 -- Two digit year
  6029. # Tue, 24 Aug 1997 16.00.00 +0200 -- Periods instead of colons
  6030. # Tue, 24 Aug 1997 16:00:00 +0200 -- Extra whitespace between year and hour
  6031. # Tue, 24 Aug 1997 6:5:2 +0200 -- Single digit hour, min, or second
  6032. # Tue, 24, Aug 1997 16:00:00 +0200 -- Extra comma
  6033. #myprint( "header: [$1][$2][$3][$4][$5][$6][$7][$8]\n" ) ;
  6034. my ($day, $month, $year, $hour, $min, $sec, $zone) = ($1,$2,$3,$4,$5,$6,$7,$8);
  6035. $year = '19' . $year if length($year) == 2 && $year =~ m/^[789]/xo;
  6036. $year = '20' . $year if length($year) == 2;
  6037. $month = substr $month, 0, 3 if length($month) > 4;
  6038. $day = mysprintf( '%02d', $day);
  6039. $hour = mysprintf( '%02d', $hour);
  6040. $min = mysprintf( '%02d', $min);
  6041. $sec = '00' if not defined $sec ;
  6042. $sec = mysprintf( '%02d', $sec ) ;
  6043. $zone = '+0000' if not defined $zone ;
  6044. $d = "$day-$month-$year $hour:$min:$sec $zone" ;
  6045. last SWITCH ;
  6046. }
  6047. 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 ) {
  6048. # Handles any combination of following formats
  6049. # Sun Aug 20 11:55:09 2006
  6050. # Wed Jan 24 11:58:38 MST 2007
  6051. # Wed Jan 2 08:40:57 2008
  6052. #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
  6053. my ($month, $day, $hour, $min, $sec, $year) = ($1,$2,$3,$4,$5,$6);
  6054. $day = mysprintf( '%02d', $day ) ;
  6055. $hour = mysprintf( '%02d', $hour ) ;
  6056. $min = mysprintf( '%02d', $min ) ;
  6057. $sec = mysprintf( '%02d', $sec ) ;
  6058. $d = "$day-$month-$year $hour:$min:$sec +0000" ;
  6059. last SWITCH ;
  6060. }
  6061. 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 ) ;
  6062. if ($d =~ m{(\d{4})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) {
  6063. # Handles the following format
  6064. # 2015/07/10 11:05:59 -- Generated by RCS Date tag.
  6065. #myprint( "$d\n" ) ;
  6066. #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
  6067. my ($year, $month, $day, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6) ;
  6068. $month = $num2mon{$month} ;
  6069. $d = "$day-$month-$year $hour:$min:$sec +0000" ;
  6070. #myprint( "$d\n" ) ;
  6071. last SWITCH ;
  6072. }
  6073. if ($d =~ m{(\d{2})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) {
  6074. # Handles the following format
  6075. # 02/06/09 22:18:08 -- Generated by AVTECH TemPageR devices
  6076. #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
  6077. my ($month, $day, $year, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6);
  6078. $year = '20' . $year;
  6079. $month = $num2mon{$month};
  6080. $d = "$day-$month-$year $hour:$min:$sec +0000";
  6081. last SWITCH ;
  6082. }
  6083. 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 ) {
  6084. # Handles the following format
  6085. # Saturday, December 14, 2002 05:00 PM - KBtoys.com order confirmations
  6086. my ($month, $day, $year, $hour, $min, $apm) = ($1,$2,$3,$4,$5,$6);
  6087. $hour += 12 if $apm eq 'PM' ;
  6088. $day = mysprintf( '%02d', $day ) ;
  6089. $d = "$day-$month-$year $hour:$min:00 +0000" ;
  6090. last SWITCH ;
  6091. }
  6092. if ($d =~ m{(\w{3})\s(\d{1,2})\s(\d{4})\s(\d{2}):(\d{2}):(\d{2})\s((?:\+|-)\d{4})}xo ) {
  6093. # Handles the following format
  6094. # Saturday, December 14, 2002 05:00 PM - jr.com order confirmations
  6095. my ($month, $day, $year, $hour, $min, $sec, $zone) = ($1,$2,$3,$4,$5,$6,$7);
  6096. $day = mysprintf( '%02d', $day ) ;
  6097. $d = "$day-$month-$year $hour:$min:$sec $zone";
  6098. last SWITCH ;
  6099. }
  6100. if ($d =~ m{(\d{1,2})-(\w{3})-(\d{4})}xo ) {
  6101. # Handles the following format
  6102. # 21-Jun-2001 - register.com domain transfer email circa 2001
  6103. my ($day, $month, $year) = ($1,$2,$3);
  6104. $day = mysprintf( '%02d', $day);
  6105. $d = "$day-$month-$year 11:11:11 +0000";
  6106. last SWITCH ;
  6107. }
  6108. # unknown or unmatch => return same string
  6109. return($d);
  6110. }
  6111. $d = qq("$d") ;
  6112. return( $d ) ;
  6113. }
  6114. sub tests_good_date {
  6115. ok(q{} eq good_date(), 'good_date no arg');
  6116. ok('"24-Aug-2010 16:00:00 +0200"' eq good_date('24-Aug-2010 16:00:00 +0200'), 'good_date internal 2digit zone');
  6117. ok('"24-Aug-2010 16:00:00 +0000"' eq good_date('24-Aug-2010 16:00:00'), 'good_date internal 2digit no zone');
  6118. ok('"01-Sep-2010 16:00:00 +0200"' eq good_date( '1-Sep-2010 16:00:00 +0200'), 'good_date internal SP 1digit');
  6119. ok('"24-Aug-2010 16:00:00 +0200"' eq good_date('Tue, 24 Aug 2010 16:00:00 +0200'), 'good_date header 2digit zone');
  6120. ok('"01-Sep-2010 16:00:00 +0000"' eq good_date('Wed, 1 Sep 2010 16:00:00'), 'good_date header SP 1digit zone');
  6121. 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');
  6122. 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');
  6123. ok('"06-Feb-2009 22:18:08 +0000"' eq good_date('02/06/09 22:18:08'), 'good_date header TemPageR');
  6124. 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');
  6125. 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');
  6126. 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');
  6127. 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');
  6128. ok('"24-Aug-2067 16:00:00 +0200"' eq good_date('Tue, 24 Aug 67 16:00:00 +0200'), 'good_date header 2digit year');
  6129. ok('"24-Aug-1977 16:00:00 +0200"' eq good_date('Tue, 24 Aug 77 16:00:00 +0200'), 'good_date header 2digit year');
  6130. ok('"24-Aug-1987 16:00:00 +0200"' eq good_date('Tue, 24 Aug 87 16:00:00 +0200'), 'good_date header 2digit year');
  6131. ok('"24-Aug-1997 16:00:00 +0200"' eq good_date('Tue, 24 Aug 97 16:00:00 +0200'), 'good_date header 2digit year');
  6132. ok('"24-Aug-2004 16:00:00 +0200"' eq good_date('Tue, 24 Aug 04 16:00:00 +0200'), 'good_date header 2digit year');
  6133. 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');
  6134. 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');
  6135. 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');
  6136. ok('"24-Aug-1997 05:06:02 +0200"' eq good_date('Tue, 24, Aug 1997 05:06:02 +0200'), 'good_date header extra commas');
  6137. ok('"01-Oct-2003 12:45:24 +0000"' eq good_date('Wednesday, 01 October 2003 12:45:24 CDT'), 'good_date header no abbrev');
  6138. ok('"11-Jan-2005 17:58:27 -0500"' eq good_date('Tue, 11 Jan 2005 17:58:27 -0500'), 'good_date extra white space');
  6139. ok('"18-Dec-2002 15:07:00 +0000"' eq good_date('Wednesday, December 18, 2002 03:07 PM'), 'good_date kbtoys.com orders');
  6140. ok('"16-Dec-2004 02:01:49 -0500"' eq good_date('Dec 16 2004 02:01:49 -0500'), 'good_date jr.com orders');
  6141. ok('"21-Jun-2001 11:11:11 +0000"' eq good_date('21-Jun-2001'), 'good_date register.com domain transfer');
  6142. 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)');
  6143. ok('"19-Sep-2015 16:11:07 +0000"' eq good_date('Date: 2015/09/19 16:11:07 '), 'good_date from RCS date' ) ;
  6144. return ;
  6145. }
  6146. sub tests_list_keys_in_2_not_in_1 {
  6147. my @list;
  6148. ok( ! list_keys_in_2_not_in_1( {}, {}), 'list_keys_in_2_not_in_1: {} {}');
  6149. ok( 0 == compare_lists( [], [ list_keys_in_2_not_in_1( {}, {} ) ] ), 'list_keys_in_2_not_in_1: {} {}');
  6150. 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}');
  6151. 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}');
  6152. 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}');
  6153. 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}');
  6154. 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}');
  6155. return ;
  6156. }
  6157. sub list_keys_in_2_not_in_1 {
  6158. my $folders1_ref = shift;
  6159. my $folders2_ref = shift;
  6160. my @list;
  6161. foreach my $folder ( sort keys %{ $folders2_ref } ) {
  6162. next if exists $folders1_ref->{$folder};
  6163. push @list, $folder;
  6164. }
  6165. return(@list);
  6166. }
  6167. sub list_folders_in_2_not_in_1 {
  6168. my (@h2_folders_not_in_h1, %h2_folders_not_in_h1) ;
  6169. @h2_folders_not_in_h1 = list_keys_in_2_not_in_1( \%h1_folders_all, \%h2_folders_all) ;
  6170. map { $h2_folders_not_in_h1{$_} = 1} @h2_folders_not_in_h1 ;
  6171. @h2_folders_not_in_h1 = list_keys_in_2_not_in_1( \%h2_folders_from_1_all, \%h2_folders_not_in_h1) ;
  6172. return( reverse @h2_folders_not_in_h1 );
  6173. }
  6174. sub delete_folders_in_2_not_in_1 {
  6175. foreach my $folder (@h2_folders_not_in_1) {
  6176. if ( defined $delete2foldersonly and eval "\$folder !~ $delete2foldersonly" ) {
  6177. myprint( "Not deleting $folder because of --delete2foldersonly $delete2foldersonly\n" ) ;
  6178. next ;
  6179. }
  6180. if ( defined $delete2foldersbutnot and eval "\$folder =~ $delete2foldersbutnot" ) {
  6181. myprint( "Not deleting $folder because of --delete2foldersbutnot $delete2foldersbutnot\n" ) ;
  6182. next ;
  6183. }
  6184. my $res = $dry ; # always success in dry mode!
  6185. $imap2->unsubscribe( $folder ) if ( ! $dry ) ;
  6186. $res = $imap2->delete( $folder ) if ( ! $dry ) ;
  6187. if ( $res ) {
  6188. myprint( "Deleted $folder", "$dry_message", "\n" ) ;
  6189. }else{
  6190. myprint( "Deleting $folder failed", "\n" ) ;
  6191. }
  6192. }
  6193. return ;
  6194. }
  6195. sub delete_folder {
  6196. my ( $sync, $imap, $folder, $Side ) = @_ ;
  6197. if ( ! $sync ) { return ; }
  6198. if ( ! $imap ) { return ; }
  6199. if ( ! $folder ) { return ; }
  6200. $Side ||= 'HostX' ;
  6201. my $res = $sync->{dry} ; # always success in dry mode!
  6202. if ( ! $sync->{dry} ) {
  6203. $imap->unsubscribe( $folder ) ;
  6204. $res = $imap->delete( $folder ) ;
  6205. }
  6206. if ( $res ) {
  6207. myprint( "$Side deleted $folder", $sync->{dry_message}, "\n" ) ;
  6208. return 1 ;
  6209. }else{
  6210. myprint( "$Side deleting $folder failed", "\n" ) ;
  6211. return ;
  6212. }
  6213. }
  6214. sub delete1emptyfolders {
  6215. my $sync = shift ;
  6216. if ( ! $sync ) { return ; } # abort if no parameter
  6217. if ( ! $sync->{delete1emptyfolders} ) { return ; } # abort if --delete1emptyfolders off
  6218. my $imap = $sync->{imap1} ;
  6219. if ( ! $imap ) { return ; } # abort if no imap
  6220. if ( $imap->IsUnconnected( ) ) { return ; } # abort if diesconnected
  6221. my %folders_kept ;
  6222. myprint( qq{Host1 deleting empty folders\n} ) ;
  6223. foreach my $folder ( reverse sort @{ $sync->{h1_folders_wanted} } ) {
  6224. my $parenthood = $imap->is_parent( $folder ) ;
  6225. if ( defined $parenthood and $parenthood ) {
  6226. myprint( "Host1 folder $folder has subfolders\n" ) ;
  6227. $folders_kept{ $folder }++ ;
  6228. next ;
  6229. }
  6230. my $nb_messages_select = examine_folder_and_count( $imap, $folder, 'Host1' ) ;
  6231. if ( ! defined $nb_messages_select ) { next ; } # Select failed => Neither continue nor keep this folder }
  6232. my $nb_messages_search = scalar( @{ $imap->messages( ) } ) ;
  6233. if ( 0 != $nb_messages_select and 0 != $nb_messages_search ) {
  6234. myprint( "Host1 folder $folder has messages: $nb_messages_search (search) $nb_messages_select (select)\n" ) ;
  6235. $folders_kept{ $folder }++ ;
  6236. next ;
  6237. }
  6238. if ( 0 != $nb_messages_select + $nb_messages_search ) {
  6239. myprint( "Host1 folder $folder odd messages count: $nb_messages_search (search) $nb_messages_select (select)\n" ) ;
  6240. $folders_kept{ $folder }++ ;
  6241. next ;
  6242. }
  6243. # Here we must have 0 messages by messages() aka "SEARCH ALL" and also "EXAMINE"
  6244. if ( uc $folder eq 'INBOX' ) {
  6245. myprint( "Host1 Not deleting $folder\n" ) ;
  6246. $folders_kept{ $folder }++ ;
  6247. next ;
  6248. }
  6249. myprint( "Host1 deleting empty folder $folder\n" ) ;
  6250. # can not delete a SELECTed or EXAMINEd folder so closing it
  6251. # could changed be SELECT INBOX
  6252. $imap->close( ) ; # close after examine does not expunge; anyway expunging an empty folder...
  6253. if ( delete_folder( $sync, $imap, $folder, 'Host1' ) ) {
  6254. next ; # Deleted, good!
  6255. }else{
  6256. $folders_kept{ $folder }++ ;
  6257. next ; # Not deleted, bad!
  6258. }
  6259. }
  6260. remove_deleted_folders_from_wanted_list( $sync, %folders_kept ) ;
  6261. myprint( qq{Host1 ended deleting empty folders\n} ) ;
  6262. return ;
  6263. }
  6264. sub remove_deleted_folders_from_wanted_list {
  6265. my ( $sync, %folders_kept ) = @ARG ;
  6266. my @h1_folders_wanted_init = @{ $sync->{h1_folders_wanted} } ;
  6267. my @h1_folders_wanted_last ;
  6268. foreach my $folder ( @h1_folders_wanted_init ) {
  6269. if ( $folders_kept{ $folder } ) {
  6270. push @h1_folders_wanted_last, $folder ;
  6271. }
  6272. }
  6273. @{ $sync->{h1_folders_wanted} } = @h1_folders_wanted_last ;
  6274. return ;
  6275. }
  6276. sub examine_folder_and_count {
  6277. my ( $imap, $folder, $Side ) = @_ ;
  6278. $Side ||= 'HostX' ;
  6279. if ( ! examine_folder( $imap, $folder, $Side ) ) {
  6280. return ;
  6281. }
  6282. my $nb_messages_select = count_from_select( $imap->History ) ;
  6283. return $nb_messages_select ;
  6284. }
  6285. sub tests_delete1emptyfolders {
  6286. is( undef, delete1emptyfolders( ), q{delete1emptyfolders: undef} ) ;
  6287. my $syncT ;
  6288. is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: undef 2} ) ;
  6289. my $imapT ;
  6290. $syncT->{imap1} = $imapT ;
  6291. is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: undef imap} ) ;
  6292. require Test::MockObject ;
  6293. $imapT = Test::MockObject->new( ) ;
  6294. $syncT->{imap1} = $imapT ;
  6295. $imapT->set_true( 'IsUnconnected' ) ;
  6296. is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: Unconnected imap} ) ;
  6297. # Now connected tests
  6298. $imapT->set_false( 'IsUnconnected' ) ;
  6299. $imapT->mock( 'LastError', sub { q{LastError mocked} } ) ;
  6300. $syncT->{delete1emptyfolders} = 0 ;
  6301. tests_delete1emptyfolders_unit(
  6302. $syncT,
  6303. [ qw{ INBOX DELME1 DELME2 } ],
  6304. [ qw{ INBOX DELME1 DELME2 } ],
  6305. q{tests_delete1emptyfolders: --delete1emptyfolders OFF}
  6306. ) ;
  6307. # All are parents => no deletion at all
  6308. $imapT->set_true( 'is_parent' ) ;
  6309. $syncT->{delete1emptyfolders} = 1 ;
  6310. tests_delete1emptyfolders_unit(
  6311. $syncT,
  6312. [ qw{ INBOX DELME1 DELME2 } ],
  6313. [ qw{ INBOX DELME1 DELME2 } ],
  6314. q{tests_delete1emptyfolders: --delete1emptyfolders ON}
  6315. ) ;
  6316. # No parents but examine false for all => skip all
  6317. $imapT->set_false( 'is_parent', 'examine' ) ;
  6318. tests_delete1emptyfolders_unit(
  6319. $syncT,
  6320. [ qw{ INBOX DELME1 DELME2 } ],
  6321. [ ],
  6322. q{tests_delete1emptyfolders: EXAMINE fails}
  6323. ) ;
  6324. # examine ok for all but History bad => skip all
  6325. $imapT->set_true( 'examine' ) ;
  6326. $imapT->mock( 'History', sub { ( q{History badly mocked} ) } ) ;
  6327. tests_delete1emptyfolders_unit(
  6328. $syncT,
  6329. [ qw{ INBOX DELME1 DELME2 } ],
  6330. [ ],
  6331. q{tests_delete1emptyfolders: examine ok but History badly mocked so count messages fails}
  6332. ) ;
  6333. # History good but some messages EXISTS == messages() => no deletion
  6334. $imapT->mock( 'History', sub { ( q{* 2 EXISTS} ) } ) ;
  6335. $imapT->mock( 'messages', sub { [ qw{ UID_1 UID_2 } ] } ) ;
  6336. tests_delete1emptyfolders_unit(
  6337. $syncT,
  6338. [ qw{ INBOX DELME1 DELME2 } ],
  6339. [ qw{ INBOX DELME1 DELME2 } ],
  6340. q{tests_delete1emptyfolders: History EXAMINE ok, several messages}
  6341. ) ;
  6342. # 0 EXISTS but != messages() => no deletion
  6343. $imapT->mock( 'History', sub { ( q{* 0 EXISTS} ) } ) ;
  6344. $imapT->mock( 'messages', sub { [ qw{ UID_1 UID_2 } ] } ) ;
  6345. tests_delete1emptyfolders_unit(
  6346. $syncT,
  6347. [ qw{ INBOX DELME1 DELME2 } ],
  6348. [ qw{ INBOX DELME1 DELME2 } ],
  6349. q{tests_delete1emptyfolders: 0 EXISTS but 2 by messages()}
  6350. ) ;
  6351. # 1 EXISTS but != 0 == messages() => no deletion
  6352. $imapT->mock( 'History', sub { ( q{* 1 EXISTS} ) } ) ;
  6353. $imapT->mock( 'messages', sub { [ ] } ) ;
  6354. tests_delete1emptyfolders_unit(
  6355. $syncT,
  6356. [ qw{ INBOX DELME1 DELME2 } ],
  6357. [ qw{ INBOX DELME1 DELME2 } ],
  6358. q{tests_delete1emptyfolders: 1 EXISTS but 0 by messages()}
  6359. ) ;
  6360. # 0 EXISTS and 0 == messages() => deletion except INBOX
  6361. $imapT->mock( 'History', sub { ( q{* 0 EXISTS} ) } ) ;
  6362. $imapT->mock( 'messages', sub { [ ] } ) ;
  6363. $imapT->set_true( qw{ delete close unsubscribe } ) ;
  6364. $syncT->{dry_message} = q{ (not really since in a mocked test)} ;
  6365. tests_delete1emptyfolders_unit(
  6366. $syncT,
  6367. [ qw{ INBOX DELME1 DELME2 } ],
  6368. [ qw{ INBOX } ],
  6369. q{tests_delete1emptyfolders: 0 EXISTS 0 by messages() delete folders, keep INBOX}
  6370. ) ;
  6371. return ;
  6372. }
  6373. sub tests_delete1emptyfolders_unit {
  6374. my $syncT = shift ;
  6375. my $folders1wanted_init_ref = shift ;
  6376. my $folders1wanted_after_ref = shift ;
  6377. my $comment = shift || q{delete1emptyfolders:} ;
  6378. my @folders1wanted_init = @{ $folders1wanted_init_ref } ;
  6379. my @folders1wanted_after = @{ $folders1wanted_after_ref } ;
  6380. @{ $syncT->{h1_folders_wanted} } = @folders1wanted_init ;
  6381. is_deeply( $syncT->{h1_folders_wanted}, \@folders1wanted_init, qq{$comment, init check} ) ;
  6382. delete1emptyfolders( $syncT ) ;
  6383. is_deeply( $syncT->{h1_folders_wanted}, \@folders1wanted_after, qq{$comment, after check} ) ;
  6384. return ;
  6385. }
  6386. sub extract_header {
  6387. my $string = shift ;
  6388. my ( $header ) = split /\n\n/x, $string ;
  6389. if ( ! $header ) { return( q{} ) ; }
  6390. #myprint( "[$header]\n" ) ;
  6391. return( $header ) ;
  6392. }
  6393. sub tests_extract_header {
  6394. my $h = <<'EOM';
  6395. Message-Id: <20100428101817.A66CB162474E@smtp.kopano.com>
  6396. Date: Wed, 28 Apr 2010 12:18:17 +0200 (CEST)
  6397. From: development@kopano.com (Kopano)
  6398. EOM
  6399. chomp $h ;
  6400. ok( $h eq extract_header(
  6401. <<'EOM'
  6402. Message-Id: <20100428101817.A66CB162474E@smtp.kopano.com>
  6403. Date: Wed, 28 Apr 2010 12:18:17 +0200 (CEST)
  6404. From: development@kopano.com (Kopano)
  6405. body
  6406. lalala
  6407. EOM
  6408. ), 'extract_header: 1') ;
  6409. return ;
  6410. }
  6411. sub decompose_header{
  6412. my $string = shift ;
  6413. # a hash, for a keyword header KEY value are list of strings [VAL1, VAL1_other, etc]
  6414. # Think of multiple "Received:" header lines.
  6415. my $header = { } ;
  6416. my ($key, $val ) ;
  6417. my @line = split /\n|\r\n/x, $string ;
  6418. foreach my $line ( @line ) {
  6419. #myprint( "DDD $line\n" ) ;
  6420. # End of header
  6421. last if ( $line =~ m{^$}xo ) ;
  6422. # Key: value
  6423. if ( $line =~ m/(^[^:]+):\s(.*)/xo ) {
  6424. $key = $1 ;
  6425. $val = $2 ;
  6426. $debugdev and myprint( "DDD KV [$key] [$val]\n" ) ;
  6427. push @{ $header->{ $key } }, $val ;
  6428. # blanc and value => value from previous line continues
  6429. }elsif( $line =~ m/^(\s+)(.*)/xo ) {
  6430. $val = $2 ;
  6431. $debugdev and myprint( "DDD V [$val]\n" ) ;
  6432. @{ $header->{ $key } }[ $LAST ] .= " $val" if $key ;
  6433. # dirty line?
  6434. }else{
  6435. next ;
  6436. }
  6437. }
  6438. #myprint( Data::Dumper->Dump( [ $header ] ) ) ;
  6439. return( $header ) ;
  6440. }
  6441. sub tests_decompose_header{
  6442. my $header_dec ;
  6443. $header_dec = decompose_header(
  6444. <<'EOH'
  6445. KEY_1: VAL_1
  6446. KEY_2: VAL_2
  6447. VAL_2_+
  6448. VAL_2_++
  6449. KEY_3: VAL_3
  6450. KEY_1: VAL_1_other
  6451. KEY_4: VAL_4
  6452. VAL_4_+
  6453. KEY_5 BLANC: VAL_5
  6454. KEY_6_BAD_BODY: VAL_6
  6455. EOH
  6456. ) ;
  6457. ok( 'VAL_3'
  6458. eq $header_dec->{ 'KEY_3' }[0], 'decompose_header: VAL_3' ) ;
  6459. ok( 'VAL_1'
  6460. eq $header_dec->{ 'KEY_1' }[0], 'decompose_header: VAL_1' ) ;
  6461. ok( 'VAL_1_other'
  6462. eq $header_dec->{ 'KEY_1' }[1], 'decompose_header: VAL_1_other' ) ;
  6463. ok( 'VAL_2 VAL_2_+ VAL_2_++'
  6464. eq $header_dec->{ 'KEY_2' }[0], 'decompose_header: VAL_2 VAL_2_+ VAL_2_++' ) ;
  6465. ok( 'VAL_4 VAL_4_+'
  6466. eq $header_dec->{ 'KEY_4' }[0], 'decompose_header: VAL_4 VAL_4_+' ) ;
  6467. ok( ' VAL_5'
  6468. eq $header_dec->{ 'KEY_5 BLANC' }[0], 'decompose_header: KEY_5 BLANC' ) ;
  6469. ok( not( defined $header_dec->{ 'KEY_6_BAD_BODY' }[0] ), 'decompose_header: KEY_6_BAD_BODY' ) ;
  6470. $header_dec = decompose_header(
  6471. <<'EOH'
  6472. Message-Id: <20100428101817.A66CB162474E@smtp.kopano.com>
  6473. Date: Wed, 28 Apr 2010 12:18:17 +0200 (CEST)
  6474. From: development@kopano.com (Kopano)
  6475. EOH
  6476. ) ;
  6477. ok( '<20100428101817.A66CB162474E@smtp.kopano.com>'
  6478. eq $header_dec->{ 'Message-Id' }[0], 'decompose_header: 1' ) ;
  6479. $header_dec = decompose_header(
  6480. <<'EOH'
  6481. Return-Path: <development@kopano.com>
  6482. Received: by smtp.kopano.com (Postfix, from userid 1000)
  6483. id 120A71624742; Wed, 28 Apr 2010 01:46:40 +0200 (CEST)
  6484. Subject: test:eekahceishukohpe
  6485. EOH
  6486. ) ;
  6487. ok(
  6488. 'by smtp.kopano.com (Postfix, from userid 1000) id 120A71624742; Wed, 28 Apr 2010 01:46:40 +0200 (CEST)'
  6489. eq $header_dec->{ 'Received' }[0], 'decompose_header: 2' ) ;
  6490. $header_dec = decompose_header(
  6491. <<'EOH'
  6492. Received: from smtp (localhost [127.0.0.1])
  6493. by smtp.kopano.com (Postfix) with ESMTP id C6EB73F6C9
  6494. for <development@localhost>; Mon, 26 Nov 2007 10:39:06 +0100 (CET)
  6495. Received: from smtp [192.168.68.7]
  6496. by smtp with POP3 (fetchmail-6.3.6)
  6497. for <development@localhost> (single-drop); Mon, 26 Nov 2007 10:39:06 +0100 (CET)
  6498. EOH
  6499. ) ;
  6500. ok(
  6501. 'from smtp (localhost [127.0.0.1]) by smtp.kopano.com (Postfix) with ESMTP id C6EB73F6C9 for <development@localhost>; Mon, 26 Nov 2007 10:39:06 +0100 (CET)'
  6502. eq $header_dec->{ 'Received' }[0], 'decompose_header: 3' ) ;
  6503. ok(
  6504. 'from smtp [192.168.68.7] by smtp with POP3 (fetchmail-6.3.6) for <development@localhost> (single-drop); Mon, 26 Nov 2007 10:39:06 +0100 (CET)'
  6505. eq $header_dec->{ 'Received' }[1], 'decompose_header: 3' ) ;
  6506. # Bad header beginning with a blank character
  6507. $header_dec = decompose_header(
  6508. <<'EOH'
  6509. KEY_1: VAL_1
  6510. KEY_2: VAL_2
  6511. VAL_2_+
  6512. VAL_2_++
  6513. KEY_3: VAL_3
  6514. KEY_1: VAL_1_other
  6515. EOH
  6516. ) ;
  6517. ok( 'VAL_3'
  6518. eq $header_dec->{ 'KEY_3' }[0], 'decompose_header: Bad header VAL_3' ) ;
  6519. ok( 'VAL_1_other'
  6520. eq $header_dec->{ 'KEY_1' }[0], 'decompose_header: Bad header VAL_1_other' ) ;
  6521. ok( 'VAL_2 VAL_2_+ VAL_2_++'
  6522. eq $header_dec->{ 'KEY_2' }[0], 'decompose_header: Bad header VAL_2 VAL_2_+ VAL_2_++' ) ;
  6523. return ;
  6524. }
  6525. sub epoch {
  6526. # incoming format:
  6527. # internal date 24-Aug-2010 16:00:00 +0200
  6528. # outgoing format: epoch
  6529. my $d = shift ;
  6530. return(q{}) if not defined $d;
  6531. my ( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m ) ;
  6532. my $time ;
  6533. 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 ) {
  6534. #myprint( "internal: [$1][$2][$3][$4][$5][$6][$7][$8][$9]\n" ) ;
  6535. ( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m )
  6536. = ( $1, $2, $3, $4, $5, $6, $7, $8, $9 ) ;
  6537. #myprint( "( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m )\n" ) ;
  6538. $sign = +1 if ( '+' eq $sign ) ;
  6539. $sign = $MINUS_ONE if ( '-' eq $sign ) ;
  6540. $time = timegm( $sec, $min, $hour, $mday, $month_abrev{$month}, $year )
  6541. - $sign * ( 3600 * $zone_h + 60 * $zone_m ) ;
  6542. #myprint( "$time ", scalar localtime($time), "\n");
  6543. }
  6544. return( $time ) ;
  6545. }
  6546. sub tests_epoch {
  6547. ok( '1282658400' eq epoch( '24-Aug-2010 16:00:00 +0200' ), 'epoch 24-Aug-2010 16:00:00 +0200 -> 1282658400' ) ;
  6548. ok( '1282658400' eq epoch( '24-Aug-2010 14:00:00 +0000' ), 'epoch 24-Aug-2010 14:00:00 +0000 -> 1282658400' ) ;
  6549. ok( '1282658400' eq epoch( '24-Aug-2010 12:00:00 -0200' ), 'epoch 24-Aug-2010 12:00:00 -0200 -> 1282658400' ) ;
  6550. ok( '1282658400' eq epoch( '24-Aug-2010 16:01:00 +0201' ), 'epoch 24-Aug-2010 16:01:00 +0201 -> 1282658400' ) ;
  6551. ok( '1282658400' eq epoch( '24-Aug-2010 14:01:00 +0001' ), 'epoch 24-Aug-2010 14:01:00 +0001 -> 1282658400' ) ;
  6552. ok( '1280671200' eq epoch( '1-Aug-2010 16:00:00 +0200' ), 'epoch 1-Aug-2010 16:00:00 +0200 -> 1280671200' ) ;
  6553. ok( '1280671200' eq epoch( '1-Aug-2010 14:00:00 +0000' ), 'epoch 1-Aug-2010 14:00:00 +0000 -> 1280671200' ) ;
  6554. ok( '1280671200' eq epoch( '1-Aug-2010 12:00:00 -0200' ), 'epoch 1-Aug-2010 12:00:00 -0200 -> 1280671200' ) ;
  6555. ok( '1280671200' eq epoch( '1-Aug-2010 16:01:00 +0201' ), 'epoch 1-Aug-2010 16:01:00 +0201 -> 1280671200' ) ;
  6556. ok( '1280671200' eq epoch( '1-Aug-2010 14:01:00 +0001' ), 'epoch 1-Aug-2010 14:01:00 +0001 -> 1280671200' ) ;
  6557. return ;
  6558. }
  6559. sub add_header {
  6560. my $header_uid = shift || 'mistake' ;
  6561. my $header_Message_Id = 'Message-Id: <' . $header_uid . '@kopano-migration-imap>' ;
  6562. return( $header_Message_Id ) ;
  6563. }
  6564. sub tests_add_header {
  6565. ok( 'Message-Id: <mistake@kopano-migration-imap>' eq add_header(), 'add_header no arg' ) ;
  6566. ok( 'Message-Id: <123456789@kopano-migration-imap>' eq add_header(123456789), 'add_header 123456789' ) ;
  6567. return ;
  6568. }
  6569. sub tests_Banner{
  6570. my $imap = Mail::IMAPClient->new( ) ;
  6571. ok( 'lalala' eq $imap->Banner('lalala'), 'Banner set lalala' ) ;
  6572. ok( 'lalala' eq $imap->Banner(), 'Banner returns lalala' ) ;
  6573. return ;
  6574. }
  6575. sub max_line_length {
  6576. my $string = shift ;
  6577. my $max = 0 ;
  6578. while ( $string =~ m/([^\n]*\n?)/msxg ) {
  6579. $max = max( $max, length $1 ) ;
  6580. }
  6581. return( $max ) ;
  6582. }
  6583. sub tests_max_line_length {
  6584. ok( 0 == max_line_length( q{} ), 'max_line_length: 0 == null string' ) ;
  6585. ok( 1 == max_line_length( "\n" ), 'max_line_length: 1 == \n' ) ;
  6586. ok( 1 == max_line_length( "\n\n" ), 'max_line_length: 1 == \n\n' ) ;
  6587. ok( 1 == max_line_length( "\n" x 500 ), 'max_line_length: 1 == 500 \n' ) ;
  6588. ok( 1 == max_line_length( 'a' ), 'max_line_length: 1 == a' ) ;
  6589. ok( 2 == max_line_length( "a\na" ), 'max_line_length: 2 == a\na' ) ;
  6590. ok( 2 == max_line_length( "a\na\n" ), 'max_line_length: 2 == a\na\n' ) ;
  6591. ok( 3 == max_line_length( "a\nab\n" ), 'max_line_length: 3 == a\nab\n' ) ;
  6592. ok( 3 == max_line_length( "a\nab\n" x 10000 ), 'max_line_length: 3 == 10000 a\nab\n' ) ;
  6593. ok( 3 == max_line_length( "a\nab\nabc" ), 'max_line_length: 3 == a\nab\nabc' ) ;
  6594. ok( 4 == max_line_length( "a\nab\nabc\n" ), 'max_line_length: 4 == a\nab\nabc\n' ) ;
  6595. ok( 5 == max_line_length( "a\nabcd\nabc\n" ), 'max_line_length: 5 == a\nabcd\nabc\n' ) ;
  6596. 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' ) ;
  6597. return ;
  6598. }
  6599. sub setlogfile {
  6600. my( $mysync ) = shift ;
  6601. $mysync->{logdir} = defined $mysync->{logdir} ? $mysync->{logdir} : 'LOG_kopano-migration-imap' ;
  6602. $mysync->{logfile} = defined $mysync->{logfile} ? "$mysync->{logdir}/$mysync->{logfile}" :
  6603. logfile( $mysync->{timestart}, $mysync->{user2}, $mysync->{logdir} ) ;
  6604. #myprint( "logdir = $mysync->{logdir}\n" ) ;
  6605. #myprint( "logfile = $mysync->{logfile}\n" ) ;
  6606. return( $mysync->{logfile} ) ;
  6607. }
  6608. sub tests_setlogfile {
  6609. my $mysync = {
  6610. timestart => 2,
  6611. user2 => 'user2',
  6612. } ;
  6613. ok( 'LOG_kopano-migration-imap/1970_01_01_01_00_02_user2.txt' eq setlogfile( $mysync ),
  6614. 'setlogfile: default is like LOG_kopano-migration-imap/1970_01_01_01_00_02_user2.txt' ) ;
  6615. $mysync->{logdir} = undef ;
  6616. $mysync->{logfile} = undef ;
  6617. ok( 'LOG_kopano-migration-imap/1970_01_01_01_00_02_user2.txt' eq setlogfile( $mysync ),
  6618. 'setlogfile: logdir undef, LOG_kopano-migration-imap/1970_01_01_01_00_02_user2.txt' ) ;
  6619. $mysync->{logdir} = q{} ;
  6620. $mysync->{logfile} = undef ;
  6621. ok( '1970_01_01_01_00_02_user2.txt' eq setlogfile( $mysync ),
  6622. 'setlogfile: logdir empty, 1970_01_01_01_00_02_user2.txt' ) ;
  6623. $mysync->{logdir} = 'vallogdir' ;
  6624. $mysync->{logfile} = undef ;
  6625. ok( 'vallogdir/1970_01_01_01_00_02_user2.txt' eq setlogfile( $mysync ),
  6626. 'setlogfile: logdir vallogdir, vallogdir/1970_01_01_01_00_02_user2.txt' ) ;
  6627. $mysync->{logdir} = 'vallogdir' ;
  6628. $mysync->{logfile} = 'vallogfile.txt' ;
  6629. ok( 'vallogdir/vallogfile.txt' eq setlogfile( $mysync ),
  6630. 'setlogfile: logdir vallogdir, logfile vallogfile.txt, vallogdir/vallogfile.txt' ) ;
  6631. return ;
  6632. }
  6633. sub logfile {
  6634. my ( $time, $suffix, $dir ) = @_ ;
  6635. $time ||= 0 ;
  6636. $suffix ||= q{} ;
  6637. my $sep_suffix = ( $suffix ) ? '_' : q{} ;
  6638. $dir ||= q{} ;
  6639. my $sep_dir = ( $dir ) ? '/' : q{} ;
  6640. my $date_str = POSIX::strftime( '%Y_%m_%d_%H_%M_%S', localtime $time ) ;
  6641. my $logfile = "${dir}${sep_dir}${date_str}${sep_suffix}${suffix}.txt" ;
  6642. $debug and myprint( "date_str: $date_str\n" ) ;
  6643. $debug and myprint( "logfile : $logfile\n" ) ;
  6644. return( $logfile ) ;
  6645. }
  6646. sub tests_logfile {
  6647. SKIP: {
  6648. # Too hard to have a well known timezone on Windows
  6649. skip( 'Too hard to have a well known timezone on Windows', 6 ) if ( 'MSWin32' eq $OSNAME ) ;
  6650. local $ENV{TZ} = 'GMT' ;
  6651. { POSIX::tzset unless ('MSWin32' eq $OSNAME) ;
  6652. ok( '1970_01_01_00_00_00.txt' eq logfile( ), 'logfile: no args => 1970_01_01_00_00_00.txt' ) ;
  6653. ok( '1970_01_01_00_00_00.txt' eq logfile( 0 ), 'logfile: 0 => 1970_01_01_00_00_00.txt' ) ;
  6654. ok( '1970_01_01_00_01_01.txt' eq logfile( 61 ), 'logfile: 0 => 1970_01_01_00_01_01.txt' ) ;
  6655. ok( '2010_08_24_14_00_00.txt' eq logfile( 1282658400 ), 'logfile: 1282658400 => 2010_08_24_14_00_00.txt' ) ;
  6656. ok( '2010_08_24_14_01_01.txt' eq logfile( 1282658461 ), 'logfile: 1282658461 => 2010_08_24_14_01_01.txt' ) ;
  6657. ok( '2010_08_24_14_01_01_poupinette.txt' eq logfile( 1282658461, 'poupinette' ), 'logfile: 1282658461 poupinette => 2010_08_24_14_01_01_poupinette.txt' ) ;
  6658. }
  6659. POSIX::tzset unless ('MSWin32' eq $OSNAME) ;
  6660. } ;
  6661. return ;
  6662. }
  6663. sub tests_million_folders_baby_2 {
  6664. my %long ;
  6665. @long{ 1 .. 900_000 } = (1) x 900_000 ;
  6666. #myprint( %long, "\n" ) ;
  6667. my $pasglop = 0 ;
  6668. foreach my $elem ( 1 .. 900_000 ) {
  6669. #$debug and myprint( "$elem " ) ;
  6670. if ( not exists $long{ $elem } ) {
  6671. $pasglop++ ;
  6672. }
  6673. }
  6674. ok( 0 == $pasglop, 'tests_million_folders_baby_2: search among 900_000' ) ;
  6675. # myprint( "$pasglop\n" ) ;
  6676. return ;
  6677. }
  6678. sub tests_always_fail {
  6679. ok( 0 == 1, '0 == 1' ) ;
  6680. ok( 1 == 1, '1 == 1' ) ;
  6681. return ;
  6682. }
  6683. sub logfileprepa {
  6684. my $logfile = shift ;
  6685. my $dirname = dirname( $logfile ) ;
  6686. is_valid_directory( $dirname ) || return( 0 ) ;
  6687. return( 1 ) ;
  6688. }
  6689. sub teelaunch {
  6690. my $mysync = shift ;
  6691. my $logfile = $mysync->{logfile} ;
  6692. logfileprepa( $logfile ) || croak "Error no valid directory to write log file $logfile : $!" ;
  6693. my $logfile_handle ;
  6694. open $logfile_handle, '>', $logfile
  6695. or croak( "Can not open $logfile for write: $!" ) ;
  6696. my $tee = IO::Tee->new( $logfile_handle, \*STDOUT ) ;
  6697. *STDERR = *$tee{IO} ;
  6698. select $tee ;
  6699. $tee->autoflush( 1 ) ;
  6700. $mysync->{logfile_handle} = $logfile_handle ;
  6701. $mysync->{tee} = $tee ;
  6702. return $logfile_handle ;
  6703. }
  6704. sub getpwuid_any_os {
  6705. my $uid = shift ;
  6706. return( scalar getlogin ) if ( 'MSWin32' eq $OSNAME ) ; # Windows system
  6707. return( scalar getpwuid $uid ) ; # Unix system
  6708. }
  6709. sub usage {
  6710. my $escape_char = ( 'MSWin32' eq $OSNAME ) ? '^' : '\\';
  6711. myprint( <<"EOF" ) ;
  6712. usage: $0 [options]
  6713. Several options are mandatory.
  6714. str means string
  6715. int means integer
  6716. reg means regular expression
  6717. cmd means command
  6718. --dry : Makes kopano-migration-imap doing nothing, just print what would
  6719. be done without --dry.
  6720. --host1 str : Source or "from" imap server. Mandatory.
  6721. --port1 int : Port to connect on host1. Default is 143, 993 if --ssl1
  6722. --user1 str : User to login on host1. Mandatory.
  6723. --showpasswords : Shows passwords on output instead of "MASKED".
  6724. Useful to restart a complete run by just reading the log.
  6725. --password1 str : Password for the user1.
  6726. --host2 str : "destination" imap server. Mandatory.
  6727. --port2 int : Port to connect on host2. Default is 143, 993 if --ssl2
  6728. --user2 str : User to login on host2. Mandatory.
  6729. --password2 str : Password for the user2.
  6730. --passfile1 str : Password file for the user1. It must contain the
  6731. password on the first line. This option avoids to show
  6732. the password on the command line like --password1 does.
  6733. --passfile2 str : Password file for the user2. Contains the password.
  6734. --ssl1 : Use a SSL connection on host1.
  6735. --ssl2 : Use a SSL connection on host2.
  6736. --tls1 : Use a TLS connection on host1.
  6737. --tls2 : Use a TLS connection on host2.
  6738. --debugssl int : SSL debug mode from 0 to 4.
  6739. --sslargs1 str : Pass any ssl parameter for host1 ssl or tls connection. Example:
  6740. --sslargs1 SSL_verify_mode=1 --sslargs1 SSL_version=SSLv3
  6741. See all possibilities in the new() method of IO::Socket::SSL
  6742. http://search.cpan.org/perldoc?IO::Socket::SSL#Description_Of_Methods
  6743. --sslargs2 str : Pass any ssl parameter for host2 ssl or tls connection.
  6744. See --sslargs1
  6745. --timeout1 int : Connection timeout in seconds for host1.
  6746. Default is 120 and 0 means no timeout at all.
  6747. --timeout2 int : Connection timeout in seconds for host2.
  6748. Default is 120 and 0 means no timeout at all.
  6749. --authmech1 str : Auth mechanism to use with host1:
  6750. PLAIN, LOGIN, CRAM-MD5 etc. Use UPPERCASE.
  6751. --authmech2 str : Auth mechanism to use with host2. See --authmech1
  6752. --authuser1 str : User to auth with on host1 (admin user).
  6753. Avoid using --authmech1 SOMETHING with --authuser1.
  6754. --authuser2 str : User to auth with on host2 (admin user).
  6755. --proxyauth1 : Use proxyauth on host1. Requires --authuser1.
  6756. Required by Sun/iPlanet/Netscape IMAP servers to
  6757. be able to use an administrative user.
  6758. --proxyauth2 : Use proxyauth on host2. Requires --authuser2.
  6759. --authmd51 : Use MD5 authentification for host1.
  6760. --authmd52 : Use MD5 authentification for host2.
  6761. --domain1 str : Domain on host1 (NTLM authentication).
  6762. --domain2 str : Domain on host2 (NTLM authentication).
  6763. --folder str : Sync this folder.
  6764. --folder str : and this one, etc.
  6765. --folderrec str : Sync this folder recursively.
  6766. --folderrec str : and this one, etc.
  6767. --folderfirst str : Sync this folder first. --folderfirst "Work"
  6768. --folderfirst str : then this one, etc.
  6769. --folderlast str : Sync this folder last. --folderlast "[Gmail]/All Mail"
  6770. --folderlast str : then this one, etc.
  6771. --nomixfolders : Do not merge folders when host1 is case sensitive
  6772. while host2 is not (like Exchange). Only the first
  6773. similar folder is synced (ex: Sent SENT sent -> Sent).
  6774. --skipemptyfolders : Empty host1 folders are not created on host2.
  6775. --include reg : Sync folders matching this regular expression
  6776. --include reg : or this one, etc.
  6777. in case both --include --exclude options are
  6778. use, include is done before.
  6779. --exclude reg : Skips folders matching this regular expression
  6780. Several folders to avoid:
  6781. --exclude 'fold1|fold2|f3' skips fold1, fold2 and f3.
  6782. --exclude reg : or this one, etc.
  6783. --subfolder2 str : Move whole host1 folders hierarchy under this
  6784. host2 folder str .
  6785. It does it by adding two --regextrans2 options before
  6786. all others. Add --debug to see what's really going on.
  6787. --automap : guesses folders mapping, for folders like
  6788. "Sent", "Junk", "Drafts", "All", "Archive", "Flagged".
  6789. --f1f2 str1=str2 : Force folder str1 to be synced to str2,
  6790. --f1f2 overrides --automap and --regextrans2.
  6791. --regextrans2 reg : Apply the whole regex to each destination folders.
  6792. --regextrans2 reg : and this one. etc.
  6793. When you play with the --regextrans2 option, first
  6794. add also the safe options --dry --justfolders
  6795. Then, when happy, remove --dry, remove --justfolders.
  6796. Have in mind that --regextrans2 is applied after prefix
  6797. and separator inversion.
  6798. --tmpdir str : Where to store temporary files and subdirectories.
  6799. Will be created if it doesn't exist.
  6800. Default is system specific, Unix is /tmp but
  6801. it's often small and deleted at reboot.
  6802. --tmpdir /var/tmp should be better.
  6803. --pidfile str : The file where kopano-migration-imap pid is written.
  6804. --pidfilelocking : Abort if pidfile already exists. Usefull to avoid
  6805. concurrent transfers on the same mailbox.
  6806. --nolog : Turn off logging on file
  6807. --logfile str : Change the default log filename (can be dirname/filename).
  6808. --logdir str : Change the default log directory. Default is LOG_kopano-migration-imap
  6809. --prefix1 str : Remove prefix to all destination folders
  6810. (usually INBOX. or INBOX/ or an empty string "")
  6811. you have to use --prefix1 if host1 imap server
  6812. does not have NAMESPACE capability, so kopano-migration-imap
  6813. suggests to use it. All other cases are bad.
  6814. --prefix2 str : Add prefix to all host2 folders. See --prefix1
  6815. --sep1 str : Host1 separator in case NAMESPACE is not supported.
  6816. --sep2 str : Host2 separator in case NAMESPACE is not supported.
  6817. --skipmess reg : Skips messages maching the regex.
  6818. Example: 'm/[\\x80-ff]/' # to avoid 8bits messages.
  6819. --skipmess is applied before --regexmess
  6820. --skipmess reg : or this one, etc.
  6821. --pipemess cmd : Apply this cmd command to each message content
  6822. before the copy.
  6823. --pipemess cmd : and this one, etc.
  6824. --disarmreadreceipts : Disarms read receipts (host2 Exchange issue)
  6825. --regexmess reg : Apply the whole regex to each message before transfer.
  6826. Example: 's/\\000/ /g' # to replace null by space.
  6827. --regexmess reg : and this one, etc.
  6828. --regexflag reg : Apply the whole regex to each flags list.
  6829. Example: 's/\"Junk"//g' # to remove "Junk" flag.
  6830. --regexflag reg : and this one, etc.
  6831. --delete : Deletes messages on host1 server after a successful
  6832. transfer. Option --delete has the following behavior:
  6833. it marks messages as deleted with the IMAP flag
  6834. \\Deleted, then messages are really deleted with an
  6835. EXPUNGE IMAP command.
  6836. --delete2 : Delete messages in host2 that are not in
  6837. host1 server. Useful for backup or pre-sync.
  6838. --delete2duplicates : Delete messages in host2 that are duplicates.
  6839. Works only without --useuid since duplicates are
  6840. detected with a header part of each message.
  6841. --delete2folders : Delete folders in host2 that are not in host1 server.
  6842. For safety, first try it like this (it is safe):
  6843. --delete2folders --dry --justfolders --nofoldersizes
  6844. --delete2foldersonly reg : Deleted only folders matching regex.
  6845. Example: --delete2foldersonly "/^Junk\$|^INBOX.Junk\$/"
  6846. --delete2foldersbutnot reg : Do not delete folders matching regex.
  6847. Example: --delete2foldersbutnot "/Tasks\$|Contacts\$|Foo\$/"
  6848. --noexpunge : Do not expunge messages on host1.
  6849. Expunge really deletes messages marked deleted.
  6850. Expunge is made at the beginning, on host1 only.
  6851. Newly transferred messages are also expunged if
  6852. option --delete is given.
  6853. No expunge is done on host2 account (unless --expunge2)
  6854. --expunge1 : Expunge messages on host1 after messages transfer.
  6855. --expunge2 : Expunge messages on host2 after messages transfer.
  6856. --uidexpunge2 : uidexpunge messages on the host2 account
  6857. that are not on the host1 account, requires --delete2
  6858. --nomixfolders : Avoid merging folders that are considered different on
  6859. host1 but the same on destination host2 because of
  6860. case sensitivities and insensitivities.
  6861. --syncinternaldates : Sets the internal dates on host2 same as host1.
  6862. Turned on by default. Internal date is the date
  6863. a message arrived on a host (mtime).
  6864. --idatefromheader : Sets the internal dates on host2 same as the
  6865. "Date:" headers.
  6866. --maxsize int : Skip messages larger (or equal) than int bytes
  6867. --minsize int : Skip messages smaller (or equal) than int bytes
  6868. --maxage int : Skip messages older than int days.
  6869. final stats (skipped) don't count older messages
  6870. see also --minage
  6871. --minage int : Skip messages newer than int days.
  6872. final stats (skipped) don't count newer messages
  6873. You can do (+ are the messages selected):
  6874. past|----maxage+++++++++++++++>now
  6875. past|+++++++++++++++minage---->now
  6876. past|----maxage+++++minage---->now (intersection)
  6877. past|++++minage-----maxage++++>now (union)
  6878. --search str : Selects only messages returned by this IMAP SEARCH
  6879. command. Applied on both sides.
  6880. --search1 str : Same as --search for selecting host1 messages only.
  6881. --search2 str : Same as --search for selecting host2 messages only.
  6882. --search CRIT equals --search1 CRIT --search2 CRIT
  6883. --exitwhenover int : Stop syncing when total bytes transferred reached.
  6884. Gmail per day allows
  6885. 2500000000 = 2.5 GB downloaded from Gmail as host2
  6886. 500000000 = 500 MB uploaded to Gmail as host1.
  6887. --maxlinelength int : skip messages with a line length longer than int bytes.
  6888. RFC 2822 says it must be no more than 1000 bytes.
  6889. --useheader str : Use this header to compare messages on both sides.
  6890. Ex: Message-ID or Subject or Date.
  6891. --useheader str and this one, etc.
  6892. --subscribed : Transfers subscribed folders.
  6893. --subscribe : Subscribe to the folders transferred on the
  6894. host2 that are subscribed on host1. On by default.
  6895. --subscribeall : Subscribe to the folders transferred on the
  6896. host2 even if they are not subscribed on host1.
  6897. --nofoldersizes : Do not calculate the size of each folder in bytes
  6898. and message counts. Default is to calculate them.
  6899. --nofoldersizesatend: Do not calculate the size of each folder in bytes
  6900. and message counts at the end. Default is on.
  6901. --justfoldersizes : Exit after having printed the folder sizes.
  6902. --syncacls : Synchronises acls (Access Control Lists).
  6903. --nosyncacls : Does not synchronize acls. This is the default.
  6904. Acls in IMAP are not standardized, be careful.
  6905. --usecache : Use cache to speedup.
  6906. --nousecache : Do not use cache. Caveat: --useuid --nousecache creates
  6907. duplicates on multiple runs.
  6908. --useuid : Use uid instead of header as a criterium to recognize
  6909. messages. Option --usecache is then implied unless
  6910. --nousecache is used.
  6911. --debug : Debug mode.
  6912. --debugfolders : Debug mode for the folders part only.
  6913. --debugcontent : Debug content of the messages transfered. Huge ouput.
  6914. --debugflags : Debug mode for flags.
  6915. --debugimap1 : IMAP debug mode for host1. Very verbose.
  6916. --debugimap2 : IMAP debug mode for host2. Very verbose.
  6917. --debugimap : IMAP debug mode for host1 and host2.
  6918. --debugmemory : Debug mode showing memory consumption after each copy.
  6919. --errorsmax int : Exit when int number of errors is reached. Default is 50.
  6920. --tests : Run local non-regression tests. Exit code 0 means all ok.
  6921. --testslive : Run a live test with test1.kopano.com imap server.
  6922. Useful to check the basics. Needs internet connexion.
  6923. --version : Print only software version.
  6924. --noid : Do not send/receive ID command to imap servers.
  6925. --justconnect : Just connect to both servers and print useful
  6926. information. Need only --host1 and --host2 options.
  6927. --justlogin : Just login to both host1 and host2 with users
  6928. credentials, then exit.
  6929. --justfolders : Do only things about folders (ignore messages).
  6930. --help : print this help.
  6931. Example: to synchronize imap account "test1" on "test1.kopano.com"
  6932. to imap account "test2" on "test2.kopano.com"
  6933. with test1 password "secret1"
  6934. and test2 password "secret2"
  6935. $0 $escape_char
  6936. --host1 test1.kopano.com --user1 test1 --password1 secret1 $escape_char
  6937. --host2 test2.kopano.com --user2 test2 --password2 secret2
  6938. EOF
  6939. return( 1 ) ;
  6940. }
  6941. sub usage_complete {
  6942. myprint( <<'EOF' ) ;
  6943. --skipheader reg : Don't take into account header keyword
  6944. matching reg ex: --skipheader 'X.*'
  6945. --skipsize : Don't take message size into account to compare
  6946. messages on both sides. On by default.
  6947. Use --no-skipsize for using size comparaison.
  6948. --allowsizemismatch : allow RFC822.SIZE != fetched msg size
  6949. consider also --skipsize to avoid duplicate messages
  6950. when running syncs more than one time per mailbox
  6951. --reconnectretry1 int : reconnect to host1 if connection is lost up to
  6952. int times per imap command (default is 3)
  6953. --reconnectretry2 int : same as --reconnectretry1 but for host2
  6954. --split1 int : split the requests in several parts on host1.
  6955. int is the number of messages handled per request.
  6956. default is like --split1 500.
  6957. --split2 int : same thing on host2.
  6958. --nofixInboxINBOX : Don't fix Inbox INBOX mapping.
  6959. EOF
  6960. return ;
  6961. }
  6962. sub get_options {
  6963. # In CGI context arguments are not in @ARGV but in QUERY_STRING variable (with GET).
  6964. my $numopt = scalar @ARGV || length $ENV{'QUERY_STRING'} ;
  6965. my $argv = join "\x00", @ARGV ;
  6966. if ( $argv =~ m/-delete\x002/x ) {
  6967. myprint( "May be you mean --delete2 instead of --delete 2\n" ) ;
  6968. exit 1 ;
  6969. }
  6970. $sync->{f1f2} = {} ;
  6971. my $opt_ret = Imapsync::Getopt::Long::GetOptions(
  6972. 'debug!' => \$debug,
  6973. 'debuglist!' => \$debuglist,
  6974. 'debugcontent!' => \$debugcontent,
  6975. 'debugsleep=f' => \$sync->{debugsleep},
  6976. 'debugflags!' => \$debugflags,
  6977. 'debugimap!' => \$debugimap,
  6978. 'debugimap1!' => \$debugimap1,
  6979. 'debugimap2!' => \$debugimap2,
  6980. 'debugdev!' => \$debugdev,
  6981. 'debugmemory!' => \$sync->{debugmemory},
  6982. 'debugfolders!' => \$sync->{debugfolders},
  6983. 'debugssl=i' => \$sync->{debugssl},
  6984. 'debugbasket=s' => \@debugbasket,
  6985. 'debugcgi!' => \$debugcgi,
  6986. 'host1=s' => \$host1,
  6987. 'host2=s' => \$host2,
  6988. 'port1=i' => \$port1,
  6989. 'port2=i' => \$port2,
  6990. 'inet4' => \$sync->{inet4},
  6991. 'inet6' => \$sync->{inet6},
  6992. 'user1=s' => \$user1,
  6993. 'user2=s' => \$user2,
  6994. 'domain1=s' => \$domain1,
  6995. 'domain2=s' => \$domain2,
  6996. 'password1=s' => \$password1,
  6997. 'password2=s' => \$password2,
  6998. 'passfile1=s' => \$passfile1,
  6999. 'passfile2=s' => \$passfile2,
  7000. 'authmd5!' => \$authmd5,
  7001. 'authmd51!' => \$authmd51,
  7002. 'authmd52!' => \$authmd52,
  7003. 'sep1=s' => \$sep1,
  7004. 'sep2=s' => \$sep2,
  7005. 'folder=s' => \@folder,
  7006. 'folderrec=s' => \@folderrec,
  7007. 'include=s' => \@include,
  7008. 'exclude=s' => \@exclude,
  7009. 'folderfirst=s' => \@folderfirst,
  7010. 'folderlast=s' => \@folderlast,
  7011. 'prefix1=s' => \$prefix1,
  7012. 'prefix2=s' => \$prefix2,
  7013. 'subfolder2=s' => \$subfolder2,
  7014. 'fixslash2!' => \$fixslash2,
  7015. 'fixInboxINBOX!' => \$fixInboxINBOX,
  7016. 'regextrans2=s' => \@regextrans2,
  7017. 'mixfolders!' => \$mixfolders,
  7018. 'skipemptyfolders!' => \$skipemptyfolders,
  7019. 'regexmess=s' => \@regexmess,
  7020. 'skipmess=s' => \@skipmess,
  7021. 'pipemess=s' => \@pipemess,
  7022. 'pipemesscheck!' => \$pipemesscheck,
  7023. 'disarmreadreceipts!' => \$disarmreadreceipts,
  7024. 'regexflag=s' => \@regexflag,
  7025. 'filterflags!' => \$filterflags,
  7026. 'flagscase!' => \$flagscase,
  7027. 'syncflagsaftercopy!' => \$syncflagsaftercopy,
  7028. 'delete|delete1!' => \$delete,
  7029. 'delete2!' => \$delete2,
  7030. 'delete2duplicates!' => \$delete2duplicates,
  7031. 'delete2folders!' => \$delete2folders,
  7032. 'delete2foldersonly=s' => \$delete2foldersonly,
  7033. 'delete2foldersbutnot=s' => \$delete2foldersbutnot,
  7034. 'syncinternaldates!' => \$syncinternaldates,
  7035. 'idatefromheader!' => \$idatefromheader,
  7036. 'syncacls!' => \$syncacls,
  7037. 'maxsize=i' => \$maxsize,
  7038. 'minsize=i' => \$minsize,
  7039. 'maxage=i' => \$maxage,
  7040. 'minage=i' => \$minage,
  7041. 'search=s' => \$search,
  7042. 'search1=s' => \$search1,
  7043. 'search2=s' => \$search2,
  7044. 'foldersizes!' => \$foldersizes,
  7045. 'foldersizesatend!' => \$foldersizesatend,
  7046. 'dry!' => \$dry,
  7047. 'expunge!' => \$expunge,
  7048. 'expunge1!' => \$expunge1,
  7049. 'expunge2!' => \$expunge2,
  7050. 'uidexpunge2!' => \$uidexpunge2,
  7051. 'subscribed!' => \$subscribed,
  7052. 'subscribe!' => \$subscribe,
  7053. 'subscribeall|subscribe_all!' => \$subscribeall,
  7054. 'justbanner!' => \$justbanner,
  7055. 'justconnect!'=> \$justconnect,
  7056. 'justfolders!'=> \$justfolders,
  7057. 'justfoldersizes!' => \$justfoldersizes,
  7058. 'fast!' => \$fast,
  7059. 'version' => \$version,
  7060. 'help' => \$help,
  7061. 'timeout=i' => \$timeout,
  7062. 'timeout1=i' => \$sync->{h1}->{timeout},
  7063. 'timeout2=i' => \$sync->{h2}->{timeout},
  7064. 'skipheader=s' => \$skipheader,
  7065. 'useheader=s' => \@useheader,
  7066. 'wholeheaderifneeded!' => \$wholeheaderifneeded,
  7067. 'messageidnodomain!' => \$messageidnodomain,
  7068. 'skipsize!' => \$skipsize,
  7069. 'allowsizemismatch!' => \$allowsizemismatch,
  7070. 'fastio1!' => \$fastio1,
  7071. 'fastio2!' => \$fastio2,
  7072. 'ssl1!' => \$ssl1,
  7073. 'ssl2!' => \$ssl2,
  7074. 'ssl1_ssl_version=s' => \$sync->{h1}->{sslargs}->{SSL_version},
  7075. 'ssl2_ssl_version=s' => \$sync->{h2}->{sslargs}->{SSL_version},
  7076. 'sslargs1=s%' => \$sync->{h1}->{sslargs},
  7077. 'sslargs2=s%' => \$sync->{h2}->{sslargs},
  7078. 'tls1!' => \$tls1,
  7079. 'tls2!' => \$tls2,
  7080. 'uid1!' => \$uid1,
  7081. 'uid2!' => \$uid2,
  7082. 'authmech1=s' => \$authmech1,
  7083. 'authmech2=s' => \$authmech2,
  7084. 'authuser1=s' => \$authuser1,
  7085. 'authuser2=s' => \$authuser2,
  7086. 'proxyauth1' => \$proxyauth1,
  7087. 'proxyauth2' => \$proxyauth2,
  7088. 'split1=i' => \$split1,
  7089. 'split2=i' => \$split2,
  7090. 'buffersize=i' => \$buffersize,
  7091. 'reconnectretry1=i' => \$reconnectretry1,
  7092. 'reconnectretry2=i' => \$reconnectretry2,
  7093. 'tests!' => \$tests,
  7094. 'testsdebug|tests_debug!' => \$testsdebug,
  7095. 'testslive!' => \$testslive,
  7096. 'justlogin!' => \$justlogin,
  7097. 'tmpdir=s' => \$tmpdir,
  7098. 'pidfile=s' => \$sync->{pidfile},
  7099. 'pidfilelocking!' => \$sync->{pidfilelocking},
  7100. 'modulesversion|modules_version!' => \$modulesversion,
  7101. 'usecache!' => \$usecache,
  7102. 'cacheaftercopy!' => \$cacheaftercopy,
  7103. 'debugcache!' => \$debugcache,
  7104. 'useuid!' => \$useuid,
  7105. 'addheader!' => \$addheader,
  7106. 'exitwhenover=i' => \$exitwhenover,
  7107. 'checkselectable!' => \$checkselectable,
  7108. 'checkmessageexists!' => \$checkmessageexists,
  7109. 'expungeaftereach!' => \$expungeaftereach,
  7110. 'abletosearch!' => \$abletosearch,
  7111. 'showpasswords!' => \$showpasswords,
  7112. 'maxlinelength=i' => \$maxlinelength,
  7113. 'maxlinelengthcmd=s' => \$maxlinelengthcmd,
  7114. 'minmaxlinelength=i' => \$minmaxlinelength,
  7115. 'debugmaxlinelength!' => \$debugmaxlinelength,
  7116. 'fixcolonbug!' => \$fixcolonbug,
  7117. 'create_folder_old!' => \$create_folder_old,
  7118. 'maxmessagespersecond=f' => \$maxmessagespersecond,
  7119. 'maxbytespersecond=i' => \$maxbytespersecond,
  7120. 'skipcrossduplicates!' => \$skipcrossduplicates,
  7121. 'debugcrossduplicates!' => \$debugcrossduplicates,
  7122. 'log!' => \$sync->{log},
  7123. 'logfile=s' => \$sync->{logfile},
  7124. 'logdir=s' => \$sync->{logdir},
  7125. 'errorsmax=i' => \$sync->{errorsmax},
  7126. 'errorsdump!' => \$sync->{errorsdump},
  7127. 'fetch_hash_set=s' => \$fetch_hash_set,
  7128. 'automap!' => \$sync->{automap},
  7129. 'justautomap!' => \$sync->{justautomap},
  7130. 'id!' => \$sync->{id},
  7131. 'f1f2=s%' => \$sync->{f1f2},
  7132. 'justfolderlists!' => \$sync->{justfolderlists},
  7133. 'delete1emptyfolders' => \$sync->{delete1emptyfolders},
  7134. ) ;
  7135. $debugcgi and myprint( map { "$_ => $ENV{$_}\n" } sort keys %ENV ) ;
  7136. $debugcgi and myprint( "@debugbasket\n" ) ;
  7137. $debug and myprint( "get options: [$opt_ret]\n" ) ;
  7138. # just the version
  7139. myprint( kopano_migration_imap_version( ), "\n" ) and exit 0 if ( $version ) ;
  7140. # $tmpdir is used in tests_pipemess()
  7141. $tmpdir ||= File::Spec->tmpdir( ) ;
  7142. if ( $tests or $testsdebug ) {
  7143. $test_builder = Test::More->builder ;
  7144. if ( $tests ) { tests( ) ; }
  7145. if ( $testsdebug ) { testsdebug( ) ; }
  7146. #$test_builder->reset( ) ;
  7147. exit ;
  7148. }
  7149. #$help = 1 if ! $numopt;
  7150. load_modules( );
  7151. # exit with --help option or no option at all
  7152. $debug and myprint( "numopt:$numopt\n" ) ;
  7153. usage( ) and exit if ( $help or not $numopt ) ;
  7154. # don't go on if options are not all known.
  7155. exit $EX_USAGE unless ( $opt_ret ) ;
  7156. # init live varaiables
  7157. testslive( ) if ( $testslive ) ;
  7158. return ;
  7159. }
  7160. sub testslive {
  7161. $host1 = 'test1.kopano.com' ;
  7162. $user1 = 'test1' ;
  7163. $password1 = 'secret1' ;
  7164. $host2 = 'test2.kopano.com' ;
  7165. $user2 = 'test2' ;
  7166. $password2 ='secret2' ;
  7167. return ;
  7168. }
  7169. sub testsdebug {
  7170. SKIP: {
  7171. skip 'No test in normal run' if ( not $testsdebug ) ;
  7172. #tests_bytes_display_string( ) ;
  7173. #tests_ucsecond( ) ;
  7174. #tests_mkpath( ) ;
  7175. #eval { tests_mkpath( ) ; } or ok( 0 == 1, 'tests_mkpath fail badly?' ) ;
  7176. #tests_format_for_imap_arg( ) ;
  7177. #tests_is_a_release_number( ) ;
  7178. #tests_delete1emptyfolders( ) ;
  7179. #tests_memory_consumption( ) ;
  7180. #tests_imap2_folder_name() ;
  7181. #tests_length_ref( ) ;
  7182. #tests_is_valid_directory( ) ;
  7183. #tests_firstline( ) ;
  7184. #tests_diff_or_NA( ) ;
  7185. #tests_match_number( ) ;
  7186. #tests_all_defined( ) ;
  7187. #tests_guess_separator( ) ;
  7188. tests_pipemess( ) ;
  7189. #tests_message_for_host2( ) ;
  7190. done_testing( ) ;
  7191. note('End of kopano-migration-imap --tests_debug') ;
  7192. }
  7193. return ;
  7194. }
  7195. sub tests {
  7196. SKIP: {
  7197. skip 'No test in normal run' if ( not $tests ) ;
  7198. tests_folder_routines( ) ;
  7199. tests_compare_lists( ) ;
  7200. tests_regexmess();
  7201. tests_skipmess( ) ;
  7202. tests_flags_regex();
  7203. tests_ucsecond( ) ;
  7204. tests_permanentflags();
  7205. tests_flags_filter( ) ;
  7206. tests_separator_invert( ) ;
  7207. tests_imap2_folder_name() ;
  7208. tests_command_line_nopassword();
  7209. tests_good_date( ) ;
  7210. tests_max();
  7211. tests_remove_not_num();
  7212. tests_memory_consumption( ) ;
  7213. tests_is_a_release_number();
  7214. tests_list_keys_in_2_not_in_1();
  7215. tests_convert_sep_to_slash( ) ;
  7216. tests_match_a_cache_file( ) ;
  7217. tests_cache_map( ) ;
  7218. tests_get_cache( ) ;
  7219. tests_clean_cache( ) ;
  7220. tests_clean_cache_2( ) ;
  7221. tests_touch( ) ;
  7222. tests_flagscase( ) ;
  7223. eval { tests_mkpath( ) ; } or ok( 0 == 1, 'tests_mkpath fail badly?' ) ;
  7224. tests_extract_header( ) ;
  7225. tests_decompose_header( ) ;
  7226. tests_epoch( ) ;
  7227. tests_add_header( ) ;
  7228. tests_cache_dir_fix( ) ;
  7229. tests_cache_dir_fix_win( ) ;
  7230. tests_filter_forbidden_characters( ) ;
  7231. tests_cache_folder( ) ;
  7232. tests_time_remaining( ) ;
  7233. tests_decompose_regex( ) ;
  7234. tests_Banner( ) ;
  7235. tests_backtick( ) ;
  7236. tests_bytes_display_string( ) ;
  7237. tests_header_line_normalize( ) ;
  7238. tests_fix_Inbox_INBOX_mapping( ) ;
  7239. tests_max_line_length( ) ;
  7240. tests_subject( ) ;
  7241. tests_msgs_from_maxmin( ) ;
  7242. tests_tmpdir_has_colon_bug( ) ;
  7243. tests_sleep_max_messages( ) ;
  7244. tests_sleep_max_bytes( ) ;
  7245. tests_logfile( ) ;
  7246. tests_setlogfile( ) ;
  7247. tests_jux_utf8( ) ;
  7248. tests_pipemess( ) ;
  7249. tests_jux_utf8_list( ) ;
  7250. tests_guess_prefix( ) ;
  7251. tests_guess_separator( ) ;
  7252. tests_format_for_imap_arg( ) ;
  7253. tests_imapsync_id( ) ;
  7254. tests_date_from_rcs( ) ;
  7255. tests_quota_extract_storage_limit_in_bytes( ) ;
  7256. tests_quota_extract_storage_current_in_bytes( ) ;
  7257. tests_guess_special( ) ;
  7258. tests_is_valid_directory( ) ;
  7259. tests_delete1emptyfolders( ) ;
  7260. tests_message_for_host2( ) ;
  7261. tests_length_ref( ) ;
  7262. tests_firstline( ) ;
  7263. tests_diff_or_NA( ) ;
  7264. #tests_always_fail( ) ;
  7265. tests_match_number( ) ;
  7266. tests_all_defined( ) ;
  7267. done_testing( 693 ) ;
  7268. note('End of kopano-migration-imap --tests') ;
  7269. }
  7270. return ;
  7271. }
  7272. # IMAPClient 3.xx ads
  7273. package Mail::IMAPClient;
  7274. sub Tls {
  7275. my $self = shift ;
  7276. my $value = shift ;
  7277. if ( defined $value ) { $self->{TLS} = $value }
  7278. return $self->{TLS};
  7279. }
  7280. sub Reconnect_counter {
  7281. my $self = shift ;
  7282. my $value = shift ;
  7283. $self->{Reconnect_counter} = 0 if ( not defined $self->{Reconnect_counter} ) ;
  7284. if ( defined $value ) { $self->{Reconnect_counter} = $value }
  7285. return( $self->{Reconnect_counter} ) ;
  7286. }
  7287. sub Banner {
  7288. my $self = shift ;
  7289. my $value = shift ;
  7290. if ( defined $value ) { $self->{ BANNER } = $value }
  7291. return $self->{ BANNER };
  7292. }
  7293. sub capability_update {
  7294. my $self = shift ;
  7295. delete $self->{CAPABILITY} ;
  7296. return( $self->capability ) ;
  7297. }
  7298. package Imapsync::Getopt::Long ;
  7299. # Started as a copy of Luke Ross Getopt::Long::CGI
  7300. # https://metacpan.org/release/Getopt-Long-CGI
  7301. # So this section is under the same license as Getopt-Long-CGI Luke Ross wants it,
  7302. # which was Perl 5.6 or later licenses at the date of the copy.
  7303. use strict ;
  7304. use warnings ;
  7305. use Getopt::Long( ) ;
  7306. sub GetOptions {
  7307. my %options = @_ ;
  7308. if ( not $ENV{SERVER_SOFTWARE} ) {
  7309. # Not CGI - pass upstream for normal command line handling
  7310. return Getopt::Long::GetOptions( %options ) ;
  7311. }
  7312. my $b_ref = $options{'debugbasket=s'} ;
  7313. require CGI ;
  7314. require CGI::Carp ;
  7315. CGI::Carp->import( 'fatalsToBrowser' ) ;
  7316. my $cgi = CGI->new( ) ;
  7317. $cgi->param( 'debugcgi' ) and myprint( "<h2>Current Values</h2>\n" . $cgi->Dump ) ;
  7318. foreach my $key (sort keys %options) {
  7319. my $val = $options{$key};
  7320. #push( @{$b_ref}, "opt:[$key] val:[$val]" . ( ('SCALAR' eq ref($val) and defined $$val ) ? " [$$val]" : q{} ) . "\n" ) ;
  7321. if ( $key !~ m/^([\w\d\|]+)([=:][isf])?([\+!\@\%])?$/ ) {
  7322. push @{$b_ref}, "Unknown opt: [$key]\n" ;
  7323. next ; # Unknown item
  7324. }
  7325. my $name = [split '|', $1, 1 ]->[0];
  7326. if (($3 || q{}) eq '+') {
  7327. ${ $val } = $cgi->param($name); # "Incremental" integer
  7328. } elsif ($2) {
  7329. my @values = $cgi->param($name);
  7330. my $type = $2;
  7331. if (($3 || q{}) eq '%' or ref($val) eq 'HASH') {
  7332. my %values = map { split /=/, $_, 1 } @values;
  7333. if ($type =~ m/i$/) {
  7334. foreach my $k (keys %values) {
  7335. $values{$k} = int $values{$k} ;
  7336. }
  7337. } elsif ($type =~ m/f$/) {
  7338. foreach my $k (keys %values) {
  7339. $values{$k} = 0 + $values{$k}
  7340. }
  7341. }
  7342. if ( ref($val) eq 'CODE') {
  7343. while(my($k, $v) = each %values) {
  7344. $val->($name, $k, $v);
  7345. }
  7346. } elsif ( 'REF' eq ref $val ) {
  7347. #push( @{$b_ref}, "refref($$val): " . ref($$val) . " %values= ", %values, "\n\n" ) ;
  7348. %{ ${ $val } } = %values;
  7349. } else {
  7350. #push( @{$b_ref}, "ref($val): " . ref($val) . " %values= ", %values, "\n\n" ) ;
  7351. %{ $val } = %values;
  7352. }
  7353. } else {
  7354. if ($type =~ m/i$/) {
  7355. @values = map { int $_ } @values;
  7356. } elsif ($type =~ m/f$/) {
  7357. @values = map { 0 + $_ } @values;
  7358. }
  7359. if (($3 || q{}) eq '@' or ref($val) eq 'ARRAY') {
  7360. if (ref($val) eq 'CODE') {
  7361. $val->($name, \@values)
  7362. } else {
  7363. @{ $val } = @values ;
  7364. }
  7365. } else {
  7366. if (ref($val) eq 'CODE') {
  7367. $val->($name, $values[0]);
  7368. } else {
  7369. ${ $val } = $values[0];
  7370. }
  7371. }
  7372. }
  7373. } else {
  7374. # Checkbox
  7375. ${ $val } = $cgi->param($name) ? 1 : undef ;
  7376. #push( @{$b_ref}, "param($name) ref($val): " . ref($val) . " val=[$$val]\n\n" ) ;
  7377. }
  7378. }
  7379. return( 1 ) ;
  7380. }