Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Associative list implementation for headers #747

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,39 @@
- lwt_jsoo: Fix `Lwt.wakeup_exn` `Invalid_arg` exception when a js
stack overflow happens in the XHR completion handler (@mefyl #762).

- Revamped Header implementation (@lyrm #747)

+ New implementation of Header modules using an associative list instead of a map, with one major semantic change (function ```get```, see below), and some new functions (```clean_dup```, ```get_multi_concat```)
+ More Alcootest tests as well as fuzzing tests for this particular module.

### Purpose

The new header implementation uses an associative list instead of a map to represent headers and is focused on predictibility and intuitivity: except for some specific and documented functions, the headers are always kept in transmission order, which makes debugging easier and is also important for [RFC7230§3.2.2](https://tools.ietf.org/html/rfc7230#section-3.2.2) that states that multiple values of a header must be kept in order.

Also, to get an intuitive function behaviour, no extra work to enforce RFCs is done by the basic functions. For example, RFC7230§3.2.2 requires that a sender does not send multiple values for a non list-value header. This particular rule could require the ```Header.add``` function to remove previous values of non-list-value headers, which means some changes of the headers would be out of control of the user. With the current implementation, an user has to actively call dedicated functions to enforce such RFCs (here ```Header.clean_dup```).

### Semantic changes
Two functions have a semantic change : ```get``` and ```update```.

#### get
```get``` was previously doing more than just returns the value associated to a key; it was also checking if the searched header could have multiple values: if not, the last value associated to the header was returned; otherwise, all the associated values were concatenated and returned. This semantics does not match the global idea behind the new header implementation, and would also be very unefficient.

+ The new ```get``` function only returns the last value associated to the searched header.
+ ```get_multi_concat``` function has been added to get a result similar to the previous ```get``` function.

#### update
```update``` is a pretty new function (#703) and changes are minor and related to ```get``` semantic changes.

+ ```update h k f``` is now modifying only the last occurences of the header ```k``` instead of all its occurrences.
+ a new function ```update_all``` function has been added and work on all the occurrences of the updated header.

### New functions :

+ ```clean_dup``` enables the user to clean headers that follows the {{:https://tools.ietf.org/html/rfc7230#section-3.2.2} RFC7230§3.2.2} (no duplicate, except ```set-cookie```)
+ ```get_multi_concat``` has been added to get a result similar to the previous ```get``` function.



## v4.0.0 (2021-03-24)

- cohttp.response: fix malformed status header for custom status codes (@mseri @aalekseyev #752)
Expand Down
5 changes: 1 addition & 4 deletions cohttp-async/bin/cohttp_curl_async.ml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,7 @@ open Async_kernel
open Cohttp_async

let show_headers h =
Cohttp.Header.iter
(fun k v ->
List.iter v ~f:(fun v_i -> Logs.info (fun m -> m "%s: %s%!" k v_i)))
h
Cohttp.Header.iter (fun k v -> Logs.info (fun m -> m "%s: %s%!" k v)) h

let make_net_req uri meth' body () =
let meth = Cohttp.Code.method_of_string meth' in
Expand Down
13 changes: 4 additions & 9 deletions cohttp-lwt-jsoo/src/cohttp_lwt_jsoo.ml
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,7 @@ module Make_client_async (P : Params) = Make_api (struct
(fun k v ->
(* some headers lead to errors in the javascript console, should
we filter then out here? *)
List.iter
(fun v -> xml ## (setRequestHeader (Js.string k) (Js.string v)))
v)
xml ## (setRequestHeader (Js.string k) (Js.string v)))
headers
in

Expand Down Expand Up @@ -291,12 +289,9 @@ module Make_client_sync (P : Params) = Make_api (struct
| Some headers ->
C.Header.iter
(fun k v ->
List.iter
(* some headers lead to errors in the javascript console, should
we filter then out here? *)
(fun v ->
xml ## (setRequestHeader (Js.string k) (Js.string v)))
v)
(* some headers lead to errors in the javascript console, should
we filter then out here? *)
xml ## (setRequestHeader (Js.string k) (Js.string v)))
headers
in
(* perform call *)
Expand Down
18 changes: 6 additions & 12 deletions cohttp-lwt-unix/test/test_parser.ml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ let basic_res =
Server: Apache/1.3.3.7 (Unix) (Red-Hat/Linux)\r\n\
Last-Modified: Wed, 08 Jan 2003 23:11:55 GMT\r\n\
Etag: \"3f80f-1b6-3e1cb03b\"\r\n\
Accept: text/*\r\n\
Accept: application/xml\r\n\
Accept-Ranges: none\r\n\
Content-Length: 0\r\n\
Connection: close\r\n\
Expand Down Expand Up @@ -244,13 +246,9 @@ let make_simple_req () =
let open Cohttp in
let open Cohttp_lwt_unix in
let expected =
"POST /foo/bar HTTP/1.1\r\n\
foo: bar\r\n\
host: localhost\r\n\
transfer-encoding: chunked\r\n\
user-agent: "
"POST /foo/bar HTTP/1.1\r\nfoo: bar\r\nhost: localhost\r\nuser-agent: "
^ user_agent
^ "\r\n\r\n6\r\nfoobar\r\n0\r\n\r\n"
^ "\r\ntransfer-encoding: chunked\r\n\r\n6\r\nfoobar\r\n0\r\n\r\n"
in
let req =
Request.make ~encoding:Transfer.Chunked ~meth:`POST
Expand All @@ -263,13 +261,9 @@ let mutate_simple_req () =
let open Cohttp in
let open Cohttp_lwt_unix in
let expected =
"POST /foo/bar HTTP/1.1\r\n\
foo: bar\r\n\
host: localhost\r\n\
transfer-encoding: chunked\r\n\
user-agent: "
"POST /foo/bar HTTP/1.1\r\nfoo: bar\r\nhost: localhost\r\nuser-agent: "
^ user_agent
^ "\r\n\r\n6\r\nfoobar\r\n0\r\n\r\n"
^ "\r\ntransfer-encoding: chunked\r\n\r\n6\r\nfoobar\r\n0\r\n\r\n"
in
let req =
Request.make ~encoding:Transfer.Chunked
Expand Down
1 change: 1 addition & 0 deletions cohttp.opam
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ depends: [
"fmt" {with-test}
"jsonm" {build}
"alcotest" {with-test}
"crowbar" {with-test}
]
build: [
["dune" "subst"] {dev}
Expand Down
26 changes: 26 additions & 0 deletions cohttp/fuzz/dune
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
(executable
(name fuzz_header)
(libraries crowbar cohttp))

(rule
(alias runtest)
(package cohttp)
(action
(run ./fuzz_header.exe)))

(rule
(alias fuzz)
(deps
(:exe fuzz_header.exe)
(source_tree inputs))
(action
(run afl-fuzz -i inputs -o findings -- ./%{exe} @@)))

(rule
(alias bun-fuzz)
(locks %{project_root}/bun)
(deps
(:exe fuzz_me.exe)
(source_tree input))
(action
(run bun --input inputs --output findings -- ./%{exe})))
Loading