123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380(*
* Copyright (c) 2012-2018 Vincent Bernardoff <vb@luminar.eu.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.
*
*)openAstringletb64_encoded_sha1sums=Base64.encode_exn(Sha1.sha_1s)letwebsocket_uuid="258EAFA5-E914-47DA-95CA-C5AB0DC85B11"moduleRng=structletinit?(state=Random.get_state())()=funlen->String.v~len(fun_->Char.of_byte(Random.State.bitsstateland0xFF))endmoduleFrame=structmoduleOpcode=structtypet=|Continuation|Text|Binary|Close|Ping|Pong|Ctrlofint|Nonctrlofintletto_string=function|Continuation->"continuation"|Text->"text"|Binary->"binary"|Close->"close"|Ping->"ping"|Pong->"pong"|Ctrli->"ctrl "^string_of_inti|Nonctrli->"nonctrl "^string_of_intiletppppft=Format.fprintfppf"%s"(to_stringt)letof_enum=function|iwhen(i<0||i>0xf)->invalid_arg"Frame.Opcode.of_enum"|0->Continuation|1->Text|2->Binary|8->Close|9->Ping|10->Pong|iwheni<8->Nonctrli|i->Ctrliletto_enum=function|Continuation->0|Text->1|Binary->2|Close->8|Ping->9|Pong->10|Ctrli->i|Nonctrli->iletis_ctrlopcode=to_enumopcode>7endtypet={opcode:Opcode.t;extension:int;final:bool;content:string;}letppppf{opcode;extension;final;content}=Format.fprintfppf"[%a (0x%x) (final=%b) %s]"Opcode.ppopcodeextensionfinalcontentletshowt=Format.asprintf"%a"pptletcreate?(opcode=Opcode.Text)?(extension=0)?(final=true)?(content="")()={opcode;extension;final;content}letof_bytes?opcode?extension?finalcontent=letcontent=Bytes.unsafe_to_stringcontentincreate?opcode?extension?final~content()letclosecode=letcontent=Bytes.create2inEndianBytes.BigEndian.set_int16content0code;of_bytes~opcode:Opcode.Closecontentendletxormaskmsg=fori=0toBytes.lengthmsg-1do(* masking msg to send *)Bytes.setmsgiChar.(to_intmask.[imod4]lxorto_int(Bytes.getmsgi)|>of_byte)doneletis_bit_setidxv=(vlsridx)land1=1letset_bitvidxb=ifbthenvlor(1lslidx)elsevland(lnot(1lslidx))letint_valueshiftlenv=(vlsrshift)land((1lsllen)-1)letupgrade_presenths=Cohttp.Header.get_multihs"connection"|>funhs->List.map(String.cuts~sep:",")hs|>funhs->List.flattenhs|>funhs->List.mapString.(funh->h|>String.Ascii.lowercase|>trim)hs|>List.mem"upgrade"exceptionProtocol_errorofstringletcheck_origin?(origin_mandatory=false)~hosts=letpredorigin_host=List.exists(funh->String.Ascii.lowercaseh=origin_host)hostsinfunrequest->letheaders=request.Cohttp.Request.headersinmatchCohttp.Header.getheaders"origin"with|None->notorigin_mandatory|Someorigin->letorigin=Uri.of_stringorigininmatchUri.hostoriginwith|None->false|Somehost->(* host is already lowercased by Uri *)predhostletcheck_origin_with_hostrequest=letheaders=request.Cohttp.Request.headersinlethost=Cohttp.Header.getheaders"host"inmatchhostwith|None->failwith"Missing host header"(* mandatory in http/1.1 *)|Somehost->(* remove port *)lethostname=matchString.cut~sep:":"hostwith|None->host|Some(h,_)->hincheck_origin~hosts:[hostname]requestmoduletypeS=sigmoduleIO:Cohttp.S.IOtypemode=|Clientof(int->string)|Servervalmake_read_frame:?buf:Buffer.t->mode:mode->IO.ic->IO.oc->(unit->Frame.tIO.t)valwrite_frame_to_buf:mode:mode->Buffer.t->Frame.t->unitmoduleRequest:Cohttp.S.Http_iowithtypet=Cohttp.Request.tandtype'aIO.t='aIO.tandtypeIO.ic=IO.icandtypeIO.oc=IO.ocmoduleResponse:Cohttp.S.Http_iowithtypet=Cohttp.Response.tandtype'aIO.t='aIO.tandtypeIO.ic=IO.icandtypeIO.oc=IO.ocmoduleConnected_client:sigtypetvalcreate:?read_buf:Buffer.t->?write_buf:Buffer.t->Cohttp.Request.t->Conduit.endp->IO.ic->IO.oc->tvalmake_standard:t->tvalsend:t->Frame.t->unitIO.tvalsend_multiple:t->Frame.tlist->unitIO.tvalrecv:t->Frame.tIO.tvalhttp_request:t->Cohttp.Request.tvalsource:t->Conduit.endpendendmoduleMake(IO:Cohttp.S.IO)=structopenIOmoduleIO=IOtypemode=|Clientof(int->string)|Serverletis_clientmode=mode<>Serverletrecread_exactlyicremainingbuf=readicremaining>>=funs->ifs=""thenreturnNoneelseletrecv_len=String.lengthsinBuffer.add_stringbufs;ifremaining-recv_len<=0thenreturn@@Some(Buffer.contentsbuf)elseread_exactlyic(remaining-recv_len)bufletread_uint16icbuf=read_exactlyic2buf>>=funs->matchswith|None->returnNone|Somes->return@@Some(EndianString.BigEndian.get_uint16s0)letread_int64icbuf=read_exactlyic8buf>>=funs->matchswith|None->returnNone|Somes->return@@Some(Int64.to_int@@EndianString.BigEndian.get_int64s0)letwrite_frame_to_buf~modebuffr=letscratch=Bytes.create8inletopenFrameinletcontent=Bytes.unsafe_of_stringfr.contentinletlen=Bytes.lengthcontentinletopcode=Opcode.to_enumfr.opcodeinletpayload_len=matchlenwith|nwhenn<126->len|nwhenn<1lsl16->126|_->127inlethdr=set_bit015(fr.final)in(* We do not support extensions for now *)lethdr=hdrlor(opcodelsl8)inlethdr=set_bithdr7(is_clientmode)inlethdr=hdrlorpayload_lenin(* Payload len is guaranteed to fit in 7 bits *)EndianBytes.BigEndian.set_int16scratch0hdr;Buffer.add_subbytesbufscratch02;beginmatchlenwith|nwhenn<126->()|nwhenn<(1lsl16)->EndianBytes.BigEndian.set_int16scratch0n;Buffer.add_subbytesbufscratch02|n->EndianBytes.BigEndian.set_int64scratch0Int64.(of_intn);Buffer.add_subbytesbufscratch08;end;beginmatchmodewith|Server->()|Clientrandom_string->letmask=random_string4inBuffer.add_stringbufmask;iflen>0thenxormaskcontent;end;Buffer.add_bytesbufcontentletclose_with_codemodebufoccode=Buffer.clearbuf;write_frame_to_buf~modebuf@@Frame.closecode;writeoc@@Buffer.contentsbufletmake_read_frame?(buf=Buffer.create128)~modeicoc=fun()->Buffer.clearbuf;read_exactlyic2buf>>=funhdr->matchhdrwith|None->raiseEnd_of_file|Somehdr->lethdr_part1=EndianString.BigEndian.get_int8hdr0inlethdr_part2=EndianString.BigEndian.get_int8hdr1inletfinal=is_bit_set7hdr_part1inletextension=int_value43hdr_part1inletopcode=int_value04hdr_part1inletframe_masked=is_bit_set7hdr_part2inletlength=int_value07hdr_part2inletopcode=Frame.Opcode.of_enumopcodeinBuffer.clearbuf;(matchlengthwith|iwheni<126->returni|126->(read_uint16icbuf>>=functionSomei->returni|None->return@@-1)|127->(read_int64icbuf>>=functionSomei->returni|None->return@@-1)|_->return@@-1)>>=funpayload_len->ifpayload_len=-1thenraise(Protocol_error("payload len = "^string_of_intlength))elseifextension<>0thenclose_with_codemodebufoc1002>>=fun()->raise(Protocol_error"unsupported extension")elseifFrame.Opcode.is_ctrlopcode&&payload_len>125thenclose_with_codemodebufoc1002>>=fun()->raise(Protocol_error"control frame too big")else(ifframe_maskedthen(Buffer.clearbuf;read_exactlyic4buf>>=function|None->raise(Protocol_error"could not read mask");|Somemask->returnmask)elsereturnString.empty)>>=funmask->ifpayload_len=0thenreturn@@Frame.create~opcode~extension~final()else(Buffer.clearbuf;read_exactlyicpayload_lenbuf)>>=funpayload->matchpayloadwith|None->raise(Protocol_error"could not read payload")|Somepayload->letpayload=Bytes.unsafe_of_stringpayloadinifframe_maskedthenxormaskpayload;letframe=Frame.of_bytes~opcode~extension~finalpayloadinreturnframemoduleRequest=Cohttp.Request.Make(IO)moduleResponse=Cohttp.Response.Make(IO)moduleConnected_client=structtypet={buffer:Buffer.t;endp:Conduit.endp;ic:Request.IO.ic;oc:Request.IO.oc;http_request:Cohttp.Request.t;standard_frame_replies:bool;read_frame:unit->Frame.tIO.t;}letsource{endp;_}=endpletcreate?read_buf?(write_buf=Buffer.create128)http_requestendpicoc=letread_frame=make_read_frame?buf:read_buf~mode:Servericocin{buffer=write_buf;endp;ic;oc;http_request;standard_frame_replies=false;read_frame;}letsend{buffer;oc;_}frame=Buffer.clearbuffer;write_frame_to_buf~mode:Serverbufferframe;IO.writeoc@@Buffer.contentsbufferletsend_multiple{buffer;oc;_}frames=Buffer.clearbuffer;List.iter(write_frame_to_buf~mode:Serverbuffer)frames;IO.writeoc@@Buffer.contentsbufferletstandard_recvt=t.read_frame()>>=funfr->matchfr.Frame.opcodewith|Frame.Opcode.Ping->sendt@@Frame.create~opcode:Frame.Opcode.Pong()>>=fun()->returnfr|Frame.Opcode.Close->(* Immediately echo and pass this last message to the user *)(ifString.lengthfr.Frame.content>=2thensendt@@Frame.create~opcode:Frame.Opcode.Close~content:(String.(sub~start:0~stop:2fr.Frame.content|>Sub.to_string))()elsesendt@@Frame.close1000)>>=fun()->returnfr|_->returnfrletrecvt=ift.standard_frame_repliesthenstandard_recvtelset.read_frame()lethttp_request{http_request;_}=http_requestletmake_standardt={twithstandard_frame_replies=true}endend