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