123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267(*
* Copyright (c) 2012-2016 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=Sha1.sha_1s|>B64.encode~pad:trueletwebsocket_uuid="258EAFA5-E914-47DA-95CA-C5AB0DC85B11"moduleOption=structletvalue~default=function|None->default|Somev->vletvalue_map~default~f=function|None->default|Somev->fvletvalue_exn=function|None->invalid_arg"Option.value_exn"|Somev->vletmap~f=function|None->None|Somev->Some(fv)endmoduleRng=structletinit?state()=letstate=Option.valuestate~default:(Random.self_init();Random.get_state())infunlen->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_errorofstringmoduleIO(IO:Cohttp.S.IO)=structopenIOtypemode=|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~finalpayloadinreturnframeend