12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167 |
- /*
- * Copyright 2005 - 2016 Zarafa and its licensors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
- #include <kopano/platform.h>
- #include <memory>
- #include <utility>
- #include <cstdio>
- #include <cstdlib>
- #include <sstream>
- #include <iostream>
- #include <algorithm>
- #include <kopano/hl.hpp>
- #include <kopano/memory.hpp>
- #include <kopano/hl.hpp>
- #include <mapi.h>
- #include <mapix.h>
- #include <mapicode.h>
- #include <mapidefs.h>
- #include <mapiutil.h>
- #include <mapiguid.h>
- #include <kopano/ECDefs.h>
- #include <kopano/ECRestriction.h>
- #include <kopano/CommonUtil.h>
- #include <kopano/ECTags.h>
- #include <kopano/ECIConv.h>
- #include <kopano/Util.h>
- #include <kopano/lockhelper.hpp>
- #include <inetmapi/inetmapi.h>
- #include <kopano/mapiext.h>
- #include <vector>
- #include <list>
- #include <set>
- #include <unordered_set>
- #include <map>
- #include <algorithm>
- #include <inetmapi/options.h>
- #include <edkmdb.h>
- #include <kopano/stringutil.h>
- #include <kopano/codepage.h>
- #include <kopano/charset/convert.h>
- #include <kopano/ecversion.h>
- #include <kopano/ECGuid.h>
- #include <kopano/namedprops.h>
- #include "ECFeatures.h"
- #include <kopano/mapi_ptr.h>
- #include "IMAP.h"
- using namespace std;
- using namespace KCHL;
- /**
- * @ingroup gateway_imap
- * @{
- */
- static const string strMonth[] = {
- "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
- };
- /**
- * Returns TRUE if the given string starts with strPrefix
- *
- * @param[in] strInput string to find prefix in
- * @param[in] strPrefix test if input starts with this string
- */
- static bool Prefix(const std::string &strInput, const std::string &strPrefix)
- {
- return (strInput.compare(0, strPrefix.size(), strPrefix) == 0);
- }
- IMAP::IMAP(const char *szServerPath, ECChannel *lpChannel, ECLogger *lpLogger,
- ECConfig *lpConfig) :
- ClientProto(szServerPath, lpChannel, lpLogger, lpConfig)
- {
- imopt_default_delivery_options(&dopt);
- dopt.add_imap_data = parseBool(lpConfig->GetSetting("imap_store_rfc822"));
- dopt.html_safety_filter = strcasecmp(lpConfig->GetSetting("html_safety_filter"), "yes") == 0;
- bOnlyMailFolders = parseBool(lpConfig->GetSetting("imap_only_mailfolders"));
- bShowPublicFolder = parseBool(lpConfig->GetSetting("imap_public_folders"));
- cache_folders_time_limit = atoui(lpConfig->GetSetting("imap_cache_folders_time_limit"));
- }
- IMAP::~IMAP() {
- if (m_lpTable)
- m_lpTable->Release();
- CleanupObject();
- }
- void IMAP::CleanupObject()
- {
- // Free/release all new and allocated memory
- if (lpPublicStore)
- lpPublicStore->Release();
- lpPublicStore = NULL;
- if (lpStore)
- lpStore->Release();
- lpStore = NULL;
- if (lpAddrBook)
- lpAddrBook->Release();
- lpAddrBook = NULL;
- m_lpsIMAPTags.reset();
- if (lpSession)
- lpSession->Release();
- lpSession = NULL;
- // idle cleanup
- if (m_lpIdleAdviseSink)
- m_lpIdleAdviseSink->Release();
- m_lpIdleAdviseSink = NULL;
- if (m_lpIdleTable)
- m_lpIdleTable->Release();
- m_lpIdleTable = NULL;
- }
- void IMAP::ReleaseContentsCache()
- {
- if (m_lpTable)
- m_lpTable->Release();
- m_lpTable = NULL;
- m_vTableDataColumns.clear();
- }
- /**
- * Returns number of minutes to keep connection alive
- *
- * @return user logged in (true) or not (false)
- */
- int IMAP::getTimeoutMinutes() {
- if (lpStore != NULL)
- return 30; // 30 minutes when logged in
- else
- return 1; // 1 minute when not logged in
- }
- /**
- * Case insensitive std::string compare
- *
- * @param[in] strA compare with strB
- * @param[in] strB compare with strA
- * @return true if equal, not considering case differences
- */
- bool IMAP::CaseCompare(const string& strA, const string& strB)
- {
- return strcasecmp(strA.c_str(), strB.c_str()) == 0;
- }
- /**
- * Split a string with IMAP_HIERARCHY_DELIMITER into a vector
- *
- * @param[in] strInput Folder string with the IMAP_HIERARCHY_DELIMITER for a hierarchy
- * @param[out] vPaths Vector to all folders found in strInput
- * @retval hrSuccess
- */
- HRESULT IMAP::HrSplitPath(const wstring &strInputParam, vector<wstring> &vPaths) {
- std::wstring strInput = strInputParam;
- // Strip leading and trailing /
- if(strInput[0] == IMAP_HIERARCHY_DELIMITER)
- strInput.erase(0,1);
-
- if(strInput[strInput.size()-1] == IMAP_HIERARCHY_DELIMITER)
- strInput.erase(strInput.size()-1, 1);
-
- // split into parts
- vPaths = tokenize(strInput, IMAP_HIERARCHY_DELIMITER);
-
- return hrSuccess;
- }
- /**
- * Concatenates a vector to a string with IMAP_HIERARCHY_DELIMITER
- *
- * @param[in] vPaths Vector to all folders names
- * @param[out] strOutput Folder string with the IMAP_HIERARCHY_DELIMITER for a hierarchy
- * @retval hrSuccess
- */
- HRESULT IMAP::HrUnsplitPath(const vector<wstring> &vPaths, wstring &strOutput) {
- strOutput.clear();
-
- for (size_t i = 0; i < vPaths.size(); ++i) {
- strOutput += vPaths[i];
- if(i != vPaths.size()-1)
- strOutput += IMAP_HIERARCHY_DELIMITER;
- }
-
- return hrSuccess;
- }
- /**
- * Splits IMAP list input into a vector and appends result in return variable.
- *
- * @param[in] strInput Single IMAP command argument that should be split in words
- * @param[out] vWords vector of words found in the strInput
- * @retval hrSuccess
- */
- HRESULT IMAP::HrSplitInput(const string &strInput, vector<string> &vWords) {
- unsigned int uSpecialCount = 0;
- string::size_type beginPos = 0;
- string::size_type currentPos = 0;
- string::size_type findPos = strInput.find_first_of("\"()[] ", currentPos);
- string::size_type specialPos = string::npos;
- while (findPos != string::npos) {
- if (uSpecialCount == 0 && strInput[findPos] == '"') {
- // find corresponding " and add the string
- specialPos = findPos;
- do {
- specialPos = strInput.find_first_of("\"", specialPos + 1);
- } while (specialPos != string::npos && strInput[specialPos-1] == '\\');
- if (specialPos != string::npos) {
- vWords.push_back(strInput.substr(findPos + 1, specialPos - findPos - 1));
- findPos = specialPos;
- beginPos = findPos + 1;
- }
- } else if (strInput[findPos] == '(' || strInput[findPos] == '[') {
- ++uSpecialCount;
- } else if (strInput[findPos] == ')' || strInput[findPos] == ']') {
- if (uSpecialCount > 0)
- --uSpecialCount;
- } else if (uSpecialCount == 0) {
- if (findPos > beginPos)
- vWords.push_back(strInput.substr(beginPos, findPos - beginPos));
- beginPos = findPos + 1;
- }
- currentPos = findPos + 1;
- findPos = strInput.find_first_of("\"()[] ", currentPos);
- }
- if (beginPos < strInput.size())
- vWords.push_back(strInput.substr(beginPos));
- return hrSuccess;
- }
- /**
- * Send login greeting to client
- *
- * @param[in] strHostString optional hostname string
- *
- * @return
- */
- HRESULT IMAP::HrSendGreeting(const std::string &strHostString)
- {
- if (parseBool(lpConfig->GetSetting("server_hostname_greeting")))
- HrResponse(RESP_UNTAGGED, "OK [" + GetCapabilityString(false) + "] IMAP gateway ready" + strHostString);
- else
- HrResponse(RESP_UNTAGGED, "OK [" + GetCapabilityString(false) + "] IMAP gateway ready");
- return hrSuccess;
- }
- /**
- * Send client an error message that the socket will be closed by the server
- *
- * @param[in] strQuitMsg quit message for client
- * @return MAPI error code
- */
- HRESULT IMAP::HrCloseConnection(const std::string &strQuitMsg)
- {
- HrResponse(RESP_UNTAGGED, strQuitMsg);
- return hrSuccess;
- }
- /**
- * Execute the IMAP command from the client
- *
- * @param strInput received input from client
- *
- * @return MAPI error code
- */
- HRESULT IMAP::HrProcessCommand(const std::string &strInput)
- {
- HRESULT hr = hrSuccess;
- vector<string> strvResult;
- std::string strTag;
- std::string strCommand;
- ULONG ulMaxMessageSize = atoui(lpConfig->GetSetting("imap_max_messagesize"));
- static constexpr const struct {
- const char *command;
- int params;
- HRESULT (IMAP::*func)(const string &, const std::vector<std::string> &);
- } cmds[] = {
- {"SELECT", 1, &IMAP::HrCmdSelect<false>},
- {"EXAMINE", 1, &IMAP::HrCmdSelect<true>},
- {"LIST", 2, &IMAP::HrCmdList<false>},
- {"LSUB", 2, &IMAP::HrCmdList<true>},
- {"LOGIN", 2, &IMAP::HrCmdLogin},
- {"CREATE", 1, &IMAP::HrCmdCreate},
- {"DELETE", 1, &IMAP::HrCmdDelete},
- {"SUBSCRIBE", 1, &IMAP::HrCmdSubscribe<true>},
- {"UNSUBSCRIBE", 1, &IMAP::HrCmdSubscribe<false>},
- {"GETQUOTAROOT", 1, &IMAP::HrCmdGetQuotaRoot},
- {"GETQUOTA", 1, &IMAP::HrCmdGetQuota},
- {"SETQUOTA", 2, &IMAP::HrCmdSetQuota},
- {"RENAME", 2, &IMAP::HrCmdRename},
- {"STATUS", 2, &IMAP::HrCmdStatus}
- };
- static constexpr const struct {
- const char *command;
- HRESULT (IMAP::*func)(const std::string &);
- } cmds_zero_args[] = {
- {"CAPABILITY", &IMAP::HrCmdCapability},
- {"NOOP", &IMAP::HrCmdNoop<false>},
- {"LOGOUT", &IMAP::HrCmdLogout},
- {"STARTTLS", &IMAP::HrCmdStarttls},
- {"CHECK", &IMAP::HrCmdNoop<true>},
- {"CLOSE", &IMAP::HrCmdClose},
- {"IDLE", &IMAP::HrCmdIdle},
- {"NAMESPACE", &IMAP::HrCmdNamespace},
- {"STARTTLS", &IMAP::HrCmdStarttls}
- };
- if (lpLogger->Log(EC_LOGLEVEL_DEBUG))
- lpLogger->Log(EC_LOGLEVEL_DEBUG, "< %s", strInput.c_str());
- HrSplitInput(strInput, strvResult);
- if (strvResult.empty())
- return MAPI_E_CALL_FAILED;
- if (strvResult.size() == 1) {
- // must be idle, and command must be done
- // DONE is not really a command, but the end of the IDLE command by the client marker
- strvResult[0] = strToUpper(strvResult[0]);
- if (strvResult[0].compare("DONE") == 0)
- return HrDone(true);
- HrResponse(RESP_UNTAGGED, "BAD Command not recognized");
- return hrSuccess;
- }
- while (hr == hrSuccess && !strvResult.empty() && strvResult.back().size() > 2 && strvResult.back()[0] == '{')
- {
- bool bPlus = false;
- char *lpcres = NULL;
- string inBuffer;
- string strByteTag;
- ULONG ulByteCount;
- strByteTag = strvResult.back().substr(1, strvResult.back().length() -1);
- ulByteCount = strtoul(strByteTag.c_str(), &lpcres, 10);
- if (lpcres == strByteTag.c_str() || (*lpcres != '}' && *lpcres != '+')) {
- // invalid tag received
- lpLogger->Log(EC_LOGLEVEL_ERROR, "Invalid size tag received: %s", strByteTag.c_str());
- return hr;
- }
- bPlus = (*lpcres == '+');
- // no need to output the
- if (!bPlus) {
- try {
- HrResponse(RESP_CONTINUE, "Ready for literal data");
- } catch (const KMAPIError &e) {
- lpLogger->Log(EC_LOGLEVEL_ERROR, "Error sending during continuation");
- return e.code();
- }
- }
- if (ulByteCount > ulMaxMessageSize) {
- lpLogger->Log(EC_LOGLEVEL_WARNING, "Discarding %d bytes of data", ulByteCount);
- hr = lpChannel->HrReadAndDiscardBytes(ulByteCount);
- if (hr != hrSuccess)
- break;
- } else {
- // @todo select for timeout
- hr = lpChannel->HrReadBytes(&inBuffer, ulByteCount);
- if (hr != hrSuccess)
- break;
- // replace size request with literal
- strvResult.back() = inBuffer;
- if (lpLogger->Log(EC_LOGLEVEL_DEBUG))
- lpLogger->Log(EC_LOGLEVEL_DEBUG, "< <%d bytes data> %s", ulByteCount, inBuffer.c_str());
- }
- hr = lpChannel->HrReadLine(&inBuffer);
- if (hr != hrSuccess) {
- if (errno)
- lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to read line: %s", strerror(errno));
- else
- lpLogger->Log(EC_LOGLEVEL_ERROR, "Client disconnected");
- break;
- }
- HrSplitInput(inBuffer, strvResult);
- if (ulByteCount > ulMaxMessageSize) {
- lpLogger->Log(EC_LOGLEVEL_ERROR, "Maximum message size reached (%u), message size is %u bytes", ulMaxMessageSize, ulByteCount);
- HrResponse(RESP_TAGGED_NO, strTag, "[ALERT] Maximum message size reached");
- hr = MAPI_E_NOT_ENOUGH_MEMORY;
- break;
- }
- }
- if (hr != hrSuccess)
- return hr;
- strTag = strvResult.front();
- strvResult.erase(strvResult.begin());
- strCommand = strvResult.front();
- strvResult.erase(strvResult.begin());
- strCommand = strToUpper(strCommand);
- if (isIdle()) {
- HrResponse(RESP_UNTAGGED, "BAD still in idle state");
- HrDone(false); // false for no output
- return hrSuccess;
- }
- // process {} and end of line
- for (const auto &cmd : cmds_zero_args) {
- if (strCommand.compare(cmd.command) != 0)
- continue;
- if (strvResult.size() == 0)
- return (this->*cmd.func)(strTag);
- HrResponse(RESP_TAGGED_BAD, strTag, std::string(cmd.command) +
- " must have 0 arguments");
- return hrSuccess;
- }
- for (const auto &cmd : cmds) {
- if (strCommand.compare(cmd.command) != 0)
- continue;
- if (strvResult.size() == cmd.params)
- return (this->*cmd.func)(strTag, strvResult);
- HrResponse(RESP_TAGGED_BAD, strTag, std::string(cmd.command) +
- " must have " + stringify(cmd.params) + " arguments");
- return hrSuccess;
- }
- if (strCommand.compare("AUTHENTICATE") == 0) {
- if (strvResult.size() == 1)
- return HrCmdAuthenticate(strTag, strvResult[0], string());
- else if (strvResult.size() == 2)
- return HrCmdAuthenticate(strTag, strvResult[0], strvResult[1]);
- HrResponse(RESP_TAGGED_BAD, strTag, "AUTHENTICATE must have 1 or 2 arguments");
- } else if (strCommand.compare("APPEND") == 0) {
- if (strvResult.size() == 2) {
- return HrCmdAppend(strTag, strvResult[0], strvResult[1]);
- } else if (strvResult.size() == 3) {
- if (strvResult[1][0] == '(')
- return HrCmdAppend(strTag, strvResult[0], strvResult[2], strvResult[1]);
- return HrCmdAppend(strTag, strvResult[0], strvResult[2], string(), strvResult[1]);
- } else if (strvResult.size() == 4) {
- // if both flags and time are given, it must be in that order
- return HrCmdAppend(strTag, strvResult[0], strvResult[3], strvResult[1], strvResult[2]);
- }
- HrResponse(RESP_TAGGED_BAD, strTag, "APPEND must have 2, 3 or 4 arguments");
- return hrSuccess;
- } else if (strCommand.compare("EXPUNGE") == 0) {
- if (!strvResult.empty()) {
- HrResponse(RESP_TAGGED_BAD, strTag, "EXPUNGE must have 0 arguments");
- return hrSuccess;
- }
- return HrCmdExpunge(strTag, string());
- } else if (strCommand.compare("SEARCH") == 0) {
- if (strvResult.empty()) {
- HrResponse(RESP_TAGGED_BAD, strTag, "SEARCH must have 1 or more arguments");
- return hrSuccess;
- }
- return HrCmdSearch(strTag, strvResult, false);
- } else if (strCommand.compare("FETCH") == 0) {
- if (strvResult.size() != 2) {
- HrResponse(RESP_TAGGED_BAD, strTag, "FETCH must have 2 arguments");
- return hrSuccess;
- }
- return HrCmdFetch(strTag, strvResult[0], strvResult[1], false);
- } else if (strCommand.compare("STORE") == 0) {
- if (strvResult.size() != 3) {
- HrResponse(RESP_TAGGED_BAD, strTag, "STORE must have 3 arguments");
- return hrSuccess;
- }
- return HrCmdStore(strTag, strvResult[0], strvResult[1], strvResult[2], false);
- } else if (strCommand.compare("COPY") == 0) {
- if (strvResult.size() != 2) {
- HrResponse(RESP_TAGGED_BAD, strTag, "COPY must have 2 arguments");
- return hrSuccess;
- }
- return HrCmdCopy(strTag, strvResult[0], strvResult[1], false);
- } else if (strCommand.compare("UID") == 0) {
- if (strvResult.empty()) {
- HrResponse(RESP_TAGGED_BAD, strTag, "UID must have a command");
- return hrSuccess;
- }
- strCommand = strvResult.front();
- strvResult.erase(strvResult.begin());
- strCommand = strToUpper(strCommand);
- if (strCommand.compare("SEARCH") == 0) {
- if (strvResult.empty()) {
- HrResponse(RESP_TAGGED_BAD, strTag, "UID SEARCH must have 1 or more arguments");
- return hrSuccess;
- }
- return HrCmdSearch(strTag, strvResult, true);
- } else if (strCommand.compare("FETCH") == 0) {
- if (strvResult.size() != 2) {
- HrResponse(RESP_TAGGED_BAD, strTag, "UID FETCH must have 2 arguments");
- return hrSuccess;
- }
- return HrCmdFetch(strTag, strvResult[0], strvResult[1], true);
- } else if (strCommand.compare("STORE") == 0) {
- if (strvResult.size() != 3) {
- HrResponse(RESP_TAGGED_BAD, strTag, "UID STORE must have 3 arguments");
- return hrSuccess;
- }
- return HrCmdStore(strTag, strvResult[0], strvResult[1], strvResult[2], true);
- } else if (strCommand.compare("COPY") == 0) {
- if (strvResult.size() != 2) {
- HrResponse(RESP_TAGGED_BAD, strTag, "UID COPY must have 2 arguments");
- return hrSuccess;
- }
- return HrCmdCopy(strTag, strvResult[0], strvResult[1], true);
- } else if (strCommand.compare("XAOL-MOVE") == 0) {
- if (strvResult.size() != 2) {
- HrResponse(RESP_TAGGED_BAD, strTag, "UID XAOL-MOVE must have 2 arguments");
- return hrSuccess;
- }
- return HrCmdUidXaolMove(strTag, strvResult[0], strvResult[1]);
- } else if (strCommand.compare("EXPUNGE") == 0) {
- if (strvResult.size() != 1) {
- HrResponse(RESP_TAGGED_BAD, strTag, "UID EXPUNGE must have 1 argument");
- return hrSuccess;
- }
- return HrCmdExpunge(strTag, strvResult[0]);
- } else {
- HrResponse(RESP_TAGGED_BAD, strTag, "UID Command not supported");
- }
- } else {
- HrResponse(RESP_TAGGED_BAD, strTag, "Command not supported");
- }
- return hrSuccess;
- }
- /**
- * Continue command, only supported for AUTHENTICATE command
- *
- * @param[in] strInput more input from client
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrProcessContinue(const string &strInput)
- {
- return HrCmdAuthenticate(m_strContinueTag, "PLAIN", strInput);
- }
- /**
- * Returns the CAPABILITY string. In some stages, items can be listed
- * or not. This depends on the command received from the client, and
- * the logged on status of the user. Last state is autodetected in the
- * class.
- *
- * @param[in] bAllFlags Whether all capabilities should be placed in the string, and not only the pre-logon capabilities.
- *
- * @return The capabilities string
- */
- std::string IMAP::GetCapabilityString(bool bAllFlags)
- {
- string strCapabilities;
- const char *idle = lpConfig->GetSetting("imap_capability_idle");
- const char *plain = lpConfig->GetSetting("disable_plaintext_auth");
- // capabilities we always have
- strCapabilities = "CAPABILITY IMAP4rev1 LITERAL+";
- if (lpSession == NULL) {
- // authentication capabilities
- if (!lpChannel->UsingSsl() && lpChannel->sslctx())
- strCapabilities += " STARTTLS";
- if (!lpChannel->UsingSsl() && lpChannel->sslctx() && plain && strcmp(plain, "yes") == 0 && lpChannel->peer_is_local() <= 0)
- strCapabilities += " LOGINDISABLED";
- else
- strCapabilities += " AUTH=PLAIN";
- }
- if (lpSession || bAllFlags) {
- // capabilities after authentication
- strCapabilities += " CHILDREN XAOL-OPTION NAMESPACE QUOTA";
- if (idle && strcmp(idle, "yes") == 0)
- strCapabilities += " IDLE";
- }
- return strCapabilities;
- }
- /**
- * @brief Handles the CAPABILITY command
- *
- * Sends all the gateway capabilities to the client, depending on the
- * state we're in. Authentication capabilities are skipped when a user
- * was already logged in.
- *
- * @param[in] strTag the IMAP tag for this command
- *
- * @return hrSuccess
- */
- HRESULT IMAP::HrCmdCapability(const string &strTag) {
- HrResponse(RESP_UNTAGGED, GetCapabilityString(true));
- HrResponse(RESP_TAGGED_OK, strTag, "CAPABILITY Completed");
- return hrSuccess;
- }
- /**
- * @brief Handles the NOOP command
- *
- * Checks the current selected folder for changes, and possebly sends
- * the EXISTS and/or RECENT values to the client.
- *
- * @param[in] strTag the IMAP tag for this command
- *
- * @return hrSuccess
- */
- HRESULT IMAP::HrCmdNoop(const std::string &strTag, bool check)
- {
- HRESULT hr = hrSuccess;
- if (!strCurrentFolder.empty() || check)
- hr = HrRefreshFolderMails(false, !bCurrentFolderReadOnly, NULL);
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_BAD, strTag, (check ? std::string("CHECK") : std::string("NOOP")) + " completed");
- return hr;
- }
- HrResponse(RESP_TAGGED_OK, strTag, (check ? std::string("CHECK") : std::string("NOOP")) + " completed");
- return hrSuccess;
- }
- template<bool check> HRESULT IMAP::HrCmdNoop(const std::string &strTag)
- {
- return HrCmdNoop(strTag, check);
- }
- /**
- * @brief Handles the LOGOUT command
- *
- * Sends the BYE response to the client. HandleIMAP() will close the
- * connection.
- *
- * @param[in] strTag the IMAP tag for this command
- *
- * @return hrSuccess
- */
- HRESULT IMAP::HrCmdLogout(const string &strTag) {
- HrResponse(RESP_UNTAGGED, "BYE server logging out");
- HrResponse(RESP_TAGGED_OK, strTag, "LOGOUT completed");
- /* Let the gateway quit from the socket read loop. */
- return MAPI_E_END_OF_SESSION;
- }
- /**
- * @brief Handles the STARTTLS command
- *
- * Tries to set the current connection to use SSL encryption.
- *
- * @param[in] strTag the IMAP tag for this command
- *
- * @return hrSuccess
- */
- HRESULT IMAP::HrCmdStarttls(const string &strTag) {
- if (!lpChannel->sslctx())
- HrResponse(RESP_TAGGED_NO, strTag, "STARTTLS error in ssl context");
- if (lpChannel->UsingSsl())
- HrResponse(RESP_TAGGED_NO, strTag, "STARTTLS already using SSL/TLS");
- HrResponse(RESP_TAGGED_OK, strTag, "Begin TLS negotiation now");
- auto hr = lpChannel->HrEnableTLS();
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_BAD, strTag, "[ALERT] Error switching to secure SSL/TLS connection");
- lpLogger->Log(EC_LOGLEVEL_ERROR, "Error switching to SSL in STARTTLS");
- /* Let the gateway quit from the socket read loop. */
- return MAPI_E_END_OF_SESSION;
- }
- if (lpChannel->UsingSsl())
- lpLogger->Log(EC_LOGLEVEL_INFO, "Using SSL now");
- return hrSuccess;
- }
- /**
- * @brief Handles the AUTENTICATE command
- *
- * The authenticate command only implements the PLAIN authentication
- * method, since we require the actual password, and not just a hash
- * value.
- *
- * According to the RFC, the data in strAuthData is UTF-8, but thunderbird still sends iso-8859-X.
- *
- * @param[in] strTag The IMAP tag for this command
- * @param[in] strAuthMethod Must be set to PLAIN
- * @param[in] strAuthData (optional) if empty we use a continuation request, otherwise we can authenticate, base64 encoded string
- *
- * @return MAPI error code
- */
- HRESULT IMAP::HrCmdAuthenticate(const string &strTag, string strAuthMethod, const string &strAuthData)
- {
- vector<string> vAuth;
- const char *plain = lpConfig->GetSetting("disable_plaintext_auth");
- // If plaintext authentication was disabled any authentication attempt must be refused very soon
- if (!lpChannel->UsingSsl() && lpChannel->sslctx() && plain && strcmp(plain, "yes") == 0 && lpChannel->peer_is_local() <= 0) {
- HrResponse(RESP_TAGGED_NO, strTag, "[PRIVACYREQUIRED] Plaintext authentication disallowed on non-secure "
- "(SSL/TLS) connections.");
- lpLogger->Log(EC_LOGLEVEL_ERROR, "Aborted login from %s without username (tried to use disallowed plaintext auth)",
- lpChannel->peer_addr());
- return hrSuccess;
- }
- strAuthMethod = strToUpper(strAuthMethod);
- if (strAuthMethod.compare("PLAIN") != 0) {
- HrResponse(RESP_TAGGED_NO, strTag, "AUTHENTICATE " + strAuthMethod + " method not supported");
- return MAPI_E_NO_SUPPORT;
- }
- if (strAuthData.empty() && !m_bContinue) {
- // request the rest of the authentication data by sending one space in a continuation line
- HrResponse(RESP_CONTINUE, string());
- m_strContinueTag = strTag;
- m_bContinue = true;
- return MAPI_W_PARTIAL_COMPLETION;
- }
- m_bContinue = false;
- vAuth = tokenize(base64_decode(strAuthData), '\0');
- // vAuth[0] is the authorization name (ignored for now, but maybe we can use this for opening other stores?)
- // vAuth[1] is the authentication name
- // vAuth[2] is the password for vAuth[1]
-
- if (vAuth.size() != 3) {
- lpLogger->Log(EC_LOGLEVEL_INFO, "Invalid authentication data received, expected 3 items, have %zu items.", vAuth.size());
- HrResponse(RESP_TAGGED_NO, strTag, "AUTHENTICATE " + strAuthMethod + " incomplete data received");
- return MAPI_E_LOGON_FAILED;
- }
- return HrCmdLogin(strTag, {vAuth[1], vAuth[2]});
- }
- /**
- * @brief Handles the LOGIN command
- *
- * Opens a MAPI session, addressbook and possebly open the public if
- * the username and password are correctly entered.
- *
- * @param[in] strTag IMAP tag
- * @param[in] strUser Username, currently tried as windows-1252 charsets
- * @param[in] strPass Password, currently tried as windows-1252 charsets
- *
- * @return MAPI error code
- */
- HRESULT IMAP::HrCmdLogin(const std::string &strTag,
- const std::vector<std::string> &args)
- {
- HRESULT hr = hrSuccess;
- string strUsername;
- size_t i;
- wstring strwUsername;
- wstring strwPassword;
- unsigned int flags;
- const char *plain = lpConfig->GetSetting("disable_plaintext_auth");
- const std::string &strUser = args[0], &strPass = args[1];
- // strUser isn't sent in imap style utf-7, but \ is escaped, so strip those
- for (i = 0; i < strUser.length(); ++i) {
- if (strUser[i] == '\\' && i+1 < strUser.length() && (strUser[i+1] == '"' || strUser[i+1] == '\\'))
- ++i;
- strUsername += strUser[i];
- }
- // If plaintext authentication was disabled any login attempt must be refused very soon
- if (!lpChannel->UsingSsl() && lpChannel->sslctx() && plain && strcmp(plain, "yes") == 0 && lpChannel->peer_is_local() <= 0) {
- HrResponse(RESP_UNTAGGED, "BAD [ALERT] Plaintext authentication not allowed without SSL/TLS, but your client "
- "did it anyway. If anyone was listening, the password was exposed.");
- HrResponse(RESP_TAGGED_NO, strTag, "[PRIVACYREQUIRED] Plaintext authentication disallowed on non-secure "
- "(SSL/TLS) connections.");
- lpLogger->Log(EC_LOGLEVEL_ERROR, "Aborted login from %s with username \"%s\" (tried to use disallowed plaintext auth)",
- lpChannel->peer_addr(), strUsername.c_str());
- goto exitpm;
- }
- if (lpSession != NULL) {
- lpLogger->Log(EC_LOGLEVEL_INFO, "Ignoring to login TWICE for username \"%s\"", strUsername.c_str());
- HrResponse(RESP_TAGGED_NO, strTag, "LOGIN Can't login twice");
- // hr = MAPI_E_CALL_FAILED;
- goto exitpm;
- }
- hr = TryConvert(strUsername, rawsize(strUsername), "windows-1252", strwUsername);
- if (hr != hrSuccess) {
- lpLogger->Log(EC_LOGLEVEL_ERROR, "Illegal byte sequence in username");
- goto exitpm;
- }
- hr = TryConvert(strPass, rawsize(strPass), "windows-1252", strwPassword);
- if (hr != hrSuccess) {
- lpLogger->Log(EC_LOGLEVEL_ERROR, "Illegal byte sequence in password");
- goto exitpm;
- }
- flags = EC_PROFILE_FLAGS_NO_COMPRESSION;
- if (!parseBool(lpConfig->GetSetting("bypass_auth")))
- flags |= EC_PROFILE_FLAGS_NO_UID_AUTH;
- // do not disable notifications for imap connections, may be idle and sessions on the storage server will disappear.
- hr = HrOpenECSession(&lpSession, "gateway/imap", PROJECT_SVN_REV_STR,
- strwUsername.c_str(), strwPassword.c_str(), m_strPath.c_str(),
- flags, NULL, NULL);
- if (hr != hrSuccess) {
- lpLogger->Log(EC_LOGLEVEL_WARNING, "Failed to login from %s with invalid username \"%s\" or wrong password. Error: 0x%08X",
- lpChannel->peer_addr(), strUsername.c_str(), hr);
- if (hr == MAPI_E_LOGON_FAILED)
- HrResponse(RESP_TAGGED_NO, strTag, "LOGIN wrong username or password");
- else
- HrResponse(RESP_TAGGED_BAD, strTag, "Internal error: OpenECSession failed");
- ++m_ulFailedLogins;
- if (m_ulFailedLogins >= LOGIN_RETRIES)
- // disconnect client
- hr = MAPI_E_END_OF_SESSION;
- goto exitpm;
- }
- hr = HrOpenDefaultStore(lpSession, &lpStore);
- if (hr != hrSuccess) {
- lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to open default store");
- HrResponse(RESP_TAGGED_NO, strTag, "LOGIN can't open default store");
- goto exitpm;
- }
- hr = lpSession->OpenAddressBook(0, NULL, AB_NO_DIALOG, &lpAddrBook);
- if (hr != hrSuccess) {
- lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to open addressbook");
- HrResponse(RESP_TAGGED_NO, strTag, "LOGIN can't open addressbook");
- goto exitpm;
- }
- // check if imap access is disabled
- if (isFeatureDisabled("imap", lpAddrBook, lpStore)) {
- lpLogger->Log(EC_LOGLEVEL_ERROR, "IMAP not enabled for user '%s'", strUsername.c_str());
- HrResponse(RESP_TAGGED_NO, strTag, "LOGIN imap feature disabled");
- hr = MAPI_E_LOGON_FAILED;
- goto exitpm;
- }
- m_strwUsername = strwUsername;
- {
- PROPMAP_START(1)
- PROPMAP_NAMED_ID(ENVELOPE, PT_STRING8, PS_EC_IMAP, dispidIMAPEnvelope);
- PROPMAP_INIT(lpStore);
- hr = MAPIAllocateBuffer(CbNewSPropTagArray(4), &~m_lpsIMAPTags);
- if (hr != hrSuccess)
- goto exitpm;
- m_lpsIMAPTags->aulPropTag[0] = PROP_ENVELOPE;
- }
- hr = HrGetSubscribedList();
- // ignore error, empty list of subscribed folder
- if(bShowPublicFolder){
- hr = HrOpenECPublicStore(lpSession, &lpPublicStore);
- if (hr != hrSuccess) {
- lpLogger->Log(EC_LOGLEVEL_WARNING, "Failed to open public store");
- lpPublicStore = NULL;
- }
- }
- hr = HrMakeSpecialsList();
- if (hr != hrSuccess) {
- lpLogger->Log(EC_LOGLEVEL_WARNING, "Failed to find special folder properties");
- HrResponse(RESP_TAGGED_NO, strTag, "LOGIN can't find special folder properties");
- goto exitpm;
- }
- lpLogger->Log(EC_LOGLEVEL_NOTICE, "IMAP Login from %s for user %s", lpChannel->peer_addr(), strUsername.c_str());
- HrResponse(RESP_TAGGED_OK, strTag, "[" + GetCapabilityString(false) + "] LOGIN completed");
- exitpm:
- if (hr != hrSuccess)
- CleanupObject();
- return hr;
- }
- /**
- * @brief Handles the SELECT and EXAMINE commands
- *
- * Make the strFolder the current working folder.
- *
- * @param[in] strTag IMAP command tag
- * @param[in] strFolder IMAP folder name in UTF-7 something charset
- * @param[in] bReadOnly The EXAMINE command was given instead of the SELECT command
- */
- HRESULT IMAP::HrCmdSelect(const std::string &strTag,
- const std::vector<std::string> &args, bool bReadOnly)
- {
- HRESULT hr = hrSuccess;
- char szResponse[IMAP_RESP_MAX + 1];
- unsigned int ulUnseen = 0;
- string command = "SELECT";
- ULONG ulUIDValidity = 1;
- const std::string &strFolder = args[0];
- if (bReadOnly)
- command = "EXAMINE";
-
- if (!lpSession) {
- HrResponse(RESP_TAGGED_NO, strTag, command + " error no session");
- return MAPI_E_CALL_FAILED;
- }
- // close old contents table if cached version was open
- ReleaseContentsCache();
- // Apple mail client does this request, so we need to block it.
- if (strFolder.empty()) {
- HrResponse(RESP_TAGGED_NO, strTag, command + " invalid folder name");
- return MAPI_E_CALL_FAILED;
- }
- hr = IMAP2MAPICharset(strFolder, strCurrentFolder);
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, command + " invalid folder name");
- return hr;
- }
- bCurrentFolderReadOnly = bReadOnly;
- hr = HrRefreshFolderMails(true, !bCurrentFolderReadOnly, &ulUnseen, &ulUIDValidity);
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, command + " error getting mails in folder");
- return hr;
- }
- // \Seen = PR_MESSAGE_FLAGS MSGFLAG_READ
- // \Answered = PR_MSG_STATUS MSGSTATUS_ANSWERED //PR_LAST_VERB_EXECUTED EXCHIVERB_REPLYTOSENDER EXCHIVERB_REPLYTOALL
- // \Draft = PR_MSG_STATUS MSGSTATUS_DRAFT //PR_MESSAGE_FLAGS MSGFLAG_UNSENT
- // \Flagged = PR_FLAG_STATUS
- // \Deleted = PR_MSG_STATUS MSGSTATUS_DELMARKED
- // \Recent = ??? (arrived after last command/login)
- // $Forwarded = PR_LAST_VERB_EXECUTED: NOTEIVERB_FORWARD
- HrResponse(RESP_UNTAGGED, "FLAGS (\\Seen \\Draft \\Deleted \\Flagged \\Answered $Forwarded)");
- HrResponse(RESP_UNTAGGED, "OK [PERMANENTFLAGS (\\Seen \\Draft \\Deleted \\Flagged \\Answered $Forwarded)] Permanent flags");
- snprintf(szResponse, IMAP_RESP_MAX, "OK [UIDNEXT %u] Predicted next UID", m_ulLastUid + 1);
- HrResponse(RESP_UNTAGGED, szResponse);
- if(ulUnseen) {
- snprintf(szResponse, IMAP_RESP_MAX, "OK [UNSEEN %u] First unseen message", ulUnseen);
- HrResponse(RESP_UNTAGGED, szResponse);
- }
- snprintf(szResponse, IMAP_RESP_MAX, "OK [UIDVALIDITY %u] UIDVALIDITY value", ulUIDValidity);
- HrResponse(RESP_UNTAGGED, szResponse);
- if (bReadOnly)
- HrResponse(RESP_TAGGED_OK, strTag, "[READ-ONLY] EXAMINE completed");
- else
- HrResponse(RESP_TAGGED_OK, strTag, "[READ-WRITE] SELECT completed");
- return hrSuccess;
- }
- template<bool read_only>
- HRESULT IMAP::HrCmdSelect(const std::string &strTag,
- const std::vector<std::string> &args)
- {
- return HrCmdSelect(strTag, args, read_only);
- }
- /**
- * @brief Handles the CREATE command
- *
- * Recursively create a new folders, starting from the root folder.
- *
- * @param[in] strTag the IMAP tag for this command
- * @param[in] strFolderParam The foldername, encoded in IMAP UTF-7 charset
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrCmdCreate(const std::string &strTag,
- const std::vector<std::string> &args)
- {
- HRESULT hr = hrSuccess;
- object_ptr<IMAPIFolder> lpFolder, lpSubFolder;
- vector<wstring> strPaths;
- wstring strFolder;
- wstring strPath;
- SPropValue sFolderClass;
- const std::string &strFolderParam = args[0];
- if (!lpSession) {
- HrResponse(RESP_TAGGED_NO, strTag, "CREATE error no session");
- hr = MAPI_E_CALL_FAILED;
- goto exit;
- }
- if (strFolderParam.empty()) {
- HrResponse(RESP_TAGGED_NO, strTag, "CREATE error no folder");
- hr = MAPI_E_CALL_FAILED;
- goto exit;
- }
- hr = IMAP2MAPICharset(strFolderParam, strFolder);
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, "CREATE invalid folder name");
- goto exit;
- }
- if (strFolder[0] == IMAP_HIERARCHY_DELIMITER) {
- // courier and dovecot also block this
- HrResponse(RESP_TAGGED_NO, strTag, "CREATE invalid folder name");
- goto exit;
- }
- hr = HrFindFolderPartial(strFolder, &~lpFolder, &strPath);
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, "CREATE error opening destination folder");
- goto exit;
- }
- if (strPath.empty()) {
- HrResponse(RESP_TAGGED_NO, strTag, "CREATE folder already exists");
- goto exit;
- }
- strPaths = tokenize(strPath, IMAP_HIERARCHY_DELIMITER);
- for (const auto &path : strPaths) {
- hr = lpFolder->CreateFolder(FOLDER_GENERIC, const_cast<TCHAR *>(path.c_str()), nullptr, nullptr, MAPI_UNICODE, &~lpSubFolder);
- if (hr != hrSuccess) {
- if (hr == MAPI_E_COLLISION)
- HrResponse(RESP_TAGGED_NO, strTag, "CREATE folder already exists");
- else
- HrResponse(RESP_TAGGED_NO, strTag, "CREATE can't create folder");
- goto exit;
- }
- sFolderClass.ulPropTag = PR_CONTAINER_CLASS_A;
- sFolderClass.Value.lpszA = const_cast<char *>("IPF.Note");
- hr = HrSetOneProp(lpSubFolder, &sFolderClass);
- if (hr != hrSuccess)
- goto exit;
- lpFolder = std::move(lpSubFolder);
- }
- HrResponse(RESP_TAGGED_OK, strTag, "CREATE completed");
- exit:
- cached_folders.clear();
- return hr;
- }
- /**
- * @brief Handles the DELETE command
- *
- * Deletes folders, starting from the root folder. Special MAPI
- * folders may not be deleted. The folder will be removed from the
- * subscribed list, if it was subscribed.
- *
- * @test write test which checks if delete removes folder from subscribed list
- *
- * @param[in] strTag the IMAP tag for this command
- * @param[in] strFolderParam The foldername, encoded in IMAP UTF-7 charset
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrCmdDelete(const std::string &strTag,
- const std::vector<std::string> &args)
- {
- HRESULT hr = hrSuccess;
- object_ptr<IMAPIFolder> lpParentFolder;
- ULONG cbEntryID;
- memory_ptr<ENTRYID> lpEntryID;
- wstring strFolder;
- const std::string &strFolderParam = args[0];
- if (!lpSession) {
- HrResponse(RESP_TAGGED_NO, strTag, "DELETE error no session");
- hr = MAPI_E_CALL_FAILED;
- goto exit;
- }
- hr = IMAP2MAPICharset(strFolderParam, strFolder);
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, "DELETE invalid folder name");
- goto exit;
- }
- strFolder = strToUpper(strFolder);
- if (strFolder.compare(L"INBOX") == 0) {
- HrResponse(RESP_TAGGED_NO, strTag, "DELETE error deleting INBOX is not allowed");
- hr = MAPI_E_CALL_FAILED;
- goto exit;
- }
- hr = HrFindFolderEntryID(strFolder, &cbEntryID, &~lpEntryID);
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, "DELETE error folder not found");
- goto exit;
- }
- if (IsSpecialFolder(cbEntryID, lpEntryID)) {
- HrResponse(RESP_TAGGED_NO, strTag, "DELETE special folder may not be deleted");
- goto exit;
- }
- hr = HrOpenParentFolder(cbEntryID, lpEntryID, &~lpParentFolder);
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, "DELETE error opening parent folder");
- goto exit;
- }
- hr = lpParentFolder->DeleteFolder(cbEntryID, lpEntryID, 0, NULL, DEL_FOLDERS | DEL_MESSAGES);
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, "DELETE error deleting folder");
- goto exit;
- }
- // remove from subscribed list
- hr = ChangeSubscribeList(false, cbEntryID, lpEntryID);
- if (hr != hrSuccess) {
- lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to update subscribed list for deleted folder '%ls'", strFolder.c_str());
- hr = hrSuccess;
- }
- // close folder if it was selected
- if (strCurrentFolder == strFolder) {
- strCurrentFolder.clear();
- // close old contents table if cached version was open
- ReleaseContentsCache();
- }
- HrResponse(RESP_TAGGED_OK, strTag, "DELETE completed");
- exit:
- cached_folders.clear();
- return hr;
- }
- /**
- * @brief Handles the RENAME command
- *
- * Renames or moves a folder. A folder cannot be moved under itself. A
- * folder cannot be renamed to another existing folder.
- *
- * @param[in] strTag the IMAP tag for this command
- * @param[in] strExistingFolderParam the folder to rename or move, in IMAP UTF-7 charset
- * @param[in] strNewFolderParam the new folder name, in IMAP UTF-7 charset
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrCmdRename(const std::string &strTag,
- const std::vector<std::string> &args)
- {
- HRESULT hr = hrSuccess;
- memory_ptr<SPropValue> lppvFromEntryID, lppvDestEntryID;
- object_ptr<IMAPIFolder> lpParentFolder, lpMakeFolder, lpSubFolder;
- ULONG ulObjType = 0;
- string::size_type deliPos;
- ULONG cbMovFolder = 0;
- memory_ptr<ENTRYID> lpMovFolder;
- wstring strExistingFolder;
- wstring strNewFolder;
- wstring strPath;
- wstring strFolder;
- SPropValue sFolderClass;
- const std::string &strExistingFolderParam = args[0];
- const std::string &strNewFolderParam = args[1];
- if (!lpSession) {
- HrResponse(RESP_TAGGED_NO, strTag, "RENAME error no session");
- hr = MAPI_E_CALL_FAILED;
- goto exit;
- }
- hr = IMAP2MAPICharset(strExistingFolderParam, strExistingFolder);
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, "RENAME invalid folder name");
- goto exit;
- }
- hr = IMAP2MAPICharset(strNewFolderParam, strNewFolder);
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, "RENAME invalid folder name");
- goto exit;
- }
- strExistingFolder = strToUpper(strExistingFolder);
- if (strExistingFolder.compare(L"INBOX") == 0) {
- // FIXME, rfc text:
- //
- // Renaming INBOX is permitted, and has special behavior. It moves
- // all messages in INBOX to a new mailbox with the given name,
- // leaving INBOX empty. If the server implementation supports
- // inferior hierarchical names of INBOX, these are unaffected by a
- // rename of INBOX.
- hr = MAPI_E_CALL_FAILED;
- goto exit;
- }
- hr = HrFindFolderEntryID(strExistingFolder, &cbMovFolder, &~lpMovFolder);
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, "RENAME error source folder not found");
- goto exit;
- }
- if (IsSpecialFolder(cbMovFolder, lpMovFolder)) {
- HrResponse(RESP_TAGGED_NO, strTag, "RENAME special folder may not be moved or renamed");
- goto exit;
- }
- hr = HrOpenParentFolder(cbMovFolder, lpMovFolder, &~lpParentFolder);
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, "RENAME error opening parent folder");
- goto exit;
- }
- // Find the folder as far as we can
- hr = HrFindFolderPartial(strNewFolder, &~lpMakeFolder, &strPath);
- if(hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, "RENAME error opening destination folder");
- goto exit;
- }
- if (strPath.empty()) {
- HrResponse(RESP_TAGGED_NO, strTag, "RENAME destination already exists");
- goto exit;
- }
-
- // strPath now contains subfolder we want to create (eg sub/new). So now we have to
- // mkdir -p all the folder leading up to the last (if any)
- do {
- deliPos = strPath.find(IMAP_HIERARCHY_DELIMITER);
- if (deliPos == string::npos) {
- strFolder = strPath;
- continue;
- }
- strFolder = strPath.substr(0, deliPos);
- strPath = strPath.substr(deliPos + 1);
- if (!strFolder.empty())
- hr = lpMakeFolder->CreateFolder(FOLDER_GENERIC, (TCHAR *)strFolder.c_str(), nullptr, nullptr, MAPI_UNICODE | OPEN_IF_EXISTS, &~lpSubFolder);
- if (hr != hrSuccess || lpSubFolder == NULL) {
- HrResponse(RESP_TAGGED_NO, strTag, "RENAME error creating folder");
- goto exit;
- }
- sFolderClass.ulPropTag = PR_CONTAINER_CLASS_A;
- sFolderClass.Value.lpszA = const_cast<char *>("IPF.Note");
- hr = HrSetOneProp(lpSubFolder, &sFolderClass);
- if (hr != hrSuccess)
- goto exit;
- lpMakeFolder = std::move(lpSubFolder);
- } while (deliPos != string::npos);
- if (HrGetOneProp(lpParentFolder, PR_ENTRYID, &~lppvFromEntryID) != hrSuccess ||
- HrGetOneProp(lpMakeFolder, PR_ENTRYID, &~lppvDestEntryID) != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, "RENAME error opening source or destination");
- goto exit;
- }
- // When moving in the same folder, just rename
- if (lppvFromEntryID->Value.bin.cb != lppvDestEntryID->Value.bin.cb || memcmp(lppvFromEntryID->Value.bin.lpb, lppvDestEntryID->Value.bin.lpb, lppvDestEntryID->Value.bin.cb) != 0) {
- // Do the real move
- hr = lpParentFolder->CopyFolder(cbMovFolder, lpMovFolder, &IID_IMAPIFolder, lpMakeFolder,
- (TCHAR *) strFolder.c_str(), 0, NULL, MAPI_UNICODE | FOLDER_MOVE);
- } else {
- // from is same as dest folder, use SetProps(PR_DISPLAY_NAME)
- SPropValue propName;
- propName.ulPropTag = PR_DISPLAY_NAME_W;
- propName.Value.lpszW = (WCHAR*)strFolder.c_str();
- hr = lpSession->OpenEntry(cbMovFolder, lpMovFolder, &IID_IMAPIFolder, MAPI_MODIFY,
- &ulObjType, &~lpSubFolder);
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, "RENAME error opening folder");
- goto exit;
- }
- hr = lpSubFolder->SetProps(1, &propName, NULL);
- }
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, "RENAME error moving folder");
- goto exit;
- }
- HrResponse(RESP_TAGGED_OK, strTag, "RENAME completed");
- exit:
- cached_folders.clear();
- return hr;
- }
- /**
- * @brief Handles the SUBSCRIBE and UNSUBSCRIBE commands
- *
- * Subscribe or unsubscribe the given folder. Special IMAP folders
- * cannot be unsubscribed. Subscribed folders are listed with the LSUB
- * command.
- *
- * @note we differ here from other IMAP server, who do allow
- * unsubscribing the INBOX (which is normally the only special IMAP
- * folder)
- *
- * @param[in] strTag the IMAP tag for this command
- * @param[in] strFolderParam The folder to (un)subscribe, in IMAP UTF-7 charset
- * @param[in] bSubscribe subscribe (true) or unsubscribe (false)
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrCmdSubscribe(const std::string &strTag,
- const std::vector<std::string> &args, bool bSubscribe)
- {
- HRESULT hr = hrSuccess;
- string strAction;
- ULONG cbEntryID = 0;
- memory_ptr<ENTRYID> lpEntryID;
- wstring strFolder;
- const std::string &strFolderParam = args[0];
- if (bSubscribe)
- strAction = "SUBSCRIBE";
- else
- strAction = "UNSUBSCRIBE";
- if (!lpSession) {
- HrResponse(RESP_TAGGED_NO, strTag, strAction + " error no session");
- return MAPI_E_CALL_FAILED;
- }
- hr = IMAP2MAPICharset(strFolderParam, strFolder);
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, strAction + " invalid folder name");
- return hr;
- }
- hr = HrFindFolderEntryID(strFolder, &cbEntryID, &~lpEntryID);
- if (hr != hrSuccess) {
- // folder not found, but not error, so thunderbird updates view correctly.
- HrResponse(RESP_TAGGED_OK, strTag, strAction + " folder not found");
- return hr;
- }
- if (IsSpecialFolder(cbEntryID, lpEntryID)) {
- if (!bSubscribe)
- HrResponse(RESP_TAGGED_NO, strTag, strAction + " cannot unsubscribe this special folder");
- else
- HrResponse(RESP_TAGGED_OK, strTag, strAction + " completed");
- return hrSuccess;
- }
- hr = ChangeSubscribeList(bSubscribe, cbEntryID, lpEntryID);
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, strAction + " writing subscriptions to server failed");
- return hr;
- }
- HrResponse(RESP_TAGGED_OK, strTag, strAction + " completed");
- return hrSuccess;
- }
- template<bool subscribe>
- HRESULT IMAP::HrCmdSubscribe(const std::string &tag,
- const std::vector<std::string> &args)
- {
- return HrCmdSubscribe(tag, args, subscribe);
- }
- /**
- * @brief Handles the LIST and LSUB commands
- *
- * Lists all or subscribed folders, with wildcard filtering. Supported
- * folder flags are \Noselect and \Has(No)Children.
- * @todo I don't think we check the \Noselect in the SELECT/EXAMINE command
- * @test with strFindFolder in UTF-7 with wildcards
- *
- * @param[in] strTag the IMAP tag for this command
- * @param[in] strReferenceFolder list folders below this folder, in IMAP UTF-7 charset
- * @param[in] strFindFolder list folders with names matching this name with/or wildcards (* and %) , in IMAP UTF-7 charset
- * @param[in] bSubscribedOnly Behave for the LIST command (false) or LSUB command (true)
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrCmdList(const std::string &strTag,
- const std::vector<std::string> &args, bool bSubscribedOnly)
- {
- HRESULT hr = hrSuccess;
- string strAction;
- string strResponse;
- wstring strPattern;
- string strListProps;
- string strCompare;
- wstring strFolderPath;
- std::string strReferenceFolder = args[0], strFindFolder = args[1];
- if (bSubscribedOnly)
- strAction = "LSUB";
- else
- strAction = "LIST";
- if (!lpSession) {
- HrResponse(RESP_TAGGED_NO, strTag, strAction + " error no session");
- return MAPI_E_CALL_FAILED;
- }
- if (strFindFolder.empty()) {
- strResponse = strAction + " (\\Noselect) \"";
- strResponse += IMAP_HIERARCHY_DELIMITER;
- strResponse += "\" \"\"";
- HrResponse(RESP_UNTAGGED, strResponse);
- HrResponse(RESP_TAGGED_OK, strTag, strAction + " completed");
- return hrSuccess;
- }
- HrGetSubscribedList();
- // ignore error
-
- // add trailing delimiter to strReferenceFolder
- if (!strReferenceFolder.empty() &&
- strReferenceFolder[strReferenceFolder.length()-1] != IMAP_HIERARCHY_DELIMITER)
- strReferenceFolder += IMAP_HIERARCHY_DELIMITER;
- strReferenceFolder += strFindFolder;
- hr = IMAP2MAPICharset(strReferenceFolder, strPattern);
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, strAction + " invalid folder name");
- return hr;
- }
- strPattern = strToUpper(strPattern);
- list<SFolder> *folders = &cached_folders;
- list<SFolder> tmp_folders;
- if (cache_folders_time_limit > 0) {
- hr = HrGetFolderList(cached_folders);
- cache_folders_last_used = std::time(nullptr);
- }
- else {
- hr = HrGetFolderList(tmp_folders);
- folders = &tmp_folders;
- }
- // Get all folders
- if(hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, strAction + " unable to list folders");
- return hr;
- }
- // Loop through all folders to see if they match
- for (auto iFld = folders->cbegin(); iFld != folders->cend(); ++iFld) {
- if (bSubscribedOnly && !iFld->bActive && !iFld->bSpecialFolder)
- // Folder is not subscribed to
- continue;
- // Get full path name
- strFolderPath.clear();
- // if path is empty, we're probably dealing the IPM_SUBTREE entry
- if(HrGetFolderPath(iFld, *folders, strFolderPath) != hrSuccess || strFolderPath.empty())
- continue;
-
- if (!strFolderPath.empty())
- strFolderPath.erase(0,1); // strip / from start of string
- if (MatchFolderPath(strFolderPath, strPattern)) {
- hr = MAPI2IMAPCharset(strFolderPath, strResponse);
- if (hr != hrSuccess) {
- lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to represent foldername '%ls' in UTF-7", strFolderPath.c_str());
- continue;
- }
- strResponse = (string)"\"" + IMAP_HIERARCHY_DELIMITER + "\" \"" + strResponse + "\""; // prepend folder delimiter
- strListProps = strAction + " (";
- if (!iFld->bMailFolder)
- strListProps += "\\Noselect ";
- if (!bSubscribedOnly) {
- // don't list flag on LSUB command
- if (iFld->bHasSubfolders)
- strListProps += "\\HasChildren";
- else
- strListProps += "\\HasNoChildren";
- }
- strListProps += ") ";
- strResponse = strListProps + strResponse;
- HrResponse(RESP_UNTAGGED, strResponse);
- }
- }
- HrResponse(RESP_TAGGED_OK, strTag, strAction + " completed");
- return hrSuccess;
- }
- template<bool sub_only> HRESULT
- IMAP::HrCmdList(const std::string &tag, const std::vector<std::string> &args)
- {
- return HrCmdList(tag, args, sub_only);
- }
- HRESULT IMAP::get_uid_next(KFolder &&folder, const std::string &tag, ULONG &uid_next)
- {
- HRESULT hr = hrSuccess;
- try {
- auto table = folder.get_contents_table(MAPI_DEFERRED_ERRORS);
- table.columns({PR_EC_IMAP_ID});
- table.sort({{PR_EC_IMAP_ID, KTable::DESCEND}});
- auto rows = table.rows(1, 0);
- uid_next = rows.count() > 0 ? (rows[0][0].ul() + 1) : 1;
- }
- catch (const KMAPIError &e) {
- hr = e.code();
- HrResponse(RESP_TAGGED_NO, tag, "STATUS error getting contents");
- }
- return hr;
- }
- HRESULT IMAP::get_recent(KFolder &&folder, const std::string &tag, ULONG &recent, const ULONG &messages) {
- HRESULT hr = hrSuccess;
- try {
- KProp max_id = nullptr;
- try {
- max_id = folder.get_prop(PR_EC_IMAP_MAX_ID);
- }
- catch (const KMAPIError &e) {
- if (e.code() != MAPI_E_NOT_FOUND)
- throw;
- }
- if (max_id != nullptr) {
- auto table = folder.get_contents_table(MAPI_DEFERRED_ERRORS);
- auto restr = ECPropertyRestriction(RELOP_GT, PR_EC_IMAP_ID, max_id, ECRestriction::Cheap);
- hr = restr.RestrictTable(table, TBL_BATCH);
- recent = table.count();
- }
- else
- recent = messages;
- }
- catch (const KMAPIError &e) {
- hr = e.code();
- HrResponse(RESP_TAGGED_NO, tag, "STATUS error getting contents");
- }
- return hr;
- }
- /**
- * @brief Handles STATUS command
- *
- * Returns values for status items. Status items are:
- * @li \b MESSAGES The number of messages in the mailbox.
- * @li \b RECENT The number of messages with the \Recent flag set.
- * @li \b UIDNEXT The next unique identifier value of the mailbox.
- * @li \b UIDVALIDITY The unique identifier validity value of the mailbox.
- * @li \b UNSEEN The number of messages which do not have the \Seen flag set.
- *
- * @note dovecot seems to be the only one enforcing strStatusData to be a list, and with actual items
- * @note courier doesn't return NIL for unknown status items
- *
- * @param[in] strTag the IMAP tag for this command
- * @param[in] strFolder The folder to query the status of, in IMAP UTF-7 charset
- * @param[in] strStatusData A list of status items to query
- *
- * @return MAPI error code
- */
- HRESULT IMAP::HrCmdStatus(const std::string &strTag,
- const std::vector<std::string> &args)
- {
- HRESULT hr = hrSuccess;
- KFolder lpStatusFolder;
- vector<string> lstStatusData;
- string strData;
- string strResponse;
- char szBuffer[11];
- ULONG ulCounter;
- ULONG ulMessages = 0;
- ULONG ulUnseen = 0;
- ULONG ulUIDValidity = 0;
- ULONG ulUIDNext = 0;
- ULONG ulRecent = 0;
- ULONG cStatusData = 0;
- ULONG cValues;
- static constexpr const SizedSPropTagArray(3, sPropsFolderCounters) =
- {3, {PR_CONTENT_COUNT, PR_CONTENT_UNREAD, PR_EC_HIERARCHYID}};
- memory_ptr<SPropValue> lpPropCounters, lpPropMaxID;
- wstring strIMAPFolder;
- std::string strFolder = args[0], strStatusData = args[1];
-
- if (!lpSession) {
- HrResponse(RESP_TAGGED_NO, strTag, "STATUS error no session");
- return MAPI_E_CALL_FAILED;
- }
- hr = IMAP2MAPICharset(strFolder, strIMAPFolder);
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, "STATUS invalid folder name");
- return hr;
- }
- strStatusData = strToUpper(strStatusData);
- strIMAPFolder = strToUpper(strIMAPFolder);
- object_ptr<IMAPIFolder> tmp_folder;
- hr = HrFindFolder(strIMAPFolder, false, &~tmp_folder);
- if(hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, "STATUS error finding folder");
- return MAPI_E_CALL_FAILED;
- }
- lpStatusFolder = tmp_folder.release();
- if (!IsMailFolder(lpStatusFolder)) {
- HrResponse(RESP_TAGGED_NO, strTag, "STATUS error no mail folder");
- return MAPI_E_CALL_FAILED;
- }
- hr = lpStatusFolder->GetProps(sPropsFolderCounters, 0, &cValues, &~lpPropCounters);
- if (FAILED(hr)) {
- HrResponse(RESP_TAGGED_NO, strTag, "STATUS error fetching folder content counters");
- return hr;
- }
- hr = hrSuccess;
- if (lpPropCounters[0].ulPropTag == PR_CONTENT_COUNT)
- ulMessages = lpPropCounters[0].Value.ul;
- if (lpPropCounters[1].ulPropTag == PR_CONTENT_UNREAD)
- ulUnseen = lpPropCounters[1].Value.ul;
- if (lpPropCounters[2].ulPropTag == PR_EC_HIERARCHYID)
- ulUIDValidity = lpPropCounters[2].Value.ul;
- // split statusdata
- if (strStatusData.size() > 1 && strStatusData[0] == '(') {
- strStatusData.erase(0,1);
- strStatusData.resize(strStatusData.size()-1);
- }
- HrSplitInput(strStatusData, lstStatusData);
- // loop statusdata
- cStatusData = lstStatusData.size();
- strResponse = "STATUS \"";
- strResponse += strFolder; // strFolder is in upper case, works with all clients?
- strResponse += "\" (";
- for (ulCounter = 0; ulCounter < cStatusData; ++ulCounter) {
- strData = lstStatusData[ulCounter];
- strResponse += strData;
- strResponse += " ";
- if (strData.compare("MESSAGES") == 0) {
- snprintf(szBuffer, 10, "%u", ulMessages);
- strResponse += szBuffer;
- } else if (strData.compare("RECENT") == 0) {
- get_recent(std::move(lpStatusFolder), strTag, ulRecent, ulMessages);
- snprintf(szBuffer, 10, "%u", ulRecent);
- strResponse += szBuffer;
- } else if (strData.compare("UIDNEXT") == 0) {
- get_uid_next(std::move(lpStatusFolder), strTag, ulUIDNext);
- snprintf(szBuffer, 10, "%u", ulUIDNext);
- strResponse += szBuffer;
- } else if (strData.compare("UIDVALIDITY") == 0) {
- snprintf(szBuffer, 10, "%u", ulUIDValidity);
- strResponse += szBuffer;
- } else if (strData.compare("UNSEEN") == 0) {
- snprintf(szBuffer, 10, "%u", ulUnseen);
- strResponse += szBuffer;
- } else {
- strResponse += "NIL";
- }
-
- if (ulCounter+1 < cStatusData)
- strResponse += " ";
- }
- strResponse += ")";
- HrResponse(RESP_UNTAGGED, strResponse);
- HrResponse(RESP_TAGGED_OK, strTag, "STATUS completed");
- return hrSuccess;
- }
- /**
- * @brief Handles the APPEND command
- *
- * Create a new mail message in the given folder, and possebly set
- * flags and received time on the new message.
- *
- * @param[in] strTag the IMAP tag for this command
- * @param[in] strFolderParam the folder to create the message in, in IMAP UTF-7 charset
- * @param[in] strData the RFC 2822 formatted email to save
- * @param[in] strFlags optional, contains a list of extra flags to save on the message (eg. \Seen)
- * @param[in] strTime optional, a timestamp for the message: internal date is received date
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrCmdAppend(const string &strTag, const string &strFolderParam, const string &strData, string strFlags, const string &strTime) {
- HRESULT hr = hrSuccess;
- object_ptr<IMAPIFolder> lpAppendFolder;
- object_ptr<IMessage> lpMessage;
- vector<string> lstFlags;
- ULONG ulCounter;
- string strFlag;
- memory_ptr<SPropValue> lpPropVal;
- wstring strFolder;
- string strAppendUid;
- ULONG ulFolderUid = 0;
- ULONG ulMsgUid = 0;
- SizedSPropTagArray(10, delFrom) = { 10, {
- PR_SENT_REPRESENTING_ADDRTYPE_W, PR_SENT_REPRESENTING_NAME_W,
- PR_SENT_REPRESENTING_EMAIL_ADDRESS_W, PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_SEARCH_KEY,
- PR_SENDER_ADDRTYPE_W, PR_SENDER_NAME_W,
- PR_SENDER_EMAIL_ADDRESS_W, PR_SENDER_ENTRYID, PR_SENDER_SEARCH_KEY
- } };
-
- if (!lpSession) {
- HrResponse(RESP_TAGGED_NO, strTag, "APPEND error no session");
- return MAPI_E_CALL_FAILED;
- }
- hr = IMAP2MAPICharset(strFolderParam, strFolder);
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, "APPEND invalid folder name");
- return hr;
- }
- hr = HrFindFolder(strFolder, false, &~lpAppendFolder);
- if(hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, "[TRYCREATE] APPEND error finding folder");
- return MAPI_E_CALL_FAILED;
- }
- if (!IsMailFolder(lpAppendFolder)) {
- HrResponse(RESP_TAGGED_NO, strTag, "APPEND error not a mail folder");
- return MAPI_E_CALL_FAILED;
- }
- hr = HrGetOneProp(lpAppendFolder, PR_EC_HIERARCHYID, &~lpPropVal);
- if (hr == hrSuccess)
- ulFolderUid = lpPropVal->Value.ul;
- hr = lpAppendFolder->CreateMessage(nullptr, 0, &~lpMessage);
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, "APPEND error creating message");
- return hr;
- }
- hr = IMToMAPI(lpSession, lpStore, lpAddrBook, lpMessage, strData, dopt);
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, "APPEND error converting message");
- return hr;
- }
- if (IsSentItemFolder(lpAppendFolder) &&
- HrGetOneProp(lpAppendFolder, PR_ENTRYID, &~lpPropVal) == hrSuccess) {
- // needed for blackberry
- lpPropVal->ulPropTag = PR_SENTMAIL_ENTRYID;
- HrSetOneProp(lpMessage, lpPropVal);
- }
- if (strFlags.size() > 2 && strFlags[0] == '(') {
- // remove () around flags
- strFlags.erase(0, 1);
- strFlags.erase(strFlags.size()-1, 1);
- }
- strFlags = strToUpper(strFlags);
- HrSplitInput(strFlags, lstFlags);
- for (ulCounter = 0; ulCounter < lstFlags.size(); ++ulCounter) {
- strFlag = lstFlags[ulCounter];
- if (strFlag.compare("\\SEEN") == 0) {
- if (HrGetOneProp(lpMessage, PR_MESSAGE_FLAGS, &~lpPropVal) != hrSuccess) {
- hr = MAPIAllocateBuffer(sizeof(SPropValue), &~lpPropVal);
- if (hr != hrSuccess)
- return hr;
- lpPropVal->ulPropTag = PR_MESSAGE_FLAGS;
- lpPropVal->Value.ul = 0;
- }
- lpPropVal->Value.ul |= MSGFLAG_READ;
- HrSetOneProp(lpMessage, lpPropVal);
- } else if (strFlag.compare("\\DRAFT") == 0) {
- if (HrGetOneProp(lpMessage, PR_MSG_STATUS, &~lpPropVal) != hrSuccess) {
- hr = MAPIAllocateBuffer(sizeof(SPropValue), &~lpPropVal);
- if (hr != hrSuccess)
- return hr;
- lpPropVal->ulPropTag = PR_MSG_STATUS;
- lpPropVal->Value.ul = 0;
- }
- lpPropVal->Value.ul |= MSGSTATUS_DRAFT;
- HrSetOneProp(lpMessage, lpPropVal);
- // When saving a draft, also mark it as UNSENT, so webaccess opens the editor, not the viewer
- if (HrGetOneProp(lpMessage, PR_MESSAGE_FLAGS, &~lpPropVal) != hrSuccess) {
- hr = MAPIAllocateBuffer(sizeof(SPropValue), &~lpPropVal);
- if (hr != hrSuccess)
- return hr;
- lpPropVal->ulPropTag = PR_MESSAGE_FLAGS;
- lpPropVal->Value.ul = 0;
- }
- lpPropVal->Value.ul |= MSGFLAG_UNSENT;
- HrSetOneProp(lpMessage, lpPropVal);
- // remove "from" properties, and ignore error
- lpMessage->DeleteProps(delFrom, NULL);
- } else if (strFlag.compare("\\FLAGGED") == 0) {
- if (lpPropVal == NULL) {
- hr = MAPIAllocateBuffer(sizeof(SPropValue), &~lpPropVal);
- if (hr != hrSuccess)
- return hr;
- }
- lpPropVal->ulPropTag = PR_FLAG_STATUS;
- lpPropVal->Value.ul = 2;
- HrSetOneProp(lpMessage, lpPropVal);
- } else if (strFlag.compare("\\ANSWERED") == 0 || strFlag.compare("$FORWARDED") == 0) {
- if (HrGetOneProp(lpMessage, PR_MSG_STATUS, &~lpPropVal) != hrSuccess) {
- hr = MAPIAllocateBuffer(sizeof(SPropValue), &~lpPropVal);
- if (hr != hrSuccess)
- return hr;
- lpPropVal->ulPropTag = PR_MSG_STATUS;
- lpPropVal->Value.ul = 0;
- }
- if (strFlag[0] == '\\') {
- lpPropVal->Value.ul |= MSGSTATUS_ANSWERED;
- HrSetOneProp(lpMessage, lpPropVal);
- }
- hr = MAPIAllocateBuffer(sizeof(SPropValue) * 3, &~lpPropVal);
- if (hr != hrSuccess)
- return hr;
- lpPropVal[0].ulPropTag = PR_LAST_VERB_EXECUTED;
- if (strFlag[0] == '\\')
- lpPropVal[0].Value.ul = NOTEIVERB_REPLYTOSENDER;
- else
- lpPropVal[0].Value.ul = NOTEIVERB_FORWARD;
- lpPropVal[1].ulPropTag = PR_LAST_VERB_EXECUTION_TIME;
- GetSystemTimeAsFileTime(&lpPropVal[1].Value.ft);
- lpPropVal[2].ulPropTag = PR_ICON_INDEX;
- if (strFlag[0] == '\\')
- lpPropVal[2].Value.ul = ICON_MAIL_REPLIED;
- else
- lpPropVal[2].Value.ul = ICON_MAIL_FORWARDED;
- hr = lpMessage->SetProps(3, lpPropVal, NULL);
- if (hr != hrSuccess)
- return hr;
- } else if (strFlag.compare("\\DELETED") == 0) {
- if (HrGetOneProp(lpMessage, PR_MSG_STATUS, &~lpPropVal) != hrSuccess) {
- hr = MAPIAllocateBuffer(sizeof(SPropValue), &~lpPropVal);
- if (hr != hrSuccess)
- return hr;
- lpPropVal->ulPropTag = PR_MSG_STATUS;
- lpPropVal->Value.ul = 0;
- }
- lpPropVal->Value.ul |= MSGSTATUS_DELMARKED;
- // what, new deleted mail? moved deleted mail? uh?
- // @todo imap_expunge_on_delete
- HrSetOneProp(lpMessage, lpPropVal);
- }
- }
- // set time
- if (lpPropVal == NULL) {
- hr = MAPIAllocateBuffer(sizeof(SPropValue), &~lpPropVal);
- if (hr != hrSuccess)
- return hr;
- }
- if (!strTime.empty()) {
- lpPropVal->Value.ft = StringToFileTime(strTime);
- lpPropVal->ulPropTag = PR_MESSAGE_DELIVERY_TIME;
- HrSetOneProp(lpMessage, lpPropVal);
-
- lpPropVal->Value.ft = StringToFileTime(strTime, true);
- lpPropVal->ulPropTag = PR_EC_MESSAGE_DELIVERY_DATE;
- HrSetOneProp(lpMessage, lpPropVal);
- }
- hr = lpMessage->SaveChanges(KEEP_OPEN_READWRITE | FORCE_SAVE);
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, "APPEND error saving message");
- return hr;
- }
- hr = HrGetOneProp(lpMessage, PR_EC_IMAP_ID, &~lpPropVal);
- if (hr == hrSuccess)
- ulMsgUid = lpPropVal->Value.ul;
- if (ulMsgUid && ulFolderUid)
- strAppendUid = string("[APPENDUID ") + stringify(ulFolderUid) + " " + stringify(ulMsgUid) + "] ";
- if (strCurrentFolder == strFolder)
- // Fixme, add the appended message instead of HrRefreshFolderMails; the message is now seen as Recent
- HrRefreshFolderMails(false, !bCurrentFolderReadOnly, NULL);
- HrResponse(RESP_TAGGED_OK, strTag, strAppendUid + "APPEND completed");
- return hr;
- }
- /**
- * @brief Handles the CLOSE command
- *
- * Closes the current selected mailbox, expunging any \Delete marked
- * messages in this mailbox.
- *
- * @param[in] strTag the IMAP tag for this command
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrCmdClose(const string &strTag) {
- HRESULT hr = hrSuccess;
- if (strCurrentFolder.empty() || !lpSession) {
- hr = MAPI_E_CALL_FAILED;
- HrResponse(RESP_TAGGED_NO, strTag, "CLOSE error no folder");
- goto exit;
- }
- // close old contents table if cached version was open
- ReleaseContentsCache();
- if (bCurrentFolderReadOnly) {
- // cannot expunge messages on a readonly folder
- HrResponse(RESP_TAGGED_OK, strTag, "CLOSE completed");
- goto exit;
- }
- hr = HrExpungeDeleted(strTag, "CLOSE", std::unique_ptr<ECRestriction>());
- if (hr != hrSuccess)
- goto exit;
- HrResponse(RESP_TAGGED_OK, strTag, "CLOSE completed");
- exit:
- strCurrentFolder.clear(); // always "close" the SELECT command
- return hr;
- }
- /**
- * @brief Handles the EXPUNGE command
- *
- * All \Deleted marked emails will actually be removed (softdeleted in
- * Kopano). Optional is the sequence set (UIDPLUS extension), which
- * messages only must be expunged if \Deleted flag was marked AND are
- * present in this sequence.
- *
- * @param[in] strTag the IMAP tag for this command
- * @param[in] strStatusData optional sequence set
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrCmdExpunge(const string &strTag, const string &strSeqSet) {
- HRESULT hr = hrSuccess;
- list<ULONG> lstMails;
- string strCommand;
- std::unique_ptr<ECRestriction> rst;
- static_assert(std::is_polymorphic<ECRestriction>::value, "ECRestriction needs to be polymorphic for unique_ptr to work");
- if (strSeqSet.empty())
- strCommand = "EXPUNGE";
- else
- strCommand = "UID EXPUNGE";
- if (strCurrentFolder.empty() || !lpSession) {
- HrResponse(RESP_TAGGED_NO, strTag, strCommand + " error no folder");
- return MAPI_E_CALL_FAILED;
- }
- if (bCurrentFolderReadOnly) {
- HrResponse(RESP_TAGGED_NO, strTag, strCommand + " error folder read only");
- return MAPI_E_CALL_FAILED;
- }
- // UID EXPUNGE is always passed UIDs
- hr = HrSeqUidSetToRestriction(strSeqSet, rst);
- if (hr != hrSuccess)
- return hr;
- hr = HrExpungeDeleted(strTag, strCommand, std::move(rst));
- if (hr != hrSuccess)
- return hr;
-
- // Let HrRefreshFolderMails output the actual EXPUNGEs
- HrRefreshFolderMails(false, !bCurrentFolderReadOnly, NULL);
- HrResponse(RESP_TAGGED_OK, strTag, strCommand + " completed");
- return hrSuccess;
- }
- /**
- * @brief Handles the SEARCH command
- *
- * Searches in the current selected folder using the given search
- * criteria.
- *
- * @param[in] strTag the IMAP tag for this command
- * @param[in] lstSearchCriteria The clients search options.
- * @param[in] bUidMode UID SEARCH (true) or flat ID SEARCH (false)
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrCmdSearch(const string &strTag, vector<string> &lstSearchCriteria, bool bUidMode) {
- HRESULT hr = hrSuccess;
- list<ULONG> lstMailnr;
- ULONG ulCriterianr = 0;
- string strResponse;
- char szBuffer[33];
- std::unique_ptr<ECIConv> iconv;
- string strMode;
- if (bUidMode)
- strMode = "UID ";
- if (strCurrentFolder.empty() || !lpSession) {
- HrResponse(RESP_TAGGED_NO, strTag, strMode + "SEARCH error no folder");
- return MAPI_E_CALL_FAILED;
- }
- // don't support other charsets
- // @todo unicode searches
- if (lstSearchCriteria[0].compare("CHARSET") == 0) {
- if (lstSearchCriteria[1] != "WINDOWS-1252") {
- iconv.reset(new ECIConv("windows-1252", lstSearchCriteria[1]));
- if (!iconv->canConvert()) {
- HrResponse(RESP_TAGGED_NO, strTag, "[BADCHARSET (WINDOWS-1252)] " + strMode + "SEARCH charset not supported");
- return MAPI_E_CALL_FAILED;
- }
- }
- ulCriterianr += 2;
- }
- hr = HrSearch(std::move(lstSearchCriteria), ulCriterianr, lstMailnr);
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, strMode + "SEARCH error");
- return hr;
- }
- strResponse = "SEARCH";
- for (auto nr : lstMailnr) {
- snprintf(szBuffer, 32, " %u", bUidMode ? lstFolderMailEIDs[nr].ulUid : nr + 1);
- strResponse += szBuffer;
- }
- HrResponse(RESP_UNTAGGED, strResponse);
- HrResponse(RESP_TAGGED_OK, strTag, strMode + "SEARCH completed");
- return hr;
- }
- /**
- * @brief Handles the FETCH command
- *
- * Fetch specified parts of a list of emails.
- *
- * @param[in] strTag the IMAP tag for this command
- * @param[in] strSeqSet an IMAP sequence of IDs or UIDs depending on bUidMode
- * @param[in] strMsgDataItemNames The parts of the emails to fetch
- * @param[in] bUidMode use UID (true) or ID (false) numbers
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrCmdFetch(const string &strTag, const string &strSeqSet, const string &strMsgDataItemNames, bool bUidMode) {
- HRESULT hr = hrSuccess;
- vector<string> lstDataItems;
- list<ULONG> lstMails;
- ULONG ulCurrent = 0;
- bool bFound = false;
- string strMode;
- if (bUidMode)
- strMode = "UID ";
- if (strCurrentFolder.empty() || !lpSession) {
- HrResponse(RESP_TAGGED_BAD, strTag, strMode + "FETCH error no folder");
- return MAPI_E_CALL_FAILED;
- }
- HrGetDataItems(strMsgDataItemNames, lstDataItems);
- if (bUidMode) {
- for (ulCurrent = 0; !bFound && ulCurrent < lstDataItems.size(); ++ulCurrent)
- if (lstDataItems[ulCurrent].compare("UID") == 0)
- bFound = true;
- if (!bFound)
- lstDataItems.push_back("UID");
- }
- if (bUidMode)
- hr = HrParseSeqUidSet(strSeqSet, lstMails);
- else
- hr = HrParseSeqSet(strSeqSet, lstMails);
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, strMode + "FETCH sequence parse error in: " + strSeqSet);
- return hr;
- }
- hr = HrPropertyFetch(lstMails, lstDataItems);
- if (hr != hrSuccess)
- HrResponse(RESP_TAGGED_NO, strTag, strMode + "FETCH failed");
- else
- HrResponse(RESP_TAGGED_OK, strTag, strMode + "FETCH completed");
- return hr;
- }
- /**
- * @brief Handles the STORE command
- *
- * Sets, appends or removes flags from a list of emails.
- *
- * @param[in] strTag the IMAP tag for this command
- * @param[in] strSeqSet an IMAP sequence of IDs or UIDs depending on bUidMode
- * @param[in] strMsgDataItemName contains the FLAGS modifier command
- * @param[in] strMsgDataItemValue contains a list of flags to modify the emails with
- * @param[in] bUidMode use UID (true) or ID (false) numbers
- *
- * @return MAPI error code
- */
- HRESULT IMAP::HrCmdStore(const string &strTag, const string &strSeqSet, const string &strMsgDataItemName, const string &strMsgDataItemValue, bool bUidMode) {
- HRESULT hr = hrSuccess;
- list<ULONG> lstMails;
- vector<string> lstDataItems;
- string strMode;
- bool bDelete = false;
- if (bUidMode)
- strMode = "UID";
- strMode += " STORE";
- if (strCurrentFolder.empty() || !lpSession) {
- HrResponse(RESP_TAGGED_NO, strTag, strMode + " error no folder");
- return MAPI_E_CALL_FAILED;
- }
- if (bCurrentFolderReadOnly) {
- HrResponse(RESP_TAGGED_NO, strTag, strMode + " error folder read only");
- return MAPI_E_CALL_FAILED;
- }
- lstDataItems.push_back("FLAGS");
- if (bUidMode)
- lstDataItems.push_back("UID");
- if (bUidMode)
- hr = HrParseSeqUidSet(strSeqSet, lstMails);
- else
- hr = HrParseSeqSet(strSeqSet, lstMails);
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, strMode + " sequence parse error in: " + strSeqSet);
- return hr;
- }
- hr = HrStore(lstMails, strMsgDataItemName, strMsgDataItemValue, &bDelete);
- if (hr != MAPI_E_NOT_ME) {
- HrPropertyFetch(lstMails, lstDataItems);
- hr = hrSuccess;
- }
- if (bDelete && parseBool(lpConfig->GetSetting("imap_expunge_on_delete"))) {
- std::unique_ptr<ECRestriction> rst;
- static_assert(std::is_polymorphic<ECRestriction>::value, "ECRestriction needs to be polymorphic for unique_ptr to work");
- if (bUidMode) {
- hr = HrSeqUidSetToRestriction(strSeqSet, rst);
- if (hr != hrSuccess)
- return hr;
- }
- if (HrExpungeDeleted(strTag, strMode, std::move(rst)) != hrSuccess)
- // HrExpungeDeleted sent client NO result.
- return hrSuccess;
- // Let HrRefreshFolderMails output the actual EXPUNGEs
- HrRefreshFolderMails(false, !bCurrentFolderReadOnly, NULL);
- }
- HrResponse(RESP_TAGGED_OK, strTag, strMode + " completed");
- return hr;
- }
- /**
- * @brief Handles the COPY command
- *
- * Copy a list of emails from the current selected folder to the given folder.
- *
- * @param[in] strTag the IMAP tag for this command
- * @param[in] strSeqSet an IMAP sequence of IDs or UIDs depending on bUidMode
- * @param[in] strFolder the folder to copy the message to, in IMAP UTF-7 charset
- * @param[in] bUidMode use UID (true) or ID (false) numbers
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrCmdCopy(const string &strTag, const string &strSeqSet, const string &strFolder, bool bUidMode) {
- HRESULT hr = hrSuccess;
- list<ULONG> lstMails;
- string strMode;
- if (bUidMode)
- strMode = "UID ";
- if (strCurrentFolder.empty() || !lpSession) {
- HrResponse(RESP_TAGGED_NO, strTag, strMode + "COPY error no folder");
- return MAPI_E_CALL_FAILED;
- }
- if (bUidMode)
- hr = HrParseSeqUidSet(strSeqSet, lstMails);
- else
- hr = HrParseSeqSet(strSeqSet, lstMails);
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, strMode + "COPY sequence parse error in: " + strSeqSet);
- return hr;
- }
- hr = HrCopy(lstMails, strFolder, false);
- if (hr == MAPI_E_NOT_FOUND) {
- HrResponse(RESP_TAGGED_NO, strTag, "[TRYCREATE] " + strMode + "COPY folder not found");
- return hr;
- } else if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, strMode + "COPY error");
- return hr;
- }
- HrResponse(RESP_TAGGED_OK, strTag, strMode + "COPY completed");
- return hr;
- }
- /**
- * @brief Handles the UID XAOL-MOVE command (non-RFC command)
- *
- * This extension, currently only used by thunderbird, moves a list of
- * emails into the given folder. Normally IMAP can only move mails
- * with COPY/STORE(\Deleted) commands.
- *
- * @param[in] strTag the IMAP tag for this command
- * @param[in] strSeqSet an IMAP sequence of IDs or UIDs depending on bUidMode
- * @param[in] strFolder the folder to copy the message to, in IMAP UTF-7 charset
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrCmdUidXaolMove(const string &strTag, const string &strSeqSet, const string &strFolder) {
- HRESULT hr = hrSuccess;
- list<ULONG> lstMails;
- if (strCurrentFolder.empty() || !lpSession) {
- HrResponse(RESP_TAGGED_NO, strTag, "UID XAOL-MOVE error no folder");
- return MAPI_E_CALL_FAILED;
- }
- hr = HrParseSeqUidSet(strSeqSet, lstMails);
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, "UID XAOL-MOVE sequence parse error in: " + strSeqSet);
- return hr;
- }
- hr = HrCopy(lstMails, strFolder, true);
- if (hr == MAPI_E_NOT_FOUND) {
- HrResponse(RESP_TAGGED_NO, strTag, "[TRYCREATE] UID XAOL-MOVE folder not found");
- return hr;
- } else if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, "UID XAOL-MOVE error");
- return hr;
- }
- // Let HrRefreshFolderMails output the actual EXPUNGEs
- HrRefreshFolderMails(false, !bCurrentFolderReadOnly, NULL);
- HrResponse(RESP_TAGGED_OK, strTag, "UID XAOL-MOVE completed");
- return hr;
- }
- /**
- * Convert a MAPI array of properties (from a table) to an IMAP FLAGS list.
- *
- * @todo, flags always are in a list, so this function should add the ()
- *
- * @param[in] lpProps Array of MAPI properties
- * @param[in] cValues Number of properties in lpProps
- * @param[in] bRecent Add the recent flag to the list
- *
- * @return string with IMAP Flags
- */
- std::string IMAP::PropsToFlags(LPSPropValue lpProps, unsigned int cValues, bool bRecent, bool bRead) {
- string strFlags;
- auto lpMessageFlags = PCpropFindProp(lpProps, cValues, PR_MESSAGE_FLAGS);
- auto lpFlagStatus = PCpropFindProp(lpProps, cValues, PR_FLAG_STATUS);
- auto lpMsgStatus = PCpropFindProp(lpProps, cValues, PR_MSG_STATUS);
- auto lpLastVerb = PCpropFindProp(lpProps, cValues, PR_LAST_VERB_EXECUTED);
- if ((lpMessageFlags != NULL &&
- lpMessageFlags->Value.ul & MSGFLAG_READ) || bRead)
- strFlags += "\\Seen ";
- if (lpFlagStatus != NULL && lpFlagStatus->Value.ul != 0)
- strFlags += "\\Flagged ";
- if (lpLastVerb) {
- if (lpLastVerb->Value.ul == NOTEIVERB_REPLYTOSENDER ||
- lpLastVerb->Value.ul == NOTEIVERB_REPLYTOALL)
- strFlags += "\\Answered ";
- // there is no flag in imap for forwards. thunderbird uses the custom flag $Forwarded,
- // and this is the only custom flag we support.
- if (lpLastVerb->Value.ul == NOTEIVERB_FORWARD)
- strFlags += "$Forwarded ";
- }
- if (lpMsgStatus) {
- if (lpMsgStatus->Value.ul & MSGSTATUS_DRAFT)
- strFlags += "\\Draft ";
- if (lpLastVerb == NULL &&
- lpMsgStatus->Value.ul & MSGSTATUS_ANSWERED)
- strFlags += "\\Answered ";
- if (lpMsgStatus->Value.ul & MSGSTATUS_DELMARKED)
- strFlags += "\\Deleted ";
- }
-
- if (bRecent)
- strFlags += "\\Recent ";
-
- // strip final space
- if (!strFlags.empty())
- strFlags.resize(strFlags.size() - 1);
- return strFlags;
- }
- /**
- * The notify callback function. Sends changes to the client on the
- * current selected folder during the IDLE command.
- *
- * @param[in] lpContext callback data, containing "this" IMAP class
- * @param[in] cNotif Number of notification messages in lpNotif
- * @param[in] lpNotif Array of changes on the folder
- *
- * @return MAPI Error code
- */
- LONG __stdcall IMAP::IdleAdviseCallback(void *lpContext, ULONG cNotif,
- LPNOTIFICATION lpNotif)
- {
- auto lpIMAP = static_cast<IMAP *>(lpContext);
- string strFlags;
- ULONG ulMailNr = 0;
- ULONG ulRecent = 0;
- bool bReload = false;
- vector<IMAP::SMail> oldMails;
- enum { EID, IKEY, IMAPID, MESSAGE_FLAGS, FLAG_STATUS, MSG_STATUS, LAST_VERB, NUM_COLS };
- IMAP::SMail sMail;
- {
- // modify:
- // * [mailnr] FETCH (FLAGS (\Seen \Recent \Flagged \Deleted))
- // remove:
- // * [mailnr] EXPUNGE
- // new mail/add:
- // * [total] exists (optional?)
- // * 1 recent
- }
- if (!lpIMAP)
- return MAPI_E_CALL_FAILED;
- scoped_lock l_idle(lpIMAP->m_mIdleLock);
- if (!lpIMAP->m_bIdleMode)
- return MAPI_E_CALL_FAILED;
- for (ULONG i = 0; i < cNotif && !bReload; ++i) {
- if (lpNotif[i].ulEventType != fnevTableModified)
- continue;
- switch (lpNotif[i].info.tab.ulTableEvent) {
- case TABLE_ROW_ADDED:
- sMail.sEntryID = BinaryArray(lpNotif[i].info.tab.row.lpProps[EID].Value.bin);
- sMail.sInstanceKey = BinaryArray(lpNotif[i].info.tab.propIndex.Value.bin);
- sMail.bRecent = true;
- if (lpNotif[i].info.tab.row.lpProps[IMAPID].ulPropTag == PR_EC_IMAP_ID)
- sMail.ulUid = lpNotif[i].info.tab.row.lpProps[IMAPID].Value.ul;
- sMail.strFlags = lpIMAP->PropsToFlags(lpNotif[i].info.tab.row.lpProps, lpNotif[i].info.tab.row.cValues, true, false);
- lpIMAP->lstFolderMailEIDs.push_back(sMail);
- lpIMAP->m_ulLastUid = max(lpIMAP->m_ulLastUid, sMail.ulUid);
- ++ulRecent;
- break;
- case TABLE_ROW_DELETED:
- // find number and print N EXPUNGE
- if (lpNotif[i].info.tab.propIndex.ulPropTag == PR_INSTANCE_KEY) {
- auto iterMail = lpIMAP->lstFolderMailEIDs.begin();
- for (; iterMail != lpIMAP->lstFolderMailEIDs.cend(); ++iterMail)
- if (iterMail->sInstanceKey == BinaryArray(lpNotif[i].info.tab.propIndex.Value.bin))
- break;
-
- if (iterMail != lpIMAP->lstFolderMailEIDs.cend()) {
- ulMailNr = iterMail - lpIMAP->lstFolderMailEIDs.cbegin();
- // remove mail from list
- lpIMAP->HrResponse(RESP_UNTAGGED, stringify(ulMailNr+1) + " EXPUNGE");
- lpIMAP->lstFolderMailEIDs.erase(iterMail);
- }
- }
- break;
- case TABLE_ROW_MODIFIED:
- // find number and print N FETCH (FLAGS (flags...))
- strFlags.clear();
- if (lpNotif[i].info.tab.row.lpProps[IMAPID].ulPropTag == PR_EC_IMAP_ID) {
- auto iterMail = find(lpIMAP->lstFolderMailEIDs.cbegin(), lpIMAP->lstFolderMailEIDs.cend(), lpNotif[i].info.tab.row.lpProps[IMAPID].Value.ul);
- // not found probably means the client needs to sync
- if (iterMail != lpIMAP->lstFolderMailEIDs.cend()) {
- ulMailNr = iterMail - lpIMAP->lstFolderMailEIDs.cbegin();
- strFlags = lpIMAP->PropsToFlags(lpNotif[i].info.tab.row.lpProps, lpNotif[i].info.tab.row.cValues, iterMail->bRecent, false);
- lpIMAP->HrResponse(RESP_UNTAGGED, stringify(ulMailNr+1) + " FETCH (FLAGS (" + strFlags + "))");
- }
- }
- break;
- case TABLE_RELOAD:
- // TABLE_RELOAD is unused in Kopano
- case TABLE_CHANGED:
- lpIMAP->HrRefreshFolderMails(false, !lpIMAP->bCurrentFolderReadOnly, NULL);
- break;
- };
- }
- if (ulRecent) {
- lpIMAP->HrResponse(RESP_UNTAGGED, stringify(ulRecent) + " RECENT");
- lpIMAP->HrResponse(RESP_UNTAGGED, stringify(lpIMAP->lstFolderMailEIDs.size()) + " EXISTS");
- }
- return S_OK;
- }
- /**
- * @brief Handles the IDLE command
- *
- * If we have a current selected folder, a notification advise is
- * created in another thread, which will print updates to the
- * client. The IMAP client can at anytime close the thread by exiting,
- * or sending the DONE command.
- *
- * @param[in] strTag the IMAP tag for this command
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrCmdIdle(const string &strTag) {
- HRESULT hr = hrSuccess;
- object_ptr<IMAPIFolder> lpFolder;
- enum { EID, IKEY, IMAPID, MESSAGE_FLAGS, FLAG_STATUS, MSG_STATUS, LAST_VERB, NUM_COLS };
- static constexpr const SizedSPropTagArray(NUM_COLS, spt) =
- {NUM_COLS, {PR_ENTRYID, PR_INSTANCE_KEY, PR_EC_IMAP_ID,
- PR_MESSAGE_FLAGS, PR_FLAG_STATUS, PR_MSG_STATUS,
- PR_LAST_VERB_EXECUTED}};
- ulock_normal l_idle(m_mIdleLock, std::defer_lock_t());
- // Outlook (express) IDLEs without selecting a folder.
- // When sending an error from this command, Outlook loops on the IDLE command forever :(
- // Therefore, we can never return a HrResultBad() or ...No() here, so we always "succeed"
- m_strIdleTag = strTag;
- m_bIdleMode = true;
- if (strCurrentFolder.empty() || !lpSession) {
- HrResponse(RESP_CONTINUE, "empty idle, nothing is going to happen");
- goto exit;
- }
- hr = HrFindFolder(strCurrentFolder, bCurrentFolderReadOnly, &~lpFolder);
- if (hr != hrSuccess) {
- HrResponse(RESP_CONTINUE, "Can't open selected folder to idle in");
- goto exit;
- }
- hr = lpFolder->GetContentsTable(0, &m_lpIdleTable);
- if (hr != hrSuccess) {
- HrResponse(RESP_CONTINUE, "Can't open selected contents table to idle in");
- goto exit;
- }
- hr = m_lpIdleTable->SetColumns(spt, 0);
- if (hr != hrSuccess) {
- HrResponse(RESP_CONTINUE, "Cannot select columns on selected contents table for idle information");
- goto exit;
- }
- hr = HrAllocAdviseSink(&IMAP::IdleAdviseCallback, (void*)this, &m_lpIdleAdviseSink);
- if (hr != hrSuccess) {
- HrResponse(RESP_CONTINUE, "Can't allocate memory to idle");
- goto exit;
- }
- l_idle.lock();
- hr = m_lpIdleTable->Advise(fnevTableModified, m_lpIdleAdviseSink, &m_ulIdleAdviseConnection);
- if (hr != hrSuccess) {
- HrResponse(RESP_CONTINUE, "Can't advise on current selected folder");
- l_idle.unlock();
- goto exit;
- }
- // \o/ we really succeeded this time
- HrResponse(RESP_CONTINUE, "waiting for notifications");
- l_idle.unlock();
- exit:
- if (hr != hrSuccess) {
- if (m_ulIdleAdviseConnection && m_lpIdleTable) {
- m_lpIdleTable->Unadvise(m_ulIdleAdviseConnection);
- m_ulIdleAdviseConnection = 0;
- }
- if (m_lpIdleAdviseSink) {
- m_lpIdleAdviseSink->Release();
- m_lpIdleAdviseSink = NULL;
- }
- if (m_lpIdleTable) {
- m_lpIdleTable->Release();
- m_lpIdleTable = NULL;
- }
- }
- return hr;
- }
- /**
- * @brief Handles the DONE command
- *
- * The DONE command closes the previous IDLE command, and has no IMAP
- * tag. This function can be also called implicitly, so a response to
- * the client is not always required.
- *
- * @param[in] bSendResponse Send a response to the client or not
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrDone(bool bSendResponse) {
- HRESULT hr = hrSuccess;
- // TODO: maybe add sleep here, so thunderbird gets all notifications?
- scoped_lock l_idle(m_mIdleLock);
- if (bSendResponse) {
- if (m_bIdleMode)
- HrResponse(RESP_TAGGED_OK, m_strIdleTag, "IDLE complete");
- else
- HrResponse(RESP_TAGGED_BAD, m_strIdleTag, "was not idling");
- }
- if (m_ulIdleAdviseConnection && m_lpIdleTable) {
- m_lpIdleTable->Unadvise(m_ulIdleAdviseConnection);
- m_ulIdleAdviseConnection = 0;
- }
- if (m_lpIdleAdviseSink)
- m_lpIdleAdviseSink->Release();
- m_lpIdleAdviseSink = NULL;
- m_ulIdleAdviseConnection = 0;
- m_bIdleMode = false;
- m_strIdleTag.clear();
- if (m_lpIdleTable)
- m_lpIdleTable->Release();
- m_lpIdleTable = NULL;
- return hr;
- }
- /**
- * @brief Handles the NAMESPACE command
- *
- * We currently only support the user namespace.
- *
- * @param[in] strTag the IMAP tag for this command
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrCmdNamespace(const string &strTag) {
- HrResponse(RESP_UNTAGGED, string("NAMESPACE ((\"\" \"") +
- IMAP_HIERARCHY_DELIMITER + "\")) NIL NIL");
- HrResponse(RESP_TAGGED_OK, strTag, "NAMESPACE Completed");
- return hrSuccess;
- }
- /**
- * Sends a response to the client with specific quota information. We
- * only export the hard quota level to the IMAP client.
- *
- * @param[in] strTag Tag of the IMAP command
- * @param[in] strQuotaRoot Which quota root is requested
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrPrintQuotaRoot(const string& strTag)
- {
- HRESULT hr = hrSuccess;
- static constexpr const SizedSPropTagArray(2, sStoreProps) =
- {2, {PR_MESSAGE_SIZE_EXTENDED, PR_QUOTA_RECEIVE_THRESHOLD}};
- memory_ptr<SPropValue> lpProps;
- ULONG cValues = 0;
- hr = lpStore->GetProps(sStoreProps, 0, &cValues, &~lpProps);
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, "GetQuota MAPI Error");
- return hr;
- }
- // only print quota if we have a level
- if (lpProps[1].Value.ul)
- HrResponse(RESP_UNTAGGED, "QUOTA \"\" (STORAGE "+stringify(lpProps[0].Value.li.QuadPart / 1024) + " " + stringify(lpProps[1].Value.ul) + ")");
- return hrSuccess;
- }
- /**
- * @brief Handles the GETQUOTAROOT command
- *
- * Sends which quota roots are available (only hard quota).
- *
- * @param[in] strTag Tag of the IMAP command
- * @param[in] strFolder The folder to request quota information on
- *
- * @todo check if folder is in public and show no quota?
- *
- * @return
- */
- HRESULT IMAP::HrCmdGetQuotaRoot(const std::string &strTag,
- const std::vector<std::string> &args)
- {
- HRESULT hr = hrSuccess;
- const std::string &strFolder = args[0];
- if (!lpStore) {
- HrResponse(RESP_TAGGED_BAD, strTag, "Login first");
- return MAPI_E_CALL_FAILED;
- }
- // @todo check if folder exists
- HrResponse(RESP_UNTAGGED, "QUOTAROOT \"" + strFolder + "\" \"\"");
- hr = HrPrintQuotaRoot(strTag);
- if (hr != hrSuccess)
- return hr; /* handle error? */
- HrResponse(RESP_TAGGED_OK, strTag, "GetQuotaRoot complete");
- return hr;
- }
- /**
- * Get the quota value for a given quota root. We only have the "" quota root.
- *
- * @param[in] strTag Tag of the IMAP command
- * @param[in] strQuotaRoot given quota root
- *
- * @return
- */
- HRESULT IMAP::HrCmdGetQuota(const std::string &strTag,
- const std::vector<std::string> &args)
- {
- const std::string &strQuotaRoot = args[0];
- if (!lpStore) {
- HrResponse(RESP_TAGGED_BAD, strTag, "Login first");
- return MAPI_E_CALL_FAILED;
- }
- if (strQuotaRoot.empty()) {
- HRESULT hr = HrPrintQuotaRoot(strTag);
- if (hr != hrSuccess)
- return hr;
- HrResponse(RESP_TAGGED_OK, strTag, "GetQuota complete");
- return hrSuccess;
- }
- HrResponse(RESP_TAGGED_NO, strTag, "Quota root does not exist");
- return hrSuccess;
- }
- HRESULT IMAP::HrCmdSetQuota(const std::string &strTag,
- const std::vector<std::string> &args)
- {
- HrResponse(RESP_TAGGED_NO, strTag, "SetQuota Permission denied");
- return hrSuccess;
- }
- /**
- * Send an untagged response.
- *
- * @param[in] strUntag Either RESP_UNTAGGED or RESP_CONTINUE
- * @param[in] strResponse Status information to send to the client
- *
- * @return MAPI Error code
- */
- void IMAP::HrResponse(const string &strUntag, const string &strResponse)
- {
- // Early cutoff of debug messages. This means the current process's config
- // determines if we log debug info (so HUP will only affect new processes if
- // you want debug output)
- if (lpLogger->Log(EC_LOGLEVEL_DEBUG))
- lpLogger->Log(EC_LOGLEVEL_DEBUG, "> %s%s", strUntag.c_str(), strResponse.c_str());
- HRESULT hr = lpChannel->HrWriteLine(strUntag + strResponse);
- if (hr != hrSuccess)
- throw KMAPIError(hr);
- }
- /**
- * Tagged response to the client. You may only send one tagged
- * response to the client per received command.
- *
- * @note be careful when sending NO or BAD results: some clients may try the same command over and over.
- *
- * @param[in] strTag The tag received from the client for a command
- * @param[in] strResult The result of the command, either RESP_TAGGED_OK, RESP_TAGGED_NO or RESP_TAGGED_BAD
- * @param[in] strResponse The result of the command to send to the client
- *
- * @return MAPI Error code
- */
- void IMAP::HrResponse(const string &strResult, const string &strTag, const string &strResponse)
- {
- unsigned int max_err;
- max_err = strtoul(lpConfig->GetSetting("imap_max_fail_commands"), NULL, 0);
- // Some clients keep looping, so if we keep sending errors, just disconnect the client.
- if (strResult.compare(RESP_TAGGED_OK) == 0)
- m_ulErrors = 0;
- else
- ++m_ulErrors;
- if (m_ulErrors >= max_err) {
- lpLogger->Log(EC_LOGLEVEL_ERROR, "Disconnecting client of user %ls because too many (%u) erroneous commands received, last reply:", m_strwUsername.c_str(), max_err);
- lpLogger->Log(EC_LOGLEVEL_ERROR, "%s%s%s", strTag.c_str(), strResult.c_str(), strResponse.c_str());
- throw KMAPIError(MAPI_E_END_OF_SESSION);
- }
-
- if (lpLogger->Log(EC_LOGLEVEL_DEBUG))
- lpLogger->Log(EC_LOGLEVEL_DEBUG, "> %s%s%s", strTag.c_str(), strResult.c_str(), strResponse.c_str());
- HRESULT hr = lpChannel->HrWriteLine(strTag + strResult + strResponse);
- if (hr != hrSuccess)
- throw KMAPIError(hr);
- }
- /**
- * Remove \Deleted marked message from current selected folder. Only
- * response in case of an error.
- *
- * @param[in] strTag IMAP tag given for command
- * @param[in] strCommand IMAP command which triggered the expunge
- * @param[in] lpUIDRestriction optional restriction to limit messages actually deleted (see HrCmdExpunge)
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrExpungeDeleted(const std::string &strTag,
- const std::string &strCommand, std::unique_ptr<ECRestriction> &&uid_rst)
- {
- HRESULT hr = hrSuccess;
- object_ptr<IMAPIFolder> lpFolder;
- ENTRYLIST sEntryList;
- memory_ptr<SRestriction> lpRootRestrict;
- object_ptr<IMAPITable> lpTable;
- rowset_ptr lpRows;
- enum { EID, NUM_COLS };
- static constexpr const SizedSPropTagArray(NUM_COLS, spt) = {NUM_COLS, {PR_ENTRYID}};
- ECAndRestriction rst;
- sEntryList.lpbin = NULL;
- hr = HrFindFolder(strCurrentFolder, bCurrentFolderReadOnly, &~lpFolder);
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, strCommand + " error opening folder");
- goto exit;
- }
- hr = lpFolder->GetContentsTable(MAPI_DEFERRED_ERRORS , &~lpTable);
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, strCommand + " error opening folder contents");
- goto exit;
- }
- if (uid_rst != nullptr)
- rst += std::move(*uid_rst.get());
- rst += ECExistRestriction(PR_MSG_STATUS);
- rst += ECBitMaskRestriction(BMR_NEZ, PR_MSG_STATUS, MSGSTATUS_DELMARKED);
- hr = rst.CreateMAPIRestriction(&~lpRootRestrict, ECRestriction::Cheap);
- if (hr != hrSuccess)
- goto exit;
- hr = HrQueryAllRows(lpTable, spt, lpRootRestrict, nullptr, 0, &~lpRows);
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, strCommand + " error queryring rows");
- goto exit;
- }
- if(lpRows->cRows) {
- sEntryList.cValues = 0;
- if ((hr = MAPIAllocateBuffer(sizeof(SBinary) * lpRows->cRows, (LPVOID *) &sEntryList.lpbin)) != hrSuccess)
- goto exit;
- for (ULONG ulMailnr = 0; ulMailnr < lpRows->cRows; ++ulMailnr) {
- hr = lpFolder->SetMessageStatus(lpRows->aRow[ulMailnr].lpProps[EID].Value.bin.cb, (LPENTRYID)lpRows->aRow[ulMailnr].lpProps[EID].Value.bin.lpb,
- 0, ~MSGSTATUS_DELMARKED, NULL);
- if (hr != hrSuccess)
- lpLogger->Log(EC_LOGLEVEL_WARNING, "Unable to update message status flag during " + strCommand);
- sEntryList.lpbin[sEntryList.cValues++] = lpRows->aRow[ulMailnr].lpProps[EID].Value.bin;
- }
- hr = lpFolder->DeleteMessages(&sEntryList, 0, NULL, 0);
- if (hr != hrSuccess) {
- HrResponse(RESP_TAGGED_NO, strTag, strCommand + " error deleting messages");
- goto exit;
- }
- }
- exit:
- MAPIFreeBuffer(sEntryList.lpbin);
- return hr;
- }
- /**
- * Create a flat list of all folders in the users tree, and possibly
- * add the public folders to this list too.
- *
- * @param[out] lstFolders Sets the folder list
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrGetFolderList(list<SFolder> &lstFolders) {
- HRESULT hr = hrSuccess;
- memory_ptr<SPropValue> lpPropVal;
- ULONG cbEntryID;
- memory_ptr<ENTRYID> lpEntryID;
- lstFolders.clear();
- hr = HrGetOneProp(lpStore, PR_IPM_SUBTREE_ENTRYID, &~lpPropVal);
- if (hr != hrSuccess)
- return hr;
- // make folders list from IPM_SUBTREE
- hr = HrGetSubTree(lstFolders, lpPropVal->Value.bin, wstring(), lstFolders.end());
- if (hr != hrSuccess)
- return hr;
- hr = lpStore->GetReceiveFolder((LPTSTR)"IPM", 0, &cbEntryID, &~lpEntryID, NULL);
- if (hr != hrSuccess)
- return hr;
- // find the inbox, and name it INBOX
- for (auto &folder : lstFolders)
- if (cbEntryID == folder.sEntryID.cb && memcmp(folder.sEntryID.lpb, lpEntryID, cbEntryID) == 0) {
- folder.strFolderName = L"INBOX";
- break;
- }
- if(!lpPublicStore)
- return hr;
- hr = HrGetOneProp(lpPublicStore, PR_IPM_PUBLIC_FOLDERS_ENTRYID, &~lpPropVal);
- if (hr != hrSuccess) {
- lpLogger->Log(EC_LOGLEVEL_WARNING, "Public store is enabled in configuration, but Public Folders inside public store could not be found.");
- return hrSuccess;
- }
- // make public folder folders list
- hr = HrGetSubTree(lstFolders, lpPropVal->Value.bin, PUBLIC_FOLDERS_NAME, --lstFolders.end());
- if (hr != hrSuccess)
- lpLogger->Log(EC_LOGLEVEL_WARNING, "Public store is enabled in configuration, but Public Folders inside public store could not be found.");
- return hrSuccess;
- }
- /**
- * Loads a binary blob from PR_EC_IMAP_SUBSCRIBED property on the
- * Inbox where entryids of folders are store in, which describes the
- * folders the user subscribed to with an IMAP client.
- *
- * @return MAPI Error code
- * @retval hrSuccess Subscribed folder list loaded in m_vSubscriptions
- */
- HRESULT IMAP::HrGetSubscribedList() {
- HRESULT hr = hrSuccess;
- object_ptr<IStream> lpStream;
- object_ptr<IMAPIFolder> lpInbox;
- ULONG cbEntryID = 0;
- memory_ptr<ENTRYID> lpEntryID;
- ULONG ulObjType = 0;
- ULONG size, i;
- ULONG read;
- ULONG cb = 0;
-
- m_vSubscriptions.clear();
- hr = lpStore->GetReceiveFolder((LPTSTR)"IPM", 0, &cbEntryID, &~lpEntryID, NULL);
- if (hr != hrSuccess)
- return hr;
- hr = lpSession->OpenEntry(cbEntryID, lpEntryID, &IID_IMAPIFolder, 0, &ulObjType, &~lpInbox);
- if (hr != hrSuccess)
- return hr;
- hr = lpInbox->OpenProperty(PR_EC_IMAP_SUBSCRIBED, &IID_IStream, 0, 0, &~lpStream);
- if (hr != hrSuccess)
- return hr;
- hr = lpStream->Read(&size, sizeof(ULONG), &read);
- if (hr != hrSuccess || read != sizeof(ULONG))
- return hr;
- if (size == 0)
- return hr;
- for (i = 0; i < size; ++i) {
- std::unique_ptr<BYTE[]> lpb;
- hr = lpStream->Read(&cb, sizeof(ULONG), &read);
- if (hr != hrSuccess || read != sizeof(ULONG))
- return hr;
- lpb.reset(new BYTE[cb]);
- hr = lpStream->Read(lpb.get(), cb, &read);
- if (hr != hrSuccess || read != cb)
- return MAPI_E_NOT_FOUND;
- m_vSubscriptions.push_back(BinaryArray(lpb.get(), cb));
- }
- return hr;
- }
- /**
- * Saves the m_vSubscriptions list of subscribed folders to
- * PR_EC_IMAP_SUBSCRIBED property in the inbox of the user.
- *
- * @return MAPI Error code
- * @retval hrSuccess Subscribed folder list saved in Inbox property
- */
- HRESULT IMAP::HrSetSubscribedList() {
- HRESULT hr = hrSuccess;
- object_ptr<IStream> lpStream;
- object_ptr<IMAPIFolder> lpInbox;
- ULONG cbEntryID = 0;
- memory_ptr<ENTRYID> lpEntryID;
- ULONG ulObjType = 0;
- ULONG written;
- ULONG size;
- ULARGE_INTEGER liZero = {{0, 0}};
- hr = lpStore->GetReceiveFolder((LPTSTR)"IPM", 0, &cbEntryID, &~lpEntryID, NULL);
- if (hr != hrSuccess)
- return hr;
- hr = lpSession->OpenEntry(cbEntryID, lpEntryID, &IID_IMAPIFolder, MAPI_BEST_ACCESS, &ulObjType, &~lpInbox);
- if (hr != hrSuccess)
- return hr;
- hr = lpInbox->OpenProperty(PR_EC_IMAP_SUBSCRIBED, &IID_IStream, STGM_TRANSACTED, MAPI_CREATE | MAPI_MODIFY, &~lpStream);
- if (hr != hrSuccess)
- return hr;
- lpStream->SetSize(liZero);
- size = m_vSubscriptions.size();
- hr = lpStream->Write(&size, sizeof(ULONG), &written);
- if (hr != hrSuccess)
- return hr;
- for (const auto &folder : m_vSubscriptions) {
- hr = lpStream->Write(&folder.cb, sizeof(ULONG), &written);
- if (hr != hrSuccess)
- return hr;
- hr = lpStream->Write(folder.lpb, folder.cb, &written);
- if (hr != hrSuccess)
- return hr;
- }
- return lpStream->Commit(0);
- }
- /**
- * Add or remove a folder EntryID from the subscribed list, and save
- * it to the server when changed.
- *
- * @param[in] bSubscribe Add (true) to the list, or remove (false)
- * @param[in] cbEntryID number of bytes in lpEntryID
- * @param[in] lpEntryID EntryID to find in the subscribed list
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::ChangeSubscribeList(bool bSubscribe, ULONG cbEntryID, LPENTRYID lpEntryID)
- {
- bool bChanged = false;
- auto iFolder = find(m_vSubscriptions.begin(), m_vSubscriptions.end(),
- BinaryArray(reinterpret_cast<BYTE *>(lpEntryID),
- cbEntryID, true));
- if (iFolder == m_vSubscriptions.cend()) {
- if (bSubscribe) {
- m_vSubscriptions.push_back(BinaryArray((BYTE*)lpEntryID, cbEntryID));
- bChanged = true;
- }
- } else if (!bSubscribe) {
- m_vSubscriptions.erase(iFolder);
- bChanged = true;
- }
- if (bChanged) {
- HRESULT hr = HrSetSubscribedList();
- if (hr != hrSuccess)
- return hr;
- }
- return hrSuccess;
- }
- /**
- * Create a list of special IMAP folders, which may not be deleted,
- * renamed or unsubscribed from.
- *
- * @return MAPI Error code
- * @retval hrSuccess the special folderlist is saved in lstSpecialEntryIDs
- */
- HRESULT IMAP::HrMakeSpecialsList() {
- HRESULT hr = hrSuccess;
- ULONG cbEntryID = 0;
- memory_ptr<ENTRYID> lpEntryID;
- object_ptr<IMAPIFolder> lpInbox;
- ULONG ulObjType = 0;
- ULONG cValues = 0;
- memory_ptr<SPropValue> lpPropArrayStore, lpPropArrayInbox, lpPropVal;
- static constexpr const SizedSPropTagArray(3, sPropsStore) =
- {3, {PR_IPM_OUTBOX_ENTRYID, PR_IPM_SENTMAIL_ENTRYID,
- PR_IPM_WASTEBASKET_ENTRYID}};
- static constexpr const SizedSPropTagArray(6, sPropsInbox) =
- {6, {PR_IPM_APPOINTMENT_ENTRYID, PR_IPM_CONTACT_ENTRYID,
- PR_IPM_DRAFTS_ENTRYID, PR_IPM_JOURNAL_ENTRYID,
- PR_IPM_NOTE_ENTRYID, PR_IPM_TASK_ENTRYID}};
- hr = lpStore->GetProps(sPropsStore, 0, &cValues, &~lpPropArrayStore);
- if (hr != hrSuccess)
- return hr;
- for (ULONG i = 0; i < cValues; ++i)
- if (PROP_TYPE(lpPropArrayStore[i].ulPropTag) == PT_BINARY)
- lstSpecialEntryIDs.insert(BinaryArray(lpPropArrayStore[i].Value.bin.lpb, lpPropArrayStore[i].Value.bin.cb));
- hr = lpStore->GetReceiveFolder((LPTSTR)"IPM", 0, &cbEntryID, &~lpEntryID, NULL);
- if (hr != hrSuccess)
- return hr;
- // inbox is special too
- lstSpecialEntryIDs.insert(BinaryArray(reinterpret_cast<unsigned char *>(lpEntryID.get()), cbEntryID));
- hr = lpSession->OpenEntry(cbEntryID, lpEntryID, &IID_IMAPIFolder, 0, &ulObjType, &~lpInbox);
- if (hr != hrSuccess)
- return hr;
- hr = lpInbox->GetProps(sPropsInbox, 0, &cValues, &~lpPropArrayInbox);
- if (hr != hrSuccess)
- return hr;
- for (ULONG i = 0; i < cValues; ++i)
- if (PROP_TYPE(lpPropArrayInbox[i].ulPropTag) == PT_BINARY)
- lstSpecialEntryIDs.insert(BinaryArray(lpPropArrayInbox[i].Value.bin.lpb, lpPropArrayInbox[i].Value.bin.cb));
- if (HrGetOneProp(lpInbox, PR_ADDITIONAL_REN_ENTRYIDS, &~lpPropVal) == hrSuccess &&
- lpPropVal->Value.MVbin.cValues >= 5 && lpPropVal->Value.MVbin.lpbin[4].cb != 0)
- lstSpecialEntryIDs.insert(BinaryArray(lpPropVal->Value.MVbin.lpbin[4].lpb, lpPropVal->Value.MVbin.lpbin[4].cb));
- if(!lpPublicStore)
- return hrSuccess;
- if (HrGetOneProp(lpPublicStore, PR_IPM_PUBLIC_FOLDERS_ENTRYID, &~lpPropVal) == hrSuccess)
- lstSpecialEntryIDs.insert(BinaryArray(lpPropVal->Value.bin.lpb, lpPropVal->Value.bin.cb));
- return hrSuccess;
- }
- /**
- * Check if the folder with the given EntryID is a special folder.
- *
- * @param[in] cbEntryID number of bytes in lpEntryID
- * @param[in] lpEntryID bytes of the entryid
- *
- * @return is a special folder (true) or a custom user folder (false)
- */
- bool IMAP::IsSpecialFolder(ULONG cbEntryID, LPENTRYID lpEntryID) {
- return lstSpecialEntryIDs.find(BinaryArray(reinterpret_cast<BYTE *>(lpEntryID), cbEntryID, true)) !=
- lstSpecialEntryIDs.end();
- }
- /**
- * Make a list of all mails in the current selected folder.
- *
- * @param[in] bInitialLoad Create a new clean list of mails (false to append only)
- * @param[in] bResetRecent Update the value of PR_EC_IMAP_MAX_ID for this folder
- * @param[out] lpulUnseen The number of unread emails in this folder
- * @param[out] lpulUIDValidity The UIDVALIDITY value for this folder (optional)
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrRefreshFolderMails(bool bInitialLoad, bool bResetRecent, unsigned int *lpulUnseen, ULONG *lpulUIDValidity) {
- HRESULT hr = hrSuccess;
- object_ptr<IMAPIFolder> lpFolder;
- ULONG ulMailnr = 0;
- ULONG ulMaxUID = 0;
- ULONG ulRecent = 0;
- int n = 0;
- SMail sMail;
- bool bNewMail = false;
- enum { EID, IKEY, IMAPID, FLAGS, FLAGSTATUS, MSGSTATUS, LAST_VERB, NUM_COLS };
- vector<SMail>::const_iterator iterMail;
- map<unsigned int, unsigned int> mapUIDs; // Map UID -> ID
- SPropValue sPropMax;
- unsigned int ulUnseen = 0;
- static constexpr const SizedSPropTagArray(2, sPropsFolderIDs) =
- {2, {PR_EC_IMAP_MAX_ID, PR_EC_HIERARCHYID}};
- memory_ptr<SPropValue> lpFolderIDs;
- ULONG cValues;
- if (strCurrentFolder.empty() || lpSession == nullptr)
- return MAPI_E_CALL_FAILED;
- hr = HrFindFolder(strCurrentFolder, bCurrentFolderReadOnly, &~lpFolder);
- if (hr != hrSuccess)
- return hr;
- hr = lpFolder->GetProps(sPropsFolderIDs, 0, &cValues, &~lpFolderIDs);
- if (FAILED(hr))
- return hr;
- if (lpFolderIDs[0].ulPropTag == PR_EC_IMAP_MAX_ID)
- ulMaxUID = lpFolderIDs[0].Value.ul;
- else
- ulMaxUID = 0;
- if (lpulUIDValidity && lpFolderIDs[1].ulPropTag == PR_EC_HIERARCHYID)
- *lpulUIDValidity = lpFolderIDs[1].Value.ul;
- auto folder = KFolder(lpFolder.release());
- auto table = KTable(nullptr);
- try {
- table = folder.get_contents_table(MAPI_DEFERRED_ERRORS);
- table.columns({PR_ENTRYID, PR_INSTANCE_KEY, PR_EC_IMAP_ID,
- PR_MESSAGE_FLAGS, PR_FLAG_STATUS, PR_MSG_STATUS,
- PR_LAST_VERB_EXECUTED}, TBL_BATCH);
- table.sort({{PR_EC_IMAP_ID, KTable::ASCEND}}, TBL_BATCH);
- }
- catch (const KMAPIError &e) {
- return e.code();
- }
- // Remember UIDs if needed
- if(!bInitialLoad)
- for (const auto &mail : lstFolderMailEIDs)
- mapUIDs[mail.ulUid] = n++;
-
- if(bInitialLoad) {
- lstFolderMailEIDs.clear();
- m_ulLastUid = 0;
- }
- iterMail = lstFolderMailEIDs.cbegin();
- // Scan MAPI for new and existing messages
- while(1) {
- rowset_ptr lpRows;
- hr = table->QueryRows(ROWS_PER_REQUEST, 0, &~lpRows);
- if (hr != hrSuccess)
- return hr;
- if(lpRows->cRows == 0)
- break;
-
- for (ulMailnr = 0; ulMailnr < lpRows->cRows; ++ulMailnr) {
- if (lpRows->aRow[ulMailnr].lpProps[EID].ulPropTag != PR_ENTRYID ||
- lpRows->aRow[ulMailnr].lpProps[IKEY].ulPropTag != PR_INSTANCE_KEY ||
- lpRows->aRow[ulMailnr].lpProps[IMAPID].ulPropTag != PR_EC_IMAP_ID)
- continue;
- auto iterUID = mapUIDs.find(lpRows->aRow[ulMailnr].lpProps[IMAPID].Value.ul);
- if(iterUID == mapUIDs.end()) {
- // There is a new message
- sMail.sEntryID = BinaryArray(lpRows->aRow[ulMailnr].lpProps[EID].Value.bin);
- sMail.sInstanceKey = BinaryArray(lpRows->aRow[ulMailnr].lpProps[IKEY].Value.bin);
- sMail.ulUid = lpRows->aRow[ulMailnr].lpProps[IMAPID].Value.ul;
- // Mark as recent if the message has a UID higher than the last highest read UID
- // in this folder. This means that this session is the only one to see the message
- // as recent.
- sMail.bRecent = sMail.ulUid > ulMaxUID;
- // Remember flags
- sMail.strFlags = PropsToFlags(lpRows->aRow[ulMailnr].lpProps, lpRows->aRow[ulMailnr].cValues, sMail.bRecent, false);
- // Put message on the end of our message
- lstFolderMailEIDs.push_back(sMail);
- m_ulLastUid = max(sMail.ulUid, m_ulLastUid);
- bNewMail = true;
-
- // Remember the first unseen message
- if (ulUnseen == 0 &&
- lpRows->aRow[ulMailnr].lpProps[FLAGS].ulPropTag == PR_MESSAGE_FLAGS && (lpRows->aRow[ulMailnr].lpProps[FLAGS].Value.ul & MSGFLAG_READ) == 0)
- ulUnseen = lstFolderMailEIDs.size()-1+1; // size()-1 = last offset, mail ID = position + 1
- continue;
- }
- // Check flags
- std::string strFlags = PropsToFlags(lpRows->aRow[ulMailnr].lpProps, lpRows->aRow[ulMailnr].cValues, lstFolderMailEIDs[iterUID->second].bRecent, false);
- if (lstFolderMailEIDs[iterUID->second].strFlags != strFlags) {
- // Flags have changed, notify it
- HrResponse(RESP_UNTAGGED, stringify(iterUID->second+1) + " FETCH (FLAGS (" + strFlags + "))");
- lstFolderMailEIDs[iterUID->second].strFlags = strFlags;
- }
- // We already had this message, remove it from setUIDs
- mapUIDs.erase(iterUID);
- }
- }
- // All messages left in mapUIDs have been deleted, so loop through the current list so we can
- // send the correct EXPUNGE calls; At the same time, count RECENT messages.
- ulMailnr = 0;
- while(ulMailnr < lstFolderMailEIDs.size()) {
- if (mapUIDs.find(lstFolderMailEIDs[ulMailnr].ulUid) != mapUIDs.cend()) {
- HrResponse(RESP_UNTAGGED, stringify(ulMailnr+1) + " EXPUNGE");
- lstFolderMailEIDs.erase(lstFolderMailEIDs.begin() + ulMailnr);
- continue;
- }
- if (lstFolderMailEIDs[ulMailnr].bRecent)
- ++ulRecent;
- ++iterMail;
- ++ulMailnr;
- }
-
- if (bNewMail || bInitialLoad) {
- HrResponse(RESP_UNTAGGED, stringify(lstFolderMailEIDs.size()) + " EXISTS");
- HrResponse(RESP_UNTAGGED, stringify(ulRecent) + " RECENT");
- }
- sort(lstFolderMailEIDs.begin(), lstFolderMailEIDs.end());
-
- // Save the max UID so that other session will not see the items as \Recent
- if(bResetRecent && ulRecent) {
- sPropMax.ulPropTag = PR_EC_IMAP_MAX_ID;
- sPropMax.Value.ul = m_ulLastUid;
- HrSetOneProp(folder, &sPropMax);
- }
- if (lpulUnseen)
- *lpulUnseen = ulUnseen;
- return hrSuccess;
- }
- /**
- * Return the IMAP Path for a given folder. Recursively recreates the
- * path using the parent iterator in the SFolder struct.
- *
- * @param[in] lpFolder Return the path name for this folder
- * @param[in] lstFolders The list of folders for this user, where lpFolder is a valid iterator in
- * @param[out] strPath The full imap path with separators in wide characters
- *
- * @return MAPI Error code
- * @retval MAPI_E_NOT_FOUND lpFolder is not a valid iterator in lstFolders
- */
- HRESULT IMAP::HrGetFolderPath(list<SFolder>::const_iterator lpFolder, const list<SFolder> &lstFolders, wstring &strPath) {
- if (lpFolder == lstFolders.cend())
- return MAPI_E_NOT_FOUND;
- if (lpFolder->lpParentFolder != lstFolders.cend()) {
- HRESULT hr = HrGetFolderPath(lpFolder->lpParentFolder, lstFolders,
- strPath);
- if (hr != hrSuccess)
- return hr;
- strPath += IMAP_HIERARCHY_DELIMITER;
- strPath += lpFolder->strFolderName;
- }
- return hrSuccess;
- }
- /**
- * Appends the given folder list with the given folder from the
- * EntryID with name. If bSubfolders is true, the given folder should
- * have subfolders, and these will recursively be processed.
- *
- * @param[out] lstFolders Add the given folder data to this list
- * @param[in] sEntryID Folder EntryID to add to list, and to process for subfolders
- * @param[in] strFolderName The name of the folder to add to the list
- * @param[in] lpParentFolder iterator in lstFolders to set as the parent folder
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrGetSubTree(list<SFolder> &folders, const SBinary &in_entry_id, const wstring &in_folder_name, list<SFolder>::const_iterator parent_folder)
- {
- if (lpSession == nullptr)
- return MAPI_E_CALL_FAILED;
- SFolder sfolder;
- sfolder.bActive = true;
- sfolder.bSpecialFolder = IsSpecialFolder(in_entry_id.cb, reinterpret_cast<ENTRYID *>(in_entry_id.lpb));
- sfolder.bMailFolder = false;
- sfolder.lpParentFolder = parent_folder;
- sfolder.strFolderName = in_folder_name;
- sfolder.bHasSubfolders = true;
- folders.push_front(sfolder);
- parent_folder = folders.cbegin();
- ULONG obj_type;
- object_ptr<IMAPIFolder> mapi_folder;
- HRESULT hr = lpSession->OpenEntry(in_entry_id.cb, reinterpret_cast<ENTRYID *>(in_entry_id.lpb), &IID_IMAPIFolder, 0, &obj_type, &~mapi_folder);
- if (hr != hrSuccess)
- return hr;
- enum { EID, PEID, NAME, IMAPID, SUBFOLDERS, CONTAINERCLASS, NUM_COLS };
- try {
- KFolder folder = mapi_folder.release();
- KTable table = folder.get_hierarchy_table(CONVENIENT_DEPTH);
- table.columns({PR_ENTRYID, PR_PARENT_ENTRYID, PR_DISPLAY_NAME_W, PR_EC_IMAP_ID, PR_SUBFOLDERS, PR_CONTAINER_CLASS_A});
- table.sort({{PR_DEPTH, KTable::ASCEND}});
- KRowSet rows = table.rows(-1, 0);
- for (unsigned int i = 0; i < rows.count(); ++i) {
- if (rows[i][IMAPID].prop_type() != PT_LONG) {
- lpLogger->Log(EC_LOGLEVEL_FATAL, "Server does not support PR_EC_IMAP_ID. Please update the storage server.");
- break;
- }
- try {
- string container_class = "";
- bool mailfolder = true;
- wstring foldername = rows[i][NAME].wstr();
- bool subfolders = rows[i][SUBFOLDERS].b();
- try {
- container_class = rows[i][CONTAINERCLASS].str();
- }
- catch (const KMAPIError &e) {
- if(e.code() != MAPI_E_NOT_FOUND && e.code() != MAPI_E_INVALID_TYPE)
- throw;
- }
- while (foldername.find(IMAP_HIERARCHY_DELIMITER) != string::npos)
- foldername.erase(foldername.find(IMAP_HIERARCHY_DELIMITER), 1);
- container_class = strToUpper(container_class);
- if (!container_class.empty() &&
- container_class.compare(0, 3, "IPM") != 0 &&
- container_class.compare("IPF.NOTE") != 0) {
- if (bOnlyMailFolders)
- continue;
- mailfolder = false;
- }
- auto entry_id = rows[i][EID].entry_id();
- auto parent_entry_id = rows[i][PEID].entry_id();
- list<SFolder>::const_iterator tmp_parent_folder = parent_folder;
- for (auto iter = folders.cbegin(); iter != folders.cend(); iter++) {
- if (iter->sEntryID == parent_entry_id) {
- tmp_parent_folder = iter;
- break;
- }
- }
- auto subscribed_iter = find(m_vSubscriptions.cbegin(), m_vSubscriptions.cend(), BinaryArray(entry_id));
- sfolder.bActive = subscribed_iter != m_vSubscriptions.cend();
- sfolder.bSpecialFolder = IsSpecialFolder(entry_id.cb(), entry_id.lpb());
- sfolder.bMailFolder = mailfolder;
- sfolder.lpParentFolder = tmp_parent_folder;
- sfolder.strFolderName = foldername;
- sfolder.sEntryID = entry_id;
- sfolder.bHasSubfolders = subfolders;
- folders.push_front(sfolder);
- }
- catch (const KMAPIError &e) {
- /* just continue */
- }
- }
- }
- catch (const KMAPIError &e) {
- return e.code();
- }
- return hrSuccess;
- }
- /**
- * Extends IMAP shortcuts into real full IMAP proptags, and returns an
- * vector of all separate and capitalized items.
- *
- * @param[in] strMsgDataItemNames String of wanted data items from a FETCH command.
- * @param[out] lstDataItems Vector of all separate items in the string in uppercase.
- *
- * @return hrSuccess
- */
- HRESULT IMAP::HrGetDataItems(string strMsgDataItemNames, vector<string> &lstDataItems) {
- // translate macro's
- strMsgDataItemNames = strToUpper(strMsgDataItemNames);
- if (strMsgDataItemNames.compare("ALL") == 0)
- strMsgDataItemNames = "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE";
- else if (strMsgDataItemNames.compare("FAST") == 0)
- strMsgDataItemNames = "FLAGS INTERNALDATE RFC822.SIZE";
- else if (strMsgDataItemNames.compare("FULL") == 0)
- strMsgDataItemNames = "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY";
- // split data items
- if (strMsgDataItemNames.size() > 1 && strMsgDataItemNames[0] == '(') {
- strMsgDataItemNames.erase(0,1);
- strMsgDataItemNames.erase(strMsgDataItemNames.size()-1, 1);
- }
- return HrSplitInput(strMsgDataItemNames, lstDataItems);
- }
- /**
- * Replaces all ; characters in the input to , characters.
- *
- * @param[in] strData The string to modify
- *
- * @return hrSuccess
- */
- HRESULT IMAP::HrSemicolonToComma(string &strData) {
- string::size_type ulPos = strData.find(";");
- while (ulPos != string::npos) {
- strData.replace(ulPos, 1, ",");
- ulPos = strData.find(";", ulPos + 1);
- }
- return hrSuccess;
- }
- /**
- * Do a FETCH based on table data for a specific list of
- * messages. Replies directly to the IMAP client with the result for
- * each mail given.
- *
- * @param[in] lstMails a full sorted list of emails to process
- * @param[in] lstDataItems vector of IMAP data items to send to the client
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrPropertyFetch(list<ULONG> &lstMails, vector<string> &lstDataItems) {
- HRESULT hr = hrSuccess;
- object_ptr<IMAPIFolder> lpFolder;
- rowset_ptr lpRows;
- LPSRow lpRow = NULL;
- LONG nRow = -1;
- ULONG ulDataItemNr;
- string strDataItem;
- SPropValue sPropVal;
- string strResponse;
- memory_ptr<SPropTagArray> lpPropTags;
- set<ULONG> setProps;
- int n;
- LPSPropValue lpProps;
- ULONG cValues;
- unsigned int ulReadAhead = 0;
- static constexpr const SizedSSortOrderSet(1, sSortUID) =
- {1, 0, 0, {{PR_EC_IMAP_ID, TABLE_SORT_ASCEND}}};
- bool bMarkAsRead = false;
- memory_ptr<ENTRYLIST> lpEntryList;
- if (strCurrentFolder.empty() || lpSession == nullptr)
- return MAPI_E_CALL_FAILED;
- // Setup the readahead length
- ulReadAhead = lstMails.size() > ROWS_PER_REQUEST ? ROWS_PER_REQUEST : lstMails.size();
- // Find out which properties we will be needing from the table. This should be kept in-sync
- // with the properties that are used in HrPropertyFetchRow()
- // Also check if we need to mark the message as read.
- for (ulDataItemNr = 0; ulDataItemNr < lstDataItems.size(); ++ulDataItemNr) {
- strDataItem = lstDataItems[ulDataItemNr];
- if (strDataItem.compare("FLAGS") == 0) {
- setProps.insert(PR_MESSAGE_FLAGS);
- setProps.insert(PR_FLAG_STATUS);
- setProps.insert(PR_MSG_STATUS);
- setProps.insert(PR_LAST_VERB_EXECUTED);
- } else if (strDataItem.compare("XAOL.SIZE") == 0) {
- // estimated size
- setProps.insert(PR_MESSAGE_SIZE);
- } else if (strDataItem.compare("INTERNALDATE") == 0) {
- setProps.insert(PR_MESSAGE_DELIVERY_TIME);
- setProps.insert(PR_CLIENT_SUBMIT_TIME);
- } else if (strDataItem.compare("BODY") == 0) {
- setProps.insert(PR_EC_IMAP_BODY);
- } else if (strDataItem.compare("BODYSTRUCTURE") == 0) {
- setProps.insert(PR_EC_IMAP_BODYSTRUCTURE);
- } else if (strDataItem.compare("ENVELOPE") == 0) {
- setProps.insert(m_lpsIMAPTags->aulPropTag[0]);
- } else if (strDataItem.compare("RFC822.SIZE") == 0) {
- // real size
- setProps.insert(PR_EC_IMAP_EMAIL_SIZE);
- } else if (strstr(strDataItem.c_str(), "HEADER") != NULL) {
- // RFC822.HEADER, BODY[HEADER or BODY.PEEK[HEADER
- setProps.insert(PR_TRANSPORT_MESSAGE_HEADERS_A);
- // if we have the full body, we can skip some hacks to make headers match with the otherwise regenerated version.
- setProps.insert(PR_EC_IMAP_EMAIL_SIZE);
- // this is where RFC822.HEADER seems to differ from BODY[HEADER] requests
- // (according to dovecot and courier)
- if (Prefix(strDataItem, "BODY["))
- bMarkAsRead = true;
- } else if (Prefix(strDataItem, "BODY") || Prefix(strDataItem, "RFC822")) {
- // we don't want PR_EC_IMAP_EMAIL in the table (size problem),
- // and it must be in sync with PR_EC_IMAP_EMAIL_SIZE anyway, so detect presence from size
- setProps.insert(PR_EC_IMAP_EMAIL_SIZE);
- if (strstr(strDataItem.c_str(), "PEEK") == NULL)
- bMarkAsRead = true;
- }
- }
- if (bMarkAsRead) {
- setProps.insert(PR_MESSAGE_FLAGS);
- setProps.insert(PR_FLAG_STATUS);
- setProps.insert(PR_MSG_STATUS);
- setProps.insert(PR_LAST_VERB_EXECUTED);
- hr = MAPIAllocateBuffer(sizeof(ENTRYLIST), &~lpEntryList);
- if (hr != hrSuccess)
- return hr;
- hr = MAPIAllocateMore(lstMails.size()*sizeof(SBinary), lpEntryList, (void**)&lpEntryList->lpbin);
- if (hr != hrSuccess)
- return hr;
- lpEntryList->cValues = 0;
- }
- if(!setProps.empty() && m_vTableDataColumns != lstDataItems) {
- ReleaseContentsCache();
- // Build an LPSPropTagArray
- hr = MAPIAllocateBuffer(CbNewSPropTagArray(setProps.size() + 1), &~lpPropTags);
- if(hr != hrSuccess)
- return hr;
- // Always get UID
- lpPropTags->aulPropTag[0] = PR_INSTANCE_KEY;
- n = 1;
- for (auto prop : setProps)
- lpPropTags->aulPropTag[n++] = prop;
- lpPropTags->cValues = setProps.size()+1;
- // Open the folder in question
- hr = HrFindFolder(strCurrentFolder, bCurrentFolderReadOnly, &~lpFolder);
- if (hr != hrSuccess)
- return hr;
- // Don't let the server cap the contents to 255 bytes, so our PR_TRANSPORT_MESSAGE_HEADERS is complete in the table
- hr = lpFolder->GetContentsTable(EC_TABLE_NOCAP | MAPI_DEFERRED_ERRORS, &m_lpTable);
- if (hr != hrSuccess)
- return hr;
- // Request our columns
- hr = m_lpTable->SetColumns(lpPropTags, TBL_BATCH);
- if (hr != hrSuccess)
- return hr;
-
- // Messages are usually requested in UID order, so sort the table in UID order too. This improves
- // the row prefetch hit ratio.
- hr = m_lpTable->SortTable(sSortUID, TBL_BATCH);
- if(hr != hrSuccess)
- return hr;
- m_vTableDataColumns = lstDataItems;
- } else if (bMarkAsRead) {
- // we need the folder to mark mails as read
- hr = HrFindFolder(strCurrentFolder, bCurrentFolderReadOnly, &~lpFolder);
- if (hr != hrSuccess)
- return hr;
- }
- if (m_lpTable) {
- hr = m_lpTable->SeekRow(BOOKMARK_BEGINNING, 0, NULL);
- if(hr != hrSuccess)
- return hr;
- }
- // Setup a find restriction that we modify for each row
- sPropVal.ulPropTag = PR_INSTANCE_KEY;
- ECPropertyRestriction sRestriction(RELOP_EQ, PR_INSTANCE_KEY, &sPropVal, ECRestriction::Cheap);
-
- // Loop through all requested rows, and get the data for each (FIXME: slow for large requests)
- for (auto mail_idx : lstMails) {
- const SPropValue *lpProp = NULL; // non-free // by default: no need to mark-as-read
- sPropVal.Value.bin.cb = lstFolderMailEIDs[mail_idx].sInstanceKey.cb;
- sPropVal.Value.bin.lpb = lstFolderMailEIDs[mail_idx].sInstanceKey.lpb;
- // We use a read-ahead mechanism here, reading 50 rows at a time.
- if (m_lpTable) {
- // First, see if the next row is somewhere in our already-read data
- lpRow = NULL;
- if (lpRows != nullptr)
- // use nRow to start checking where we left off
- for (unsigned int i = nRow + 1; i < lpRows->cRows; ++i)
- if(lpRows->aRow[i].lpProps[0].ulPropTag == PR_INSTANCE_KEY && BinaryArray(lpRows->aRow[i].lpProps[0].Value.bin) == BinaryArray(sPropVal.Value.bin)) {
- lpRow = &lpRows->aRow[i];
- nRow = i;
- break;
- }
- if(lpRow == NULL) {
- lpRows.reset();
-
- // Row was not found in our current data, request new data
- if (sRestriction.FindRowIn(m_lpTable, BOOKMARK_CURRENT, 0) == hrSuccess &&
- m_lpTable->QueryRows(ulReadAhead, 0, &~lpRows) == hrSuccess &&
- lpRows->cRows != 0) {
- // The row we want is the first returned row
- lpRow = &lpRows->aRow[0];
- nRow = 0;
- }
- }
-
- // Pass the row data for conversion
- if(lpRow) {
- // possebly add message to mark-as-read
- if (bMarkAsRead) {
- lpProp = PCpropFindProp(lpRow->lpProps, lpRow->cValues, PR_MESSAGE_FLAGS);
- if (!lpProp || (lpProp->Value.ul & MSGFLAG_READ) == 0) {
- lpEntryList->lpbin[lpEntryList->cValues].cb = lstFolderMailEIDs[mail_idx].sEntryID.cb;
- lpEntryList->lpbin[lpEntryList->cValues].lpb = lstFolderMailEIDs[mail_idx].sEntryID.lpb;
- ++lpEntryList->cValues;
- }
- }
- cValues = lpRow->cValues;
- lpProps = lpRow->lpProps;
- } else {
- cValues = 0;
- lpProps = NULL;
- }
- } else {
- // If the row is unavailable, or the table is not needed, do not pass any properties
- cValues = 0;
- lpProps = NULL;
- }
-
- // Fetch the row data
- if (HrPropertyFetchRow(lpProps, cValues, strResponse, mail_idx, (lpProp != NULL), lstDataItems) != hrSuccess) {
- lpLogger->Log(EC_LOGLEVEL_WARNING, "{?} Error fetching mail");
- } else {
- HrResponse(RESP_UNTAGGED, strResponse);
- }
- }
- if (lpEntryList && lpEntryList->cValues) {
- // mark unread messages as read
- hr = lpFolder->SetReadFlags(lpEntryList, 0, NULL, SUPPRESS_RECEIPT);
- if (FAILED(hr))
- return hr;
- }
- return hr;
- }
- /**
- * Does a FETCH based on row-data from a MAPI table. If the table data
- * is not sufficient, the PR_EC_IMAP_EMAIL property may be fetched
- * when present, or a full re-generation of the email will be
- * triggered to get the data.
- *
- * @param[in] lpProps Array of MAPI properties of a message
- * @param[in] cValues Number of properties in lpProps
- * @param[out] strResponse The string to send to the client
- * @param[in] ulMailnr Number of current email which we're creating a response for
- * @param[in] lstDataItems IMAP data items to add to the result string
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrPropertyFetchRow(LPSPropValue lpProps, ULONG cValues, string &strResponse, ULONG ulMailnr, bool bForceFlags, const vector<string> &lstDataItems)
- {
- HRESULT hr = hrSuccess;
- string strItem;
- string strParts;
- string::size_type ulPos;
- char szBuffer[IMAP_RESP_MAX + 1];
- IMessage *lpMessage = NULL;
- ULONG ulObjType = 0;
- sending_options sopt;
- imopt_default_sending_options(&sopt);
- sopt.no_recipients_workaround = true; // do not stop processing mail on empty recipient table
- sopt.alternate_boundary = const_cast<char *>("=_ZG_static");
- sopt.force_utf8 = parseBool(lpConfig->GetSetting("imap_generate_utf8"));
- sopt.ignore_missing_attachments = true;
- string strMessage;
- string strMessagePart;
- unsigned int ulCount = 0;
- ostringstream oss;
- string strFlags;
- bool bSkipOpen = true;
- vector<string> vProps;
-
- // Response always starts with "<id> FETCH ("
- snprintf(szBuffer, IMAP_RESP_MAX, "%u FETCH (", ulMailnr + 1);
- strResponse = szBuffer;
- // rules to open the message:
- // 1. BODY requested and not present in table (generate)
- // 2. BODYSTRUCTURE requested and not present in table (generate)
- // 3. ENVELOPE requested and not present in table
- // 4. BODY* or body part requested
- // 5. RFC822* requested
- // and ! cached
- for (auto iFetch = lstDataItems.cbegin();
- bSkipOpen && iFetch != lstDataItems.cend(); ++iFetch)
- {
- if (iFetch->compare("BODY") == 0)
- bSkipOpen = PCpropFindProp(lpProps, cValues, PR_EC_IMAP_BODY) != NULL;
- else if (iFetch->compare("BODYSTRUCTURE") == 0)
- bSkipOpen = PCpropFindProp(lpProps, cValues, PR_EC_IMAP_BODYSTRUCTURE) != NULL;
- else if (iFetch->compare("ENVELOPE") == 0)
- bSkipOpen = PCpropFindProp(lpProps, cValues, m_lpsIMAPTags->aulPropTag[0]) != NULL;
- else if (iFetch->compare("RFC822.SIZE") == 0)
- bSkipOpen = PCpropFindProp(lpProps, cValues, PR_EC_IMAP_EMAIL_SIZE) != NULL;
- else if (strstr(iFetch->c_str(), "HEADER") != NULL)
- // we can only use PR_TRANSPORT_MESSAGE_HEADERS when we have the full email.
- bSkipOpen = (PCpropFindProp(lpProps, cValues, PR_TRANSPORT_MESSAGE_HEADERS_A) != NULL &&
- PCpropFindProp(lpProps, cValues, PR_EC_IMAP_EMAIL_SIZE) != NULL);
- // full/partial body fetches, or size
- else if (Prefix(*iFetch, "BODY") || Prefix(*iFetch, "RFC822"))
- bSkipOpen = false;
- }
- if (!bSkipOpen && m_ulCacheUID != lstFolderMailEIDs[ulMailnr].ulUid)
- // ignore error, we can't print an error halfway to the imap client
- lpSession->OpenEntry(lstFolderMailEIDs[ulMailnr].sEntryID.cb, (LPENTRYID) lstFolderMailEIDs[ulMailnr].sEntryID.lpb,
- &IID_IMessage, MAPI_DEFERRED_ERRORS, &ulObjType, (LPUNKNOWN *) &lpMessage);
- // Handle requested properties
- for (const auto &item : lstDataItems) {
- if (item.compare("FLAGS") == 0) {
- // if flags were already set from message, skip this version.
- if (strFlags.empty()) {
- strFlags = "FLAGS (";
- strFlags += PropsToFlags(lpProps, cValues, lstFolderMailEIDs[ulMailnr].bRecent, bForceFlags);
- strFlags += ")";
- }
- } else if (item.compare("XAOL.SIZE") == 0) {
- auto lpProp = PCpropFindProp(lpProps, cValues, PR_MESSAGE_SIZE);
- vProps.push_back(item);
- vProps.push_back(lpProp ? stringify(lpProp->Value.ul) : "NIL");
- } else if (item.compare("INTERNALDATE") == 0) {
- vProps.push_back(item);
- auto lpProp = PCpropFindProp(lpProps, cValues, PR_MESSAGE_DELIVERY_TIME);
- if (!lpProp)
- lpProp = PCpropFindProp(lpProps, cValues, PR_CLIENT_SUBMIT_TIME);
- if (!lpProp)
- lpProp = PCpropFindProp(lpProps, cValues, PR_CREATION_TIME);
- if (lpProp != NULL)
- vProps.push_back("\"" + FileTimeToString(lpProp->Value.ft) + "\"");
- else
- vProps.push_back("NIL");
- } else if (item.compare("UID") == 0) {
- vProps.push_back(item);
- vProps.push_back(stringify(lstFolderMailEIDs[ulMailnr].ulUid));
- } else if (item.compare("ENVELOPE") == 0) {
- auto lpProp = PCpropFindProp(lpProps, cValues, m_lpsIMAPTags->aulPropTag[0]);
- if (lpProp) {
- vProps.push_back(item);
- vProps.push_back(string("(") + lpProp->Value.lpszA + ")");
- } else if (lpMessage) {
- string strEnvelope;
- HrGetMessageEnvelope(strEnvelope, lpMessage);
- vProps.push_back(strEnvelope); // @note contains ENVELOPE (...)
- } else {
- vProps.push_back(item);
- vProps.push_back("NIL");
- }
- } else if (bSkipOpen && item.compare("BODY") == 0) {
- // table version
- auto lpProp = PCpropFindProp(lpProps, cValues, PR_EC_IMAP_BODY);
- vProps.push_back(item);
- vProps.push_back(lpProp ? lpProp->Value.lpszA : "NIL");
- } else if (bSkipOpen && item.compare("BODYSTRUCTURE") == 0) {
- // table version
- auto lpProp = PCpropFindProp(lpProps, cValues, PR_EC_IMAP_BODYSTRUCTURE);
- vProps.push_back(item);
- vProps.push_back(lpProp ? lpProp->Value.lpszA : "NIL");
- } else if (Prefix(item, "BODY") || Prefix(item, "RFC822")) {
- // the only exceptions when we don't need to generate anything yet.
- if (item.compare("RFC822.SIZE") == 0) {
- auto lpProp = PCpropFindProp(lpProps, cValues, PR_EC_IMAP_EMAIL_SIZE);
- if (lpProp) {
- vProps.push_back(item);
- vProps.push_back(stringify(lpProp->Value.ul));
- continue;
- }
- }
- // mapping with RFC822 to BODY[* requests
- strItem = item;
- if (strItem.compare("RFC822") == 0)
- strItem = "BODY[]";
- else if (strItem.compare("RFC822.TEXT") == 0)
- strItem = "BODY[TEXT]";
- else if (strItem.compare("RFC822.HEADER") == 0)
- strItem = "BODY[HEADER]";
- // structure only, take shortcut if we have it
- if (strItem.find('[') == string::npos) {
- /* RFC 3501 6.4.5:
- * BODY
- * Non-extensible form of BODYSTRUCTURE.
- * BODYSTRUCTURE
- * The [MIME-IMB] body structure of the message.
- */
- const SPropValue *lpProp;
- if (item.length() > 4)
- lpProp = PCpropFindProp(lpProps, cValues, PR_EC_IMAP_BODYSTRUCTURE);
- else
- lpProp = PCpropFindProp(lpProps, cValues, PR_EC_IMAP_BODY);
- if (lpProp) {
- vProps.push_back(item);
- vProps.push_back(lpProp->Value.lpszA);
- continue;
- }
- // data not available in table, need to regenerate.
- }
- strMessage.clear();
- sopt.headers_only = strstr(strItem.c_str(), "HEADER") != NULL;
- if (m_ulCacheUID == lstFolderMailEIDs[ulMailnr].ulUid) {
- // Get message from cache
- strMessage = m_strCache;
- } else {
- // We need to send headers or a body(part) to the client.
- // For some clients, we need to make sure that headers match the bodies,
- // So if we don't have the full email in the database, we must fix the headers to match
- // vmime regenerated messages.
- if (sopt.headers_only && bSkipOpen) {
- auto lpProp = PCpropFindProp(lpProps, cValues, PR_TRANSPORT_MESSAGE_HEADERS_A);
- if (lpProp != NULL)
- strMessage = lpProp->Value.lpszA;
- else
- // still need to convert message
- hr = MAPI_E_NOT_FOUND;
- } else {
- // If we have the full body, download that property
- auto lpProp = PCpropFindProp(lpProps, cValues, PR_EC_IMAP_EMAIL_SIZE);
- if (lpProp) {
- // we have PR_EC_IMAP_EMAIL_SIZE, so we also have PR_EC_IMAP_EMAIL
- object_ptr<IStream> lpStream;
- hr = lpMessage->OpenProperty(PR_EC_IMAP_EMAIL, &IID_IStream, 0, 0, &~lpStream);
- if (hr == hrSuccess)
- hr = Util::HrStreamToString(lpStream, strMessage);
- } else {
- hr = MAPI_E_NOT_FOUND;
- }
- }
- // no full imap email in database available, so regenerate all
- if (hr != hrSuccess) {
- assert(lpMessage);
- if (oss.tellp() == ostringstream::pos_type(0) && // already converted in previous loop?
- (lpMessage == NULL || IMToINet(lpSession, lpAddrBook, lpMessage, oss, sopt) != hrSuccess)) {
- vProps.push_back(item);
- vProps.push_back("NIL");
- lpLogger->Log(EC_LOGLEVEL_WARNING, "Error in generating message %d for user %ls in folder %ls", ulMailnr+1, m_strwUsername.c_str(), strCurrentFolder.c_str());
- continue;
- }
- strMessage = oss.str();
- hr = hrSuccess;
- // @todo save message and all generated crap and when not headers only
- }
- // Cache the generated message
- if(!sopt.headers_only) {
- m_ulCacheUID = lstFolderMailEIDs[ulMailnr].ulUid;
- m_strCache = strMessage;
- }
- }
- if (item.compare("RFC822.SIZE") == 0) {
- // We must return the real size, since clients use this when using chunked mode to download the full message
- vProps.push_back(item);
- vProps.push_back(stringify(strMessage.size()));
- continue;
- }
- if (item.compare("BODY") == 0 || item.compare("BODYSTRUCTURE") == 0) {
- string strData;
- HrGetBodyStructure(item.length() > 4, strData, strMessage);
- vProps.push_back(item);
- vProps.push_back(strData);
- continue;
- }
- /* RFC 3501 6.4.5:
- * BODY[<section>]<<partial>>
- * The text of a particular body section, without boundaries.
- * BODY.PEEK[<section>]<<partial>>
- * An alternate form of BODY[<section>] that does not implicitly
- * set the \Seen flag.
- */
- if (strstr(strItem.c_str(), "[]") != NULL) {
- // Nasty: eventhough the client requests .PEEK, it may not be present in the reply.
- string strReply = item;
- ulPos = strReply.find(".PEEK");
- if (ulPos != string::npos)
- strReply.erase(ulPos, strlen(".PEEK"));
- // Nasty: eventhough the client requests <12345.12345>, it may not be present in the reply.
- ulPos = strReply.rfind('<');
- if (ulPos != string::npos)
- strReply.erase(ulPos, string::npos);
- vProps.push_back(strReply);
- // Handle BODY[] and RFC822 (entire message)
- strMessagePart = strMessage;
- } else {
- // Handle BODY[subparts]
- // BODY[subpart], strParts = <subpart> (so "1.2.3" or "3.HEADER" or "TEXT" etc)
- ulPos = strItem.find("[");
- if (ulPos != string::npos)
- strParts = strItem.substr(ulPos + 1);
- ulPos = strParts.find("]");
- if (ulPos != string::npos)
- strParts.erase(ulPos);
- if (Prefix(item, "BODY"))
- vProps.push_back("BODY[" + strParts + "]");
- else
- vProps.push_back("RFC822." + strParts);
- // Get the correct message part (1.2.3, TEXT, HEADER, 1.2.3.TEXT, 1.2.3.HEADER)
- HrGetMessagePart(strMessagePart, strMessage, strParts);
- }
- // Process byte-part request ( <12345.12345> ) for BODY
- ulPos = strItem.rfind('<');
- if (ulPos != string::npos) {
- strParts = strItem.substr(ulPos + 1, strItem.size() - ulPos - 2);
- ulPos = strParts.find('.');
- if (ulPos != string::npos) {
- ulCount = strtoul(strParts.substr(0, ulPos).c_str(), NULL, 0);
- ulPos = strtoul(strParts.substr(ulPos + 1).c_str(), NULL, 0);
- } else {
- ulCount = strtoul(strParts.c_str(), NULL, 0);
- ulPos = strMessagePart.size();
- }
- if (ulCount > strMessagePart.size()) {
- strMessagePart.clear();
- } else if (ulCount + ulPos > strMessagePart.size()) {
- strMessagePart.erase(0, ulCount);
- } else {
- strMessagePart.erase(0, ulCount);
- strMessagePart.erase(ulPos);
- }
- snprintf(szBuffer, IMAP_RESP_MAX, "<%u>", ulCount);
- vProps.back() += szBuffer;
- }
- if (strMessagePart.empty()) {
- vProps.push_back("NIL");
- } else {
- // Output actual data
- snprintf(szBuffer, IMAP_RESP_MAX, "{%u}\r\n", (ULONG)strMessagePart.size());
- vProps.push_back(szBuffer);
- vProps.back() += strMessagePart;
- }
- } else {
- // unknown item
- vProps.push_back(item);
- vProps.push_back("NIL");
- }
- }
- if (bForceFlags && strFlags.empty()) {
- strFlags = "FLAGS (";
- strFlags += PropsToFlags(lpProps, cValues, lstFolderMailEIDs[ulMailnr].bRecent, bForceFlags);
- strFlags += ")";
- }
- // Output flags if modified
- if (!strFlags.empty())
- vProps.push_back(std::move(strFlags));
- strResponse += kc_join(vProps, " ");
- strResponse += ")";
- if(lpMessage)
- lpMessage->Release();
- return hr;
- }
- /**
- * Returns a recipient block for the envelope request. Format:
- * (("fullname" NIL "email name" "email domain")(...))
- *
- * @param[in] lpRows recipient table rows
- * @param[in] ulType recipient type to print
- * @param[in] strCharset charset for the fullname
- * @return string containing the To/Cc/Bcc recipient data
- */
- std::string IMAP::HrEnvelopeRecipients(LPSRowSet lpRows, ULONG ulType, std::string& strCharset, bool bIgnore)
- {
- ULONG ulCount;
- std::string strResponse;
- std::string::size_type ulPos;
- enum { EMAIL_ADDRESS, DISPLAY_NAME, RECIPIENT_TYPE, ADDRTYPE, ENTRYID, NUM_COLS };
- strResponse = "(";
- for (ulCount = 0; ulCount < lpRows->cRows; ++ulCount) {
- SPropValue *pr = lpRows->aRow[ulCount].lpProps;
- if (pr[RECIPIENT_TYPE].Value.ul != ulType)
- continue;
- /*
- * """The fields of an address structure are in the following
- * order: personal name, SMTP at-domain-list (source route),
- * mailbox name, and host name.""" RFC 3501 §2.3.5 p.76.
- */
- strResponse += "(";
- if (pr[DISPLAY_NAME].ulPropTag == PR_DISPLAY_NAME_W)
- strResponse += EscapeString(pr[DISPLAY_NAME].Value.lpszW, strCharset, bIgnore);
- else
- strResponse += "NIL";
- strResponse += " NIL ";
- bool has_email = pr[EMAIL_ADDRESS].ulPropTag == PR_EMAIL_ADDRESS_A;
- bool za_addr = pr[ADDRTYPE].ulPropTag == PR_ADDRTYPE_W &&
- wcscmp(pr[ADDRTYPE].Value.lpszW, L"ZARAFA") == 0;
- std::string strPart;
- if (has_email && za_addr) {
- std::wstring name, type, email;
- HRESULT ret;
- ret = HrGetAddress(lpAddrBook, pr, NUM_COLS,
- PR_ENTRYID, PR_DISPLAY_NAME_W, PR_ADDRTYPE_W,
- PR_EMAIL_ADDRESS_A, name, type, email);
- if (ret == hrSuccess)
- strPart = convert_to<std::string>(email);
- } else if (has_email) {
- /* treat all non-ZARAFA cases as "SMTP" */
- strPart = pr[EMAIL_ADDRESS].Value.lpszA;
- }
- if (strPart.length() > 0) {
- ulPos = strPart.find("@");
- if (ulPos != string::npos) {
- strResponse += EscapeStringQT(strPart.substr(0, ulPos));
- strResponse += " ";
- strResponse += EscapeStringQT(strPart.substr(ulPos + 1));
- } else {
- strResponse += EscapeStringQT(strPart);
- strResponse += " NIL";
- }
- } else {
- strResponse += "NIL NIL";
- }
- strResponse += ") ";
- }
- if (strResponse.compare(strResponse.size() - 1, 1, " ") == 0) {
- strResponse.resize(strResponse.size() - 1);
- strResponse += ") ";
- } else {
- // no recipients at all
- strResponse.resize(strResponse.size() - 1);
- strResponse += "NIL ";
- }
- return strResponse;
- }
- /**
- * Returns a sender block for the envelope request. Format:
- * (("fullname" NIL "email name" "email domain"))
- *
- * @param[in] lpMessage GetProps object
- * @param[in] ulTagName Proptag to get for the fullname
- * @param[in] ulTagEmail Proptag to get for the email address
- * @param[in] strCharset Charset for the fullname
- * @return string containing the From/Sender/Reply-To envelope data
- */
- std::string IMAP::HrEnvelopeSender(LPMESSAGE lpMessage, ULONG ulTagName, ULONG ulTagEmail, std::string& strCharset, bool bIgnore)
- {
- HRESULT hr = hrSuccess;
- std::string strResponse;
- std::string strPart;
- std::string::size_type ulPos;
- memory_ptr<SPropValue> lpPropValues;
- ULONG ulProps;
- SizedSPropTagArray(2, sPropTags) = { 2, {ulTagName, ulTagEmail} };
- hr = lpMessage->GetProps(sPropTags, 0, &ulProps, &~lpPropValues);
- strResponse = "((";
- if (!FAILED(hr) && PROP_TYPE(lpPropValues[0].ulPropTag) != PT_ERROR)
- strResponse += EscapeString(lpPropValues[0].Value.lpszW, strCharset, bIgnore);
- else
- strResponse += "NIL";
- strResponse += " NIL ";
- if (!FAILED(hr) && PROP_TYPE(lpPropValues[1].ulPropTag) != PT_ERROR) {
- strPart = lpPropValues[1].Value.lpszA;
- ulPos = strPart.find("@", 0);
- if (ulPos != string::npos) {
- strResponse += EscapeStringQT(strPart.substr(0, ulPos));
- strResponse += " ";
- strResponse += EscapeStringQT(strPart.substr(ulPos + 1));
- } else {
- strResponse += EscapeStringQT(strPart);
- strResponse += " NIL";
- }
- } else {
- strResponse += "NIL";
- }
- strResponse += ")) ";
- return strResponse;
- }
- /**
- * Returns the IMAP ENVELOPE string of a specific email. Since this
- * doesn't come from vmime, the values in this response may differ
- * from other requests.
- *
- * @param[out] strResponse The ENVELOPE answer will be concatenated to this string
- * @param[in] lpMessage The MAPI message object
- * @return MAPI Error code
- */
- HRESULT IMAP::HrGetMessageEnvelope(string &strResponse, LPMESSAGE lpMessage) {
- HRESULT hr = hrSuccess;
- memory_ptr<SPropValue> lpPropVal, lpInternetCPID;
- const char *lpszCharset = NULL;
- string strCharset;
- bool bIgnoreCharsetErrors = false;
- object_ptr<IMAPITable> lpTable;
- rowset_ptr lpRows;
- static constexpr const SizedSPropTagArray(5, spt) =
- {5, {PR_EMAIL_ADDRESS_A, PR_DISPLAY_NAME_W, PR_RECIPIENT_TYPE,
- PR_ADDRTYPE_W, PR_ENTRYID}};
- if (lpMessage == nullptr)
- return MAPI_E_CALL_FAILED;
- // Get the outgoing charset we want to be using
- // @todo, add gateway force_utf8 option
- if (!parseBool(lpConfig->GetSetting("imap_generate_utf8")) &&
- HrGetOneProp(lpMessage, PR_INTERNET_CPID, &~lpInternetCPID) == hrSuccess &&
- HrGetCharsetByCP(lpInternetCPID->Value.ul, &lpszCharset) == hrSuccess)
- {
- strCharset = lpszCharset;
- bIgnoreCharsetErrors = true;
- } else {
- // default to UTF-8 if not set
- strCharset = "UTF-8";
- }
- strResponse += "ENVELOPE (";
- // date string
- if (HrGetOneProp(lpMessage, PR_CLIENT_SUBMIT_TIME, &~lpPropVal) == hrSuccess ||
- HrGetOneProp(lpMessage, PR_MESSAGE_DELIVERY_TIME, &~lpPropVal) == hrSuccess) {
- strResponse += "\"";
- strResponse += FileTimeToString(lpPropVal->Value.ft);
- strResponse += "\" ";
- } else {
- strResponse += "NIL ";
- }
- // subject
- if (HrGetOneProp(lpMessage, PR_SUBJECT_W, &~lpPropVal) == hrSuccess)
- strResponse += EscapeString(lpPropVal->Value.lpszW, strCharset, bIgnoreCharsetErrors);
- strResponse += " ";
- // from
- strResponse += HrEnvelopeSender(lpMessage, PR_SENT_REPRESENTING_NAME_W, PR_SENT_REPRESENTING_EMAIL_ADDRESS_A, strCharset, bIgnoreCharsetErrors);
- // sender
- strResponse += HrEnvelopeSender(lpMessage, PR_SENDER_NAME_W, PR_SENDER_EMAIL_ADDRESS_A, strCharset, bIgnoreCharsetErrors);
- // reply-to, @fixme use real reply-to info from PR_REPLY_RECIPIENT_ENTRIES
- strResponse += HrEnvelopeSender(lpMessage, PR_SENT_REPRESENTING_NAME_W, PR_SENT_REPRESENTING_EMAIL_ADDRESS_A, strCharset, bIgnoreCharsetErrors);
- // recipients
- hr = lpMessage->GetRecipientTable(0, &~lpTable);
- if (hr != hrSuccess)
- goto recipientsdone;
- hr = lpTable->SetColumns(spt, 0);
- if (hr != hrSuccess)
- goto recipientsdone;
- hr = lpTable->QueryRows(-1, 0, &~lpRows);
- if (hr != hrSuccess)
- goto recipientsdone;
- strResponse += HrEnvelopeRecipients(lpRows, MAPI_TO, strCharset, bIgnoreCharsetErrors);
- strResponse += HrEnvelopeRecipients(lpRows, MAPI_CC, strCharset, bIgnoreCharsetErrors);
- strResponse += HrEnvelopeRecipients(lpRows, MAPI_BCC, strCharset, bIgnoreCharsetErrors);
- recipientsdone:
- if (hr != hrSuccess) {
- strResponse += "NIL NIL NIL ";
- hr = hrSuccess;
- }
- // in reply to
- if (HrGetOneProp(lpMessage, PR_IN_REPLY_TO_ID_A, &~lpPropVal) == hrSuccess)
- strResponse += EscapeStringQT(lpPropVal->Value.lpszA);
- else
- strResponse += "NIL";
- strResponse += " ";
- // internet message id
- if (HrGetOneProp(lpMessage, PR_INTERNET_MESSAGE_ID_A, &~lpPropVal) == hrSuccess)
- strResponse += EscapeStringQT(lpPropVal->Value.lpszA);
- else
- strResponse += "NIL";
- strResponse += ")";
- return hrSuccess;
- }
- /**
- * Returns IMAP flags for a given message
- *
- * @param[out] strResponse the FLAGS reply for the given message
- * @param[in] lpMessage the MAPI message to get the IMAP flags for
- * @param[in] bRecent mark this message as recent
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrGetMessageFlags(string &strResponse, LPMESSAGE lpMessage, bool bRecent) {
- memory_ptr<SPropValue> lpProps;
- ULONG cValues;
- static constexpr const SizedSPropTagArray(4, sptaFlagProps) =
- {4, {PR_MESSAGE_FLAGS, PR_FLAG_STATUS, PR_MSG_STATUS,
- PR_LAST_VERB_EXECUTED}};
-
- if (lpMessage == nullptr)
- return MAPI_E_CALL_FAILED;
- HRESULT hr = lpMessage->GetProps(sptaFlagProps, 0, &cValues, &~lpProps);
- if (FAILED(hr))
- return hr;
- strResponse += "FLAGS (" + PropsToFlags(lpProps, cValues, bRecent, false) + ")";
- return hrSuccess;
- }
- /*
- * RFC 3501, section 6.4.5:
- *
- * BODY[<section>]<<partial>>
- *
- * The text of a particular body section. The section
- * specification is a set of zero or more part specifiers
- * delimited by periods. A part specifier is either a part number
- * or one of the following: HEADER, HEADER.FIELDS,
- * HEADER.FIELDS.NOT, MIME, and TEXT. An empty section
- * specification refers to the entire message, including the
- * header.
- *
- * --- end of RFC text
- *
- * Please note that there is a difference between getting MIME and HEADER:
- * - HEADER and TEXT are only used in the top-level object OR an embedded message/rfc822 message
- * - MIME are the Content-* headers for the MIME part, NEVER the header for a message/rfc822 message
- * - The 'whole' part is HEADER + TEXT for toplevel and message/rfc822 parts
- * - The 'whole' part does NOT include the MIME-IMB headers part
- *
- */
- /**
- * Returns a body part of an RFC 2822 message
- *
- * @param[out] strMessagePart The requested message part
- * @param[in] strMessage The full mail to scan for the part
- * @param[in] strPartName IMAP request identifying a part in strMessage
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrGetMessagePart(string &strMessagePart, string &strMessage, string strPartName) {
- string::size_type ulPos;
- unsigned long int ulPartnr;
- string strHeaders;
- string strBoundary;
- size_t ulHeaderBegin;
- size_t ulHeaderEnd;
- size_t ulCounter;
- const char *ptr, *end;
- if (strPartName.find_first_of("123456789") == 0) {
- // @todo rewrite without copying strings
- std::string strNextPart;
- // BODY[1] or BODY[1.2] etc
-
- // Find subsection
- ulPos = strPartName.find(".");
- if (ulPos == string::npos) // first section
- ulPartnr = strtoul(strPartName.c_str(), NULL, 0);
- else // sub section
- ulPartnr = strtoul(strPartName.substr(0, ulPos).c_str(), NULL, 0);
- // Find the correct part
- end = str_ifind((char*)strMessage.c_str(), "\r\n\r\n");
- ptr = str_ifind((char*)strMessage.c_str(), "boundary=");
- if (ptr && end && ptr < end) {
- ulHeaderBegin = std::distance(strMessage.c_str(), ptr) + strlen("boundary=");
- if (strMessage[ulHeaderBegin] == '"') {
- ++ulHeaderBegin;
- // space in boundary is a possebility.
- ulHeaderEnd = strMessage.find_first_of("\"", ulHeaderBegin);
- } else {
- ulHeaderEnd = strMessage.find_first_of(" ;\t\r\n", ulHeaderBegin);
- }
- if (ulHeaderEnd != string::npos) {
- // strBoundary is the boundary we are looking for
- strBoundary = strMessage.substr(ulHeaderBegin, ulHeaderEnd - ulHeaderBegin);
- // strHeaders is what we are looking for
- strHeaders = (string) "\r\n--" + strBoundary + "\r\n"; //Skip always the end header
- ulHeaderBegin = strMessage.find(strHeaders, ulHeaderBegin);
- // Find the section/part by looking for the Nth boundary string
- for (ulCounter = 0; ulCounter < ulPartnr && ulHeaderBegin != string::npos; ++ulCounter) {
- ulHeaderBegin += strHeaders.size();
- ulHeaderEnd = ulHeaderBegin;
- ulHeaderBegin = strMessage.find(strHeaders, ulHeaderBegin);
- }
- if (ulHeaderBegin != string::npos) {
- // Found it, discard data after and before the part we want
- strMessage.erase(ulHeaderBegin);
- strMessage.erase(0, ulHeaderEnd);
- } else {
- // Didn't find it, see if we can find the trailing boundary
- ulHeaderBegin = strMessage.find((string) "\r\n--" + strBoundary + "--\r\n", ulHeaderEnd);
- if(ulHeaderBegin != string::npos) {
- // If found, output everything up to the trailing boundary
- strMessage.erase(ulHeaderBegin);
- strMessage.erase(0, ulHeaderEnd);
- } else {
- // Otherwise, treat the rest of the message as data
- strMessage.erase(0, ulHeaderEnd);
- }
- }
- }
- }
- // We now have the entire MIME part in strMessage, decide what to do with it
- if (ulPos != string::npos) {
- // There are sub sections, see what we want to do
- strNextPart = strPartName.substr(ulPos+1);
- if(strNextPart.compare("MIME") == 0) {
- // Handle MIME request
- ulPos = strMessage.find("\r\n\r\n");
- if (ulPos != string::npos)
- strMessagePart = strMessage.substr(0, ulPos+4); // include trailing \r\n\r\n (+4)
- else
- // Only headers in the message
- strMessagePart = strMessage + "\r\n\r\n";
- // All done
- return hrSuccess;
- } else if(strNextPart.find_first_of("123456789") == 0) {
- // Handle Subpart
- HrGetMessagePart(strMessagePart, strMessage, strNextPart);
- // All done
- return hrSuccess;
- }
- }
-
- // Handle any other request (HEADER, TEXT or 'empty'). This means we first skip the MIME-IMB headers
- // and process the rest from there.
- ulPos = strMessage.find("\r\n\r\n");
- if (ulPos != string::npos)
- strMessage.erase(0, ulPos + 4);
- else
- // The message only has headers ?
- strMessage.clear();
- // Handle HEADER and TEXT if requested
- if (!strNextPart.empty())
- HrGetMessagePart(strMessagePart, strMessage, strNextPart);
- else
- // Swap to conserve memory: Original: strMessagePart = strMessage
- swap(strMessagePart, strMessage);
- } else if (strPartName.compare("TEXT") == 0) {
- // Everything except for the headers
- ulPos = strMessage.find("\r\n\r\n");
- if (ulPos != string::npos) {
- // Swap for less memory usage. Original: strMessagePart = strMessage.substr(ulPos+4)
- strMessage.erase(0,ulPos+4);
- swap(strMessage, strMessagePart);
- } else {
- // The message only has headers ?
- strMessagePart.clear();
- }
- } else if (strPartName.compare("HEADER") == 0) {
- // Only the headers
- ulPos = strMessage.find("\r\n\r\n");
- if (ulPos != string::npos) {
- // Swap for less memory usage. Original: strMessagePart = strMessage.substr(0, ulPos+4);
- strMessage.erase(ulPos+4, strMessage.size() - (ulPos+4));
- swap(strMessagePart, strMessage);
- } else {
- // Only headers in the message
- strMessagePart = strMessage + "\r\n\r\n";
- }
- } else if (Prefix(strPartName, "HEADER.FIELDS")) {
- /* RFC 3501, section 6.4.5
- *
- * HEADER.FIELDS and HEADER.FIELDS.NOT are followed by a list of
- * field-name (as defined in [RFC-2822]) names, and return a
- * subset of the header.
- *
- * eg. HEADER.FIELDS (SUBJECT TO)
- * eg. HEADER.FIELDS.NOT (SUBJECT)
- */
- bool bNot = Prefix(strPartName, "HEADER.FIELDS.NOT");
- list<pair<string, string> > lstFields;
- string strFields;
-
- // Parse headers in message
- HrParseHeaders(strMessage, lstFields);
-
- // Get (<fields>)
- HrGetSubString(strFields, strPartName, "(", ")");
-
- strMessagePart.clear();
-
- if(bNot) {
- set<string> setFields;
- // Get fields as set
- HrTokenize(setFields, strFields);
-
- // Output all headers except those specified
- for (const auto &field : lstFields) {
- std::string strFieldUpper = field.first;
- strFieldUpper = strToUpper(strFieldUpper);
- if (setFields.find(strFieldUpper) != setFields.cend())
- continue;
- strMessagePart += field.first + ": " + field.second + "\r\n";
- }
- } else {
- vector<string> lstReqFields;
- std::unordered_set<std::string> seen;
- // Get fields as vector
- lstReqFields = tokenize(strFields, " ");
-
- // Output headers specified, in order of field set
- for (const auto &reqfield : lstReqFields) {
- if (!seen.insert(reqfield).second)
- continue;
- for (const auto &field : lstFields) {
- if (!CaseCompare(reqfield, field.first))
- continue;
- strMessagePart += field.first + ": " + field.second + "\r\n";
- break;
- }
- }
- }
- // mark end-of-headers
- strMessagePart += "\r\n";
- } else {
- strMessagePart = "NIL";
- }
- return hrSuccess;
- }
- /**
- * Convert a sequence number to its actual number. It will either
- * return a number or a UID, depending on the input.
- * A special treatment for
- *
- * @param[in] szNr a number of the sequence input
- * @param[in] bUID sequence input are UID numbers or not
- *
- * @return the number corresponding to the input.
- */
- ULONG IMAP::LastOrNumber(const char *szNr, bool bUID)
- {
- if (*szNr != '*')
- return atoui(szNr);
- if (!bUID)
- return lstFolderMailEIDs.size();
- if (lstFolderMailEIDs.empty())
- return 0; // special case: return an "invalid" number
- else
- return lstFolderMailEIDs.back().ulUid;
- }
- /**
- * Convert a UID sequence set into a MAPI restriction.
- *
- * @param[in] strSeqSet The sequence set with uids given by the client
- * @param[out] lppRestriction The MAPI restriction to get all messages matching the sequence
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrSeqUidSetToRestriction(const string &strSeqSet,
- std::unique_ptr<ECRestriction> &ret)
- {
- vector<string> vSequences;
- string::size_type ulPos = 0;
- SPropValue sProp;
- SPropValue sPropEnd;
- if (strSeqSet.empty()) {
- // no restriction
- ret.reset();
- return hrSuccess;
- }
- sProp.ulPropTag = PR_EC_IMAP_ID;
- sPropEnd.ulPropTag = PR_EC_IMAP_ID;
- vSequences = tokenize(strSeqSet, ',');
- auto rst = new ECOrRestriction();
- for (ULONG i = 0; i < vSequences.size(); ++i) {
- ulPos = vSequences[i].find(':');
- if (ulPos == string::npos) {
- // single number
- sProp.Value.ul = LastOrNumber(vSequences[i].c_str(), true);
- *rst += ECPropertyRestriction(RELOP_EQ, PR_EC_IMAP_ID, &sProp, ECRestriction::Full);
- } else {
- sProp.Value.ul = LastOrNumber(vSequences[i].c_str(), true);
- sPropEnd.Value.ul = LastOrNumber(vSequences[i].c_str() + ulPos + 1, true);
- if (sProp.Value.ul > sPropEnd.Value.ul)
- swap(sProp.Value.ul, sPropEnd.Value.ul);
- *rst += ECAndRestriction(
- ECPropertyRestriction(RELOP_GE, PR_EC_IMAP_ID, &sProp, ECRestriction::Full) +
- ECPropertyRestriction(RELOP_LE, PR_EC_IMAP_ID, &sPropEnd, ECRestriction::Full));
- }
- }
- ret.reset(rst);
- return hrSuccess;
- }
- /**
- * Convert an IMAP sequence set to a flat list of email numbers. See
- * RFC-3501 paragraph 9 for the syntax. This function will return the
- * closest range of requested items, and thus may return an empty
- * list.
- *
- * @param[in] strSeqSet IMAP sequence set of UID numbers
- * @param[out] lstMails flat list of email numbers
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrParseSeqUidSet(const string &strSeqSet, list<ULONG> &lstMails) {
- HRESULT hr = hrSuccess;
- vector<string> vSequences;
- string::size_type ulPos = 0;
- ULONG ulMailnr;
- ULONG ulBeginMailnr;
- // split different sequence parts into a vector
- vSequences = tokenize(strSeqSet, ',');
- for (ULONG i = 0; i < vSequences.size(); ++i) {
- ulPos = vSequences[i].find(':');
- if (ulPos == string::npos) {
- // single number
- ulMailnr = LastOrNumber(vSequences[i].c_str(), true);
- auto i = find(lstFolderMailEIDs.cbegin(), lstFolderMailEIDs.cend(), ulMailnr);
- if (i != lstFolderMailEIDs.cend())
- lstMails.push_back(std::distance(lstFolderMailEIDs.cbegin(), i));
- } else {
- // range
- ulBeginMailnr = LastOrNumber(vSequences[i].c_str(), true);
- ulMailnr = LastOrNumber(vSequences[i].c_str() + ulPos + 1, true);
- if (ulBeginMailnr > ulMailnr && ulBeginMailnr <= lstFolderMailEIDs.size())
- swap(ulBeginMailnr, ulMailnr);
- auto b = std::lower_bound(lstFolderMailEIDs.cbegin(), lstFolderMailEIDs.cend(), ulBeginMailnr);
- auto e = std::upper_bound(lstFolderMailEIDs.cbegin(), lstFolderMailEIDs.cend(), ulMailnr);
- for (auto i = b; i != e; ++i)
- lstMails.push_back(std::distance(lstFolderMailEIDs.cbegin(), i));
- }
- }
- lstMails.sort();
- lstMails.unique();
- return hr;
- }
- /**
- * Convert an IMAP sequence set to a flat list of email numbers. See
- * RFC-3501 paragraph 9 for the syntax. These exact numbers requested
- * must be present in the folder, otherwise an error will be returned.
- *
- * @param[in] strSeqSet IMAP sequence set of direct numbers
- * @param[out] lstMails flat list of email numbers
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrParseSeqSet(const string &strSeqSet, list<ULONG> &lstMails) {
- vector<string> vSequences;
- string::size_type ulPos = 0;
- ULONG ulMailnr;
- ULONG ulBeginMailnr;
- if (lstFolderMailEIDs.empty())
- return MAPI_E_NOT_FOUND;
- // split different sequence parts into a vector
- vSequences = tokenize(strSeqSet, ',');
- for (ULONG i = 0; i < vSequences.size(); ++i) {
- ulPos = vSequences[i].find(':');
- if (ulPos == string::npos) {
- // single number
- ulMailnr = LastOrNumber(vSequences[i].c_str(), false) - 1;
- if (ulMailnr >= lstFolderMailEIDs.size())
- return MAPI_E_CALL_FAILED;
- lstMails.push_back(ulMailnr);
- } else {
- // range
- ulBeginMailnr = LastOrNumber(vSequences[i].c_str(), false) - 1;
- ulMailnr = LastOrNumber(vSequences[i].c_str() + ulPos + 1, false) - 1;
- if (ulBeginMailnr > ulMailnr)
- swap(ulBeginMailnr, ulMailnr);
- if (ulBeginMailnr >= lstFolderMailEIDs.size() ||
- ulMailnr >= lstFolderMailEIDs.size())
- return MAPI_E_CALL_FAILED;
- for (ULONG j = ulBeginMailnr; j <= ulMailnr; ++j)
- lstMails.push_back(j);
- }
- }
- lstMails.sort();
- lstMails.unique();
- return hrSuccess;
- }
- /**
- * Implementation of the STORE command
- *
- * @param[in] lstMails list of emails to process
- * @param[in] strMsgDataItemName how to modify the message (set, append, remove)
- * @param[in] strMsgDataItemValue new flag values from IMAP client
- *
- * @return MAPI Error code
- */
- // @todo c store 2 (+FLAGS) (\Deleted) shouldn't but does work
- // @todo c store 2 +FLAGS (\Deleted) should and does work
- HRESULT IMAP::HrStore(const list<ULONG> &lstMails, string strMsgDataItemName, string strMsgDataItemValue, bool *lpbDoDelete)
- {
- HRESULT hr = hrSuccess;
- vector<string> lstFlags;
- ULONG ulCurrent;
- memory_ptr<SPropValue> lpPropVal;
- ULONG cValues;
- ULONG ulObjType;
- string strNewFlags;
- bool bDelete = false;
- static constexpr const SizedSPropTagArray(4, proptags4) =
- {4, {PR_MSG_STATUS, PR_ICON_INDEX, PR_LAST_VERB_EXECUTED, PR_LAST_VERB_EXECUTION_TIME}};
- static constexpr const SizedSPropTagArray(5, proptags5) =
- {5, {PR_MSG_STATUS, PR_FLAG_STATUS, PR_ICON_INDEX,
- PR_LAST_VERB_EXECUTED, PR_LAST_VERB_EXECUTION_TIME}};
- if (strCurrentFolder.empty() || lpSession == nullptr)
- return MAPI_E_CALL_FAILED;
- strMsgDataItemName = strToUpper(strMsgDataItemName);
- strMsgDataItemValue = strToUpper(strMsgDataItemValue);
- if (strMsgDataItemValue.size() > 1 && strMsgDataItemValue[0] == '(') {
- strMsgDataItemValue.erase(0, 1);
- strMsgDataItemValue.erase(strMsgDataItemValue.size() - 1, 1);
- }
- HrSplitInput(strMsgDataItemValue, lstFlags);
- for (auto mail_idx : lstMails) {
- object_ptr<IMessage> lpMessage;
- hr = lpSession->OpenEntry(lstFolderMailEIDs[mail_idx].sEntryID.cb, reinterpret_cast<ENTRYID *>(lstFolderMailEIDs[mail_idx].sEntryID.lpb),
- &IID_IMessage, MAPI_MODIFY, &ulObjType, &~lpMessage);
- if (hr != hrSuccess)
- return hr;
- // FLAGS, FLAGS.SILENT, +FLAGS, +FLAGS.SILENT, -FLAGS, -FLAGS.SILENT
- if (strMsgDataItemName.compare(0, 5, "FLAGS") == 0) {
- if (strMsgDataItemValue.find("\\SEEN") == string::npos)
- hr = lpMessage->SetReadFlag(CLEAR_READ_FLAG);
- else
- hr = lpMessage->SetReadFlag(SUPPRESS_RECEIPT);
- if (hr != hrSuccess)
- return hr;
- hr = lpMessage->GetProps(proptags5, 0, &cValues, &~lpPropVal);
- if (FAILED(hr))
- return hr;
- cValues = 5;
- lpPropVal[1].ulPropTag = PR_FLAG_STATUS;
- if (strMsgDataItemValue.find("\\FLAGGED") == string::npos)
- lpPropVal[1].Value.ul = 0;
- else
- lpPropVal[1].Value.ul = 2;
- if (lpPropVal[2].ulPropTag != PR_ICON_INDEX) {
- lpPropVal[2].ulPropTag = PR_ICON_INDEX;
- lpPropVal[2].Value.l = ICON_FOLDER_DEFAULT;
- }
- if (lpPropVal[0].ulPropTag != PR_MSG_STATUS) {
- lpPropVal[0].ulPropTag = PR_MSG_STATUS;
- lpPropVal[0].Value.ul = 0;
- }
- if (strMsgDataItemValue.find("\\ANSWERED") == string::npos) {
- lpPropVal[0].Value.ul &= ~MSGSTATUS_ANSWERED;
- cValues -= 2; // leave PR_LAST_VERB_EXECUTED properties
- } else {
- lpPropVal[0].Value.ul |= MSGSTATUS_ANSWERED;
- lpPropVal[2].Value.ul = ICON_MAIL_REPLIED;
- lpPropVal[3].ulPropTag = PR_LAST_VERB_EXECUTED;
- lpPropVal[3].Value.ul = NOTEIVERB_REPLYTOSENDER;
- lpPropVal[4].ulPropTag = PR_LAST_VERB_EXECUTION_TIME;
- GetSystemTimeAsFileTime(&lpPropVal[4].Value.ft);
- }
- if (strMsgDataItemValue.find("$FORWARDED") == string::npos) {
- if (cValues == 5)
- cValues -= 2; // leave PR_LAST_VERB_EXECUTED properties if still present
- } else {
- lpPropVal[2].Value.ul = ICON_MAIL_FORWARDED;
- lpPropVal[3].ulPropTag = PR_LAST_VERB_EXECUTED;
- lpPropVal[3].Value.ul = NOTEIVERB_FORWARD;
- lpPropVal[4].ulPropTag = PR_LAST_VERB_EXECUTION_TIME;
- GetSystemTimeAsFileTime(&lpPropVal[4].Value.ft);
- }
- if (strMsgDataItemValue.find("\\DELETED") == string::npos) {
- lpPropVal[0].Value.ul &= ~MSGSTATUS_DELMARKED;
- } else {
- lpPropVal[0].Value.ul |= MSGSTATUS_DELMARKED;
- bDelete = true;
- }
- // remove all "flag" properties
- hr = lpMessage->DeleteProps(proptags5, NULL);
- if (hr != hrSuccess)
- return hr;
- // set new values (can be partial, see answered and forwarded)
- hr = lpMessage->SetProps(cValues, lpPropVal, NULL);
- if (hr != hrSuccess)
- return hr;
- hr = lpMessage->SaveChanges(KEEP_OPEN_READWRITE | FORCE_SAVE);
- if (hr != hrSuccess)
- return hr;
- } else if (strMsgDataItemName.compare(0, 6, "+FLAGS") == 0) {
- for (ulCurrent = 0; ulCurrent < lstFlags.size(); ++ulCurrent) {
- if (lstFlags[ulCurrent].compare("\\SEEN") == 0) {
- hr = lpMessage->SetReadFlag(SUPPRESS_RECEIPT);
- if (hr != hrSuccess)
- return hr;
- } else if (lstFlags[ulCurrent].compare("\\DRAFT") == 0) {
- // not allowed
- } else if (lstFlags[ulCurrent].compare("\\FLAGGED") == 0) {
- hr = MAPIAllocateBuffer(sizeof(SPropValue), &~lpPropVal);
- if (hr != hrSuccess)
- return hr;
- lpPropVal->ulPropTag = PR_FLAG_STATUS;
- lpPropVal->Value.ul = 2; // 0: none, 1: green ok mark, 2: red flag
- HrSetOneProp(lpMessage, lpPropVal);
- // TODO: set PR_FLAG_ICON here too?
- } else if (lstFlags[ulCurrent].compare("\\ANSWERED") == 0 || lstFlags[ulCurrent].compare("$FORWARDED") == 0) {
- hr = lpMessage->GetProps(proptags4, 0, &cValues, &~lpPropVal);
- if (FAILED(hr))
- return hr;
- cValues = 4;
- if (lpPropVal[0].ulPropTag != PR_MSG_STATUS) {
- lpPropVal[0].ulPropTag = PR_MSG_STATUS;
- lpPropVal[0].Value.ul = 0;
- }
- // answered
- if (lstFlags[ulCurrent][0] == '\\')
- lpPropVal->Value.ul |= MSGSTATUS_ANSWERED;
- lpPropVal[1].ulPropTag = PR_ICON_INDEX;
- if (lstFlags[ulCurrent][0] == '\\')
- lpPropVal[1].Value.l = ICON_MAIL_REPLIED;
- else
- lpPropVal[1].Value.l = ICON_MAIL_FORWARDED;
- lpPropVal[2].ulPropTag = PR_LAST_VERB_EXECUTED;
- if (lstFlags[ulCurrent][0] == '\\')
- lpPropVal[2].Value.ul = NOTEIVERB_REPLYTOSENDER;
- else
- lpPropVal[2].Value.l = NOTEIVERB_FORWARD;
- lpPropVal[3].ulPropTag = PR_LAST_VERB_EXECUTION_TIME;
- GetSystemTimeAsFileTime(&lpPropVal[3].Value.ft);
- hr = lpMessage->SetProps(cValues, lpPropVal, NULL);
- if (hr != hrSuccess)
- return hr;
- } else if (lstFlags[ulCurrent].compare("\\DELETED") == 0) {
- if (HrGetOneProp(lpMessage, PR_MSG_STATUS, &~lpPropVal) != hrSuccess) {
- hr = MAPIAllocateBuffer(sizeof(SPropValue), &~lpPropVal);
- if (hr != hrSuccess)
- return hr;
- lpPropVal->ulPropTag = PR_MSG_STATUS;
- lpPropVal->Value.ul = 0;
- }
- lpPropVal->Value.ul |= MSGSTATUS_DELMARKED;
- HrSetOneProp(lpMessage, lpPropVal);
- bDelete = true;
- }
- lpMessage->SaveChanges(KEEP_OPEN_READWRITE | FORCE_SAVE);
- }
- } else if (strMsgDataItemName.compare(0, 6, "-FLAGS") == 0) {
- for (ulCurrent = 0; ulCurrent < lstFlags.size(); ++ulCurrent) {
- if (lstFlags[ulCurrent].compare("\\SEEN") == 0) {
- hr = lpMessage->SetReadFlag(CLEAR_READ_FLAG);
- if (hr != hrSuccess)
- return hr;
- } else if (lstFlags[ulCurrent].compare("\\DRAFT") == 0) {
- // not allowed
- } else if (lstFlags[ulCurrent].compare("\\FLAGGED") == 0) {
- if (lpPropVal == NULL) {
- hr = MAPIAllocateBuffer(sizeof(SPropValue), &~lpPropVal);
- if (hr != hrSuccess)
- return hr;
- }
- lpPropVal->ulPropTag = PR_FLAG_STATUS;
- lpPropVal->Value.ul = 0;
- HrSetOneProp(lpMessage, lpPropVal);
- } else if (lstFlags[ulCurrent].compare("\\ANSWERED") == 0 || lstFlags[ulCurrent].compare("$FORWARDED") == 0) {
- hr = lpMessage->GetProps(proptags4, 0, &cValues, &~lpPropVal);
- if (FAILED(hr))
- return hr;
- cValues = 4;
- if (lpPropVal[0].ulPropTag != PR_MSG_STATUS) {
- lpPropVal[0].ulPropTag = PR_MSG_STATUS;
- lpPropVal[0].Value.ul = 0;
- }
- lpPropVal->Value.ul &= ~MSGSTATUS_ANSWERED;
- lpPropVal[1].ulPropTag = PR_ICON_INDEX;
- lpPropVal[1].Value.l = ICON_FOLDER_DEFAULT;
- lpPropVal[2].ulPropTag = PR_LAST_VERB_EXECUTED;
- lpPropVal[2].Value.ul = NOTEIVERB_OPEN;
- lpPropVal[3].ulPropTag = PR_LAST_VERB_EXECUTION_TIME;
- GetSystemTimeAsFileTime(&lpPropVal[3].Value.ft);
- hr = lpMessage->SetProps(cValues, lpPropVal, NULL);
- if (hr != hrSuccess)
- return hr;
- } else if (lstFlags[ulCurrent].compare("\\DELETED") == 0) {
- if (HrGetOneProp(lpMessage, PR_MSG_STATUS, &~lpPropVal) != hrSuccess) {
- hr = MAPIAllocateBuffer(sizeof(SPropValue), &~lpPropVal);
- if (hr != hrSuccess)
- return hr;
- lpPropVal->ulPropTag = PR_MSG_STATUS;
- lpPropVal->Value.ul = 0;
- }
- lpPropVal->Value.ul &= ~MSGSTATUS_DELMARKED;
- HrSetOneProp(lpMessage, lpPropVal);
- }
- lpMessage->SaveChanges(KEEP_OPEN_READWRITE | FORCE_SAVE);
- }
- }
- /* Get the newly updated flags */
- hr = HrGetMessageFlags(strNewFlags, lpMessage, lstFolderMailEIDs[mail_idx].bRecent);
- if (hr != hrSuccess)
- return hr;
- /* Update our internal flag status */
- lstFolderMailEIDs[mail_idx].strFlags = strNewFlags;
- } // loop on mails
- if (strMsgDataItemName.size() > 7 &&
- strMsgDataItemName.compare(strMsgDataItemName.size() - 7, 7, ".SILENT") == 0)
- hr = MAPI_E_NOT_ME; // abuse error code that will not be used elsewhere from this function
- if (lpbDoDelete)
- *lpbDoDelete = bDelete;
- return hr;
- }
- /**
- * Implementation of the COPY and XAOL-MOVE commands
- *
- * @param[in] lstMails list of email to copy or move
- * @param[in] strFolderParam folder to copy/move mails to, in IMAP UTF-7 charset
- * @param[in] bMove copy (false) or move (true)
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrCopy(const list<ULONG> &lstMails, const string &strFolderParam, bool bMove) {
- HRESULT hr = hrSuccess;
- object_ptr<IMAPIFolder> lpFromFolder, lpDestFolder;
- ULONG ulCount;
- ENTRYLIST sEntryList;
- wstring strFolder;
- sEntryList.lpbin = NULL;
- if (strCurrentFolder.empty() || !lpSession) {
- hr = MAPI_E_CALL_FAILED;
- goto exit;
- }
- hr = HrFindFolder(strCurrentFolder, bCurrentFolderReadOnly, &~lpFromFolder);
- if (hr != hrSuccess)
- goto exit;
- // get dest folder
- hr = IMAP2MAPICharset(strFolderParam, strFolder);
- if (hr != hrSuccess)
- goto exit;
- hr = HrFindFolder(strFolder, false, &~lpDestFolder);
- if (hr != hrSuccess)
- goto exit;
- sEntryList.cValues = lstMails.size();
- if ((hr = MAPIAllocateBuffer(sizeof(SBinary) * lstMails.size(), (LPVOID *) &sEntryList.lpbin)) != hrSuccess)
- goto exit;
- ulCount = 0;
- for (auto mail_idx : lstMails) {
- sEntryList.lpbin[ulCount].cb = lstFolderMailEIDs[mail_idx].sEntryID.cb;
- sEntryList.lpbin[ulCount].lpb = lstFolderMailEIDs[mail_idx].sEntryID.lpb;
- ++ulCount;
- }
- hr = lpFromFolder->CopyMessages(&sEntryList, NULL, lpDestFolder, 0, NULL, bMove ? MESSAGE_MOVE : 0);
- exit:
- MAPIFreeBuffer(sEntryList.lpbin);
- return hr;
- }
- /**
- * Implements the SEARCH command
- *
- * @param[in] lstSearchCriteria search options from the (this value is modified, cannot be used again)
- * @param[in] ulStartCriteria offset in the lstSearchCriteria to start parsing
- * @param[out] lstMailnr number of email messages that match the search criteria
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrSearch(std::vector<std::string> &&lstSearchCriteria,
- ULONG ulStartCriteria, std::list<ULONG> &lstMailnr)
- {
- HRESULT hr = hrSuccess;
- string strSearchCriterium;
- vector<string> vSubSearch;
- list<ULONG> lstMails;
- object_ptr<IMAPIFolder> lpFolder;
- object_ptr<IMAPITable> lpTable;
- ULONG ulMailnr, ulRownr;
- enum { EID, NUM_COLS };
- static constexpr const SizedSPropTagArray(NUM_COLS, spt) = {NUM_COLS, {PR_EC_IMAP_ID}};
- map<unsigned int, unsigned int> mapUIDs;
- int n = 0;
-
- if (strCurrentFolder.empty() || lpSession == nullptr)
- return MAPI_E_CALL_FAILED;
- // no need to search in empty folders, won't find anything
- if (lstFolderMailEIDs.empty())
- return hr;
- // don't search if only search for uid, sequence set, all, recent, new or old
- strSearchCriterium = lstSearchCriteria[ulStartCriteria];
- strSearchCriterium = strToUpper(strSearchCriterium);
- if (lstSearchCriteria.size() - ulStartCriteria == 2 &&
- strSearchCriterium.compare("UID") == 0)
- return HrParseSeqUidSet(lstSearchCriteria[ulStartCriteria + 1], lstMailnr);
- if (lstSearchCriteria.size() - ulStartCriteria == 1) {
- if (strSearchCriterium.find_first_of("123456789*") == 0) {
- hr = HrParseSeqSet(lstSearchCriteria[ulStartCriteria], lstMailnr);
- return hr;
- } else if (strSearchCriterium.compare("ALL") == 0) {
- for (ulMailnr = 0; ulMailnr < lstFolderMailEIDs.size(); ++ulMailnr)
- lstMailnr.push_back(ulMailnr);
- return hr;
- } else if (strSearchCriterium.compare("RECENT") == 0) {
- for (ulMailnr = 0; ulMailnr < lstFolderMailEIDs.size(); ++ulMailnr)
- if(lstFolderMailEIDs[ulMailnr].bRecent)
- lstMailnr.push_back(ulMailnr);
- return hr;
- } else if (strSearchCriterium.compare("NEW") == 0) {
- for (ulMailnr = 0; ulMailnr < lstFolderMailEIDs.size(); ++ulMailnr)
- if(lstFolderMailEIDs[ulMailnr].bRecent && lstFolderMailEIDs[ulMailnr].strFlags.find("Seen") == std::string::npos)
- lstMailnr.push_back(ulMailnr);
- return hr;
- } else if (strSearchCriterium.compare("OLD") == 0) {
- for (ulMailnr = 0; ulMailnr < lstFolderMailEIDs.size(); ++ulMailnr)
- if(!lstFolderMailEIDs[ulMailnr].bRecent)
- lstMailnr.push_back(ulMailnr);
- return hr;
- }
- }
-
- // Make a map of UID->ID
- for (const auto &e : lstFolderMailEIDs)
- mapUIDs[e.ulUid] = n++;
- hr = HrFindFolder(strCurrentFolder, bCurrentFolderReadOnly, &~lpFolder);
- if (hr != hrSuccess)
- return hr;
- hr = lpFolder->GetContentsTable(MAPI_DEFERRED_ERRORS, &~lpTable);
- if (hr != hrSuccess)
- return hr;
- ECAndRestriction root_rst;
- std::vector<IRestrictionPush *> lstRestrictions;
- lstRestrictions.push_back(&root_rst);
- /*
- * Add EXIST(PR_INSTANCE_KEY) to make sure that the query will not be
- * passed to the indexer.
- */
- root_rst += ECExistRestriction(PR_INSTANCE_KEY);
- // Thunderbird searches:
- // or:
- // 12 uid SEARCH UNDELETED (OR SUBJECT "Undelivered" TO "henk")
- // 15 uid SEARCH UNDELETED (OR (OR SUBJECT "sender" SUBJECT "mail") SUBJECT "returned")
- // 17 uid SEARCH UNDELETED (OR SUBJECT "Undelivered" NOT TO "henk")
- // and:
- // 14 uid SEARCH UNDELETED SUBJECT "Undelivered" TO "henk"
- // 16 uid SEARCH UNDELETED SUBJECT "Undelivered" NOT TO "henk"
- // both cases, ulStartCriteria == 3: UNDELETED
- // this breaks to following search:
- // (or subject henk subject kees) (or to henk from kees)
- // since this will translate in all or's, not: and(or(subj:henk,subj:kees),or(to:henk,from:kees))
- // however, thunderbird cannot build such a query, so we don't care currently.
- while (ulStartCriteria < lstSearchCriteria.size())
- {
- if (lstSearchCriteria[ulStartCriteria][0] == '(') {
- // remove all () and [], and resplit.
- strSearchCriterium.clear();
- for (auto c : lstSearchCriteria[ulStartCriteria]) {
- if (c == '(' || c == ')' || c == '[' || c == ']')
- continue;
- strSearchCriterium += c;
- }
- vSubSearch.clear();
- HrSplitInput(strSearchCriterium, vSubSearch);
- // replace in list.
- lstSearchCriteria.erase(lstSearchCriteria.begin() + ulStartCriteria);
- lstSearchCriteria.insert(lstSearchCriteria.begin() + ulStartCriteria, vSubSearch.begin(), vSubSearch.end());
- }
- strSearchCriterium = lstSearchCriteria[ulStartCriteria];
- strSearchCriterium = strToUpper(strSearchCriterium);
- assert(lstRestrictions.size() >= 1);
- IRestrictionPush &top_rst = *lstRestrictions[lstRestrictions.size()-1];
- if (lstRestrictions.size() > 1)
- lstRestrictions.pop_back();
- SPropValue pv, pv2;
- if (strSearchCriterium.find_first_of("123456789*") == 0) { // sequence set
- lstMails.clear();
- hr = HrParseSeqSet(strSearchCriterium, lstMails);
- if (hr != hrSuccess)
- return hr;
- ECOrRestriction or_rst;
- for (auto mail_idx : lstMails) {
- pv.ulPropTag = PR_EC_IMAP_ID;
- pv.Value.ul = lstFolderMailEIDs[mail_idx].ulUid;
- or_rst += ECPropertyRestriction(RELOP_EQ, pv.ulPropTag, &pv, ECRestriction::Shallow);
- }
- top_rst += std::move(or_rst);
- ++ulStartCriteria;
- } else if (strSearchCriterium.compare("ALL") == 0 || strSearchCriterium.compare("NEW") == 0 || strSearchCriterium.compare("RECENT") == 0) {
- // do nothing
- ++ulStartCriteria;
- } else if (strSearchCriterium.compare("ANSWERED") == 0) {
- top_rst += ECAndRestriction(
- ECExistRestriction(PR_MSG_STATUS) +
- ECBitMaskRestriction(BMR_NEZ, PR_MSG_STATUS, MSGSTATUS_ANSWERED));
- ++ulStartCriteria;
- // TODO: find also in PR_LAST_VERB_EXECUTED
- } else if (strSearchCriterium.compare("BEFORE") == 0) {
- if (lstSearchCriteria.size() - ulStartCriteria <= 1)
- return MAPI_E_CALL_FAILED;
- pv.ulPropTag = PR_EC_MESSAGE_DELIVERY_DATE;
- pv.Value.ft = StringToFileTime(lstSearchCriteria[ulStartCriteria+1].c_str());
- top_rst += ECAndRestriction(
- ECExistRestriction(pv.ulPropTag) +
- ECPropertyRestriction(RELOP_LT, pv.ulPropTag, &pv, ECRestriction::Shallow));
- ulStartCriteria += 2;
- } else if (strSearchCriterium == "BODY") {
- if (lstSearchCriteria.size() - ulStartCriteria <= 1)
- return MAPI_E_CALL_FAILED;
- unsigned int flags = lstSearchCriteria[ulStartCriteria+1].size() > 0 ? (FL_SUBSTRING | FL_IGNORECASE) : FL_FULLSTRING;
- pv.ulPropTag = PR_BODY_A;
- pv.Value.lpszA = const_cast<char *>(lstSearchCriteria[ulStartCriteria+1].c_str());
- top_rst += ECAndRestriction(
- ECExistRestriction(PR_BODY) +
- ECContentRestriction(flags, PR_BODY, &pv, ECRestriction::Shallow));
- ulStartCriteria += 2;
- } else if (strSearchCriterium.compare("DELETED") == 0) {
- top_rst += ECAndRestriction(
- ECExistRestriction(PR_MSG_STATUS) +
- ECBitMaskRestriction(BMR_NEZ, PR_MSG_STATUS, MSGSTATUS_DELMARKED));
- ++ulStartCriteria;
- } else if (strSearchCriterium.compare("DRAFT") == 0) {
- top_rst += ECAndRestriction(
- ECExistRestriction(PR_MSG_STATUS) +
- ECBitMaskRestriction(BMR_NEZ, PR_MSG_STATUS, MSGSTATUS_DRAFT));
- ++ulStartCriteria;
- // FIXME: add restriction to find PR_MESSAGE_FLAGS with MSGFLAG_UNSENT on
- } else if (strSearchCriterium.compare("FLAGGED") == 0) {
- top_rst += ECAndRestriction(
- ECExistRestriction(PR_FLAG_STATUS) +
- ECBitMaskRestriction(BMR_NEZ, PR_FLAG_STATUS, 0xFFFF));
- ++ulStartCriteria;
- } else if (strSearchCriterium.compare("FROM") == 0) {
- if (lstSearchCriteria.size() - ulStartCriteria <= 1)
- return MAPI_E_CALL_FAILED;
- unsigned int flags = lstSearchCriteria[ulStartCriteria+1].size() > 0 ? (FL_SUBSTRING | FL_IGNORECASE) : FL_FULLSTRING;
- pv.ulPropTag = PR_SENT_REPRESENTING_NAME_A;
- pv.Value.lpszA = const_cast<char *>(lstSearchCriteria[ulStartCriteria+1].c_str());
- top_rst += ECOrRestriction(
- ECContentRestriction(flags, PR_SENT_REPRESENTING_NAME, &pv, ECRestriction::Shallow) +
- ECContentRestriction(flags, PR_SENT_REPRESENTING_EMAIL_ADDRESS, &pv, ECRestriction::Shallow));
- ulStartCriteria += 2;
- } else if (strSearchCriterium.compare("KEYWORD") == 0) {
- top_rst += ECBitMaskRestriction(BMR_NEZ, PR_ENTRYID, 0);
- ulStartCriteria += 2;
- } else if (strSearchCriterium.compare("LARGER") == 0) {
- if (lstSearchCriteria.size() - ulStartCriteria <= 1)
- return MAPI_E_CALL_FAILED;
- pv.ulPropTag = PR_EC_IMAP_EMAIL_SIZE;
- pv2.ulPropTag = PR_MESSAGE_SIZE;
- pv2.Value.ul = pv.Value.ul = strtoul(lstSearchCriteria[ulStartCriteria+1].c_str(), nullptr, 0);
- top_rst += ECOrRestriction(
- ECAndRestriction(
- ECExistRestriction(pv.ulPropTag) +
- ECPropertyRestriction(RELOP_GT, pv.ulPropTag, &pv, ECRestriction::Shallow)
- ) +
- ECAndRestriction(
- ECNotRestriction(ECExistRestriction(pv.ulPropTag)) +
- ECPropertyRestriction(RELOP_GT, pv2.ulPropTag, &pv2, ECRestriction::Shallow)
- ));
- ulStartCriteria += 2;
- // NEW done with ALL
- } else if (strSearchCriterium.compare("NOT") == 0) {
- ECRestriction *r = top_rst += ECNotRestriction(nullptr);
- lstRestrictions.push_back(static_cast<ECNotRestriction *>(r));
- ++ulStartCriteria;
- } else if (strSearchCriterium.compare("OLD") == 0) { // none?
- top_rst += ECBitMaskRestriction(BMR_NEZ, PR_ENTRYID, 0);
- ++ulStartCriteria;
- } else if (strSearchCriterium.compare("ON") == 0) {
- if (lstSearchCriteria.size() - ulStartCriteria <= 1)
- return MAPI_E_CALL_FAILED;
- pv.ulPropTag = pv2.ulPropTag = PR_EC_MESSAGE_DELIVERY_DATE;
- pv.Value.ft = StringToFileTime(lstSearchCriteria[ulStartCriteria+1].c_str());
- pv2.Value.ft = AddDay(pv.Value.ft);
- top_rst += ECAndRestriction(
- ECExistRestriction(pv.ulPropTag) +
- ECPropertyRestriction(RELOP_GE, pv.ulPropTag, &pv, ECRestriction::Shallow) +
- ECPropertyRestriction(RELOP_LT, pv2.ulPropTag, &pv2, ECRestriction::Shallow));
- ulStartCriteria += 2;
- } else if (strSearchCriterium.compare("OR") == 0) {
- ECRestriction *new_rst = top_rst += ECOrRestriction();
- auto or_rst = static_cast<ECOrRestriction *>(new_rst);
- lstRestrictions.push_back(or_rst);
- lstRestrictions.push_back(or_rst);
- ++ulStartCriteria;
- // RECENT done with ALL
- } else if (strSearchCriterium.compare("SEEN") == 0) {
- top_rst += ECAndRestriction(
- ECExistRestriction(PR_MESSAGE_FLAGS) +
- ECBitMaskRestriction(BMR_NEZ, PR_MESSAGE_FLAGS, MSGFLAG_READ));
- ++ulStartCriteria;
- } else if (strSearchCriterium.compare("SENTBEFORE") == 0) {
- if (lstSearchCriteria.size() - ulStartCriteria <= 1)
- return MAPI_E_CALL_FAILED;
- pv.ulPropTag = PR_EC_CLIENT_SUBMIT_DATE;
- pv.Value.ft = StringToFileTime(lstSearchCriteria[ulStartCriteria+1].c_str());
- top_rst += ECAndRestriction(
- ECExistRestriction(pv.ulPropTag) +
- ECPropertyRestriction(RELOP_LT, pv.ulPropTag, &pv, ECRestriction::Shallow));
- ulStartCriteria += 2;
- } else if (strSearchCriterium.compare("SENTON") == 0) {
- if (lstSearchCriteria.size() - ulStartCriteria <= 1)
- return MAPI_E_CALL_FAILED;
- pv.ulPropTag = pv2.ulPropTag = PR_EC_CLIENT_SUBMIT_DATE;
- pv.Value.ft = StringToFileTime(lstSearchCriteria[ulStartCriteria+1].c_str());
- pv2.Value.ft = AddDay(pv.Value.ft);
- top_rst += ECAndRestriction(
- ECExistRestriction(pv.ulPropTag) +
- ECPropertyRestriction(RELOP_GE, pv.ulPropTag, &pv, ECRestriction::Shallow) +
- ECPropertyRestriction(RELOP_LT, pv2.ulPropTag, &pv2, ECRestriction::Shallow));
- ulStartCriteria += 2;
- } else if (strSearchCriterium.compare("SENTSINCE") == 0) {
- if (lstSearchCriteria.size() - ulStartCriteria <= 1)
- return MAPI_E_CALL_FAILED;
- pv.ulPropTag = PR_EC_CLIENT_SUBMIT_DATE;
- pv.Value.ft = StringToFileTime(lstSearchCriteria[ulStartCriteria+1].c_str());
- top_rst += ECAndRestriction(
- ECExistRestriction(pv.ulPropTag) +
- ECPropertyRestriction(RELOP_GE, pv.ulPropTag, &pv, ECRestriction::Shallow));
- ulStartCriteria += 2;
- } else if (strSearchCriterium.compare("SINCE") == 0) {
- if (lstSearchCriteria.size() - ulStartCriteria <= 1)
- return MAPI_E_CALL_FAILED;
- pv.ulPropTag = PR_EC_MESSAGE_DELIVERY_DATE;
- pv.Value.ft = StringToFileTime(lstSearchCriteria[ulStartCriteria+1].c_str());
- top_rst += ECAndRestriction(
- ECExistRestriction(pv.ulPropTag) +
- ECPropertyRestriction(RELOP_GE, pv.ulPropTag, &pv, ECRestriction::Shallow));
- ulStartCriteria += 2;
- } else if (strSearchCriterium.compare("SMALLER") == 0) {
- if (lstSearchCriteria.size() - ulStartCriteria <= 1)
- return MAPI_E_CALL_FAILED;
- pv.ulPropTag = PR_EC_IMAP_EMAIL_SIZE;
- pv2.ulPropTag = PR_MESSAGE_SIZE;
- pv.Value.ul = pv2.Value.ul = strtoul(lstSearchCriteria[ulStartCriteria+1].c_str(), nullptr, 0);
- top_rst += ECOrRestriction(
- ECAndRestriction(
- ECExistRestriction(pv.ulPropTag) +
- ECPropertyRestriction(RELOP_LT, pv.ulPropTag, &pv, ECRestriction::Shallow)
- ) +
- ECAndRestriction(
- ECNotRestriction(ECExistRestriction(pv.ulPropTag)) +
- ECPropertyRestriction(RELOP_LT, pv2.ulPropTag, &pv2, ECRestriction::Shallow)
- ));
- ulStartCriteria += 2;
- } else if (strSearchCriterium.compare("SUBJECT") == 0) {
- if (lstSearchCriteria.size() - ulStartCriteria <= 1)
- return MAPI_E_CALL_FAILED;
- // Handle SUBJECT <s>
- const char *const szSearch = lstSearchCriteria[ulStartCriteria+1].c_str();
- unsigned int flags = szSearch[0] ? (FL_SUBSTRING | FL_IGNORECASE) : FL_FULLSTRING;
- pv.ulPropTag = PR_SUBJECT_A;
- pv.Value.lpszA = const_cast<char *>(szSearch);
- top_rst += ECAndRestriction(
- ECExistRestriction(PR_SUBJECT) +
- ECContentRestriction(flags, PR_SUBJECT, &pv, ECRestriction::Shallow));
- ulStartCriteria += 2;
-
- } else if (strSearchCriterium.compare("TEXT") == 0) {
- unsigned int flags = lstSearchCriteria[ulStartCriteria+1].size() > 0 ? (FL_SUBSTRING | FL_IGNORECASE) : FL_FULLSTRING;
- pv.ulPropTag = PR_BODY_A;
- pv2.ulPropTag = PR_TRANSPORT_MESSAGE_HEADERS_A;
- pv.Value.lpszA = pv2.Value.lpszA = const_cast<char *>(lstSearchCriteria[ulStartCriteria+1].c_str());
- top_rst += ECOrRestriction(
- ECAndRestriction(
- ECExistRestriction(PR_BODY) +
- ECContentRestriction(flags, PR_BODY, &pv, ECRestriction::Shallow)
- ) +
- ECAndRestriction(
- ECExistRestriction(pv2.ulPropTag) +
- ECContentRestriction(flags, pv2.ulPropTag, &pv2, ECRestriction::Shallow)
- ));
- ulStartCriteria += 2;
- }
- else if (strSearchCriterium.compare("TO") == 0 || strSearchCriterium.compare("CC") == 0 || strSearchCriterium.compare("BCC") == 0) {
- if (lstSearchCriteria.size() - ulStartCriteria <= 1)
- return MAPI_E_CALL_FAILED;
-
- // Search for "^HEADER:.*DATA" in PR_TRANSPORT_HEADERS
- std::string strSearch = (string)"^" + strSearchCriterium + ":.*" + lstSearchCriteria[ulStartCriteria+1];
- pv.ulPropTag = PR_TRANSPORT_MESSAGE_HEADERS_A;
- pv.Value.lpszA = const_cast<char *>(strSearch.c_str());
- top_rst += ECPropertyRestriction(RELOP_RE, pv.ulPropTag, &pv, ECRestriction::Shallow);
- ulStartCriteria += 2;
- } else if (strSearchCriterium.compare("UID") == 0) {
- ECOrRestriction or_rst;
- lstMails.clear();
- hr = HrParseSeqUidSet(lstSearchCriteria[ulStartCriteria + 1], lstMails);
- if (hr != hrSuccess)
- return hr;
- for (auto mail_idx : lstMails) {
- pv.ulPropTag = PR_EC_IMAP_ID;
- pv.Value.ul = lstFolderMailEIDs[mail_idx].ulUid;
- or_rst += ECPropertyRestriction(RELOP_EQ, pv.ulPropTag, &pv, ECRestriction::Shallow);
- }
- top_rst += std::move(or_rst);
- ulStartCriteria += 2;
- } else if (strSearchCriterium.compare("UNANSWERED") == 0) {
- top_rst += ECOrRestriction(
- ECNotRestriction(ECExistRestriction(PR_MSG_STATUS)) +
- ECBitMaskRestriction(BMR_EQZ, PR_MSG_STATUS, MSGSTATUS_ANSWERED));
- ++ulStartCriteria;
- // TODO: also find in PR_LAST_VERB_EXECUTED
- } else if (strSearchCriterium.compare("UNDELETED") == 0) {
- top_rst += ECOrRestriction(
- ECNotRestriction(ECExistRestriction(PR_MSG_STATUS)) +
- ECBitMaskRestriction(BMR_EQZ, PR_MSG_STATUS, MSGSTATUS_DELMARKED));
- ++ulStartCriteria;
- } else if (strSearchCriterium.compare("UNDRAFT") == 0) {
- top_rst += ECOrRestriction(
- ECNotRestriction(ECExistRestriction(PR_MSG_STATUS)) +
- ECBitMaskRestriction(BMR_EQZ, PR_MSG_STATUS, MSGSTATUS_DRAFT));
- ++ulStartCriteria;
- // FIXME: add restrictin to find PR_MESSAGE_FLAGS with MSGFLAG_UNSENT off
- } else if (strSearchCriterium.compare("UNFLAGGED") == 0) {
- top_rst += ECOrRestriction(
- ECNotRestriction(ECExistRestriction(PR_FLAG_STATUS)) +
- ECBitMaskRestriction(BMR_EQZ, PR_FLAG_STATUS, 0xFFFF));
- ++ulStartCriteria;
- } else if (strSearchCriterium.compare("UNKEYWORD") == 0) {
- top_rst += ECExistRestriction(PR_ENTRYID);
- ulStartCriteria += 2;
- } else if (strSearchCriterium.compare("UNSEEN") == 0) {
- top_rst += ECOrRestriction(
- ECNotRestriction(ECExistRestriction(PR_MESSAGE_FLAGS)) +
- ECBitMaskRestriction(BMR_EQZ, PR_MESSAGE_FLAGS, MSGFLAG_READ));
- ++ulStartCriteria;
- } else if (strSearchCriterium.compare("HEADER") == 0) {
- if (lstSearchCriteria.size() - ulStartCriteria <= 2)
- return MAPI_E_CALL_FAILED;
-
- // Search for "^HEADER:.*DATA" in PR_TRANSPORT_HEADERS
- std::string strSearch = "^" + lstSearchCriteria[ulStartCriteria+1] + ":.*" + lstSearchCriteria[ulStartCriteria+2];
- pv.ulPropTag = PR_TRANSPORT_MESSAGE_HEADERS_A;
- pv.Value.lpszA = const_cast<char *>(strSearch.c_str());
- top_rst += ECPropertyRestriction(RELOP_RE, pv.ulPropTag, &pv, ECRestriction::Shallow);
- ulStartCriteria += 3;
- } else {
- return MAPI_E_CALL_FAILED;
- }
- }
- root_rst += ECExistRestriction(PR_ENTRYID);
- memory_ptr<SRestriction> classic_rst;
- hr = root_rst.CreateMAPIRestriction(&~classic_rst, ECRestriction::Cheap);
- if (hr != hrSuccess)
- return hr;
- rowset_ptr lpRows;
- hr = HrQueryAllRows(lpTable, spt, classic_rst, nullptr, 0, &~lpRows);
- if (hr != hrSuccess)
- return hr;
- if (lpRows->cRows == 0)
- return hrSuccess;
- for (ulRownr = 0; ulRownr < lpRows->cRows; ++ulRownr) {
- auto iterUID = mapUIDs.find(lpRows->aRow[ulRownr].lpProps[0].Value.ul);
- if (iterUID == mapUIDs.cend())
- // Found a match for a message that is not in our message list .. skip it
- continue;
-
- lstMailnr.push_back(iterUID->second);
- }
- lstMailnr.sort();
- return hrSuccess;
- }
- /**
- * Lookup a header value in a full message
- *
- * @param[in] strMessage The message to find the header in
- * @param[in] strHeader The header to find in the header part of the message
- * @param[in] strDefault The default value of the header if it wasn't found
- *
- * @return The header value from the message enclosed in quotes, or the given default
- */
- string IMAP::GetHeaderValue(const string &strMessage, const string &strHeader, const string &strDefault) {
- string::size_type posStart, posEnd;
- posStart = strMessage.find(strHeader);
- if (posStart == string::npos)
- return strDefault;
- posStart += strHeader.length();
- posEnd = strMessage.find("\r\n", posStart);
- if (posEnd == string::npos)
- return strDefault;
- // don't care about uppercase, it's all good.
- return "\"" + strMessage.substr(posStart, posEnd - posStart) + "\"";
- }
- /**
- * Create a bodystructure (RFC 3501). Since this is parsed from a
- * VMIME generated message, this function has alot of assumptions. It
- * will still fail to correctly generate a body structure in the case
- * when an email contains another (quoted) RFC 2822 email.
- *
- * @param[in] bExtended generate BODYSTRUCTURE (true) or BODY (false) version
- * @param[out] strBodyStructure The generated body structure
- * @param[in] strMessage use this message to generate the output
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrGetBodyStructure(bool bExtended, string &strBodyStructure, const string& strMessage) {
- if (bExtended)
- return createIMAPProperties(strMessage, nullptr, nullptr, &strBodyStructure);
- return createIMAPProperties(strMessage, nullptr, &strBodyStructure, nullptr);
- }
- /**
- * Convert a MAPI FILETIME structure to a IMAP string. This string is
- * slightly differently formatted than an RFC 2822 string.
- *
- * Format: 01-Jan-2006 00:00:00 +0000
- *
- * @param[in] sFileTime time structure to convert
- *
- * @return date/time in string format
- */
- string IMAP::FileTimeToString(FILETIME sFileTime) {
- string strTime;
- char szBuffer[31];
- time_t sTime;
- struct tm ptr;
- sTime = FileTimeToUnixTime(sFileTime.dwHighDateTime, sFileTime.dwLowDateTime);
- gmtime_safe(&sTime, &ptr);
- strftime(szBuffer, 30, "%d-", &ptr);
- strTime += szBuffer;
- strTime += strMonth[ptr.tm_mon];
- strftime(szBuffer, 30, "-%Y %H:%M:%S +0000", &ptr);
- strTime += szBuffer;
- return strTime;
- }
- /**
- * Parses an IMAP date/time string into a MAPI FILETIME struct.
- *
- * @todo, rewrite this function, but it's not widely used (append + search, both slow anyway)
- *
- * @param[in] strTime parse this string into a FILETIME structure
- * @param[in] bDateOnly parse date part only (true) or add time (false)
- *
- * @return MAPI FILETIME structure
- */
- FILETIME IMAP::StringToFileTime(string strTime, bool bDateOnly) {
- FILETIME sFileTime;
- struct tm sTm;
- ULONG ulMonth;
- time_t sTime;
-
- sTm.tm_mday = 1;
- sTm.tm_mon = 0;
- sTm.tm_year = 100; // years since 1900
- sTm.tm_hour = 0;
- sTm.tm_min = 0;
- sTm.tm_sec = 0;
- sTm.tm_isdst = -1; // daylight saving time off
- // 01-Jan-2006 00:00:00 +0000
- if (strTime.size() < 2)
- goto done;
- if (strTime.at(1) == '-')
- strTime = " " + strTime;
- // day of month
- if (strTime.at(0) == ' ')
- sTm.tm_mday = atoi(strTime.substr(1, 1).c_str());
- else
- sTm.tm_mday = atoi(strTime.substr(0, 2).c_str());
- // month name 3 chars
- if (strTime.size() < 6)
- goto done;
- sTm.tm_mon = 0;
- for (ulMonth = 0; ulMonth < 12; ++ulMonth)
- if (CaseCompare(strMonth[ulMonth],strTime.substr(3, 3)))
- sTm.tm_mon = ulMonth;
- if (strTime.size() < 11)
- goto done;
- sTm.tm_year = atoi(strTime.substr(7, 4).c_str()) - 1900; // year 4 chars
- if (strTime.size() < 14)
- goto done;
- if (bDateOnly)
- goto done;
- sTm.tm_hour = atoi(strTime.substr(12, 2).c_str()); // hours
- if (strTime.size() < 17)
- goto done;
- sTm.tm_min = atoi(strTime.substr(15, 2).c_str()); // minutes
- if (strTime.size() < 20)
- goto done;
- sTm.tm_sec = atoi(strTime.substr(18, 2).c_str()); // seconds
- if (strTime.size() < 26)
- goto done;
- if (strTime.substr(21, 1) == "+") {
- sTm.tm_hour -= atoi(strTime.substr(22, 2).c_str());
- sTm.tm_min -= atoi(strTime.substr(24, 2).c_str());
- } else if (strTime.substr(21, 1) == "-") {
- sTm.tm_hour += atoi(strTime.substr(22, 2).c_str());
- sTm.tm_min += atoi(strTime.substr(24, 2).c_str());
- }
- done:
- sTime = timegm(&sTm);
- UnixTimeToFileTime(sTime, &sFileTime);
- return sFileTime;
- }
- /**
- * Add 24 hours to the given time struct
- *
- * @param[in] sFileTime Original time
- *
- * @return Input + 24 hours
- */
- FILETIME IMAP::AddDay(FILETIME sFileTime) {
- FILETIME sFT;
- // add 24 hour in seconds = 24*60*60 seconds
- UnixTimeToFileTime(FileTimeToUnixTime(sFileTime.dwHighDateTime, sFileTime.dwLowDateTime) + 24 * 60 * 60, &sFT);
- return sFT;
- }
- /**
- * Converts a unicode string to an encoded representation in a
- * specified charset. This function can return either quoted-printable
- * or base64 encoded data.
- *
- * @param[in] input string to escape in quoted-printable or base64
- * @param[in] charset charset for output string
- * @param[in] bIgnore add the //TRANSLIT or //IGNORE flag to iconv
- *
- * @return
- */
- string IMAP::EscapeString(WCHAR *input, std::string& charset, bool bIgnore)
- {
- std::string tmp;
- std::string iconvCharset = charset;
- if (bIgnore)
- setCharsetBestAttempt(iconvCharset);
- try {
- tmp = convert_to<std::string>(iconvCharset.c_str(), input, rawsize(input), CHARSET_WCHAR);
- } catch (const convert_exception &ce) {
- return "NIL";
- }
- // known charsets that are better represented in base64 than quoted-printable
- if (CaseCompare(charset, "UTF-8") || CaseCompare(charset, "ISO-2022-JP"))
- return "\"" + ToQuotedBase64Header(tmp, charset) + "\"";
- else
- return "\"" + ToQuotedPrintable(tmp, charset, true, true) + "\"";
- }
- /**
- * Escapes input string with \ character for specified characters.
- *
- * @param[in] input string to escape
- *
- * @return escaped string
- */
- string IMAP::EscapeStringQT(const string &input) {
- string s;
- unsigned int i;
- /*
- * qtext = NO-WS-CTL / ; Non white space controls
- * %d33 / ; The rest of the US-ASCII
- * %d35-91 / ; characters not including "\"
- * %d93-126 ; or the quote character
- */
- s.reserve(input.length() * 2); // worst-case, only short strings are passing in this function
- s.append(1, '"');
- // We quote NO-WS-CTL anyway, just to be sure
- for (i = 0; i < input.length(); ++i) {
- if (input[i] == 33 || (input[i] >= 35 && input[i] <= 91) || (input[i] >= 93 && input[i] <= 126))
- s += input[i];
- else if (input[i] == 34) {
- // " found, should send literal and data
- return "{" + stringify(input.length()) + "}\n" + input;
- } else {
- s.append(1, '\\');
- s += input[i];
- }
- }
- s.append(1, '"');
- return s;
- }
- /**
- * @brief Converts an unicode string to modified UTF-7
- *
- * IMAP folder encoding is a modified form of utf-7 (+ becomes &, so & is "escaped",
- * utf-7 is a modifed form of base64, based from the utf16 character
- * I'll use the iconv convertor for this, per character .. sigh
- *
- * @param[in] input unicode string to convert
- * @param[out] output valid IMAP folder name to send to the client
- * @return MAPI Error code
- */
- HRESULT IMAP::MAPI2IMAPCharset(const wstring &input, string &output) {
- size_t i;
- convert_context converter;
- output.clear();
- output.reserve(input.size() * 2);
- for (i = 0; i < input.length(); ++i) {
- if ( (input[i] >= 0x20 && input[i] <= 0x25) || (input[i] >= 0x27 && input[i] <= 0x7e) ) {
- if (input[i] == '"' || input[i] == '\\')
- output += '\\';
- output += input[i];
- } else if (input[i] == 0x26) {
- output += "&-"; // & is encoded as &-
- } else {
- wstring conv;
- string utf7;
- conv = input[i];
- while (i+1 < input.length() && (input[i+1] < 0x20 || input[i+1] >= 0x7f))
- conv += input[++i];
- try {
- utf7 = converter.convert_to<string>("UTF-7", conv, rawsize(conv), CHARSET_WCHAR);
- } catch(...) {
- return MAPI_E_BAD_CHARWIDTH;
- }
- utf7[0] = '&'; // convert + to &
- for (size_t j = 0; j < utf7.size(); ++j)
- if (utf7[j] == '/')
- utf7[j] = ','; // convert / from base64 to ,
- output += utf7; // also contains the terminating -
- }
- }
- return hrSuccess;
- }
- /**
- * Converts an IMAP encoded folder string to an unicode string.
- *
- * @param[in] input IMAP folder name, in modified UTF-7
- * @param[out] output widestring version of input
- * @return MAPI Error code
- */
- HRESULT IMAP::IMAP2MAPICharset(const string& input, wstring& output) {
- size_t i;
- convert_context converter;
- output.clear();
- output.reserve(input.size());
- for (i = 0; i < input.length(); ++i) {
- if (input[i] < 0 || input[i] > 127)
- return MAPI_E_BAD_CHARWIDTH;
- if (input[i] != '&') {
- if (input[i] == '\\' && i+1 < input.length() && (input[i+1] == '"' || input[i+1] == '\\'))
- ++i;
- output += input[i];
- continue;
- }
- if (i+1 >= input.length()) {
- // premature end of string
- output += input[i];
- break;
- }
- if (input[i+1] == '-') {
- output += '&';
- ++i; // skip '-'
- continue;
- }
- string conv = "+";
- ++i; // skip imap '&', is a '+' in utf-7
- while (i < input.length() && input[i] != '-') {
- if (input[i] == ',')
- conv += '/'; // , -> / for utf-7
- else
- conv += input[i];
- ++i;
- }
- try {
- output += converter.convert_to<wstring>(CHARSET_WCHAR, conv, rawsize(conv), "UTF-7");
- } catch(...) {
- return MAPI_E_BAD_CHARWIDTH;
- }
- }
- return hrSuccess;
- }
- /**
- * Check for a MAPI LIST/LSUB pattern in a given foldername.
- *
- * @param[in] strFolder folderpath to match
- * @param[in] strPattern Pattern to match with, in uppercase.
- *
- * @return whether folder matches
- */
- bool IMAP::MatchFolderPath(wstring strFolder, const wstring& strPattern)
- {
- bool bMatch = false;
- int f = 0;
- int p = 0;
-
- strFolder = strToUpper(strFolder);
-
- while(1) {
- if (f == static_cast<int>(strFolder.size()) &&
- p == static_cast<int>(strPattern.size()))
- // Reached the end of the folder and the pattern strings, so match
- return true;
-
- if(strPattern[p] == '*') {
- // Match 0-n chars, try longest match first
- for (int i = strFolder.size(); i >= f; --i)
- // Try matching the rest of the string from position i in the string
- if (MatchFolderPath(strFolder.substr(i), strPattern.substr(p + 1)))
- // Match OK, apply the 'skip i' chars
- return true;
-
- // No match found, failed
- return false;
- } else if(strPattern[p] == '%') {
- // Match 0-n chars excluding '/', try longest match first
- size_t slash = strFolder.find('/', f);
- if(slash == std::string::npos)
- slash = strFolder.size();
- for (int i = slash; i >= f; --i)
- // Try matching the rest of the string from position i in the string
- if (MatchFolderPath(strFolder.substr(i), strPattern.substr(p + 1)))
- // Match OK, apply the 'skip i' chars
- return true;
- // No match found, failed
- return false;
- } else {
- // Match normal string
- if(strFolder[f] != strPattern[p])
- break;
- ++f;
- ++p;
- }
- }
- return bMatch;
- }
- /**
- * Parse RFC 2822 email headers in to a list of string <name, value> pairs.
- *
- * @param[in] strHeaders Email headers to parse (this data will be modified and should not be used after this function)
- * @param[out] lstHeaders list of headers, in header / value pairs
- */
- void IMAP::HrParseHeaders(const string &strHeaders, list<pair<string, string> > &lstHeaders)
- {
- size_t pos = 0;
- string strLine;
- string strField;
- string strData;
- list<pair<string, string> >::iterator iterLast;
-
- lstHeaders.clear();
- iterLast = lstHeaders.end();
-
- while(1) {
- size_t end = strHeaders.find("\r\n", pos);
-
- if (end == string::npos)
- strLine = strHeaders.substr(pos);
- else
- strLine = strHeaders.substr(pos, end-pos);
- if (strLine.empty())
- break; // parsed all headers
- if((strLine[0] == ' ' || strLine[0] == '\t') && iterLast != lstHeaders.end()) {
- // Continuation of previous header
- iterLast->second += "\r\n" + strLine;
- } else {
- size_t colon = strLine.find(":");
-
- if(colon != string::npos) {
- // Get field name
- strField = strLine.substr(0, colon);
-
- strData = strLine.substr(colon+1);
-
- // Remove leading spaces
- while (strData[0] == ' ')
- strData.erase(0,1);
- lstHeaders.push_back(pair<string, string>(strField, strData));
- iterLast = --lstHeaders.end();
- }
- // else: Broken header ? (no :)
- }
-
- if(end == string::npos)
- break;
-
- pos = end + 2; // Go to next line (+2 = \r\n)
- }
- }
- /**
- * Find a substring in strInput that starts with strBegin, and
- * possebly ends with strEnd. Only the data between the strBegin and
- * strEnd is returned.
- *
- * @param[out] strOutput The found substring in strInput
- * @param[in] strInput Input string
- * @param[in] strBegin Substring should start with
- * @param[in] strEnd Substring should end with
- */
- void IMAP::HrGetSubString(std::string &strOutput, const std::string &strInput,
- const std::string &strBegin, const std::string &strEnd)
- {
- size_t begin;
- size_t end;
- strOutput.clear();
- begin = strInput.find(strBegin);
- if(begin == string::npos)
- return;
-
- end = strInput.find(strEnd, begin+1);
- if(end == string::npos)
- strOutput = strInput.substr(begin+1);
- else
- strOutput = strInput.substr(begin+1, end-begin-1);
- }
- /**
- * Make a set of tokens from a string, separated by spaces.
- *
- * @param[out] setTokens A set of strings from input
- * @param[in] strInput split by spaces into the set
- */
- void IMAP::HrTokenize(std::set<std::string> &setTokens,
- const std::string &strInput)
- {
- vector<string> lstTokens = tokenize(strInput, " ");
- setTokens.clear();
- std::copy(lstTokens.begin(), lstTokens.end(), std::inserter(setTokens, setTokens.begin()));
- }
- /**
- * Find the MAPI folder from a full folder path
- *
- * @param[in] strFolder The full folder path
- * @param[in] bReadOnly open read-only or with write access (if possible)
- * @param[out] lppFolder The MAPI folder corresponding to the given name
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrFindFolder(const wstring& strFolder, bool bReadOnly, IMAPIFolder **lppFolder)
- {
- HRESULT hr = hrSuccess;
- ULONG cbEntryID = 0;
- memory_ptr<ENTRYID> lpEntryID;
- ULONG ulObjType = 0;
- object_ptr<IMAPIFolder> lpFolder;
- ULONG ulFlags = 0;
- if (!bReadOnly)
- ulFlags |= MAPI_MODIFY;
- hr = HrFindFolderEntryID(strFolder, &cbEntryID, &~lpEntryID);
- if(hr != hrSuccess)
- return hr;
- hr = lpSession->OpenEntry(cbEntryID, lpEntryID, nullptr, ulFlags, &ulObjType, &~lpFolder);
- if(hr != hrSuccess)
- return hr;
- if (ulObjType != MAPI_FOLDER)
- return MAPI_E_INVALID_PARAMETER;
- *lppFolder = lpFolder.release();
- return hrSuccess;
- }
- /**
- * Find an EntryID of a folder from a full folder path
- *
- * @param[in] strFolder Full path of a folder to find the MAPI EntryID for
- * @param[out] lpcbEntryID number of bytes in lppEntryID
- * @param[in] lppEntryID The EntryID of the given folder
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrFindFolderEntryID(const wstring& strFolder, ULONG *lpcbEntryID, LPENTRYID *lppEntryID)
- {
- HRESULT hr = hrSuccess;
- list<SFolder> tmp_folders;
- list<SFolder> *folders = &cached_folders;
- bool should_cache_folders = cache_folders_time_limit > 0;
- time_t expire_time = cache_folders_last_used + cache_folders_time_limit;
- if (should_cache_folders &&
- (std::time(nullptr) > expire_time || !cached_folders.size())) {
- HrGetFolderList(cached_folders);
- cache_folders_last_used = std::time(nullptr);
- }
- else if (!should_cache_folders) {
- HrGetFolderList(tmp_folders);
- folders = &tmp_folders;
- }
- wstring find_folder = strFolder;
- if (find_folder.length() == 0)
- return MAPI_E_NOT_FOUND;
- if (find_folder[0] != '/')
- find_folder = wstring(L"/") + find_folder;
- find_folder = strToUpper(find_folder);
- auto iter = folders->cbegin();
- for (; iter != folders->cend(); iter++) {
- wstring folder_name;
- hr = HrGetFolderPath(iter, *folders, folder_name);
- if (hr != hrSuccess)
- return hr;
- folder_name = strToUpper(folder_name);
- if (folder_name == find_folder)
- break;
- }
- if (iter == folders->cend())
- return MAPI_E_NOT_FOUND;
- *lpcbEntryID = iter->sEntryID.cb;
- hr = MAPIAllocateBuffer(*lpcbEntryID, (void **)lppEntryID);
- if (hr != hrSuccess)
- return hr;
- memcpy(*lppEntryID, iter->sEntryID.lpb, *lpcbEntryID);
- return hrSuccess;
- }
- /**
- * Find the EntryID for a named subfolder in a given MAPI Folder
- *
- * @param[in] lpFolder The parent folder to find strFolder in, or NULL when no parent is present yet.
- * When no parent is present, one will be found as either it's: INBOX, "public", or the user store.
- * @param[in] strFolder The name of the subfolder to find
- * @param[out] lpcbEntryID number of bytes in lppEntryID
- * @param[out] lppEntryID The EntryID of the folder
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrFindSubFolder(IMAPIFolder *lpFolder, const wstring& strFolder, ULONG *lpcbEntryID, LPENTRYID *lppEntryID)
- {
- HRESULT hr = hrSuccess;
- object_ptr<IMAPITable> lpTable;
- SPropValue sProp;
- static constexpr const SizedSPropTagArray(2, sptaCols) =
- {2, {PR_ENTRYID, PR_DISPLAY_NAME_W}};
- LPENTRYID lpEntryID = NULL;
- ULONG cbEntryID = 0;
- memory_ptr<SPropValue> lpProp;
- object_ptr<IMAPIFolder> lpSubTree;
- ULONG ulObjType = 0;
- sProp.ulPropTag = PR_DISPLAY_NAME_W;
- sProp.Value.lpszW = (WCHAR *)strFolder.c_str();
-
- // lpFolder is NULL when we're referring to the IMAP root. The IMAP root contains
- // INBOX, the public folder container, and all folders under the users IPM_SUBTREE.
-
- if(lpFolder == NULL) {
- if(wcscasecmp(strFolder.c_str(), L"INBOX") == 0) {
- // Inbox request, we know where that is.
- return lpStore->GetReceiveFolder((LPTSTR)"IPM", 0, lpcbEntryID, lppEntryID, nullptr);
- } else if(wcscasecmp(strFolder.c_str(), PUBLIC_FOLDERS_NAME) == 0) {
- // Public folders requested, we know where that is too
- if (lpPublicStore == nullptr)
- return MAPI_E_NOT_FOUND;
- hr = HrGetOneProp(lpPublicStore, PR_IPM_PUBLIC_FOLDERS_ENTRYID, &~lpProp);
- if(hr != hrSuccess)
- return hr;
- cbEntryID = lpProp->Value.bin.cb;
- hr = MAPIAllocateBuffer(cbEntryID, (void **)&lpEntryID);
- if(hr != hrSuccess)
- return hr;
- memcpy(lpEntryID, lpProp->Value.bin.lpb, cbEntryID);
-
- *lppEntryID = lpEntryID;
- *lpcbEntryID = cbEntryID;
- return hr;
- } else {
- // Other folder in the root requested, use normal search algorithm to find it
- // under IPM_SUBTREE
- hr = HrGetOneProp(lpStore, PR_IPM_SUBTREE_ENTRYID, &~lpProp);
- if(hr != hrSuccess)
- return hr;
- hr = lpStore->OpenEntry(lpProp->Value.bin.cb, reinterpret_cast<ENTRYID *>(lpProp->Value.bin.lpb), nullptr, 0, &ulObjType, &~lpSubTree);
- if(hr != hrSuccess)
- return hr;
-
- lpFolder = lpSubTree;
- // Fall through to normal folder lookup code
- }
- }
-
- // Use a restriction to find the folder in the hierarchy table
- hr = lpFolder->GetHierarchyTable(MAPI_DEFERRED_ERRORS, &~lpTable);
- if(hr != hrSuccess)
- return hr;
- hr = lpTable->SetColumns(sptaCols, 0);
- if(hr != hrSuccess)
- return hr;
- hr = ECPropertyRestriction(RELOP_EQ, PR_DISPLAY_NAME_W, &sProp, ECRestriction::Cheap)
- .RestrictTable(lpTable, TBL_BATCH);
- if (hr != hrSuccess)
- return hr;
- rowset_ptr lpRowSet;
- hr = lpTable->QueryRows(1, 0, &~lpRowSet);
- if (hr != hrSuccess)
- return hr;
- if (lpRowSet->cRows == 0)
- return MAPI_E_NOT_FOUND;
- if (lpRowSet->aRow[0].lpProps[0].ulPropTag != PR_ENTRYID)
- return MAPI_E_INVALID_PARAMETER;
-
- cbEntryID = lpRowSet->aRow[0].lpProps[0].Value.bin.cb;
- hr = MAPIAllocateBuffer(cbEntryID, (void **)&lpEntryID);
- if(hr != hrSuccess)
- return hr;
-
- memcpy(lpEntryID, lpRowSet->aRow[0].lpProps[0].Value.bin.lpb, cbEntryID);
-
- *lppEntryID = lpEntryID;
- *lpcbEntryID = cbEntryID;
- return hrSuccess;
- }
- /**
- * Find the deepest folder in strFolder and return that IMAPIFolder
- * and the remaining folders which were not found.
- * If no folders are found from strFolder at all, return the IPM subtree.
- *
- * @param[in] strFolder Folder string complete path to find deepest IMAPIFolder for
- * @param[out] lppFolder Last IMAPIFolder found in strFolder
- * @param[out] strNotFound Folders not found in strFolder
- */
- HRESULT IMAP::HrFindFolderPartial(const wstring& strFolder, IMAPIFolder **lppFolder, wstring *strNotFound)
- {
- HRESULT hr = hrSuccess;
- vector<wstring> vFolders;
- ULONG cbEntryID = 0;
- memory_ptr<ENTRYID> lpEntryID;
- object_ptr<IMAPIFolder> lpFolder;
- ULONG ulObjType = 0;
- memory_ptr<SPropValue> lpTree;
- unsigned int i = 0;
-
- hr = HrSplitPath(strFolder, vFolders);
- if(hr != hrSuccess)
- return hr;
-
- // Loop through all the path parts until we find a part that we can't find
- for (i = 0; i < vFolders.size(); ++i) {
- hr = HrFindSubFolder(lpFolder, vFolders[i], &cbEntryID, &~lpEntryID);
- if(hr != hrSuccess) {
- hr = hrSuccess; // Not an error
- break;
- }
- hr = lpSession->OpenEntry(cbEntryID, lpEntryID, nullptr, MAPI_MODIFY, &ulObjType, &~lpFolder);
- if(hr != hrSuccess)
- return hr;
- }
-
- // Remove parts that we already have processed
- vFolders.erase(vFolders.begin(),vFolders.begin()+i);
- // The remaining path parts are the relative path that we could not find
- hr = HrUnsplitPath(vFolders, *strNotFound);
- if(hr != hrSuccess)
- return hr;
- if(lpFolder == NULL) {
- hr = HrGetOneProp(lpStore, PR_IPM_SUBTREE_ENTRYID, &~lpTree);
- if(hr != hrSuccess)
- return hr;
- hr = lpSession->OpenEntry(lpTree->Value.bin.cb, reinterpret_cast<ENTRYID *>(lpTree->Value.bin.lpb), nullptr, MAPI_MODIFY, &ulObjType, &~lpFolder);
- if(hr != hrSuccess)
- return hr;
- }
- *lppFolder = lpFolder.release();
- return hrSuccess;
- }
- /**
- * Special MAPI Folders are blocked to delete, rename or unsubscribe from.
- *
- * @param[in] lpFolder MAPI Folder to check
- *
- * @return Special (true) or not (false)
- */
- bool IMAP::IsSpecialFolder(IMAPIFolder *lpFolder)
- {
- memory_ptr<SPropValue> lpProp;
- if (HrGetOneProp(lpFolder, PR_ENTRYID, &~lpProp) != hrSuccess)
- return false;
- return IsSpecialFolder(lpProp->Value.bin.cb, reinterpret_cast<ENTRYID *>(lpProp->Value.bin.lpb));
- }
- /**
- * Check if this folder contains e-mail
- *
- * @param[in] lpFolder MAPI Folder to check
- *
- * @return may contain e-mail (true) or not (false)
- */
- bool IMAP::IsMailFolder(IMAPIFolder *lpFolder)
- {
- memory_ptr<SPropValue> lpProp;
- if (HrGetOneProp(lpFolder, PR_CONTAINER_CLASS_A, &~lpProp) != hrSuccess)
- // if the property is missing, treat it as an email folder
- return true;
- return strcasecmp(lpProp->Value.lpszA, "IPM") == 0 ||
- strcasecmp(lpProp->Value.lpszA, "IPF.NOTE") == 0;
- }
- bool IMAP::IsSentItemFolder(IMAPIFolder *lpFolder)
- {
- ULONG ulResult = FALSE;
- memory_ptr<SPropValue> lpProp, lpPropStore;
- HRESULT hr = HrGetOneProp(lpFolder, PR_ENTRYID, &~lpProp);
- if (hr != hrSuccess)
- return false;
- hr = HrGetOneProp(lpStore, PR_IPM_SENTMAIL_ENTRYID, &~lpPropStore);
- if (hr != hrSuccess)
- return false;
- hr = lpStore->CompareEntryIDs(lpProp->Value.bin.cb, (LPENTRYID)lpProp->Value.bin.lpb, lpPropStore->Value.bin.cb, (LPENTRYID)lpPropStore->Value.bin.lpb , 0, &ulResult);
- if (hr != hrSuccess)
- return false;
- return ulResult;
- }
- /**
- * Return the parent folder for an EntryID
- *
- * @param[in] cbEntryID number of bytes in lpEntryID
- * @param[in] lpEntryID EntryID of a folder
- * @param[out] lppFolder Parent MAPI Folder of given EntryID
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrOpenParentFolder(ULONG cbEntryID, LPENTRYID lpEntryID, IMAPIFolder **lppFolder)
- {
- HRESULT hr = hrSuccess;
- object_ptr<IMAPIFolder> lpFolder;
- ULONG ulObjType = 0;
- hr = lpSession->OpenEntry(cbEntryID, lpEntryID, nullptr, MAPI_MODIFY, &ulObjType, &~lpFolder);
- if (hr != hrSuccess)
- return hr;
- if (ulObjType != MAPI_FOLDER)
- return MAPI_E_NOT_FOUND;
- return HrOpenParentFolder(lpFolder, lppFolder);
- }
- /**
- * Open parent MAPI folder for given MAPI folder
- *
- * @param[in] lpFolder MAPI Folder to open the parent folder for
- * @param[out] lppFolder Parent folder
- *
- * @return MAPI Error code
- */
- HRESULT IMAP::HrOpenParentFolder(IMAPIFolder *lpFolder, IMAPIFolder **lppFolder)
- {
- memory_ptr<SPropValue> lpParent;
- ULONG ulObjType = 0;
-
- HRESULT hr = HrGetOneProp(lpFolder, PR_PARENT_ENTRYID, &~lpParent);
- if(hr != hrSuccess)
- return hr;
- return lpSession->OpenEntry(lpParent->Value.bin.cb,
- reinterpret_cast<ENTRYID *>(lpParent->Value.bin.lpb), nullptr,
- MAPI_MODIFY, &ulObjType, reinterpret_cast<IUnknown **>(lppFolder));
- }
- /** @} */
|