diff --git a/src/vsgi/multipart.vala b/src/vsgi/multipart.vala new file mode 100644 index 000000000..62f8b63a7 --- /dev/null +++ b/src/vsgi/multipart.vala @@ -0,0 +1,128 @@ +using GLib; +using Soup; + +namespace VSGI { + + /** + * It provides similar APIs to {@link Soup.MultipartInputStream}. + * + * @since 0.3 + */ + public class MultipartInputStream : FilterInputStream { + + public MessageHeaders headers { construct; get; } + + public MultipartInputStream (MessageHeaders headers, InputStream base_stream) { + Object (headers: headers, base_stream: base_stream); + } + + /** + * Obtain the stream over the next part of that multipart message. + * + * @return a stream over the next part of null if none's available + */ + public InputStream? next_part (out MessageHeaders part_headers, Cancellable? cancellable = null) throws IOError { + HashTable @params; + headers.get_content_type (out @params); + + var boundary = @params["boundary"]; + + assert (@params.contains ("boundary")); + + var line_reader = new DataInputStream (base_stream); + + line_reader.newline_type = DataStreamNewlineType.CR_LF; + + // read until the next boundary + do { + var line = line_reader.read_line (null, cancellable); + + if (line == null) + break; // end of input + + if (line == "--" + boundary) { + // consume current headers + var headers = new StringBuilder (); + + do { + var header_line = line_reader.read_line (null, cancellable); + + if (header_line == "") + break; // empty line preceeding the body + + if (header_line == null) + return null; // end of input..? + + headers.append (header_line + "\r\n"); + } while (true); + + headers_parse (headers.str, (int) headers.len, part_headers); + + return base_stream; + } + + if (line == "--" + boundary + "--") + return null; // end of multipart message (eiplogue follows...) + } while (true); + + return null; // end of input + } + + public override ssize_t read (uint8[] buffer, Cancellable? cancellable = null) throws IOError { + return base_stream.read (buffer, cancellable); + } + + public override bool close (Cancellable? cancellable = null) throws IOError { + return base_stream.close (cancellable); + } + } + + /** + * + * @since 0.3 + */ + public class MultipartOutputStream : FilterOutputStream { + + public MessageHeaders headers { construct; get; } + + public MultipartOutputStream (MessageHeaders headers, OutputStream base_stream) { + Object (headers: headers, base_stream: base_stream); + } + + /** + * Create a new part in this multipart message. + */ + public OutputStream new_part (MessageHeaders part_headers) throws IOError { + HashTable @params; + headers.get_content_type (out @params); + + var boundary = @params["boundary"]; + var writer = new DataOutputStream (base_stream); + + part_headers.foreach ((k, v) => { + writer.put_string ("%s: %s\r\n".printf (k, v)); + }); + + writer.put_string ("\r\n"); + + return base_stream; + } + + public override ssize_t write (uint8[] buffer, Cancellable? cancellable = null) throws IOError { + return base_stream.write (buffer, cancellable); + } + + /** + * Append the final enclosing boundary and close the base stream. + */ + public override bool close (Cancellable? cancellable = null) throws IOError { + HashTable @params; + headers.get_content_type (out @params); + + var boundary = @params["boundary"]; + var writer = new DataOutputStream (base_stream); + + return writer.put_string ("--" + boundary + "--\r\n", cancellable) && base_stream.close (cancellable); + } + } +}