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)  2002-2012, University of Amsterdam
   7                              VU University Amsterdam
   8    All rights reserved.
   9
  10    Redistribution and use in source and binary forms, with or without
  11    modification, are permitted provided that the following conditions
  12    are met:
  13
  14    1. Redistributions of source code must retain the above copyright
  15       notice, this list of conditions and the following disclaimer.
  16
  17    2. Redistributions in binary form must reproduce the above copyright
  18       notice, this list of conditions and the following disclaimer in
  19       the documentation and/or other materials provided with the
  20       distribution.
  21
  22    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  23    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  24    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  25    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  26    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  27    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  28    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  29    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  30    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  31    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  32    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  33    POSSIBILITY OF SUCH DAMAGE.
  34*/
  35
  36:- module(mime_pack,
  37          [ mime_pack/3                 % +Input, +Stream, ?Boundary
  38          ]).
  39:- use_module(mimetype).
  40:- use_module(html_write).
  41:- use_module(library(lists)).
  42:- use_module(library(error)).
  43
  44/** <module> Create a MIME message
  45
  46Simple and partial implementation of MIME   encoding. MIME is covered by
  47RFC 2045. This library is used by  e.g., http_post_data/3 when using the
  48form_data(+ListOfData) input specification.
  49
  50MIME decoding is now  arranged  through   library(mime)  from  the  clib
  51package, based on the  external  librfc2045   library.  Most  likely the
  52functionality of this package will be moved to the same library someday.
  53Packing however is a lot simpler then parsing.
  54*/
  55
  56%!  mime_pack(+Inputs, +Out:stream, ?Boundary) is det.
  57%
  58%   Pack a number of inputs into a MIME package using a specified or
  59%   generated boundary. The  generated  boundary   consists  of  the
  60%   current time in milliseconds  since  the   epoch  and  10 random
  61%   hexadecimal numbers. Inputs is a  list   of  _documents_ that is
  62%   added to the mime message.  Each element is one of:
  63%
  64%     * Name = Value
  65%     Name the document. This emits a header of the form below. The
  66%     =filename= is present if Value is of the form file(File).
  67%     Value may be any of remaining value specifications.
  68%
  69%       ==
  70%       Content-Disposition: form-data; name="Name"[; filename="<File>"
  71%       ==
  72%
  73%     * html(Tokens)
  74%     Tokens is a list of HTML tokens as produced by html//1. The
  75%     token list is emitted using print_html/1.
  76%
  77%     * file(File)
  78%     Emit the contents of File. The =|Content-type|= is derived
  79%     from the File using file_mime_type/2.  If the content-type
  80%     is =|text/_|=, the file data is copied in text mode, which
  81%     implies that it is read in the default encoding of the system
  82%     and written using the encoding of the Out stream.  Otherwise
  83%     the file data is copied binary.
  84%
  85%     * stream(In, Len)
  86%     Content is the next Len units from In.  Data is copied using
  87%     copy_stream_data/3. Units is bytes for binary streams and
  88%     characters codes for text streams.
  89%
  90%     * stream(In)
  91%     Content of the stream In, copied using copy_stream_data/2.
  92%     This is often used with memory files (see new_memory_file/1).
  93%
  94%     * mime(Attributes, Value, [])
  95%     Create a MIME header from Attributes and add Value, which can
  96%     be any of remaining values of this list. Attributes may
  97%     contain type(ContentType) and/or character_set(CharSet).  This
  98%     can be used to give a content-type to values that otherwise
  99%     do not have a content-type.  For example:
 100%
 101%       ==
 102%       mime([type(text/html)], '<b>Hello World</b>', [])
 103%       ==
 104%
 105%     * mime([], '', Parts)
 106%     Creates a nested multipart MIME message.  Parts is passed
 107%     as Inputs to a recursive call to mime_pack/2.
 108%
 109%     * Atomic
 110%     Atomic values are passed to write/1. This embeds simple atoms
 111%     and numbers.
 112%
 113%   @param  Out is a stream opened for writing. Typically, it should
 114%           be opened in text mode using UTF-8 encoding.
 115%
 116%   @bug    Does not validate that the boundary does not appear in
 117%           any of the input documents.
 118
 119mime_pack(Inputs, OutputStream, Boundary) :-
 120    make_boundary(Inputs, Boundary),
 121    pack_list(Inputs, OutputStream, Boundary).
 122
 123pack_list([], Out, Boundary) :-
 124    format(Out, '--~w--\r\n', [Boundary]).
 125pack_list([H|T], Out, Boundary) :-
 126    format(Out, '--~w\r\n', [Boundary]),
 127    pack(H, Out),
 128    format(Out, '\r\n', []),
 129    pack_list(T, Out, Boundary).
 130
 131pack(X, _Out) :-
 132    var(X),
 133    !,
 134    instantiation_error(X).
 135pack(Name=Value, Out) :-
 136    !,
 137    (   Value = file(FileName)
 138    ->  format(Out, 'Content-Disposition: form-data; name="~w"; filename="~w"\r\n',
 139               [Name, FileName])
 140    ;   format(Out, 'Content-Disposition: form-data; name="~w"\r\n', [Name])
 141    ),
 142    pack(Value, Out).
 143pack(html(HTML), Out) :-
 144    format(Out, 'Content-Type: text/html\r\n\r\n', []),
 145    print_html(Out, HTML).
 146pack(file(File), Out) :-
 147    !,
 148    (   file_mime_type(File, Type)
 149    ->  true
 150    ;   Type = text/plain
 151    ),
 152    format(Out, 'Content-Type: ~w\r\n\r\n', [Type]),
 153    (   Type = text/_
 154    ->  setup_call_cleanup(
 155            open(File, read, In),
 156            copy_stream_data(In, Out),
 157            close(In))
 158    ;   stream_property(Out, encoding(OldEncoding)),
 159        setup_call_cleanup(
 160            set_stream(Out, encoding(octet)),
 161            setup_call_cleanup(
 162                open(File, read, In, [type(binary)]),
 163                copy_stream_data(In, Out),
 164                close(In)),
 165            set_stream(Out, encoding(OldEncoding)))
 166    ).
 167pack(stream(In, Len), Out) :-
 168    !,
 169    format(Out, '\r\n', []),
 170    copy_stream_data(In, Out, Len).
 171pack(stream(In), Out) :-
 172    !,
 173    format(Out, '\r\n', []),
 174    copy_stream_data(In, Out).
 175pack(mime(Atts, Data, []), Out) :-             % mime_parse compatibility
 176    !,
 177    write_mime_attributes(Atts, Out),
 178    pack(Data, Out).
 179pack(mime(_Atts, '', Parts), Out) :-
 180    make_boundary(Parts, Boundary),
 181    format('Content-type: multipart/mixed; boundary=~w\r\n\r\n',
 182           [Boundary]),
 183    mime_pack(Parts, Out, Boundary).
 184pack(Atom, Out) :-
 185    atomic(Atom),
 186    !,
 187    format(Out, '\r\n', []),
 188    write(Out, Atom).
 189pack(Value, _) :-
 190    throw(error(type_error(mime_part, Value), _)).
 191
 192write_mime_attributes([], _) :- !.
 193write_mime_attributes(Atts, Out) :-
 194    select(type(Type), Atts, A1),
 195    !,
 196    (   select(character_set(CharSet), A1, A2)
 197    ->  format(Out, 'Content-type: ~w; charset=~w\r\n', [Type, CharSet]),
 198        write_mime_attributes(A2, Out)
 199    ;   format(Out, 'Content-type: ~w\r\n', [Type]),
 200        write_mime_attributes(A1, Out)
 201    ).
 202write_mime_attributes([_|T], Out) :-
 203    write_mime_attributes(T, Out).
 204
 205
 206%!  make_boundary(+Inputs, ?Boundary) is det.
 207%
 208%   Generate a boundary.  This should check all input sources whether
 209%   the boundary is enclosed.
 210
 211make_boundary(_, Boundary) :-
 212    atomic(Boundary),
 213    !.
 214make_boundary(_, Boundary) :-
 215    get_time(Now),
 216    A is random(1<<16),
 217    B is random(1<<16),
 218    C is random(1<<16),
 219    D is random(1<<16),
 220    E is random(1<<16),
 221    format(atom(Boundary), '------~3f~16r~16r~16r~16r~16r',
 222           [Now, A, B, C, D, E]).