livery_multipart (livery v0.2.0)

View Source

Streaming multipart/form-data parser (RFC 7578).

Sits on the request body reader (livery_body) and works over both streamed ({stream, _}) and buffered ({buffered, _}) bodies. Pull the parts one at a time:

{ok, MP0} = livery_multipart:new(Req),
case livery_multipart:next_part(MP0, 5000) of
    {part, #{name := Name, filename := File}, MP1} ->
        %% drain this part's body incrementally
        drain(MP1);
    {done, _} -> ok
end.

read_all/1,2 is a convenience that collects every part fully into memory under the configured limits.

Security: the parser bounds all buffering (max_header_bytes, max_header_count, max_parts, max_part_size, max_body) and never touches the filesystem. A part's filename is returned verbatim; a handler MUST confine/sanitize it before using it as a path.

Summary

Functions

Build a parser from a request. Reads the boundary from Content-Type.

new/1 with parser options.

Advance to the next part, returning its parsed metadata.

next_part/1 with an explicit per-chunk timeout.

Collect every part fully into memory under the configured limits.

read_all/1 with parser options.

Read the next chunk of the current part body.

Types

mp()

-opaque mp()

opts()

-type opts() ::
          #{part_timeout => timeout(),
            max_parts => pos_integer(),
            max_header_bytes => pos_integer(),
            max_header_count => pos_integer(),
            max_part_size => pos_integer(),
            max_body => pos_integer()}.

part()

-type part() ::
          #{name := binary() | undefined,
            filename := binary() | undefined,
            content_type := binary() | undefined,
            headers := [{binary(), binary()}]}.

part_full()

-type part_full() ::
          #{name := binary() | undefined,
            filename := binary() | undefined,
            content_type := binary() | undefined,
            headers := [{binary(), binary()}],
            body := binary()}.

reason()

-type reason() ::
          malformed |
          {client_reset, term()} |
          timeout |
          {limit, max_parts | max_header_bytes | max_header_count | max_part_size | max_body}.

Functions

new(Req)

-spec new(livery_req:req()) -> {ok, mp()} | {error, not_multipart | no_boundary}.

Build a parser from a request. Reads the boundary from Content-Type.

new(Req, Opts0)

-spec new(livery_req:req(), opts()) -> {ok, mp()} | {error, not_multipart | no_boundary}.

new/1 with parser options.

next_part(MP)

-spec next_part(mp()) -> {part, part(), mp()} | {done, mp()} | {error, reason(), mp()}.

Advance to the next part, returning its parsed metadata.

next_part/2

-spec next_part(mp(), timeout()) -> {part, part(), mp()} | {done, mp()} | {error, reason(), mp()}.

next_part/1 with an explicit per-chunk timeout.

read_all(Req)

-spec read_all(livery_req:req()) -> {ok, [part_full()]} | {error, reason()}.

Collect every part fully into memory under the configured limits.

read_all(Req, Opts)

-spec read_all(livery_req:req(), opts()) -> {ok, [part_full()]} | {error, reason()}.

read_all/1 with parser options.

read_part/2

-spec read_part(mp(), timeout()) -> {ok, binary(), mp()} | {done, mp()} | {error, reason(), mp()}.

Read the next chunk of the current part body.