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])