1 /*******************************************************************************
2 
3         copyright:      Copyright (c) Nov 2007 Kris Bell. All rights reserved
4 
5         license:        BSD style: $(LICENSE)
6 
7         version:        Nov 2007: Initial release
8 
9         author:         Kris
10 
11         Support for HTTP chunked I/O.
12 
13         See http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html
14 
15 *******************************************************************************/
16 
17 module tango.net.http.ChunkStream;
18 
19 private import  tango.io.stream.Lines;
20 
21 private import  tango.io.device.Conduit,
22                 tango.io.stream.Buffered;
23 
24 private import  Integer = tango.text.convert.Integer;
25 
26 /*******************************************************************************
27 
28         Prefix each block of data with its length (in hex digits) and add
29         appropriate \r\n sequences. To commit the stream you'll need to use
30         the terminate() function and optionally provide it with a callback
31         for writing trailing headers
32 
33 *******************************************************************************/
34 
35 class ChunkOutput : OutputFilter
36 {
37         private OutputBuffer output;
38 
39         /***********************************************************************
40 
41                 Use a buffer belonging to our sibling, if one is available
42 
43         ***********************************************************************/
44 
45         this (OutputStream stream)
46         {
47                 super (output = BufferedOutput.create(stream));
48         }
49 
50         /***********************************************************************
51 
52                 Write a chunk to the output, prefixed and postfixed in a
53                 manner consistent with the HTTP chunked transfer coding
54 
55         ***********************************************************************/
56 
57         final override size_t write (const(void)[] src)
58         {
59                 char[8] tmp = void;
60 
61                 output.append (Integer.format (tmp, src.length, "x"))
62                       .append ("\r\n")
63                       .append (src)
64                       .append ("\r\n");
65                 return src.length;
66         }
67 
68         /***********************************************************************
69 
70                 Write a zero length chunk, trailing headers and a terminating
71                 blank line
72 
73         ***********************************************************************/
74 
75         final void terminate (scope void delegate(OutputBuffer) headers = null)
76         {
77                 output.append ("0\r\n");
78                 if (headers)
79                     headers (output);
80                 output.append ("\r\n");
81         }
82 }
83 
84 
85 /*******************************************************************************
86 
87         Parse hex digits, and use the resultant size to modulate requests
88         for incoming data. A chunk size of 0 terminates the stream, so to
89         read any trailing headers you'll need to provide a delegate handler
90         for receiving those
91 
92 *******************************************************************************/
93 
94 class ChunkInput : Lines!(char)
95 {
96         private alias void delegate(const(char)[] line) Headers;
97 
98         private Headers         headers;
99         private uint            available;
100 
101         /***********************************************************************
102 
103                 Prime the available chunk size by reading and parsing the
104                 first available line
105 
106         ***********************************************************************/
107 
108         this (InputStream stream, Headers headers = null)
109         {
110                 set (stream);
111                 this.headers = headers;
112         }
113 
114         /***********************************************************************
115 
116                 Reset ChunkInput to a new InputStream
117 
118         ***********************************************************************/
119 
120         override ChunkInput set (InputStream stream)
121         {
122                 super.set (stream);
123                 available = nextChunk();
124                 return this;
125         }
126 
127         /***********************************************************************
128 
129                 Read content based on a previously parsed chunk size
130 
131         ***********************************************************************/
132 
133         final override size_t read (void[] dst)
134         {
135                 if (available is 0)
136                    {
137                    // terminated 0 - read headers and empty line, per rfc2616
138                    const(char)[] line;
139                    while ((line = super.next).length)
140                            if (headers)
141                                headers (line);
142                    return IConduit.Eof;
143                    }
144 
145                 auto size = dst.length > available ? available : dst.length;
146                 auto read = super.read (dst [0 .. size]);
147 
148                 // check for next chunk header
149                 if (read != IConduit.Eof && (available -= read) is 0)
150                    {
151                    // consume trailing \r\n
152                    super.input.seek (2);
153                    available = nextChunk ();
154                    }
155 
156                 return read;
157         }
158 
159         /***********************************************************************
160 
161                 Read and parse another chunk size
162 
163         ***********************************************************************/
164 
165         private final uint nextChunk ()
166         {
167                 const(char)[] tmp;
168 
169                 if ((tmp = super.next).ptr)
170                      return cast(uint) Integer.parse (tmp, 16);
171                 return 0;
172         }
173 }
174 
175 
176 /*******************************************************************************
177 
178 *******************************************************************************/
179 
180 debug (ChunkStream)
181 {
182         import tango.io.Console;
183         import tango.io.device.Array;
184 
185         void main()
186         {
187                 auto buf = new Array(40);
188                 auto chunk = new ChunkOutput (buf);
189                 chunk.write ("hello world");
190                 chunk.terminate;
191                 auto input = new ChunkInput (buf);
192                 Cout.stream.copy (input);
193         }
194 }