View source with raw comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        J.Wielemaker@vu.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  2014-2022, University of Amsterdam
    7                              VU University Amsterdam
    8                              SWI-Prolog Solutions b.v.
    9    All rights reserved.
   10
   11    Redistribution and use in source and binary forms, with or without
   12    modification, are permitted provided that the following conditions
   13    are met:
   14
   15    1. Redistributions of source code must retain the above copyright
   16       notice, this list of conditions and the following disclaimer.
   17
   18    2. Redistributions in binary form must reproduce the above copyright
   19       notice, this list of conditions and the following disclaimer in
   20       the documentation and/or other materials provided with the
   21       distribution.
   22
   23    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   24    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   25    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   26    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   27    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   28    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   29    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   30    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   31    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   32    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   33    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   34    POSSIBILITY OF SUCH DAMAGE.
   35*/
   36
   37:- module(http_multipart_plugin,
   38          [
   39          ]).   40:- use_module(http_stream).   41:- use_module(http_header).   42:- use_module(library(debug)).   43:- use_module(library(option)).

Multipart form-data plugin

This plugin for library(http_client) automatically translates messages with content-type multipart/form-data into a list of Name = Value pairs, greatly simplifying the processing of forms with this type.

After loading this plugin, multipart form-data can be accessed through http_parameters/3 from library(http/http_parameters) or http_read_data/3 from library(http/http_client). */

   56:- multifile
   57    http_client:http_convert_data/4,
   58    http_parameters:form_data_content_type/1.
 http_client:http_convert_data(+In, +Fields, -Data, +Options) is semidet
Convert multipart/form-data messages for http_read_data/3. This plugin adds the folling options to http_read_data/3:
form_data(+AsForm)
If the content-type is multipart/form-data, return the form-data either in one of the following formats:
AsForm=form
A list of Name=Value, where Value is an atom.
AsForm=mime
A list of mime(Properties, Value, []). This is a backward compatibility mode, emulating library(http/http_mime_plugin). Note that if the disposition contains a filename property, the data is read as binary unless there is a charset parameter in the Content-Type stating otherwise, while the old library would use UTF-8 for text files.
input_encoding(+Encoding)
Encoding to be used for parts that have no filename disposition and no Content-Type with a charset indication. This is typically the case for input widgets and browsers encode this using the encoding of the page. As the SWI-Prolog http library emits pages in UTF-8, the default is utf8.
on_filename(:CallBack)
If a part with a filename disposition is found and this option is given, call CallBack as below. Stream is the multipart input stream, which has octet (raw) encoding. Value is returned as result. Note that the callback may wish to save the result into a file and return e.g., file(Path) to indicate where the file was saved.
call(:CallBack, +Stream, -Value, +Options).

The Options list contains information from the part header. It always contains name(Name) and filename(FileName). It may contain a term media(Type/SubType, Params) if the part contains a Content-Type header.

  101http_client:http_convert_data(In, Fields, Data, Options) :-
  102    memberchk(content_type(Type), Fields),
  103    multipart_type(Type, Boundary),
  104    !,
  105    setup_call_cleanup(
  106        multipart_open(In, Stream, [boundary(Boundary)]),
  107        process_parts(Stream, Data, Options),
  108        close(Stream)).
 multipart_type(+Type, -Boundary) is semidet
True if Type is of the form multipart/form-data; boundary="..." and Boundary is a string describing the boundary.
  116multipart_type(Type, Boundary) :-
  117    http_parse_header_value(content_type, Type,
  118                            media(multipart/'form-data', Params)),
  119    memberchk(boundary=Boundary, Params).
  120
  121
  122process_parts(Stream, [Part|More], Options) :-
  123    http_read_header(Stream, HTTPHeader),
  124    part_header(HTTPHeader, Params, Name, Encoding),
  125    part_value(Stream, Name, Params, Encoding, Part, Options),
  126    debug(multipart(content), 'Got ~q~n', [Part]),
  127    (   multipart_open_next(Stream)
  128    ->  process_parts(Stream, More, Options)
  129    ;   More = []
  130    ).
  131
  132set_encoding(text, Stream, _) :-
  133    !,
  134    (   set_stream(Stream, encoding(bom))
  135    ->  (   debugging(multipart(bom))
  136        ->  stream_property(Stream, encoding(Enc)),
  137            debug(multipart(bom), "BOM: ~q", [Enc])
  138        ;   true
  139        )
  140    ;   set_stream(Stream, encoding(iso_latin_1)) % RFC2616, sec. 3.7.1
  141    ).
  142set_encoding(input, Stream, Options) :-
  143    !,
  144    option(input_encoding(Enc), Options, utf8),
  145    set_stream(Stream, encoding(Enc)).
  146set_encoding(Enc, Stream, _) :-
  147    set_stream(Stream, encoding(Enc)).
 part_header(+PartHeader, -Params, -Name, -Encoding) is det
Extract the form-field Name, the content Encoding and possible other properties of the form-field. Extra properties are:
  158part_header(PartHeader, Extra, Name, Encoding) :-
  159    memberchk(content_disposition(disposition('form-data', DProps)),
  160              PartHeader),
  161    memberchk(name=Name, DProps),
  162    (   filename(DProps, Extra, Extra1)
  163    ->  part_encoding(PartHeader, Extra1, Encoding)
  164    ;   Encoding = input,
  165        Extra = []
  166    ).
  167
  168filename(DProps, Extra, Tail) :-
  169    memberchk(filename=FileName, DProps),
  170    !,
  171    Extra = [filename(FileName)|Tail].
  172
  173part_encoding(PartHeader, Extra, Encoding) :-
  174    memberchk(content_type(TypeA), PartHeader),
  175    http_parse_header_value(content_type, TypeA, MediaType),
  176    !,
  177    Extra = [MediaType],
  178    media_type_encoding(MediaType, Encoding).
  179
  180media_type_encoding(media(_Type, Params), Encoding) :-
  181    memberchk(charset=CharSet, Params),
  182    charset_encoding(CharSet, Encoding).
  183media_type_encoding(media(Type/SubType, _Params), Encoding) :-
  184    media_encoding(Type, SubType, Encoding).
  185
  186charset_encoding(CharSet, utf8) :-
  187    sub_atom_icasechk(CharSet, _, 'utf-8'),
  188    !.
  189charset_encoding(_, octet).
  190
  191media_encoding(text, _, text) :- !.
  192media_encoding(_,    _, octet).
 part_value(+Stream, +Name, +Params, +Encoding, -Part, +Options)
  197part_value(Stream, Name, Params, Encoding, Part, Options) :-
  198    option(form_data(mime), Options),
  199    !,
  200    set_encoding(Encoding, Stream, Options),
  201    Part = mime([disposition('form-data'),name(Name)|Properties], Atom, []),
  202    mime_properties(Params, Properties),
  203    read_string(Stream, _, String),
  204    atom_string(Atom, String).
  205part_value(Stream, Name, Params, _Encoding, Name=Value, Options) :-
  206    memberchk(filename(_), Params),
  207    option(on_filename(Goal), Options),
  208    !,
  209%   Always save files binary
  210%   set_encoding(Encoding, Stream, Options),
  211    call(Goal, Stream, Value, [name(Name)|Params]).
  212part_value(Stream, Name, _, Encoding, Name=Value, Options) :-
  213    set_encoding(Encoding, Stream, Options),
  214    read_string(Stream, _, String),
  215    atom_string(Value, String).
  216
  217mime_properties([], []).
  218mime_properties([media(Type/SubType, Params)|T0],
  219                [type(ContentType)|T]) :-
  220    !,
  221    atomic_list_concat([Type, SubType], /, ContentType),
  222    (   memberchk(charset(CharSet), Params)
  223    ->  T = [character_set(CharSet)|T1]
  224    ;   T = T1
  225    ),
  226    mime_properties(T0, T1).
  227mime_properties([H|T0], [H|T]) :-
  228    mime_properties(T0, T).
  229
  230
  231http_parameters:form_data_content_type(ContentType) :-
  232    sub_atom(ContentType, 0, _, _, 'multipart/form-data')