Index: Module.h ================================================================== --- Module.h +++ Module.h @@ -18,10 +18,9 @@ #import @protocol Module - (void)parseConfig: (OFXMLElement *)config; -- (void)server: (OFHTTPServer *)server - didReceiveRequest: (OFHTTPRequest *)request - requestBody: (OFStream *)requestBody - response: (OFHTTPResponse *)response; +- (bool)handleRequest: (OFHTTPRequest *)request + requestBody: (OFStream *)requestBody + response: (OFHTTPResponse *)response; @end Index: ObjWebServer.m ================================================================== --- ObjWebServer.m +++ ObjWebServer.m @@ -107,11 +107,11 @@ of_log(@"Request: %@", request); for (OFPair OF_GENERIC(OFString *, id ) *module in _modules) if ([path hasPrefix: [module firstObject]]) - [[module secondObject] server: server - didReceiveRequest: request - requestBody: requestBody - response: response]; + if ([[module secondObject] handleRequest: request + requestBody: requestBody + response: response]) + return; } @end Index: ObjWebServer.xml ================================================================== --- ObjWebServer.xml +++ ObjWebServer.xml @@ -1,11 +1,35 @@ - - - - - - - - htdocs + + + + + + + + htdocs + + + + + + + + + + + + + + + + + + + + + + + Index: StaticModule.m ================================================================== --- StaticModule.m +++ StaticModule.m @@ -17,14 +17,71 @@ */ #import #import "Module.h" + +#define BUFFER_SIZE 4096 @interface StaticModule: OFPlugin { OFString *_root; + OFDictionary OF_GENERIC(OFString *, OFString *) *_MIMETypes; +} +@end + +@interface StaticModule_FileSender: OFObject +{ +@public + OFFile *_file; + OFHTTPResponse *_response; +} +@end + +static OFData * +readData(OFStream *stream) +{ + void *buffer; + OFData *ret; + + if ((buffer = malloc(BUFFER_SIZE)) == NULL) + @throw [OFOutOfMemoryException + exceptionWithRequestedSize: BUFFER_SIZE]; + + @try { + size_t length = [stream readIntoBuffer: buffer + length: BUFFER_SIZE]; + + ret = [OFData dataWithItemsNoCopy: buffer + count: length + freeWhenDone: true]; + } @catch (id e) { + free(buffer); + @throw e; + } + + return ret; +} + +@implementation StaticModule_FileSender +- (void)dealloc +{ + [_file release]; + [_response release]; + + [super dealloc]; +} + +- (OFData *)stream: (OF_KINDOF(OFStream *))stream + didWriteData: (OFData *)data + bytesWritten: (size_t)bytesWritten + exception: (id)exception +{ + if (exception != nil || [_file isAtEndOfStream]) + return nil; + + return readData(_file); } @end @implementation StaticModule - (void)dealloc @@ -32,23 +89,107 @@ [_root release]; [super dealloc]; } -- (void)parseConfig: (OFXMLElement *)element +- (void)parseConfig: (OFXMLElement *)config { - _root = [[[element elementForName: @"root"] stringValue] copy]; + OFMutableDictionary OF_GENERIC(OFString *, OFString *) *MIMETypes; + + _root = [[[config elementForName: @"root"] stringValue] copy]; + if (_root == nil) { + [of_stderr writeString: + @"Error parsing config: No element!"]; + [OFApplication terminateWithStatus: 1]; + } + + MIMETypes = [OFMutableDictionary dictionary]; + for (OFXMLElement *MIMEType in [config elementsForName: @"mime-type"]) { + OFString *extension = + [[MIMEType attributeForName: @"extension"] stringValue]; + OFString *type = + [[MIMEType attributeForName: @"type"] stringValue]; + + if (extension == nil) { + [of_stderr writeString: + @"Error parsing config: " + @" has no extension attribute!"]; + [OFApplication terminateWithStatus: 1]; + } + if (type == nil) { + [of_stderr writeString: + @"Error parsing config: " + @" has no type attribute!"]; + [OFApplication terminateWithStatus: 1]; + } + + [MIMETypes setObject: type + forKey: extension]; + } + [MIMETypes makeImmutable]; + _MIMETypes = [MIMETypes mutableCopy]; } -- (void)server: (OFHTTPServer *)server - didReceiveRequest: (OFHTTPRequest *)request - requestBody: (OFStream *)requestBody - response: (OFHTTPResponse *)response +- (bool)handleRequest: (OFHTTPRequest *)request + requestBody: (OFStream *)requestBody + response: (OFHTTPResponse *)response { + OFURL *URL = [[request URL] URLByStandardizingPath]; + OFString *path = [URL path]; + OFMutableDictionary *headers = [OFMutableDictionary dictionary]; + OFFileManager *fileManager; + bool firstComponent = true; + OFString *MIMEType; + StaticModule_FileSender *fileSender; + + for (OFString *component in [URL pathComponents]) { + if (firstComponent && [component length] != 0) + return false; + + if ([component isEqual: @"."] || [component isEqual: @".."]) + return false; + + firstComponent = false; + } + + /* TODO: Properly handle for OSes that do not use UNIX paths */ + if (![path hasPrefix: @"/"]) + return false; + + path = [_root stringByAppendingString: path]; + + fileManager = [OFFileManager defaultManager]; + if ([fileManager directoryExistsAtPath: path]) + path = [path stringByAppendingPathComponent: @"index.html"]; + + if (![fileManager fileExistsAtPath: path]) { + [response setStatusCode: 404]; + return false; + } + + MIMEType = [_MIMETypes objectForKey: [path pathExtension]]; + if (MIMEType == nil) + MIMEType = [_MIMETypes objectForKey: @""]; + + if (MIMEType != nil) + [headers setObject: MIMEType + forKey: @"Content-Type"]; + + fileSender = [[[StaticModule_FileSender alloc] init] autorelease]; + fileSender->_file = [[OFFile alloc] initWithPath: path + mode: @"r"]; + fileSender->_response = [response retain]; + + [response setStatusCode: 200]; + [response setHeaders: headers]; + [response setDelegate: fileSender]; + [response asyncWriteData: readData(fileSender->_file)]; + + return true; } @end StaticModule * init_plugin(void) { return [[[StaticModule alloc] init] autorelease]; }