123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211(*{{{ 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=Http.Request.t={headers:Header.t;meth:Code.meth;resource:string;version:Code.version;}[@@derivingsexp]letcompare{headers;meth;resource;version}y=matchHeader.compareheadersy.headerswith|0->(matchCode.compare_methodmethy.methwith|0->(matchString.compareresourcey.resourcewith|0->Code.compare_versionversiony.version|i->i)|i->i)|i->iletheaderst=t.headersletmetht=t.methletresourcet=t.resourceletversiont=t.versionletencodingt=Header.get_transfer_encodingt.headersletmake?(meth=`GET)?(version=`HTTP_1_1)?encoding?(headers=Header.init())uri=letheaders=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|_,_,_->headersinletresource=Uri.path_and_queryuriinletheaders=matchencodingwith|None->headers|Someencoding->Header.add_transfer_encodingheadersencodingin{headers;meth;resource;version}letis_keep_alivet=Http.Request.is_keep_alivet(* Make a client request, which involves guessing encoding and
adding content headers if appropriate.
@param chunked Forces chunked encoding
*)letmake_for_client?headers?chunked?body_lengthmethuri=letencoding=match(chunked,body_length)with|Sometrue,None->Transfer.Chunked|(None|Somefalse),Somefixed->Transfer.Fixedfixed|(Somefalse|None),None->Transfer.Unknown|Sometrue,Some_->invalid_arg"cannot set both ?chunked and ?body_length:"inmake~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|"*"->(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_->(Uri.((* we have an absoluteURI *)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)inmatchHeader.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)))typett=tmoduleMake(IO:S.IO)=structtypet=ttmoduleIO=IOmoduleHeader_IO=Header_io.Make(IO)moduleTransfer_IO=Transfer_io.Make(IO)typereader=Transfer_IO.readertypewriter=Transfer_IO.writeropenIOletrecreadic=letresult=IO.with_input_bufferic~f:(funbuf~pos~len->matchHttp.Private.Parser.parse_request~pos~lenbufwith|Ok(req,consumed)->(`Okreq,consumed)|ErrorPartial->(`Partial,0)|Error(Msgmsg)->(`Invalidmsg,0))inmatchresultwith|`Partial->(IO.refillic>>=function`Ok->readic|`Eof->return`Eof)|`Okreq->ifis_valid_uri(Http.Request.resourcereq)(Http.Request.methreq)thenreturn(`Okreq)elsereturn(`Invalid"bad request URI")|`Invalidmsg->return(`Invalidmsg)letmake_body_readerreqic=Transfer_IO.make_reader(Header.get_transfer_encodingreq.headers)icletread_body_chunk=Transfer_IO.readletwrite_headerreqoc=letfst_line=Printf.sprintf"%s %s %s\r\n"(Http.Method.to_stringreq.meth)(ifreq.resource=""then"/"elsereq.resource)(Http.Version.to_stringreq.version)inIO.writeocfst_line>>=fun_->Header_IO.writereq.headersocletmake_body_writer~flushreqoc=Transfer_IO.make_writer~flush(Header.get_transfer_encodingreq.headers)ocletwrite_body=Transfer_IO.writeletwrite_footerheadersoc=matchHeader.get_transfer_encodingheaderswith|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_footerreq.headersocendmodulePrivate=structmoduleMake=Makeendlethas_body=Http.Request.has_body