/* * ObjWebServer * Copyright (C) 2018, 2019 Jonathan Schleifer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #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: (OFStream *)stream didWriteData: (OFData *)data bytesWritten: (size_t)bytesWritten exception: (id)exception { if (exception != nil || _file.atEndOfStream) return nil; return readData(_file); } @end @implementation StaticModule - (void)dealloc { [_root release]; [super dealloc]; } - (void)parseConfig: (OFXMLElement *)config { OFMutableDictionary OF_GENERIC(OFString *, OFString *) *MIMETypes; _root = [[config elementForName: @"root"].stringValue copy]; if (_root == nil) { [OFStdErr 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) { [OFStdErr writeString: @"Error parsing config: " @" has no extension attribute!"]; [OFApplication terminateWithStatus: 1]; } if (type == nil) { [OFStdErr writeString: @"Error parsing config: " @" has no type attribute!"]; [OFApplication terminateWithStatus: 1]; } [MIMETypes setObject: type forKey: extension]; } [MIMETypes makeImmutable]; _MIMETypes = [MIMETypes mutableCopy]; } - (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.statusCode = 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.statusCode = 200; response.headers = headers; response.delegate = fileSender; [response asyncWriteData: readData(fileSender->_file)]; return true; } @end StaticModule * OFPluginInit(void) { return [[[StaticModule alloc] init] autorelease]; }