1/*
2  Copyright (C) 2000-2005 SKYRIX Software AG
3
4  This file is part of SOPE.
5
6  SOPE is free software; you can redistribute it and/or modify it under
7  the terms of the GNU Lesser General Public License as published by the
8  Free Software Foundation; either version 2, or (at your option) any
9  later version.
10
11  SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12  WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14  License for more details.
15
16  You should have received a copy of the GNU Lesser General Public
17  License along with SOPE; see the file COPYING.  If not, write to the
18  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19  02111-1307, USA.
20*/
21
22#import "common.h"
23#import "NGHttpHeaderFieldParser.h"
24#import "NGHttpHeaderFields.h"
25#import "NGHttpCookie.h"
26
27static Class NSArrayClass = Nil;
28
29@implementation NGHttpStringHeaderFieldParser
30
31- (id)parseValue:(id)_data ofHeaderField:(NSString *)_field {
32  unsigned              len   = 0;
33  const unsigned char   *src  = NULL;
34  id                     value = nil;
35  NSString *str = nil;
36
37  if ([_data isKindOfClass:[NSData class]]) {
38    len   = [_data length];
39    src = (unsigned char *)[_data bytes];
40  }
41  else {
42    len = [_data cStringLength];
43    src = (const unsigned char *)[_data cString];
44  }
45  if (len == 0) {
46#if DEBUG
47    NSLog(@"WARNING: empty value for header field %@ ..", _field);
48#endif
49    return nil;
50  }
51
52  // strip leading spaces
53  while (isRfc822_LWSP(*src) && (len > 0)) {
54    src++;
55    len--;
56  }
57
58  str = [[NSString alloc] initWithCString:(char *)src length:len];
59  NSAssert(str, @"string allocation failed ..");
60
61  if ([_field isEqualToString:@"host"])
62    value = [[NGHttpHostHeaderField alloc] initWithString:str];
63  else if ([_field isEqualToString:@"user-agent"])
64    value = [[NGHttpUserAgent alloc] initWithString:str];
65  else if ([_field isEqualToString:@"connection"])
66    value = [[NGHttpConnectionHeaderField alloc] initWithString:str];
67  else
68    value = RETAIN(str);
69
70  RELEASE(str); str = nil;
71
72  return AUTORELEASE(value);
73}
74
75@end /* NGHttpStringHeaderFieldParser */
76
77@implementation NGHttpCredentialsFieldParser
78
79- (id)parseValue:(id)_data ofHeaderField:(NSString *)_field {
80  unsigned      len     = 0;
81  const unsigned char *src    = NULL;
82  NSString      *scheme = nil;
83  NSData        *data   = nil;
84
85  if ([_data isKindOfClass:[NSData class]]) {
86    len   = [_data length];
87    src = (unsigned char *)[_data bytes];
88  }
89  else {
90    len = [_data cStringLength];
91    src = (const unsigned char *)[_data cString];
92  }
93
94  if (len == 0) {
95    NSLog(@"WARNING: empty value for header field %@ ..", _field);
96    return nil;
97  }
98
99  // strip leading spaces
100  while (isRfc822_LWSP(*src) && (len > 0)) {
101    src++;
102    len--;
103  }
104  if (len == 0) {
105    NSLog(@"WARNING: empty value for header field %@ ..", _field);
106    return nil;
107  }
108
109  // find name
110  {
111    const unsigned char *start = src;
112
113    while (!isRfc822_LWSP(*src) && (len > 0)) {
114      src++;
115      len--;
116    }
117    scheme = [NSString stringWithCString:(char *)start length:(src - start)];
118  }
119
120  // skip spaces
121  while (isRfc822_LWSP(*src) && (len > 0)) {
122    src++;
123    len--;
124  }
125  if (len == 0) {
126    NSLog(@"WARNING: invalid credentials header field %@ .. (missing credentials)",
127          _field);
128    return nil;
129  }
130
131  // make credentials
132  data = [NSData dataWithBytes:src length:len];
133
134  return [NGHttpCredentials credentialsWithScheme:[scheme lowercaseString]
135                            credentials:data];
136}
137
138@end /* NGHttpCredentialsFieldParser */
139
140@implementation NGHttpStringArrayHeaderFieldParser
141
142- (id)initWithSplitChar:(unsigned char)_c {
143  if ((self = [super init])) {
144    self->splitChar = _c;
145  }
146  return self;
147}
148- (id)init {
149  return [self initWithSplitChar:','];
150}
151
152- (id)parseValuePart:(const char *)_b length:(unsigned)_len
153  zone:(NSZone *)_zone
154{
155  return [[NSString allocWithZone:_zone]
156                    initWithCString:_b length:_len];
157}
158
159- (id)parseValue:(id)_data ofHeaderField:(NSString *)_field {
160  unsigned       len   = 0;
161  const unsigned char  *src  = NULL;
162  NSMutableArray *array = nil;
163
164  if ([_data isKindOfClass:[NSData class]]) {
165    len   = [_data length];
166    src = (unsigned char *)[_data bytes];
167  }
168  else {
169    len = [_data cStringLength];
170    src = (const unsigned char *)[_data cString];
171  }
172
173  if (len == 0) {
174#if DEBUG
175    NSLog(@"WARNING: empty value for header field %@ ..", _field);
176#endif
177    return nil;
178  }
179
180#if 0 && DEBUG
181  NSLog(@"field %@ is %@",
182        _field,
183        [[NSString alloc] initWithData:_data encoding:NSASCIIStringEncoding]);
184#endif
185
186
187  array = [NSMutableArray arrayWithCapacity:16];
188  NSAssert(array, @"array allocation failed ..");
189  do {
190    const unsigned char *startPos = NULL;
191
192    // strip leading spaces
193    while ((len > 0) && (*src != '\0') && isRfc822_LWSP(*src)) {
194      src++;
195      len--;
196    }
197    if (len <= 0)
198      break;
199    else
200      startPos = src;
201
202    while ((len > 0) && (*src != self->splitChar) && !isRfc822_LWSP(*src)) {
203      src++;
204      len--;
205    }
206
207    if (src > startPos) {
208      id part = nil;
209      unsigned partLen;
210
211      partLen = (src - startPos);
212#if DEBUG && 0
213      NSLog(@"field %@: current len=%i %s(%i)", _field, len, startPos, partLen);
214#endif
215
216      part = [self parseValuePart:(const char *)startPos
217                   length:partLen
218                   zone:[array zone]];
219      if (part) {
220        [array addObject:part];
221        RELEASE(part); part = nil;
222      }
223    }
224
225    if (len > 0) {
226      if (isRfc822_LWSP(*src)) { // skip until splitchar or to len ..
227        while ((*src != '\0') && (*src != self->splitChar) && (len > 0)) {
228          src++;
229          len--;
230        }
231      }
232      else if (*src == self->splitChar) { // skip ','
233        src++;
234        len--;
235      }
236    }
237  }
238  while ((len > 0) && (*src != '\0'));
239
240  return array;
241}
242
243@end /* NGHttpStringArrayHeaderFieldParser */
244
245@implementation NGHttpCharsetHeaderFieldParser
246
247- (id)parseValue:(id)_data ofHeaderField:(NSString *)_field {
248  id value = nil;
249
250  if ((value = [super parseValue:_data ofHeaderField:_field]) == nil)
251    return nil;
252
253  if (NSArrayClass == Nil)
254    NSArrayClass = [NSArray class];
255
256  NSAssert([value isKindOfClass:NSArrayClass], @"invalid value ..");
257
258  value = [[NGHttpCharsetHeaderField alloc] initWithArray:value];
259  value = [value autorelease];
260  return value;
261}
262
263@end /* NGHttpCharsetHeaderFieldParser */
264
265@implementation NGHttpTypeArrayHeaderFieldParser
266
267- (id)parseValuePart:(const char *)_b length:(unsigned)_len zone:(NSZone *)_zone {
268  NSString   *typeString = [[NSString alloc] initWithCString:_b length:_len];
269  NGMimeType *type       = nil;
270
271  type = typeString ? [NGMimeType mimeType:typeString] : nil;
272  RELEASE(typeString);
273
274  return RETAIN(type);
275}
276
277- (id)parseValue:(id)_data ofHeaderField:(NSString *)_field {
278  id value = nil;
279
280  value = [super parseValue:_data ofHeaderField:_field];
281  if (value) {
282    if (NSArrayClass == Nil)
283      NSArrayClass = [NSArray class];
284
285    NSAssert([value isKindOfClass:NSArrayClass], @"invalid value ..");
286
287    value = [[NGHttpTypeSetHeaderField alloc] initWithArray:value];
288    value = AUTORELEASE(value);
289  }
290  return value;
291}
292
293@end /* NGHttpTypeArrayHeaderFieldParser */
294
295@implementation NGHttpLanguageArrayHeaderFieldParser
296
297- (id)parseValue:(id)_data ofHeaderField:(NSString *)_field {
298  id value = nil;
299
300  value = [super parseValue:_data ofHeaderField:_field];
301  if (value) {
302    if (NSArrayClass == Nil)
303      NSArrayClass = [NSArray class];
304
305    NSAssert([value isKindOfClass:NSArrayClass], @"invalid value ..");
306
307    value = [[NGHttpLanguageSetHeaderField alloc] initWithArray:value];
308    value = AUTORELEASE(value);
309  }
310  return value;
311}
312
313@end /* NGHttpLanguageArrayHeaderFieldParser */
314
315@implementation NGHttpCookieFieldParser
316
317- (id)init {
318  return [self initWithSplitChar:';'];
319}
320- (id)initWithSplitChar:(unsigned char)_splitChar {
321  if ((self = [super initWithSplitChar:_splitChar])) {
322    self->fetchedCookies = NSCreateMapTable(NSObjectMapKeyCallBacks,
323                                            NSObjectMapValueCallBacks,
324                                            16);
325    self->isRunning      = NO;
326    self->foundInvalidPairs = NO;
327  }
328  return self;
329}
330
331#if !LIB_FOUNDATION_BOEHM_GC
332- (void)dealloc {
333  if (self->fetchedCookies) {
334    NSFreeMapTable(self->fetchedCookies);
335    self->fetchedCookies = NULL;
336  }
337  [super dealloc];
338}
339#endif
340
341- (id)parseValuePart:(const char *)_bytes length:(unsigned)_len
342  zone:(NSZone *)_z
343{
344  NGHttpCookie *cookie   = nil;
345  unsigned     pos, toGo;
346
347  for (pos = 0, toGo = _len; (toGo > 0) && (_bytes[pos] != '='); toGo--, pos++)
348    ;
349
350  if (toGo > 0) {
351    NSString *name   = nil;
352    NSString *value  = nil;
353
354    // NSLog(@"pos=%i toGo=%i", pos, toGo);
355
356    name  = [[NSString allocWithZone:_z]
357                       initWithCString:_bytes
358                       length:pos];
359    value = [[NSString allocWithZone:_z]
360                       initWithCString:&(_bytes[pos + 1])
361                       length:(toGo - 1)];
362
363    //NSLog(@"pair='%@'", [NSString stringWithCString:_bytes length:_len]);
364    //NSLog(@"name='%@' value='%@'", name, value);
365
366    if (name == nil) {
367      NSLog(@"ERROR: invalid cookie pair missing name: %@",
368            [NSString stringWithCString:_bytes length:_len]);
369      RELEASE(name);
370      RELEASE(value);
371      return nil;
372    }
373    else if (value == nil) {
374      NSLog(@"ERROR: invalid cookie pair missing value (name=%@): %@",
375            name,
376            [NSString stringWithCString:_bytes length:_len]);
377      RELEASE(name);
378      RELEASE(value);
379      return nil;
380    }
381    else {
382      cookie = (id)NSMapGet(self->fetchedCookies, name);
383
384      if (cookie) {
385        [cookie addAdditionalValue:[value stringByUnescapingURL]];
386        cookie = nil;
387      }
388      else {
389        cookie = [[NGHttpCookie allocWithZone:_z]
390                                initWithName:[name stringByUnescapingURL]
391                                value:[value stringByUnescapingURL]];
392        NSMapInsert(self->fetchedCookies, name, cookie);
393      }
394    }
395
396    RELEASE(name);  name  = nil;
397    RELEASE(value); value = nil;
398  }
399#if DEBUG
400  else {
401    NSLog(@"ERROR(%s:%i): invalid cookie pair: %@",
402          __PRETTY_FUNCTION__, __LINE__,
403          [NSString stringWithCString:_bytes length:_len]);
404    self->foundInvalidPairs = YES;
405  }
406#endif
407  return cookie;
408}
409
410- (id)parseValue:(id)_data ofHeaderField:(NSString *)_field {
411  id value = nil;
412
413  if (NSArrayClass == Nil)
414    NSArrayClass = [NSArray class];
415
416  NSAssert(self->isRunning == NO, @"parser used in multiple threads !");
417  self->foundInvalidPairs = NO;
418
419#if 0 && DEBUG
420  NSLog(@"cookie: field %@ is %@",
421        _field,
422        [[NSString alloc] initWithData:_data encoding:NSUTF8StringEncoding]);
423#endif
424
425  self->isRunning = YES; // semi-lock
426  {
427    value = [super parseValue:_data ofHeaderField:_field];
428    NSResetMapTable(self->fetchedCookies);
429  }
430  self->isRunning = NO; // semi-unlock
431
432  if (self->foundInvalidPairs) {
433#if DEBUG
434    NSString *s;
435
436    if ([_data isKindOfClass:[NSData class]])
437      s = [[NSString alloc] initWithData:_data encoding:NSASCIIStringEncoding];
438    else
439      s = _data;
440
441    NSLog(@"ERROR(%s:%i): got invalid cookie pairs for field %@ data %@.",
442          __PRETTY_FUNCTION__, __LINE__,
443          _field, s);
444    RELEASE(s);
445#endif
446    // return nil;
447  }
448
449  if (value) {
450    NSAssert1([value isKindOfClass:NSArrayClass],
451              @"invalid value '%@' ..", value);
452
453    //value = [[NGHttpTypeSetHeaderField alloc] initWithArray:value];
454    //AUTORELEASE(value);
455  }
456  return value;
457}
458
459@end /* NGHttpCookieFieldParser */
460
461@implementation NGMimeHeaderFieldParserSet(HttpFieldParserSet)
462
463static NGMimeHeaderFieldParserSet *httpSet = nil;
464
465static inline void NGRegisterParser(NSString *_field, NSString *_parserClass) {
466  id parser = [[NSClassFromString(_parserClass) alloc] init];
467
468  if (parser) {
469    [httpSet setParser:parser forField:_field];
470    RELEASE(parser);
471    parser = nil;
472  }
473  else {
474    NSLog(@"WARNING: did not find header field parser %@", _parserClass);
475  }
476}
477
478+ (id)defaultHttpHeaderFieldParserSet {
479  if (httpSet == nil) {
480    id parser = nil;
481
482    httpSet = [[self alloc] initWithParseSet:
483      [NGMimeHeaderFieldParserSet defaultRfc822HeaderFieldParserSet]];
484
485    parser = [[NGHttpStringHeaderFieldParser alloc] init];
486    [httpSet setParser:parser forField:@"host"];
487    [httpSet setParser:parser forField:@"user-agent"];
488    [httpSet setParser:parser forField:@"connection"];
489    RELEASE(parser); parser = nil;
490
491    NGRegisterParser(@"accept-charset",  @"NGHttpCharsetHeaderFieldParser");
492    NGRegisterParser(@"accept-language", @"NGHttpLanguageArrayHeaderFieldParser");
493    NGRegisterParser(@"accept",          @"NGHttpTypeArrayHeaderFieldParser");
494    NGRegisterParser(@"accept-encoding", @"NGHttpStringArrayHeaderFieldParser");
495    NGRegisterParser(@"cookie",          @"NGHttpCookieFieldParser");
496    NGRegisterParser(@"authorization",   @"NGHttpCredentialsFieldParser");
497  }
498  return httpSet;
499}
500
501@end /* NGMimeHeaderFieldParserSet(HttpFieldParserSet) */
502