libs-gsweb/GSWeb/GSWResponse.m
Sebastian Reitenbach d4cc39b551 * nearly every file
get rid of RCS_ID

as shortly discussed with David and Manuel



git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gsweb/trunk@37929 72102866-910b-0410-8b05-ffd578937521
2014-05-30 18:28:04 +00:00

645 lines
20 KiB
Objective-C

/** GSWResponse.m - <title>GSWeb: Class GSWResponse</title>
Copyright (C) 1999-2004 Free Software Foundation, Inc.
Written by: Manuel Guesdon <mguesdon@orange-concept.com>
Date: Jan 1999
$Revision$
$Date$
$Id$
This file is part of the GNUstep Web Library.
<license>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
</license>
**/
#include "config.h"
#include "GSWeb.h"
#include "NSData+Compress.h"
#include "GSWPrivate.h"
#include <GNUstepBase/NSObject+GNUstepBase.h>
//====================================================================
@implementation GSWResponse
static NSString* disabledCacheDateString=nil;
static NSArray* cacheControlHeaderValues=nil;
static NSArray* compressableContentTypesCache=nil;
static SEL appendTagAttributeValueEscapingHTMLAttributeValueSEL = NULL;
//====================================================================
/** Fill impsPtr structure with IMPs for response **/
void GetGSWResponseIMPs(GSWResponseIMPs* impsPtr,GSWResponse* aResponse)
{
memset(impsPtr,0,sizeof(GSWResponseIMPs));
NSCAssert(aResponse,@"No response");
impsPtr->_appendTagAttributeValueEscapingHTMLAttributeValueIMP =
[aResponse methodForSelector:appendTagAttributeValueEscapingHTMLAttributeValueSEL];
};
//====================================================================
/** functions to accelerate calls of frequently used GSWResponse methods **/
//--------------------------------------------------------------------
void GSWResponse_appendTagAttributeValueEscapingHTMLAttributeValue(GSWResponse* aResponse,
NSString* aString,
id value,
BOOL escaping)
{
if (aResponse)
{
(*(aResponse->_selfIMPs._appendTagAttributeValueEscapingHTMLAttributeValueIMP))
(aResponse,appendTagAttributeValueEscapingHTMLAttributeValueSEL,aString,value,escaping);
};
}
//--------------------------------------------------------------------
+(void)initialize
{
if (self==[GSWResponse class])
{
// So cache date stamp will be set to earlier date
ASSIGN(disabledCacheDateString,[[NSCalendarDate date] htmlDescription]);
// Other cache control headers
ASSIGN(cacheControlHeaderValues,([NSArray arrayWithObjects:@"private",
@"no-cache",
@"no-store",
@"must-revalidate",
@"max-age=0",
nil]));
appendTagAttributeValueEscapingHTMLAttributeValueSEL = @selector(_appendTagAttribute:value:escapingHTMLAttributeValue:);
ASSIGN(compressableContentTypesCache, ([NSArray arrayWithObjects:@"text/html",
@"text/plain",
@"text/css",
@"text/csv",
@"text/xml",
@"text/rtf",
@"text/calendar",
@"text/x-vcalendar",
@"text/enriched",
@"text/directory",
@"image/svg+xml",
nil]));
};
};
+ (NSArray*) compressableContentTypes
{
return compressableContentTypesCache;
}
+ (void) setCompressableContentTypes:(NSArray*) cTypes
{
ASSIGN(compressableContentTypesCache,cTypes);
}
//--------------------------------------------------------------------
// init
-(id)init
{
//OK
if ((self=[super init]))
{
GetGSWResponseIMPs(&_selfIMPs,self);
_canDisableClientCaching=YES;
_status=200;
};
return self;
};
//--------------------------------------------------------------------
-(void)dealloc
{
// GSWLogAssertGood(self);
// NSDebugFLog(@"dealloc Response %p",self);
// NSDebugFLog0(@"Release Response contentFaults");
DESTROY(_contentFaults);
// NSDebugFLog0(@"Release Response contentData");
DESTROY(_contentStreamFileHandle);
// NSDebugFLog0(@"Release Response");
[super dealloc];
};
//--------------------------------------------------------------------
-(id)copyWithZone:(NSZone*)zone
{
GSWResponse* clone = (GSWResponse*)[super copyWithZone:zone];
if (clone)
{
clone->_status=_status;
ASSIGNCOPY(clone->_contentFaults,_contentFaults);
clone->_isClientCachingDisabled=_isClientCachingDisabled;
clone->_canDisableClientCaching=_canDisableClientCaching;
clone->_contentFaultsHaveBeenResolved=_contentFaultsHaveBeenResolved;
};
return clone;
};
//--------------------------------------------------------------------
// willSend
//NDFN
-(void)willSend
{
NSAssert(_isFinalizeInContextHasBeenCalled,@"GSWResponse _finalizeInContext: not called");
};
//--------------------------------------------------------------------
-(void)forceFinalizeInContext
{
_isFinalizeInContextHasBeenCalled=YES;
};
//--------------------------------------------------------------------
// setStatus:
//sets http status
-(void)setStatus:(unsigned int)status
{
_status=status;
};
//--------------------------------------------------------------------
// status
-(unsigned int)status
{
return _status;
};
//--------------------------------------------------------------------
// should be called before finalizeInContext
-(void)setCanDisableClientCaching:(BOOL)yn
{
_canDisableClientCaching=yn;
};
//--------------------------------------------------------------------
-(void)disableClientCaching
{
if (!_isClientCachingDisabled && _canDisableClientCaching)
{
[self setHeader:disabledCacheDateString
forKey:@"Date"];
[self setHeader:disabledCacheDateString
forKey:@"Expires"];
[self setHeader:@"no-cache"
forKey:@"Pragma"];
if([[GSWApp class] _allowsCacheControlHeader])
[self setHeaders:cacheControlHeaderValues
forKey:@"cache-control"];
_isClientCachingDisabled=YES;
};
};
//--------------------------------------------------------------------
-(NSString*)description
{
NSString* description=nil;
description=[NSString stringWithFormat:
@"<%s %p - httpVersion=%@ status=%d headers=%p contentFaults=%p contentData=%p contentEncoding=%d userInfo=%p>",
object_getClassName(self),
(void*)self,
_httpVersion,
_status,
(void*)_headers,
(void*)_contentFaults,
(void*)_contentData,
(int)_contentEncoding,
(void*)_userInfo];
return description;
}
// it seems that in WO, this is a class method, which makes no sense
- (void) _redirectResponse:(NSString *) location contentString:(NSString *) content
{
GSWResponse_appendContentString(self, content);
[self setStatus:302];
[self setHeader:location
forKey:GSWHTTPHeader_Location];
[self setHeader:@"YES"
forKey:GSWHTTPHeader_RefusingRedirection[1]];
}
//--------------------------------------------------------------------
//NDFN
-(BOOL)isFinalizeInContextHasBeenCalled
{
return _isFinalizeInContextHasBeenCalled;
};
- (BOOL)_browserSupportsCompression:(GSWRequest *)aRequest
{
NSString *value;
NSRange range;
if (aRequest && ((value = [aRequest headerForKey:GSWHTTPHeader_AcceptEncoding]))) {
range = [value rangeOfString:@"gzip" options:0];
if (range.length) {
return YES;
}
}
return NO;
}
-(void)_finalizeContentEncodingInContext:(GSWContext*)aContext
{
#ifdef HAVE_LIBZ
NSUInteger dataLength=0;
dataLength=[self _contentLength];
if (dataLength>0) {
/*
NSString * eTagString = [NSString stringWithFormat:@"%lx",
(unsigned long) [_contentData hash]];
[self setHeader:eTagString
forKey:@"ETag"];
*/
}
// Now we see if we can gzip the content
// it does not make sense to compress data less than 150 bytes.
if ((dataLength > 150) && ([self _browserSupportsCompression:[aContext request]]))
{
NSString* contentType=[self headerForKey:GSWHTTPHeader_ContentType];
NSString* contentEncoding=[self headerForKey:GSWHTTPHeader_ContentEncoding];
if ((contentEncoding) || (!compressableContentTypesCache)) {
return;
}
// only compress if we know it makes sense
if ([compressableContentTypesCache containsObject:contentType])
{
#ifdef DEBUG
NSDate* compressStartDate=[NSDate date];
#endif
NSData* content=[self content];
NSData* compressedData=[content deflate];
if (compressedData)
{
#ifdef DEBUG
NSDate* compressStopDate=[NSDate date];
NSString* sizeInfoHeader=[NSString stringWithFormat:@"deflate from %d to %d in %0.3f s",
dataLength,
[compressedData length],
[compressStopDate timeIntervalSinceDate:compressStartDate]];
[self setHeader:sizeInfoHeader
forKey:@"deflate-info"];
#endif
[self setContent:compressedData];
dataLength=[self _contentLength];
[self setHeader:@"gzip"
forKey:GSWHTTPHeader_ContentEncoding];
}
}
}
#endif // HAVE_LIBZ
}
//--------------------------------------------------------------------
-(void)_finalizeInContext:(GSWContext*)aContext
{
int dataLength=0;
NSString* dataLengthString=nil;
NSData* content=nil;
NSAssert(!_isFinalizeInContextHasBeenCalled,@"GSWResponse _finalizeInContext: already called");
//TODOV: if !session in request and session created: no client cache
if (![self _isClientCachingDisabled] && [aContext hasSession] && ![aContext _requestSessionID])
[self disableClientCaching];
// where does this come from?
//[self _resolveContentFaultsInContext:aContext];
// Finalize cookies
[self _finalizeCookiesInContext:aContext];
// Add load info to headers
if (![self headersForKey:GSWHTTPHeader_LoadAverage])
[self setHeader:GSWIntToNSString([GSWApp activeSessionsCount])
forKey:GSWHTTPHeader_LoadAverage];
// Add refusing new sessions info to headers
if ([GSWApp isRefusingNewSessions]
&& ![self headersForKey:GSWHTTPHeader_RefuseSessions])
[self setHeader:GSWIntToNSString((int)[GSWApp _refuseNewSessionsTimeInterval])
forKey:GSWHTTPHeader_RefuseSessions];
[self _finalizeContentEncodingInContext:aContext];
content=[self content];
dataLength=[self _contentLength];
dataLengthString=GSWIntToNSString(dataLength);
[self setHeader:dataLengthString
forKey:GSWHTTPHeader_ContentLength];
_isFinalizeInContextHasBeenCalled=YES;
};
//--------------------------------------------------------------------
// called _appendTagAttributeAndValue in WO 5
-(void)_appendTagAttribute:(NSString*)attributeName
value:(id)value
escapingHTMLAttributeValue:(BOOL)escape
{
GSWResponse_appendContentCharacter(self,' ');
GSWResponse_appendContentAsciiString(self,attributeName);
GSWResponse_appendContentAsciiString(self,@"=\"");
if (escape) {
GSWResponse_appendContentString(self,
GSWResponse_stringByEscapingHTMLAttributeValue(self,value));
} else {
GSWResponse_appendContentString(self,value);
}
GSWResponse_appendContentCharacter(self,'"');
};
-(void)_resolveContentFaultsInContext:(GSWContext*)aContext
{
[self notImplemented: _cmd]; //TODOFN
};
//--------------------------------------------------------------------
-(void)_appendContentFault:(id)unknown
{
[self notImplemented: _cmd]; //TODOFN
};
//--------------------------------------------------------------------
-(BOOL)_isClientCachingDisabled
{
return _isClientCachingDisabled;
};
//--------------------------------------------------------------------
-(unsigned int)_contentDataLength
{
return [_contentData length];
};
//--------------------------------------------------------------------
-(BOOL)_responseIsEqual:(GSWResponse*)aResponse
{
[self notImplemented: _cmd]; //TODOFN
return NO;
};
//--------------------------------------------------------------------
-(GSWResponse*)generateResponse
{
return self;
};
-(void)setContentStreamFileHandle:(NSFileHandle*)fileHandle
bufferSize:(unsigned int)bufferSize
length:(unsigned long)length
{
ASSIGN(_contentStreamFileHandle,fileHandle);
if (bufferSize==0)
_contentStreamBufferSize=4096;
else
_contentStreamBufferSize=bufferSize;
_contentStreamBufferLength=length;
};
//--------------------------------------------------------------------
//NDFN
//Last cHance Response
+(GSWResponse*)responseWithMessage:(NSString*)aMessage
inContext:(GSWContext*)aContext
forRequest:(GSWRequest*)aRequest
{
return [self responseWithMessage:aMessage
inContext:aContext
forRequest:aRequest
forceFinalize:NO];
};
+(GSWResponse*)responseWithMessage:(NSString*)aMessage
inContext:(GSWContext*)aContext
forRequest:(GSWRequest*)aRequest
forceFinalize:(BOOL)forceFinalize
{
GSWResponse* aResponse=nil;
NSString* httpVersion=nil;
aResponse=[GSWApp createResponseInContext:aContext];
if (aResponse)
{
NSString* responseString=nil;
if (aContext && [aContext request])
aRequest=[aContext request];
httpVersion=[aRequest httpVersion];
if (httpVersion)
[aResponse setHTTPVersion:httpVersion];
[aResponse setHeader:@"text/html"
forKey:GSWHTTPHeader_ContentType];
[aContext _setResponse:aResponse];
responseString=[NSString stringWithFormat:@"<HTML>\n<TITLE>GNUstepWeb Error</TITLE>\n</HEAD>\n<BODY bgcolor=\"white\">\n<CENTER>\n%@\n</CENTER>\n</BODY>\n</HTML>\n",
GSWResponse_stringByEscapingHTMLString(aResponse,aMessage)];
GSWResponse_appendContentString(aResponse,responseString);
if (forceFinalize)
[aResponse forceFinalizeInContext];
};
return aResponse;
};
//--------------------------------------------------------------------
//
//Refuse Response
+(GSWResponse*)generateRefusingResponseInContext:(GSWContext*)aContext
forRequest:(GSWRequest*)aRequest
{
GSWResponse* response=nil;
NSString* httpVersion=nil;
response=[GSWApp createResponseInContext:aContext];
if (response)
{
NSString* responseString=nil;
NSString* locationURLString=nil;
NSString* message=nil;
if (aContext && [aContext request])
{
aRequest=[aContext request];
}
httpVersion=[aRequest httpVersion];
if (httpVersion)
{
[response setHTTPVersion:httpVersion];
}
locationURLString = [NSString stringWithFormat:@"%@/%@.gswa",
[aRequest adaptorPrefix],
[aRequest applicationName]];
message = [NSString stringWithFormat:@"Sorry, your request could not immediately be processed. Please try this URL: <a href=\"%@\">%@</a>\nConnection closed by foreign host.",
locationURLString,
locationURLString];
responseString=[NSString stringWithFormat:@"<HTML>\n<TITLE>GNUstepWeb</TITLE>\n</HEAD>\n<BODY bgcolor=\"white\">\n<CENTER>\n%@\n</CENTER>\n</BODY>\n</HTML>\n",
message];
[response _generateRedirectResponseWithMessage:responseString
location:locationURLString
isDefinitive:NO];
if (aContext)
{
[aContext _setResponse:response];
}
};
return response;
};
//--------------------------------------------------------------------
-(void)_generateRedirectResponseWithMessage:(NSString*)message
location:(NSString*)location
isDefinitive:(BOOL)isDefinitive
{
if (message)
{
GSWResponse_appendContentString(self,message);
[self setHeader:GSWIntToNSString([[self content] length])
forKey:GSWHTTPHeader_ContentLength];
};
if (isDefinitive)
[self setStatus:301]; // redirect definitive !
else
[self setStatus:302]; // redirect temporary !
[self setHeader:location
forKey:GSWHTTPHeader_Location];
[self setHeader:@"text/html"
forKey:GSWHTTPHeader_ContentType];
[self setHeader:@"YES"
forKey:GSWHTTPHeader_RefusingRedirection[0]];
}
//--------------------------------------------------------------------
//
//Redirect Response
+(GSWResponse*)generateRedirectResponseWithMessage:(NSString*)message
location:(NSString*)location
isDefinitive:(BOOL)isDefinitive
inContext:(GSWContext*)aContext
forRequest:(GSWRequest*)aRequest
{
GSWResponse* response=nil;
NSString* httpVersion=nil;
response=[GSWApp createResponseInContext:aContext];
if (response)
{
if (aContext && [aContext request])
{
aRequest=[aContext request];
}
httpVersion=[aRequest httpVersion];
if (httpVersion)
{
[response setHTTPVersion:httpVersion];
}
[response _generateRedirectResponseWithMessage:message
location:location
isDefinitive:isDefinitive];
if (aContext)
{
[aContext _setResponse:response];
}
};
return response;
};
//--------------------------------------------------------------------
//
//Redirect Response
+(GSWResponse*)generateRedirectResponseWithMessage:(NSString*)message
location:(NSString*)location
isDefinitive:(BOOL)isDefinitive
{
GSWResponse* response=nil;
response=[self generateRedirectResponseWithMessage:message
location:location
isDefinitive:isDefinitive
inContext:nil
forRequest:nil];
return response;
};
//--------------------------------------------------------------------
+(GSWResponse*)generateRedirectDefaultResponseWithLocation:(NSString*)location
isDefinitive:(BOOL)isDefinitive
inContext:(GSWContext*)aContext
forRequest:(GSWRequest*)aRequest
{
NSString* message=nil;
GSWResponse* response=nil;
message=[NSString stringWithFormat:@"This page has been moved%s to <a HREF=\"%@\">%@</a>",
(isDefinitive ? "" : " temporarily"),
location,
location];
response=[self generateRedirectResponseWithMessage:message
location:location
isDefinitive:isDefinitive
inContext:aContext
forRequest:aRequest];
return response;
};
//--------------------------------------------------------------------
+(GSWResponse*)generateRedirectDefaultResponseWithLocation:(NSString*)location
isDefinitive:(BOOL)isDefinitive
{
GSWResponse* response=nil;
response=[self generateRedirectDefaultResponseWithLocation:location
isDefinitive:isDefinitive
inContext:nil
forRequest:nil];
return response;
}
@end