imap4.py 207 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248
  1. # -*- test-case-name: twisted.mail.test.test_imap.IMAP4HelperTests -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. An IMAP4 protocol implementation
  6. @author: Jp Calderone
  7. To do::
  8. Suspend idle timeout while server is processing
  9. Use an async message parser instead of buffering in memory
  10. Figure out a way to not queue multi-message client requests (Flow? A simple callback?)
  11. Clarify some API docs (Query, etc)
  12. Make APPEND recognize (again) non-existent mailboxes before accepting the literal
  13. """
  14. import binascii
  15. import codecs
  16. import copy
  17. import email.utils
  18. import functools
  19. import re
  20. import string
  21. import tempfile
  22. import time
  23. import uuid
  24. from base64 import decodebytes, encodebytes
  25. from io import BytesIO
  26. from itertools import chain
  27. from typing import Any, List, Optional, cast
  28. from zope.interface import implementer
  29. from twisted.cred import credentials
  30. from twisted.cred.error import UnauthorizedLogin, UnhandledCredentials
  31. from twisted.internet import defer, error, interfaces
  32. from twisted.internet.defer import maybeDeferred
  33. from twisted.mail._cred import (
  34. CramMD5ClientAuthenticator,
  35. LOGINAuthenticator,
  36. LOGINCredentials,
  37. PLAINAuthenticator,
  38. PLAINCredentials,
  39. )
  40. from twisted.mail._except import (
  41. IllegalClientResponse,
  42. IllegalIdentifierError,
  43. IllegalMailboxEncoding,
  44. IllegalOperation,
  45. IllegalQueryError,
  46. IllegalServerResponse,
  47. IMAP4Exception,
  48. MailboxCollision,
  49. MailboxException,
  50. MismatchedNesting,
  51. MismatchedQuoting,
  52. NegativeResponse,
  53. NoSuchMailbox,
  54. NoSupportedAuthentication,
  55. ReadOnlyMailbox,
  56. UnhandledResponse,
  57. )
  58. # Re-exported for compatibility reasons
  59. from twisted.mail.interfaces import (
  60. IAccountIMAP as IAccount,
  61. IClientAuthentication,
  62. ICloseableMailboxIMAP as ICloseableMailbox,
  63. IMailboxIMAP as IMailbox,
  64. IMailboxIMAPInfo as IMailboxInfo,
  65. IMailboxIMAPListener as IMailboxListener,
  66. IMessageIMAP as IMessage,
  67. IMessageIMAPCopier as IMessageCopier,
  68. IMessageIMAPFile as IMessageFile,
  69. IMessageIMAPPart as IMessagePart,
  70. INamespacePresenter,
  71. ISearchableIMAPMailbox as ISearchableMailbox,
  72. )
  73. from twisted.protocols import basic, policies
  74. from twisted.python import log, text
  75. from twisted.python.compat import (
  76. _matchingString,
  77. iterbytes,
  78. nativeString,
  79. networkString,
  80. )
  81. # locale-independent month names to use instead of strftime's
  82. _MONTH_NAMES = dict(
  83. zip(range(1, 13), "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split())
  84. )
  85. def _swap(this, that, ifIs):
  86. """
  87. Swap C{this} with C{that} if C{this} is C{ifIs}.
  88. @param this: The object that may be replaced.
  89. @param that: The object that may replace C{this}.
  90. @param ifIs: An object whose identity will be compared to
  91. C{this}.
  92. """
  93. return that if this is ifIs else this
  94. def _swapAllPairs(of, that, ifIs):
  95. """
  96. Swap each element in each pair in C{of} with C{that} it is
  97. C{ifIs}.
  98. @param of: A list of 2-L{tuple}s, whose members may be the object
  99. C{that}
  100. @type of: L{list} of 2-L{tuple}s
  101. @param ifIs: An object whose identity will be compared to members
  102. of each pair in C{of}
  103. @return: A L{list} of 2-L{tuple}s with all occurences of C{ifIs}
  104. replaced with C{that}
  105. """
  106. return [
  107. (_swap(first, that, ifIs), _swap(second, that, ifIs)) for first, second in of
  108. ]
  109. class MessageSet:
  110. """
  111. A set of message identifiers usable by both L{IMAP4Client} and
  112. L{IMAP4Server} via L{IMailboxIMAP.store} and
  113. L{IMailboxIMAP.fetch}.
  114. These identifiers can be either message sequence numbers or unique
  115. identifiers. See Section 2.3.1, "Message Numbers", RFC 3501.
  116. This represents the C{sequence-set} described in Section 9,
  117. "Formal Syntax" of RFC 3501:
  118. - A L{MessageSet} can describe a single identifier, e.g.
  119. C{MessageSet(1)}
  120. - A L{MessageSet} can describe C{*} via L{None}, e.g.
  121. C{MessageSet(None)}
  122. - A L{MessageSet} can describe a range of identifiers, e.g.
  123. C{MessageSet(1, 2)}. The range is inclusive and unordered
  124. (see C{seq-range} in RFC 3501, Section 9), so that
  125. C{Message(2, 1)} is equivalent to C{MessageSet(1, 2)}, and
  126. both describe messages 1 and 2. Ranges can include C{*} by
  127. specifying L{None}, e.g. C{MessageSet(None, 1)}. In all
  128. cases ranges are normalized so that the smallest identifier
  129. comes first, and L{None} always comes last; C{Message(2, 1)}
  130. becomes C{MessageSet(1, 2)} and C{MessageSet(None, 1)}
  131. becomes C{MessageSet(1, None)}
  132. - A L{MessageSet} can describe a sequence of single
  133. identifiers and ranges, constructed by addition.
  134. C{MessageSet(1) + MessageSet(5, 10)} refers the message
  135. identified by C{1} and the messages identified by C{5}
  136. through C{10}.
  137. B{NB: The meaning of * varies, but it always represents the
  138. largest number in use}.
  139. B{For servers}: Your L{IMailboxIMAP} provider must set
  140. L{MessageSet.last} to the highest-valued identifier (unique or
  141. message sequence) before iterating over it.
  142. B{For clients}: C{*} consumes ranges smaller than it, e.g.
  143. C{MessageSet(1, 100) + MessageSet(50, None)} is equivalent to
  144. C{1:*}.
  145. @type getnext: Function taking L{int} returning L{int}
  146. @ivar getnext: A function that returns the next message number,
  147. used when iterating through the L{MessageSet}. By default, a
  148. function returning the next integer is supplied, but as this
  149. can be rather inefficient for sparse UID iterations, it is
  150. recommended to supply one when messages are requested by UID.
  151. The argument is provided as a hint to the implementation and
  152. may be ignored if it makes sense to do so (eg, if an iterator
  153. is being used that maintains its own state, it is guaranteed
  154. that it will not be called out-of-order).
  155. """
  156. _empty: List[Any] = []
  157. _infinity = float("inf")
  158. def __init__(self, start=_empty, end=_empty):
  159. """
  160. Create a new MessageSet()
  161. @type start: Optional L{int}
  162. @param start: Start of range, or only message number
  163. @type end: Optional L{int}
  164. @param end: End of range.
  165. """
  166. self._last = self._empty # Last message/UID in use
  167. self.ranges = [] # List of ranges included
  168. self.getnext = lambda x: x + 1 # A function which will return the next
  169. # message id. Handy for UID requests.
  170. if start is self._empty:
  171. return
  172. if isinstance(start, list):
  173. self.ranges = start[:]
  174. self.clean()
  175. else:
  176. self.add(start, end)
  177. @property
  178. def last(self):
  179. """
  180. The largest number in use.
  181. This is undefined until it has been set by assigning to this property.
  182. """
  183. return self._last
  184. @last.setter
  185. def last(self, value):
  186. """
  187. Replaces all occurrences of "*". This should be the
  188. largest number in use. Must be set before attempting to
  189. use the MessageSet as a container.
  190. @raises ValueError: if a largest value has already been set.
  191. """
  192. if self._last is not self._empty:
  193. raise ValueError("last already set")
  194. self._last = value
  195. for i, (low, high) in enumerate(self.ranges):
  196. if low is None:
  197. low = value
  198. if high is None:
  199. high = value
  200. if low > high:
  201. low, high = high, low
  202. self.ranges[i] = (low, high)
  203. self.clean()
  204. def add(self, start, end=_empty):
  205. """
  206. Add another range
  207. @type start: L{int}
  208. @param start: Start of range, or only message number
  209. @type end: Optional L{int}
  210. @param end: End of range.
  211. """
  212. if end is self._empty:
  213. end = start
  214. if self._last is not self._empty:
  215. if start is None:
  216. start = self.last
  217. if end is None:
  218. end = self.last
  219. start, end = sorted(
  220. [start, end], key=functools.partial(_swap, that=self._infinity, ifIs=None)
  221. )
  222. self.ranges.append((start, end))
  223. self.clean()
  224. def __add__(self, other):
  225. if isinstance(other, MessageSet):
  226. ranges = self.ranges + other.ranges
  227. return MessageSet(ranges)
  228. else:
  229. res = MessageSet(self.ranges)
  230. if self.last is not self._empty:
  231. res.last = self.last
  232. try:
  233. res.add(*other)
  234. except TypeError:
  235. res.add(other)
  236. return res
  237. def extend(self, other):
  238. """
  239. Extend our messages with another message or set of messages.
  240. @param other: The messages to include.
  241. @type other: L{MessageSet}, L{tuple} of two L{int}s, or a
  242. single L{int}
  243. """
  244. if isinstance(other, MessageSet):
  245. self.ranges.extend(other.ranges)
  246. self.clean()
  247. else:
  248. try:
  249. self.add(*other)
  250. except TypeError:
  251. self.add(other)
  252. return self
  253. def clean(self):
  254. """
  255. Clean ranges list, combining adjacent ranges
  256. """
  257. ranges = sorted(_swapAllPairs(self.ranges, that=self._infinity, ifIs=None))
  258. mergedRanges = [(float("-inf"), float("-inf"))]
  259. for low, high in ranges:
  260. previousLow, previousHigh = mergedRanges[-1]
  261. if previousHigh < low - 1:
  262. mergedRanges.append((low, high))
  263. continue
  264. mergedRanges[-1] = (min(previousLow, low), max(previousHigh, high))
  265. self.ranges = _swapAllPairs(mergedRanges[1:], that=None, ifIs=self._infinity)
  266. def _noneInRanges(self):
  267. """
  268. Is there a L{None} in our ranges?
  269. L{MessageSet.clean} merges overlapping or consecutive ranges.
  270. None is represents a value larger than any number. There are
  271. thus two cases:
  272. 1. C{(x, *) + (y, z)} such that C{x} is smaller than C{y}
  273. 2. C{(z, *) + (x, y)} such that C{z} is larger than C{y}
  274. (Other cases, such as C{y < x < z}, can be split into these
  275. two cases; for example C{(y - 1, y)} + C{(x, x) + (z, z + 1)})
  276. In case 1, C{* > y} and C{* > z}, so C{(x, *) + (y, z) = (x,
  277. *)}
  278. In case 2, C{z > x and z > y}, so the intervals do not merge,
  279. and the ranges are sorted as C{[(x, y), (z, *)]}. C{*} is
  280. represented as C{(*, *)}, so this is the same as 2. but with
  281. a C{z} that is greater than everything.
  282. The result is that there is a maximum of two L{None}s, and one
  283. of them has to be the high element in the last tuple in
  284. C{self.ranges}. That means checking if C{self.ranges[-1][-1]}
  285. is L{None} suffices to check if I{any} element is L{None}.
  286. @return: L{True} if L{None} is in some range in ranges and
  287. L{False} if otherwise.
  288. """
  289. return self.ranges[-1][-1] is None
  290. def __contains__(self, value):
  291. """
  292. May raise TypeError if we encounter an open-ended range
  293. @param value: Is this in our ranges?
  294. @type value: L{int}
  295. """
  296. if self._noneInRanges():
  297. raise TypeError("Can't determine membership; last value not set")
  298. for low, high in self.ranges:
  299. if low <= value <= high:
  300. return True
  301. return False
  302. def _iterator(self):
  303. for l, h in self.ranges:
  304. l = self.getnext(l - 1)
  305. while l <= h:
  306. yield l
  307. l = self.getnext(l)
  308. def __iter__(self):
  309. if self._noneInRanges():
  310. raise TypeError("Can't iterate; last value not set")
  311. return self._iterator()
  312. def __len__(self):
  313. res = 0
  314. for l, h in self.ranges:
  315. if l is None:
  316. res += 1
  317. elif h is None:
  318. raise TypeError("Can't size object; last value not set")
  319. else:
  320. res += (h - l) + 1
  321. return res
  322. def __str__(self) -> str:
  323. p = []
  324. for low, high in self.ranges:
  325. if low == high:
  326. if low is None:
  327. p.append("*")
  328. else:
  329. p.append(str(low))
  330. elif high is None:
  331. p.append("%d:*" % (low,))
  332. else:
  333. p.append("%d:%d" % (low, high))
  334. return ",".join(p)
  335. def __repr__(self) -> str:
  336. return f"<MessageSet {str(self)}>"
  337. def __eq__(self, other: object) -> bool:
  338. if isinstance(other, MessageSet):
  339. return cast(bool, self.ranges == other.ranges)
  340. return NotImplemented
  341. class LiteralString:
  342. def __init__(self, size, defered):
  343. self.size = size
  344. self.data = []
  345. self.defer = defered
  346. def write(self, data):
  347. self.size -= len(data)
  348. passon = None
  349. if self.size > 0:
  350. self.data.append(data)
  351. else:
  352. if self.size:
  353. data, passon = data[: self.size], data[self.size :]
  354. else:
  355. passon = b""
  356. if data:
  357. self.data.append(data)
  358. return passon
  359. def callback(self, line):
  360. """
  361. Call deferred with data and rest of line
  362. """
  363. self.defer.callback((b"".join(self.data), line))
  364. class LiteralFile:
  365. _memoryFileLimit = 1024 * 1024 * 10
  366. def __init__(self, size, defered):
  367. self.size = size
  368. self.defer = defered
  369. if size > self._memoryFileLimit:
  370. self.data = tempfile.TemporaryFile()
  371. else:
  372. self.data = BytesIO()
  373. def write(self, data):
  374. self.size -= len(data)
  375. passon = None
  376. if self.size > 0:
  377. self.data.write(data)
  378. else:
  379. if self.size:
  380. data, passon = data[: self.size], data[self.size :]
  381. else:
  382. passon = b""
  383. if data:
  384. self.data.write(data)
  385. return passon
  386. def callback(self, line):
  387. """
  388. Call deferred with data and rest of line
  389. """
  390. self.data.seek(0, 0)
  391. self.defer.callback((self.data, line))
  392. class WriteBuffer:
  393. """
  394. Buffer up a bunch of writes before sending them all to a transport at once.
  395. """
  396. def __init__(self, transport, size=8192):
  397. self.bufferSize = size
  398. self.transport = transport
  399. self._length = 0
  400. self._writes = []
  401. def write(self, s):
  402. self._length += len(s)
  403. self._writes.append(s)
  404. if self._length > self.bufferSize:
  405. self.flush()
  406. def flush(self):
  407. if self._writes:
  408. self.transport.writeSequence(self._writes)
  409. self._writes = []
  410. self._length = 0
  411. class Command:
  412. _1_RESPONSES = (
  413. b"CAPABILITY",
  414. b"FLAGS",
  415. b"LIST",
  416. b"LSUB",
  417. b"STATUS",
  418. b"SEARCH",
  419. b"NAMESPACE",
  420. )
  421. _2_RESPONSES = (b"EXISTS", b"EXPUNGE", b"FETCH", b"RECENT")
  422. _OK_RESPONSES = (
  423. b"UIDVALIDITY",
  424. b"UNSEEN",
  425. b"READ-WRITE",
  426. b"READ-ONLY",
  427. b"UIDNEXT",
  428. b"PERMANENTFLAGS",
  429. )
  430. defer = None
  431. def __init__(
  432. self,
  433. command,
  434. args=None,
  435. wantResponse=(),
  436. continuation=None,
  437. *contArgs,
  438. **contKw,
  439. ):
  440. self.command = command
  441. self.args = args
  442. self.wantResponse = wantResponse
  443. self.continuation = lambda x: continuation(x, *contArgs, **contKw)
  444. self.lines = []
  445. def __repr__(self) -> str:
  446. return "<imap4.Command {!r} {!r} {!r} {!r} {!r}>".format(
  447. self.command, self.args, self.wantResponse, self.continuation, self.lines
  448. )
  449. def format(self, tag):
  450. if self.args is None:
  451. return b" ".join((tag, self.command))
  452. return b" ".join((tag, self.command, self.args))
  453. def finish(self, lastLine, unusedCallback):
  454. send = []
  455. unuse = []
  456. for L in self.lines:
  457. names = parseNestedParens(L)
  458. N = len(names)
  459. if (
  460. N >= 1
  461. and names[0] in self._1_RESPONSES
  462. or N >= 2
  463. and names[1] in self._2_RESPONSES
  464. or N >= 2
  465. and names[0] == b"OK"
  466. and isinstance(names[1], list)
  467. and names[1][0] in self._OK_RESPONSES
  468. ):
  469. send.append(names)
  470. else:
  471. unuse.append(names)
  472. d, self.defer = self.defer, None
  473. d.callback((send, lastLine))
  474. if unuse:
  475. unusedCallback(unuse)
  476. # Some constants to help define what an atom is and is not - see the grammar
  477. # section of the IMAP4 RFC - <https://tools.ietf.org/html/rfc3501#section-9>.
  478. # Some definitions (SP, CTL, DQUOTE) are also from the ABNF RFC -
  479. # <https://tools.ietf.org/html/rfc2234>.
  480. _SP = b" "
  481. _CTL = bytes(chain(range(0x21), range(0x80, 0x100)))
  482. # It is easier to define ATOM-CHAR in terms of what it does not match than in
  483. # terms of what it does match.
  484. _nonAtomChars = b']\\\\(){%*"' + _SP + _CTL
  485. # _nonAtomRE is only used in Query, so it uses native strings.
  486. _nativeNonAtomChars = _nonAtomChars.decode("charmap")
  487. _nonAtomRE = re.compile("[" + _nativeNonAtomChars + "]")
  488. # This is all the bytes that match the ATOM-CHAR from the grammar in the RFC.
  489. _atomChars = bytes(ch for ch in range(0x100) if ch not in _nonAtomChars)
  490. @implementer(IMailboxListener)
  491. class IMAP4Server(basic.LineReceiver, policies.TimeoutMixin):
  492. """
  493. Protocol implementation for an IMAP4rev1 server.
  494. The server can be in any of four states:
  495. - Non-authenticated
  496. - Authenticated
  497. - Selected
  498. - Logout
  499. """
  500. # Identifier for this server software
  501. IDENT = b"Twisted IMAP4rev1 Ready"
  502. # Number of seconds before idle timeout
  503. # Initially 1 minute. Raised to 30 minutes after login.
  504. timeOut = 60
  505. POSTAUTH_TIMEOUT = 60 * 30
  506. # Whether STARTTLS has been issued successfully yet or not.
  507. startedTLS = False
  508. # Whether our transport supports TLS
  509. canStartTLS = False
  510. # Mapping of tags to commands we have received
  511. tags = None
  512. # The object which will handle logins for us
  513. portal = None
  514. # The account object for this connection
  515. account = None
  516. # Logout callback
  517. _onLogout = None
  518. # The currently selected mailbox
  519. mbox = None
  520. # Command data to be processed when literal data is received
  521. _pendingLiteral = None
  522. # Maximum length to accept for a "short" string literal
  523. _literalStringLimit = 4096
  524. # IChallengeResponse factories for AUTHENTICATE command
  525. challengers = None
  526. # Search terms the implementation of which needs to be passed both the last
  527. # message identifier (UID) and the last sequence id.
  528. _requiresLastMessageInfo = {b"OR", b"NOT", b"UID"}
  529. state = "unauth"
  530. parseState = "command"
  531. def __init__(self, chal=None, contextFactory=None, scheduler=None):
  532. if chal is None:
  533. chal = {}
  534. self.challengers = chal
  535. self.ctx = contextFactory
  536. if scheduler is None:
  537. scheduler = iterateInReactor
  538. self._scheduler = scheduler
  539. self._queuedAsync = []
  540. def capabilities(self):
  541. cap = {b"AUTH": list(self.challengers.keys())}
  542. if self.ctx and self.canStartTLS:
  543. if (
  544. not self.startedTLS
  545. and interfaces.ISSLTransport(self.transport, None) is None
  546. ):
  547. cap[b"LOGINDISABLED"] = None
  548. cap[b"STARTTLS"] = None
  549. cap[b"NAMESPACE"] = None
  550. cap[b"IDLE"] = None
  551. return cap
  552. def connectionMade(self):
  553. self.tags = {}
  554. self.canStartTLS = interfaces.ITLSTransport(self.transport, None) is not None
  555. self.setTimeout(self.timeOut)
  556. self.sendServerGreeting()
  557. def connectionLost(self, reason):
  558. self.setTimeout(None)
  559. if self._onLogout:
  560. self._onLogout()
  561. self._onLogout = None
  562. def timeoutConnection(self):
  563. self.sendLine(b"* BYE Autologout; connection idle too long")
  564. self.transport.loseConnection()
  565. if self.mbox:
  566. self.mbox.removeListener(self)
  567. cmbx = ICloseableMailbox(self.mbox, None)
  568. if cmbx is not None:
  569. maybeDeferred(cmbx.close).addErrback(log.err)
  570. self.mbox = None
  571. self.state = "timeout"
  572. def rawDataReceived(self, data):
  573. self.resetTimeout()
  574. passon = self._pendingLiteral.write(data)
  575. if passon is not None:
  576. self.setLineMode(passon)
  577. # Avoid processing commands while buffers are being dumped to
  578. # our transport
  579. blocked = None
  580. def _unblock(self):
  581. commands = self.blocked
  582. self.blocked = None
  583. while commands and self.blocked is None:
  584. self.lineReceived(commands.pop(0))
  585. if self.blocked is not None:
  586. self.blocked.extend(commands)
  587. def lineReceived(self, line):
  588. if self.blocked is not None:
  589. self.blocked.append(line)
  590. return
  591. self.resetTimeout()
  592. f = getattr(self, "parse_" + self.parseState)
  593. try:
  594. f(line)
  595. except Exception as e:
  596. self.sendUntaggedResponse(b"BAD Server error: " + networkString(str(e)))
  597. log.err()
  598. def parse_command(self, line):
  599. args = line.split(None, 2)
  600. rest = None
  601. if len(args) == 3:
  602. tag, cmd, rest = args
  603. elif len(args) == 2:
  604. tag, cmd = args
  605. elif len(args) == 1:
  606. tag = args[0]
  607. self.sendBadResponse(tag, b"Missing command")
  608. return None
  609. else:
  610. self.sendBadResponse(None, b"Null command")
  611. return None
  612. cmd = cmd.upper()
  613. try:
  614. return self.dispatchCommand(tag, cmd, rest)
  615. except IllegalClientResponse as e:
  616. self.sendBadResponse(tag, b"Illegal syntax: " + networkString(str(e)))
  617. except IllegalOperation as e:
  618. self.sendNegativeResponse(
  619. tag, b"Illegal operation: " + networkString(str(e))
  620. )
  621. except IllegalMailboxEncoding as e:
  622. self.sendNegativeResponse(
  623. tag, b"Illegal mailbox name: " + networkString(str(e))
  624. )
  625. def parse_pending(self, line):
  626. d = self._pendingLiteral
  627. self._pendingLiteral = None
  628. self.parseState = "command"
  629. d.callback(line)
  630. def dispatchCommand(self, tag, cmd, rest, uid=None):
  631. f = self.lookupCommand(cmd)
  632. if f:
  633. fn = f[0]
  634. parseargs = f[1:]
  635. self.__doCommand(tag, fn, [self, tag], parseargs, rest, uid)
  636. else:
  637. self.sendBadResponse(tag, b"Unsupported command")
  638. def lookupCommand(self, cmd):
  639. return getattr(self, "_".join((self.state, nativeString(cmd.upper()))), None)
  640. def __doCommand(self, tag, handler, args, parseargs, line, uid):
  641. for i, arg in enumerate(parseargs):
  642. if callable(arg):
  643. parseargs = parseargs[i + 1 :]
  644. maybeDeferred(arg, self, line).addCallback(
  645. self.__cbDispatch, tag, handler, args, parseargs, uid
  646. ).addErrback(self.__ebDispatch, tag)
  647. return
  648. else:
  649. args.append(arg)
  650. if line:
  651. # Too many arguments
  652. raise IllegalClientResponse("Too many arguments for command: " + repr(line))
  653. if uid is not None:
  654. handler(uid=uid, *args)
  655. else:
  656. handler(*args)
  657. def __cbDispatch(self, result, tag, fn, args, parseargs, uid):
  658. (arg, rest) = result
  659. args.append(arg)
  660. self.__doCommand(tag, fn, args, parseargs, rest, uid)
  661. def __ebDispatch(self, failure, tag):
  662. if failure.check(IllegalClientResponse):
  663. self.sendBadResponse(
  664. tag, b"Illegal syntax: " + networkString(str(failure.value))
  665. )
  666. elif failure.check(IllegalOperation):
  667. self.sendNegativeResponse(
  668. tag, b"Illegal operation: " + networkString(str(failure.value))
  669. )
  670. elif failure.check(IllegalMailboxEncoding):
  671. self.sendNegativeResponse(
  672. tag, b"Illegal mailbox name: " + networkString(str(failure.value))
  673. )
  674. else:
  675. self.sendBadResponse(
  676. tag, b"Server error: " + networkString(str(failure.value))
  677. )
  678. log.err(failure)
  679. def _stringLiteral(self, size):
  680. if size > self._literalStringLimit:
  681. raise IllegalClientResponse(
  682. "Literal too long! I accept at most %d octets"
  683. % (self._literalStringLimit,)
  684. )
  685. d = defer.Deferred()
  686. self.parseState = "pending"
  687. self._pendingLiteral = LiteralString(size, d)
  688. self.sendContinuationRequest(
  689. networkString("Ready for %d octets of text" % size)
  690. )
  691. self.setRawMode()
  692. return d
  693. def _fileLiteral(self, size):
  694. d = defer.Deferred()
  695. self.parseState = "pending"
  696. self._pendingLiteral = LiteralFile(size, d)
  697. self.sendContinuationRequest(
  698. networkString("Ready for %d octets of data" % size)
  699. )
  700. self.setRawMode()
  701. return d
  702. def arg_finalastring(self, line):
  703. """
  704. Parse an astring from line that represents a command's final
  705. argument. This special case exists to enable parsing empty
  706. string literals.
  707. @param line: A line that contains a string literal.
  708. @type line: L{bytes}
  709. @return: A 2-tuple containing the parsed argument and any
  710. trailing data, or a L{Deferred} that fires with that
  711. 2-tuple
  712. @rtype: L{tuple} of (L{bytes}, L{bytes}) or a L{Deferred}
  713. @see: https://twistedmatrix.com/trac/ticket/9207
  714. """
  715. return self.arg_astring(line, final=True)
  716. def arg_astring(self, line, final=False):
  717. """
  718. Parse an astring from the line, return (arg, rest), possibly
  719. via a deferred (to handle literals)
  720. @param line: A line that contains a string literal.
  721. @type line: L{bytes}
  722. @param final: Is this the final argument?
  723. @type final L{bool}
  724. @return: A 2-tuple containing the parsed argument and any
  725. trailing data, or a L{Deferred} that fires with that
  726. 2-tuple
  727. @rtype: L{tuple} of (L{bytes}, L{bytes}) or a L{Deferred}
  728. """
  729. line = line.strip()
  730. if not line:
  731. raise IllegalClientResponse("Missing argument")
  732. d = None
  733. arg, rest = None, None
  734. if line[0:1] == b'"':
  735. try:
  736. spam, arg, rest = line.split(b'"', 2)
  737. rest = rest[1:] # Strip space
  738. except ValueError:
  739. raise IllegalClientResponse("Unmatched quotes")
  740. elif line[0:1] == b"{":
  741. # literal
  742. if line[-1:] != b"}":
  743. raise IllegalClientResponse("Malformed literal")
  744. try:
  745. size = int(line[1:-1])
  746. except ValueError:
  747. raise IllegalClientResponse("Bad literal size: " + repr(line[1:-1]))
  748. if final and not size:
  749. return (b"", b"")
  750. d = self._stringLiteral(size)
  751. else:
  752. arg = line.split(b" ", 1)
  753. if len(arg) == 1:
  754. arg.append(b"")
  755. arg, rest = arg
  756. return d or (arg, rest)
  757. # ATOM: Any CHAR except ( ) { % * " \ ] CTL SP (CHAR is 7bit)
  758. atomre = re.compile(
  759. b"(?P<atom>[" + re.escape(_atomChars) + b"]+)( (?P<rest>.*$)|$)"
  760. )
  761. def arg_atom(self, line):
  762. """
  763. Parse an atom from the line
  764. """
  765. if not line:
  766. raise IllegalClientResponse("Missing argument")
  767. m = self.atomre.match(line)
  768. if m:
  769. return m.group("atom"), m.group("rest")
  770. else:
  771. raise IllegalClientResponse("Malformed ATOM")
  772. def arg_plist(self, line):
  773. """
  774. Parse a (non-nested) parenthesised list from the line
  775. """
  776. if not line:
  777. raise IllegalClientResponse("Missing argument")
  778. if line[:1] != b"(":
  779. raise IllegalClientResponse("Missing parenthesis")
  780. i = line.find(b")")
  781. if i == -1:
  782. raise IllegalClientResponse("Mismatched parenthesis")
  783. return (parseNestedParens(line[1:i], 0), line[i + 2 :])
  784. def arg_literal(self, line):
  785. """
  786. Parse a literal from the line
  787. """
  788. if not line:
  789. raise IllegalClientResponse("Missing argument")
  790. if line[:1] != b"{":
  791. raise IllegalClientResponse("Missing literal")
  792. if line[-1:] != b"}":
  793. raise IllegalClientResponse("Malformed literal")
  794. try:
  795. size = int(line[1:-1])
  796. except ValueError:
  797. raise IllegalClientResponse(f"Bad literal size: {line[1:-1]!r}")
  798. return self._fileLiteral(size)
  799. def arg_searchkeys(self, line):
  800. """
  801. searchkeys
  802. """
  803. query = parseNestedParens(line)
  804. # XXX Should really use list of search terms and parse into
  805. # a proper tree
  806. return (query, b"")
  807. def arg_seqset(self, line):
  808. """
  809. sequence-set
  810. """
  811. rest = b""
  812. arg = line.split(b" ", 1)
  813. if len(arg) == 2:
  814. rest = arg[1]
  815. arg = arg[0]
  816. try:
  817. return (parseIdList(arg), rest)
  818. except IllegalIdentifierError as e:
  819. raise IllegalClientResponse("Bad message number " + str(e))
  820. def arg_fetchatt(self, line):
  821. """
  822. fetch-att
  823. """
  824. p = _FetchParser()
  825. p.parseString(line)
  826. return (p.result, b"")
  827. def arg_flaglist(self, line):
  828. """
  829. Flag part of store-att-flag
  830. """
  831. flags = []
  832. if line[0:1] == b"(":
  833. if line[-1:] != b")":
  834. raise IllegalClientResponse("Mismatched parenthesis")
  835. line = line[1:-1]
  836. while line:
  837. m = self.atomre.search(line)
  838. if not m:
  839. raise IllegalClientResponse("Malformed flag")
  840. if line[0:1] == b"\\" and m.start() == 1:
  841. flags.append(b"\\" + m.group("atom"))
  842. elif m.start() == 0:
  843. flags.append(m.group("atom"))
  844. else:
  845. raise IllegalClientResponse("Malformed flag")
  846. line = m.group("rest")
  847. return (flags, b"")
  848. def arg_line(self, line):
  849. """
  850. Command line of UID command
  851. """
  852. return (line, b"")
  853. def opt_plist(self, line):
  854. """
  855. Optional parenthesised list
  856. """
  857. if line.startswith(b"("):
  858. return self.arg_plist(line)
  859. else:
  860. return (None, line)
  861. def opt_datetime(self, line):
  862. """
  863. Optional date-time string
  864. """
  865. if line.startswith(b'"'):
  866. try:
  867. spam, date, rest = line.split(b'"', 2)
  868. except ValueError:
  869. raise IllegalClientResponse("Malformed date-time")
  870. return (date, rest[1:])
  871. else:
  872. return (None, line)
  873. def opt_charset(self, line):
  874. """
  875. Optional charset of SEARCH command
  876. """
  877. if line[:7].upper() == b"CHARSET":
  878. arg = line.split(b" ", 2)
  879. if len(arg) == 1:
  880. raise IllegalClientResponse("Missing charset identifier")
  881. if len(arg) == 2:
  882. arg.append(b"")
  883. spam, arg, rest = arg
  884. return (arg, rest)
  885. else:
  886. return (None, line)
  887. def sendServerGreeting(self):
  888. msg = b"[CAPABILITY " + b" ".join(self.listCapabilities()) + b"] " + self.IDENT
  889. self.sendPositiveResponse(message=msg)
  890. def sendBadResponse(self, tag=None, message=b""):
  891. self._respond(b"BAD", tag, message)
  892. def sendPositiveResponse(self, tag=None, message=b""):
  893. self._respond(b"OK", tag, message)
  894. def sendNegativeResponse(self, tag=None, message=b""):
  895. self._respond(b"NO", tag, message)
  896. def sendUntaggedResponse(self, message, isAsync=None):
  897. if not isAsync or (self.blocked is None):
  898. self._respond(message, None, None)
  899. else:
  900. self._queuedAsync.append(message)
  901. def sendContinuationRequest(self, msg=b"Ready for additional command text"):
  902. if msg:
  903. self.sendLine(b"+ " + msg)
  904. else:
  905. self.sendLine(b"+")
  906. def _respond(self, state, tag, message):
  907. if state in (b"OK", b"NO", b"BAD") and self._queuedAsync:
  908. lines = self._queuedAsync
  909. self._queuedAsync = []
  910. for msg in lines:
  911. self._respond(msg, None, None)
  912. if not tag:
  913. tag = b"*"
  914. if message:
  915. self.sendLine(b" ".join((tag, state, message)))
  916. else:
  917. self.sendLine(b" ".join((tag, state)))
  918. def listCapabilities(self):
  919. caps = [b"IMAP4rev1"]
  920. for c, v in self.capabilities().items():
  921. if v is None:
  922. caps.append(c)
  923. elif len(v):
  924. caps.extend([(c + b"=" + cap) for cap in v])
  925. return caps
  926. def do_CAPABILITY(self, tag):
  927. self.sendUntaggedResponse(b"CAPABILITY " + b" ".join(self.listCapabilities()))
  928. self.sendPositiveResponse(tag, b"CAPABILITY completed")
  929. unauth_CAPABILITY = (do_CAPABILITY,)
  930. auth_CAPABILITY = unauth_CAPABILITY
  931. select_CAPABILITY = unauth_CAPABILITY
  932. logout_CAPABILITY = unauth_CAPABILITY
  933. def do_LOGOUT(self, tag):
  934. self.sendUntaggedResponse(b"BYE Nice talking to you")
  935. self.sendPositiveResponse(tag, b"LOGOUT successful")
  936. self.transport.loseConnection()
  937. unauth_LOGOUT = (do_LOGOUT,)
  938. auth_LOGOUT = unauth_LOGOUT
  939. select_LOGOUT = unauth_LOGOUT
  940. logout_LOGOUT = unauth_LOGOUT
  941. def do_NOOP(self, tag):
  942. self.sendPositiveResponse(tag, b"NOOP No operation performed")
  943. unauth_NOOP = (do_NOOP,)
  944. auth_NOOP = unauth_NOOP
  945. select_NOOP = unauth_NOOP
  946. logout_NOOP = unauth_NOOP
  947. def do_AUTHENTICATE(self, tag, args):
  948. args = args.upper().strip()
  949. if args not in self.challengers:
  950. self.sendNegativeResponse(tag, b"AUTHENTICATE method unsupported")
  951. else:
  952. self.authenticate(self.challengers[args](), tag)
  953. unauth_AUTHENTICATE = (do_AUTHENTICATE, arg_atom)
  954. def authenticate(self, chal, tag):
  955. if self.portal is None:
  956. self.sendNegativeResponse(tag, b"Temporary authentication failure")
  957. return
  958. self._setupChallenge(chal, tag)
  959. def _setupChallenge(self, chal, tag):
  960. try:
  961. challenge = chal.getChallenge()
  962. except Exception as e:
  963. self.sendBadResponse(tag, b"Server error: " + networkString(str(e)))
  964. else:
  965. coded = encodebytes(challenge)[:-1]
  966. self.parseState = "pending"
  967. self._pendingLiteral = defer.Deferred()
  968. self.sendContinuationRequest(coded)
  969. self._pendingLiteral.addCallback(self.__cbAuthChunk, chal, tag)
  970. self._pendingLiteral.addErrback(self.__ebAuthChunk, tag)
  971. def __cbAuthChunk(self, result, chal, tag):
  972. try:
  973. uncoded = decodebytes(result)
  974. except binascii.Error:
  975. raise IllegalClientResponse("Malformed Response - not base64")
  976. chal.setResponse(uncoded)
  977. if chal.moreChallenges():
  978. self._setupChallenge(chal, tag)
  979. else:
  980. self.portal.login(chal, None, IAccount).addCallbacks(
  981. self.__cbAuthResp, self.__ebAuthResp, (tag,), None, (tag,), None
  982. )
  983. def __cbAuthResp(self, result, tag):
  984. (iface, avatar, logout) = result
  985. assert iface is IAccount, "IAccount is the only supported interface"
  986. self.account = avatar
  987. self.state = "auth"
  988. self._onLogout = logout
  989. self.sendPositiveResponse(tag, b"Authentication successful")
  990. self.setTimeout(self.POSTAUTH_TIMEOUT)
  991. def __ebAuthResp(self, failure, tag):
  992. if failure.check(UnauthorizedLogin):
  993. self.sendNegativeResponse(tag, b"Authentication failed: unauthorized")
  994. elif failure.check(UnhandledCredentials):
  995. self.sendNegativeResponse(
  996. tag, b"Authentication failed: server misconfigured"
  997. )
  998. else:
  999. self.sendBadResponse(tag, b"Server error: login failed unexpectedly")
  1000. log.err(failure)
  1001. def __ebAuthChunk(self, failure, tag):
  1002. self.sendNegativeResponse(
  1003. tag, b"Authentication failed: " + networkString(str(failure.value))
  1004. )
  1005. def do_STARTTLS(self, tag):
  1006. if self.startedTLS:
  1007. self.sendNegativeResponse(tag, b"TLS already negotiated")
  1008. elif self.ctx and self.canStartTLS:
  1009. self.sendPositiveResponse(tag, b"Begin TLS negotiation now")
  1010. self.transport.startTLS(self.ctx)
  1011. self.startedTLS = True
  1012. self.challengers = self.challengers.copy()
  1013. if b"LOGIN" not in self.challengers:
  1014. self.challengers[b"LOGIN"] = LOGINCredentials
  1015. if b"PLAIN" not in self.challengers:
  1016. self.challengers[b"PLAIN"] = PLAINCredentials
  1017. else:
  1018. self.sendNegativeResponse(tag, b"TLS not available")
  1019. unauth_STARTTLS = (do_STARTTLS,)
  1020. def do_LOGIN(self, tag, user, passwd):
  1021. if b"LOGINDISABLED" in self.capabilities():
  1022. self.sendBadResponse(tag, b"LOGIN is disabled before STARTTLS")
  1023. return
  1024. maybeDeferred(self.authenticateLogin, user, passwd).addCallback(
  1025. self.__cbLogin, tag
  1026. ).addErrback(self.__ebLogin, tag)
  1027. unauth_LOGIN = (do_LOGIN, arg_astring, arg_finalastring)
  1028. def authenticateLogin(self, user, passwd):
  1029. """
  1030. Lookup the account associated with the given parameters
  1031. Override this method to define the desired authentication behavior.
  1032. The default behavior is to defer authentication to C{self.portal}
  1033. if it is not None, or to deny the login otherwise.
  1034. @type user: L{str}
  1035. @param user: The username to lookup
  1036. @type passwd: L{str}
  1037. @param passwd: The password to login with
  1038. """
  1039. if self.portal:
  1040. return self.portal.login(
  1041. credentials.UsernamePassword(user, passwd), None, IAccount
  1042. )
  1043. raise UnauthorizedLogin()
  1044. def __cbLogin(self, result, tag):
  1045. (iface, avatar, logout) = result
  1046. if iface is not IAccount:
  1047. self.sendBadResponse(tag, b"Server error: login returned unexpected value")
  1048. log.err(f"__cbLogin called with {iface!r}, IAccount expected")
  1049. else:
  1050. self.account = avatar
  1051. self._onLogout = logout
  1052. self.sendPositiveResponse(tag, b"LOGIN succeeded")
  1053. self.state = "auth"
  1054. self.setTimeout(self.POSTAUTH_TIMEOUT)
  1055. def __ebLogin(self, failure, tag):
  1056. if failure.check(UnauthorizedLogin):
  1057. self.sendNegativeResponse(tag, b"LOGIN failed")
  1058. else:
  1059. self.sendBadResponse(
  1060. tag, b"Server error: " + networkString(str(failure.value))
  1061. )
  1062. log.err(failure)
  1063. def do_NAMESPACE(self, tag):
  1064. personal = public = shared = None
  1065. np = INamespacePresenter(self.account, None)
  1066. if np is not None:
  1067. personal = np.getPersonalNamespaces()
  1068. public = np.getSharedNamespaces()
  1069. shared = np.getSharedNamespaces()
  1070. self.sendUntaggedResponse(
  1071. b"NAMESPACE " + collapseNestedLists([personal, public, shared])
  1072. )
  1073. self.sendPositiveResponse(tag, b"NAMESPACE command completed")
  1074. auth_NAMESPACE = (do_NAMESPACE,)
  1075. select_NAMESPACE = auth_NAMESPACE
  1076. def _selectWork(self, tag, name, rw, cmdName):
  1077. if self.mbox:
  1078. self.mbox.removeListener(self)
  1079. cmbx = ICloseableMailbox(self.mbox, None)
  1080. if cmbx is not None:
  1081. maybeDeferred(cmbx.close).addErrback(log.err)
  1082. self.mbox = None
  1083. self.state = "auth"
  1084. name = _parseMbox(name)
  1085. maybeDeferred(self.account.select, _parseMbox(name), rw).addCallback(
  1086. self._cbSelectWork, cmdName, tag
  1087. ).addErrback(self._ebSelectWork, cmdName, tag)
  1088. def _ebSelectWork(self, failure, cmdName, tag):
  1089. self.sendBadResponse(tag, cmdName + b" failed: Server error")
  1090. log.err(failure)
  1091. def _cbSelectWork(self, mbox, cmdName, tag):
  1092. if mbox is None:
  1093. self.sendNegativeResponse(tag, b"No such mailbox")
  1094. return
  1095. if "\\noselect" in [s.lower() for s in mbox.getFlags()]:
  1096. self.sendNegativeResponse(tag, "Mailbox cannot be selected")
  1097. return
  1098. flags = [networkString(flag) for flag in mbox.getFlags()]
  1099. self.sendUntaggedResponse(b"%d EXISTS" % (mbox.getMessageCount(),))
  1100. self.sendUntaggedResponse(b"%d RECENT" % (mbox.getRecentCount(),))
  1101. self.sendUntaggedResponse(b"FLAGS (" + b" ".join(flags) + b")")
  1102. self.sendPositiveResponse(None, b"[UIDVALIDITY %d]" % (mbox.getUIDValidity(),))
  1103. s = mbox.isWriteable() and b"READ-WRITE" or b"READ-ONLY"
  1104. mbox.addListener(self)
  1105. self.sendPositiveResponse(tag, b"[" + s + b"] " + cmdName + b" successful")
  1106. self.state = "select"
  1107. self.mbox = mbox
  1108. auth_SELECT = (_selectWork, arg_astring, 1, b"SELECT")
  1109. select_SELECT = auth_SELECT
  1110. auth_EXAMINE = (_selectWork, arg_astring, 0, b"EXAMINE")
  1111. select_EXAMINE = auth_EXAMINE
  1112. def do_IDLE(self, tag):
  1113. self.sendContinuationRequest(None)
  1114. self.parseTag = tag
  1115. self.lastState = self.parseState
  1116. self.parseState = "idle"
  1117. def parse_idle(self, *args):
  1118. self.parseState = self.lastState
  1119. del self.lastState
  1120. self.sendPositiveResponse(self.parseTag, b"IDLE terminated")
  1121. del self.parseTag
  1122. select_IDLE = (do_IDLE,)
  1123. auth_IDLE = select_IDLE
  1124. def do_CREATE(self, tag, name):
  1125. name = _parseMbox(name)
  1126. try:
  1127. result = self.account.create(name)
  1128. except MailboxException as c:
  1129. self.sendNegativeResponse(tag, networkString(str(c)))
  1130. except BaseException:
  1131. self.sendBadResponse(
  1132. tag, b"Server error encountered while creating mailbox"
  1133. )
  1134. log.err()
  1135. else:
  1136. if result:
  1137. self.sendPositiveResponse(tag, b"Mailbox created")
  1138. else:
  1139. self.sendNegativeResponse(tag, b"Mailbox not created")
  1140. auth_CREATE = (do_CREATE, arg_finalastring)
  1141. select_CREATE = auth_CREATE
  1142. def do_DELETE(self, tag, name):
  1143. name = _parseMbox(name)
  1144. if name.lower() == "inbox":
  1145. self.sendNegativeResponse(tag, b"You cannot delete the inbox")
  1146. return
  1147. try:
  1148. self.account.delete(name)
  1149. except MailboxException as m:
  1150. self.sendNegativeResponse(tag, str(m).encode("imap4-utf-7"))
  1151. except BaseException:
  1152. self.sendBadResponse(
  1153. tag, b"Server error encountered while deleting mailbox"
  1154. )
  1155. log.err()
  1156. else:
  1157. self.sendPositiveResponse(tag, b"Mailbox deleted")
  1158. auth_DELETE = (do_DELETE, arg_finalastring)
  1159. select_DELETE = auth_DELETE
  1160. def do_RENAME(self, tag, oldname, newname):
  1161. oldname, newname = (_parseMbox(n) for n in (oldname, newname))
  1162. if oldname.lower() == "inbox" or newname.lower() == "inbox":
  1163. self.sendNegativeResponse(
  1164. tag, b"You cannot rename the inbox, or rename another mailbox to inbox."
  1165. )
  1166. return
  1167. try:
  1168. self.account.rename(oldname, newname)
  1169. except TypeError:
  1170. self.sendBadResponse(tag, b"Invalid command syntax")
  1171. except MailboxException as m:
  1172. self.sendNegativeResponse(tag, networkString(str(m)))
  1173. except BaseException:
  1174. self.sendBadResponse(
  1175. tag, b"Server error encountered while renaming mailbox"
  1176. )
  1177. log.err()
  1178. else:
  1179. self.sendPositiveResponse(tag, b"Mailbox renamed")
  1180. auth_RENAME = (do_RENAME, arg_astring, arg_finalastring)
  1181. select_RENAME = auth_RENAME
  1182. def do_SUBSCRIBE(self, tag, name):
  1183. name = _parseMbox(name)
  1184. try:
  1185. self.account.subscribe(name)
  1186. except MailboxException as m:
  1187. self.sendNegativeResponse(tag, networkString(str(m)))
  1188. except BaseException:
  1189. self.sendBadResponse(
  1190. tag, b"Server error encountered while subscribing to mailbox"
  1191. )
  1192. log.err()
  1193. else:
  1194. self.sendPositiveResponse(tag, b"Subscribed")
  1195. auth_SUBSCRIBE = (do_SUBSCRIBE, arg_finalastring)
  1196. select_SUBSCRIBE = auth_SUBSCRIBE
  1197. def do_UNSUBSCRIBE(self, tag, name):
  1198. name = _parseMbox(name)
  1199. try:
  1200. self.account.unsubscribe(name)
  1201. except MailboxException as m:
  1202. self.sendNegativeResponse(tag, networkString(str(m)))
  1203. except BaseException:
  1204. self.sendBadResponse(
  1205. tag, b"Server error encountered while unsubscribing from mailbox"
  1206. )
  1207. log.err()
  1208. else:
  1209. self.sendPositiveResponse(tag, b"Unsubscribed")
  1210. auth_UNSUBSCRIBE = (do_UNSUBSCRIBE, arg_finalastring)
  1211. select_UNSUBSCRIBE = auth_UNSUBSCRIBE
  1212. def _listWork(self, tag, ref, mbox, sub, cmdName):
  1213. mbox = _parseMbox(mbox)
  1214. ref = _parseMbox(ref)
  1215. maybeDeferred(self.account.listMailboxes, ref, mbox).addCallback(
  1216. self._cbListWork, tag, sub, cmdName
  1217. ).addErrback(self._ebListWork, tag)
  1218. def _cbListWork(self, mailboxes, tag, sub, cmdName):
  1219. for name, box in mailboxes:
  1220. if not sub or self.account.isSubscribed(name):
  1221. flags = [networkString(flag) for flag in box.getFlags()]
  1222. delim = box.getHierarchicalDelimiter().encode("imap4-utf-7")
  1223. resp = (
  1224. DontQuoteMe(cmdName),
  1225. map(DontQuoteMe, flags),
  1226. delim,
  1227. name.encode("imap4-utf-7"),
  1228. )
  1229. self.sendUntaggedResponse(collapseNestedLists(resp))
  1230. self.sendPositiveResponse(tag, cmdName + b" completed")
  1231. def _ebListWork(self, failure, tag):
  1232. self.sendBadResponse(tag, b"Server error encountered while listing mailboxes.")
  1233. log.err(failure)
  1234. auth_LIST = (_listWork, arg_astring, arg_astring, 0, b"LIST")
  1235. select_LIST = auth_LIST
  1236. auth_LSUB = (_listWork, arg_astring, arg_astring, 1, b"LSUB")
  1237. select_LSUB = auth_LSUB
  1238. def do_STATUS(self, tag, mailbox, names):
  1239. nativeNames = []
  1240. for name in names:
  1241. nativeNames.append(nativeString(name))
  1242. mailbox = _parseMbox(mailbox)
  1243. maybeDeferred(self.account.select, mailbox, 0).addCallback(
  1244. self._cbStatusGotMailbox, tag, mailbox, nativeNames
  1245. ).addErrback(self._ebStatusGotMailbox, tag)
  1246. def _cbStatusGotMailbox(self, mbox, tag, mailbox, names):
  1247. if mbox:
  1248. maybeDeferred(mbox.requestStatus, names).addCallbacks(
  1249. self.__cbStatus,
  1250. self.__ebStatus,
  1251. (tag, mailbox),
  1252. None,
  1253. (tag, mailbox),
  1254. None,
  1255. )
  1256. else:
  1257. self.sendNegativeResponse(tag, b"Could not open mailbox")
  1258. def _ebStatusGotMailbox(self, failure, tag):
  1259. self.sendBadResponse(tag, b"Server error encountered while opening mailbox.")
  1260. log.err(failure)
  1261. auth_STATUS = (do_STATUS, arg_astring, arg_plist)
  1262. select_STATUS = auth_STATUS
  1263. def __cbStatus(self, status, tag, box):
  1264. # STATUS names should only be ASCII
  1265. line = networkString(" ".join(["%s %s" % x for x in status.items()]))
  1266. self.sendUntaggedResponse(
  1267. b"STATUS " + box.encode("imap4-utf-7") + b" (" + line + b")"
  1268. )
  1269. self.sendPositiveResponse(tag, b"STATUS complete")
  1270. def __ebStatus(self, failure, tag, box):
  1271. self.sendBadResponse(
  1272. tag, b"STATUS " + box + b" failed: " + networkString(str(failure.value))
  1273. )
  1274. def do_APPEND(self, tag, mailbox, flags, date, message):
  1275. mailbox = _parseMbox(mailbox)
  1276. maybeDeferred(self.account.select, mailbox).addCallback(
  1277. self._cbAppendGotMailbox, tag, flags, date, message
  1278. ).addErrback(self._ebAppendGotMailbox, tag)
  1279. def _cbAppendGotMailbox(self, mbox, tag, flags, date, message):
  1280. if not mbox:
  1281. self.sendNegativeResponse(tag, "[TRYCREATE] No such mailbox")
  1282. return
  1283. decodedFlags = [nativeString(flag) for flag in flags]
  1284. d = mbox.addMessage(message, decodedFlags, date)
  1285. d.addCallback(self.__cbAppend, tag, mbox)
  1286. d.addErrback(self.__ebAppend, tag)
  1287. def _ebAppendGotMailbox(self, failure, tag):
  1288. self.sendBadResponse(tag, b"Server error encountered while opening mailbox.")
  1289. log.err(failure)
  1290. auth_APPEND = (do_APPEND, arg_astring, opt_plist, opt_datetime, arg_literal)
  1291. select_APPEND = auth_APPEND
  1292. def __cbAppend(self, result, tag, mbox):
  1293. self.sendUntaggedResponse(b"%d EXISTS" % (mbox.getMessageCount(),))
  1294. self.sendPositiveResponse(tag, b"APPEND complete")
  1295. def __ebAppend(self, failure, tag):
  1296. self.sendBadResponse(
  1297. tag, b"APPEND failed: " + networkString(str(failure.value))
  1298. )
  1299. def do_CHECK(self, tag):
  1300. d = self.checkpoint()
  1301. if d is None:
  1302. self.__cbCheck(None, tag)
  1303. else:
  1304. d.addCallbacks(
  1305. self.__cbCheck, self.__ebCheck, callbackArgs=(tag,), errbackArgs=(tag,)
  1306. )
  1307. select_CHECK = (do_CHECK,)
  1308. def __cbCheck(self, result, tag):
  1309. self.sendPositiveResponse(tag, b"CHECK completed")
  1310. def __ebCheck(self, failure, tag):
  1311. self.sendBadResponse(tag, b"CHECK failed: " + networkString(str(failure.value)))
  1312. def checkpoint(self):
  1313. """
  1314. Called when the client issues a CHECK command.
  1315. This should perform any checkpoint operations required by the server.
  1316. It may be a long running operation, but may not block. If it returns
  1317. a deferred, the client will only be informed of success (or failure)
  1318. when the deferred's callback (or errback) is invoked.
  1319. """
  1320. return None
  1321. def do_CLOSE(self, tag):
  1322. d = None
  1323. if self.mbox.isWriteable():
  1324. d = maybeDeferred(self.mbox.expunge)
  1325. cmbx = ICloseableMailbox(self.mbox, None)
  1326. if cmbx is not None:
  1327. if d is not None:
  1328. d.addCallback(lambda result: cmbx.close())
  1329. else:
  1330. d = maybeDeferred(cmbx.close)
  1331. if d is not None:
  1332. d.addCallbacks(self.__cbClose, self.__ebClose, (tag,), None, (tag,), None)
  1333. else:
  1334. self.__cbClose(None, tag)
  1335. select_CLOSE = (do_CLOSE,)
  1336. def __cbClose(self, result, tag):
  1337. self.sendPositiveResponse(tag, b"CLOSE completed")
  1338. self.mbox.removeListener(self)
  1339. self.mbox = None
  1340. self.state = "auth"
  1341. def __ebClose(self, failure, tag):
  1342. self.sendBadResponse(tag, b"CLOSE failed: " + networkString(str(failure.value)))
  1343. def do_EXPUNGE(self, tag):
  1344. if self.mbox.isWriteable():
  1345. maybeDeferred(self.mbox.expunge).addCallbacks(
  1346. self.__cbExpunge, self.__ebExpunge, (tag,), None, (tag,), None
  1347. )
  1348. else:
  1349. self.sendNegativeResponse(tag, b"EXPUNGE ignored on read-only mailbox")
  1350. select_EXPUNGE = (do_EXPUNGE,)
  1351. def __cbExpunge(self, result, tag):
  1352. for e in result:
  1353. self.sendUntaggedResponse(b"%d EXPUNGE" % (e,))
  1354. self.sendPositiveResponse(tag, b"EXPUNGE completed")
  1355. def __ebExpunge(self, failure, tag):
  1356. self.sendBadResponse(
  1357. tag, b"EXPUNGE failed: " + networkString(str(failure.value))
  1358. )
  1359. log.err(failure)
  1360. def do_SEARCH(self, tag, charset, query, uid=0):
  1361. sm = ISearchableMailbox(self.mbox, None)
  1362. if sm is not None:
  1363. maybeDeferred(sm.search, query, uid=uid).addCallback(
  1364. self.__cbSearch, tag, self.mbox, uid
  1365. ).addErrback(self.__ebSearch, tag)
  1366. else:
  1367. # that's not the ideal way to get all messages, there should be a
  1368. # method on mailboxes that gives you all of them
  1369. s = parseIdList(b"1:*")
  1370. maybeDeferred(self.mbox.fetch, s, uid=uid).addCallback(
  1371. self.__cbManualSearch, tag, self.mbox, query, uid
  1372. ).addErrback(self.__ebSearch, tag)
  1373. select_SEARCH = (do_SEARCH, opt_charset, arg_searchkeys)
  1374. def __cbSearch(self, result, tag, mbox, uid):
  1375. if uid:
  1376. result = map(mbox.getUID, result)
  1377. ids = networkString(" ".join([str(i) for i in result]))
  1378. self.sendUntaggedResponse(b"SEARCH " + ids)
  1379. self.sendPositiveResponse(tag, b"SEARCH completed")
  1380. def __cbManualSearch(self, result, tag, mbox, query, uid, searchResults=None):
  1381. """
  1382. Apply the search filter to a set of messages. Send the response to the
  1383. client.
  1384. @type result: L{list} of L{tuple} of (L{int}, provider of
  1385. L{imap4.IMessage})
  1386. @param result: A list two tuples of messages with their sequence ids,
  1387. sorted by the ids in descending order.
  1388. @type tag: L{str}
  1389. @param tag: A command tag.
  1390. @type mbox: Provider of L{imap4.IMailbox}
  1391. @param mbox: The searched mailbox.
  1392. @type query: L{list}
  1393. @param query: A list representing the parsed form of the search query.
  1394. @param uid: A flag indicating whether the search is over message
  1395. sequence numbers or UIDs.
  1396. @type searchResults: L{list}
  1397. @param searchResults: The search results so far or L{None} if no
  1398. results yet.
  1399. """
  1400. if searchResults is None:
  1401. searchResults = []
  1402. i = 0
  1403. # result is a list of tuples (sequenceId, Message)
  1404. lastSequenceId = result and result[-1][0]
  1405. lastMessageId = result and result[-1][1].getUID()
  1406. for i, (msgId, msg) in list(zip(range(5), result)):
  1407. # searchFilter and singleSearchStep will mutate the query. Dang.
  1408. # Copy it here or else things will go poorly for subsequent
  1409. # messages.
  1410. if self._searchFilter(
  1411. copy.deepcopy(query), msgId, msg, lastSequenceId, lastMessageId
  1412. ):
  1413. searchResults.append(b"%d" % (msg.getUID() if uid else msgId,))
  1414. if i == 4:
  1415. from twisted.internet import reactor
  1416. reactor.callLater(
  1417. 0,
  1418. self.__cbManualSearch,
  1419. list(result[5:]),
  1420. tag,
  1421. mbox,
  1422. query,
  1423. uid,
  1424. searchResults,
  1425. )
  1426. else:
  1427. if searchResults:
  1428. self.sendUntaggedResponse(b"SEARCH " + b" ".join(searchResults))
  1429. self.sendPositiveResponse(tag, b"SEARCH completed")
  1430. def _searchFilter(self, query, id, msg, lastSequenceId, lastMessageId):
  1431. """
  1432. Pop search terms from the beginning of C{query} until there are none
  1433. left and apply them to the given message.
  1434. @param query: A list representing the parsed form of the search query.
  1435. @param id: The sequence number of the message being checked.
  1436. @param msg: The message being checked.
  1437. @type lastSequenceId: L{int}
  1438. @param lastSequenceId: The highest sequence number of any message in
  1439. the mailbox being searched.
  1440. @type lastMessageId: L{int}
  1441. @param lastMessageId: The highest UID of any message in the mailbox
  1442. being searched.
  1443. @return: Boolean indicating whether all of the query terms match the
  1444. message.
  1445. """
  1446. while query:
  1447. if not self._singleSearchStep(
  1448. query, id, msg, lastSequenceId, lastMessageId
  1449. ):
  1450. return False
  1451. return True
  1452. def _singleSearchStep(self, query, msgId, msg, lastSequenceId, lastMessageId):
  1453. """
  1454. Pop one search term from the beginning of C{query} (possibly more than
  1455. one element) and return whether it matches the given message.
  1456. @param query: A list representing the parsed form of the search query.
  1457. @param msgId: The sequence number of the message being checked.
  1458. @param msg: The message being checked.
  1459. @param lastSequenceId: The highest sequence number of any message in
  1460. the mailbox being searched.
  1461. @param lastMessageId: The highest UID of any message in the mailbox
  1462. being searched.
  1463. @return: Boolean indicating whether the query term matched the message.
  1464. """
  1465. q = query.pop(0)
  1466. if isinstance(q, list):
  1467. if not self._searchFilter(q, msgId, msg, lastSequenceId, lastMessageId):
  1468. return False
  1469. else:
  1470. c = q.upper()
  1471. if not c[:1].isalpha():
  1472. # A search term may be a word like ALL, ANSWERED, BCC, etc (see
  1473. # below) or it may be a message sequence set. Here we
  1474. # recognize a message sequence set "N:M".
  1475. messageSet = parseIdList(c, lastSequenceId)
  1476. return msgId in messageSet
  1477. else:
  1478. f = getattr(self, "search_" + nativeString(c), None)
  1479. if f is None:
  1480. raise IllegalQueryError(
  1481. "Invalid search command %s" % nativeString(c)
  1482. )
  1483. if c in self._requiresLastMessageInfo:
  1484. result = f(query, msgId, msg, (lastSequenceId, lastMessageId))
  1485. else:
  1486. result = f(query, msgId, msg)
  1487. if not result:
  1488. return False
  1489. return True
  1490. def search_ALL(self, query, id, msg):
  1491. """
  1492. Returns C{True} if the message matches the ALL search key (always).
  1493. @type query: A L{list} of L{str}
  1494. @param query: A list representing the parsed query string.
  1495. @type id: L{int}
  1496. @param id: The sequence number of the message being checked.
  1497. @type msg: Provider of L{imap4.IMessage}
  1498. """
  1499. return True
  1500. def search_ANSWERED(self, query, id, msg):
  1501. """
  1502. Returns C{True} if the message has been answered.
  1503. @type query: A L{list} of L{str}
  1504. @param query: A list representing the parsed query string.
  1505. @type id: L{int}
  1506. @param id: The sequence number of the message being checked.
  1507. @type msg: Provider of L{imap4.IMessage}
  1508. """
  1509. return "\\Answered" in msg.getFlags()
  1510. def search_BCC(self, query, id, msg):
  1511. """
  1512. Returns C{True} if the message has a BCC address matching the query.
  1513. @type query: A L{list} of L{str}
  1514. @param query: A list whose first element is a BCC L{str}
  1515. @type id: L{int}
  1516. @param id: The sequence number of the message being checked.
  1517. @type msg: Provider of L{imap4.IMessage}
  1518. """
  1519. bcc = msg.getHeaders(False, "bcc").get("bcc", "")
  1520. return bcc.lower().find(query.pop(0).lower()) != -1
  1521. def search_BEFORE(self, query, id, msg):
  1522. date = parseTime(query.pop(0))
  1523. return email.utils.parsedate(nativeString(msg.getInternalDate())) < date
  1524. def search_BODY(self, query, id, msg):
  1525. body = query.pop(0).lower()
  1526. return text.strFile(body, msg.getBodyFile(), False)
  1527. def search_CC(self, query, id, msg):
  1528. cc = msg.getHeaders(False, "cc").get("cc", "")
  1529. return cc.lower().find(query.pop(0).lower()) != -1
  1530. def search_DELETED(self, query, id, msg):
  1531. return "\\Deleted" in msg.getFlags()
  1532. def search_DRAFT(self, query, id, msg):
  1533. return "\\Draft" in msg.getFlags()
  1534. def search_FLAGGED(self, query, id, msg):
  1535. return "\\Flagged" in msg.getFlags()
  1536. def search_FROM(self, query, id, msg):
  1537. fm = msg.getHeaders(False, "from").get("from", "")
  1538. return fm.lower().find(query.pop(0).lower()) != -1
  1539. def search_HEADER(self, query, id, msg):
  1540. hdr = query.pop(0).lower()
  1541. hdr = msg.getHeaders(False, hdr).get(hdr, "")
  1542. return hdr.lower().find(query.pop(0).lower()) != -1
  1543. def search_KEYWORD(self, query, id, msg):
  1544. query.pop(0)
  1545. return False
  1546. def search_LARGER(self, query, id, msg):
  1547. return int(query.pop(0)) < msg.getSize()
  1548. def search_NEW(self, query, id, msg):
  1549. return "\\Recent" in msg.getFlags() and "\\Seen" not in msg.getFlags()
  1550. def search_NOT(self, query, id, msg, lastIDs):
  1551. """
  1552. Returns C{True} if the message does not match the query.
  1553. @type query: A L{list} of L{str}
  1554. @param query: A list representing the parsed form of the search query.
  1555. @type id: L{int}
  1556. @param id: The sequence number of the message being checked.
  1557. @type msg: Provider of L{imap4.IMessage}
  1558. @param msg: The message being checked.
  1559. @type lastIDs: L{tuple}
  1560. @param lastIDs: A tuple of (last sequence id, last message id).
  1561. The I{last sequence id} is an L{int} containing the highest sequence
  1562. number of a message in the mailbox. The I{last message id} is an
  1563. L{int} containing the highest UID of a message in the mailbox.
  1564. """
  1565. (lastSequenceId, lastMessageId) = lastIDs
  1566. return not self._singleSearchStep(query, id, msg, lastSequenceId, lastMessageId)
  1567. def search_OLD(self, query, id, msg):
  1568. return "\\Recent" not in msg.getFlags()
  1569. def search_ON(self, query, id, msg):
  1570. date = parseTime(query.pop(0))
  1571. return email.utils.parsedate(msg.getInternalDate()) == date
  1572. def search_OR(self, query, id, msg, lastIDs):
  1573. """
  1574. Returns C{True} if the message matches any of the first two query
  1575. items.
  1576. @type query: A L{list} of L{str}
  1577. @param query: A list representing the parsed form of the search query.
  1578. @type id: L{int}
  1579. @param id: The sequence number of the message being checked.
  1580. @type msg: Provider of L{imap4.IMessage}
  1581. @param msg: The message being checked.
  1582. @type lastIDs: L{tuple}
  1583. @param lastIDs: A tuple of (last sequence id, last message id).
  1584. The I{last sequence id} is an L{int} containing the highest sequence
  1585. number of a message in the mailbox. The I{last message id} is an
  1586. L{int} containing the highest UID of a message in the mailbox.
  1587. """
  1588. (lastSequenceId, lastMessageId) = lastIDs
  1589. a = self._singleSearchStep(query, id, msg, lastSequenceId, lastMessageId)
  1590. b = self._singleSearchStep(query, id, msg, lastSequenceId, lastMessageId)
  1591. return a or b
  1592. def search_RECENT(self, query, id, msg):
  1593. return "\\Recent" in msg.getFlags()
  1594. def search_SEEN(self, query, id, msg):
  1595. return "\\Seen" in msg.getFlags()
  1596. def search_SENTBEFORE(self, query, id, msg):
  1597. """
  1598. Returns C{True} if the message date is earlier than the query date.
  1599. @type query: A L{list} of L{str}
  1600. @param query: A list whose first element starts with a stringified date
  1601. that is a fragment of an L{imap4.Query()}. The date must be in the
  1602. format 'DD-Mon-YYYY', for example '03-March-2003' or '03-Mar-2003'.
  1603. @type id: L{int}
  1604. @param id: The sequence number of the message being checked.
  1605. @type msg: Provider of L{imap4.IMessage}
  1606. """
  1607. date = msg.getHeaders(False, "date").get("date", "")
  1608. date = email.utils.parsedate(date)
  1609. return date < parseTime(query.pop(0))
  1610. def search_SENTON(self, query, id, msg):
  1611. """
  1612. Returns C{True} if the message date is the same as the query date.
  1613. @type query: A L{list} of L{str}
  1614. @param query: A list whose first element starts with a stringified date
  1615. that is a fragment of an L{imap4.Query()}. The date must be in the
  1616. format 'DD-Mon-YYYY', for example '03-March-2003' or '03-Mar-2003'.
  1617. @type msg: Provider of L{imap4.IMessage}
  1618. """
  1619. date = msg.getHeaders(False, "date").get("date", "")
  1620. date = email.utils.parsedate(date)
  1621. return date[:3] == parseTime(query.pop(0))[:3]
  1622. def search_SENTSINCE(self, query, id, msg):
  1623. """
  1624. Returns C{True} if the message date is later than the query date.
  1625. @type query: A L{list} of L{str}
  1626. @param query: A list whose first element starts with a stringified date
  1627. that is a fragment of an L{imap4.Query()}. The date must be in the
  1628. format 'DD-Mon-YYYY', for example '03-March-2003' or '03-Mar-2003'.
  1629. @type msg: Provider of L{imap4.IMessage}
  1630. """
  1631. date = msg.getHeaders(False, "date").get("date", "")
  1632. date = email.utils.parsedate(date)
  1633. return date > parseTime(query.pop(0))
  1634. def search_SINCE(self, query, id, msg):
  1635. date = parseTime(query.pop(0))
  1636. return email.utils.parsedate(msg.getInternalDate()) > date
  1637. def search_SMALLER(self, query, id, msg):
  1638. return int(query.pop(0)) > msg.getSize()
  1639. def search_SUBJECT(self, query, id, msg):
  1640. subj = msg.getHeaders(False, "subject").get("subject", "")
  1641. return subj.lower().find(query.pop(0).lower()) != -1
  1642. def search_TEXT(self, query, id, msg):
  1643. # XXX - This must search headers too
  1644. body = query.pop(0).lower()
  1645. return text.strFile(body, msg.getBodyFile(), False)
  1646. def search_TO(self, query, id, msg):
  1647. to = msg.getHeaders(False, "to").get("to", "")
  1648. return to.lower().find(query.pop(0).lower()) != -1
  1649. def search_UID(self, query, id, msg, lastIDs):
  1650. """
  1651. Returns C{True} if the message UID is in the range defined by the
  1652. search query.
  1653. @type query: A L{list} of L{bytes}
  1654. @param query: A list representing the parsed form of the search
  1655. query. Its first element should be a L{str} that can be interpreted
  1656. as a sequence range, for example '2:4,5:*'.
  1657. @type id: L{int}
  1658. @param id: The sequence number of the message being checked.
  1659. @type msg: Provider of L{imap4.IMessage}
  1660. @param msg: The message being checked.
  1661. @type lastIDs: L{tuple}
  1662. @param lastIDs: A tuple of (last sequence id, last message id).
  1663. The I{last sequence id} is an L{int} containing the highest sequence
  1664. number of a message in the mailbox. The I{last message id} is an
  1665. L{int} containing the highest UID of a message in the mailbox.
  1666. """
  1667. (lastSequenceId, lastMessageId) = lastIDs
  1668. c = query.pop(0)
  1669. m = parseIdList(c, lastMessageId)
  1670. return msg.getUID() in m
  1671. def search_UNANSWERED(self, query, id, msg):
  1672. return "\\Answered" not in msg.getFlags()
  1673. def search_UNDELETED(self, query, id, msg):
  1674. return "\\Deleted" not in msg.getFlags()
  1675. def search_UNDRAFT(self, query, id, msg):
  1676. return "\\Draft" not in msg.getFlags()
  1677. def search_UNFLAGGED(self, query, id, msg):
  1678. return "\\Flagged" not in msg.getFlags()
  1679. def search_UNKEYWORD(self, query, id, msg):
  1680. query.pop(0)
  1681. return False
  1682. def search_UNSEEN(self, query, id, msg):
  1683. return "\\Seen" not in msg.getFlags()
  1684. def __ebSearch(self, failure, tag):
  1685. self.sendBadResponse(
  1686. tag, b"SEARCH failed: " + networkString(str(failure.value))
  1687. )
  1688. log.err(failure)
  1689. def do_FETCH(self, tag, messages, query, uid=0):
  1690. if query:
  1691. self._oldTimeout = self.setTimeout(None)
  1692. maybeDeferred(self.mbox.fetch, messages, uid=uid).addCallback(
  1693. iter
  1694. ).addCallback(self.__cbFetch, tag, query, uid).addErrback(
  1695. self.__ebFetch, tag
  1696. )
  1697. else:
  1698. self.sendPositiveResponse(tag, b"FETCH complete")
  1699. select_FETCH = (do_FETCH, arg_seqset, arg_fetchatt)
  1700. def __cbFetch(self, results, tag, query, uid):
  1701. if self.blocked is None:
  1702. self.blocked = []
  1703. try:
  1704. id, msg = next(results)
  1705. except StopIteration:
  1706. # The idle timeout was suspended while we delivered results,
  1707. # restore it now.
  1708. self.setTimeout(self._oldTimeout)
  1709. del self._oldTimeout
  1710. # All results have been processed, deliver completion notification.
  1711. # It's important to run this *after* resetting the timeout to "rig
  1712. # a race" in some test code. writing to the transport will
  1713. # synchronously call test code, which synchronously loses the
  1714. # connection, calling our connectionLost method, which cancels the
  1715. # timeout. We want to make sure that timeout is cancelled *after*
  1716. # we reset it above, so that the final state is no timed
  1717. # calls. This avoids reactor uncleanliness errors in the test
  1718. # suite.
  1719. # XXX: Perhaps loopback should be fixed to not call the user code
  1720. # synchronously in transport.write?
  1721. self.sendPositiveResponse(tag, b"FETCH completed")
  1722. # Instance state is now consistent again (ie, it is as though
  1723. # the fetch command never ran), so allow any pending blocked
  1724. # commands to execute.
  1725. self._unblock()
  1726. else:
  1727. self.spewMessage(id, msg, query, uid).addCallback(
  1728. lambda _: self.__cbFetch(results, tag, query, uid)
  1729. ).addErrback(self.__ebSpewMessage)
  1730. def __ebSpewMessage(self, failure):
  1731. # This indicates a programming error.
  1732. # There's no reliable way to indicate anything to the client, since we
  1733. # may have already written an arbitrary amount of data in response to
  1734. # the command.
  1735. log.err(failure)
  1736. self.transport.loseConnection()
  1737. def spew_envelope(self, id, msg, _w=None, _f=None):
  1738. if _w is None:
  1739. _w = self.transport.write
  1740. _w(b"ENVELOPE " + collapseNestedLists([getEnvelope(msg)]))
  1741. def spew_flags(self, id, msg, _w=None, _f=None):
  1742. if _w is None:
  1743. _w = self.transport.writen
  1744. encodedFlags = [networkString(flag) for flag in msg.getFlags()]
  1745. _w(b"FLAGS " + b"(" + b" ".join(encodedFlags) + b")")
  1746. def spew_internaldate(self, id, msg, _w=None, _f=None):
  1747. if _w is None:
  1748. _w = self.transport.write
  1749. idate = msg.getInternalDate()
  1750. ttup = email.utils.parsedate_tz(nativeString(idate))
  1751. if ttup is None:
  1752. log.msg("%d:%r: unpareseable internaldate: %r" % (id, msg, idate))
  1753. raise IMAP4Exception("Internal failure generating INTERNALDATE")
  1754. # need to specify the month manually, as strftime depends on locale
  1755. strdate = time.strftime("%d-%%s-%Y %H:%M:%S ", ttup[:9])
  1756. odate = networkString(strdate % (_MONTH_NAMES[ttup[1]],))
  1757. if ttup[9] is None:
  1758. odate = odate + b"+0000"
  1759. else:
  1760. if ttup[9] >= 0:
  1761. sign = b"+"
  1762. else:
  1763. sign = b"-"
  1764. odate = (
  1765. odate
  1766. + sign
  1767. + b"%04d"
  1768. % ((abs(ttup[9]) // 3600) * 100 + (abs(ttup[9]) % 3600) // 60,)
  1769. )
  1770. _w(b"INTERNALDATE " + _quote(odate))
  1771. def spew_rfc822header(self, id, msg, _w=None, _f=None):
  1772. if _w is None:
  1773. _w = self.transport.write
  1774. hdrs = _formatHeaders(msg.getHeaders(True))
  1775. _w(b"RFC822.HEADER " + _literal(hdrs))
  1776. def spew_rfc822text(self, id, msg, _w=None, _f=None):
  1777. if _w is None:
  1778. _w = self.transport.write
  1779. _w(b"RFC822.TEXT ")
  1780. _f()
  1781. return FileProducer(msg.getBodyFile()).beginProducing(self.transport)
  1782. def spew_rfc822size(self, id, msg, _w=None, _f=None):
  1783. if _w is None:
  1784. _w = self.transport.write
  1785. _w(b"RFC822.SIZE %d" % (msg.getSize(),))
  1786. def spew_rfc822(self, id, msg, _w=None, _f=None):
  1787. if _w is None:
  1788. _w = self.transport.write
  1789. _w(b"RFC822 ")
  1790. _f()
  1791. mf = IMessageFile(msg, None)
  1792. if mf is not None:
  1793. return FileProducer(mf.open()).beginProducing(self.transport)
  1794. return MessageProducer(msg, None, self._scheduler).beginProducing(
  1795. self.transport
  1796. )
  1797. def spew_uid(self, id, msg, _w=None, _f=None):
  1798. if _w is None:
  1799. _w = self.transport.write
  1800. _w(b"UID %d" % (msg.getUID(),))
  1801. def spew_bodystructure(self, id, msg, _w=None, _f=None):
  1802. _w(b"BODYSTRUCTURE " + collapseNestedLists([getBodyStructure(msg, True)]))
  1803. def spew_body(self, part, id, msg, _w=None, _f=None):
  1804. if _w is None:
  1805. _w = self.transport.write
  1806. for p in part.part:
  1807. if msg.isMultipart():
  1808. msg = msg.getSubPart(p)
  1809. elif p > 0:
  1810. # Non-multipart messages have an implicit first part but no
  1811. # other parts - reject any request for any other part.
  1812. raise TypeError("Requested subpart of non-multipart message")
  1813. if part.header:
  1814. hdrs = msg.getHeaders(part.header.negate, *part.header.fields)
  1815. hdrs = _formatHeaders(hdrs)
  1816. _w(part.__bytes__() + b" " + _literal(hdrs))
  1817. elif part.text:
  1818. _w(part.__bytes__() + b" ")
  1819. _f()
  1820. return FileProducer(msg.getBodyFile()).beginProducing(self.transport)
  1821. elif part.mime:
  1822. hdrs = _formatHeaders(msg.getHeaders(True))
  1823. _w(part.__bytes__() + b" " + _literal(hdrs))
  1824. elif part.empty:
  1825. _w(part.getBytes(length=msg.getSize()) + b" ")
  1826. _f()
  1827. if part.part:
  1828. return FileProducer(msg.getBodyFile()).beginProducing(self.transport)
  1829. else:
  1830. mf = IMessageFile(msg, None)
  1831. if mf is not None:
  1832. return FileProducer(mf.open()).beginProducing(self.transport)
  1833. return MessageProducer(msg, None, self._scheduler).beginProducing(
  1834. self.transport
  1835. )
  1836. else:
  1837. _w(b"BODY " + collapseNestedLists([getBodyStructure(msg)]))
  1838. def spewMessage(self, id, msg, query, uid):
  1839. wbuf = WriteBuffer(self.transport)
  1840. write = wbuf.write
  1841. flush = wbuf.flush
  1842. def start():
  1843. write(b"* %d FETCH (" % (id,))
  1844. def finish():
  1845. write(b")\r\n")
  1846. def space():
  1847. write(b" ")
  1848. def spew():
  1849. seenUID = False
  1850. start()
  1851. for part in query:
  1852. if part.type == "uid":
  1853. seenUID = True
  1854. if part.type == "body":
  1855. yield self.spew_body(part, id, msg, write, flush)
  1856. else:
  1857. f = getattr(self, "spew_" + part.type)
  1858. yield f(id, msg, write, flush)
  1859. if part is not query[-1]:
  1860. space()
  1861. if uid and not seenUID:
  1862. space()
  1863. yield self.spew_uid(id, msg, write, flush)
  1864. finish()
  1865. flush()
  1866. return self._scheduler(spew())
  1867. def __ebFetch(self, failure, tag):
  1868. self.setTimeout(self._oldTimeout)
  1869. del self._oldTimeout
  1870. log.err(failure)
  1871. self.sendBadResponse(tag, b"FETCH failed: " + networkString(str(failure.value)))
  1872. def do_STORE(self, tag, messages, mode, flags, uid=0):
  1873. mode = mode.upper()
  1874. silent = mode.endswith(b"SILENT")
  1875. if mode.startswith(b"+"):
  1876. mode = 1
  1877. elif mode.startswith(b"-"):
  1878. mode = -1
  1879. else:
  1880. mode = 0
  1881. flags = [nativeString(flag) for flag in flags]
  1882. maybeDeferred(self.mbox.store, messages, flags, mode, uid=uid).addCallbacks(
  1883. self.__cbStore,
  1884. self.__ebStore,
  1885. (tag, self.mbox, uid, silent),
  1886. None,
  1887. (tag,),
  1888. None,
  1889. )
  1890. select_STORE = (do_STORE, arg_seqset, arg_atom, arg_flaglist)
  1891. def __cbStore(self, result, tag, mbox, uid, silent):
  1892. if result and not silent:
  1893. for k, v in result.items():
  1894. if uid:
  1895. uidstr = b" UID %d" % (mbox.getUID(k),)
  1896. else:
  1897. uidstr = b""
  1898. flags = [networkString(flag) for flag in v]
  1899. self.sendUntaggedResponse(
  1900. b"%d FETCH (FLAGS (%b)%b)" % (k, b" ".join(flags), uidstr)
  1901. )
  1902. self.sendPositiveResponse(tag, b"STORE completed")
  1903. def __ebStore(self, failure, tag):
  1904. self.sendBadResponse(tag, b"Server error: " + networkString(str(failure.value)))
  1905. def do_COPY(self, tag, messages, mailbox, uid=0):
  1906. mailbox = _parseMbox(mailbox)
  1907. maybeDeferred(self.account.select, mailbox).addCallback(
  1908. self._cbCopySelectedMailbox, tag, messages, mailbox, uid
  1909. ).addErrback(self._ebCopySelectedMailbox, tag)
  1910. select_COPY = (do_COPY, arg_seqset, arg_finalastring)
  1911. def _cbCopySelectedMailbox(self, mbox, tag, messages, mailbox, uid):
  1912. if not mbox:
  1913. self.sendNegativeResponse(tag, "No such mailbox: " + mailbox)
  1914. else:
  1915. maybeDeferred(self.mbox.fetch, messages, uid).addCallback(
  1916. self.__cbCopy, tag, mbox
  1917. ).addCallback(self.__cbCopied, tag, mbox).addErrback(self.__ebCopy, tag)
  1918. def _ebCopySelectedMailbox(self, failure, tag):
  1919. self.sendBadResponse(tag, b"Server error: " + networkString(str(failure.value)))
  1920. def __cbCopy(self, messages, tag, mbox):
  1921. # XXX - This should handle failures with a rollback or something
  1922. addedDeferreds = []
  1923. fastCopyMbox = IMessageCopier(mbox, None)
  1924. for id, msg in messages:
  1925. if fastCopyMbox is not None:
  1926. d = maybeDeferred(fastCopyMbox.copy, msg)
  1927. addedDeferreds.append(d)
  1928. continue
  1929. # XXX - The following should be an implementation of IMessageCopier.copy
  1930. # on an IMailbox->IMessageCopier adapter.
  1931. flags = msg.getFlags()
  1932. date = msg.getInternalDate()
  1933. body = IMessageFile(msg, None)
  1934. if body is not None:
  1935. bodyFile = body.open()
  1936. d = maybeDeferred(mbox.addMessage, bodyFile, flags, date)
  1937. else:
  1938. def rewind(f):
  1939. f.seek(0)
  1940. return f
  1941. buffer = tempfile.TemporaryFile()
  1942. d = (
  1943. MessageProducer(msg, buffer, self._scheduler)
  1944. .beginProducing(None)
  1945. .addCallback(
  1946. lambda _, b=buffer, f=flags, d=date: mbox.addMessage(
  1947. rewind(b), f, d
  1948. )
  1949. )
  1950. )
  1951. addedDeferreds.append(d)
  1952. return defer.DeferredList(addedDeferreds)
  1953. def __cbCopied(self, deferredIds, tag, mbox):
  1954. ids = []
  1955. failures = []
  1956. for status, result in deferredIds:
  1957. if status:
  1958. ids.append(result)
  1959. else:
  1960. failures.append(result.value)
  1961. if failures:
  1962. self.sendNegativeResponse(tag, "[ALERT] Some messages were not copied")
  1963. else:
  1964. self.sendPositiveResponse(tag, b"COPY completed")
  1965. def __ebCopy(self, failure, tag):
  1966. self.sendBadResponse(tag, b"COPY failed:" + networkString(str(failure.value)))
  1967. log.err(failure)
  1968. def do_UID(self, tag, command, line):
  1969. command = command.upper()
  1970. if command not in (b"COPY", b"FETCH", b"STORE", b"SEARCH"):
  1971. raise IllegalClientResponse(command)
  1972. self.dispatchCommand(tag, command, line, uid=1)
  1973. select_UID = (do_UID, arg_atom, arg_line)
  1974. #
  1975. # IMailboxListener implementation
  1976. #
  1977. def modeChanged(self, writeable):
  1978. if writeable:
  1979. self.sendUntaggedResponse(message=b"[READ-WRITE]", isAsync=True)
  1980. else:
  1981. self.sendUntaggedResponse(message=b"[READ-ONLY]", isAsync=True)
  1982. def flagsChanged(self, newFlags):
  1983. for mId, flags in newFlags.items():
  1984. encodedFlags = [networkString(flag) for flag in flags]
  1985. msg = b"%d FETCH (FLAGS (%b))" % (mId, b" ".join(encodedFlags))
  1986. self.sendUntaggedResponse(msg, isAsync=True)
  1987. def newMessages(self, exists, recent):
  1988. if exists is not None:
  1989. self.sendUntaggedResponse(b"%d EXISTS" % (exists,), isAsync=True)
  1990. if recent is not None:
  1991. self.sendUntaggedResponse(b"%d RECENT" % (recent,), isAsync=True)
  1992. TIMEOUT_ERROR = error.TimeoutError()
  1993. @implementer(IMailboxListener)
  1994. class IMAP4Client(basic.LineReceiver, policies.TimeoutMixin):
  1995. """IMAP4 client protocol implementation
  1996. @ivar state: A string representing the state the connection is currently
  1997. in.
  1998. """
  1999. tags = None
  2000. waiting = None
  2001. queued = None
  2002. tagID = 1
  2003. state = None
  2004. startedTLS = False
  2005. # Number of seconds to wait before timing out a connection.
  2006. # If the number is <= 0 no timeout checking will be performed.
  2007. timeout = 0
  2008. # Capabilities are not allowed to change during the session
  2009. # So cache the first response and use that for all later
  2010. # lookups
  2011. _capCache = None
  2012. _memoryFileLimit = 1024 * 1024 * 10
  2013. # Authentication is pluggable. This maps names to IClientAuthentication
  2014. # objects.
  2015. authenticators = None
  2016. STATUS_CODES = ("OK", "NO", "BAD", "PREAUTH", "BYE")
  2017. STATUS_TRANSFORMATIONS = {"MESSAGES": int, "RECENT": int, "UNSEEN": int}
  2018. context = None
  2019. def __init__(self, contextFactory=None):
  2020. self.tags = {}
  2021. self.queued = []
  2022. self.authenticators = {}
  2023. self.context = contextFactory
  2024. self._tag = None
  2025. self._parts = None
  2026. self._lastCmd = None
  2027. def registerAuthenticator(self, auth):
  2028. """
  2029. Register a new form of authentication
  2030. When invoking the authenticate() method of IMAP4Client, the first
  2031. matching authentication scheme found will be used. The ordering is
  2032. that in which the server lists support authentication schemes.
  2033. @type auth: Implementor of C{IClientAuthentication}
  2034. @param auth: The object to use to perform the client
  2035. side of this authentication scheme.
  2036. """
  2037. self.authenticators[auth.getName().upper()] = auth
  2038. def rawDataReceived(self, data):
  2039. if self.timeout > 0:
  2040. self.resetTimeout()
  2041. self._pendingSize -= len(data)
  2042. if self._pendingSize > 0:
  2043. self._pendingBuffer.write(data)
  2044. else:
  2045. passon = b""
  2046. if self._pendingSize < 0:
  2047. data, passon = data[: self._pendingSize], data[self._pendingSize :]
  2048. self._pendingBuffer.write(data)
  2049. rest = self._pendingBuffer
  2050. self._pendingBuffer = None
  2051. self._pendingSize = None
  2052. rest.seek(0, 0)
  2053. self._parts.append(rest.read())
  2054. self.setLineMode(passon.lstrip(b"\r\n"))
  2055. # def sendLine(self, line):
  2056. # print 'S:', repr(line)
  2057. # return basic.LineReceiver.sendLine(self, line)
  2058. def _setupForLiteral(self, rest, octets):
  2059. self._pendingBuffer = self.messageFile(octets)
  2060. self._pendingSize = octets
  2061. if self._parts is None:
  2062. self._parts = [rest, b"\r\n"]
  2063. else:
  2064. self._parts.extend([rest, b"\r\n"])
  2065. self.setRawMode()
  2066. def connectionMade(self):
  2067. if self.timeout > 0:
  2068. self.setTimeout(self.timeout)
  2069. def connectionLost(self, reason):
  2070. """
  2071. We are no longer connected
  2072. """
  2073. if self.timeout > 0:
  2074. self.setTimeout(None)
  2075. if self.queued is not None:
  2076. queued = self.queued
  2077. self.queued = None
  2078. for cmd in queued:
  2079. cmd.defer.errback(reason)
  2080. if self.tags is not None:
  2081. tags = self.tags
  2082. self.tags = None
  2083. for cmd in tags.values():
  2084. if cmd is not None and cmd.defer is not None:
  2085. cmd.defer.errback(reason)
  2086. def lineReceived(self, line):
  2087. """
  2088. Attempt to parse a single line from the server.
  2089. @type line: L{bytes}
  2090. @param line: The line from the server, without the line delimiter.
  2091. @raise IllegalServerResponse: If the line or some part of the line
  2092. does not represent an allowed message from the server at this time.
  2093. """
  2094. # print('C: ' + repr(line))
  2095. if self.timeout > 0:
  2096. self.resetTimeout()
  2097. lastPart = line.rfind(b"{")
  2098. if lastPart != -1:
  2099. lastPart = line[lastPart + 1 :]
  2100. if lastPart.endswith(b"}"):
  2101. # It's a literal a-comin' in
  2102. try:
  2103. octets = int(lastPart[:-1])
  2104. except ValueError:
  2105. raise IllegalServerResponse(line)
  2106. if self._parts is None:
  2107. self._tag, parts = line.split(None, 1)
  2108. else:
  2109. parts = line
  2110. self._setupForLiteral(parts, octets)
  2111. return
  2112. if self._parts is None:
  2113. # It isn't a literal at all
  2114. self._regularDispatch(line)
  2115. else:
  2116. # If an expression is in progress, no tag is required here
  2117. # Since we didn't find a literal indicator, this expression
  2118. # is done.
  2119. self._parts.append(line)
  2120. tag, rest = self._tag, b"".join(self._parts)
  2121. self._tag = self._parts = None
  2122. self.dispatchCommand(tag, rest)
  2123. def timeoutConnection(self):
  2124. if self._lastCmd and self._lastCmd.defer is not None:
  2125. d, self._lastCmd.defer = self._lastCmd.defer, None
  2126. d.errback(TIMEOUT_ERROR)
  2127. if self.queued:
  2128. for cmd in self.queued:
  2129. if cmd.defer is not None:
  2130. d, cmd.defer = cmd.defer, d
  2131. d.errback(TIMEOUT_ERROR)
  2132. self.transport.loseConnection()
  2133. def _regularDispatch(self, line):
  2134. parts = line.split(None, 1)
  2135. if len(parts) != 2:
  2136. parts.append(b"")
  2137. tag, rest = parts
  2138. self.dispatchCommand(tag, rest)
  2139. def messageFile(self, octets):
  2140. """
  2141. Create a file to which an incoming message may be written.
  2142. @type octets: L{int}
  2143. @param octets: The number of octets which will be written to the file
  2144. @rtype: Any object which implements C{write(string)} and
  2145. C{seek(int, int)}
  2146. @return: A file-like object
  2147. """
  2148. if octets > self._memoryFileLimit:
  2149. return tempfile.TemporaryFile()
  2150. else:
  2151. return BytesIO()
  2152. def makeTag(self):
  2153. tag = ("%0.4X" % self.tagID).encode("ascii")
  2154. self.tagID += 1
  2155. return tag
  2156. def dispatchCommand(self, tag, rest):
  2157. if self.state is None:
  2158. f = self.response_UNAUTH
  2159. else:
  2160. f = getattr(self, "response_" + self.state.upper(), None)
  2161. if f:
  2162. try:
  2163. f(tag, rest)
  2164. except BaseException:
  2165. log.err()
  2166. self.transport.loseConnection()
  2167. else:
  2168. log.err(f"Cannot dispatch: {self.state}, {tag!r}, {rest!r}")
  2169. self.transport.loseConnection()
  2170. def response_UNAUTH(self, tag, rest):
  2171. if self.state is None:
  2172. # Server greeting, this is
  2173. status, rest = rest.split(None, 1)
  2174. if status.upper() == b"OK":
  2175. self.state = "unauth"
  2176. elif status.upper() == b"PREAUTH":
  2177. self.state = "auth"
  2178. else:
  2179. # XXX - This is rude.
  2180. self.transport.loseConnection()
  2181. raise IllegalServerResponse(tag + b" " + rest)
  2182. b, e = rest.find(b"["), rest.find(b"]")
  2183. if b != -1 and e != -1:
  2184. self.serverGreeting(
  2185. self.__cbCapabilities(([parseNestedParens(rest[b + 1 : e])], None))
  2186. )
  2187. else:
  2188. self.serverGreeting(None)
  2189. else:
  2190. self._defaultHandler(tag, rest)
  2191. def response_AUTH(self, tag, rest):
  2192. self._defaultHandler(tag, rest)
  2193. def _defaultHandler(self, tag, rest):
  2194. if tag == b"*" or tag == b"+":
  2195. if not self.waiting:
  2196. self._extraInfo([parseNestedParens(rest)])
  2197. else:
  2198. cmd = self.tags[self.waiting]
  2199. if tag == b"+":
  2200. cmd.continuation(rest)
  2201. else:
  2202. cmd.lines.append(rest)
  2203. else:
  2204. try:
  2205. cmd = self.tags[tag]
  2206. except KeyError:
  2207. # XXX - This is rude.
  2208. self.transport.loseConnection()
  2209. raise IllegalServerResponse(tag + b" " + rest)
  2210. else:
  2211. status, line = rest.split(None, 1)
  2212. if status == b"OK":
  2213. # Give them this last line, too
  2214. cmd.finish(rest, self._extraInfo)
  2215. else:
  2216. cmd.defer.errback(IMAP4Exception(line))
  2217. del self.tags[tag]
  2218. self.waiting = None
  2219. self._flushQueue()
  2220. def _flushQueue(self):
  2221. if self.queued:
  2222. cmd = self.queued.pop(0)
  2223. t = self.makeTag()
  2224. self.tags[t] = cmd
  2225. self.sendLine(cmd.format(t))
  2226. self.waiting = t
  2227. def _extraInfo(self, lines):
  2228. # XXX - This is terrible.
  2229. # XXX - Also, this should collapse temporally proximate calls into single
  2230. # invocations of IMailboxListener methods, where possible.
  2231. flags = {}
  2232. recent = exists = None
  2233. for response in lines:
  2234. elements = len(response)
  2235. if elements == 1 and response[0] == [b"READ-ONLY"]:
  2236. self.modeChanged(False)
  2237. elif elements == 1 and response[0] == [b"READ-WRITE"]:
  2238. self.modeChanged(True)
  2239. elif elements == 2 and response[1] == b"EXISTS":
  2240. exists = int(response[0])
  2241. elif elements == 2 and response[1] == b"RECENT":
  2242. recent = int(response[0])
  2243. elif elements == 3 and response[1] == b"FETCH":
  2244. mId = int(response[0])
  2245. values, _ = self._parseFetchPairs(response[2])
  2246. flags.setdefault(mId, []).extend(values.get("FLAGS", ()))
  2247. else:
  2248. log.msg(f"Unhandled unsolicited response: {response}")
  2249. if flags:
  2250. self.flagsChanged(flags)
  2251. if recent is not None or exists is not None:
  2252. self.newMessages(exists, recent)
  2253. def sendCommand(self, cmd):
  2254. cmd.defer = defer.Deferred()
  2255. if self.waiting:
  2256. self.queued.append(cmd)
  2257. return cmd.defer
  2258. t = self.makeTag()
  2259. self.tags[t] = cmd
  2260. self.sendLine(cmd.format(t))
  2261. self.waiting = t
  2262. self._lastCmd = cmd
  2263. return cmd.defer
  2264. def getCapabilities(self, useCache=1):
  2265. """
  2266. Request the capabilities available on this server.
  2267. This command is allowed in any state of connection.
  2268. @type useCache: C{bool}
  2269. @param useCache: Specify whether to use the capability-cache or to
  2270. re-retrieve the capabilities from the server. Server capabilities
  2271. should never change, so for normal use, this flag should never be
  2272. false.
  2273. @rtype: L{Deferred}
  2274. @return: A deferred whose callback will be invoked with a
  2275. dictionary mapping capability types to lists of supported
  2276. mechanisms, or to None if a support list is not applicable.
  2277. """
  2278. if useCache and self._capCache is not None:
  2279. return defer.succeed(self._capCache)
  2280. cmd = b"CAPABILITY"
  2281. resp = (b"CAPABILITY",)
  2282. d = self.sendCommand(Command(cmd, wantResponse=resp))
  2283. d.addCallback(self.__cbCapabilities)
  2284. return d
  2285. def __cbCapabilities(self, result):
  2286. (lines, tagline) = result
  2287. caps = {}
  2288. for rest in lines:
  2289. for cap in rest[1:]:
  2290. parts = cap.split(b"=", 1)
  2291. if len(parts) == 1:
  2292. category, value = parts[0], None
  2293. else:
  2294. category, value = parts
  2295. caps.setdefault(category, []).append(value)
  2296. # Preserve a non-ideal API for backwards compatibility. It would
  2297. # probably be entirely sensible to have an object with a wider API than
  2298. # dict here so this could be presented less insanely.
  2299. for category in caps:
  2300. if caps[category] == [None]:
  2301. caps[category] = None
  2302. self._capCache = caps
  2303. return caps
  2304. def logout(self):
  2305. """
  2306. Inform the server that we are done with the connection.
  2307. This command is allowed in any state of connection.
  2308. @rtype: L{Deferred}
  2309. @return: A deferred whose callback will be invoked with None
  2310. when the proper server acknowledgement has been received.
  2311. """
  2312. d = self.sendCommand(Command(b"LOGOUT", wantResponse=(b"BYE",)))
  2313. d.addCallback(self.__cbLogout)
  2314. return d
  2315. def __cbLogout(self, result):
  2316. (lines, tagline) = result
  2317. self.transport.loseConnection()
  2318. # We don't particularly care what the server said
  2319. return None
  2320. def noop(self):
  2321. """
  2322. Perform no operation.
  2323. This command is allowed in any state of connection.
  2324. @rtype: L{Deferred}
  2325. @return: A deferred whose callback will be invoked with a list
  2326. of untagged status updates the server responds with.
  2327. """
  2328. d = self.sendCommand(Command(b"NOOP"))
  2329. d.addCallback(self.__cbNoop)
  2330. return d
  2331. def __cbNoop(self, result):
  2332. # Conceivable, this is elidable.
  2333. # It is, afterall, a no-op.
  2334. (lines, tagline) = result
  2335. return lines
  2336. def startTLS(self, contextFactory=None):
  2337. """
  2338. Initiates a 'STARTTLS' request and negotiates the TLS / SSL
  2339. Handshake.
  2340. @param contextFactory: The TLS / SSL Context Factory to
  2341. leverage. If the contextFactory is None the IMAP4Client will
  2342. either use the current TLS / SSL Context Factory or attempt to
  2343. create a new one.
  2344. @type contextFactory: C{ssl.ClientContextFactory}
  2345. @return: A Deferred which fires when the transport has been
  2346. secured according to the given contextFactory, or which fails
  2347. if the transport cannot be secured.
  2348. """
  2349. assert (
  2350. not self.startedTLS
  2351. ), "Client and Server are currently communicating via TLS"
  2352. if contextFactory is None:
  2353. contextFactory = self._getContextFactory()
  2354. if contextFactory is None:
  2355. return defer.fail(
  2356. IMAP4Exception(
  2357. "IMAP4Client requires a TLS context to "
  2358. "initiate the STARTTLS handshake"
  2359. )
  2360. )
  2361. if b"STARTTLS" not in self._capCache:
  2362. return defer.fail(
  2363. IMAP4Exception(
  2364. "Server does not support secure communication " "via TLS / SSL"
  2365. )
  2366. )
  2367. tls = interfaces.ITLSTransport(self.transport, None)
  2368. if tls is None:
  2369. return defer.fail(
  2370. IMAP4Exception(
  2371. "IMAP4Client transport does not implement "
  2372. "interfaces.ITLSTransport"
  2373. )
  2374. )
  2375. d = self.sendCommand(Command(b"STARTTLS"))
  2376. d.addCallback(self._startedTLS, contextFactory)
  2377. d.addCallback(lambda _: self.getCapabilities())
  2378. return d
  2379. def authenticate(self, secret):
  2380. """
  2381. Attempt to enter the authenticated state with the server
  2382. This command is allowed in the Non-Authenticated state.
  2383. @rtype: L{Deferred}
  2384. @return: A deferred whose callback is invoked if the authentication
  2385. succeeds and whose errback will be invoked otherwise.
  2386. """
  2387. if self._capCache is None:
  2388. d = self.getCapabilities()
  2389. else:
  2390. d = defer.succeed(self._capCache)
  2391. d.addCallback(self.__cbAuthenticate, secret)
  2392. return d
  2393. def __cbAuthenticate(self, caps, secret):
  2394. auths = caps.get(b"AUTH", ())
  2395. for scheme in auths:
  2396. if scheme.upper() in self.authenticators:
  2397. cmd = Command(
  2398. b"AUTHENTICATE", scheme, (), self.__cbContinueAuth, scheme, secret
  2399. )
  2400. return self.sendCommand(cmd)
  2401. if self.startedTLS:
  2402. return defer.fail(
  2403. NoSupportedAuthentication(auths, self.authenticators.keys())
  2404. )
  2405. else:
  2406. def ebStartTLS(err):
  2407. err.trap(IMAP4Exception)
  2408. # We couldn't negotiate TLS for some reason
  2409. return defer.fail(
  2410. NoSupportedAuthentication(auths, self.authenticators.keys())
  2411. )
  2412. d = self.startTLS()
  2413. d.addErrback(ebStartTLS)
  2414. d.addCallback(lambda _: self.getCapabilities())
  2415. d.addCallback(self.__cbAuthTLS, secret)
  2416. return d
  2417. def __cbContinueAuth(self, rest, scheme, secret):
  2418. try:
  2419. chal = decodebytes(rest + b"\n")
  2420. except binascii.Error:
  2421. self.sendLine(b"*")
  2422. raise IllegalServerResponse(rest)
  2423. else:
  2424. auth = self.authenticators[scheme]
  2425. chal = auth.challengeResponse(secret, chal)
  2426. self.sendLine(encodebytes(chal).strip())
  2427. def __cbAuthTLS(self, caps, secret):
  2428. auths = caps.get(b"AUTH", ())
  2429. for scheme in auths:
  2430. if scheme.upper() in self.authenticators:
  2431. cmd = Command(
  2432. b"AUTHENTICATE", scheme, (), self.__cbContinueAuth, scheme, secret
  2433. )
  2434. return self.sendCommand(cmd)
  2435. raise NoSupportedAuthentication(auths, self.authenticators.keys())
  2436. def login(self, username, password):
  2437. """
  2438. Authenticate with the server using a username and password
  2439. This command is allowed in the Non-Authenticated state. If the
  2440. server supports the STARTTLS capability and our transport supports
  2441. TLS, TLS is negotiated before the login command is issued.
  2442. A more secure way to log in is to use C{startTLS} or
  2443. C{authenticate} or both.
  2444. @type username: L{str}
  2445. @param username: The username to log in with
  2446. @type password: L{str}
  2447. @param password: The password to log in with
  2448. @rtype: L{Deferred}
  2449. @return: A deferred whose callback is invoked if login is successful
  2450. and whose errback is invoked otherwise.
  2451. """
  2452. d = maybeDeferred(self.getCapabilities)
  2453. d.addCallback(self.__cbLoginCaps, username, password)
  2454. return d
  2455. def serverGreeting(self, caps):
  2456. """
  2457. Called when the server has sent us a greeting.
  2458. @type caps: C{dict}
  2459. @param caps: Capabilities the server advertised in its greeting.
  2460. """
  2461. def _getContextFactory(self):
  2462. if self.context is not None:
  2463. return self.context
  2464. try:
  2465. from twisted.internet import ssl
  2466. except ImportError:
  2467. return None
  2468. else:
  2469. return ssl.ClientContextFactory()
  2470. def __cbLoginCaps(self, capabilities, username, password):
  2471. # If the server advertises STARTTLS, we might want to try to switch to TLS
  2472. tryTLS = b"STARTTLS" in capabilities
  2473. # If our transport supports switching to TLS, we might want to try to switch to TLS.
  2474. tlsableTransport = interfaces.ITLSTransport(self.transport, None) is not None
  2475. # If our transport is not already using TLS, we might want to try to switch to TLS.
  2476. nontlsTransport = interfaces.ISSLTransport(self.transport, None) is None
  2477. if not self.startedTLS and tryTLS and tlsableTransport and nontlsTransport:
  2478. d = self.startTLS()
  2479. d.addCallbacks(
  2480. self.__cbLoginTLS,
  2481. self.__ebLoginTLS,
  2482. callbackArgs=(username, password),
  2483. )
  2484. return d
  2485. else:
  2486. if nontlsTransport:
  2487. log.msg("Server has no TLS support. logging in over cleartext!")
  2488. args = b" ".join((_quote(username), _quote(password)))
  2489. return self.sendCommand(Command(b"LOGIN", args))
  2490. def _startedTLS(self, result, context):
  2491. self.transport.startTLS(context)
  2492. self._capCache = None
  2493. self.startedTLS = True
  2494. return result
  2495. def __cbLoginTLS(self, result, username, password):
  2496. args = b" ".join((_quote(username), _quote(password)))
  2497. return self.sendCommand(Command(b"LOGIN", args))
  2498. def __ebLoginTLS(self, failure):
  2499. log.err(failure)
  2500. return failure
  2501. def namespace(self):
  2502. """
  2503. Retrieve information about the namespaces available to this account
  2504. This command is allowed in the Authenticated and Selected states.
  2505. @rtype: L{Deferred}
  2506. @return: A deferred whose callback is invoked with namespace
  2507. information. An example of this information is::
  2508. [[['', '/']], [], []]
  2509. which indicates a single personal namespace called '' with '/'
  2510. as its hierarchical delimiter, and no shared or user namespaces.
  2511. """
  2512. cmd = b"NAMESPACE"
  2513. resp = (b"NAMESPACE",)
  2514. d = self.sendCommand(Command(cmd, wantResponse=resp))
  2515. d.addCallback(self.__cbNamespace)
  2516. return d
  2517. def __cbNamespace(self, result):
  2518. (lines, last) = result
  2519. # Namespaces and their delimiters qualify and delimit
  2520. # mailboxes, so they should be native strings
  2521. #
  2522. # On Python 2, no decoding is necessary to maintain
  2523. # the API contract.
  2524. #
  2525. # On Python 3, users specify mailboxes with native strings, so
  2526. # they should receive namespaces and delimiters as native
  2527. # strings. Both cases are possible because of the imap4-utf-7
  2528. # encoding.
  2529. def _prepareNamespaceOrDelimiter(namespaceList):
  2530. return [element.decode("imap4-utf-7") for element in namespaceList]
  2531. for parts in lines:
  2532. if len(parts) == 4 and parts[0] == b"NAMESPACE":
  2533. return [
  2534. []
  2535. if pairOrNone is None
  2536. else [_prepareNamespaceOrDelimiter(value) for value in pairOrNone]
  2537. for pairOrNone in parts[1:]
  2538. ]
  2539. log.err("No NAMESPACE response to NAMESPACE command")
  2540. return [[], [], []]
  2541. def select(self, mailbox):
  2542. """
  2543. Select a mailbox
  2544. This command is allowed in the Authenticated and Selected states.
  2545. @type mailbox: L{str}
  2546. @param mailbox: The name of the mailbox to select
  2547. @rtype: L{Deferred}
  2548. @return: A deferred whose callback is invoked with mailbox
  2549. information if the select is successful and whose errback is
  2550. invoked otherwise. Mailbox information consists of a dictionary
  2551. with the following L{str} keys and values::
  2552. FLAGS: A list of strings containing the flags settable on
  2553. messages in this mailbox.
  2554. EXISTS: An integer indicating the number of messages in this
  2555. mailbox.
  2556. RECENT: An integer indicating the number of "recent"
  2557. messages in this mailbox.
  2558. UNSEEN: The message sequence number (an integer) of the
  2559. first unseen message in the mailbox.
  2560. PERMANENTFLAGS: A list of strings containing the flags that
  2561. can be permanently set on messages in this mailbox.
  2562. UIDVALIDITY: An integer uniquely identifying this mailbox.
  2563. """
  2564. cmd = b"SELECT"
  2565. args = _prepareMailboxName(mailbox)
  2566. # This appears not to be used, so we can use native strings to
  2567. # indicate that the return type is native strings.
  2568. resp = ("FLAGS", "EXISTS", "RECENT", "UNSEEN", "PERMANENTFLAGS", "UIDVALIDITY")
  2569. d = self.sendCommand(Command(cmd, args, wantResponse=resp))
  2570. d.addCallback(self.__cbSelect, 1)
  2571. return d
  2572. def examine(self, mailbox):
  2573. """
  2574. Select a mailbox in read-only mode
  2575. This command is allowed in the Authenticated and Selected states.
  2576. @type mailbox: L{str}
  2577. @param mailbox: The name of the mailbox to examine
  2578. @rtype: L{Deferred}
  2579. @return: A deferred whose callback is invoked with mailbox
  2580. information if the examine is successful and whose errback
  2581. is invoked otherwise. Mailbox information consists of a dictionary
  2582. with the following keys and values::
  2583. 'FLAGS': A list of strings containing the flags settable on
  2584. messages in this mailbox.
  2585. 'EXISTS': An integer indicating the number of messages in this
  2586. mailbox.
  2587. 'RECENT': An integer indicating the number of \"recent\"
  2588. messages in this mailbox.
  2589. 'UNSEEN': An integer indicating the number of messages not
  2590. flagged \\Seen in this mailbox.
  2591. 'PERMANENTFLAGS': A list of strings containing the flags that
  2592. can be permanently set on messages in this mailbox.
  2593. 'UIDVALIDITY': An integer uniquely identifying this mailbox.
  2594. """
  2595. cmd = b"EXAMINE"
  2596. args = _prepareMailboxName(mailbox)
  2597. resp = (
  2598. b"FLAGS",
  2599. b"EXISTS",
  2600. b"RECENT",
  2601. b"UNSEEN",
  2602. b"PERMANENTFLAGS",
  2603. b"UIDVALIDITY",
  2604. )
  2605. d = self.sendCommand(Command(cmd, args, wantResponse=resp))
  2606. d.addCallback(self.__cbSelect, 0)
  2607. return d
  2608. def _intOrRaise(self, value, phrase):
  2609. """
  2610. Parse C{value} as an integer and return the result or raise
  2611. L{IllegalServerResponse} with C{phrase} as an argument if C{value}
  2612. cannot be parsed as an integer.
  2613. """
  2614. try:
  2615. return int(value)
  2616. except ValueError:
  2617. raise IllegalServerResponse(phrase)
  2618. def __cbSelect(self, result, rw):
  2619. """
  2620. Handle lines received in response to a SELECT or EXAMINE command.
  2621. See RFC 3501, section 6.3.1.
  2622. """
  2623. (lines, tagline) = result
  2624. # In the absence of specification, we are free to assume:
  2625. # READ-WRITE access
  2626. datum = {"READ-WRITE": rw}
  2627. lines.append(parseNestedParens(tagline))
  2628. for split in lines:
  2629. if len(split) > 0 and split[0].upper() == b"OK":
  2630. # Handle all the kinds of OK response.
  2631. content = split[1]
  2632. if isinstance(content, list):
  2633. key = content[0]
  2634. else:
  2635. # not multi-valued, like OK LOGIN
  2636. key = content
  2637. key = key.upper()
  2638. if key == b"READ-ONLY":
  2639. datum["READ-WRITE"] = False
  2640. elif key == b"READ-WRITE":
  2641. datum["READ-WRITE"] = True
  2642. elif key == b"UIDVALIDITY":
  2643. datum["UIDVALIDITY"] = self._intOrRaise(content[1], split)
  2644. elif key == b"UNSEEN":
  2645. datum["UNSEEN"] = self._intOrRaise(content[1], split)
  2646. elif key == b"UIDNEXT":
  2647. datum["UIDNEXT"] = self._intOrRaise(content[1], split)
  2648. elif key == b"PERMANENTFLAGS":
  2649. datum["PERMANENTFLAGS"] = tuple(
  2650. nativeString(flag) for flag in content[1]
  2651. )
  2652. else:
  2653. log.err(f"Unhandled SELECT response (2): {split}")
  2654. elif len(split) == 2:
  2655. # Handle FLAGS, EXISTS, and RECENT
  2656. if split[0].upper() == b"FLAGS":
  2657. datum["FLAGS"] = tuple(nativeString(flag) for flag in split[1])
  2658. elif isinstance(split[1], bytes):
  2659. # Must make sure things are strings before treating them as
  2660. # strings since some other forms of response have nesting in
  2661. # places which results in lists instead.
  2662. if split[1].upper() == b"EXISTS":
  2663. datum["EXISTS"] = self._intOrRaise(split[0], split)
  2664. elif split[1].upper() == b"RECENT":
  2665. datum["RECENT"] = self._intOrRaise(split[0], split)
  2666. else:
  2667. log.err(f"Unhandled SELECT response (0): {split}")
  2668. else:
  2669. log.err(f"Unhandled SELECT response (1): {split}")
  2670. else:
  2671. log.err(f"Unhandled SELECT response (4): {split}")
  2672. return datum
  2673. def create(self, name):
  2674. """
  2675. Create a new mailbox on the server
  2676. This command is allowed in the Authenticated and Selected states.
  2677. @type name: L{str}
  2678. @param name: The name of the mailbox to create.
  2679. @rtype: L{Deferred}
  2680. @return: A deferred whose callback is invoked if the mailbox creation
  2681. is successful and whose errback is invoked otherwise.
  2682. """
  2683. return self.sendCommand(Command(b"CREATE", _prepareMailboxName(name)))
  2684. def delete(self, name):
  2685. """
  2686. Delete a mailbox
  2687. This command is allowed in the Authenticated and Selected states.
  2688. @type name: L{str}
  2689. @param name: The name of the mailbox to delete.
  2690. @rtype: L{Deferred}
  2691. @return: A deferred whose calblack is invoked if the mailbox is
  2692. deleted successfully and whose errback is invoked otherwise.
  2693. """
  2694. return self.sendCommand(Command(b"DELETE", _prepareMailboxName(name)))
  2695. def rename(self, oldname, newname):
  2696. """
  2697. Rename a mailbox
  2698. This command is allowed in the Authenticated and Selected states.
  2699. @type oldname: L{str}
  2700. @param oldname: The current name of the mailbox to rename.
  2701. @type newname: L{str}
  2702. @param newname: The new name to give the mailbox.
  2703. @rtype: L{Deferred}
  2704. @return: A deferred whose callback is invoked if the rename is
  2705. successful and whose errback is invoked otherwise.
  2706. """
  2707. oldname = _prepareMailboxName(oldname)
  2708. newname = _prepareMailboxName(newname)
  2709. return self.sendCommand(Command(b"RENAME", b" ".join((oldname, newname))))
  2710. def subscribe(self, name):
  2711. """
  2712. Add a mailbox to the subscription list
  2713. This command is allowed in the Authenticated and Selected states.
  2714. @type name: L{str}
  2715. @param name: The mailbox to mark as 'active' or 'subscribed'
  2716. @rtype: L{Deferred}
  2717. @return: A deferred whose callback is invoked if the subscription
  2718. is successful and whose errback is invoked otherwise.
  2719. """
  2720. return self.sendCommand(Command(b"SUBSCRIBE", _prepareMailboxName(name)))
  2721. def unsubscribe(self, name):
  2722. """
  2723. Remove a mailbox from the subscription list
  2724. This command is allowed in the Authenticated and Selected states.
  2725. @type name: L{str}
  2726. @param name: The mailbox to unsubscribe
  2727. @rtype: L{Deferred}
  2728. @return: A deferred whose callback is invoked if the unsubscription
  2729. is successful and whose errback is invoked otherwise.
  2730. """
  2731. return self.sendCommand(Command(b"UNSUBSCRIBE", _prepareMailboxName(name)))
  2732. def list(self, reference, wildcard):
  2733. """
  2734. List a subset of the available mailboxes
  2735. This command is allowed in the Authenticated and Selected
  2736. states.
  2737. @type reference: L{str}
  2738. @param reference: The context in which to interpret
  2739. C{wildcard}
  2740. @type wildcard: L{str}
  2741. @param wildcard: The pattern of mailbox names to match,
  2742. optionally including either or both of the '*' and '%'
  2743. wildcards. '*' will match zero or more characters and
  2744. cross hierarchical boundaries. '%' will also match zero
  2745. or more characters, but is limited to a single
  2746. hierarchical level.
  2747. @rtype: L{Deferred}
  2748. @return: A deferred whose callback is invoked with a list of
  2749. L{tuple}s, the first element of which is a L{tuple} of
  2750. mailbox flags, the second element of which is the
  2751. hierarchy delimiter for this mailbox, and the third of
  2752. which is the mailbox name; if the command is unsuccessful,
  2753. the deferred's errback is invoked instead. B{NB}: the
  2754. delimiter and the mailbox name are L{str}s.
  2755. """
  2756. cmd = b"LIST"
  2757. args = (f'"{reference}" "{wildcard}"').encode("imap4-utf-7")
  2758. resp = (b"LIST",)
  2759. d = self.sendCommand(Command(cmd, args, wantResponse=resp))
  2760. d.addCallback(self.__cbList, b"LIST")
  2761. return d
  2762. def lsub(self, reference, wildcard):
  2763. """
  2764. List a subset of the subscribed available mailboxes
  2765. This command is allowed in the Authenticated and Selected states.
  2766. The parameters and returned object are the same as for the L{list}
  2767. method, with one slight difference: Only mailboxes which have been
  2768. subscribed can be included in the resulting list.
  2769. """
  2770. cmd = b"LSUB"
  2771. encodedReference = reference.encode("ascii")
  2772. encodedWildcard = wildcard.encode("imap4-utf-7")
  2773. args = b"".join(
  2774. [
  2775. b'"',
  2776. encodedReference,
  2777. b'"' b' "',
  2778. encodedWildcard,
  2779. b'"',
  2780. ]
  2781. )
  2782. resp = (b"LSUB",)
  2783. d = self.sendCommand(Command(cmd, args, wantResponse=resp))
  2784. d.addCallback(self.__cbList, b"LSUB")
  2785. return d
  2786. def __cbList(self, result, command):
  2787. (lines, last) = result
  2788. results = []
  2789. for parts in lines:
  2790. if len(parts) == 4 and parts[0] == command:
  2791. # flags
  2792. parts[1] = tuple(nativeString(flag) for flag in parts[1])
  2793. # The mailbox should be a native string.
  2794. # On Python 2, this maintains the API's contract.
  2795. #
  2796. # On Python 3, users specify mailboxes with native
  2797. # strings, so they should receive mailboxes as native
  2798. # strings. Both cases are possible because of the
  2799. # imap4-utf-7 encoding.
  2800. #
  2801. # Mailbox names contain the hierarchical delimiter, so
  2802. # it too should be a native string.
  2803. # delimiter
  2804. parts[2] = parts[2].decode("imap4-utf-7")
  2805. # mailbox
  2806. parts[3] = parts[3].decode("imap4-utf-7")
  2807. results.append(tuple(parts[1:]))
  2808. return results
  2809. _statusNames = {
  2810. name: name.encode("ascii")
  2811. for name in (
  2812. "MESSAGES",
  2813. "RECENT",
  2814. "UIDNEXT",
  2815. "UIDVALIDITY",
  2816. "UNSEEN",
  2817. )
  2818. }
  2819. def status(self, mailbox, *names):
  2820. """
  2821. Retrieve the status of the given mailbox
  2822. This command is allowed in the Authenticated and Selected states.
  2823. @type mailbox: L{str}
  2824. @param mailbox: The name of the mailbox to query
  2825. @type names: L{bytes}
  2826. @param names: The status names to query. These may be any number of:
  2827. C{'MESSAGES'}, C{'RECENT'}, C{'UIDNEXT'}, C{'UIDVALIDITY'}, and
  2828. C{'UNSEEN'}.
  2829. @rtype: L{Deferred}
  2830. @return: A deferred which fires with the status information if the
  2831. command is successful and whose errback is invoked otherwise. The
  2832. status information is in the form of a C{dict}. Each element of
  2833. C{names} is a key in the dictionary. The value for each key is the
  2834. corresponding response from the server.
  2835. """
  2836. cmd = b"STATUS"
  2837. preparedMailbox = _prepareMailboxName(mailbox)
  2838. try:
  2839. names = b" ".join(self._statusNames[name] for name in names)
  2840. except KeyError:
  2841. raise ValueError(f"Unknown names: {set(names) - set(self._statusNames)!r}")
  2842. args = b"".join([preparedMailbox, b" (", names, b")"])
  2843. resp = (b"STATUS",)
  2844. d = self.sendCommand(Command(cmd, args, wantResponse=resp))
  2845. d.addCallback(self.__cbStatus)
  2846. return d
  2847. def __cbStatus(self, result):
  2848. (lines, last) = result
  2849. status = {}
  2850. for parts in lines:
  2851. if parts[0] == b"STATUS":
  2852. items = parts[2]
  2853. items = [items[i : i + 2] for i in range(0, len(items), 2)]
  2854. for k, v in items:
  2855. try:
  2856. status[nativeString(k)] = v
  2857. except UnicodeDecodeError:
  2858. raise IllegalServerResponse(repr(items))
  2859. for k in status.keys():
  2860. t = self.STATUS_TRANSFORMATIONS.get(k)
  2861. if t:
  2862. try:
  2863. status[k] = t(status[k])
  2864. except Exception as e:
  2865. raise IllegalServerResponse(
  2866. "(" + k + " " + status[k] + "): " + str(e)
  2867. )
  2868. return status
  2869. def append(self, mailbox, message, flags=(), date=None):
  2870. """
  2871. Add the given message to the given mailbox.
  2872. This command is allowed in the Authenticated and Selected states.
  2873. @type mailbox: L{str}
  2874. @param mailbox: The mailbox to which to add this message.
  2875. @type message: Any file-like object opened in B{binary mode}.
  2876. @param message: The message to add, in RFC822 format. Newlines
  2877. in this file should be \\r\\n-style.
  2878. @type flags: Any iterable of L{str}
  2879. @param flags: The flags to associated with this message.
  2880. @type date: L{str}
  2881. @param date: The date to associate with this message. This should
  2882. be of the format DD-MM-YYYY HH:MM:SS +/-HHMM. For example, in
  2883. Eastern Standard Time, on July 1st 2004 at half past 1 PM,
  2884. \"01-07-2004 13:30:00 -0500\".
  2885. @rtype: L{Deferred}
  2886. @return: A deferred whose callback is invoked when this command
  2887. succeeds or whose errback is invoked if it fails.
  2888. """
  2889. message.seek(0, 2)
  2890. L = message.tell()
  2891. message.seek(0, 0)
  2892. if date:
  2893. date = networkString(' "%s"' % nativeString(date))
  2894. else:
  2895. date = b""
  2896. encodedFlags = [networkString(flag) for flag in flags]
  2897. cmd = b"%b (%b)%b {%d}" % (
  2898. _prepareMailboxName(mailbox),
  2899. b" ".join(encodedFlags),
  2900. date,
  2901. L,
  2902. )
  2903. d = self.sendCommand(
  2904. Command(b"APPEND", cmd, (), self.__cbContinueAppend, message)
  2905. )
  2906. return d
  2907. def __cbContinueAppend(self, lines, message):
  2908. s = basic.FileSender()
  2909. return s.beginFileTransfer(message, self.transport, None).addCallback(
  2910. self.__cbFinishAppend
  2911. )
  2912. def __cbFinishAppend(self, foo):
  2913. self.sendLine(b"")
  2914. def check(self):
  2915. """
  2916. Tell the server to perform a checkpoint
  2917. This command is allowed in the Selected state.
  2918. @rtype: L{Deferred}
  2919. @return: A deferred whose callback is invoked when this command
  2920. succeeds or whose errback is invoked if it fails.
  2921. """
  2922. return self.sendCommand(Command(b"CHECK"))
  2923. def close(self):
  2924. """
  2925. Return the connection to the Authenticated state.
  2926. This command is allowed in the Selected state.
  2927. Issuing this command will also remove all messages flagged \\Deleted
  2928. from the selected mailbox if it is opened in read-write mode,
  2929. otherwise it indicates success by no messages are removed.
  2930. @rtype: L{Deferred}
  2931. @return: A deferred whose callback is invoked when the command
  2932. completes successfully or whose errback is invoked if it fails.
  2933. """
  2934. return self.sendCommand(Command(b"CLOSE"))
  2935. def expunge(self):
  2936. """
  2937. Return the connection to the Authenticate state.
  2938. This command is allowed in the Selected state.
  2939. Issuing this command will perform the same actions as issuing the
  2940. close command, but will also generate an 'expunge' response for
  2941. every message deleted.
  2942. @rtype: L{Deferred}
  2943. @return: A deferred whose callback is invoked with a list of the
  2944. 'expunge' responses when this command is successful or whose errback
  2945. is invoked otherwise.
  2946. """
  2947. cmd = b"EXPUNGE"
  2948. resp = (b"EXPUNGE",)
  2949. d = self.sendCommand(Command(cmd, wantResponse=resp))
  2950. d.addCallback(self.__cbExpunge)
  2951. return d
  2952. def __cbExpunge(self, result):
  2953. (lines, last) = result
  2954. ids = []
  2955. for parts in lines:
  2956. if len(parts) == 2 and parts[1] == b"EXPUNGE":
  2957. ids.append(self._intOrRaise(parts[0], parts))
  2958. return ids
  2959. def search(self, *queries, uid=False):
  2960. """
  2961. Search messages in the currently selected mailbox
  2962. This command is allowed in the Selected state.
  2963. Any non-zero number of queries are accepted by this method, as returned
  2964. by the C{Query}, C{Or}, and C{Not} functions.
  2965. @param uid: if true, the server is asked to return message UIDs instead
  2966. of message sequence numbers.
  2967. @type uid: L{bool}
  2968. @rtype: L{Deferred}
  2969. @return: A deferred whose callback will be invoked with a list of all
  2970. the message sequence numbers return by the search, or whose errback
  2971. will be invoked if there is an error.
  2972. """
  2973. # Queries should be encoded as ASCII unless a charset
  2974. # identifier is provided. See #9201.
  2975. queries = [query.encode("charmap") for query in queries]
  2976. cmd = b"UID SEARCH" if uid else b"SEARCH"
  2977. args = b" ".join(queries)
  2978. d = self.sendCommand(Command(cmd, args, wantResponse=(cmd,)))
  2979. d.addCallback(self.__cbSearch)
  2980. return d
  2981. def __cbSearch(self, result):
  2982. (lines, end) = result
  2983. ids = []
  2984. for parts in lines:
  2985. if len(parts) > 0 and parts[0] == b"SEARCH":
  2986. ids.extend([self._intOrRaise(p, parts) for p in parts[1:]])
  2987. return ids
  2988. def fetchUID(self, messages, uid=0):
  2989. """
  2990. Retrieve the unique identifier for one or more messages
  2991. This command is allowed in the Selected state.
  2992. @type messages: L{MessageSet} or L{str}
  2993. @param messages: A message sequence set
  2994. @type uid: L{bool}
  2995. @param uid: Indicates whether the message sequence set is of message
  2996. numbers or of unique message IDs.
  2997. @rtype: L{Deferred}
  2998. @return: A deferred whose callback is invoked with a dict mapping
  2999. message sequence numbers to unique message identifiers, or whose
  3000. errback is invoked if there is an error.
  3001. """
  3002. return self._fetch(messages, useUID=uid, uid=1)
  3003. def fetchFlags(self, messages, uid=0):
  3004. """
  3005. Retrieve the flags for one or more messages
  3006. This command is allowed in the Selected state.
  3007. @type messages: L{MessageSet} or L{str}
  3008. @param messages: The messages for which to retrieve flags.
  3009. @type uid: L{bool}
  3010. @param uid: Indicates whether the message sequence set is of message
  3011. numbers or of unique message IDs.
  3012. @rtype: L{Deferred}
  3013. @return: A deferred whose callback is invoked with a dict mapping
  3014. message numbers to lists of flags, or whose errback is invoked if
  3015. there is an error.
  3016. """
  3017. return self._fetch(messages, useUID=uid, flags=1)
  3018. def fetchInternalDate(self, messages, uid=0):
  3019. """
  3020. Retrieve the internal date associated with one or more messages
  3021. This command is allowed in the Selected state.
  3022. @type messages: L{MessageSet} or L{str}
  3023. @param messages: The messages for which to retrieve the internal date.
  3024. @type uid: L{bool}
  3025. @param uid: Indicates whether the message sequence set is of message
  3026. numbers or of unique message IDs.
  3027. @rtype: L{Deferred}
  3028. @return: A deferred whose callback is invoked with a dict mapping
  3029. message numbers to date strings, or whose errback is invoked
  3030. if there is an error. Date strings take the format of
  3031. \"day-month-year time timezone\".
  3032. """
  3033. return self._fetch(messages, useUID=uid, internaldate=1)
  3034. def fetchEnvelope(self, messages, uid=0):
  3035. """
  3036. Retrieve the envelope data for one or more messages
  3037. This command is allowed in the Selected state.
  3038. @type messages: L{MessageSet} or L{str}
  3039. @param messages: The messages for which to retrieve envelope
  3040. data.
  3041. @type uid: L{bool}
  3042. @param uid: Indicates whether the message sequence set is of
  3043. message numbers or of unique message IDs.
  3044. @rtype: L{Deferred}
  3045. @return: A deferred whose callback is invoked with a dict
  3046. mapping message numbers to envelope data, or whose errback
  3047. is invoked if there is an error. Envelope data consists
  3048. of a sequence of the date, subject, from, sender,
  3049. reply-to, to, cc, bcc, in-reply-to, and message-id header
  3050. fields. The date, subject, in-reply-to, and message-id
  3051. fields are L{str}, while the from, sender, reply-to, to,
  3052. cc, and bcc fields contain address data as L{str}s.
  3053. Address data consists of a sequence of name, source route,
  3054. mailbox name, and hostname. Fields which are not present
  3055. for a particular address may be L{None}.
  3056. """
  3057. return self._fetch(messages, useUID=uid, envelope=1)
  3058. def fetchBodyStructure(self, messages, uid=0):
  3059. """
  3060. Retrieve the structure of the body of one or more messages
  3061. This command is allowed in the Selected state.
  3062. @type messages: L{MessageSet} or L{str}
  3063. @param messages: The messages for which to retrieve body structure
  3064. data.
  3065. @type uid: L{bool}
  3066. @param uid: Indicates whether the message sequence set is of message
  3067. numbers or of unique message IDs.
  3068. @rtype: L{Deferred}
  3069. @return: A deferred whose callback is invoked with a dict mapping
  3070. message numbers to body structure data, or whose errback is invoked
  3071. if there is an error. Body structure data describes the MIME-IMB
  3072. format of a message and consists of a sequence of mime type, mime
  3073. subtype, parameters, content id, description, encoding, and size.
  3074. The fields following the size field are variable: if the mime
  3075. type/subtype is message/rfc822, the contained message's envelope
  3076. information, body structure data, and number of lines of text; if
  3077. the mime type is text, the number of lines of text. Extension fields
  3078. may also be included; if present, they are: the MD5 hash of the body,
  3079. body disposition, body language.
  3080. """
  3081. return self._fetch(messages, useUID=uid, bodystructure=1)
  3082. def fetchSimplifiedBody(self, messages, uid=0):
  3083. """
  3084. Retrieve the simplified body structure of one or more messages
  3085. This command is allowed in the Selected state.
  3086. @type messages: L{MessageSet} or L{str}
  3087. @param messages: A message sequence set
  3088. @type uid: C{bool}
  3089. @param uid: Indicates whether the message sequence set is of message
  3090. numbers or of unique message IDs.
  3091. @rtype: L{Deferred}
  3092. @return: A deferred whose callback is invoked with a dict mapping
  3093. message numbers to body data, or whose errback is invoked
  3094. if there is an error. The simplified body structure is the same
  3095. as the body structure, except that extension fields will never be
  3096. present.
  3097. """
  3098. return self._fetch(messages, useUID=uid, body=1)
  3099. def fetchMessage(self, messages, uid=0):
  3100. """
  3101. Retrieve one or more entire messages
  3102. This command is allowed in the Selected state.
  3103. @type messages: L{MessageSet} or L{str}
  3104. @param messages: A message sequence set
  3105. @type uid: C{bool}
  3106. @param uid: Indicates whether the message sequence set is of message
  3107. numbers or of unique message IDs.
  3108. @rtype: L{Deferred}
  3109. @return: A L{Deferred} which will fire with a C{dict} mapping message
  3110. sequence numbers to C{dict}s giving message data for the
  3111. corresponding message. If C{uid} is true, the inner dictionaries
  3112. have a C{'UID'} key mapped to a L{str} giving the UID for the
  3113. message. The text of the message is a L{str} associated with the
  3114. C{'RFC822'} key in each dictionary.
  3115. """
  3116. return self._fetch(messages, useUID=uid, rfc822=1)
  3117. def fetchHeaders(self, messages, uid=0):
  3118. """
  3119. Retrieve headers of one or more messages
  3120. This command is allowed in the Selected state.
  3121. @type messages: L{MessageSet} or L{str}
  3122. @param messages: A message sequence set
  3123. @type uid: L{bool}
  3124. @param uid: Indicates whether the message sequence set is of message
  3125. numbers or of unique message IDs.
  3126. @rtype: L{Deferred}
  3127. @return: A deferred whose callback is invoked with a dict mapping
  3128. message numbers to dicts of message headers, or whose errback is
  3129. invoked if there is an error.
  3130. """
  3131. return self._fetch(messages, useUID=uid, rfc822header=1)
  3132. def fetchBody(self, messages, uid=0):
  3133. """
  3134. Retrieve body text of one or more messages
  3135. This command is allowed in the Selected state.
  3136. @type messages: L{MessageSet} or L{str}
  3137. @param messages: A message sequence set
  3138. @type uid: L{bool}
  3139. @param uid: Indicates whether the message sequence set is of message
  3140. numbers or of unique message IDs.
  3141. @rtype: L{Deferred}
  3142. @return: A deferred whose callback is invoked with a dict mapping
  3143. message numbers to file-like objects containing body text, or whose
  3144. errback is invoked if there is an error.
  3145. """
  3146. return self._fetch(messages, useUID=uid, rfc822text=1)
  3147. def fetchSize(self, messages, uid=0):
  3148. """
  3149. Retrieve the size, in octets, of one or more messages
  3150. This command is allowed in the Selected state.
  3151. @type messages: L{MessageSet} or L{str}
  3152. @param messages: A message sequence set
  3153. @type uid: L{bool}
  3154. @param uid: Indicates whether the message sequence set is of message
  3155. numbers or of unique message IDs.
  3156. @rtype: L{Deferred}
  3157. @return: A deferred whose callback is invoked with a dict mapping
  3158. message numbers to sizes, or whose errback is invoked if there is
  3159. an error.
  3160. """
  3161. return self._fetch(messages, useUID=uid, rfc822size=1)
  3162. def fetchFull(self, messages, uid=0):
  3163. """
  3164. Retrieve several different fields of one or more messages
  3165. This command is allowed in the Selected state. This is equivalent
  3166. to issuing all of the C{fetchFlags}, C{fetchInternalDate},
  3167. C{fetchSize}, C{fetchEnvelope}, and C{fetchSimplifiedBody}
  3168. functions.
  3169. @type messages: L{MessageSet} or L{str}
  3170. @param messages: A message sequence set
  3171. @type uid: L{bool}
  3172. @param uid: Indicates whether the message sequence set is of message
  3173. numbers or of unique message IDs.
  3174. @rtype: L{Deferred}
  3175. @return: A deferred whose callback is invoked with a dict mapping
  3176. message numbers to dict of the retrieved data values, or whose
  3177. errback is invoked if there is an error. They dictionary keys
  3178. are "flags", "date", "size", "envelope", and "body".
  3179. """
  3180. return self._fetch(
  3181. messages,
  3182. useUID=uid,
  3183. flags=1,
  3184. internaldate=1,
  3185. rfc822size=1,
  3186. envelope=1,
  3187. body=1,
  3188. )
  3189. def fetchAll(self, messages, uid=0):
  3190. """
  3191. Retrieve several different fields of one or more messages
  3192. This command is allowed in the Selected state. This is equivalent
  3193. to issuing all of the C{fetchFlags}, C{fetchInternalDate},
  3194. C{fetchSize}, and C{fetchEnvelope} functions.
  3195. @type messages: L{MessageSet} or L{str}
  3196. @param messages: A message sequence set
  3197. @type uid: L{bool}
  3198. @param uid: Indicates whether the message sequence set is of message
  3199. numbers or of unique message IDs.
  3200. @rtype: L{Deferred}
  3201. @return: A deferred whose callback is invoked with a dict mapping
  3202. message numbers to dict of the retrieved data values, or whose
  3203. errback is invoked if there is an error. They dictionary keys
  3204. are "flags", "date", "size", and "envelope".
  3205. """
  3206. return self._fetch(
  3207. messages, useUID=uid, flags=1, internaldate=1, rfc822size=1, envelope=1
  3208. )
  3209. def fetchFast(self, messages, uid=0):
  3210. """
  3211. Retrieve several different fields of one or more messages
  3212. This command is allowed in the Selected state. This is equivalent
  3213. to issuing all of the C{fetchFlags}, C{fetchInternalDate}, and
  3214. C{fetchSize} functions.
  3215. @type messages: L{MessageSet} or L{str}
  3216. @param messages: A message sequence set
  3217. @type uid: L{bool}
  3218. @param uid: Indicates whether the message sequence set is of message
  3219. numbers or of unique message IDs.
  3220. @rtype: L{Deferred}
  3221. @return: A deferred whose callback is invoked with a dict mapping
  3222. message numbers to dict of the retrieved data values, or whose
  3223. errback is invoked if there is an error. They dictionary keys are
  3224. "flags", "date", and "size".
  3225. """
  3226. return self._fetch(messages, useUID=uid, flags=1, internaldate=1, rfc822size=1)
  3227. def _parseFetchPairs(self, fetchResponseList):
  3228. """
  3229. Given the result of parsing a single I{FETCH} response, construct a
  3230. L{dict} mapping response keys to response values.
  3231. @param fetchResponseList: The result of parsing a I{FETCH} response
  3232. with L{parseNestedParens} and extracting just the response data
  3233. (that is, just the part that comes after C{"FETCH"}). The form
  3234. of this input (and therefore the output of this method) is very
  3235. disagreeable. A valuable improvement would be to enumerate the
  3236. possible keys (representing them as structured objects of some
  3237. sort) rather than using strings and tuples of tuples of strings
  3238. and so forth. This would allow the keys to be documented more
  3239. easily and would allow for a much simpler application-facing API
  3240. (one not based on looking up somewhat hard to predict keys in a
  3241. dict). Since C{fetchResponseList} notionally represents a
  3242. flattened sequence of pairs (identifying keys followed by their
  3243. associated values), collapsing such complex elements of this
  3244. list as C{["BODY", ["HEADER.FIELDS", ["SUBJECT"]]]} into a
  3245. single object would also greatly simplify the implementation of
  3246. this method.
  3247. @return: A C{dict} of the response data represented by C{pairs}. Keys
  3248. in this dictionary are things like C{"RFC822.TEXT"}, C{"FLAGS"}, or
  3249. C{("BODY", ("HEADER.FIELDS", ("SUBJECT",)))}. Values are entirely
  3250. dependent on the key with which they are associated, but retain the
  3251. same structured as produced by L{parseNestedParens}.
  3252. """
  3253. # TODO: RFC 3501 Section 7.4.2, "FETCH Response", says for
  3254. # BODY responses that "8-bit textual data is permitted if a
  3255. # charset identifier is part of the body parameter
  3256. # parenthesized list". Every other component is 7-bit. This
  3257. # should parse out the charset identifier and use it to decode
  3258. # 8-bit bodies. Until then, on Python 2 it should continue to
  3259. # return native (byte) strings, while on Python 3 it should
  3260. # decode bytes to native strings via charmap, ensuring data
  3261. # fidelity at the cost of mojibake.
  3262. def nativeStringResponse(thing):
  3263. if isinstance(thing, bytes):
  3264. return thing.decode("charmap")
  3265. elif isinstance(thing, list):
  3266. return [nativeStringResponse(subthing) for subthing in thing]
  3267. values = {}
  3268. unstructured = []
  3269. responseParts = iter(fetchResponseList)
  3270. while True:
  3271. try:
  3272. key = next(responseParts)
  3273. except StopIteration:
  3274. break
  3275. try:
  3276. value = next(responseParts)
  3277. except StopIteration:
  3278. raise IllegalServerResponse(b"Not enough arguments", fetchResponseList)
  3279. # The parsed forms of responses like:
  3280. #
  3281. # BODY[] VALUE
  3282. # BODY[TEXT] VALUE
  3283. # BODY[HEADER.FIELDS (SUBJECT)] VALUE
  3284. # BODY[HEADER.FIELDS (SUBJECT)]<N.M> VALUE
  3285. #
  3286. # are:
  3287. #
  3288. # ["BODY", [], VALUE]
  3289. # ["BODY", ["TEXT"], VALUE]
  3290. # ["BODY", ["HEADER.FIELDS", ["SUBJECT"]], VALUE]
  3291. # ["BODY", ["HEADER.FIELDS", ["SUBJECT"]], "<N.M>", VALUE]
  3292. #
  3293. # Additionally, BODY responses for multipart messages are
  3294. # represented as:
  3295. #
  3296. # ["BODY", VALUE]
  3297. #
  3298. # with list as the type of VALUE and the type of VALUE[0].
  3299. #
  3300. # See #6281 for ideas on how this might be improved.
  3301. if key not in (b"BODY", b"BODY.PEEK"):
  3302. # Only BODY (and by extension, BODY.PEEK) responses can have
  3303. # body sections.
  3304. hasSection = False
  3305. elif not isinstance(value, list):
  3306. # A BODY section is always represented as a list. Any non-list
  3307. # is not a BODY section.
  3308. hasSection = False
  3309. elif len(value) > 2:
  3310. # The list representing a BODY section has at most two elements.
  3311. hasSection = False
  3312. elif value and isinstance(value[0], list):
  3313. # A list containing a list represents the body structure of a
  3314. # multipart message, instead.
  3315. hasSection = False
  3316. else:
  3317. # Otherwise it must have a BODY section to examine.
  3318. hasSection = True
  3319. # If it has a BODY section, grab some extra elements and shuffle
  3320. # around the shape of the key a little bit.
  3321. key = nativeString(key)
  3322. unstructured.append(key)
  3323. if hasSection:
  3324. if len(value) < 2:
  3325. value = [nativeString(v) for v in value]
  3326. unstructured.append(value)
  3327. key = (key, tuple(value))
  3328. else:
  3329. valueHead = nativeString(value[0])
  3330. valueTail = [nativeString(v) for v in value[1]]
  3331. unstructured.append([valueHead, valueTail])
  3332. key = (key, (valueHead, tuple(valueTail)))
  3333. try:
  3334. value = next(responseParts)
  3335. except StopIteration:
  3336. raise IllegalServerResponse(
  3337. b"Not enough arguments", fetchResponseList
  3338. )
  3339. # Handle partial ranges
  3340. if value.startswith(b"<") and value.endswith(b">"):
  3341. try:
  3342. int(value[1:-1])
  3343. except ValueError:
  3344. # This isn't really a range, it's some content.
  3345. pass
  3346. else:
  3347. value = nativeString(value)
  3348. unstructured.append(value)
  3349. key = key + (value,)
  3350. try:
  3351. value = next(responseParts)
  3352. except StopIteration:
  3353. raise IllegalServerResponse(
  3354. b"Not enough arguments", fetchResponseList
  3355. )
  3356. value = nativeStringResponse(value)
  3357. unstructured.append(value)
  3358. values[key] = value
  3359. return values, unstructured
  3360. def _cbFetch(self, result, requestedParts, structured):
  3361. (lines, last) = result
  3362. info = {}
  3363. for parts in lines:
  3364. if len(parts) == 3 and parts[1] == b"FETCH":
  3365. id = self._intOrRaise(parts[0], parts)
  3366. if id not in info:
  3367. info[id] = [parts[2]]
  3368. else:
  3369. info[id][0].extend(parts[2])
  3370. results = {}
  3371. decodedInfo = {}
  3372. for messageId, values in info.items():
  3373. structuredMap, unstructuredList = self._parseFetchPairs(values[0])
  3374. decodedInfo.setdefault(messageId, [[]])[0].extend(unstructuredList)
  3375. results.setdefault(messageId, {}).update(structuredMap)
  3376. info = decodedInfo
  3377. flagChanges = {}
  3378. for messageId in list(results.keys()):
  3379. values = results[messageId]
  3380. for part in list(values.keys()):
  3381. if part not in requestedParts and part == "FLAGS":
  3382. flagChanges[messageId] = values["FLAGS"]
  3383. # Find flags in the result and get rid of them.
  3384. for i in range(len(info[messageId][0])):
  3385. if info[messageId][0][i] == "FLAGS":
  3386. del info[messageId][0][i : i + 2]
  3387. break
  3388. del values["FLAGS"]
  3389. if not values:
  3390. del results[messageId]
  3391. if flagChanges:
  3392. self.flagsChanged(flagChanges)
  3393. if structured:
  3394. return results
  3395. else:
  3396. return info
  3397. def fetchSpecific(
  3398. self,
  3399. messages,
  3400. uid=0,
  3401. headerType=None,
  3402. headerNumber=None,
  3403. headerArgs=None,
  3404. peek=None,
  3405. offset=None,
  3406. length=None,
  3407. ):
  3408. """
  3409. Retrieve a specific section of one or more messages
  3410. @type messages: L{MessageSet} or L{str}
  3411. @param messages: A message sequence set
  3412. @type uid: L{bool}
  3413. @param uid: Indicates whether the message sequence set is of message
  3414. numbers or of unique message IDs.
  3415. @type headerType: L{str}
  3416. @param headerType: If specified, must be one of HEADER, HEADER.FIELDS,
  3417. HEADER.FIELDS.NOT, MIME, or TEXT, and will determine which part of
  3418. the message is retrieved. For HEADER.FIELDS and HEADER.FIELDS.NOT,
  3419. C{headerArgs} must be a sequence of header names. For MIME,
  3420. C{headerNumber} must be specified.
  3421. @type headerNumber: L{int} or L{int} sequence
  3422. @param headerNumber: The nested rfc822 index specifying the entity to
  3423. retrieve. For example, C{1} retrieves the first entity of the
  3424. message, and C{(2, 1, 3}) retrieves the 3rd entity inside the first
  3425. entity inside the second entity of the message.
  3426. @type headerArgs: A sequence of L{str}
  3427. @param headerArgs: If C{headerType} is HEADER.FIELDS, these are the
  3428. headers to retrieve. If it is HEADER.FIELDS.NOT, these are the
  3429. headers to exclude from retrieval.
  3430. @type peek: C{bool}
  3431. @param peek: If true, cause the server to not set the \\Seen flag on
  3432. this message as a result of this command.
  3433. @type offset: L{int}
  3434. @param offset: The number of octets at the beginning of the result to
  3435. skip.
  3436. @type length: L{int}
  3437. @param length: The number of octets to retrieve.
  3438. @rtype: L{Deferred}
  3439. @return: A deferred whose callback is invoked with a mapping of message
  3440. numbers to retrieved data, or whose errback is invoked if there is
  3441. an error.
  3442. """
  3443. fmt = "%s BODY%s[%s%s%s]%s"
  3444. if headerNumber is None:
  3445. number = ""
  3446. elif isinstance(headerNumber, int):
  3447. number = str(headerNumber)
  3448. else:
  3449. number = ".".join(map(str, headerNumber))
  3450. if headerType is None:
  3451. header = ""
  3452. elif number:
  3453. header = "." + headerType
  3454. else:
  3455. header = headerType
  3456. if header and headerType in ("HEADER.FIELDS", "HEADER.FIELDS.NOT"):
  3457. if headerArgs is not None:
  3458. payload = " (%s)" % " ".join(headerArgs)
  3459. else:
  3460. payload = " ()"
  3461. else:
  3462. payload = ""
  3463. if offset is None:
  3464. extra = ""
  3465. else:
  3466. extra = "<%d.%d>" % (offset, length)
  3467. fetch = uid and b"UID FETCH" or b"FETCH"
  3468. cmd = fmt % (messages, peek and ".PEEK" or "", number, header, payload, extra)
  3469. # APPEND components should be encoded as ASCII unless a
  3470. # charset identifier is provided. See #9201.
  3471. cmd = cmd.encode("charmap")
  3472. d = self.sendCommand(Command(fetch, cmd, wantResponse=(b"FETCH",)))
  3473. d.addCallback(self._cbFetch, (), False)
  3474. return d
  3475. def _fetch(self, messages, useUID=0, **terms):
  3476. messages = str(messages).encode("ascii")
  3477. fetch = useUID and b"UID FETCH" or b"FETCH"
  3478. if "rfc822text" in terms:
  3479. del terms["rfc822text"]
  3480. terms["rfc822.text"] = True
  3481. if "rfc822size" in terms:
  3482. del terms["rfc822size"]
  3483. terms["rfc822.size"] = True
  3484. if "rfc822header" in terms:
  3485. del terms["rfc822header"]
  3486. terms["rfc822.header"] = True
  3487. # The terms in 6.4.5 are all ASCII congruent, so wing it.
  3488. # Note that this isn't a public API, so terms in responses
  3489. # should not be decoded to native strings.
  3490. encodedTerms = [networkString(s) for s in terms]
  3491. cmd = messages + b" (" + b" ".join([s.upper() for s in encodedTerms]) + b")"
  3492. d = self.sendCommand(Command(fetch, cmd, wantResponse=(b"FETCH",)))
  3493. d.addCallback(self._cbFetch, [t.upper() for t in terms.keys()], True)
  3494. return d
  3495. def setFlags(self, messages, flags, silent=1, uid=0):
  3496. """
  3497. Set the flags for one or more messages.
  3498. This command is allowed in the Selected state.
  3499. @type messages: L{MessageSet} or L{str}
  3500. @param messages: A message sequence set
  3501. @type flags: Any iterable of L{str}
  3502. @param flags: The flags to set
  3503. @type silent: L{bool}
  3504. @param silent: If true, cause the server to suppress its verbose
  3505. response.
  3506. @type uid: L{bool}
  3507. @param uid: Indicates whether the message sequence set is of message
  3508. numbers or of unique message IDs.
  3509. @rtype: L{Deferred}
  3510. @return: A deferred whose callback is invoked with a list of the
  3511. server's responses (C{[]} if C{silent} is true) or whose
  3512. errback is invoked if there is an error.
  3513. """
  3514. return self._store(messages, b"FLAGS", silent, flags, uid)
  3515. def addFlags(self, messages, flags, silent=1, uid=0):
  3516. """
  3517. Add to the set flags for one or more messages.
  3518. This command is allowed in the Selected state.
  3519. @type messages: C{MessageSet} or L{str}
  3520. @param messages: A message sequence set
  3521. @type flags: Any iterable of L{str}
  3522. @param flags: The flags to set
  3523. @type silent: C{bool}
  3524. @param silent: If true, cause the server to suppress its verbose
  3525. response.
  3526. @type uid: C{bool}
  3527. @param uid: Indicates whether the message sequence set is of message
  3528. numbers or of unique message IDs.
  3529. @rtype: L{Deferred}
  3530. @return: A deferred whose callback is invoked with a list of the
  3531. server's responses (C{[]} if C{silent} is true) or whose
  3532. errback is invoked if there is an error.
  3533. """
  3534. return self._store(messages, b"+FLAGS", silent, flags, uid)
  3535. def removeFlags(self, messages, flags, silent=1, uid=0):
  3536. """
  3537. Remove from the set flags for one or more messages.
  3538. This command is allowed in the Selected state.
  3539. @type messages: L{MessageSet} or L{str}
  3540. @param messages: A message sequence set
  3541. @type flags: Any iterable of L{str}
  3542. @param flags: The flags to set
  3543. @type silent: L{bool}
  3544. @param silent: If true, cause the server to suppress its verbose
  3545. response.
  3546. @type uid: L{bool}
  3547. @param uid: Indicates whether the message sequence set is of message
  3548. numbers or of unique message IDs.
  3549. @rtype: L{Deferred}
  3550. @return: A deferred whose callback is invoked with a list of the
  3551. server's responses (C{[]} if C{silent} is true) or whose
  3552. errback is invoked if there is an error.
  3553. """
  3554. return self._store(messages, b"-FLAGS", silent, flags, uid)
  3555. def _store(self, messages, cmd, silent, flags, uid):
  3556. messages = str(messages).encode("ascii")
  3557. encodedFlags = [networkString(flag) for flag in flags]
  3558. if silent:
  3559. cmd = cmd + b".SILENT"
  3560. store = uid and b"UID STORE" or b"STORE"
  3561. args = b" ".join((messages, cmd, b"(" + b" ".join(encodedFlags) + b")"))
  3562. d = self.sendCommand(Command(store, args, wantResponse=(b"FETCH",)))
  3563. expected = ()
  3564. if not silent:
  3565. expected = ("FLAGS",)
  3566. d.addCallback(self._cbFetch, expected, True)
  3567. return d
  3568. def copy(self, messages, mailbox, uid):
  3569. """
  3570. Copy the specified messages to the specified mailbox.
  3571. This command is allowed in the Selected state.
  3572. @type messages: L{MessageSet} or L{str}
  3573. @param messages: A message sequence set
  3574. @type mailbox: L{str}
  3575. @param mailbox: The mailbox to which to copy the messages
  3576. @type uid: C{bool}
  3577. @param uid: If true, the C{messages} refers to message UIDs, rather
  3578. than message sequence numbers.
  3579. @rtype: L{Deferred}
  3580. @return: A deferred whose callback is invoked with a true value
  3581. when the copy is successful, or whose errback is invoked if there
  3582. is an error.
  3583. """
  3584. messages = str(messages).encode("ascii")
  3585. if uid:
  3586. cmd = b"UID COPY"
  3587. else:
  3588. cmd = b"COPY"
  3589. args = b" ".join([messages, _prepareMailboxName(mailbox)])
  3590. return self.sendCommand(Command(cmd, args))
  3591. #
  3592. # IMailboxListener methods
  3593. #
  3594. def modeChanged(self, writeable):
  3595. """Override me"""
  3596. def flagsChanged(self, newFlags):
  3597. """Override me"""
  3598. def newMessages(self, exists, recent):
  3599. """Override me"""
  3600. def parseIdList(s, lastMessageId=None):
  3601. """
  3602. Parse a message set search key into a C{MessageSet}.
  3603. @type s: L{bytes}
  3604. @param s: A string description of an id list, for example "1:3, 4:*"
  3605. @type lastMessageId: L{int}
  3606. @param lastMessageId: The last message sequence id or UID, depending on
  3607. whether we are parsing the list in UID or sequence id context. The
  3608. caller should pass in the correct value.
  3609. @rtype: C{MessageSet}
  3610. @return: A C{MessageSet} that contains the ids defined in the list
  3611. """
  3612. res = MessageSet()
  3613. parts = s.split(b",")
  3614. for p in parts:
  3615. if b":" in p:
  3616. low, high = p.split(b":", 1)
  3617. try:
  3618. if low == b"*":
  3619. low = None
  3620. else:
  3621. low = int(low)
  3622. if high == b"*":
  3623. high = None
  3624. else:
  3625. high = int(high)
  3626. if low is high is None:
  3627. # *:* does not make sense
  3628. raise IllegalIdentifierError(p)
  3629. # non-positive values are illegal according to RFC 3501
  3630. if (low is not None and low <= 0) or (high is not None and high <= 0):
  3631. raise IllegalIdentifierError(p)
  3632. # star means "highest value of an id in the mailbox"
  3633. high = high or lastMessageId
  3634. low = low or lastMessageId
  3635. res.add(low, high)
  3636. except ValueError:
  3637. raise IllegalIdentifierError(p)
  3638. else:
  3639. try:
  3640. if p == b"*":
  3641. p = None
  3642. else:
  3643. p = int(p)
  3644. if p is not None and p <= 0:
  3645. raise IllegalIdentifierError(p)
  3646. except ValueError:
  3647. raise IllegalIdentifierError(p)
  3648. else:
  3649. res.extend(p or lastMessageId)
  3650. return res
  3651. _SIMPLE_BOOL = (
  3652. "ALL",
  3653. "ANSWERED",
  3654. "DELETED",
  3655. "DRAFT",
  3656. "FLAGGED",
  3657. "NEW",
  3658. "OLD",
  3659. "RECENT",
  3660. "SEEN",
  3661. "UNANSWERED",
  3662. "UNDELETED",
  3663. "UNDRAFT",
  3664. "UNFLAGGED",
  3665. "UNSEEN",
  3666. )
  3667. _NO_QUOTES = ("LARGER", "SMALLER", "UID")
  3668. _sorted = sorted
  3669. def Query(sorted=0, **kwarg):
  3670. """
  3671. Create a query string
  3672. Among the accepted keywords are::
  3673. all : If set to a true value, search all messages in the
  3674. current mailbox
  3675. answered : If set to a true value, search messages flagged with
  3676. \\Answered
  3677. bcc : A substring to search the BCC header field for
  3678. before : Search messages with an internal date before this
  3679. value. The given date should be a string in the format
  3680. of 'DD-Mon-YYYY'. For example, '03-Mar-2003'.
  3681. body : A substring to search the body of the messages for
  3682. cc : A substring to search the CC header field for
  3683. deleted : If set to a true value, search messages flagged with
  3684. \\Deleted
  3685. draft : If set to a true value, search messages flagged with
  3686. \\Draft
  3687. flagged : If set to a true value, search messages flagged with
  3688. \\Flagged
  3689. from : A substring to search the From header field for
  3690. header : A two-tuple of a header name and substring to search
  3691. for in that header
  3692. keyword : Search for messages with the given keyword set
  3693. larger : Search for messages larger than this number of octets
  3694. messages : Search only the given message sequence set.
  3695. new : If set to a true value, search messages flagged with
  3696. \\Recent but not \\Seen
  3697. old : If set to a true value, search messages not flagged with
  3698. \\Recent
  3699. on : Search messages with an internal date which is on this
  3700. date. The given date should be a string in the format
  3701. of 'DD-Mon-YYYY'. For example, '03-Mar-2003'.
  3702. recent : If set to a true value, search for messages flagged with
  3703. \\Recent
  3704. seen : If set to a true value, search for messages flagged with
  3705. \\Seen
  3706. sentbefore : Search for messages with an RFC822 'Date' header before
  3707. this date. The given date should be a string in the format
  3708. of 'DD-Mon-YYYY'. For example, '03-Mar-2003'.
  3709. senton : Search for messages with an RFC822 'Date' header which is
  3710. on this date The given date should be a string in the format
  3711. of 'DD-Mon-YYYY'. For example, '03-Mar-2003'.
  3712. sentsince : Search for messages with an RFC822 'Date' header which is
  3713. after this date. The given date should be a string in the format
  3714. of 'DD-Mon-YYYY'. For example, '03-Mar-2003'.
  3715. since : Search for messages with an internal date that is after
  3716. this date.. The given date should be a string in the format
  3717. of 'DD-Mon-YYYY'. For example, '03-Mar-2003'.
  3718. smaller : Search for messages smaller than this number of octets
  3719. subject : A substring to search the 'subject' header for
  3720. text : A substring to search the entire message for
  3721. to : A substring to search the 'to' header for
  3722. uid : Search only the messages in the given message set
  3723. unanswered : If set to a true value, search for messages not
  3724. flagged with \\Answered
  3725. undeleted : If set to a true value, search for messages not
  3726. flagged with \\Deleted
  3727. undraft : If set to a true value, search for messages not
  3728. flagged with \\Draft
  3729. unflagged : If set to a true value, search for messages not
  3730. flagged with \\Flagged
  3731. unkeyword : Search for messages without the given keyword set
  3732. unseen : If set to a true value, search for messages not
  3733. flagged with \\Seen
  3734. @type sorted: C{bool}
  3735. @param sorted: If true, the output will be sorted, alphabetically.
  3736. The standard does not require it, but it makes testing this function
  3737. easier. The default is zero, and this should be acceptable for any
  3738. application.
  3739. @rtype: L{str}
  3740. @return: The formatted query string
  3741. """
  3742. cmd = []
  3743. keys = kwarg.keys()
  3744. if sorted:
  3745. keys = _sorted(keys)
  3746. for k in keys:
  3747. v = kwarg[k]
  3748. k = k.upper()
  3749. if k in _SIMPLE_BOOL and v:
  3750. cmd.append(k)
  3751. elif k == "HEADER":
  3752. cmd.extend([k, str(v[0]), str(v[1])])
  3753. elif k == "KEYWORD" or k == "UNKEYWORD":
  3754. # Discard anything that does not fit into an "atom". Perhaps turn
  3755. # the case where this actually removes bytes from the value into a
  3756. # warning and then an error, eventually. See #6277.
  3757. v = _nonAtomRE.sub("", v)
  3758. cmd.extend([k, v])
  3759. elif k not in _NO_QUOTES:
  3760. if isinstance(v, MessageSet):
  3761. fmt = '"%s"'
  3762. elif isinstance(v, str):
  3763. fmt = '"%s"'
  3764. else:
  3765. fmt = '"%d"'
  3766. cmd.extend([k, fmt % (v,)])
  3767. elif isinstance(v, int):
  3768. cmd.extend([k, "%d" % (v,)])
  3769. else:
  3770. cmd.extend([k, f"{v}"])
  3771. if len(cmd) > 1:
  3772. return "(" + " ".join(cmd) + ")"
  3773. else:
  3774. return " ".join(cmd)
  3775. def Or(*args):
  3776. """
  3777. The disjunction of two or more queries
  3778. """
  3779. if len(args) < 2:
  3780. raise IllegalQueryError(args)
  3781. elif len(args) == 2:
  3782. return "(OR %s %s)" % args
  3783. else:
  3784. return f"(OR {args[0]} {Or(*args[1:])})"
  3785. def Not(query):
  3786. """The negation of a query"""
  3787. return f"(NOT {query})"
  3788. def wildcardToRegexp(wildcard, delim=None):
  3789. wildcard = wildcard.replace("*", "(?:.*?)")
  3790. if delim is None:
  3791. wildcard = wildcard.replace("%", "(?:.*?)")
  3792. else:
  3793. wildcard = wildcard.replace("%", "(?:(?:[^%s])*?)" % re.escape(delim))
  3794. return re.compile(wildcard, re.I)
  3795. def splitQuoted(s):
  3796. """
  3797. Split a string into whitespace delimited tokens
  3798. Tokens that would otherwise be separated but are surrounded by \"
  3799. remain as a single token. Any token that is not quoted and is
  3800. equal to \"NIL\" is tokenized as L{None}.
  3801. @type s: L{bytes}
  3802. @param s: The string to be split
  3803. @rtype: L{list} of L{bytes}
  3804. @return: A list of the resulting tokens
  3805. @raise MismatchedQuoting: Raised if an odd number of quotes are present
  3806. """
  3807. s = s.strip()
  3808. result = []
  3809. word = []
  3810. inQuote = inWord = False
  3811. qu = _matchingString('"', s)
  3812. esc = _matchingString("\x5c", s)
  3813. empty = _matchingString("", s)
  3814. nil = _matchingString("NIL", s)
  3815. for i, c in enumerate(iterbytes(s)):
  3816. if c == qu:
  3817. if i and s[i - 1 : i] == esc:
  3818. word.pop()
  3819. word.append(qu)
  3820. elif not inQuote:
  3821. inQuote = True
  3822. else:
  3823. inQuote = False
  3824. result.append(empty.join(word))
  3825. word = []
  3826. elif (
  3827. not inWord
  3828. and not inQuote
  3829. and c not in (qu + (string.whitespace.encode("ascii")))
  3830. ):
  3831. inWord = True
  3832. word.append(c)
  3833. elif inWord and not inQuote and c in string.whitespace.encode("ascii"):
  3834. w = empty.join(word)
  3835. if w == nil:
  3836. result.append(None)
  3837. else:
  3838. result.append(w)
  3839. word = []
  3840. inWord = False
  3841. elif inWord or inQuote:
  3842. word.append(c)
  3843. if inQuote:
  3844. raise MismatchedQuoting(s)
  3845. if inWord:
  3846. w = empty.join(word)
  3847. if w == nil:
  3848. result.append(None)
  3849. else:
  3850. result.append(w)
  3851. return result
  3852. def splitOn(sequence, predicate, transformers):
  3853. result = []
  3854. mode = predicate(sequence[0])
  3855. tmp = [sequence[0]]
  3856. for e in sequence[1:]:
  3857. p = predicate(e)
  3858. if p != mode:
  3859. result.extend(transformers[mode](tmp))
  3860. tmp = [e]
  3861. mode = p
  3862. else:
  3863. tmp.append(e)
  3864. result.extend(transformers[mode](tmp))
  3865. return result
  3866. def collapseStrings(results):
  3867. """
  3868. Turns a list of length-one strings and lists into a list of longer
  3869. strings and lists. For example,
  3870. ['a', 'b', ['c', 'd']] is returned as ['ab', ['cd']]
  3871. @type results: L{list} of L{bytes} and L{list}
  3872. @param results: The list to be collapsed
  3873. @rtype: L{list} of L{bytes} and L{list}
  3874. @return: A new list which is the collapsed form of C{results}
  3875. """
  3876. copy = []
  3877. begun = None
  3878. pred = lambda e: isinstance(e, tuple)
  3879. tran = {
  3880. 0: lambda e: splitQuoted(b"".join(e)),
  3881. 1: lambda e: [b"".join([i[0] for i in e])],
  3882. }
  3883. for i, c in enumerate(results):
  3884. if isinstance(c, list):
  3885. if begun is not None:
  3886. copy.extend(splitOn(results[begun:i], pred, tran))
  3887. begun = None
  3888. copy.append(collapseStrings(c))
  3889. elif begun is None:
  3890. begun = i
  3891. if begun is not None:
  3892. copy.extend(splitOn(results[begun:], pred, tran))
  3893. return copy
  3894. def parseNestedParens(s, handleLiteral=1):
  3895. """
  3896. Parse an s-exp-like string into a more useful data structure.
  3897. @type s: L{bytes}
  3898. @param s: The s-exp-like string to parse
  3899. @rtype: L{list} of L{bytes} and L{list}
  3900. @return: A list containing the tokens present in the input.
  3901. @raise MismatchedNesting: Raised if the number or placement
  3902. of opening or closing parenthesis is invalid.
  3903. """
  3904. s = s.strip()
  3905. inQuote = 0
  3906. contentStack = [[]]
  3907. try:
  3908. i = 0
  3909. L = len(s)
  3910. while i < L:
  3911. c = s[i : i + 1]
  3912. if inQuote:
  3913. if c == b"\\":
  3914. contentStack[-1].append(s[i : i + 2])
  3915. i += 2
  3916. continue
  3917. elif c == b'"':
  3918. inQuote = not inQuote
  3919. contentStack[-1].append(c)
  3920. i += 1
  3921. else:
  3922. if c == b'"':
  3923. contentStack[-1].append(c)
  3924. inQuote = not inQuote
  3925. i += 1
  3926. elif handleLiteral and c == b"{":
  3927. end = s.find(b"}", i)
  3928. if end == -1:
  3929. raise ValueError("Malformed literal")
  3930. literalSize = int(s[i + 1 : end])
  3931. contentStack[-1].append((s[end + 3 : end + 3 + literalSize],))
  3932. i = end + 3 + literalSize
  3933. elif c == b"(" or c == b"[":
  3934. contentStack.append([])
  3935. i += 1
  3936. elif c == b")" or c == b"]":
  3937. contentStack[-2].append(contentStack.pop())
  3938. i += 1
  3939. else:
  3940. contentStack[-1].append(c)
  3941. i += 1
  3942. except IndexError:
  3943. raise MismatchedNesting(s)
  3944. if len(contentStack) != 1:
  3945. raise MismatchedNesting(s)
  3946. return collapseStrings(contentStack[0])
  3947. def _quote(s):
  3948. qu = _matchingString('"', s)
  3949. esc = _matchingString("\x5c", s)
  3950. return qu + s.replace(esc, esc + esc).replace(qu, esc + qu) + qu
  3951. def _literal(s: bytes) -> bytes:
  3952. return b"{%d}\r\n%b" % (len(s), s)
  3953. class DontQuoteMe:
  3954. def __init__(self, value):
  3955. self.value = value
  3956. def __str__(self) -> str:
  3957. return str(self.value)
  3958. _ATOM_SPECIALS = b'(){ %*"'
  3959. def _needsQuote(s):
  3960. if s == b"":
  3961. return 1
  3962. for c in iterbytes(s):
  3963. if c < b"\x20" or c > b"\x7f":
  3964. return 1
  3965. if c in _ATOM_SPECIALS:
  3966. return 1
  3967. return 0
  3968. def _parseMbox(name):
  3969. if isinstance(name, str):
  3970. return name
  3971. try:
  3972. return name.decode("imap4-utf-7")
  3973. except BaseException:
  3974. log.err()
  3975. raise IllegalMailboxEncoding(name)
  3976. def _prepareMailboxName(name):
  3977. if not isinstance(name, str):
  3978. name = name.decode("charmap")
  3979. name = name.encode("imap4-utf-7")
  3980. if _needsQuote(name):
  3981. return _quote(name)
  3982. return name
  3983. def _needsLiteral(s):
  3984. # change this to "return 1" to wig out stupid clients
  3985. cr = _matchingString("\n", s)
  3986. lf = _matchingString("\r", s)
  3987. return cr in s or lf in s or len(s) > 1000
  3988. def collapseNestedLists(items):
  3989. """
  3990. Turn a nested list structure into an s-exp-like string.
  3991. Strings in C{items} will be sent as literals if they contain CR or LF,
  3992. otherwise they will be quoted. References to None in C{items} will be
  3993. translated to the atom NIL. Objects with a 'read' attribute will have
  3994. it called on them with no arguments and the returned string will be
  3995. inserted into the output as a literal. Integers will be converted to
  3996. strings and inserted into the output unquoted. Instances of
  3997. C{DontQuoteMe} will be converted to strings and inserted into the output
  3998. unquoted.
  3999. This function used to be much nicer, and only quote things that really
  4000. needed to be quoted (and C{DontQuoteMe} did not exist), however, many
  4001. broken IMAP4 clients were unable to deal with this level of sophistication,
  4002. forcing the current behavior to be adopted for practical reasons.
  4003. @type items: Any iterable
  4004. @rtype: L{str}
  4005. """
  4006. pieces = []
  4007. for i in items:
  4008. if isinstance(i, str):
  4009. # anything besides ASCII will have to wait for an RFC 5738
  4010. # implementation. See
  4011. # https://twistedmatrix.com/trac/ticket/9258
  4012. i = i.encode("ascii")
  4013. if i is None:
  4014. pieces.extend([b" ", b"NIL"])
  4015. elif isinstance(i, int):
  4016. pieces.extend([b" ", networkString(str(i))])
  4017. elif isinstance(i, DontQuoteMe):
  4018. pieces.extend([b" ", i.value])
  4019. elif isinstance(i, bytes):
  4020. # XXX warning
  4021. if _needsLiteral(i):
  4022. pieces.extend([b" ", b"{%d}" % (len(i),), IMAP4Server.delimiter, i])
  4023. else:
  4024. pieces.extend([b" ", _quote(i)])
  4025. elif hasattr(i, "read"):
  4026. d = i.read()
  4027. pieces.extend([b" ", b"{%d}" % (len(d),), IMAP4Server.delimiter, d])
  4028. else:
  4029. pieces.extend([b" ", b"(" + collapseNestedLists(i) + b")"])
  4030. return b"".join(pieces[1:])
  4031. @implementer(IAccount)
  4032. class MemoryAccountWithoutNamespaces:
  4033. mailboxes = None
  4034. subscriptions = None
  4035. top_id = 0
  4036. def __init__(self, name):
  4037. self.name = name
  4038. self.mailboxes = {}
  4039. self.subscriptions = []
  4040. def allocateID(self):
  4041. id = self.top_id
  4042. self.top_id += 1
  4043. return id
  4044. ##
  4045. ## IAccount
  4046. ##
  4047. def addMailbox(self, name, mbox=None):
  4048. name = _parseMbox(name.upper())
  4049. if name in self.mailboxes:
  4050. raise MailboxCollision(name)
  4051. if mbox is None:
  4052. mbox = self._emptyMailbox(name, self.allocateID())
  4053. self.mailboxes[name] = mbox
  4054. return 1
  4055. def create(self, pathspec):
  4056. paths = [path for path in pathspec.split("/") if path]
  4057. for accum in range(1, len(paths)):
  4058. try:
  4059. self.addMailbox("/".join(paths[:accum]))
  4060. except MailboxCollision:
  4061. pass
  4062. try:
  4063. self.addMailbox("/".join(paths))
  4064. except MailboxCollision:
  4065. if not pathspec.endswith("/"):
  4066. return False
  4067. return True
  4068. def _emptyMailbox(self, name, id):
  4069. raise NotImplementedError
  4070. def select(self, name, readwrite=1):
  4071. return self.mailboxes.get(_parseMbox(name.upper()))
  4072. def delete(self, name):
  4073. name = _parseMbox(name.upper())
  4074. # See if this mailbox exists at all
  4075. mbox = self.mailboxes.get(name)
  4076. if not mbox:
  4077. raise MailboxException("No such mailbox")
  4078. # See if this box is flagged \Noselect
  4079. if r"\Noselect" in mbox.getFlags():
  4080. # Check for hierarchically inferior mailboxes with this one
  4081. # as part of their root.
  4082. for others in self.mailboxes.keys():
  4083. if others != name and others.startswith(name):
  4084. raise MailboxException(
  4085. "Hierarchically inferior mailboxes exist and \\Noselect is set"
  4086. )
  4087. mbox.destroy()
  4088. # iff there are no hierarchically inferior names, we will
  4089. # delete it from our ken.
  4090. if len(self._inferiorNames(name)) > 1:
  4091. raise MailboxException(f'Name "{name}" has inferior hierarchical names')
  4092. del self.mailboxes[name]
  4093. def rename(self, oldname, newname):
  4094. oldname = _parseMbox(oldname.upper())
  4095. newname = _parseMbox(newname.upper())
  4096. if oldname not in self.mailboxes:
  4097. raise NoSuchMailbox(oldname)
  4098. inferiors = self._inferiorNames(oldname)
  4099. inferiors = [(o, o.replace(oldname, newname, 1)) for o in inferiors]
  4100. for old, new in inferiors:
  4101. if new in self.mailboxes:
  4102. raise MailboxCollision(new)
  4103. for old, new in inferiors:
  4104. self.mailboxes[new] = self.mailboxes[old]
  4105. del self.mailboxes[old]
  4106. def _inferiorNames(self, name):
  4107. inferiors = []
  4108. for infname in self.mailboxes.keys():
  4109. if infname.startswith(name):
  4110. inferiors.append(infname)
  4111. return inferiors
  4112. def isSubscribed(self, name):
  4113. return _parseMbox(name.upper()) in self.subscriptions
  4114. def subscribe(self, name):
  4115. name = _parseMbox(name.upper())
  4116. if name not in self.subscriptions:
  4117. self.subscriptions.append(name)
  4118. def unsubscribe(self, name):
  4119. name = _parseMbox(name.upper())
  4120. if name not in self.subscriptions:
  4121. raise MailboxException(f"Not currently subscribed to {name}")
  4122. self.subscriptions.remove(name)
  4123. def listMailboxes(self, ref, wildcard):
  4124. ref = self._inferiorNames(_parseMbox(ref.upper()))
  4125. wildcard = wildcardToRegexp(wildcard, "/")
  4126. return [(i, self.mailboxes[i]) for i in ref if wildcard.match(i)]
  4127. @implementer(INamespacePresenter)
  4128. class MemoryAccount(MemoryAccountWithoutNamespaces):
  4129. ##
  4130. ## INamespacePresenter
  4131. ##
  4132. def getPersonalNamespaces(self):
  4133. return [[b"", b"/"]]
  4134. def getSharedNamespaces(self):
  4135. return None
  4136. def getOtherNamespaces(self):
  4137. return None
  4138. def getUserNamespaces(self):
  4139. # INamespacePresenter.getUserNamespaces
  4140. return None
  4141. _statusRequestDict = {
  4142. "MESSAGES": "getMessageCount",
  4143. "RECENT": "getRecentCount",
  4144. "UIDNEXT": "getUIDNext",
  4145. "UIDVALIDITY": "getUIDValidity",
  4146. "UNSEEN": "getUnseenCount",
  4147. }
  4148. def statusRequestHelper(mbox, names):
  4149. r = {}
  4150. for n in names:
  4151. r[n] = getattr(mbox, _statusRequestDict[n.upper()])()
  4152. return r
  4153. def parseAddr(addr):
  4154. if addr is None:
  4155. return [
  4156. (None, None, None),
  4157. ]
  4158. addr = email.utils.getaddresses([addr])
  4159. return [[fn or None, None] + address.split("@") for fn, address in addr]
  4160. def getEnvelope(msg):
  4161. headers = msg.getHeaders(True)
  4162. date = headers.get("date")
  4163. subject = headers.get("subject")
  4164. from_ = headers.get("from")
  4165. sender = headers.get("sender", from_)
  4166. reply_to = headers.get("reply-to", from_)
  4167. to = headers.get("to")
  4168. cc = headers.get("cc")
  4169. bcc = headers.get("bcc")
  4170. in_reply_to = headers.get("in-reply-to")
  4171. mid = headers.get("message-id")
  4172. return (
  4173. date,
  4174. subject,
  4175. parseAddr(from_),
  4176. parseAddr(sender),
  4177. reply_to and parseAddr(reply_to),
  4178. to and parseAddr(to),
  4179. cc and parseAddr(cc),
  4180. bcc and parseAddr(bcc),
  4181. in_reply_to,
  4182. mid,
  4183. )
  4184. def getLineCount(msg):
  4185. # XXX - Super expensive, CACHE THIS VALUE FOR LATER RE-USE
  4186. # XXX - This must be the number of lines in the ENCODED version
  4187. lines = 0
  4188. for _ in msg.getBodyFile():
  4189. lines += 1
  4190. return lines
  4191. def unquote(s):
  4192. if s[0] == s[-1] == '"':
  4193. return s[1:-1]
  4194. return s
  4195. def _getContentType(msg):
  4196. """
  4197. Return a two-tuple of the main and subtype of the given message.
  4198. """
  4199. attrs = None
  4200. mm = msg.getHeaders(False, "content-type").get("content-type", "")
  4201. mm = "".join(mm.splitlines())
  4202. if mm:
  4203. mimetype = mm.split(";")
  4204. type = mimetype[0].split("/", 1)
  4205. if len(type) == 1:
  4206. major = type[0]
  4207. minor = None
  4208. else:
  4209. # length must be 2, because of split('/', 1)
  4210. major, minor = type
  4211. attrs = dict(x.strip().lower().split("=", 1) for x in mimetype[1:])
  4212. else:
  4213. major = minor = None
  4214. return major, minor, attrs
  4215. def _getMessageStructure(message):
  4216. """
  4217. Construct an appropriate type of message structure object for the given
  4218. message object.
  4219. @param message: A L{IMessagePart} provider
  4220. @return: A L{_MessageStructure} instance of the most specific type available
  4221. for the given message, determined by inspecting the MIME type of the
  4222. message.
  4223. """
  4224. main, subtype, attrs = _getContentType(message)
  4225. if main is not None:
  4226. main = main.lower()
  4227. if subtype is not None:
  4228. subtype = subtype.lower()
  4229. if main == "multipart":
  4230. return _MultipartMessageStructure(message, subtype, attrs)
  4231. elif (main, subtype) == ("message", "rfc822"):
  4232. return _RFC822MessageStructure(message, main, subtype, attrs)
  4233. elif main == "text":
  4234. return _TextMessageStructure(message, main, subtype, attrs)
  4235. else:
  4236. return _SinglepartMessageStructure(message, main, subtype, attrs)
  4237. class _MessageStructure:
  4238. """
  4239. L{_MessageStructure} is a helper base class for message structure classes
  4240. representing the structure of particular kinds of messages, as defined by
  4241. their MIME type.
  4242. """
  4243. def __init__(self, message, attrs):
  4244. """
  4245. @param message: An L{IMessagePart} provider which this structure object
  4246. reports on.
  4247. @param attrs: A C{dict} giving the parameters of the I{Content-Type}
  4248. header of the message.
  4249. """
  4250. self.message = message
  4251. self.attrs = attrs
  4252. def _disposition(self, disp):
  4253. """
  4254. Parse a I{Content-Disposition} header into a two-sequence of the
  4255. disposition and a flattened list of its parameters.
  4256. @return: L{None} if there is no disposition header value, a L{list} with
  4257. two elements otherwise.
  4258. """
  4259. if disp:
  4260. disp = disp.split("; ")
  4261. if len(disp) == 1:
  4262. disp = (disp[0].lower(), None)
  4263. elif len(disp) > 1:
  4264. # XXX Poorly tested parser
  4265. params = [x for param in disp[1:] for x in param.split("=", 1)]
  4266. disp = [disp[0].lower(), params]
  4267. return disp
  4268. else:
  4269. return None
  4270. def _unquotedAttrs(self):
  4271. """
  4272. @return: The I{Content-Type} parameters, unquoted, as a flat list with
  4273. each Nth element giving a parameter name and N+1th element giving
  4274. the corresponding parameter value.
  4275. """
  4276. if self.attrs:
  4277. unquoted = [(k, unquote(v)) for (k, v) in self.attrs.items()]
  4278. return [y for x in sorted(unquoted) for y in x]
  4279. return None
  4280. class _SinglepartMessageStructure(_MessageStructure):
  4281. """
  4282. L{_SinglepartMessageStructure} represents the message structure of a
  4283. non-I{multipart/*} message.
  4284. """
  4285. _HEADERS = ["content-id", "content-description", "content-transfer-encoding"]
  4286. def __init__(self, message, main, subtype, attrs):
  4287. """
  4288. @param message: An L{IMessagePart} provider which this structure object
  4289. reports on.
  4290. @param main: A L{str} giving the main MIME type of the message (for
  4291. example, C{"text"}).
  4292. @param subtype: A L{str} giving the MIME subtype of the message (for
  4293. example, C{"plain"}).
  4294. @param attrs: A C{dict} giving the parameters of the I{Content-Type}
  4295. header of the message.
  4296. """
  4297. _MessageStructure.__init__(self, message, attrs)
  4298. self.main = main
  4299. self.subtype = subtype
  4300. self.attrs = attrs
  4301. def _basicFields(self):
  4302. """
  4303. Return a list of the basic fields for a single-part message.
  4304. """
  4305. headers = self.message.getHeaders(False, *self._HEADERS)
  4306. # Number of octets total
  4307. size = self.message.getSize()
  4308. major, minor = self.main, self.subtype
  4309. # content-type parameter list
  4310. unquotedAttrs = self._unquotedAttrs()
  4311. return [
  4312. major,
  4313. minor,
  4314. unquotedAttrs,
  4315. headers.get("content-id"),
  4316. headers.get("content-description"),
  4317. headers.get("content-transfer-encoding"),
  4318. size,
  4319. ]
  4320. def encode(self, extended):
  4321. """
  4322. Construct and return a list of the basic and extended fields for a
  4323. single-part message. The list suitable to be encoded into a BODY or
  4324. BODYSTRUCTURE response.
  4325. """
  4326. result = self._basicFields()
  4327. if extended:
  4328. result.extend(self._extended())
  4329. return result
  4330. def _extended(self):
  4331. """
  4332. The extension data of a non-multipart body part are in the
  4333. following order:
  4334. 1. body MD5
  4335. A string giving the body MD5 value as defined in [MD5].
  4336. 2. body disposition
  4337. A parenthesized list with the same content and function as
  4338. the body disposition for a multipart body part.
  4339. 3. body language
  4340. A string or parenthesized list giving the body language
  4341. value as defined in [LANGUAGE-TAGS].
  4342. 4. body location
  4343. A string list giving the body content URI as defined in
  4344. [LOCATION].
  4345. """
  4346. result = []
  4347. headers = self.message.getHeaders(
  4348. False,
  4349. "content-md5",
  4350. "content-disposition",
  4351. "content-language",
  4352. "content-language",
  4353. )
  4354. result.append(headers.get("content-md5"))
  4355. result.append(self._disposition(headers.get("content-disposition")))
  4356. result.append(headers.get("content-language"))
  4357. result.append(headers.get("content-location"))
  4358. return result
  4359. class _TextMessageStructure(_SinglepartMessageStructure):
  4360. """
  4361. L{_TextMessageStructure} represents the message structure of a I{text/*}
  4362. message.
  4363. """
  4364. def encode(self, extended):
  4365. """
  4366. A body type of type TEXT contains, immediately after the basic
  4367. fields, the size of the body in text lines. Note that this
  4368. size is the size in its content transfer encoding and not the
  4369. resulting size after any decoding.
  4370. """
  4371. result = _SinglepartMessageStructure._basicFields(self)
  4372. result.append(getLineCount(self.message))
  4373. if extended:
  4374. result.extend(self._extended())
  4375. return result
  4376. class _RFC822MessageStructure(_SinglepartMessageStructure):
  4377. """
  4378. L{_RFC822MessageStructure} represents the message structure of a
  4379. I{message/rfc822} message.
  4380. """
  4381. def encode(self, extended):
  4382. """
  4383. A body type of type MESSAGE and subtype RFC822 contains,
  4384. immediately after the basic fields, the envelope structure,
  4385. body structure, and size in text lines of the encapsulated
  4386. message.
  4387. """
  4388. result = _SinglepartMessageStructure.encode(self, extended)
  4389. contained = self.message.getSubPart(0)
  4390. result.append(getEnvelope(contained))
  4391. result.append(getBodyStructure(contained, False))
  4392. result.append(getLineCount(contained))
  4393. return result
  4394. class _MultipartMessageStructure(_MessageStructure):
  4395. """
  4396. L{_MultipartMessageStructure} represents the message structure of a
  4397. I{multipart/*} message.
  4398. """
  4399. def __init__(self, message, subtype, attrs):
  4400. """
  4401. @param message: An L{IMessagePart} provider which this structure object
  4402. reports on.
  4403. @param subtype: A L{str} giving the MIME subtype of the message (for
  4404. example, C{"plain"}).
  4405. @param attrs: A C{dict} giving the parameters of the I{Content-Type}
  4406. header of the message.
  4407. """
  4408. _MessageStructure.__init__(self, message, attrs)
  4409. self.subtype = subtype
  4410. def _getParts(self):
  4411. """
  4412. Return an iterator over all of the sub-messages of this message.
  4413. """
  4414. i = 0
  4415. while True:
  4416. try:
  4417. part = self.message.getSubPart(i)
  4418. except IndexError:
  4419. break
  4420. else:
  4421. yield part
  4422. i += 1
  4423. def encode(self, extended):
  4424. """
  4425. Encode each sub-message and added the additional I{multipart} fields.
  4426. """
  4427. result = [_getMessageStructure(p).encode(extended) for p in self._getParts()]
  4428. result.append(self.subtype)
  4429. if extended:
  4430. result.extend(self._extended())
  4431. return result
  4432. def _extended(self):
  4433. """
  4434. The extension data of a multipart body part are in the following order:
  4435. 1. body parameter parenthesized list
  4436. A parenthesized list of attribute/value pairs [e.g., ("foo"
  4437. "bar" "baz" "rag") where "bar" is the value of "foo", and
  4438. "rag" is the value of "baz"] as defined in [MIME-IMB].
  4439. 2. body disposition
  4440. A parenthesized list, consisting of a disposition type
  4441. string, followed by a parenthesized list of disposition
  4442. attribute/value pairs as defined in [DISPOSITION].
  4443. 3. body language
  4444. A string or parenthesized list giving the body language
  4445. value as defined in [LANGUAGE-TAGS].
  4446. 4. body location
  4447. A string list giving the body content URI as defined in
  4448. [LOCATION].
  4449. """
  4450. result = []
  4451. headers = self.message.getHeaders(
  4452. False, "content-language", "content-location", "content-disposition"
  4453. )
  4454. result.append(self._unquotedAttrs())
  4455. result.append(self._disposition(headers.get("content-disposition")))
  4456. result.append(headers.get("content-language", None))
  4457. result.append(headers.get("content-location", None))
  4458. return result
  4459. def getBodyStructure(msg, extended=False):
  4460. """
  4461. RFC 3501, 7.4.2, BODYSTRUCTURE::
  4462. A parenthesized list that describes the [MIME-IMB] body structure of a
  4463. message. This is computed by the server by parsing the [MIME-IMB] header
  4464. fields, defaulting various fields as necessary.
  4465. For example, a simple text message of 48 lines and 2279 octets can have
  4466. a body structure of: ("TEXT" "PLAIN" ("CHARSET" "US-ASCII") NIL NIL
  4467. "7BIT" 2279 48)
  4468. This is represented as::
  4469. ["TEXT", "PLAIN", ["CHARSET", "US-ASCII"], None, None, "7BIT", 2279, 48]
  4470. These basic fields are documented in the RFC as:
  4471. 1. body type
  4472. A string giving the content media type name as defined in
  4473. [MIME-IMB].
  4474. 2. body subtype
  4475. A string giving the content subtype name as defined in
  4476. [MIME-IMB].
  4477. 3. body parameter parenthesized list
  4478. A parenthesized list of attribute/value pairs [e.g., ("foo"
  4479. "bar" "baz" "rag") where "bar" is the value of "foo" and
  4480. "rag" is the value of "baz"] as defined in [MIME-IMB].
  4481. 4. body id
  4482. A string giving the content id as defined in [MIME-IMB].
  4483. 5. body description
  4484. A string giving the content description as defined in
  4485. [MIME-IMB].
  4486. 6. body encoding
  4487. A string giving the content transfer encoding as defined in
  4488. [MIME-IMB].
  4489. 7. body size
  4490. A number giving the size of the body in octets. Note that this size is
  4491. the size in its transfer encoding and not the resulting size after any
  4492. decoding.
  4493. Put another way, the body structure is a list of seven elements. The
  4494. semantics of the elements of this list are:
  4495. 1. Byte string giving the major MIME type
  4496. 2. Byte string giving the minor MIME type
  4497. 3. A list giving the Content-Type parameters of the message
  4498. 4. A byte string giving the content identifier for the message part, or
  4499. None if it has no content identifier.
  4500. 5. A byte string giving the content description for the message part, or
  4501. None if it has no content description.
  4502. 6. A byte string giving the Content-Encoding of the message body
  4503. 7. An integer giving the number of octets in the message body
  4504. The RFC goes on::
  4505. Multiple parts are indicated by parenthesis nesting. Instead of a body
  4506. type as the first element of the parenthesized list, there is a sequence
  4507. of one or more nested body structures. The second element of the
  4508. parenthesized list is the multipart subtype (mixed, digest, parallel,
  4509. alternative, etc.).
  4510. For example, a two part message consisting of a text and a
  4511. BASE64-encoded text attachment can have a body structure of: (("TEXT"
  4512. "PLAIN" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 1152 23)("TEXT" "PLAIN"
  4513. ("CHARSET" "US-ASCII" "NAME" "cc.diff")
  4514. "<960723163407.20117h@cac.washington.edu>" "Compiler diff" "BASE64" 4554
  4515. 73) "MIXED")
  4516. This is represented as::
  4517. [["TEXT", "PLAIN", ["CHARSET", "US-ASCII"], None, None, "7BIT", 1152,
  4518. 23],
  4519. ["TEXT", "PLAIN", ["CHARSET", "US-ASCII", "NAME", "cc.diff"],
  4520. "<960723163407.20117h@cac.washington.edu>", "Compiler diff",
  4521. "BASE64", 4554, 73],
  4522. "MIXED"]
  4523. In other words, a list of N + 1 elements, where N is the number of parts in
  4524. the message. The first N elements are structures as defined by the previous
  4525. section. The last element is the minor MIME subtype of the multipart
  4526. message.
  4527. Additionally, the RFC describes extension data::
  4528. Extension data follows the multipart subtype. Extension data is never
  4529. returned with the BODY fetch, but can be returned with a BODYSTRUCTURE
  4530. fetch. Extension data, if present, MUST be in the defined order.
  4531. The C{extended} flag controls whether extension data might be returned with
  4532. the normal data.
  4533. """
  4534. return _getMessageStructure(msg).encode(extended)
  4535. def _formatHeaders(headers):
  4536. # TODO: This should use email.header.Header, which handles encoding
  4537. hdrs = [
  4538. ": ".join((k.title(), "\r\n".join(v.splitlines())))
  4539. for (k, v) in headers.items()
  4540. ]
  4541. hdrs = "\r\n".join(hdrs) + "\r\n"
  4542. return networkString(hdrs)
  4543. def subparts(m):
  4544. i = 0
  4545. try:
  4546. while True:
  4547. yield m.getSubPart(i)
  4548. i += 1
  4549. except IndexError:
  4550. pass
  4551. def iterateInReactor(i):
  4552. """
  4553. Consume an interator at most a single iteration per reactor iteration.
  4554. If the iterator produces a Deferred, the next iteration will not occur
  4555. until the Deferred fires, otherwise the next iteration will be taken
  4556. in the next reactor iteration.
  4557. @rtype: C{Deferred}
  4558. @return: A deferred which fires (with None) when the iterator is
  4559. exhausted or whose errback is called if there is an exception.
  4560. """
  4561. from twisted.internet import reactor
  4562. d = defer.Deferred()
  4563. def go(last):
  4564. try:
  4565. r = next(i)
  4566. except StopIteration:
  4567. d.callback(last)
  4568. except BaseException:
  4569. d.errback()
  4570. else:
  4571. if isinstance(r, defer.Deferred):
  4572. r.addCallback(go)
  4573. else:
  4574. reactor.callLater(0, go, r)
  4575. go(None)
  4576. return d
  4577. class MessageProducer:
  4578. CHUNK_SIZE = 2**2**2**2
  4579. _uuid4 = staticmethod(uuid.uuid4)
  4580. def __init__(self, msg, buffer=None, scheduler=None):
  4581. """
  4582. Produce this message.
  4583. @param msg: The message I am to produce.
  4584. @type msg: L{IMessage}
  4585. @param buffer: A buffer to hold the message in. If None, I will
  4586. use a L{tempfile.TemporaryFile}.
  4587. @type buffer: file-like
  4588. """
  4589. self.msg = msg
  4590. if buffer is None:
  4591. buffer = tempfile.TemporaryFile()
  4592. self.buffer = buffer
  4593. if scheduler is None:
  4594. scheduler = iterateInReactor
  4595. self.scheduler = scheduler
  4596. self.write = self.buffer.write
  4597. def beginProducing(self, consumer):
  4598. self.consumer = consumer
  4599. return self.scheduler(self._produce())
  4600. def _produce(self):
  4601. headers = self.msg.getHeaders(True)
  4602. boundary = None
  4603. if self.msg.isMultipart():
  4604. content = headers.get("content-type")
  4605. parts = [x.split("=", 1) for x in content.split(";")[1:]]
  4606. parts = {k.lower().strip(): v for (k, v) in parts}
  4607. boundary = parts.get("boundary")
  4608. if boundary is None:
  4609. # Bastards
  4610. boundary = f"----={self._uuid4().hex}"
  4611. headers["content-type"] += f'; boundary="{boundary}"'
  4612. else:
  4613. if boundary.startswith('"') and boundary.endswith('"'):
  4614. boundary = boundary[1:-1]
  4615. boundary = networkString(boundary)
  4616. self.write(_formatHeaders(headers))
  4617. self.write(b"\r\n")
  4618. if self.msg.isMultipart():
  4619. for p in subparts(self.msg):
  4620. self.write(b"\r\n--" + boundary + b"\r\n")
  4621. yield MessageProducer(p, self.buffer, self.scheduler).beginProducing(
  4622. None
  4623. )
  4624. self.write(b"\r\n--" + boundary + b"--\r\n")
  4625. else:
  4626. f = self.msg.getBodyFile()
  4627. while True:
  4628. b = f.read(self.CHUNK_SIZE)
  4629. if b:
  4630. self.buffer.write(b)
  4631. yield None
  4632. else:
  4633. break
  4634. if self.consumer:
  4635. self.buffer.seek(0, 0)
  4636. yield FileProducer(self.buffer).beginProducing(self.consumer).addCallback(
  4637. lambda _: self
  4638. )
  4639. class _FetchParser:
  4640. class Envelope:
  4641. # Response should be a list of fields from the message:
  4642. # date, subject, from, sender, reply-to, to, cc, bcc, in-reply-to,
  4643. # and message-id.
  4644. #
  4645. # from, sender, reply-to, to, cc, and bcc are themselves lists of
  4646. # address information:
  4647. # personal name, source route, mailbox name, host name
  4648. #
  4649. # reply-to and sender must not be None. If not present in a message
  4650. # they should be defaulted to the value of the from field.
  4651. type = "envelope"
  4652. __str__ = lambda self: "envelope"
  4653. class Flags:
  4654. type = "flags"
  4655. __str__ = lambda self: "flags"
  4656. class InternalDate:
  4657. type = "internaldate"
  4658. __str__ = lambda self: "internaldate"
  4659. class RFC822Header:
  4660. type = "rfc822header"
  4661. __str__ = lambda self: "rfc822.header"
  4662. class RFC822Text:
  4663. type = "rfc822text"
  4664. __str__ = lambda self: "rfc822.text"
  4665. class RFC822Size:
  4666. type = "rfc822size"
  4667. __str__ = lambda self: "rfc822.size"
  4668. class RFC822:
  4669. type = "rfc822"
  4670. __str__ = lambda self: "rfc822"
  4671. class UID:
  4672. type = "uid"
  4673. __str__ = lambda self: "uid"
  4674. class Body:
  4675. type = "body"
  4676. peek = False
  4677. header = None
  4678. mime = None
  4679. text = None
  4680. part = ()
  4681. empty = False
  4682. partialBegin = None
  4683. partialLength = None
  4684. def __str__(self) -> str:
  4685. return self.__bytes__().decode("ascii")
  4686. def getBytes(self, length: Optional[int] = None) -> bytes:
  4687. """
  4688. Prepare the initial command response for a Fetch BODY request.
  4689. Interpret the Fetch request from the client and return the
  4690. appropriate response based on RFC 3501.
  4691. This is not the body itself of the response, merely the section
  4692. of the first response line that describes the body part.
  4693. """
  4694. base = b"BODY"
  4695. part = b""
  4696. separator = b""
  4697. if self.part:
  4698. part = b".".join([str(x + 1).encode("ascii") for x in self.part]) # type: ignore[unreachable]
  4699. separator = b"."
  4700. # if self.peek:
  4701. # base += '.PEEK'
  4702. if self.header:
  4703. base += ( # type: ignore[unreachable]
  4704. b"[" + part + separator + str(self.header).encode("ascii") + b"]"
  4705. )
  4706. elif self.text:
  4707. base += b"[" + part + separator + b"TEXT]" # type: ignore[unreachable]
  4708. elif self.mime:
  4709. base += b"[" + part + separator + b"MIME]" # type: ignore[unreachable]
  4710. elif self.empty:
  4711. base += b"[" + part + b"]"
  4712. if self.partialBegin is not None:
  4713. if length is None or length > self.partialLength: # type: ignore[unreachable]
  4714. base += b"<%d.%d>" % (self.partialBegin, self.partialLength)
  4715. else:
  4716. # IMAP4rev1 says that if the partial length is greater than
  4717. # the length of the data, the server should send the entire
  4718. # data., with a "0" as the partial length
  4719. # https://datatracker.ietf.org/doc/html/rfc3501#section-6.4.5
  4720. base += b"<0>"
  4721. return base
  4722. def __bytes__(self) -> bytes:
  4723. return self.getBytes()
  4724. class BodyStructure:
  4725. type = "bodystructure"
  4726. __str__ = lambda self: "bodystructure"
  4727. # These three aren't top-level, they don't need type indicators
  4728. class Header:
  4729. negate = False
  4730. fields = None
  4731. part = None
  4732. def __str__(self) -> str:
  4733. return self.__bytes__().decode("ascii")
  4734. def __bytes__(self) -> bytes:
  4735. base = b"HEADER"
  4736. if self.fields:
  4737. base += b".FIELDS" # type: ignore[unreachable]
  4738. if self.negate:
  4739. base += b".NOT"
  4740. fields = []
  4741. for f in self.fields:
  4742. f = f.title()
  4743. if _needsQuote(f):
  4744. f = _quote(f)
  4745. fields.append(f)
  4746. base += b" (" + b" ".join(fields) + b")"
  4747. if self.part:
  4748. # TODO: _FetchParser never assigns Header.part - dead
  4749. # code?
  4750. base = b".".join([(x + 1).__bytes__() for x in self.part]) + b"." + base # type: ignore[unreachable]
  4751. return base
  4752. class Text:
  4753. pass
  4754. class MIME:
  4755. pass
  4756. parts = None
  4757. _simple_fetch_att = [
  4758. (b"envelope", Envelope),
  4759. (b"flags", Flags),
  4760. (b"internaldate", InternalDate),
  4761. (b"rfc822.header", RFC822Header),
  4762. (b"rfc822.text", RFC822Text),
  4763. (b"rfc822.size", RFC822Size),
  4764. (b"rfc822", RFC822),
  4765. (b"uid", UID),
  4766. (b"bodystructure", BodyStructure),
  4767. ]
  4768. def __init__(self):
  4769. self.state = ["initial"]
  4770. self.result = []
  4771. self.remaining = b""
  4772. def parseString(self, s):
  4773. s = self.remaining + s
  4774. try:
  4775. while s or self.state:
  4776. if not self.state:
  4777. raise IllegalClientResponse("Invalid Argument")
  4778. # print 'Entering state_' + self.state[-1] + ' with', repr(s)
  4779. state = self.state.pop()
  4780. try:
  4781. used = getattr(self, "state_" + state)(s)
  4782. except BaseException:
  4783. self.state.append(state)
  4784. raise
  4785. else:
  4786. # print state, 'consumed', repr(s[:used])
  4787. s = s[used:]
  4788. finally:
  4789. self.remaining = s
  4790. def state_initial(self, s):
  4791. # In the initial state, the literals "ALL", "FULL", and "FAST"
  4792. # are accepted, as is a ( indicating the beginning of a fetch_att
  4793. # token, as is the beginning of a fetch_att token.
  4794. if s == b"":
  4795. return 0
  4796. l = s.lower()
  4797. if l.startswith(b"all"):
  4798. self.result.extend(
  4799. (self.Flags(), self.InternalDate(), self.RFC822Size(), self.Envelope())
  4800. )
  4801. return 3
  4802. if l.startswith(b"full"):
  4803. self.result.extend(
  4804. (
  4805. self.Flags(),
  4806. self.InternalDate(),
  4807. self.RFC822Size(),
  4808. self.Envelope(),
  4809. self.Body(),
  4810. )
  4811. )
  4812. return 4
  4813. if l.startswith(b"fast"):
  4814. self.result.extend(
  4815. (
  4816. self.Flags(),
  4817. self.InternalDate(),
  4818. self.RFC822Size(),
  4819. )
  4820. )
  4821. return 4
  4822. if l.startswith(b"("):
  4823. self.state.extend(("close_paren", "maybe_fetch_att", "fetch_att"))
  4824. return 1
  4825. self.state.append("fetch_att")
  4826. return 0
  4827. def state_close_paren(self, s):
  4828. if s.startswith(b")"):
  4829. return 1
  4830. # TODO: does maybe_fetch_att's startswith(b')') make this dead
  4831. # code?
  4832. raise Exception("Missing )")
  4833. def state_whitespace(self, s):
  4834. # Eat up all the leading whitespace
  4835. if not s or not s[0:1].isspace():
  4836. raise Exception("Whitespace expected, none found")
  4837. i = 0
  4838. for i in range(len(s)):
  4839. if not s[i : i + 1].isspace():
  4840. break
  4841. return i
  4842. def state_maybe_fetch_att(self, s):
  4843. if not s.startswith(b")"):
  4844. self.state.extend(("maybe_fetch_att", "fetch_att", "whitespace"))
  4845. return 0
  4846. def state_fetch_att(self, s):
  4847. # Allowed fetch_att tokens are "ENVELOPE", "FLAGS", "INTERNALDATE",
  4848. # "RFC822", "RFC822.HEADER", "RFC822.SIZE", "RFC822.TEXT", "BODY",
  4849. # "BODYSTRUCTURE", "UID",
  4850. # "BODY [".PEEK"] [<section>] ["<" <number> "." <nz_number> ">"]
  4851. l = s.lower()
  4852. for name, cls in self._simple_fetch_att:
  4853. if l.startswith(name):
  4854. self.result.append(cls())
  4855. return len(name)
  4856. b = self.Body()
  4857. if l.startswith(b"body.peek"):
  4858. b.peek = True
  4859. used = 9
  4860. elif l.startswith(b"body"):
  4861. used = 4
  4862. else:
  4863. raise Exception(f"Nothing recognized in fetch_att: {l}")
  4864. self.pending_body = b
  4865. self.state.extend(("got_body", "maybe_partial", "maybe_section"))
  4866. return used
  4867. def state_got_body(self, s):
  4868. self.result.append(self.pending_body)
  4869. del self.pending_body
  4870. return 0
  4871. def state_maybe_section(self, s):
  4872. if not s.startswith(b"["):
  4873. return 0
  4874. self.state.extend(("section", "part_number"))
  4875. return 1
  4876. _partExpr = re.compile(rb"(\d+(?:\.\d+)*)\.?")
  4877. def state_part_number(self, s):
  4878. m = self._partExpr.match(s)
  4879. if m is not None:
  4880. self.parts = [int(p) - 1 for p in m.groups()[0].split(b".")]
  4881. return m.end()
  4882. else:
  4883. self.parts = []
  4884. return 0
  4885. def state_section(self, s):
  4886. # Grab "HEADER]" or "HEADER.FIELDS (Header list)]" or
  4887. # "HEADER.FIELDS.NOT (Header list)]" or "TEXT]" or "MIME]" or
  4888. # just "]".
  4889. l = s.lower()
  4890. used = 0
  4891. if l.startswith(b"]"):
  4892. self.pending_body.empty = True
  4893. used += 1
  4894. elif l.startswith(b"header]"):
  4895. h = self.pending_body.header = self.Header()
  4896. h.negate = True
  4897. h.fields = ()
  4898. used += 7
  4899. elif l.startswith(b"text]"):
  4900. self.pending_body.text = self.Text()
  4901. used += 5
  4902. elif l.startswith(b"mime]"):
  4903. self.pending_body.mime = self.MIME()
  4904. used += 5
  4905. else:
  4906. h = self.Header()
  4907. if l.startswith(b"header.fields.not"):
  4908. h.negate = True
  4909. used += 17
  4910. elif l.startswith(b"header.fields"):
  4911. used += 13
  4912. else:
  4913. raise Exception(f"Unhandled section contents: {l!r}")
  4914. self.pending_body.header = h
  4915. self.state.extend(("finish_section", "header_list", "whitespace"))
  4916. self.pending_body.part = tuple(self.parts)
  4917. self.parts = None
  4918. return used
  4919. def state_finish_section(self, s):
  4920. if not s.startswith(b"]"):
  4921. raise Exception("section must end with ]")
  4922. return 1
  4923. def state_header_list(self, s):
  4924. if not s.startswith(b"("):
  4925. raise Exception("Header list must begin with (")
  4926. end = s.find(b")")
  4927. if end == -1:
  4928. raise Exception("Header list must end with )")
  4929. headers = s[1:end].split()
  4930. self.pending_body.header.fields = [h.upper() for h in headers]
  4931. return end + 1
  4932. def state_maybe_partial(self, s):
  4933. # Grab <number.number> or nothing at all
  4934. if not s.startswith(b"<"):
  4935. return 0
  4936. end = s.find(b">")
  4937. if end == -1:
  4938. raise Exception("Found < but not >")
  4939. partial = s[1:end]
  4940. parts = partial.split(b".", 1)
  4941. if len(parts) != 2:
  4942. raise Exception(
  4943. "Partial specification did not include two .-delimited integers"
  4944. )
  4945. begin, length = map(int, parts)
  4946. self.pending_body.partialBegin = begin
  4947. self.pending_body.partialLength = length
  4948. return end + 1
  4949. class FileProducer:
  4950. CHUNK_SIZE = 2**2**2**2
  4951. firstWrite = True
  4952. def __init__(self, f):
  4953. self.f = f
  4954. def beginProducing(self, consumer):
  4955. self.consumer = consumer
  4956. self.produce = consumer.write
  4957. d = self._onDone = defer.Deferred()
  4958. self.consumer.registerProducer(self, False)
  4959. return d
  4960. def resumeProducing(self):
  4961. b = b""
  4962. if self.firstWrite:
  4963. b = b"{%d}\r\n" % (self._size(),)
  4964. self.firstWrite = False
  4965. if not self.f:
  4966. return
  4967. b = b + self.f.read(self.CHUNK_SIZE)
  4968. if not b:
  4969. self.consumer.unregisterProducer()
  4970. self._onDone.callback(self)
  4971. self._onDone = self.f = self.consumer = None
  4972. else:
  4973. self.produce(b)
  4974. def pauseProducing(self):
  4975. """
  4976. Pause the producer. This does nothing.
  4977. """
  4978. def stopProducing(self):
  4979. """
  4980. Stop the producer. This does nothing.
  4981. """
  4982. def _size(self):
  4983. b = self.f.tell()
  4984. self.f.seek(0, 2)
  4985. e = self.f.tell()
  4986. self.f.seek(b, 0)
  4987. return e - b
  4988. def parseTime(s):
  4989. # XXX - This may require localization :(
  4990. months = [
  4991. "jan",
  4992. "feb",
  4993. "mar",
  4994. "apr",
  4995. "may",
  4996. "jun",
  4997. "jul",
  4998. "aug",
  4999. "sep",
  5000. "oct",
  5001. "nov",
  5002. "dec",
  5003. "january",
  5004. "february",
  5005. "march",
  5006. "april",
  5007. "may",
  5008. "june",
  5009. "july",
  5010. "august",
  5011. "september",
  5012. "october",
  5013. "november",
  5014. "december",
  5015. ]
  5016. expr = {
  5017. "day": r"(?P<day>3[0-1]|[1-2]\d|0[1-9]|[1-9]| [1-9])",
  5018. "mon": r"(?P<mon>\w+)",
  5019. "year": r"(?P<year>\d\d\d\d)",
  5020. }
  5021. m = re.match("%(day)s-%(mon)s-%(year)s" % expr, s)
  5022. if not m:
  5023. raise ValueError(f"Cannot parse time string {s!r}")
  5024. d = m.groupdict()
  5025. try:
  5026. d["mon"] = 1 + (months.index(d["mon"].lower()) % 12)
  5027. d["year"] = int(d["year"])
  5028. d["day"] = int(d["day"])
  5029. except ValueError:
  5030. raise ValueError(f"Cannot parse time string {s!r}")
  5031. else:
  5032. return time.struct_time((d["year"], d["mon"], d["day"], 0, 0, 0, -1, -1, -1))
  5033. # we need to cast Python >=3.3 memoryview to chars (from unsigned bytes), but
  5034. # cast is absent in previous versions: thus, the lambda returns the
  5035. # memoryview instance while ignoring the format
  5036. memory_cast = getattr(memoryview, "cast", lambda *x: x[0])
  5037. def modified_base64(s):
  5038. s_utf7 = s.encode("utf-7")
  5039. return s_utf7[1:-1].replace(b"/", b",")
  5040. def modified_unbase64(s):
  5041. s_utf7 = b"+" + s.replace(b",", b"/") + b"-"
  5042. return s_utf7.decode("utf-7")
  5043. def encoder(s, errors=None):
  5044. """
  5045. Encode the given C{unicode} string using the IMAP4 specific variation of
  5046. UTF-7.
  5047. @type s: C{unicode}
  5048. @param s: The text to encode.
  5049. @param errors: Policy for handling encoding errors. Currently ignored.
  5050. @return: L{tuple} of a L{str} giving the encoded bytes and an L{int}
  5051. giving the number of code units consumed from the input.
  5052. """
  5053. r = bytearray()
  5054. _in = []
  5055. valid_chars = set(map(chr, range(0x20, 0x7F))) - {"&"}
  5056. for c in s:
  5057. if c in valid_chars:
  5058. if _in:
  5059. r += b"&" + modified_base64("".join(_in)) + b"-"
  5060. del _in[:]
  5061. r.append(ord(c))
  5062. elif c == "&":
  5063. if _in:
  5064. r += b"&" + modified_base64("".join(_in)) + b"-"
  5065. del _in[:]
  5066. r += b"&-"
  5067. else:
  5068. _in.append(c)
  5069. if _in:
  5070. r.extend(b"&" + modified_base64("".join(_in)) + b"-")
  5071. return (bytes(r), len(s))
  5072. def decoder(s, errors=None):
  5073. """
  5074. Decode the given L{str} using the IMAP4 specific variation of UTF-7.
  5075. @type s: L{str}
  5076. @param s: The bytes to decode.
  5077. @param errors: Policy for handling decoding errors. Currently ignored.
  5078. @return: a L{tuple} of a C{unicode} string giving the text which was
  5079. decoded and an L{int} giving the number of bytes consumed from the
  5080. input.
  5081. """
  5082. r = []
  5083. decode = []
  5084. s = memory_cast(memoryview(s), "c")
  5085. for c in s:
  5086. if c == b"&" and not decode:
  5087. decode.append(b"&")
  5088. elif c == b"-" and decode:
  5089. if len(decode) == 1:
  5090. r.append("&")
  5091. else:
  5092. r.append(modified_unbase64(b"".join(decode[1:])))
  5093. decode = []
  5094. elif decode:
  5095. decode.append(c)
  5096. else:
  5097. r.append(c.decode())
  5098. if decode:
  5099. r.append(modified_unbase64(b"".join(decode[1:])))
  5100. return ("".join(r), len(s))
  5101. class StreamReader(codecs.StreamReader):
  5102. def decode(self, s, errors="strict"):
  5103. return decoder(s)
  5104. class StreamWriter(codecs.StreamWriter):
  5105. def encode(self, s, errors="strict"):
  5106. return encoder(s)
  5107. _codecInfo = codecs.CodecInfo(encoder, decoder, StreamReader, StreamWriter)
  5108. def imap4_utf_7(name):
  5109. # In Python 3.9, codecs.lookup() was changed to normalize the codec name
  5110. # in the same way as encodings.normalize_encoding(). The docstring
  5111. # for encodings.normalize_encoding() describes how the codec name is
  5112. # normalized. We need to replace '-' with '_' to be compatible with
  5113. # older Python versions.
  5114. # See: https://bugs.python.org/issue37751
  5115. # https://github.com/python/cpython/pull/17997
  5116. if name.replace("-", "_") == "imap4_utf_7":
  5117. return _codecInfo
  5118. codecs.register(imap4_utf_7)
  5119. __all__ = [
  5120. # Protocol classes
  5121. "IMAP4Server",
  5122. "IMAP4Client",
  5123. # Interfaces
  5124. "IMailboxListener",
  5125. "IClientAuthentication",
  5126. "IAccount",
  5127. "IMailbox",
  5128. "INamespacePresenter",
  5129. "ICloseableMailbox",
  5130. "IMailboxInfo",
  5131. "IMessage",
  5132. "IMessageCopier",
  5133. "IMessageFile",
  5134. "ISearchableMailbox",
  5135. "IMessagePart",
  5136. # Exceptions
  5137. "IMAP4Exception",
  5138. "IllegalClientResponse",
  5139. "IllegalOperation",
  5140. "IllegalMailboxEncoding",
  5141. "UnhandledResponse",
  5142. "NegativeResponse",
  5143. "NoSupportedAuthentication",
  5144. "IllegalServerResponse",
  5145. "IllegalIdentifierError",
  5146. "IllegalQueryError",
  5147. "MismatchedNesting",
  5148. "MismatchedQuoting",
  5149. "MailboxException",
  5150. "MailboxCollision",
  5151. "NoSuchMailbox",
  5152. "ReadOnlyMailbox",
  5153. # Auth objects
  5154. "CramMD5ClientAuthenticator",
  5155. "PLAINAuthenticator",
  5156. "LOGINAuthenticator",
  5157. "PLAINCredentials",
  5158. "LOGINCredentials",
  5159. # Simple query interface
  5160. "Query",
  5161. "Not",
  5162. "Or",
  5163. # Miscellaneous
  5164. "MemoryAccount",
  5165. "statusRequestHelper",
  5166. ]