libs-gsweb/GSWeb.framework/GSWResponse.m
Dave Wetzel 01c0036479 fix compression
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gsweb/trunk@21753 72102866-910b-0410-8b05-ffd578937521
2005-09-28 09:00:31 +00:00

678 lines
22 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"
RCS_ID("$Id$")
#include "GSWeb.h"
#include "NSData+Compress.h"
//====================================================================
@implementation GSWResponse
static NSString* disabledCacheDateString=nil;
static NSArray* cacheControlHeaderValues=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:);
};
};
//--------------------------------------------------------------------
// init
-(id)init
{
//OK
LOGObjectFnStart();
if ((self=[super init]))
{
GetGSWResponseIMPs(&_selfIMPs,self);
_canDisableClientCaching=YES;
_status=200;
};
LOGObjectFnStop();
return self;
};
//--------------------------------------------------------------------
-(void)dealloc
{
// GSWLogAssertGood(self);
// NSDebugFLog(@"dealloc Response %p",self);
// NSDebugFLog0(@"Release Response contentFaults");
DESTROY(_contentFaults);
// NSDebugFLog0(@"Release Response contentData");
DESTROY(_contentStreamFileHandle);
DESTROY(_acceptedEncodings);
// NSDebugFLog0(@"Release Response");
[super dealloc];
};
//--------------------------------------------------------------------
-(id)copyWithZone:(NSZone*)zone
{
GSWResponse* clone = (GSWResponse*)[super copyWithZone:zone];
if (clone)
{
clone->_status=_status;
ASSIGNCOPY(clone->_contentFaults,_contentFaults);
ASSIGNCOPY(clone->_acceptedEncodings,_acceptedEncodings);
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;
};
//--------------------------------------------------------------------
-(NSArray*)acceptedEncodings
{
return _acceptedEncodings;
};
//--------------------------------------------------------------------
-(void)setAcceptedEncodings:(NSArray*)acceptedEncodings
{
ASSIGN(_acceptedEncodings,acceptedEncodings);
};
//--------------------------------------------------------------------
// 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
{
LOGObjectFnStart();
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;
};
LOGObjectFnStop();
};
//--------------------------------------------------------------------
-(NSString*)description
{
NSString* description=nil;
LOGObjectFnStart();
description=[NSString stringWithFormat:
@"<%s %p - httpVersion=%@ status=%d headers=%p contentFaults=%p contentData=%p contentEncoding=%d userInfo=%p>",
object_get_class_name(self),
(void*)self,
_httpVersion,
_status,
(void*)_headers,
(void*)_contentFaults,
(void*)_contentData,
(int)_contentEncoding,
(void*)_userInfo];
LOGObjectFnStop();
return description;
};
@end
//====================================================================
@implementation GSWResponse (GSWResponseA)
//--------------------------------------------------------------------
//NDFN
-(BOOL)isFinalizeInContextHasBeenCalled
{
return _isFinalizeInContextHasBeenCalled;
};
//--------------------------------------------------------------------
-(void)_finalizeContentEncodingInContext:(GSWContext*)aContext
{
int dataLength=0;
LOGObjectFnStart();
#ifdef HAVE_ZLIB
dataLength=[self _contentLength];
NSDebugMLog(@"dataLength=%d",dataLength);
// Now we see if we can gzip the content
if (dataLength>1024) // min length: 1024
{
// we could do better by having parameters for types
NSArray* appAcceptedContentEncodingArray=[GSWApplication acceptedContentEncodingArray];
NSDebugMLog(@"appAcceptedContentEncodingArray=%@",appAcceptedContentEncodingArray);
if ([appAcceptedContentEncodingArray count]>0)
{
NSString* contentType=[self headerForKey:@"content-type"];
NSString* gzHeader=[self headerForKey:@"gzip"];
if ([contentType isEqualTo:@"text/html"])
{
NSString* contentEncoding=[self headerForKey:@"content-encoding"];
// we could do better by handling compress,...
if (([contentEncoding length]==0 // Not already encoded
&& [_acceptedEncodings containsObject:@"gzip"]
&& [appAcceptedContentEncodingArray containsObject:@"gzip"])
&& ((gzHeader == nil) || ([gzHeader isEqual:@"0"])))
{
NSDate* compressStartDate=[NSDate date];
NSData* content=[self content];
NSData* compressedData=[content deflate];
NSDebugMLog(@"compressedData=%@",compressedData);
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:@"content-encoding"];
};
};
};
};
};
#endif
LOGObjectFnStop();
};
//--------------------------------------------------------------------
-(void)_finalizeInContext:(GSWContext*)aContext
{
int dataLength=0;
NSString* dataLengthString=nil;
NSData* content=nil;
LOGObjectFnStart();
NSAssert(!_isFinalizeInContextHasBeenCalled,@"GSWResponse _finalizeInContext: already called");
#ifndef NDEBUG
if(GSDebugSet(@"GSWDocStructure"))
{
NSString* docStructure=[aContext docStructure];
if (docStructure)
GSWResponse_appendContentString(self,
([NSString stringWithFormat:@"\n<!-- %@ -->\n",docStructure]));
}
#endif
//TODOV: if !session in request and session created: no client cache
if (![self _isClientCachingDisabled] && [aContext hasSession] && ![aContext _requestSessionID])
[self disableClientCaching];
[self _resolveContentFaultsInContext:aContext];
// Finalize cookies
[self _finalizeCookiesInContext:aContext];
// Add load info to headers
if (![self headersForKey:GSWHTTPHeader_LoadAverage[GSWebNamingConv]])
[self setHeader:GSWIntToNSString([GSWApp activeSessionsCount])
forKey:GSWHTTPHeader_LoadAverage[GSWebNamingConv]];
// Add refusing new sessions info to headers
if ([GSWApp isRefusingNewSessions]
&& ![self headersForKey:GSWHTTPHeader_RefuseSessions[GSWebNamingConv]])
[self setHeader:GSWIntToNSString((int)[GSWApp _refuseNewSessionsTimeInterval])
forKey:GSWHTTPHeader_RefuseSessions[GSWebNamingConv]];
[self _finalizeContentEncodingInContext:aContext];
content=[self content];
dataLength=[self _contentLength];
NSDebugMLog(@"dataLength=%d",dataLength);
dataLengthString=GSWIntToNSString(dataLength);
[self setHeader:dataLengthString
forKey:GSWHTTPHeader_ContentLength];
NSDebugMLLog(@"low",@"headers:%@",_headers);
_isFinalizeInContextHasBeenCalled=YES;
LOGObjectFnStop();
};
//--------------------------------------------------------------------
-(void)_appendTagAttribute:(NSString*)attributeName
value:(id)value
escapingHTMLAttributeValue:(BOOL)escape
{
LOGObjectFnStart();
GSWResponse_appendContentCharacter(self,' ');
GSWResponse_appendContentAsciiString(self,attributeName);
GSWResponse_appendContentAsciiString(self,@"=\"");
if (escape)
GSWResponse_appendContentAsciiString(self,
GSWResponse_stringByEscapingHTMLAttributeValue(self,value));
else
GSWResponse_appendContentAsciiString(self,value);
GSWResponse_appendContentCharacter(self,'"');
LOGObjectFnStop();
};
@end
//====================================================================
@implementation GSWResponse (GSWResponseB)
-(void)_resolveContentFaultsInContext:(GSWContext*)aContext
{
LOGObjectFnNotImplemented(); //TODOFN
};
//--------------------------------------------------------------------
-(void)_appendContentFault:(id)unknown
{
LOGObjectFnNotImplemented(); //TODOFN
};
@end
//====================================================================
@implementation GSWResponse (GSWResponseC)
//--------------------------------------------------------------------
-(BOOL)_isClientCachingDisabled
{
return _isClientCachingDisabled;
};
//--------------------------------------------------------------------
-(unsigned int)_contentDataLength
{
return [_contentData length];
};
@end
//====================================================================
@implementation GSWResponse (GSWResponseD)
//--------------------------------------------------------------------
-(BOOL)_responseIsEqual:(GSWResponse*)aResponse
{
LOGObjectFnNotImplemented(); //TODOFN
return NO;
};
@end
//====================================================================
@implementation GSWResponse (GSWActionResults)
//--------------------------------------------------------------------
-(GSWResponse*)generateResponse
{
return self;
};
@end
//====================================================================
@implementation GSWResponse (Stream)
-(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;
};
@end
//====================================================================
@implementation GSWResponse (GSWResponseError)
//--------------------------------------------------------------------
//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;
LOGClassFnStart();
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:@"content-type"];
[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];
};
LOGClassFnStop();
return aResponse;
};
@end
//====================================================================
@implementation GSWResponse (GSWResponseRefused)
//--------------------------------------------------------------------
//
//Refuse Response
+(GSWResponse*)generateRefusingResponseInContext:(GSWContext*)aContext
forRequest:(GSWRequest*)aRequest
{
GSWResponse* response=nil;
NSString* httpVersion=nil;
LOGClassFnStart();
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];
}
};
LOGClassFnStop();
return response;
};
@end
//====================================================================
@implementation GSWResponse (GSWResponseRedirected)
//--------------------------------------------------------------------
-(void)_generateRedirectResponseWithMessage:(NSString*)message
location:(NSString*)location
isDefinitive:(BOOL)isDefinitive
{
if (message)
{
GSWResponse_appendContentString(self,message);
[self setHeader:GSWIntToNSString([[self content] length])
forKey:@"content-length"];
};
if (isDefinitive)
[self setStatus:301]; // redirect definitive !
else
[self setStatus:302]; // redirect temporary !
[self setHeader:location
forKey:@"Location"];
[self setHeader:@"text/html"
forKey:@"content-type"];
[self setHeader:@"YES"
forKey:@"x-gsweb-refusing-redirection"];
}
//--------------------------------------------------------------------
//
//Redirect Response
+(GSWResponse*)generateRedirectResponseWithMessage:(NSString*)message
location:(NSString*)location
isDefinitive:(BOOL)isDefinitive
inContext:(GSWContext*)aContext
forRequest:(GSWRequest*)aRequest
{
GSWResponse* response=nil;
NSString* httpVersion=nil;
LOGClassFnStart();
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];
}
};
LOGClassFnStop();
return response;
};
//--------------------------------------------------------------------
//
//Redirect Response
+(GSWResponse*)generateRedirectResponseWithMessage:(NSString*)message
location:(NSString*)location
isDefinitive:(BOOL)isDefinitive
{
GSWResponse* response=nil;
LOGClassFnStart();
response=[self generateRedirectResponseWithMessage:message
location:location
isDefinitive:isDefinitive
inContext:nil
forRequest:nil];
LOGClassFnStop();
return response;
};
//--------------------------------------------------------------------
+(GSWResponse*)generateRedirectDefaultResponseWithLocation:(NSString*)location
isDefinitive:(BOOL)isDefinitive
inContext:(GSWContext*)aContext
forRequest:(GSWRequest*)aRequest
{
NSString* message=nil;
GSWResponse* response=nil;
LOGClassFnStart();
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];
LOGClassFnStop();
return response;
};
//--------------------------------------------------------------------
+(GSWResponse*)generateRedirectDefaultResponseWithLocation:(NSString*)location
isDefinitive:(BOOL)isDefinitive
{
GSWResponse* response=nil;
LOGClassFnStart();
response=[self generateRedirectDefaultResponseWithLocation:location
isDefinitive:isDefinitive
inContext:nil
forRequest:nil];
LOGClassFnStop();
return response;
};
@end