Current section

Files

Jump to
multipartkit src multipartkit@form.erl
Raw

src/multipartkit@form.erl

-module(multipartkit@form).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/multipartkit/form.gleam").
-export([new/0, parts/1, unsafe_add_part/2, add_field/3, add_file/5, add_file_auto_with/5, add_file_auto/4]).
-export_type([form/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-opaque form() :: {form, list(multipartkit@part:part())}.
-file("src/multipartkit/form.gleam", 18).
?DOC(" A new empty form.\n").
-spec new() -> form().
new() ->
{form, []}.
-file("src/multipartkit/form.gleam", 118).
?DOC(" Read the parts in insertion order.\n").
-spec parts(form()) -> list(multipartkit@part:part()).
parts(Form) ->
lists:reverse(erlang:element(2, Form)).
-file("src/multipartkit/form.gleam", 122).
-spec push(form(), multipartkit@part:part()) -> form().
push(Form, The_part) ->
{form, [The_part | erlang:element(2, Form)]}.
-file("src/multipartkit/form.gleam", 113).
?DOC(
" Append a pre-built `Part` without validation or normalisation.\n"
"\n"
" The caller is responsible for keeping `headers`, `name`, `filename`, and\n"
" `content_type` mutually consistent. Prefer `add_field` / `add_file` /\n"
" `add_file_auto` for library-maintained consistency.\n"
).
-spec unsafe_add_part(form(), multipartkit@part:part()) -> form().
unsafe_add_part(Form, The_part) ->
push(Form, The_part).
-file("src/multipartkit/form.gleam", 155).
?DOC(
" Strip CR, LF, and NUL from `value`. Used to neutralise header injection\n"
" in any value that ends up on a header line — `name`, `filename`, and\n"
" `content_type`.\n"
).
-spec sanitize_value(binary()) -> binary().
sanitize_value(Value) ->
_pipe = Value,
_pipe@1 = gleam@string:replace(_pipe, <<"\r\n"/utf8>>, <<""/utf8>>),
_pipe@2 = gleam@string:replace(_pipe@1, <<"\r"/utf8>>, <<""/utf8>>),
_pipe@3 = gleam@string:replace(_pipe@2, <<"\n"/utf8>>, <<""/utf8>>),
gleam@string:replace(_pipe@3, <<"\x{0000}"/utf8>>, <<""/utf8>>).
-file("src/multipartkit/form.gleam", 167).
-spec escape_quoted(binary(), binary()) -> binary().
escape_quoted(Remaining, Acc) ->
case gleam_stdlib:string_pop_grapheme(Remaining) of
{error, nil} ->
Acc;
{ok, {<<"\\"/utf8>>, Rest}} ->
escape_quoted(Rest, <<Acc/binary, "\\\\"/utf8>>);
{ok, {<<"\""/utf8>>, Rest@1}} ->
escape_quoted(Rest@1, <<Acc/binary, "\\\""/utf8>>);
{ok, {Other, Rest@2}} ->
escape_quoted(Rest@2, <<Acc/binary, Other/binary>>)
end.
-file("src/multipartkit/form.gleam", 163).
-spec quote(binary()) -> binary().
quote(Value) ->
<<<<"\""/utf8, (escape_quoted(Value, <<""/utf8>>))/binary>>/binary,
"\""/utf8>>.
-file("src/multipartkit/form.gleam", 30).
?DOC(
" Append a text field. `value` is encoded as UTF-8 in the part body. No\n"
" filename is set.\n"
"\n"
" Carriage returns, line feeds, and NUL bytes in `name` are silently\n"
" stripped to prevent header injection. The cached `name` on the resulting\n"
" `Part` reflects the sanitized value, matching what a parse-after-encode\n"
" round-trip would produce. Use `unsafe_add_part` if byte-exact\n"
" preservation is required.\n"
).
-spec add_field(form(), binary(), binary()) -> form().
add_field(Form, Name, Value) ->
Safe_name = sanitize_value(Name),
Disposition = {<<"Content-Disposition"/utf8>>,
<<"form-data; name="/utf8, (quote(Safe_name))/binary>>},
New_part = {part,
[Disposition],
{some, Safe_name},
none,
none,
<<Value/binary>>},
push(Form, New_part).
-file("src/multipartkit/form.gleam", 126).
-spec build_file_part(binary(), binary(), binary(), bitstring()) -> multipartkit@part:part().
build_file_part(Name, Filename, Content_type, Body) ->
Safe_name = sanitize_value(Name),
Safe_filename = sanitize_value(Filename),
Safe_content_type = sanitize_value(Content_type),
Disposition_header = {<<"Content-Disposition"/utf8>>,
<<<<<<"form-data; name="/utf8, (quote(Safe_name))/binary>>/binary,
"; filename="/utf8>>/binary,
(quote(Safe_filename))/binary>>},
Content_type_header = {<<"Content-Type"/utf8>>, Safe_content_type},
{part,
[Disposition_header, Content_type_header],
{some, Safe_name},
{some, Safe_filename},
{some, Safe_content_type},
Body}.
-file("src/multipartkit/form.gleam", 54).
?DOC(
" Append a file part with an explicit content type.\n"
"\n"
" Carriage returns, line feeds, and NUL bytes in `name`, `filename`, and\n"
" `content_type` are silently stripped to prevent header injection. The\n"
" cached `name`, `filename`, and `content_type` on the resulting `Part`\n"
" reflect the sanitized values. Use `unsafe_add_part` if byte-exact\n"
" preservation is required.\n"
).
-spec add_file(form(), binary(), binary(), binary(), bitstring()) -> form().
add_file(Form, Name, Filename, Content_type, Body) ->
push(Form, build_file_part(Name, Filename, Content_type, Body)).
-file("src/multipartkit/form.gleam", 90).
?DOC(
" Append a file part, inferring the content type via the supplied\n"
" `Inferer`.\n"
"\n"
" Inference precedence:\n"
"\n"
" 1. `inferer.from_filename(filename)`\n"
" 2. `inferer.from_bytes(body)`\n"
" 3. `application/octet-stream`\n"
"\n"
" The inferred content type is sanitized (CR / LF / NUL stripped) before\n"
" being written to the header.\n"
).
-spec add_file_auto_with(
form(),
binary(),
binary(),
bitstring(),
multipartkit@infer:inferer()
) -> form().
add_file_auto_with(Form, Name, Filename, Body, Inferer) ->
Content_type = case (erlang:element(2, Inferer))(Filename) of
{some, Value} ->
Value;
none ->
case (erlang:element(3, Inferer))(Body) of
{some, Value@1} ->
Value@1;
none ->
<<"application/octet-stream"/utf8>>
end
end,
add_file(Form, Name, Filename, Content_type, Body).
-file("src/multipartkit/form.gleam", 70).
?DOC(
" Append a file part using the default (no-op) inferer.\n"
"\n"
" Equivalent to `add_file_auto_with(form, name, filename, body,\n"
" infer.default_inferer())`. The default inferer returns `None` from both\n"
" helpers in v0.1.0, so this falls through to `application/octet-stream`\n"
" unless you call `add_file_auto_with` with a real inferer.\n"
).
-spec add_file_auto(form(), binary(), binary(), bitstring()) -> form().
add_file_auto(Form, Name, Filename, Body) ->
add_file_auto_with(
Form,
Name,
Filename,
Body,
multipartkit@infer:default_inferer()
).