123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268(*{{{ Copyright (c) 2012 Anil Madhavapeddy <anil@recoil.org>
* Copyright (c) 2011-2012 Martin Jambon <martin@mjambon.com>
* Copyright (c) 2010 Mika Illouz
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
}}}*)moduleLString:sigtypetvalof_string:string->tvalto_string:t->stringvalcompare:t->t->intend=structtypet=stringletof_stringx=String.lowercase_asciixletto_stringx=xletcompareab=String.compareabendmoduleStringMap=Map.Make(LString)typet=stringlistStringMap.tletuser_agent=Printf.sprintf"ocaml-cohttp/%s"Conf.versionletcompare=StringMap.comparePervasives.compareletheaders_with_list_values=Array.mapLString.of_string[|"accept";"accept-charset";"accept-encoding";"accept-language";"accept-ranges";"allow";"cache-control";"connection";"content-encoding";"content-language";"expect";"if-match";"if-none-match";"link";"pragma";"proxy-authenticate";"te";"trailer";"transfer-encoding";"upgrade";"vary";"via";"warning";"www-authenticate";|]letis_header_with_list_value=lettbl=Hashtbl.create(Array.lengthheaders_with_list_values)inheaders_with_list_values|>Array.iter(funh->Hashtbl.addtblh());funh->Hashtbl.memtblhletinit()=StringMap.emptyletis_emptyx=StringMap.is_emptyxletinit_withkv=StringMap.singleton(LString.of_stringk)[v]letaddhkv=letk=LString.of_stringkintryStringMap.addk(v::(StringMap.findkh))hwithNot_found->StringMap.addk[v]hletadd_listhl=List.fold_left(funh(k,v)->addhkv)hlletadd_multihkl=List.fold_left(funhv->addhkv)hlletadd_opthkv=matchhwith|None->init_withkv|Someh->addhkvletremovehk=letk=LString.of_stringkinStringMap.removekhletreplacehkv=letk=LString.of_stringkinStringMap.addk[v]hletgethk=letk=LString.of_stringkintryletv=StringMap.findkhinifis_header_with_list_valuekthenSome(String.concat","v)elseSome(List.hdv)withNot_found|Failure_->Noneletmemhk=StringMap.mem(LString.of_stringk)hletadd_unless_existshkv=ifmemhkthenhelseaddhkvletadd_opt_unless_existshkv=matchhwith|None->init_withkv|Someh->add_unless_existshkvletget_multihk=letk=LString.of_stringkintryStringMap.findkhwithNot_found->[]letmapfnh=StringMap.mapi(funkv->fn(LString.to_stringk)v)hletiterfnh=ignore(mapfnh)letfoldfnhacc=StringMap.fold(funkvacc->List.fold_left(funaccv->fn(LString.to_stringk)vacc)accv)haccletof_listl=List.fold_left(funh(k,v)->addhkv)(init())lletto_listh=List.rev(fold(funkvacc->(k,v)::acc)h[])letheader_linekv=Printf.sprintf"%s: %s\r\n"kvletto_linesh=List.rev(fold(funkvacc->(header_linekv)::acc)h[])letto_frames=letto_framekvacc=(Printf.sprintf"%s: %s"kv)::accinfunh->List.rev(foldto_frameh[])letto_stringh=letb=Buffer.create128inh|>iter(funkv->v|>List.iter(funv->Buffer.add_stringbk;Buffer.add_stringb": ";Buffer.add_stringbv;Buffer.add_stringb"\r\n"););Buffer.add_stringb"\r\n";Buffer.contentsbletparse_content_ranges=tryletstart,fini,total=Scanf.sscanfs"bytes %Ld-%Ld/%Ld"(funstartfinitotal->start,fini,total)inSome(start,fini,total)withScanf.Scan_failure_->None(* If we see a "Content-Range" header, than we should limit the
number of bytes we attempt to read *)letget_content_rangeheaders=matchgetheaders"content-length"with|Someclen->(trySome(Int64.of_stringclen)with_->None)|None->beginmatchgetheaders"content-range"with|Somerange_s->beginmatchparse_content_rangerange_swith|Some(start,fini,total)->(* some sanity checking before we act on these values *)iffini<total&&start<=total&&0L<=start&&0L<=totalthen(letnum_bytes_to_read=Int64.add(Int64.subfinistart)1LinSomenum_bytes_to_read)elseNone|None->Noneend|None->Noneendletget_connection_closeheaders=matchgetheaders"connection"with|Some"close"->true|_->falseletmedia_type_re=letre=Re.Emacs.re~case:true"[ \t]*\\([^ \t;]+\\)"inRe.(compile(seq([start;re])))letget_first_match_res=tryletsubs=Re.exec~pos:0media_type_resinlet(start,stop)=Re.get_ofssubs1inSome(String.subsstart(stop-start))withNot_found->None(* Grab "foo/bar" from " foo/bar ; charset=UTF-8" *)letget_media_typeheaders=matchgetheaders"content-type"with|Somes->get_first_matchmedia_type_res|None->Noneletget_acceptable_media_rangesheaders=Accept.media_ranges(getheaders"accept")letget_acceptable_charsetsheaders=Accept.charsets(getheaders"accept-charset")letget_acceptable_encodingsheaders=Accept.encodings(getheaders"accept-encoding")letget_acceptable_languagesheaders=Accept.languages(getheaders"accept-language")(* Parse the transfer-encoding and content-length headers to
* determine how to decode a body *)letget_transfer_encodingheaders=matchgetheaders"transfer-encoding"with|Some"chunked"->Transfer.Chunked|Some_|None->beginmatchget_content_rangeheaderswith|Somelen->Transfer.Fixedlen|None->Transfer.Unknownendletadd_transfer_encodingheadersenc=letopenTransferin(* Only add a header if one doesnt already exist, e.g. from the app *)matchget_transfer_encodingheaders,encwith|Fixed_,_(* App has supplied a content length, so use that *)|Chunked,_->headers(* TODO: this is a protocol violation *)|Unknown,Chunked->addheaders"transfer-encoding""chunked"|Unknown,Fixedlen->addheaders"content-length"(Int64.to_stringlen)|Unknown,Unknown->headersletadd_authorization_reqheaderschallenge=addheaders"www-authenticate"(Auth.string_of_challengechallenge)letadd_authorizationheaderscred=addheaders"authorization"(Auth.string_of_credentialcred)letget_authorizationheaders=matchgetheaders"authorization"with|None->None|Somev->Some(Auth.credential_of_stringv)letis_formheaders=get_media_typeheaders=(Some"application/x-www-form-urlencoded")letget_locationheaders=matchgetheaders"location"with|None->None|Someu->Some(Uri.of_stringu)letget_linksheaders=List.rev(List.fold_left(funlistlink_s->List.rev_append(Link.of_stringlink_s)list)[](get_multiheaders"link"))letadd_linksheaderslinks=add_multiheaders"link"(List.mapLink.to_stringlinks)letprepend_user_agentheadersuser_agent=letk="user-agent"inmatchgetheaderskwith|Someua->replaceheadersk(user_agent^" "^ua)|None->addheaderskuser_agentletconnectionh=matchgeth"connection"with|Somevwhenv="keep-alive"->Some`Keep_alive|Somevwhenv="close"->Some`Close|Somex->Some(`Unknownx)|_->NoneopenSexplib0.Sexp_convletsexp_of_tt=sexp_of_list(sexp_of_pairsexp_of_stringsexp_of_string)(to_listt)lett_of_sexps=of_list(list_of_sexp(pair_of_sexpstring_of_sexpstring_of_sexp)s)letpp_humppfh=Format.fprintfppf"%s"(h|>sexp_of_t|>Sexplib0.Sexp.to_string_hum)