123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125moduleHeaders=Httpaf.Headersletcreate_request~nonce~headerstarget=letnonce=Base64.encode_exnnonceinletheaders=Headers.add_listheaders["upgrade","websocket";"connection","upgrade";"sec-websocket-version","13";"sec-websocket-key",nonce]inHttpaf.Request.create~headers`GETtargetletsec_websocket_key_proof~sha1sec_websocket_key=(* From RFC6455§1.3:
* For this header field, the server has to take the value (as present
* in the header field, e.g., the base64-encoded [RFC4648] version minus
* any leading and trailing whitespace) and concatenate this with the
* Globally Unique Identifier (GUID, [RFC4122]) "258EAFA5-E914-47DA-
* 95CA-C5AB0DC85B11" in string form, which is unlikely to be used by
* network endpoints that do not understand the WebSocket Protocol. A
* SHA-1 hash (160 bits) [FIPS.180-3], base64-encoded (see Section 4 of
* [RFC4648]), of this concatenation is then returned in the server's
* handshake. *)letconcatenation=sec_websocket_key^"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"inBase64.encode_exn~pad:true(sha1concatenation)(* Copied from headers.ml in http/af.
* Compares ASCII strings in a Case Insensitive manner. *)moduleCI=structlet[@inlinealways]lowerc=ifc>=0x41&&c<=0x5athenc+32elsecletequalxy=letlen=String.lengthxinlen=String.lengthy&&(letequal_so_far=reftrueinleti=ref0inwhile!equal_so_far&&!i<lendoletc1=Char.code(String.unsafe_getx!i)inletc2=Char.code(String.unsafe_gety!i)inequal_so_far:=lowerc1=lowerc2;incridone;!equal_so_far)end(* TODO: this function can just return the reason *)letpasses_scrutiny~request_methodheaders=(* From RFC6455§4.2.1:
* The client's opening handshake consists of the following parts. If the
* server, while reading the handshake, finds that the client did not send
* a handshake that matches the description below [...], the server MUST
* stop processing the client's handshake and return an HTTP response with
* an appropriate error code (such as 400 Bad Request).
*
* 1. An HTTP/1.1 or higher GET request, including a "Request-URI"
* [RFC2616] [...].
*
* 2. A |Host| header field containing the server's authority.
*
* 3. An |Upgrade| header field containing the value "websocket", treated
* as an ASCII case-insensitive value.
*
* 4. A |Connection| header field that includes the token "Upgrade",treated
* as an ASCII case-insensitive value.
*
* 5. A |Sec-WebSocket-Key| header field with a base64-encoded (see Section
* 4 of [RFC4648]) value that, when decoded, is 16 bytes in length.
*
* 6. A |Sec-WebSocket-Version| header field, with a value of 13.
*
* [...]
*
* Note: there are 9 points in the above section of the RFC, and the last
* 3 refer to optional fields.
*)matchrequest_method,Headers.get_exnheaders"host",Headers.get_exnheaders"upgrade",Headers.get_exnheaders"connection",Headers.get_exnheaders"sec-websocket-key",Headers.get_exnheaders"sec-websocket-version"with(* 1, 2 *)|`GET,_host,upgrade,connection,sec_websocket_key,"13"->(* 3 *)CI.equalupgrade"websocket"&&(* 4 *)(List.exists(funv->CI.equal(String.trimv)"upgrade")(String.split_on_char','connection))&&(* 5 *)(tryString.length(Base64.decode_exn~pad:truesec_websocket_key)=16with|_->false)|_->false|exception_->falseletupgrade_headers~sha1~request_methodheaders=ifpasses_scrutiny~request_methodheadersthenbeginletsec_websocket_key=Headers.get_exnheaders"sec-websocket-key"inletaccept=sec_websocket_key_proof~sha1sec_websocket_keyinletupgrade_headers=["Upgrade","websocket";"Connection","upgrade";"Sec-Websocket-Accept",accept]inOkupgrade_headersendelseError"Didn't pass scrutiny"letrespond_with_upgrade?(headers=Headers.empty)~sha1reqdupgrade_handler=letrequest=Httpaf.Reqd.requestreqdinmatchupgrade_headers~sha1~request_method:request.methrequest.headerswith|Okupgrade_headers->Httpaf.Reqd.respond_with_upgradereqd(Headers.add_listheadersupgrade_headers)upgrade_handler;Ok()|Errormsg->Errormsg