123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242(*{{{ Copyright (c) 2012 Anil Madhavapeddy <anil@recoil.org>
*
* 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.
*
}}}*)openSexplib0.Sexp_convtypet={headers:Header.t;meth:Code.meth;scheme:stringoption;resource:string;version:Code.version;encoding:Transfer.encoding;}[@@derivingsexp]letcomparexy=matchHeader.comparex.headersy.headerswith|0->letheaders=Header.init()inStdlib.compare{xwithheaders}{ywithheaders}|i->iletheaderst=t.headersletmetht=t.methletschemet=t.schemeletresourcet=t.resourceletversiont=t.versionletencodingt=t.encodingletfixed_zero=Transfer.FixedInt64.zeroletguess_encoding?(encoding=fixed_zero)headers=matchHeader.get_transfer_encodingheaderswith|Transfer.(Chunked|Fixed_)asenc->enc|Unknown->encodingletmake?(meth=`GET)?(version=`HTTP_1_1)?encoding?headersuri=letheaders=matchheaderswithNone->Header.init()|Someh->hinletheaders=Header.add_unless_existsheaders"host"(matchUri.schemeuriwith|Some"httpunix"->""|_->(Uri.host_with_default~default:"localhost"uri^matchUri.porturiwithSomep->":"^string_of_intp|None->""))inletheaders=Header.add_unless_existsheaders"user-agent"Header.user_agentinletheaders=(* Add user:password auth to headers from uri
* if headers don't already have auth *)match(Header.get_authorizationheaders,Uri.useruri,Uri.passworduri)with|None,Someuser,Somepass->letauth=`Basic(user,pass)inHeader.add_authorizationheadersauth|_,_,_->headersinletencoding=guess_encoding?encodingheadersin{meth;version;headers;scheme=Uri.schemeuri;resource=Uri.path_and_queryuri;encoding;}letis_keep_alive{version;headers;_}=not(version=`HTTP_1_0||matchHeader.connectionheaderswithSome`Close->true|_->false)(* Make a client request, which involves guessing encoding and
adding content headers if appropriate.
@param chunked Forces chunked encoding
*)letmake_for_client?headers?(chunked=true)?(body_length=Int64.zero)methuri=letencoding=matchchunkedwith|true->Transfer.Chunked|false->Transfer.Fixedbody_lengthinmake~meth~encoding?headersuriletpp_humppfr=Format.fprintfppf"%s"(r|>sexp_of_t|>Sexplib0.Sexp.to_string_hum)(* Validate path when reading URI. Implemented for compatibility with old
implementation rather than efficiency *)letis_valid_uripathmeth=path="*"||meth=`CONNECT||matchUri.scheme(Uri.of_stringpath)with|Some_->true|None->not(String.lengthpath>0&&path.[0]<>'/')leturi{scheme;resource;headers;meth;_}=leturi=matchresourcewith|"*"->(matchHeader.getheaders"host"with|None->Uri.of_string""|Somehost->lethost_uri=Uri.of_string("//"^host)inUri.(make?host:(hosthost_uri)?port:(porthost_uri)()))|authoritywhenmeth=`CONNECT->Uri.of_string("//"^authority)|path->(leturi=Uri.of_stringpathinmatchUri.schemeuriwith|Some_->((* we have an absoluteURI *)Uri.(matchpathuriwith""->with_pathuri"/"|_->uri))|None->letempty=Uri.of_string""inletempty_base=Uri.of_string"///"inletpqs=matchStringext.split~max:2path~on:'?'with|[]->empty_base|[path]->Uri.resolve"http"empty_base(Uri.with_pathemptypath)|path::qs::_->letpath_base=Uri.resolve"http"empty_base(Uri.with_pathemptypath)inUri.with_querypath_base(Uri.query_of_encodedqs)inleturi=matchHeader.getheaders"host"with|None->Uri.(with_scheme(with_hostpqsNone)None)|Somehost->lethost_uri=Uri.of_string("//"^host)inleturi=Uri.with_hostpqs(Uri.hosthost_uri)inUri.with_porturi(Uri.porthost_uri)inuri)in(* Only set the scheme if it's not already part of the URI *)matchUri.schemeuriwithSome_->uri|None->Uri.with_schemeurischemetypett=tmoduleMake(IO:S.IO)=structtypet=ttmoduleIO=IOmoduleHeader_IO=Header_io.Make(IO)moduleTransfer_IO=Transfer_io.Make(IO)typereader=Transfer_IO.readertypewriter=Transfer_IO.writeropenIOletparse_request_fst_lineic=letopenCodeinread_lineic>>=function|Somerequest_line->(matchStringext.splitrequest_line~on:' 'with|[meth_raw;path;http_ver_raw]->(letm=method_of_stringmeth_rawinmatchversion_of_stringhttp_ver_rawwith|(`HTTP_1_1|`HTTP_1_0)asv->return(`Ok(m,path,v))|`Other_->return(`Invalid("Malformed request HTTP version: "^http_ver_raw)))|_->return(`Invalid("Malformed request header: "^request_line)))|None->return`Eofletreadic=parse_request_fst_lineic>>=function|`Eof->return`Eof|`Invalid_reasonasr->returnr|`Ok(meth,resource,version)->ifis_valid_uriresourcemeththenHeader_IO.parseic>>=funheaders->letencoding=Header.get_transfer_encodingheadersinreturn(`Ok{headers;meth;scheme=None;resource;version;encoding})elsereturn(`Invalid"bad request URI")(* Defined for method types in RFC7231 *)lethas_bodyreq=matchreq.methwith|`GET|`HEAD|`CONNECT|`TRACE->`No|`DELETE|`POST|`PUT|`PATCH|`OPTIONS|`Other_->Transfer.has_bodyreq.encodingletmake_body_readerreqic=Transfer_IO.make_readerreq.encodingicletread_body_chunk=Transfer_IO.readletwrite_headerreqoc=letfst_line=Printf.sprintf"%s %s %s\r\n"(Code.string_of_methodreq.meth)(ifreq.resource=""then"/"elsereq.resource)(Code.string_of_versionreq.version)inletheaders=req.headersinletheaders=matchhas_bodyreqwith|`Yes|`Unknown->Header.add_transfer_encodingheadersreq.encoding|`No->headersinIO.writeocfst_line>>=fun_->Header_IO.writeheadersocletmake_body_writer?flushreqoc=Transfer_IO.make_writer?flushreq.encodingocletwrite_body=Transfer_IO.writeletwrite_footerreqoc=matchreq.encodingwith|Transfer.Chunked->(* TODO Trailer header support *)IO.writeoc"0\r\n\r\n"|Transfer.Fixed_|Transfer.Unknown->return()letwrite?flushwrite_bodyreqoc=write_headerreqoc>>=fun()->letwriter=make_body_writer?flushreqocinwrite_bodywriter>>=fun()->write_footerreqocend