Current section
34 Versions
Jump to
Current section
34 Versions
Compare versions
13
files changed
+863
additions
-589
deletions
| @@ -1,8 +1,8 @@ | |
| 1 1 | name = "gleam_http" |
| 2 | - version = "4.1.1" |
| 2 | + version = "4.2.0" |
| 3 3 | licences = ["Apache-2.0"] |
| 4 4 | description = "Types and functions for Gleam HTTP clients and servers" |
| 5 | - gleam = ">= 1.4.0" |
| 5 | + gleam = ">= 1.11.0" |
| 6 6 | |
| 7 7 | repository = { type = "github", user = "gleam-lang", repo = "http" } |
| 8 8 | links = [ |
| @@ -1,6 +1,6 @@ | |
| 1 1 | {<<"name">>, <<"gleam_http">>}. |
| 2 2 | {<<"app">>, <<"gleam_http">>}. |
| 3 | - {<<"version">>, <<"4.1.1">>}. |
| 3 | + {<<"version">>, <<"4.2.0">>}. |
| 4 4 | {<<"description">>, <<"Types and functions for Gleam HTTP clients and servers"/utf8>>}. |
| 5 5 | {<<"licenses">>, [<<"Apache-2.0">>]}. |
| 6 6 | {<<"build_tools">>, [<<"gleam">>]}. |
| @@ -28,6 +28,25 @@ pub type Method { | |
| 28 28 | Other(String) |
| 29 29 | } |
| 30 30 | |
| 31 | + pub fn parse_method(method: String) -> Result(Method, Nil) { |
| 32 | + case method { |
| 33 | + "CONNECT" -> Ok(Connect) |
| 34 | + "DELETE" -> Ok(Delete) |
| 35 | + "GET" -> Ok(Get) |
| 36 | + "HEAD" -> Ok(Head) |
| 37 | + "OPTIONS" -> Ok(Options) |
| 38 | + "PATCH" -> Ok(Patch) |
| 39 | + "POST" -> Ok(Post) |
| 40 | + "PUT" -> Ok(Put) |
| 41 | + "TRACE" -> Ok(Trace) |
| 42 | + method -> |
| 43 | + case is_valid_token(method) { |
| 44 | + True -> Ok(Other(method)) |
| 45 | + False -> Error(Nil) |
| 46 | + } |
| 47 | + } |
| 48 | + } |
| 49 | + |
| 31 50 | // A token is defined as: |
| 32 51 | // |
| 33 52 | // token = 1*tchar |
| @@ -45,53 +64,101 @@ pub type Method { | |
| 45 64 | // |
| 46 65 | // (From https://www.rfc-editor.org/rfc/rfc5234#appendix-B.1) |
| 47 66 | // |
| 48 | - fn is_valid_token(s: String) -> Bool { |
| 49 | - bit_array.from_string(s) |
| 50 | - |> do_is_valid_token(True) |
| 51 | - } |
| 52 | - |
| 53 | - fn do_is_valid_token(bytes: BitArray, acc: Bool) { |
| 54 | - case bytes, acc { |
| 55 | - <<char, rest:bytes>>, True -> do_is_valid_token(rest, is_valid_tchar(char)) |
| 56 | - _, _ -> acc |
| 67 | + fn is_valid_token(token: String) -> Bool { |
| 68 | + case token { |
| 69 | + // A token must have at least a single valid characther |
| 70 | + "" -> False |
| 71 | + _ -> is_valid_token_loop(token) |
| 57 72 | } |
| 58 73 | } |
| 59 74 | |
| 60 | - fn is_valid_tchar(ch: Int) -> Bool { |
| 61 | - case ch { |
| 62 | - // "!" | "#" | "$" | "%" | "&" | "'" | "*" | "+" | "-" |
| 63 | - // | "." | "^" | "_" | "`" | "|" | "~" |
| 64 | - 33 | 35 | 36 | 37 | 38 | 39 | 42 | 43 | 45 | 46 | 94 | 95 | 96 | 124 | 126 -> |
| 65 | - True |
| 66 | - // DIGIT |
| 67 | - ch if ch >= 0x30 && ch <= 0x39 -> True |
| 68 | - // ALPHA |
| 69 | - ch if ch >= 0x41 && ch <= 0x5A || ch >= 0x61 && ch <= 0x7A -> True |
| 75 | + fn is_valid_token_loop(token: String) -> Bool { |
| 76 | + case token { |
| 77 | + "" -> True |
| 78 | + // SYMBOLS |
| 79 | + "!" <> rest |
| 80 | + | "#" <> rest |
| 81 | + | "$" <> rest |
| 82 | + | "%" <> rest |
| 83 | + | "&" <> rest |
| 84 | + | "'" <> rest |
| 85 | + | "*" <> rest |
| 86 | + | "+" <> rest |
| 87 | + | "-" <> rest |
| 88 | + | "." <> rest |
| 89 | + | "^" <> rest |
| 90 | + | "_" <> rest |
| 91 | + | "`" <> rest |
| 92 | + | "|" <> rest |
| 93 | + | "~" <> rest |
| 94 | + | // DIGITS |
| 95 | + "0" <> rest |
| 96 | + | "1" <> rest |
| 97 | + | "2" <> rest |
| 98 | + | "3" <> rest |
| 99 | + | "4" <> rest |
| 100 | + | "5" <> rest |
| 101 | + | "6" <> rest |
| 102 | + | "7" <> rest |
| 103 | + | "8" <> rest |
| 104 | + | "9" <> rest |
| 105 | + | // ALPHA |
| 106 | + "A" <> rest |
| 107 | + | "B" <> rest |
| 108 | + | "C" <> rest |
| 109 | + | "D" <> rest |
| 110 | + | "E" <> rest |
| 111 | + | "F" <> rest |
| 112 | + | "G" <> rest |
| 113 | + | "H" <> rest |
| 114 | + | "I" <> rest |
| 115 | + | "J" <> rest |
| 116 | + | "K" <> rest |
| 117 | + | "L" <> rest |
| 118 | + | "M" <> rest |
| 119 | + | "N" <> rest |
| 120 | + | "O" <> rest |
| 121 | + | "P" <> rest |
| 122 | + | "Q" <> rest |
| 123 | + | "R" <> rest |
| 124 | + | "S" <> rest |
| 125 | + | "T" <> rest |
| 126 | + | "U" <> rest |
| 127 | + | "V" <> rest |
| 128 | + | "W" <> rest |
| 129 | + | "X" <> rest |
| 130 | + | "Y" <> rest |
| 131 | + | "Z" <> rest |
| 132 | + | "a" <> rest |
| 133 | + | "b" <> rest |
| 134 | + | "c" <> rest |
| 135 | + | "d" <> rest |
| 136 | + | "e" <> rest |
| 137 | + | "f" <> rest |
| 138 | + | "g" <> rest |
| 139 | + | "h" <> rest |
| 140 | + | "i" <> rest |
| 141 | + | "j" <> rest |
| 142 | + | "k" <> rest |
| 143 | + | "l" <> rest |
| 144 | + | "m" <> rest |
| 145 | + | "n" <> rest |
| 146 | + | "o" <> rest |
| 147 | + | "p" <> rest |
| 148 | + | "q" <> rest |
| 149 | + | "r" <> rest |
| 150 | + | "s" <> rest |
| 151 | + | "t" <> rest |
| 152 | + | "u" <> rest |
| 153 | + | "v" <> rest |
| 154 | + | "w" <> rest |
| 155 | + | "x" <> rest |
| 156 | + | "y" <> rest |
| 157 | + | "z" <> rest -> is_valid_token_loop(rest) |
| 70 158 | _ -> False |
| 71 159 | } |
| 72 160 | } |
| 73 161 | |
| 74 | - // TODO: check if the a is a valid HTTP method (i.e. it is a token, as per the |
| 75 | - // spec) and return Ok(Other(s)) if so. |
| 76 | - pub fn parse_method(s) -> Result(Method, Nil) { |
| 77 | - case s { |
| 78 | - "CONNECT" -> Ok(Connect) |
| 79 | - "DELETE" -> Ok(Delete) |
| 80 | - "GET" -> Ok(Get) |
| 81 | - "HEAD" -> Ok(Head) |
| 82 | - "OPTIONS" -> Ok(Options) |
| 83 | - "PATCH" -> Ok(Patch) |
| 84 | - "POST" -> Ok(Post) |
| 85 | - "PUT" -> Ok(Put) |
| 86 | - "TRACE" -> Ok(Trace) |
| 87 | - s -> |
| 88 | - case is_valid_token(s) { |
| 89 | - True -> Ok(Other(s)) |
| 90 | - False -> Error(Nil) |
| 91 | - } |
| 92 | - } |
| 93 | - } |
| 94 | - |
| 95 162 | pub fn method_to_string(method: Method) -> String { |
| 96 163 | case method { |
| 97 164 | Connect -> "CONNECT" |
| @@ -103,7 +170,7 @@ pub fn method_to_string(method: Method) -> String { | |
| 103 170 | Post -> "POST" |
| 104 171 | Put -> "PUT" |
| 105 172 | Trace -> "TRACE" |
| 106 | - Other(s) -> s |
| 173 | + Other(method) -> method |
| 107 174 | } |
| 108 175 | } |
| 109 176 | |
| @@ -118,11 +185,10 @@ pub type Scheme { | |
| 118 185 | /// |
| 119 186 | /// # Examples |
| 120 187 | /// |
| 121 | - /// > scheme_to_string(Http) |
| 122 | - /// "http" |
| 123 | - /// |
| 124 | - /// > scheme_to_string(Https) |
| 125 | - /// "https" |
| 188 | + /// ```gleam |
| 189 | + /// assert "http" == scheme_to_string(Http) |
| 190 | + /// assert "https" == scheme_to_string(Https) |
| 191 | + /// ``` |
| 126 192 | /// |
| 127 193 | pub fn scheme_to_string(scheme: Scheme) -> String { |
| 128 194 | case scheme { |
| @@ -135,11 +201,10 @@ pub fn scheme_to_string(scheme: Scheme) -> String { | |
| 135 201 | /// |
| 136 202 | /// # Examples |
| 137 203 | /// |
| 138 | - /// > scheme_from_string("http") |
| 139 | - /// Ok(Http) |
| 140 | - /// |
| 141 | - /// > scheme_from_string("ftp") |
| 142 | - /// Error(Nil) |
| 204 | + /// ```gleam |
| 205 | + /// assert Ok(Http) == scheme_from_string("http") |
| 206 | + /// assert Error(Nil) == scheme_from_string("ftp") |
| 207 | + /// ``` |
| 143 208 | /// |
| 144 209 | pub fn scheme_from_string(scheme: String) -> Result(Scheme, Nil) { |
| 145 210 | case string.lowercase(scheme) { |
| @@ -1,7 +1,7 @@ | |
| 1 1 | import gleam/http.{type Scheme} |
| 2 2 | import gleam/int |
| 3 3 | import gleam/list |
| 4 | - import gleam/option.{type Option, Some} |
| 4 | + import gleam/option.{type Option} |
| 5 5 | import gleam/result |
| 6 6 | import gleam/string |
| 7 7 | |
| @@ -14,7 +14,7 @@ pub type SameSitePolicy { | |
| 14 14 | None |
| 15 15 | } |
| 16 16 | |
| 17 | - fn same_site_to_string(policy) { |
| 17 | + fn same_site_to_string(policy: SameSitePolicy) -> String { |
| 18 18 | case policy { |
| 19 19 | Lax -> "Lax" |
| 20 20 | Strict -> "Strict" |
| @@ -37,64 +37,65 @@ pub type Attributes { | |
| 37 37 | /// Helper to create sensible default attributes for a set cookie. |
| 38 38 | /// |
| 39 39 | /// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#Attributes |
| 40 | - pub fn defaults(scheme: Scheme) { |
| 40 | + pub fn defaults(scheme: Scheme) -> Attributes { |
| 41 41 | Attributes( |
| 42 42 | max_age: option.None, |
| 43 43 | domain: option.None, |
| 44 44 | path: option.Some("/"), |
| 45 45 | secure: scheme == http.Https, |
| 46 46 | http_only: True, |
| 47 | - same_site: Some(Lax), |
| 47 | + same_site: option.Some(Lax), |
| 48 48 | ) |
| 49 49 | } |
| 50 50 | |
| 51 51 | const epoch = "Expires=Thu, 01 Jan 1970 00:00:00 GMT" |
| 52 52 | |
| 53 | - fn cookie_attributes_to_list(attributes) { |
| 54 | - let Attributes( |
| 55 | - max_age: max_age, |
| 56 | - domain: domain, |
| 57 | - path: path, |
| 58 | - secure: secure, |
| 59 | - http_only: http_only, |
| 60 | - same_site: same_site, |
| 61 | - ) = attributes |
| 53 | + fn cookie_attributes_to_list(attributes: Attributes) -> List(String) { |
| 54 | + let Attributes(max_age:, domain:, path:, secure:, http_only:, same_site:) = |
| 55 | + attributes |
| 56 | + |
| 62 57 | [ |
| 63 58 | // Expires is a deprecated attribute for cookies, it has been replaced with MaxAge |
| 64 59 | // MaxAge is widely supported and so Expires values are not set. |
| 65 60 | // Only when deleting cookies is the exception made to use the old format, |
| 66 61 | // to ensure complete clearup of cookies if required by an application. |
| 67 62 | case max_age { |
| 68 | - option.Some(0) -> option.Some([epoch]) |
| 63 | + option.Some(0) -> option.Some(epoch) |
| 69 64 | _ -> option.None |
| 70 65 | }, |
| 71 | - option.map(max_age, fn(max_age) { ["Max-Age=", int.to_string(max_age)] }), |
| 72 | - option.map(domain, fn(domain) { ["Domain=", domain] }), |
| 73 | - option.map(path, fn(path) { ["Path=", path] }), |
| 66 | + option.map(max_age, fn(max_age) { "Max-Age=" <> int.to_string(max_age) }), |
| 67 | + option.map(domain, fn(domain) { "Domain=" <> domain }), |
| 68 | + option.map(path, fn(path) { "Path=" <> path }), |
| 74 69 | case secure { |
| 75 | - True -> option.Some(["Secure"]) |
| 70 | + True -> option.Some("Secure") |
| 76 71 | False -> option.None |
| 77 72 | }, |
| 78 73 | case http_only { |
| 79 | - True -> option.Some(["HttpOnly"]) |
| 74 | + True -> option.Some("HttpOnly") |
| 80 75 | False -> option.None |
| 81 76 | }, |
| 82 77 | option.map(same_site, fn(same_site) { |
| 83 | - ["SameSite=", same_site_to_string(same_site)] |
| 78 | + "SameSite=" <> same_site_to_string(same_site) |
| 84 79 | }), |
| 85 80 | ] |
| 86 81 | |> list.filter_map(option.to_result(_, Nil)) |
| 87 82 | } |
| 88 83 | |
| 89 84 | pub fn set_header(name: String, value: String, attributes: Attributes) -> String { |
| 90 | - [[name, "=", value], ..cookie_attributes_to_list(attributes)] |
| 91 | - |> list.map(string.join(_, "")) |
| 85 | + [name <> "=" <> value, ..cookie_attributes_to_list(attributes)] |
| 92 86 | |> string.join("; ") |
| 93 87 | } |
| 94 88 | |
| 95 89 | /// Parse a list of cookies from a header string. Any malformed cookies will be |
| 96 90 | /// discarded. |
| 97 91 | /// |
| 92 | + /// ## Backwards compatibility |
| 93 | + /// |
| 94 | + /// RFC 6265 states that cookies in the cookie header should be separated by a |
| 95 | + /// `;`, however this function will also accept a `,` separator to remain |
| 96 | + /// compatible with the now-deprecated RFC 2965, and any older software |
| 97 | + /// following that specification. |
| 98 | + /// |
| 98 99 | pub fn parse(cookie_string: String) -> List(#(String, String)) { |
| 99 100 | cookie_string |
| 100 101 | |> string.split(";") |
| @@ -104,8 +105,8 @@ pub fn parse(cookie_string: String) -> List(#(String, String)) { | |
| 104 105 | Ok(#("", _)) -> Error(Nil) |
| 105 106 | Ok(#(key, value)) -> { |
| 106 107 | let key = string.trim(key) |
| 107 | - let value = string.trim(value) |
| 108 108 | use _ <- result.try(check_token(key)) |
| 109 | + let value = string.trim(value) |
| 109 110 | use _ <- result.try(check_token(value)) |
| 110 111 | Ok(#(key, value)) |
| 111 112 | } |
| @@ -115,13 +116,15 @@ pub fn parse(cookie_string: String) -> List(#(String, String)) { | |
| 115 116 | } |
| 116 117 | |
| 117 118 | fn check_token(token: String) -> Result(Nil, Nil) { |
| 118 | - case string.pop_grapheme(token) { |
| 119 | - Error(Nil) -> Ok(Nil) |
| 120 | - Ok(#(" ", _)) -> Error(Nil) |
| 121 | - Ok(#("\t", _)) -> Error(Nil) |
| 122 | - Ok(#("\r", _)) -> Error(Nil) |
| 123 | - Ok(#("\n", _)) -> Error(Nil) |
| 124 | - Ok(#("\f", _)) -> Error(Nil) |
| 125 | - Ok(#(_, rest)) -> check_token(rest) |
| 119 | + let contains_invalid_charachter = |
| 120 | + string.contains(token, " ") |
| 121 | + || string.contains(token, "\t") |
| 122 | + || string.contains(token, "\r") |
| 123 | + || string.contains(token, "\n") |
| 124 | + || string.contains(token, "\f") |
| 125 | + |
| 126 | + case contains_invalid_charachter { |
| 127 | + True -> Error(Nil) |
| 128 | + False -> Ok(Nil) |
| 126 129 | } |
| 127 130 | } |
| @@ -27,7 +27,7 @@ pub type Request(body) { | |
| 27 27 | |
| 28 28 | /// Return the uri that a request was sent to. |
| 29 29 | /// |
| 30 | - pub fn to_uri(request: Request(a)) -> Uri { |
| 30 | + pub fn to_uri(request: Request(body)) -> Uri { |
| 31 31 | Uri( |
| 32 32 | scheme: option.Some(http.scheme_to_string(request.scheme)), |
| 33 33 | userinfo: option.None, |
| @@ -56,8 +56,8 @@ pub fn from_uri(uri: Uri) -> Result(Request(String), Nil) { | |
| 56 56 | method: Get, |
| 57 57 | headers: [], |
| 58 58 | body: "", |
| 59 | - scheme: scheme, |
| 60 | - host: host, |
| 59 | + scheme:, |
| 60 | + host:, |
| 61 61 | port: uri.port, |
| 62 62 | path: uri.path, |
| 63 63 | query: uri.query, |
| @@ -89,7 +89,7 @@ pub fn set_header( | |
| 89 89 | value: String, |
| 90 90 | ) -> Request(body) { |
| 91 91 | let headers = list.key_set(request.headers, string.lowercase(key), value) |
| 92 | - Request(..request, headers: headers) |
| 92 | + Request(..request, headers:) |
| 93 93 | } |
| 94 94 | |
| 95 95 | /// Prepend the header with the given value under the given header key. |
| @@ -106,33 +106,13 @@ pub fn prepend_header( | |
| 106 106 | value: String, |
| 107 107 | ) -> Request(body) { |
| 108 108 | let headers = [#(string.lowercase(key), value), ..request.headers] |
| 109 | - Request(..request, headers: headers) |
| 109 | + Request(..request, headers:) |
| 110 110 | } |
| 111 111 | |
| 112 | - // TODO: record update syntax, which can't be done currently as body type changes |
| 113 112 | /// Set the body of the request, overwriting any existing body. |
| 114 113 | /// |
| 115 114 | pub fn set_body(req: Request(old_body), body: new_body) -> Request(new_body) { |
| 116 | - let Request( |
| 117 | - method: method, |
| 118 | - headers: headers, |
| 119 | - scheme: scheme, |
| 120 | - host: host, |
| 121 | - port: port, |
| 122 | - path: path, |
| 123 | - query: query, |
| 124 | - .., |
| 125 | - ) = req |
| 126 | - Request( |
| 127 | - method: method, |
| 128 | - headers: headers, |
| 129 | - body: body, |
| 130 | - scheme: scheme, |
| 131 | - host: host, |
| 132 | - port: port, |
| 133 | - path: path, |
| 134 | - query: query, |
| 135 | - ) |
| 115 | + Request(..req, body:) |
| 136 116 | } |
| 137 117 | |
| 138 118 | /// Update the body of a request using a given function. |
| @@ -177,22 +157,21 @@ pub fn set_query( | |
| 177 157 | req: Request(body), |
| 178 158 | query: List(#(String, String)), |
| 179 159 | ) -> Request(body) { |
| 180 | - let pair = fn(t: #(String, String)) { |
| 181 | - uri.percent_encode(t.0) <> "=" <> uri.percent_encode(t.1) |
| 182 | - } |
| 183 160 | let query = |
| 184 | - query |
| 185 | - |> list.map(pair) |
| 186 | - |> list.intersperse("&") |
| 187 | - |> string.concat |
| 161 | + list.map(query, fn(pair) { |
| 162 | + let #(key, value) = pair |
| 163 | + uri.percent_encode(key) <> "=" <> uri.percent_encode(value) |
| 164 | + }) |
| 165 | + |> string.join(with: "&") |
| 188 166 | |> option.Some |
| 189 | - Request(..req, query: query) |
| 167 | + |
| 168 | + Request(..req, query:) |
| 190 169 | } |
| 191 170 | |
| 192 171 | /// Set the method of the request. |
| 193 172 | /// |
| 194 173 | pub fn set_method(req: Request(body), method: Method) -> Request(body) { |
| 195 | - Request(..req, method: method) |
| 174 | + Request(..req, method:) |
| 196 175 | } |
| 197 176 | |
| 198 177 | /// A request with commonly used default values. This request can be used as |
| @@ -222,13 +201,13 @@ pub fn to(url: String) -> Result(Request(String), Nil) { | |
| 222 201 | /// Set the scheme (protocol) of the request. |
| 223 202 | /// |
| 224 203 | pub fn set_scheme(req: Request(body), scheme: Scheme) -> Request(body) { |
| 225 | - Request(..req, scheme: scheme) |
| 204 | + Request(..req, scheme:) |
| 226 205 | } |
| 227 206 | |
| 228 207 | /// Set the host of the request. |
| 229 208 | /// |
| 230 209 | pub fn set_host(req: Request(body), host: String) -> Request(body) { |
| 231 | - Request(..req, host: host) |
| 210 | + Request(..req, host:) |
| 232 211 | } |
| 233 212 | |
| 234 213 | /// Set the port of the request. |
| @@ -240,28 +219,29 @@ pub fn set_port(req: Request(body), port: Int) -> Request(body) { | |
| 240 219 | /// Set the path of the request. |
| 241 220 | /// |
| 242 221 | pub fn set_path(req: Request(body), path: String) -> Request(body) { |
| 243 | - Request(..req, path: path) |
| 222 | + Request(..req, path:) |
| 244 223 | } |
| 245 224 | |
| 246 225 | /// Set a cookie on a request, replacing any previous cookie with that name. |
| 247 226 | /// |
| 248 | - /// All cookies stored in a single header named `cookie`. There should be |
| 249 | - /// at most one header with the name `cookie`, otherwise this function cannot |
| 250 | - /// guarentee that previous cookies with the same name are replaced. |
| 227 | + /// All cookies should be stored in a single header named `cookie`. |
| 228 | + /// There should be at most one header with the name `cookie`, otherwise this |
| 229 | + /// function cannot guarentee that previous cookies with the same name are |
| 230 | + /// replaced. |
| 251 231 | /// |
| 252 | - pub fn set_cookie(req: Request(body), name: String, value: String) { |
| 232 | + pub fn set_cookie( |
| 233 | + req: Request(body), |
| 234 | + name: String, |
| 235 | + value: String, |
| 236 | + ) -> Request(body) { |
| 253 237 | // Get the cookies |
| 254 238 | let #(cookies, headers) = |
| 255 239 | list.key_pop(req.headers, "cookie") |> result.unwrap(#("", req.headers)) |
| 256 240 | |
| 257 | - // Parse them |
| 258 | - let cookies = |
| 259 | - string.split(cookies, ";") |
| 260 | - |> list.filter_map(fn(c) { string.trim_start(c) |> string.split_once("=") }) |
| 261 | - |
| 262 241 | // Set the new cookie, replacing any previous one with the same name |
| 263 242 | let cookies = |
| 264 | - list.key_set(cookies, name, value) |
| 243 | + cookie.parse(cookies) |
| 244 | + |> list.key_set(name, value) |
| 265 245 | |> list.map(fn(pair) { pair.0 <> "=" <> pair.1 }) |
| 266 246 | |> string.join("; ") |
| 267 247 | |
| @@ -272,38 +252,33 @@ pub fn set_cookie(req: Request(body), name: String, value: String) { | |
| 272 252 | /// |
| 273 253 | /// Note badly formed cookie pairs will be ignored. |
| 274 254 | /// RFC6265 specifies that invalid cookie names/attributes should be ignored. |
| 275 | - pub fn get_cookies(req) -> List(#(String, String)) { |
| 276 | - let Request(headers: headers, ..) = req |
| 255 | + pub fn get_cookies(req: Request(body)) -> List(#(String, String)) { |
| 256 | + let Request(headers:, ..) = req |
| 277 257 | |
| 278 | - headers |
| 279 | - |> list.filter_map(fn(header) { |
| 280 | - let #(name, value) = header |
| 281 | - case name { |
| 282 | - "cookie" -> Ok(cookie.parse(value)) |
| 283 | - _ -> Error(Nil) |
| 258 | + list.flat_map(headers, fn(header) { |
| 259 | + case header { |
| 260 | + #("cookie", value) -> cookie.parse(value) |
| 261 | + _ -> [] |
| 284 262 | } |
| 285 263 | }) |
| 286 | - |> list.flatten() |
| 287 264 | } |
| 288 265 | |
| 289 266 | /// Remove a cookie from a request |
| 290 267 | /// |
| 291 | - /// Remove a cookie from the request. If no cookie is found return the request unchanged. |
| 292 | - /// This will not remove the cookie from the client. |
| 293 | - pub fn remove_cookie(req: Request(body), name: String) { |
| 268 | + /// Remove a cookie from the request. If no cookie is found return the request |
| 269 | + /// unchanged. This will not remove the cookie from the client. |
| 270 | + pub fn remove_cookie(req: Request(body), name: String) -> Request(body) { |
| 294 271 | case list.key_pop(req.headers, "cookie") { |
| 295 272 | Ok(#(cookies_string, headers)) -> { |
| 296 273 | let new_cookies_string = |
| 297 | - string.split(cookies_string, ";") |
| 298 | - |> list.filter(fn(str) { |
| 299 | - string.trim(str) |
| 300 | - |> string.split_once("=") |
| 301 | - // Keep cookie if name does not match |
| 302 | - |> result.map(fn(tup) { tup.0 != name }) |
| 303 | - // Don't do anything with malformed cookies |
| 304 | - |> result.unwrap(True) |
| 274 | + cookie.parse(cookies_string) |
| 275 | + |> list.filter_map(fn(cookie) { |
| 276 | + case cookie { |
| 277 | + #(cookie_name, _) if cookie_name == name -> Error(Nil) |
| 278 | + #(name, value) -> Ok(name <> "=" <> value) |
| 279 | + } |
| 305 280 | }) |
| 306 | - |> string.join(";") |
| 281 | + |> string.join("; ") |
| 307 282 | |
| 308 283 | Request(..req, headers: [#("cookie", new_cookies_string), ..headers]) |
| 309 284 | } |
Loading more files…