1/*
2  Copyright (C) 2000-2008 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#include <NGObjWeb/WOCookie.h>
23#include "common.h"
24
25@interface WOCookie(PrivateMethods)
26
27- (id)initWithName:(NSString *)_name value:(NSString *)_value
28  path:(NSString *)_path domain:(NSString *)_domain
29  expires:(NSDate *)_date
30  isSecure:(BOOL)_secure
31  httpOnly: (BOOL)_httpOnly;
32
33@end
34
35@implementation WOCookie
36
37static WOCookie *_parseCookie(const char *_bytes, unsigned _len);
38
39// abbr weekday, day-of-month, abbr-month, year hour:min:sec GMT
40static NSString *cookieDateFormat =  @"%a, %d-%b-%Y %H:%M:%S %Z";
41
42+ (id)cookieWithString:(NSString *)_string {
43  /* private method ! */
44  const char *utf8 = [_string UTF8String];
45  if (utf8 == NULL) return nil;
46  return _parseCookie(utf8, strlen(utf8));
47}
48
49+ (id)cookieWithName:(NSString *)_name value:(NSString *)_value {
50  return [[[self alloc] initWithName:_name value:_value
51                        path:nil domain:nil
52                        expires:nil isSecure:NO httpOnly:NO]
53                        autorelease];
54}
55
56+ (id)cookieWithName:(NSString *)_name value:(NSString *)_value
57  path:(NSString *)_path domain:(NSString *)_domain
58  expires:(NSDate *)_date
59  isSecure:(BOOL)_secure
60{
61  return [[[self alloc] initWithName:_name value:_value
62                        path:_path domain:_domain
63                        expires:_date isSecure:_secure httpOnly:NO]
64                        autorelease];
65}
66
67+ (id)cookieWithName:(NSString *)_name value:(NSString *)_value
68  path:(NSString *)_path domain:(NSString *)_domain
69  expires:(NSDate *)_date
70  isSecure:(BOOL)_secure
71  httpOnly: (BOOL)_httpOnly
72{
73  return [[[self alloc] initWithName:_name value:_value
74                        path:_path domain:_domain
75                        expires:_date isSecure:_secure httpOnly:_httpOnly]
76                        autorelease];
77}
78
79- (id)initWithName:(NSString *)_name value:(NSString *)_value
80  path:(NSString *)_path domain:(NSString *)_domain
81  expires:(NSDate *)_date
82  isSecure:(BOOL)_secure
83  httpOnly:(BOOL)_httpOnly
84{
85  if ((self = [super init]) != nil) {
86    NSZone *z = [self zone];
87    self->name         = [_name   copyWithZone:z];
88    self->value        = [_value  copyWithZone:z];
89    self->path         = [_path   copyWithZone:z];
90    self->domainName   = [_domain copyWithZone:z];
91    self->expireDate   = [_date   retain]; // TBD: should be copy?
92    self->onlyIfSecure = _secure;
93    self->httpOnly     = _httpOnly;
94  }
95  return self;
96}
97
98- (void)dealloc {
99  [self->name       release];
100  [self->value      release];
101  [self->expireDate release];
102  [self->path       release];
103  [self->domainName release];
104  [super dealloc];
105}
106
107/* accessors */
108
109- (NSString *)cookieName {
110  /* ?? */
111  return self->name;
112}
113
114- (void)setName:(NSString *)_name {
115  ASSIGNCOPY(self->name, _name);
116}
117- (NSString *)name {
118  return self->name;
119}
120
121- (void)setValue:(NSString *)_value {
122  ASSIGNCOPY(self->value, _value);
123}
124- (NSString *)value {
125  return self->value;
126}
127
128- (void)setPath:(NSString *)_path {
129  ASSIGNCOPY(self->path, _path);
130}
131- (NSString *)path {
132  return self->path;
133}
134
135- (void)setExpires:(NSDate *)_date {
136  ASSIGNCOPY(self->expireDate, _date);
137}
138- (NSDate *)expires {
139  return self->expireDate;
140}
141
142- (void)setDomain:(NSString *)_domain {
143  ASSIGNCOPY(self->domainName, _domain);
144}
145- (NSString *)domain {
146  return self->domainName;
147}
148
149- (void)setIsSecure:(BOOL)_flag {
150  self->onlyIfSecure = _flag ? YES : NO;
151}
152- (BOOL)isSecure {
153  return self->onlyIfSecure;
154}
155
156- (void)setHttpOnly:(BOOL)_flag {
157  self->httpOnly = _flag ? YES : NO;
158}
159- (BOOL)isHttpOnly {
160  return self->httpOnly;
161}
162
163- (NSDate *)expireDate {
164  // DEPRECATED
165  return self->expireDate;
166}
167
168/* NSCopying */
169
170- (id)copyWithZone:(NSZone *)_zone {
171  return [[WOCookie alloc] initWithName:self->name value:self->value
172			   path:self->path domain:self->domainName
173			   expires:self->expireDate
174			   isSecure:self->onlyIfSecure
175                           httpOnly:self->httpOnly];
176}
177
178/* description */
179
180- (NSString *)headerString {
181  return [@"set-cookie: " stringByAppendingString:[self stringValue]];
182}
183
184- (NSString *)stringValue {
185  NSMutableString *str;
186
187  str = [NSMutableString stringWithCapacity:512];
188  [str appendString:[self->name stringByEscapingURL]];
189  [str appendString:@"="];
190  [str appendString:[[self->value stringValue] stringByEscapingURL]];
191
192  if (self->expireDate) {
193    static NSTimeZone *gmt = nil;
194    static NSMutableDictionary *localeDict = nil;
195    NSString *s;
196    if (gmt == nil)
197      gmt = [[NSTimeZone timeZoneWithAbbreviation:@"GMT"] retain];
198    if (localeDict == nil)
199      {
200        localeDict = [NSMutableDictionary new];
201
202        [localeDict setObject: [NSArray arrayWithObjects: @"Jan", @"Feb",
203                                        @"Mar", @"Apr", @"May", @"Jun",
204                                        @"Jul", @"Aug", @"Sep", @"Oct",
205                                        @"Nov", @"Dec", nil]
206                       forKey: @"NSShortMonthNameArray"];
207        [localeDict setObject: [NSArray arrayWithObjects: @"Sun", @"Mon",
208                                        @"Tue", @"Wed", @"Thu", @"Fri",
209                                        @"Sat", nil]
210                       forKey: @"NSShortWeekDayNameArray"];
211      }
212
213    // TODO: replace, -descriptionWithCalendarFormat is *slow*
214    s = [self->expireDate descriptionWithCalendarFormat:cookieDateFormat
215                          timeZone:gmt
216	                  locale:localeDict];
217
218    [str appendString:@"; expires="];
219    [str appendString:s];
220  }
221  if (self->path) {
222    [str appendString:@"; path="];
223    [str appendString:self->path];
224  }
225  if (self->domainName) {
226    [str appendString:@"; domain="];
227    [str appendString:self->domainName];
228  }
229  if (self->onlyIfSecure)
230    [str appendString:@"; secure"];
231
232  if (self->httpOnly)
233    [str appendString:@"; HttpOnly"];
234
235  return str;
236}
237
238- (NSString *)description {
239  NSMutableString *str;
240
241  str = [NSMutableString stringWithCapacity:128];
242  [str appendFormat:@"<%@[0x%p]: name=%@ value=%@",
243         NSStringFromClass([self class]), self,
244         self->name, self->value];
245
246  if (self->expireDate) {
247    [str appendString:@" expires="];
248    [str appendString:[self->expireDate description]];
249  }
250
251  if (self->path) {
252    [str appendString:@" path="];
253    [str appendString:self->path];
254  }
255  if (self->domainName) {
256    [str appendString:@" domain="];
257    [str appendString:self->domainName];
258  }
259  if (self->onlyIfSecure)
260    [str appendString:@" secure"];
261
262  if (self->httpOnly)
263    [str appendString:@" HttpOnly"];
264
265  [str appendString:@">"];
266
267  return str;
268}
269
270/* cookie parsing */
271
272static WOCookie *_parseCookie(const char *_bytes, unsigned _len) {
273  WOCookie *cookie   = nil;
274  unsigned pos, toGo;
275
276  for (pos = 0, toGo = _len; (toGo > 0) && (_bytes[pos] != '='); toGo--, pos++)
277    ;
278
279  if (toGo > 0) {
280    NSString *name   = nil;
281    NSString *value  = nil;
282
283    // NSLog(@"pos=%i toGo=%i", pos, toGo);
284
285    name  = [[NSString alloc]
286                       initWithCString:_bytes
287                       length:pos];
288    value = [[NSString alloc]
289                       initWithCString:&(_bytes[pos + 1])
290                       length:(toGo - 1)];
291
292    //NSLog(@"pair='%@'", [NSString stringWithCString:_bytes length:_len]);
293    //NSLog(@"name='%@' value='%@'", name, value);
294
295    if ((name == nil) || (value == nil)) {
296      NSLog(@"ERROR: invalid cookie pair%s%s: %@",
297            value ? "" : ", no value",
298            name  ? "" : ", no name",
299            [NSString stringWithCString:_bytes length:_len]);
300      [name  release];
301      [value release];
302      return nil;
303    }
304    else {
305      cookie = [WOCookie cookieWithName:[name stringByUnescapingURL]
306                         value:[value stringByUnescapingURL]];
307    }
308
309    [name  release];  name  = nil;
310    [value release]; value = nil;
311  }
312#if DEBUG
313  else {
314    NSLog(@"ERROR(%s:%i): invalid cookie pair: %@",
315          __PRETTY_FUNCTION__, __LINE__,
316          [NSString stringWithCString:_bytes length:_len]);
317  }
318#endif
319  return cookie;
320}
321
322@end /* WOCookie */
323