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, John Reimer
10 
11 *******************************************************************************/
12 
13 module tango.net.http.HttpStack;
14 
15 private import  tango.core.Exception;
16 
17 /******************************************************************************
18 
19         Unix doesn't appear to have a memicmp() ... JJR notes that the
20         strncasecmp() is available instead.
21 
22 ******************************************************************************/
23 
24 version (Win32)
25         {
26         extern (C) int memicmp (const(char) *, const(char) *, size_t);
27         }
28 
29 version (Posix) 
30         {
31         extern (C) int strncasecmp (const(char) *, const(char)*, size_t);
32         }
33 
34 extern (C) void* memmove (void* dst, const(void)* src, int n);
35 
36 
37 /******************************************************************************
38 
39         Internal representation of a token
40 
41 ******************************************************************************/
42 
43 class Token
44 {
45         private const(char)[] value;
46 
47         Token set (const(char)[] value)
48         {
49                 this.value = value;
50                 return this;
51         }
52 
53         override string toString ()
54         {
55                 return value.idup;
56         }
57 }
58 
59 /******************************************************************************
60 
61         A stack of Tokens, used for capturing http headers. The tokens
62         themselves are typically mapped onto the content of a Buffer, 
63         or some other external content, so there's minimal allocation 
64         involved (typically zero).
65 
66 ******************************************************************************/
67 
68 class HttpStack
69 {
70         private int     depth;
71         private Token[] tokens;
72 
73         private static const int MaxHttpStackSize = 256;
74 
75         /**********************************************************************
76 
77                 Construct a HttpStack with the specified initial size. 
78                 The stack will later be resized as necessary.
79 
80         **********************************************************************/
81 
82         this (int size = 10)
83         {
84                 tokens = new Token[0];
85                 resize (tokens, size);
86         }
87 
88         /**********************************************************************
89 
90                 Clone this stack of tokens
91 
92         **********************************************************************/
93 
94         HttpStack clone ()
95         {
96                 // setup a new HttpStack of the same depth
97                 HttpStack clone = new HttpStack(depth);
98                 
99                 clone.depth = depth;
100 
101                 // duplicate the content of each original token
102                 for (int i=0; i < depth; ++i)
103                      clone.tokens[i].set (tokens[i].toString());
104 
105                 return clone;
106         }
107 
108         /**********************************************************************
109 
110                 Iterate over all tokens in stack
111 
112         **********************************************************************/
113 
114         int opApply (scope int delegate(ref Token) dg)
115         {
116                 int result = 0;
117 
118                 for (int i=0; i < depth; ++i)
119                      if ((result = dg (tokens[i])) != 0)
120                           break;
121                 return result;
122         }
123 
124         /**********************************************************************
125 
126                 Pop the stack all the way back to zero
127 
128         **********************************************************************/
129 
130         final void reset ()
131         {
132                 depth = 0;
133         }
134 
135         /**********************************************************************
136 
137                 Scan the tokens looking for the first one with a matching
138                 name. Returns the matching Token, or null if there is no
139                 such match.
140 
141         **********************************************************************/
142 
143         final Token findToken (const(char)[] match)
144         {
145                 Token tok;
146 
147                 for (int i=0; i < depth; ++i)
148                     {
149                     tok = tokens[i];
150                     if (isMatch (tok, match))
151                         return tok;
152                     }
153                 return null;
154         }
155 
156         /**********************************************************************
157 
158                 Scan the tokens looking for the first one with a matching
159                 name, and remove it. Returns true if a match was found, or
160                 false if not.
161 
162         **********************************************************************/
163 
164         final bool removeToken (const(char)[] match)
165         {
166                 for (int i=0; i < depth; ++i)
167                      if (isMatch (tokens[i], match))
168                         {
169                         tokens[i].value = null;
170                         return true;
171                         }
172                 return false;
173         }
174 
175         /**********************************************************************
176 
177                 Return the current stack depth
178 
179         **********************************************************************/
180 
181         @property final int size ()
182         {       
183                 return depth;
184         }
185 
186         /**********************************************************************
187 
188                 Push a new token onto the stack, and set it content to 
189                 that provided. Returns the new Token.
190 
191         **********************************************************************/
192 
193         final Token push (const(char)[] content)
194         {
195                 return push().set (content);  
196         }
197 
198         /**********************************************************************
199 
200                 Push a new token onto the stack, and set it content to 
201                 be that of the specified token. Returns the new Token.
202 
203         **********************************************************************/
204 
205         final Token push (ref Token token)
206         {
207                 return push (token.toString());  
208         }
209 
210         /**********************************************************************
211 
212                 Push a new token onto the stack, and return it.
213 
214         **********************************************************************/
215 
216         final Token push ()
217         {
218                 if (depth == tokens.length)
219                     resize (tokens, depth * 2);
220                 return tokens[depth++];
221         }
222 
223         /**********************************************************************
224 
225                 Pop the stack by one.
226 
227         **********************************************************************/
228 
229         final void pop ()
230         {
231                 if (depth)
232                     --depth;
233                 else
234                    throw new IOException ("illegal attempt to pop Token stack");
235         }
236 
237         /**********************************************************************
238 
239                 See if the given token matches the specified text. The 
240                 two must match the minimal extent exactly.
241 
242         **********************************************************************/
243 
244         final static bool isMatch (ref Token token, const(char)[] match)
245         {
246                 const(char)[] target = token.toString();
247 
248                 size_t length = target.length;
249                 if (length > match.length)
250                     length = match.length;
251 
252                 if (length is 0)
253                     return false;
254 
255                 version (Win32)
256                          return memicmp (target.ptr, match.ptr, length) is 0;
257                 version (Posix)
258                          return strncasecmp (target.ptr, match.ptr, length) is 0;
259         }
260         
261         /**********************************************************************
262 
263                 Resize this stack by extending the array.
264 
265         **********************************************************************/
266 
267         final static void resize (ref Token[] tokens, int size)
268         {
269                 size_t i = tokens.length;
270 
271                 // this should *never* realistically happen 
272                 if (size > MaxHttpStackSize)
273                     throw new IOException ("Token stack exceeds maximum depth");
274 
275                 for (tokens.length=size; i < tokens.length; ++i)
276                      tokens[i] = new Token();
277         }
278 }