123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532(* $Id$
* ----------------------------------------------------------------------
*
*)openNetchannelstypestore=[`Memory|`Fileofstring]exceptionImmutableofstringclasstypemime_header_ro=objectmethodfields:(string*string)listmethodfield:string ->stringmethodmultiple_field:string->stringlistendclasstypemime_header=objectinheritmime_header_romethodro:boolmethodset_fields:(string*string)list->unitmethod update_field :string->string->unitmethodupdate_multiple_field:string->stringlist->unitmethod delete_field:string->unitendclasstypemime_body_ro=objectmethodvalue:stringmethodstore:storemethod open_value_rd:unit ->in_obj_channelmethodfinalize:unit->unitendclasstypemime_body=objectinheritmime_body_romethodro:boolmethodset_value:string->unitmethodopen_value_wr:unit ->out_obj_channelendtypecomplex_mime_message=mime_header*complex_mime_bodyandcomplex_mime_body =[`Body ofmime_body|`Partsofcomplex_mime_messagelist]typecomplex_mime_message_ro =mime_header_ro*complex_mime_body_roandcomplex_mime_body_ro=[`Bodyofmime_body_ro|`Partsofcomplex_mime_message_rolist](* Check that coercion is possible: *)let_=funx->(x:complex_mime_message:>complex_mime_message_ro)typemime_message=mime_header*[`Bodyofmime_body]typemime_message_ro=mime_header_ro *[`Bodyofmime_body_ro]moduleCI:sig(* case-insensitive strings *)typetvalcompare:t->t->intvalmake:string->tend=structtypet=stringletcompare(a_ci:t)(b_ci:t)=compare a_cib_cilet makes=STRING_LOWERCASE sendmodule CIMap=Map.Make(CI)(* Maps from case-insensitive strings to any type *)moduleDL:sig(* doubly-linked lists *)type'attype'acellvalcreate:unit->'atvalis_empty:'at->boolvalcell:'a->'acellvalcontents:'acell->'avalfirst:'at-> 'acell(* or Not_found *)vallast:'at->'acell(* or Not_found *)valprev:'acell->'acell(* or Not_found *)val next:'acell->'acell(* or Not_found *)valiter:('acell ->unit)->'at->unitvaldelete:'acell->unitval insert_after:neo:'acell->'acell->unitvaladd_at_end:neo:'acell->'at->unitval replace:neo:'acell->'acell->unitvalof_list:'alist->'atvalto_list:'at->'alistend=structtype'at={mutablefirst:'acelloption;mutablelast:'acelloption;}and 'acell ={mutableprev:'acell option;mutablenext:'acelloption;mutable list:'atoption;contents:'a;}letcreate()={first=None;last=None}letis_emptyl=l.first=Noneletcellx={prev=None;next=None;list=None;contents=x}letcontentsc=c.contentsletfirstl=matchl.firstwithSomec->c|None->raiseNot_foundletlastl=matchl.lastwithSomec->c|None->raiseNot_foundletprevc=matchc.prevwithSomec'->c'|None->raiseNot_foundletnextc=matchc.nextwithSomec'->c'|None->raiseNot_foundletiterfl=matchl.firstwithSomec->fc;letcurrent=refcinwhile(letc0=!currentinc0.next)<>Nonedo(* Error incamlp4 *)current:=next!current;f!currentdone;()|None->()letdeletec=matchc.listwithSomel->(matchc.prevwithSome p->p.next<-c.next|None->l.first<-c.next);(matchc.nextwithSomen->n.prev<-c.prev|None->l.last<-c.prev);c.prev<-None;c.next<-None;c.list<-None|None->failwith"DL.delete: cannot delete free cell"letinsert_after~neoc=ifneo.list<>Nonethenfailwith"DL.insert_after: new cell must be free";matchc.listwithSomel->letnx=c.next inc.next<-Someneo;neo.prev<-Somec;(matchnxwithSomen->n.prev<-Someneo;neo.next<-Somen;|None->l.last<-Someneo;neo.next<-None);neo.list<-Somel|None->failwith"DL.insert_after: cannot insert after free cell"letadd_at_end~neol=ifneo.list<>Nonethenfailwith"DL.insert_after: new cell must be free";matchl.lastwithSomen->n.next<-Someneo;neo.prev <-Somen;neo.next<-None;neo.list<-Somel;l.last<-Someneo|None->l.last<-Someneo;l.first <-Someneo;neo.prev<-None;neo.next<-None;neo.list<-Somelletreplace~neoc=ifneo.list<>Nonethenfailwith"DL.replace: new cell must be free";matchc.listwithSomel->(matchc.prevwithSomep->p.next<-Someneo|None->l.first<-Someneo);neo.prev<-c.prev;(matchc.nextwithSomen->n.prev<-Some neo|None->l.last<-Someneo);neo.next<-c.next;neo.list<-Somel;c.prev<-None;c.next<-None;c.list<-None|None->failwith"DL.replace: cannot replace free cell"letof_listl=letdl=create()inList.iter(funx->add_at_end~neo:(cellx)dl)l;dlletrecto_listdl=chain_to_list dl.firstandchain_to_listchain=matchchainwithNone->[]|Somec->c.contents::chain_to_listc.nextendclassbasic_mime_headerh:mime_header=object(self)valmutablehdr_map=lazy(assertfalse)valmutablehdr_dl=lazy(assertfalse)initializerself#do_set_fieldshmethodro=false(* Heirs can redefine [ro] to make this object immutable *)methodfields=DL.to_list(Lazy.forcehdr_dl)methodfieldn=letm=Lazy.forcehdr_mapinmatchCIMap.find(CI.maken)mwith[]->raiseNot_found|cell::_->snd(DL.contentscell)methodmultiple_fieldn=letm=Lazy.forcehdr_mapintryList.map(funcell->snd (DL.contentscell))(CIMap.find(CI.maken)m)withNot_found->[]methodset_fieldsh=ifself#rothenraise(Immutable"set_fields");self#do_set_fieldshmethodprivatedo_set_fieldsh=hdr_dl<-lazy(DL.of_listh);hdr_map<-lazybegin(* This seems to be expensive (O(n log n)). Because of this we do it only
* on demand; maybe nobody accesses the header at all
*)letm=refCIMap.emptyinDL.iter(funcell->let(n,v)=DL.contentscellinletn_ci=CI.makeninlet current=tryCIMap.findn_ci!mwithNot_found->[]inm:= CIMap.addn_ci(cell::current)!m;)(Lazy.forcehdr_dl);CIMap.mapList.rev!mendmethodupdate_fieldnv=ifself#rothenraise(Immutable"update_field");self#update_multiple_field n[v]methodupdate_multiple_fieldnvl=ifself#rothenraise(Immutable"update_multiple_field");letn_ci=CI.makeninletm=Lazy.forcehdr_mapinletdl=Lazy.forcehdr_dlin(* Algorithm: First try to replace existing values.
* If thereare more new values than old values,
* at the excess values after the last old value,
* or if not possible, at the end.
*)letinsert_point=refNoneinletold_cells=ref(tryCIMap.findn_cimwithNot_found->[])inletnew_vals=refvlinletnew_cells =ref[]inwhile!old_cells<>[]||!new_vals<>[]domatch !old_cells,!new_valswith(old_cell ::old_cells'),(new_val::new_vals')->(* Only update if the value has changed: *)let(old_n,old_val)=DL.contentsold_cellinifold_val=new_valthen(new_cells:=old_cell::!new_cells;insert_point:=Someold_cell;)else(letnew_cell=DL.cell(n,new_val)inDL.replace~neo:new_cellold_cell;insert_point:=Somenew_cell;new_cells:=new_cell::!new_cells);old_cells:=old_cells';new_vals :=new_vals';|[],(new_val ::new_vals')->letnew_cell =DL.cell (n,new_val)in(match!insert_point withSomep->DL.insert_after~neo:new_cellp;|None ->DL.add_at_end~neo:new_celldl);new_vals:=new_vals';insert_point:=Somenew_cell;new_cells:=new_cell::!new_cells|(old_cell ::old_cells'),[]->DL.deleteold_cell;old_cells :=old_cells'|[],[]->assertfalsedone;letm'=CIMap.addn_ci(List.rev!new_cells)minhdr_map<-lazym'methoddelete_field n=ifself#rothenraise(Immutable"delete_field");letn_ci=CI.makeninletm=Lazy.forcehdr_mapinletold_cells=tryCIMap.findn_cimwithNot_found->[]inList.iterDL.deleteold_cells;letm'=CIMap.removen_ciminhdr_map<-lazym';end;;letbasic_mime_header=newbasic_mime_headerclasswrap_mime_header hdr:mime_header=object(self)methodfields=hdr#fieldsmethodfield=hdr#fieldmethod multiple_field=hdr#multiple_fieldmethodro=hdr#ro(* Heirs can redefine [ro] to make this object immutable *)methodset_fieldsfields=ifself#rothenraise(Immutable"set_fields");hdr#set_fieldsfieldsmethodupdate_fieldnv=ifself#rothenraise(Immutable"update_field");hdr#update_fieldnvmethodupdate_multiple_fieldnv=ifself#rothenraise(Immutable"update_multiple_fields");hdr#update_multiple_fieldnvmethoddelete_fieldn=ifself#rothenraise(Immutable"delete_field");hdr#delete_fieldnendclasswrap_mime_header_rohdr:mime_header=object(self)methodfields=hdr#fieldsmethodfield=hdr#fieldmethodmultiple_field=hdr#multiple_fieldmethodro=truemethodset_fields_=raise(Immutable"set_fields")methodupdate_field__=raise(Immutable"update_field")methodupdate_multiple_field__=raise(Immutable"update_multiple_field")methoddelete_field_=raise (Immutable"delete_field")endletwrap_mime_header_ro=newwrap_mime_header_roclassmemory_mime_bodyv:mime_body=object(self)valmutable value=vvalmutablefinalized=falsemethodvalue=iffinalizedthenself#finalized();valuemethodstore=`Memorymethodopen_value_rd()=iffinalizedthenself#finalized();newinput_stringvaluemethodfinalize()=finalized<-truemethodro=(* Heirs can redefine [ro] to make this object immutable *)falsemethodset_values=ifself#rothenraise(Immutable"set_value");iffinalizedthenself#finalized();value<-s;methodopen_value_wr()=ifself#rothenraise(Immutable"open_value_wr");iffinalizedthenself#finalized();letb=Netbuffer.create60innewoutput_netbuffer~onclose:(fun()->value<-Netbuffer.contentsb)b;methodprivatefinalized()=failwith"Netmime.memory_mime_body: object is finalized";end;;letmemory_mime_body=newmemory_mime_bodyclassfile_mime_body?(fin=false)f:mime_body =object(self)valmutablefinalized=falsevalfin=finvalfilename=fvalcached_value=Weak.create1methodro=(* Heirs can redefine [ro] to make this object immutable *)falsemethodstore=`Filefilenamemethodvalue=iffinalizedthenself#finalized();matchWeak.getcached_value0withNone->with_in_obj_channel(newinput_channel(open_in_binfilename))(funobjch->letv=string_of_in_obj_channelobjchinWeak.set cached_value0(Somev);v)|Somev->vmethodopen_value_rd()=iffinalizedthenself#finalized();newinput_channel(open_in_binfilename)methodset_values=ifself#rothenraise(Immutable"set_value");iffinalizedthenself#finalized();with_out_obj_channel(newoutput_channel(open_out_binfilename))(funch->ch#output_strings);methodopen_value_wr()=ifself#rothenraise (Immutable"open_value_wr");iffinalized thenself#finalized();newoutput_channel(open_out_binfilename)methodfinalize()=iffin&¬finalizedthenbegintrySys.removefilenamewith_->()end;finalized<-truemethodprivatefinalized()=failwith"Netmime.file_mime_body: object is finalized";end;;letfile_mime_body=newfile_mime_bodyclasswrap_mime_bodybdy:mime_body=object(self)methodvalue=bdy#valuemethodstore=bdy#storemethodopen_value_rd =bdy#open_value_rdmethodfinalize =bdy#finalizemethodro=bdy#romethodset_value=bdy#set_valuemethodopen_value_wr=bdy#open_value_wrendclasswrap_mime_body_robdy:mime_body=object(self)methodvalue=bdy#valuemethodstore=bdy#storemethodopen_value_rd=bdy#open_value_rdmethodfinalize=bdy#finalizemethodro=truemethodset_value_=raise(Immutable"set_value")methodopen_value_wr_=raise(Immutable"open_value_wr")endletwrap_mime_body_ro=newwrap_mime_body_rolet recwrap_complex_mime_message_ro(h,cb)=(wrap_mime_header_roh,matchcbwith`Bodyb->`Body(wrap_mime_body_rob)|`Partsp->`Parts(List.mapwrap_complex_mime_message_rop));;