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