~ubuntu-branches/ubuntu/utopic/gnustep-base/utopic

1.2.3 by Hubert Chan
Import upstream version 1.13.0
1
/* Implementation for NSHTTPCookie for GNUstep
2
   Copyright (C) 2006 Software Foundation, Inc.
3
1.3.3 by Gürkan Sengün
Import upstream version 1.19.3
4
   Written by:  Richard Frith-Macdonald <rfm@gnu.org>
1.2.3 by Hubert Chan
Import upstream version 1.13.0
5
   Date: 2006
6
   
7
   This file is part of the GNUstep Base Library.
8
9
   This library is free software; you can redistribute it and/or
1.2.5 by Hubert Chathi
Import upstream version 1.14.1
10
   modify it under the terms of the GNU Lesser General Public
1.2.3 by Hubert Chan
Import upstream version 1.13.0
11
   License as published by the Free Software Foundation; either
1.2.6 by Hubert Chathi
Import upstream version 1.16.3
12
   version 2 of the License, or (at your option) any later version.
1.2.3 by Hubert Chan
Import upstream version 1.13.0
13
   
14
   This library is distributed in the hope that it will be useful,
15
   but WITHOUT ANY WARRANTY; without even the implied warranty of
16
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17
   Library General Public License for more details.
18
   
1.2.5 by Hubert Chathi
Import upstream version 1.14.1
19
   You should have received a copy of the GNU Lesser General Public
1.2.3 by Hubert Chan
Import upstream version 1.13.0
20
   License along with this library; if not, write to the Free
21
   Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22
   Boston, MA 02111 USA.
23
   */ 
24
1.2.8 by Yavor Doganov
Import upstream version 1.20.0
25
/* 
26
  Try to handle cookies via the original Netscape specification
27
  (http://web.archive.org/web/20070805052634/http://wp.netscape.com/newsref/std/cookie_spec.html)
28
  and the official RFC2965 (http://tools.ietf.org/html/rfc2965).
29
30
  Header fields named "Set-Cookie" are processed using either the original
31
  spec or RFC2965. "Set-Cookie2" fields use the RFC spec. There are some 
32
  crazy things to be aware of though. Multiple cookies can be specified in the
33
  same header and are separated by a comma. However, cookies themselves can
34
  also contain commas, most notably in the Expires field (which is not quoted
35
  and can contain spaces as well). The last key/value does not have to have a
36
  semi-colon, so this can be tricky to parse if another cookie occurs
37
  after this (See GSRangeOfCookie).
38
*/
39
40
#import "common.h"
41
#define	EXPOSE_NSHTTPCookie_IVARS	1
42
#import "GSURLPrivate.h"
43
#import "Foundation/NSSet.h"
44
#import "Foundation/NSValue.h"
45
#import "Foundation/NSString.h"
46
#import "Foundation/NSCalendarDate.h"
47
#import "GNUstepBase/Unicode.h"
48
49
NSString * const NSHTTPCookieComment = @"Comment";
50
NSString * const NSHTTPCookieCommentURL = @"CommentURL";
51
NSString * const NSHTTPCookieDiscard = @"Discard";
52
NSString * const NSHTTPCookieDomain = @"Domain";
53
NSString * const NSHTTPCookieExpires = @"Expires";
54
NSString * const NSHTTPCookieMaximumAge = @"MaximumAge";
55
NSString * const NSHTTPCookieName = @"Name";
56
NSString * const NSHTTPCookieOriginURL = @"OriginURL";
57
NSString * const NSHTTPCookiePath = @"Path";
58
NSString * const NSHTTPCookiePort = @"Port";
59
NSString * const NSHTTPCookieSecure = @"Secure";
60
NSString * const NSHTTPCookieValue = @"Value";
61
NSString * const NSHTTPCookieVersion = @"Version";
1.2.3 by Hubert Chan
Import upstream version 1.13.0
62
63
// Internal data storage
64
typedef struct {
65
  NSDictionary	*_properties;
66
} Internal;
67
 
1.2.8 by Yavor Doganov
Import upstream version 1.20.0
68
#define	this	((Internal*)(self->_NSHTTPCookieInternal))
69
#define	inst	((Internal*)(o->_NSHTTPCookieInternal))
70
71
/* Bitmap of characters considered white space if in an old style property
72
 * list. This is the same as the set given by the isspace() function in the
73
 * POSIX locale, but (for cross-locale portability of property list files)
74
 * is fixed, rather than locale dependent.
75
 */
76
static const unsigned char whitespace[32] = {
77
  '\x00',
78
  '\x3f',
79
  '\x00',
80
  '\x00',
81
  '\x01',
82
  '\x00',
83
  '\x00',
84
  '\x00',
85
  '\x00',
86
  '\x00',
87
  '\x00',
88
  '\x00',
89
  '\x00',
90
  '\x00',
91
  '\x00',
92
  '\x00',
93
  '\x00',
94
  '\x00',
95
  '\x00',
96
  '\x00',
97
  '\x00',
98
  '\x00',
99
  '\x00',
100
  '\x00',
101
  '\x00',
102
  '\x00',
103
  '\x00',
104
  '\x00',
105
  '\x00',
106
  '\x00',
107
  '\x00',
108
  '\x00',
109
};
110
111
#define IS_BIT_SET(a,i) ((((a) & (1<<(i)))) > 0)
112
113
#define GS_IS_WHITESPACE(X) IS_BIT_SET(whitespace[(X)/8], (X) % 8)
114
1.4.1 by Yavor Doganov
Import upstream version 1.22.0
115
static id GSPropertyListFromCookieFormat(NSString *string, int version);
1.2.8 by Yavor Doganov
Import upstream version 1.20.0
116
static NSRange GSRangeOfCookie(NSString *string);
1.2.3 by Hubert Chan
Import upstream version 1.13.0
117
118
@implementation NSHTTPCookie
119
120
+ (id) allocWithZone: (NSZone*)z
121
{
122
  NSHTTPCookie	*o = [super allocWithZone: z];
123
124
  if (o != nil)
125
    {
1.2.4 by Hubert Chathi
Import upstream version 1.14.0
126
      o->_NSHTTPCookieInternal = NSZoneCalloc(z, 1, sizeof(Internal));
1.2.3 by Hubert Chan
Import upstream version 1.13.0
127
    }
128
  return o;
129
}
130
131
+ (id) cookieWithProperties: (NSDictionary *)properties
132
{
133
  NSHTTPCookie	*o;
134
135
  o = [[self alloc] initWithProperties: properties];
136
  return AUTORELEASE(o);
137
}
138
1.2.8 by Yavor Doganov
Import upstream version 1.20.0
139
+ (NSMutableArray *) _parseField: (NSString *)field 
140
		       forHeader: (NSString *)header
141
			  andURL: (NSURL *)url
142
{
143
  int version;
144
  NSString *defaultPath, *defaultDomain;
1.4.1 by Yavor Doganov
Import upstream version 1.22.0
145
  NSMutableArray *a;
146
1.2.8 by Yavor Doganov
Import upstream version 1.20.0
147
  if ([header isEqual: @"Set-Cookie"])
148
    version = 0;
149
  else if ([header isEqual: @"Set-Cookie2"])
150
    version = 1;
151
  else
152
    return nil;
153
1.4.1 by Yavor Doganov
Import upstream version 1.22.0
154
  a = [NSMutableArray array];
1.2.8 by Yavor Doganov
Import upstream version 1.20.0
155
  defaultDomain = [url host];
156
  defaultPath = [url path];
157
  if ([[url absoluteString] hasSuffix: @"/"] == NO)
158
    defaultPath = [defaultPath stringByDeletingLastPathComponent];
159
160
  /* We could use an NSScanner here, but this string could contain all
161
     sorts of odd stuff. It's not quite a property list either - it has
162
     dates and also could have tokens without values. */
163
  while (1)
164
    {
165
      NSHTTPCookie *cookie;
166
      NSMutableDictionary *dict;
167
      NSString *onecookie;
168
      NSRange range = GSRangeOfCookie(field);
1.4.1 by Yavor Doganov
Import upstream version 1.22.0
169
1.2.8 by Yavor Doganov
Import upstream version 1.20.0
170
      if (range.location == NSNotFound)
171
	break;
1.4.2 by Yavor Doganov
Import upstream version 1.24.0
172
      onecookie = [field substringWithRange: range];
1.2.8 by Yavor Doganov
Import upstream version 1.20.0
173
      NS_DURING
1.4.1 by Yavor Doganov
Import upstream version 1.22.0
174
	dict = GSPropertyListFromCookieFormat(onecookie, version);
1.2.8 by Yavor Doganov
Import upstream version 1.20.0
175
      NS_HANDLER
176
	dict = nil;
177
      NS_ENDHANDLER
1.4.1 by Yavor Doganov
Import upstream version 1.22.0
178
      if ([dict count])
179
	{
180
	  if ([dict objectForKey: NSHTTPCookiePath] == nil)
181
	    [dict setObject: defaultPath forKey: NSHTTPCookiePath];
182
	  if ([dict objectForKey: NSHTTPCookieDomain] == nil)
183
	    [dict setObject: defaultDomain forKey: NSHTTPCookieDomain];
184
	  cookie = [NSHTTPCookie cookieWithProperties: dict];
185
	  if (cookie)
186
	    [a addObject: cookie];
187
	}
1.2.8 by Yavor Doganov
Import upstream version 1.20.0
188
      if ([field length] <= NSMaxRange(range))
189
	break;
190
      field = [field substringFromIndex: NSMaxRange(range)+1];
191
    }
192
  return a;
193
}
194
1.2.3 by Hubert Chan
Import upstream version 1.13.0
195
+ (NSArray *) cookiesWithResponseHeaderFields: (NSDictionary *)headerFields
196
				       forURL: (NSURL *)URL
197
{
1.2.8 by Yavor Doganov
Import upstream version 1.20.0
198
  NSEnumerator   *henum = [headerFields keyEnumerator];
199
  NSMutableArray *a = [NSMutableArray array];
200
  NSString *header;
1.4.3 by Yavor Doganov
Import upstream version 1.24.6
201
1.2.8 by Yavor Doganov
Import upstream version 1.20.0
202
  while ((header = [henum nextObject]))
203
    {
204
      NSMutableArray *suba 
205
	= [self _parseField: [headerFields objectForKey: header] 
206
		forHeader: header andURL: URL];
207
      if (suba)
208
	[a addObjectsFromArray: suba];
209
    }
210
  
1.2.3 by Hubert Chan
Import upstream version 1.13.0
211
  return a;
212
}
213
214
+ (NSDictionary *) requestHeaderFieldsWithCookies: (NSArray *)cookies
215
{
1.2.8 by Yavor Doganov
Import upstream version 1.20.0
216
  int version;
217
  NSString *field;
218
  NSHTTPCookie *ck;
219
  NSEnumerator *ckenum = [cookies objectEnumerator];
220
221
  if ([cookies count] == 0)
222
    {
223
      NSLog(@"NSHTTPCookie requestHeaderFieldWithCookies: empty array");
224
      return nil;
225
    }
226
  /* Assume these cookies all came from the same URL so we format based
227
     on the version of the first. */
228
  field = nil;
229
  version = [(NSHTTPCookie *)[cookies objectAtIndex: 0] version];
230
  if (version)
231
    field = @"$Version=\"1\"";
232
  while ((ck = [ckenum nextObject]))
233
    {
234
      NSString *str;
235
      str = [NSString stringWithFormat: @"%@=%@", [ck name], [ck value]];
236
      if (field)
237
	field = [field stringByAppendingFormat: @"; %@", str];
238
      else
239
	field = str;
240
      if (version && [ck path])
241
	field = [field stringByAppendingFormat: @"; $Path=\"%@\"", [ck path]];
242
    }
243
244
  return [NSDictionary dictionaryWithObject: field forKey: @"Cookie"];
1.2.3 by Hubert Chan
Import upstream version 1.13.0
245
}
246
247
- (NSString *) comment
248
{
249
  return [this->_properties objectForKey: NSHTTPCookieComment];
250
}
251
252
- (NSURL *) commentURL
253
{
254
  return [this->_properties objectForKey: NSHTTPCookieCommentURL];
255
}
256
257
- (void) dealloc
258
{
259
  if (this != 0)
260
    {
261
      RELEASE(this->_properties);
262
      NSZoneFree([self zone], this);
263
    }
264
  [super dealloc];
265
}
266
267
- (NSString *) domain
268
{
269
  return [this->_properties objectForKey: NSHTTPCookieDomain];
270
}
271
272
- (NSDate *) expiresDate
273
{
274
  return [this->_properties objectForKey: NSHTTPCookieExpires];
275
}
276
1.2.8 by Yavor Doganov
Import upstream version 1.20.0
277
- (BOOL) _isValidProperty: (NSString *)prop
278
{
279
  return ([prop length]
280
	  && [prop rangeOfString: @"\n"].location == NSNotFound);
281
}
282
1.2.3 by Hubert Chan
Import upstream version 1.13.0
283
- (id) initWithProperties: (NSDictionary *)properties
284
{
1.2.8 by Yavor Doganov
Import upstream version 1.20.0
285
  NSMutableDictionary *rawProps;
286
  if ((self = [super init]) == nil)
287
    return nil;
288
289
  /* Check a few values.  Based on Mac OS X tests. */
290
  if (![self _isValidProperty: [properties objectForKey: NSHTTPCookiePath]] 
1.4.3 by Yavor Doganov
Import upstream version 1.24.6
291
    || ![self _isValidProperty: [properties objectForKey: NSHTTPCookieDomain]]
292
    || ![self _isValidProperty: [properties objectForKey: NSHTTPCookieName]]
293
    || ![self _isValidProperty: [properties objectForKey: NSHTTPCookieValue]]
294
    )
1.2.3 by Hubert Chan
Import upstream version 1.13.0
295
    {
1.2.8 by Yavor Doganov
Import upstream version 1.20.0
296
      [self release];
297
      return nil;
1.2.3 by Hubert Chan
Import upstream version 1.13.0
298
    }
1.2.8 by Yavor Doganov
Import upstream version 1.20.0
299
300
  rawProps = [[properties mutableCopy] autorelease];
301
  if ([rawProps objectForKey: @"Created"] == nil)
1.4.3 by Yavor Doganov
Import upstream version 1.24.6
302
    {
303
      NSInteger seconds;
304
      NSDate	*now;
305
306
      /* Round to whole seconds, so that a serialization/deserialisation
307
       * cycle produces an identical object whic hcan be used to eliminate
308
       * duplicates.
309
       */
310
      seconds = [NSDate timeIntervalSinceReferenceDate];
311
      now = [NSDate dateWithTimeIntervalSinceReferenceDate: seconds]; 
312
      [rawProps setObject: now forKey: @"Created"];
313
    }
1.2.8 by Yavor Doganov
Import upstream version 1.20.0
314
  if ([rawProps objectForKey: NSHTTPCookieExpires] == nil
1.4.3 by Yavor Doganov
Import upstream version 1.24.6
315
    || [[rawProps objectForKey: NSHTTPCookieExpires] 
1.2.8 by Yavor Doganov
Import upstream version 1.20.0
316
		isKindOfClass: [NSDate class]] == NO)
1.4.3 by Yavor Doganov
Import upstream version 1.24.6
317
    {
318
      [rawProps setObject: [NSNumber numberWithBool: YES] 
319
		   forKey: NSHTTPCookieDiscard];
320
    }
1.2.8 by Yavor Doganov
Import upstream version 1.20.0
321
322
  this->_properties = [rawProps copy];
1.2.3 by Hubert Chan
Import upstream version 1.13.0
323
  return self;
324
}
325
326
- (BOOL) isSecure
327
{
328
  return [[this->_properties objectForKey: NSHTTPCookieSecure] boolValue];
329
}
330
331
- (BOOL) isSessionOnly
332
{
333
  return [[this->_properties objectForKey: NSHTTPCookieDiscard] boolValue];
334
}
335
336
- (NSString *) name
337
{
338
  return [this->_properties objectForKey: NSHTTPCookieName];
339
}
340
341
- (NSString *) path
342
{
343
  return [this->_properties objectForKey: NSHTTPCookiePath];
344
}
345
346
- (NSArray *) portList
347
{
348
  return [[this->_properties objectForKey: NSHTTPCookiePort]
349
    componentsSeparatedByString: @","];
350
}
351
352
- (NSDictionary *) properties
353
{
354
  return this->_properties;
355
}
356
357
- (NSString *) value
358
{
359
  return [this->_properties objectForKey: NSHTTPCookieValue];
360
}
361
1.3.3 by Gürkan Sengün
Import upstream version 1.19.3
362
- (NSUInteger) version
1.2.3 by Hubert Chan
Import upstream version 1.13.0
363
{
1.3.3 by Gürkan Sengün
Import upstream version 1.19.3
364
  return [[this->_properties objectForKey: NSHTTPCookieVersion] integerValue];
1.2.3 by Hubert Chan
Import upstream version 1.13.0
365
}
366
1.2.8 by Yavor Doganov
Import upstream version 1.20.0
367
- (NSString *) description
368
{
369
  return [NSString stringWithFormat: @"<NSHTTPCookie %p: %@=%@>", self,
370
		   [self name], [self value]];
371
}
372
1.4.3 by Yavor Doganov
Import upstream version 1.24.6
373
- (NSUInteger) hash
374
{
375
  return [[self properties] hash];
376
}
377
378
- (BOOL) isEqual: (id)other
379
{
380
  return [[other properties] isEqual: [self properties]];
381
}
1.2.8 by Yavor Doganov
Import upstream version 1.20.0
382
1.2.3 by Hubert Chan
Import upstream version 1.13.0
383
@end
384
1.2.8 by Yavor Doganov
Import upstream version 1.20.0
385
#define inrange(ch,min,max) ((ch)>=(min) && (ch)<=(max))
386
#define char2num(ch) \
387
inrange(ch,'0','9') \
388
? ((ch)-0x30) \
389
: (inrange(ch,'a','f') \
390
? ((ch)-0x57) : ((ch)-0x37))
391
392
typedef	struct	{
393
  const unsigned char	*ptr;
394
  unsigned	end;
395
  unsigned	pos;
396
  unsigned	lin;
397
  NSString	*err;
398
  int           opt;
399
  BOOL		key;
400
  BOOL		old;
401
} pldata;
402
403
/*
404
 *	Returns YES if there is any non-whitespace text remaining.
405
 */
406
static BOOL skipSpace(pldata *pld)
407
{
408
  unsigned char	c;
409
410
  while (pld->pos < pld->end)
411
    {
412
      c = pld->ptr[pld->pos];
413
414
      if (GS_IS_WHITESPACE(c) == NO)
415
	{
416
	  return YES;
417
	}
418
      if (c == '\n')
419
	{
420
	  pld->lin++;
421
	}
422
      pld->pos++;
423
    }
424
  pld->err = @"reached end of string";
425
  return NO;
426
}
427
428
static inline id parseQuotedString(pldata* pld)
429
{
430
  unsigned	start = ++pld->pos;
431
  unsigned	escaped = 0;
432
  unsigned	shrink = 0;
433
  BOOL		hex = NO;
434
  NSString	*obj;
435
436
  while (pld->pos < pld->end)
437
    {
438
      unsigned char	c = pld->ptr[pld->pos];
439
440
      if (escaped)
441
	{
442
	  if (escaped == 1 && c >= '0' && c <= '7')
443
	    {
444
	      escaped = 2;
445
	      hex = NO;
446
	    }
447
	  else if (escaped == 1 && (c == 'u' || c == 'U'))
448
	    {
449
	      escaped = 2;
450
	      hex = YES;
451
	    }
452
	  else if (escaped > 1)
453
	    {
454
	      if (hex && isxdigit(c))
455
		{
456
		  shrink++;
457
		  escaped++;
458
		  if (escaped == 6)
459
		    {
460
		      escaped = 0;
461
		    }
462
		}
463
	      else if (c >= '0' && c <= '7')
464
		{
465
		  shrink++;
466
		  escaped++;
467
		  if (escaped == 4)
468
		    {
469
		      escaped = 0;
470
		    }
471
		}
472
	      else
473
		{
474
		  pld->pos--;
475
		  escaped = 0;
476
		}
477
	    }
478
	  else
479
	    {
480
	      escaped = 0;
481
	    }
482
	}
483
      else
484
	{
485
	  if (c == '\\')
486
	    {
487
	      escaped = 1;
488
	      shrink++;
489
	    }
490
	  else if (c == '"')
491
	    {
492
	      break;
493
	    }
494
	}
495
      if (c == '\n')
496
	pld->lin++;
497
      pld->pos++;
498
    }
499
  if (pld->pos >= pld->end)
500
    {
501
      pld->err = @"reached end of string while parsing quoted string";
502
      return nil;
503
    }
504
  if (pld->pos - start - shrink == 0)
505
    {
506
      obj = @"";
507
    }
508
  else
509
    {
510
      unsigned	length;
511
      unichar	*chars;
512
      unichar	*temp = NULL;
513
      unsigned	int temp_length = 0;
514
      unsigned	j;
515
      unsigned	k;
516
517
      if (!GSToUnicode(&temp, &temp_length, &pld->ptr[start],
518
		       pld->pos - start, NSUTF8StringEncoding,
519
		       NSDefaultMallocZone(), 0))
520
	{
521
	  pld->err = @"invalid utf8 data while parsing quoted string";
522
	  return nil;
523
	}
524
      length = temp_length - shrink;
525
      chars = NSAllocateCollectable(sizeof(unichar) * length, 0);
526
      escaped = 0;
527
      hex = NO;
528
      for (j = 0, k = 0; j < temp_length; j++)
529
	{
530
	  unichar c = temp[j];
531
532
	  if (escaped)
533
	    {
534
	      if (escaped == 1 && c >= '0' && c <= '7')
535
		{
536
		  chars[k] = c - '0';
537
		  hex = NO;
538
		  escaped++;
539
		}
540
	      else if (escaped == 1 && (c == 'u' || c == 'U'))
541
		{
542
		  chars[k] = 0;
543
		  hex = YES;
544
		  escaped++;
545
		}
546
	      else if (escaped > 1)
547
		{
548
		  if (hex && isxdigit(c))
549
		    {
550
		      chars[k] <<= 4;
551
		      chars[k] |= char2num(c);
552
		      escaped++;
553
		      if (escaped == 6)
554
			{
555
			  escaped = 0;
556
			  k++;
557
			}
558
		    }
559
		  else if (c >= '0' && c <= '7')
560
		    {
561
		      chars[k] <<= 3;
562
		      chars[k] |= (c - '0');
563
		      escaped++;
564
		      if (escaped == 4)
565
			{
566
			  escaped = 0;
567
			  k++;
568
			}
569
		    }
570
		  else
571
		    {
572
		      escaped = 0;
573
		      j--;
574
		      k++;
575
		    }
576
		}
577
	      else
578
		{
579
		  escaped = 0;
580
		  switch (c)
581
		    {
582
		      case 'a' : chars[k] = '\a'; break;
583
		      case 'b' : chars[k] = '\b'; break;
584
		      case 't' : chars[k] = '\t'; break;
585
		      case 'r' : chars[k] = '\r'; break;
586
		      case 'n' : chars[k] = '\n'; break;
587
		      case 'v' : chars[k] = '\v'; break;
588
		      case 'f' : chars[k] = '\f'; break;
589
		      default  : chars[k] = c; break;
590
		    }
591
		  k++;
592
		}
593
	    }
594
	  else
595
	    {
596
	      chars[k] = c;
597
	      if (c == '\\')
598
		{
599
		  escaped = 1;
600
		}
601
	      else
602
		{
603
		  k++;
604
		}
605
	    }
606
	}
607
608
      NSZoneFree(NSDefaultMallocZone(), temp);
609
      length = k;
610
611
      obj = [NSString alloc];
612
      obj = [obj initWithCharactersNoCopy: chars
613
		 length: length
614
		 freeWhenDone: YES];
615
    }
616
  pld->pos++;
617
  return obj;
618
}
619
620
/* In cookies, keys are terminated by '=' and values are terminated by ';'
621
   or and EOL */
622
static inline id parseUnquotedString(pldata *pld, char endChar)
623
{
624
  unsigned	start = pld->pos;
625
  unsigned	i;
626
  unsigned	length;
627
  id		obj;
628
  unichar	*chars;
629
630
  while (pld->pos < pld->end)
631
    {
632
      if ((pld->ptr[pld->pos]) == endChar)
633
	break;
634
      pld->pos++;
635
    }
636
637
  length = pld->pos - start;
638
  chars = NSAllocateCollectable(sizeof(unichar) * length, 0);
639
  for (i = 0; i < length; i++)
640
    {
641
      chars[i] = pld->ptr[start + i];
642
    }
643
644
    {
645
      obj = [NSString alloc];
646
      obj = [obj initWithCharactersNoCopy: chars
647
				   length: length
648
			     freeWhenDone: YES];
649
    }
650
  return obj;
651
}
652
653
static BOOL
654
_setCookieKey(NSMutableDictionary *dict, NSString *key, NSString *value)
655
{
656
  if ([dict count] == 0)
657
    {
658
      /* This must be the name=value pair */
659
      if ([value length] == 0)
660
	return NO;
661
      [dict setObject: key forKey: NSHTTPCookieName];
662
      [dict setObject: value forKey: NSHTTPCookieValue];
663
      return YES;
664
    }
665
  if ([[key lowercaseString] isEqual: @"comment"])
666
    [dict setObject: value forKey: NSHTTPCookieComment];
667
  else if ([[key lowercaseString] isEqual: @"commenturl"])
668
    [dict setObject: value forKey: NSHTTPCookieCommentURL];
669
  else if ([[key lowercaseString] isEqual: @"discard"])
670
    [dict setObject: [NSNumber numberWithBool: YES] 
671
	     forKey: NSHTTPCookieDiscard];
672
  else if ([[key lowercaseString] isEqual: @"domain"])
673
    [dict setObject: value forKey: NSHTTPCookieDomain];
674
  else if ([[key lowercaseString] isEqual: @"expires"])
675
    {
676
      NSDate *expireDate;
677
      expireDate = [NSCalendarDate dateWithString: value
678
				calendarFormat: @"%a, %d-%b-%Y %I:%M:%S %Z"];
679
      if (expireDate)
680
        [dict setObject: expireDate forKey: NSHTTPCookieExpires];
681
    }
682
  else if ([[key lowercaseString] isEqual: @"max-age"])
683
    [dict setObject: value forKey: NSHTTPCookieMaximumAge];
684
  else if ([[key lowercaseString] isEqual: @"originurl"])
685
    [dict setObject: value forKey: NSHTTPCookieOriginURL];
686
  else if ([[key lowercaseString] isEqual: @"path"])
687
    [dict setObject: value forKey: NSHTTPCookiePath];
688
  else if ([[key lowercaseString] isEqual: @"port"])
689
    [dict setObject: value forKey: NSHTTPCookiePort];
690
  else if ([[key lowercaseString] isEqual: @"secure"])
691
    [dict setObject: [NSNumber numberWithBool: YES] 
692
	     forKey: NSHTTPCookieSecure];
693
  else if ([[key lowercaseString] isEqual: @"version"])
694
    [dict setObject: value forKey: NSHTTPCookieVersion];
695
  return YES;
696
}
697
698
static id
1.4.1 by Yavor Doganov
Import upstream version 1.22.0
699
GSPropertyListFromCookieFormat(NSString *string, int version)
1.2.8 by Yavor Doganov
Import upstream version 1.20.0
700
{
701
  NSMutableDictionary	*dict;
702
  pldata		_pld;
703
  pldata		*pld = &_pld;
704
  NSData		*d;
705
  BOOL			moreCharacters;
706
707
  /*
708
   * An empty string is a nil property list.
709
   */
710
  if ([string length] == 0)
711
    {
712
      return nil;
713
    }
714
715
  d = [string dataUsingEncoding: NSUTF8StringEncoding];
716
  NSCAssert(d, @"Couldn't get utf8 data from string.");
717
  _pld.ptr = (unsigned char*)[d bytes];
718
  _pld.pos = 0;
719
  _pld.end = [d length];
720
  _pld.err = nil;
721
  _pld.lin = 0;
722
  _pld.opt = 0;
723
  _pld.key = NO;
724
  _pld.old = YES;	// OpenStep style
725
726
  dict = [[NSMutableDictionary allocWithZone: NSDefaultMallocZone()]
727
    initWithCapacity: 0];
728
  while (skipSpace(pld) == YES)
729
    {
730
      id	key;
731
      id	val;
732
733
      if (pld->ptr[pld->pos] == '"')
734
	{
735
	  key = parseQuotedString(pld);
736
	}
737
      else
738
	{
739
	  key = parseUnquotedString(pld, '=');
740
	}
741
      if (key == nil)
742
	{
743
	  DESTROY(dict);
744
	  break;
745
	}
746
      moreCharacters = skipSpace(pld);
747
      if (moreCharacters == NO || pld->ptr[pld->pos] == ';')
748
	{
749
	  pld->pos++;
750
	  if (_setCookieKey(dict, key, @"") == NO)
751
	    {
752
	      pld->err = @"invalid cookie pair";
753
	      DESTROY(dict);
754
	    }
755
	  RELEASE(key);
756
	}
757
      else if (pld->ptr[pld->pos] == '=')
758
	{
759
	  pld->pos++;
760
	  if (skipSpace(pld) == NO)
761
	    {
762
	      RELEASE(key);
763
	      DESTROY(dict);
764
	      break;
765
	    }
766
	  if (pld->ptr[pld->pos] == '"')
767
	    {
768
	      val = parseQuotedString(pld);
769
	    }
770
	  else
771
	    {
772
	      val = parseUnquotedString(pld, ';');
773
	    }
774
	  if (val == nil)
775
	    {
776
	      RELEASE(key);
777
	      DESTROY(dict);
778
	      break;
779
	    }
1.4.1 by Yavor Doganov
Import upstream version 1.22.0
780
          skipSpace(pld);
1.2.8 by Yavor Doganov
Import upstream version 1.20.0
781
	  if (_setCookieKey(dict, key, val) == NO)
782
	    {
783
	      pld->err = @"invalid cookie pair";
784
	      DESTROY(dict);
785
	    }
786
	  RELEASE(key);
787
	  RELEASE(val);
788
	  if (pld->ptr[pld->pos] == ';')
789
	    {
790
	      pld->pos++;
791
	    }
792
	  else
793
	    {
794
	      break;
795
	    }
796
	}
797
      else
798
	{
799
	  pld->err = @"unexpected character (wanted '=' or ';')";
800
	  RELEASE(key);
801
	  DESTROY(dict);
802
	  break;
803
	}
804
    }
805
  if (dict == nil && _pld.err != nil)
806
    {
807
      RELEASE(dict);
808
      [NSException raise: NSGenericException
809
		  format: @"Parse failed at line %d (char %d) - %@",
810
	_pld.lin + 1, _pld.pos + 1, _pld.err];
811
    }
812
  return AUTORELEASE(dict);
813
}
814
815
/* Look for the comma that separates cookies. Commas can also occur in
816
   date strings, like "expires", but perhaps it can occur other places.
817
   For instance, the key/value pair  key=value1,value2 is not really
818
   valid, but should we handle it anyway? Definitely we should handle the
819
   perfectly normal case of:
820
821
   Set-Cookie: domain=test.com; expires=Thu, 12-Sep-2109 14:58:04 GMT;
822
     session=foo
823
   Set-Cookie: bar=baz
824
825
  which gets concatenated into something like:
826
827
  Set-Cookie: domain=test.com; expires=Thu, 12-Sep-2109 14:58:04 GMT;
828
    session=foo,bar=baz
829
830
*/
831
static NSRange 
832
GSRangeOfCookie(NSString *string)
833
{
834
  pldata		_pld;
835
  pldata		*pld = &_pld;
836
  NSData		*d;
837
  NSRange               range;
838
839
  /*
840
   * An empty string is a nil property list.
841
   */
842
  range = NSMakeRange(NSNotFound, NSNotFound);
843
  if ([string length] == 0)
844
    {
845
      return range;
846
    }
847
848
  d = [string dataUsingEncoding: NSUTF8StringEncoding];
849
  NSCAssert(d, @"Couldn't get utf8 data from string.");
850
  _pld.ptr = (unsigned char*)[d bytes];
851
  _pld.pos = 0;
852
  _pld.end = [d length];
853
  _pld.err = nil;
854
  _pld.lin = 0;
855
  _pld.opt = 0;
856
  _pld.key = NO;
857
  _pld.old = YES;	// OpenStep style
858
859
  while (skipSpace(pld) == YES)
860
    {
861
      if (pld->ptr[pld->pos] == ',')
862
	{
863
	  /* Look ahead for something that will tell us if this is a
864
	     separate cookie or not */
865
          unsigned saved_pos = pld->pos;
866
	  while (pld->ptr[pld->pos] != '=' && pld->ptr[pld->pos] != ';'
867
		&& pld->ptr[pld->pos] != ',' && pld->pos < pld->end )
868
	    pld->pos++;
869
	  if (pld->ptr[pld->pos] == '=')
870
	    {
871
	      /* Separate comment */
872
	      range = NSMakeRange(0, saved_pos-1);
873
	      break;
874
	    }
875
	  pld->pos = saved_pos;
876
	}
877
      pld->pos++;
878
    }
879
  if (range.location == NSNotFound)
880
    range = NSMakeRange(0, [string length]);
881
882
  return range;
883
}