2 Copyright (C) 2000-2004 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
6 OGo 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
11 OGo 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.
16 You should have received a copy of the GNU Lesser General Public
17 License along with OGo; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
23 #include <NGObjWeb/WOResponse.h>
24 #include <NGObjWeb/WORequest.h>
25 #include <NGObjWeb/WOCookie.h>
26 #include <NGExtensions/NSData+gzip.h>
27 #include <EOControl/EOControl.h>
30 @implementation WOResponse
32 static Class NSStringClass = Nil;
33 static unsigned char OWDefaultZipLevel = 3;
34 static unsigned int OWMinimumZipSize = 1024;
35 static BOOL dontZip = NO;
36 static BOOL debugZip = NO;
39 return [super version] + 1 /* v5 */;
42 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
43 static BOOL didInit = NO;
46 NSAssert2([super version] == 4,
47 @"invalid superclass (%@) version %i !",
48 NSStringFromClass([self superclass]), [super version]);
50 dontZip = [ud boolForKey:@"WODontZipResponse"];
51 debugZip = [ud boolForKey:@"WODebugZipResponse"];
54 + (WOResponse *)responseWithRequest:(WORequest *)_request {
55 return [[(WOResponse *)[self alloc] initWithRequest:_request] autorelease];
59 if ((self = [super init])) {
61 [self setHTTPVersion:@"HTTP/1.0"];
62 //[self setHeader:@"text/html" forKey:@"content-type"];
67 - (id)initWithRequest:(WORequest *)_request {
68 if ((self = [self init])) {
69 // don't fake being the request protocol, but rather stay to the truth ;-)
70 // [self setHTTPVersion:[_request httpVersion]];
77 - (void)setStatus:(unsigned int)_status {
78 self->status = _status;
80 - (unsigned int)status {
86 static __inline__ unsigned char *weekdayName(int dow) {
88 case 0: return "Sun"; case 1: return "Mon"; case 2: return "Tue";
89 case 3: return "Wed"; case 4: return "Thu"; case 5: return "Fri";
90 case 6: return "Sat"; case 7: return "Sun";
91 default: return "UNKNOWN DAY OF WEEK !";
94 static __inline__ unsigned char *monthName(int m) {
96 case 1: return "Jan"; case 2: return "Feb"; case 3: return "Mar";
97 case 4: return "Apr"; case 5: return "May"; case 6: return "Jun";
98 case 7: return "Jul"; case 8: return "Aug"; case 9: return "Sep";
99 case 10: return "Oct"; case 11: return "Nov"; case 12: return "Dec";
100 default: return "UNKNOWN MONTH !";
104 - (void)disableClientCaching {
106 OSX Prof: 7.1% of WOSession -appendToResponse !
108 /* HTTP/1.1 caching directive, prevents browser from caching dynamic pages */
109 static NSTimeZone *gmt = nil;
111 if (gmt == nil) gmt = [[NSTimeZone timeZoneWithAbbreviation:@"GMT"] retain];
113 [self logWithFormat:@"disabled client caching: %@ ..", self];
117 Set expire time to one hour before now to catch inconsitencies between
118 client and server time. Not using -description of NSCalendarDate to
119 avoid locales and to improve performance.
124 unsigned char buf[32];
126 now = [[NSCalendarDate alloc] initWithTimeIntervalSinceNow:-3600.0];
127 [now setTimeZone:gmt];
129 sprintf(buf, "%s, %02i %s %04i %02i:%02i:%02i GMT",
130 weekdayName([now dayOfWeek]),
132 monthName([now monthOfYear]),
133 [now yearOfCommonEra],
134 [now hourOfDay], [now minuteOfHour], [now secondOfMinute]);
137 s = [[NSString alloc] initWithCString:buf];
138 [self setHeader:s forKey:@"expires"];
141 [self setHeader:@"no-cache" forKey:@"cache-control"];
142 [self setHeader:@"no-cache" forKey:@"pragma"];
147 - (NSString *)contentString {
150 if (NSStringClass == Nil)
151 NSStringClass = [NSString class];
153 s = [[NSStringClass alloc] initWithData:[self content]
154 encoding:[self contentEncoding]];
155 return [s autorelease];
158 /* WOActionResults */
160 - (WOResponse *)generateResponse {
166 - (BOOL)shouldZipResponseToRequest:(WORequest *)_rq {
167 NSString *contentType;
168 NSString *acceptEncoding;
172 if (debugZip) [self logWithFormat:@"Zipping of response disabled"];
176 if ((body = [self content]) == nil)
178 if (![body isKindOfClass:[NSData class]])
180 if ([body length] < OWMinimumZipSize) {
183 @"content length is below minimum size for zipping (%i vs %i)",
184 [body length], OWMinimumZipSize];
189 contentType = [self headerForKey:@"content-type"];
191 if ([self headerForKey:@"content-encoding"] != nil) {
192 /* already applied some content-encoding */
194 [self logWithFormat:@"Do not zip, already has a 'content-encoding'!"];
197 if ([contentType hasPrefix:@"application"]) {
198 /* browser often seem to have problems with zipped bodies */
200 [self logWithFormat:@"Do not zip, is 'application/' MIME type."];
203 if ([contentType hasPrefix:@"image"]) {
204 /* do not zip images (usually already compressed ...) */
206 [self logWithFormat:@"Do not zip, is an image."];
213 acceptEncoding = [[_rq headerForKey:@"accept-encoding"] stringValue];
214 if (acceptEncoding == nil) {
217 @"Do not zip, browser sent no 'accept-encoding' header."];
221 // TODO: improve naive parsing of accept header
222 if ([acceptEncoding rangeOfString:@"gzip"].length == 0) {
225 @"Do not zip, browser does not understand 'gzip' encoding: %@",
233 - (NSData *)zipResponse {
234 NSMutableDictionary *ui;
236 NSData *zipped = nil;
240 if ((body = [self content]) == nil) return nil;
246 if ((zipped = [body gzipWithLevel:OWDefaultZipLevel]) == nil) {
248 [self logWithFormat:@"gzip refused to zip body ..."];
252 /* check if it's smaller */
253 if ((int)[zipped length] >= len) {
256 @"zipped length is larger than raw length (%i vs %i)",
257 [zipped length], len];
259 return body; /* it's not */
262 /* it is smaller .. */
266 @"zipped content %i => %i bytes (gain: %-.2g%%).",
267 len, [zipped length],
268 (double)(100.0 - (((double)[zipped length]) /
269 (((double)len) / 100.0)))];
273 [self setHeader:@"gzip" forKey:@"content-encoding"];
277 if ((ui = [[self userInfo] mutableCopy]) == nil)
278 ui = [[NSMutableDictionary alloc] initWithCapacity:2];
280 [ui setObject:zipped forKey:@"WOZippedContent"];
281 zlen = [NSNumber numberWithUnsignedInt:len];
282 [ui setObject:zlen forKey:@"WOResponseUnzippedLength"];
283 zlen = [NSNumber numberWithUnsignedInt:[zipped length]];
284 [ui setObject:zlen forKey:@"WOResponseZippedLength"];
286 [self setUserInfo:ui];
287 [ui release]; ui = nil;
288 [self setContent:zipped];
295 - (NSString *)description {
299 ms = [NSMutableString stringWithCapacity:64];
300 [ms appendFormat:@"<0x%08X[%@]:", self,
301 NSStringFromClass((Class)*(void**)self)];
303 [ms appendFormat:@" status=%i", [self status]];
304 [ms appendFormat:@" headers=%@", [self headers]];
306 if ((data = [self content])) {
307 if ([data length] == 0)
308 [ms appendString:@" empty-content"];
310 [ms appendFormat:@" content-size=%i", [data length]];
313 [ms appendString:@" no-content"];
315 [ms appendString:@">"];
319 @end /* WOResponse */