1 /*******************************************************************************
2 
3         copyright:      Copyright (c) 2004 Kris Bell. All rights reserved
4 
5         license:        BSD style: $(LICENSE)
6        
7         version:        Initial release: April 2004      
8         
9         author:         Kris
10 
11 *******************************************************************************/
12 
13 module tango.net.http.HttpTokens;
14 
15 private import  tango.time.Time;
16 
17 private import  tango.io.device.Array;
18 
19 private import  tango.io.stream.Buffered;
20 
21 private import  tango.net.http.HttpStack,
22                 tango.net.http.HttpConst;
23 
24 private import  Text = tango.text.Util;
25 
26 private import  Integer = tango.text.convert.Integer;
27 
28 private import  TimeStamp = tango.text.convert.TimeStamp;
29 
30 /******************************************************************************
31 
32         Struct used to expose freachable HttpToken instances.
33 
34 ******************************************************************************/
35 
36 struct HttpToken
37 {
38         const(char)[]  name,
39                        value;
40 }
41 
42 /******************************************************************************
43 
44         Maintains a set of HTTP tokens. These tokens include headers, query-
45         parameters, and anything else vaguely related. Both input and output
46         are supported, though a subclass may choose to expose as read-only.
47 
48         All tokens are mapped directly onto a buffer, so there is no memory
49         allocation or copying involved. 
50 
51         Note that this class does not support deleting tokens, per se. Instead
52         it marks tokens as being 'unused' by setting content to null, avoiding 
53         unwarranted reshaping of the token stack. The token stack is reused as
54         time goes on, so there's only minor runtime overhead.
55 
56 ******************************************************************************/
57 
58 class HttpTokens
59 {
60         protected HttpStack     stack;
61         private Array           input;
62         private Array           output;
63         private bool            parsed;
64         private bool            inclusive;
65         private char            separator;
66         private char[1]         sepString;
67 
68         /**********************************************************************
69                 
70                 Construct a set of tokens based upon the given delimiter, 
71                 and an indication of whether said delimiter should be
72                 considered part of the left side (effectively the name).
73         
74                 The latter is useful with headers, since the seperating
75                 ':' character should really be considered part of the 
76                 name for purposes of subsequent token matching.
77 
78         **********************************************************************/
79 
80         this (char separator, bool inclusive = false)
81         {
82                 stack = new HttpStack;
83 
84                 this.inclusive = inclusive;
85                 this.separator = separator;
86                 
87                 // convert separator into a string, for later use
88                 sepString[0] = separator;
89 
90                 // pre-construct an empty buffer for wrapping char[] parsing
91                 input = new Array (0);
92 
93                 // construct an array for containing stack tokens
94                 output = new Array (4096, 1024);
95         }
96 
97         /**********************************************************************
98                 
99                 Clone a source set of HttpTokens
100 
101         **********************************************************************/
102 
103         this (HttpTokens source)
104         {
105                 stack = source.stack.clone();
106                 input = null;
107                 output = source.output;
108                 parsed = true;
109                 inclusive = source.inclusive;
110                 separator = source.separator;
111                 sepString[0] = source.sepString[0];
112         }
113 
114         /**********************************************************************
115                 
116                 Read all tokens. Everything is mapped rather than being 
117                 allocated & copied
118 
119         **********************************************************************/
120 
121         abstract void parse (InputBuffer input);
122 
123         /**********************************************************************
124                 
125                 Parse an input string.
126 
127         **********************************************************************/
128 
129         void parse (char[] content)
130         {
131                 input.assign (content);
132                 parse (input);       
133         }
134 
135         /**********************************************************************
136                 
137                 Reset this set of tokens.
138 
139         **********************************************************************/
140 
141         HttpTokens reset ()
142         {
143                 stack.reset();
144                 parsed = false;
145 
146                 // reset output buffer
147                 output.clear();
148                 return this;
149         }
150 
151         /**********************************************************************
152                 
153                 Have tokens been parsed yet?
154 
155         **********************************************************************/
156 
157         bool isParsed ()
158         {
159                 return parsed;
160         }
161 
162         /**********************************************************************
163                 
164                 Indicate whether tokens have been parsed or not.
165 
166         **********************************************************************/
167 
168         void setParsed (bool parsed)
169         {
170                 this.parsed = parsed;
171         }
172 
173         /**********************************************************************
174                 
175                 Return the value of the provided header, or null if the
176                 header does not exist
177 
178         **********************************************************************/
179 
180         const(char)[] get (const(char)[] name, const(char)[] ret = null)
181         {
182                 Token token = stack.findToken (name);
183                 if (token)
184                    {
185                    HttpToken element;
186 
187                    if (split (token, element))
188                        ret = trim (element.value);
189                    }
190                 return ret;
191         }
192 
193         /**********************************************************************
194                 
195                 Return the integer value of the provided header, or the 
196                 provided default-vaule if the header does not exist
197 
198         **********************************************************************/
199 
200         int getInt (const(char)[] name, int ret = -1)
201         {       
202                 auto value = get (name);
203 
204                 if (value.length)
205                     ret = cast(int) Integer.parse (value);
206 
207                 return ret;
208         }
209 
210         /**********************************************************************
211                 
212                 Return the date value of the provided header, or the 
213                 provided default-value if the header does not exist
214 
215         **********************************************************************/
216 
217         Time getDate (const(char)[] name, Time date = Time.epoch)
218         {
219                 auto value = get (name);
220 
221                 if (value.length)
222                     date = TimeStamp.parse (value);
223 
224                 return date;
225         }
226 
227         /**********************************************************************
228 
229                 Iterate over the set of tokens
230 
231         **********************************************************************/
232 
233         int opApply (scope int delegate(ref HttpToken) dg)
234         {
235                 HttpToken element;
236                 int       result = 0;
237 
238                 foreach (Token t; stack)
239                          if (split (t, element))
240                             {
241                             result = dg (element);
242                             if (result)
243                                 break;
244                             }
245                 return result;
246         }
247 
248         /**********************************************************************
249 
250                 Output the token list to the provided consumer
251 
252         **********************************************************************/
253 
254         void produce (scope size_t delegate(const(void)[]) consume, const(char)[] eol = null)
255         {
256                 foreach (Token token; stack)
257                         {
258                         auto content = token.toString();
259                         if (content.length)
260                            {
261                            consume (content);
262                            if (eol.length)
263                                consume (eol);
264                            }
265                         }                           
266         }
267 
268         /**********************************************************************
269 
270                 overridable method to handle the case where a token does
271                 not have a separator. Apparently, this can happen in HTTP 
272                 usage
273 
274         **********************************************************************/
275 
276         protected bool handleMissingSeparator (const(char)[] s, ref HttpToken element)
277         {
278                 return false;
279         }
280 
281         /**********************************************************************
282 
283                 split basic token into an HttpToken
284 
285         **********************************************************************/
286 
287         final private bool split (Token t, ref HttpToken element)
288         {
289                 auto s = t.toString();
290 
291                 if (s.length)
292                    {
293                    auto i = Text.locate (s, separator);
294 
295                    // we should always find the separator
296                    if (i < s.length)
297                       {
298                       auto j = (inclusive) ? i+1 : i;
299                       element.name = s[0 .. j];
300                       element.value = (++i < s.length) ? s[i .. $] : null;
301                       return true;
302                       }
303                    else
304                       // allow override to specialize this case
305                       return handleMissingSeparator (s, element);
306                    }
307                 return false;                           
308         }
309 
310         /**********************************************************************
311 
312                 Create a filter for iterating over the tokens matching
313                 a particular name. 
314         
315         **********************************************************************/
316 
317         FilteredTokens createFilter (char[] match)
318         {
319                 return new FilteredTokens (this, match);
320         }
321 
322         /**********************************************************************
323 
324                 Implements a filter for iterating over tokens matching
325                 a particular name. We do it like this because there's no 
326                 means of passing additional information to an opApply() 
327                 method.
328         
329         **********************************************************************/
330 
331         package static class FilteredTokens
332         {       
333                 private const(char)[]          match;
334                 private HttpTokens      tokens;
335 
336                 /**************************************************************
337 
338                         Construct this filter upon the given tokens, and
339                         set the pattern to match against.
340 
341                 **************************************************************/
342 
343                 this (HttpTokens tokens, const(char)[] match)
344                 {
345                         this.match = match;
346                         this.tokens = tokens;
347                 }
348 
349                 /**************************************************************
350 
351                         Iterate over all tokens matching the given name
352 
353                 **************************************************************/
354 
355                 int opApply (scope int delegate(ref HttpToken) dg)
356                 {
357                         HttpToken       element;
358                         int             result = 0;
359                         
360                         foreach (Token token; tokens.stack)
361                                  if (tokens.stack.isMatch (token, match))
362                                      if (tokens.split (token, element))
363                                         {
364                                         result = dg (element);
365                                         if (result)
366                                             break;
367                                         }
368                         return result;
369                 }
370 
371         }
372 
373         /**********************************************************************
374 
375                 Is the argument a whitespace character?
376 
377         **********************************************************************/
378 
379         private bool isSpace (char c)
380         {
381                 return cast(bool) (c is ' ' || c is '\t' || c is '\r' || c is '\n');
382         }
383 
384         /**********************************************************************
385 
386                 Trim the provided string by stripping whitespace from 
387                 both ends. Returns a slice of the original content.
388 
389         **********************************************************************/
390 
391         private inout(char)[] trim (inout(char)[] source)
392         {
393                 size_t  front,
394                      back = source.length;
395 
396                 if (back)
397                    {
398                    while (front < back && isSpace(source[front]))
399                           ++front;
400 
401                    while (back > front && isSpace(source[back-1]))
402                           --back;
403                    } 
404                 return source [front .. back];
405         }
406 
407 
408         /**********************************************************************
409         ****************** these should be exposed carefully ******************
410         **********************************************************************/
411 
412 
413         /**********************************************************************
414                 
415                 Return a char[] representing the output. An empty array
416                 is returned if output was not configured. This perhaps
417                 could just return our 'output' buffer content, but that
418                 would not reflect deletes, or seperators. Better to do 
419                 it like this instead, for a small cost.
420 
421         **********************************************************************/
422 
423         char[] formatTokens (OutputBuffer dst, const(char)[] delim)
424         {
425                 bool first = true;
426 
427                 foreach (Token token; stack)
428                         {
429                         auto content = token.toString();
430                         if (content.length)
431                            {
432                            if (first)
433                                first = false;
434                            else
435                               dst.write (delim);
436                            dst.write (content);
437                            }
438                         }    
439                 return cast(char[]) dst.slice();
440         }
441 
442         /**********************************************************************
443                 
444                 Add a token with the given name. The content is provided
445                 via the specified delegate. We stuff this name & content
446                 into the output buffer, and map a new Token onto the
447                 appropriate buffer slice.
448 
449         **********************************************************************/
450 
451         protected void add (const(char)[] name, scope void delegate(OutputBuffer) value)
452         {
453                 // save the buffer write-position
454                 //int prior = output.limit;
455                 auto prior = output.slice().length;
456 
457                 // add the name
458                 output.append (name);
459 
460                 // don't append separator if it's already part of the name
461                 if (! inclusive)
462                       output.append (sepString);
463                 
464                 // add the value
465                 value (output);
466 
467                 // map new token onto buffer slice
468                 stack.push (cast(char[]) output.slice() [prior .. $]);
469         }
470 
471         /**********************************************************************
472                 
473                 Add a simple name/value pair to the output
474 
475         **********************************************************************/
476 
477         protected void add (const(char)[] name, const(char)[] value)
478         {
479                 void addValue (OutputBuffer buffer)
480                 {
481                         buffer.write (value);
482                 }
483 
484                 add (name, &addValue);
485         }
486 
487         /**********************************************************************
488                 
489                 Add a name/integer pair to the output
490 
491         **********************************************************************/
492 
493         protected void addInt (const(char)[] name, size_t value)
494         {
495                 char[16] tmp = void;
496 
497                 add (name, Integer.format (tmp, cast(long) value));
498         }
499 
500         /**********************************************************************
501                
502                Add a name/date(long) pair to the output
503                 
504         **********************************************************************/
505 
506         protected void addDate (const(char)[] name, Time value)
507         {
508                 char[40] tmp = void;
509 
510                 add (name, TimeStamp.format (tmp, value));
511         }
512 
513         /**********************************************************************
514                
515                remove a token from our list. Returns false if the named
516                token is not found.
517                 
518         **********************************************************************/
519 
520         protected bool remove (const(char)[] name)
521         {
522                 return stack.removeToken (name);
523         }
524 }