imap4.py 192 KB

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