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
22 #include <NGObjWeb/WOResponse.h>
23 #include <NGObjWeb/WORequest.h>
24 #include <NGObjWeb/WOCookie.h>
25 #include <NGExtensions/NSData+gzip.h>
26 #include <EOControl/EOControl.h>
29 @implementation WOResponse
31 static Class NSStringClass = Nil;
32 static unsigned char OWDefaultZipLevel = 3;
33 static unsigned int OWMinimumZipSize = 1024;
34 static BOOL dontZip = NO;
35 static BOOL debugZip = NO;
38 return [super version] + 1 /* v6 */;
41 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
42 static BOOL didInit = NO;
45 NSAssert2([super version] == 5,
46 @"invalid superclass (%@) version %i !",
47 NSStringFromClass([self superclass]), [super version]);
49 dontZip = [ud boolForKey:@"WODontZipResponse"];
50 debugZip = [ud boolForKey:@"WODebugZipResponse"];
53 + (WOResponse *)responseWithRequest:(WORequest *)_request {
54 return [[(WOResponse *)[self alloc] initWithRequest:_request] autorelease];
58 if ((self = [super init])) {
60 [self setHTTPVersion:@"HTTP/1.0"];
61 //[self setHeader:@"text/html" forKey:@"content-type"];
66 - (id)initWithRequest:(WORequest *)_request {
67 if ((self = [self init])) {
68 // don't fake being the request protocol, but rather stay to the truth ;-)
69 // [self setHTTPVersion:[_request httpVersion]];
76 - (void)setStatus:(unsigned int)_status {
77 self->status = _status;
79 - (unsigned int)status {
85 static __inline__ unsigned char *weekdayName(int dow) {
87 case 0: return "Sun"; case 1: return "Mon"; case 2: return "Tue";
88 case 3: return "Wed"; case 4: return "Thu"; case 5: return "Fri";
89 case 6: return "Sat"; case 7: return "Sun";
90 default: return "UNKNOWN DAY OF WEEK !";
93 static __inline__ unsigned char *monthName(int m) {
95 case 1: return "Jan"; case 2: return "Feb"; case 3: return "Mar";
96 case 4: return "Apr"; case 5: return "May"; case 6: return "Jun";
97 case 7: return "Jul"; case 8: return "Aug"; case 9: return "Sep";
98 case 10: return "Oct"; case 11: return "Nov"; case 12: return "Dec";
99 default: return "UNKNOWN MONTH !";
103 - (void)disableClientCaching {
105 OSX Prof: 7.1% of WOSession -appendToResponse !
107 /* HTTP/1.1 caching directive, prevents browser from caching dynamic pages */
108 static NSTimeZone *gmt = nil;
110 if (gmt == nil) gmt = [[NSTimeZone timeZoneWithAbbreviation:@"GMT"] retain];
112 [self logWithFormat:@"disabled client caching: %@ ..", self];
116 Set expire time to one hour before now to catch inconsitencies between
117 client and server time. Not using -description of NSCalendarDate to
118 avoid locales and to improve performance.
123 unsigned char buf[32];
125 now = [[NSCalendarDate alloc] initWithTimeIntervalSinceNow:-3600.0];
126 [now setTimeZone:gmt];
128 sprintf(buf, "%s, %02i %s %04i %02i:%02i:%02i GMT",
129 weekdayName([now dayOfWeek]),
131 monthName([now monthOfYear]),
132 [now yearOfCommonEra],
133 [now hourOfDay], [now minuteOfHour], [now secondOfMinute]);
136 s = [[NSString alloc] initWithCString:buf];
137 [self setHeader:s forKey:@"expires"];
140 [self setHeader:@"no-cache" forKey:@"cache-control"];
141 [self setHeader:@"no-cache" forKey:@"pragma"];
146 - (NSString *)contentString {
149 if (NSStringClass == Nil)
150 NSStringClass = [NSString class];
152 s = [[NSStringClass alloc] initWithData:[self content]
153 encoding:[self contentEncoding]];
154 return [s autorelease];
157 /* WOActionResults */
159 - (WOResponse *)generateResponse {
165 - (BOOL)shouldZipResponseToRequest:(WORequest *)_rq {
166 NSString *contentType;
167 NSString *acceptEncoding;
171 if (debugZip) [self logWithFormat:@"Zipping of response disabled"];
175 if ((body = [self content]) == nil)
177 if (![body isKindOfClass:[NSData class]])
179 if ([body length] < OWMinimumZipSize) {
182 @"content length is below minimum size for zipping (%i vs %i)",
183 [body length], OWMinimumZipSize];
188 contentType = [self headerForKey:@"content-type"];
190 if ([self headerForKey:@"content-encoding"] != nil) {
191 /* already applied some content-encoding */
193 [self logWithFormat:@"Do not zip, already has a 'content-encoding'!"];
196 if ([contentType hasPrefix:@"application"]) {
197 /* browser often seem to have problems with zipped bodies */
199 [self logWithFormat:@"Do not zip, is 'application/' MIME type."];
202 if ([contentType hasPrefix:@"image"]) {
203 /* do not zip images (usually already compressed ...) */
205 [self logWithFormat:@"Do not zip, is an image."];
212 acceptEncoding = [[_rq headerForKey:@"accept-encoding"] stringValue];
213 if (acceptEncoding == nil) {
216 @"Do not zip, browser sent no 'accept-encoding' header."];
220 // TODO: improve naive parsing of accept header
221 if ([acceptEncoding rangeOfString:@"gzip"].length == 0) {
224 @"Do not zip, browser does not understand 'gzip' encoding: %@",
232 - (NSData *)zipResponse {
233 NSMutableDictionary *ui;
235 NSData *zipped = nil;
239 if ((body = [self content]) == nil) return nil;
245 if ((zipped = [body gzipWithLevel:OWDefaultZipLevel]) == nil) {
247 [self logWithFormat:@"gzip refused to zip body ..."];
251 /* check if it's smaller */
252 if ((int)[zipped length] >= len) {
255 @"zipped length is larger than raw length (%i vs %i)",
256 [zipped length], len];
258 return body; /* it's not */
261 /* it is smaller .. */
265 @"zipped content %i => %i bytes (gain: %-.2g%%).",
266 len, [zipped length],
267 (double)(100.0 - (((double)[zipped length]) /
268 (((double)len) / 100.0)))];
272 [self setHeader:@"gzip" forKey:@"content-encoding"];
276 if ((ui = [[self userInfo] mutableCopy]) == nil)
277 ui = [[NSMutableDictionary alloc] initWithCapacity:2];
279 [ui setObject:zipped forKey:@"WOZippedContent"];
280 zlen = [NSNumber numberWithUnsignedInt:len];
281 [ui setObject:zlen forKey:@"WOResponseUnzippedLength"];
282 zlen = [NSNumber numberWithUnsignedInt:[zipped length]];
283 [ui setObject:zlen forKey:@"WOResponseZippedLength"];
285 [self setUserInfo:ui];
286 [ui release]; ui = nil;
287 [self setContent:zipped];
294 - (NSString *)description {
298 ms = [NSMutableString stringWithCapacity:64];
299 [ms appendFormat:@"<0x%08X[%@]:", self,
300 NSStringFromClass((Class)*(void**)self)];
302 [ms appendFormat:@" status=%i", [self status]];
303 [ms appendFormat:@" headers=%@", [self headers]];
305 if ((data = [self content])) {
306 if ([data length] == 0)
307 [ms appendString:@" empty-content"];
309 [ms appendFormat:@" content-size=%i", [data length]];
312 [ms appendString:@" no-content"];
314 [ms appendString:@">"];
318 @end /* WOResponse */