123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208(*{{{ 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;resource:string;version:Code.version;encoding:Transfer.encoding;}[@@derivingfields,sexp]letfixed_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=matchheaderswith|None->Header.init()|Someh->hinletheaders=Header.add_unless_existsheaders"host"(matchUri.schemeuriwith|Some"httpunix"->""|_->Uri.host_with_default~default:"localhost"uri^matchUri.porturiwith|Somep->":"^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 *)matchHeader.get_authorizationheaders,Uri.useruri,Uri.passworduriwith|None,Someuser,Somepass->letauth=`Basic(user,pass)inHeader.add_authorizationheadersauth|_,_,_->headersinletencoding=guess_encoding?encodingheadersin{meth;version;headers;resource=(Uri.path_and_queryuri);encoding}letis_keep_alive{version;headers;_}=not(version=`HTTP_1_0||(matchHeader.connectionheaderswith|Some`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{resource;headers;meth;_}=matchresourcewith|"*"->beginmatchHeader.getheaders"host"with|None->Uri.of_string""|Somehost->lethost_uri=Uri.of_string("//"^host)inleturi=Uri.(with_host(of_string"")(hosthost_uri))inUri.(with_porturi(porthost_uri))end|authoritywhenmeth=`CONNECT->Uri.of_string("//"^authority)|path->leturi=Uri.of_stringpathinbeginmatchUri.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)inuriendtypett=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->beginmatchStringext.splitrequest_line~on:' 'with|[meth_raw;path;http_ver_raw]->beginletm=method_of_stringmeth_rawinmatchversion_of_stringhttp_ver_rawwith|`HTTP_1_1|`HTTP_1_0asv->return(`Ok(m,path,v))|`Other_->return(`Invalid("Malformed request HTTP version: "^http_ver_raw))end|_->return(`Invalid("Malformed request header: "^request_line))end|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;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