summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonathan Schleifer <js@heap.zone>2019-02-25 00:26:57 +0100
committerJonathan Schleifer <js@heap.zone>2019-02-25 00:26:57 +0100
commitf2d1d1b4d795ada0bdf4d7a4dc77fd462a341bf4 (patch)
tree9c2373a8fa48ac5d2979484b6db4766ada82fb29
parent80964db180fdadf6ed21f711036cf3dea91305cc (diff)
Implement serving static files
-rw-r--r--Module.h7
-rw-r--r--ObjWebServer.m8
-rw-r--r--ObjWebServer.xml34
-rw-r--r--StaticModule.m153
4 files changed, 183 insertions, 19 deletions
diff --git a/Module.h b/Module.h
index 6ae0e91..08e5312 100644
--- a/Module.h
+++ b/Module.h
@@ -20,8 +20,7 @@
@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
diff --git a/ObjWebServer.m b/ObjWebServer.m
index 7f43685..5d4de20 100644
--- a/ObjWebServer.m
+++ b/ObjWebServer.m
@@ -109,9 +109,9 @@ OF_APPLICATION_DELEGATE(ObjWebServer)
for (OFPair OF_GENERIC(OFString *, id <Module>) *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
diff --git a/ObjWebServer.xml b/ObjWebServer.xml
index 0212c5c..9001f90 100644
--- a/ObjWebServer.xml
+++ b/ObjWebServer.xml
@@ -1,11 +1,35 @@
<ObjWebServer>
- <listen host="0.0.0.0" port="1234">
- <tls cert="test.crt" key="test.key"/>
+ <listen host='0.0.0.0' port='1234'>
+ <tls cert='test.crt' key='test.key'/>
</listen>
- <listen host="::" port="1234">
- <tls cert="test.crt" key="test.key"/>
+ <listen host='::' port='1234'>
+ <tls cert='test.crt' key='test.key'/>
</listen>
- <module prefix="/" path="modules/static">
+ <module prefix='/' path='modules/static'>
<root>htdocs</root>
+ <mime-type extension='' type='application/octet-stream'/>
+ <mime-type extension='c' type='text/plain; encoding-UTF-8'/>
+ <mime-type extension='cc' type='text/plain; encoding=UTF-8'/>
+ <mime-type extension='css' type='text/css; encoding=UTF-8'/>
+ <mime-type extension='gz' type='application/gzip'/>
+ <mime-type extension='h' type='text/plain; encoding=UTF-8'/>
+ <mime-type extension='htm' type='text/html; encoding=UTF-8'/>
+ <mime-type extension='html' type='text/html; encoding=UTF-8'/>
+ <mime-type extension='jpeg' type='image/jpeg'/>
+ <mime-type extension='jpg' type='image/jpeg'/>
+ <mime-type extension='js'
+ type='text/javascript; encoding=UTF-8'/>
+ <mime-type extension='json' type='application/json'/>
+ <mime-type extension='m' type='text/plain; encoding=UTF-8'/>
+ <mime-type extension='mm' type='text/plain; encoding=UTF-8'/>
+ <mime-type extension='mp3' type='audio/mpeg'/>
+ <mime-type extension='mp4' type='video/mp4'/>
+ <mime-type extension='ogg' type='application/ogg'/>
+ <mime-type extension='opus' type='audio/opus'/>
+ <mime-type extension='png' type='image/png'/>
+ <mime-type extension='svg' type='image/svg+xml'/>
+ <mime-type extension='txt' type='text/plain; encoding=UTF-8'/>
+ <mime-type extension='webm' type='video/webm'/>
+ <mime-type extension='zip' type='application/zip'/>
</module>
</ObjWebServer>
diff --git a/StaticModule.m b/StaticModule.m
index 7688a93..99fad61 100644
--- a/StaticModule.m
+++ b/StaticModule.m
@@ -20,9 +20,66 @@
#import "Module.h"
+#define BUFFER_SIZE 4096
+
@interface StaticModule: OFPlugin <Module>
{
OFString *_root;
+ OFDictionary OF_GENERIC(OFString *, OFString *) *_MIMETypes;
+}
+@end
+
+@interface StaticModule_FileSender: OFObject <OFStreamDelegate>
+{
+@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
@@ -34,16 +91,100 @@
[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 <root/> 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: "
+ @"<mime-type/> has no extension attribute!"];
+ [OFApplication terminateWithStatus: 1];
+ }
+ if (type == nil) {
+ [of_stderr writeString:
+ @"Error parsing config: "
+ @"<mime-type/> 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