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

Make HTTP methods case sensitive, also allow non-standard HTTP methods #65

Merged
merged 7 commits into from
Feb 6, 2025
Merged
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
99 changes: 67 additions & 32 deletions src/gleam/http.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import gleam/bit_array
import gleam/bool
import gleam/dynamic.{type DecodeError, type Dynamic, DecodeError}
import gleam/list
import gleam/result
import gleam/string
Expand All @@ -29,34 +28,81 @@ pub type Method {
Other(String)
}

// A token is defined as:
//
// token = 1*tchar
//
// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
// / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
// / DIGIT / ALPHA
// ; any VCHAR, except delimiters
//
// (From https://www.rfc-editor.org/rfc/rfc9110.html#name-tokens)
//
// Where DIGIT = %x30-39
// ALPHA = %x41-5A / %x61-7A
// (%xXX is a hexadecimal ASCII value)
//
// (From https://www.rfc-editor.org/rfc/rfc5234#appendix-B.1)
//
fn is_valid_token(s: String) -> Bool {
bit_array.from_string(s)
|> do_is_valid_token(True)
}

fn do_is_valid_token(bytes: BitArray, acc: Bool) {
case bytes, acc {
<<char, rest:bytes>>, True -> do_is_valid_token(rest, is_valid_tchar(char))
_, _ -> acc
}
}

fn is_valid_tchar(ch: Int) -> Bool {
case ch {
// "!" | "#" | "$" | "%" | "&" | "'" | "*" | "+" | "-"
// | "." | "^" | "_" | "`" | "|" | "~"
33 | 35 | 36 | 37 | 38 | 39 | 42 | 43 | 45 | 46 | 94 | 95 | 96 | 124 | 126 ->
True
// DIGIT
ch if ch >= 0x30 && ch <= 0x39 -> True
// ALPHA
ch if ch >= 0x41 && ch <= 0x5A || ch >= 0x61 && ch <= 0x7A -> True
_ -> False
}
}

// TODO: check if the a is a valid HTTP method (i.e. it is a token, as per the
// spec) and return Ok(Other(s)) if so.
pub fn parse_method(s) -> Result(Method, Nil) {
case string.lowercase(s) {
"connect" -> Ok(Connect)
"delete" -> Ok(Delete)
"get" -> Ok(Get)
"head" -> Ok(Head)
"options" -> Ok(Options)
"patch" -> Ok(Patch)
"post" -> Ok(Post)
"put" -> Ok(Put)
"trace" -> Ok(Trace)
_ -> Error(Nil)
case s {
"CONNECT" -> Ok(Connect)
"DELETE" -> Ok(Delete)
"GET" -> Ok(Get)
"HEAD" -> Ok(Head)
"OPTIONS" -> Ok(Options)
"PATCH" -> Ok(Patch)
"POST" -> Ok(Post)
"PUT" -> Ok(Put)
"TRACE" -> Ok(Trace)
s ->
case is_valid_token(s) {
True -> Ok(Other(s))
False -> Error(Nil)
}
}
}

pub fn method_to_string(method: Method) -> String {
case method {
Connect -> "connect"
Delete -> "delete"
Get -> "get"
Head -> "head"
Options -> "options"
Patch -> "patch"
Post -> "post"
Put -> "put"
Trace -> "trace"
Connect -> "CONNECT"
Delete -> "DELETE"
Get -> "GET"
Head -> "HEAD"
Options -> "OPTIONS"
Patch -> "PATCH"
Post -> "POST"
Put -> "PUT"
Trace -> "TRACE"
Other(s) -> s
}
}
Expand Down Expand Up @@ -103,13 +149,6 @@ pub fn scheme_from_string(scheme: String) -> Result(Scheme, Nil) {
}
}

pub fn method_from_dynamic(value: Dynamic) -> Result(Method, List(DecodeError)) {
case do_method_from_dynamic(value) {
Ok(method) -> Ok(method)
Error(_) -> Error([DecodeError("HTTP method", dynamic.classify(value), [])])
}
}

pub type MultipartHeaders {
/// The headers for the part have been fully parsed.
/// Header keys are all lowercase.
Expand Down Expand Up @@ -553,10 +592,6 @@ fn more_please_body(
|> Ok
}

@external(erlang, "gleam_http_native", "decode_method")
@external(javascript, "../gleam_http_native.mjs", "decode_method")
fn do_method_from_dynamic(a: Dynamic) -> Result(Method, Nil)

/// A HTTP header is a key-value pair. Header keys must be all lowercase
/// characters.
pub type Header =
Expand Down
88 changes: 0 additions & 88 deletions src/gleam_http_native.erl

This file was deleted.

38 changes: 0 additions & 38 deletions src/gleam_http_native.mjs

This file was deleted.

Loading