]> err.no Git - sope/blob - sope-appserver/NGObjWeb/WOResponse.m
increased element nesting depth
[sope] / sope-appserver / NGObjWeb / WOResponse.m
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 #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>
27 #include "common.h"
28
29 @implementation WOResponse
30
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;
36
37 + (int)version {
38   return [super version] + 1 /* v6 */;
39 }
40 + (void)initialize {
41   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
42   static BOOL didInit = NO;
43   if (didInit) return;
44   didInit = YES;
45   NSAssert2([super version] == 5,
46             @"invalid superclass (%@) version %i !",
47             NSStringFromClass([self superclass]), [super version]);
48   
49   dontZip  = [ud boolForKey:@"WODontZipResponse"];
50   debugZip = [ud boolForKey:@"WODebugZipResponse"];
51 }
52
53 + (WOResponse *)responseWithRequest:(WORequest *)_request {
54   return [[(WOResponse *)[self alloc] initWithRequest:_request] autorelease];
55 }
56
57 - (id)init {
58   if ((self = [super init])) {
59     [self setStatus:200];
60     [self setHTTPVersion:@"HTTP/1.0"];
61     //[self setHeader:@"text/html" forKey:@"content-type"];
62   }
63   return self;
64 }
65
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]];
70   }
71   return self;
72 }
73
74 /* HTTP */
75
76 - (void)setStatus:(unsigned int)_status {
77   self->status = _status;
78 }
79 - (unsigned int)status {
80   return self->status;
81 }
82
83 /* client caching */
84
85 static __inline__ unsigned char *weekdayName(int dow) {
86   switch (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 !";
91   }
92 }
93 static __inline__ unsigned char *monthName(int m) {
94   switch (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 !";
100   }
101 }
102
103 - (void)disableClientCaching {
104   /*
105     OSX Prof: 7.1% of WOSession -appendToResponse !
106   */
107   /* HTTP/1.1 caching directive, prevents browser from caching dynamic pages */
108   static NSTimeZone *gmt = nil;
109   
110   if (gmt == nil) gmt = [[NSTimeZone timeZoneWithAbbreviation:@"GMT"] retain];
111 #if DEBUG && 0
112   [self logWithFormat:@"disabled client caching: %@ ..", self];
113 #endif
114   
115   /*
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.
119   */
120   {
121     NSCalendarDate *now;
122     NSString *s;
123     unsigned char buf[32];
124     
125     now = [[NSCalendarDate alloc] initWithTimeIntervalSinceNow:-3600.0];
126     [now setTimeZone:gmt];
127     
128     sprintf(buf, "%s, %02i %s %04i %02i:%02i:%02i GMT",
129             weekdayName([now dayOfWeek]),
130             [now dayOfMonth], 
131             monthName([now monthOfYear]),
132             [now yearOfCommonEra],
133             [now hourOfDay], [now minuteOfHour], [now secondOfMinute]);
134     [now release];
135     
136     s = [[NSString alloc] initWithCString:buf];
137     [self setHeader:s forKey:@"expires"];
138     [s release];
139   }
140   [self setHeader:@"no-cache" forKey:@"cache-control"];
141   [self setHeader:@"no-cache" forKey:@"pragma"];
142 }
143
144 /* WO methods */
145
146 - (NSString *)contentString {
147   NSString *s;
148   
149   if (NSStringClass == Nil)
150     NSStringClass = [NSString class];
151   
152   s = [[NSStringClass alloc] initWithData:[self content]
153                              encoding:[self contentEncoding]];
154   return [s autorelease];
155 }
156
157 /* WOActionResults */
158
159 - (WOResponse *)generateResponse {
160   return self;
161 }
162
163 /* zipping */
164
165 - (BOOL)shouldZipResponseToRequest:(WORequest *)_rq {
166   NSString *contentType;
167   NSString *acceptEncoding;
168   id       body;
169   
170   if (dontZip) {
171     if (debugZip) [self logWithFormat:@"Zipping of response disabled"];
172     return NO;
173   }
174   
175   if ((body = [self content]) == nil)  
176     return NO;
177   if (![body isKindOfClass:[NSData class]])
178     return NO;
179   if ([body length] < OWMinimumZipSize) {
180     if (debugZip) {
181       [self logWithFormat:
182               @"content length is below minimum size for zipping (%i vs %i)",
183               [body length], OWMinimumZipSize];
184     }
185     return NO;
186   }
187   
188   contentType = [self headerForKey:@"content-type"];
189   
190   if ([self headerForKey:@"content-encoding"] != nil) {
191     /* already applied some content-encoding */
192     if (debugZip)
193       [self logWithFormat:@"Do not zip, already has a 'content-encoding'!"];
194     return NO;
195   }
196   if ([contentType hasPrefix:@"application"]) {
197     /* browser often seem to have problems with zipped bodies */
198     if (debugZip)
199       [self logWithFormat:@"Do not zip, is 'application/' MIME type."];
200     return NO;
201   }
202   if ([contentType hasPrefix:@"image"]) {
203     /* do not zip images (usually already compressed ...) */
204     if (debugZip)
205       [self logWithFormat:@"Do not zip, is an image."];
206     return NO;
207   }
208   
209   if (_rq == nil)
210     return YES;
211   
212   acceptEncoding = [[_rq headerForKey:@"accept-encoding"] stringValue];
213   if (acceptEncoding == nil) {
214     if (debugZip) {
215       [self logWithFormat:
216               @"Do not zip, browser sent no 'accept-encoding' header."];
217     }
218     return NO;
219   }
220   // TODO: improve naive parsing of accept header
221   if ([acceptEncoding rangeOfString:@"gzip"].length == 0) {
222     if (debugZip) {
223       [self logWithFormat:
224               @"Do not zip, browser does not understand 'gzip' encoding: %@",
225               acceptEncoding];
226     }
227     return NO;
228   }
229   return YES;
230 }
231
232 - (NSData *)zipResponse {
233   NSMutableDictionary *ui;
234   NSNumber *zlen;
235   NSData *zipped = nil;
236   int    len;
237   id     body;
238   
239   if ((body = [self content]) == nil) return nil;
240   
241   len = [body length];
242
243   /* zip body data */
244   
245   if ((zipped = [body gzipWithLevel:OWDefaultZipLevel]) == nil) {
246     if (debugZip)
247       [self logWithFormat:@"gzip refused to zip body ..."];
248     return body;
249   }
250   
251   /* check if it's smaller */
252   if ((int)[zipped length] >= len) {
253     if (debugZip) {
254       [self logWithFormat:
255               @"zipped length is larger than raw length (%i vs %i)",
256               [zipped length], len];
257     }
258     return body; /* it's not */
259   }
260   
261   /* it is smaller .. */
262       
263   if (debugZip) {
264     [self logWithFormat:
265             @"zipped content %i => %i bytes (gain: %-.2g%%).",
266             len, [zipped length],
267             (double)(100.0 - (((double)[zipped length]) /
268                               (((double)len) / 100.0)))];
269   }
270   
271   body = zipped;
272   [self setHeader:@"gzip" forKey:@"content-encoding"];
273
274   /* statistics */
275   
276   if ((ui = [[self userInfo] mutableCopy]) == nil)
277     ui = [[NSMutableDictionary alloc] initWithCapacity:2];
278   
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"];
284   
285   [self setUserInfo:ui];
286   [ui release]; ui = nil;
287   [self setContent:zipped];
288   
289   return zipped;
290 }
291
292 /* description */
293
294 - (NSString *)description {
295   NSMutableString *ms;
296   NSData *data;
297   
298   ms = [NSMutableString stringWithCapacity:64];
299   [ms appendFormat:@"<0x%08X[%@]:", self,
300         NSStringFromClass((Class)*(void**)self)];
301   
302   [ms appendFormat:@" status=%i",  [self status]];
303   [ms appendFormat:@" headers=%@", [self headers]];
304
305   if ((data = [self content])) {
306     if ([data length] == 0)
307       [ms appendString:@" empty-content"];
308     else
309       [ms appendFormat:@" content-size=%i", [data length]];
310   }
311   else
312     [ms appendString:@" no-content"];
313   
314   [ms appendString:@">"];
315   return ms;
316 }
317
318 @end /* WOResponse */