/* * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, * 2018, 2019 * Jonathan Schleifer <js@heap.zone> * * All rights reserved. * * This file is part of ObjFW. It may be distributed under the terms of the * Q Public License 1.0, which can be found in the file LICENSE.QPL included in * the packaging of this file. * * Alternatively, it may be distributed under the terms of the GNU General * Public License, either version 2 or 3, which can be found in the file * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ #include "config.h" #include <stdlib.h> #include <string.h> #import "OFHTTPServer.h" #import "OFData.h" #import "OFDate.h" #import "OFDictionary.h" #import "OFHTTPRequest.h" #import "OFHTTPResponse.h" #import "OFNumber.h" #import "OFTCPSocket.h" #import "OFTLSSocket.h" #import "OFTimer.h" #import "OFURL.h" #import "OFAlreadyConnectedException.h" #import "OFInvalidArgumentException.h" #import "OFInvalidFormatException.h" #import "OFNotOpenException.h" #import "OFOutOfMemoryException.h" #import "OFOutOfRangeException.h" #import "OFUnsupportedProtocolException.h" #import "OFWriteFailedException.h" #import "socket_helpers.h" #define BUFFER_SIZE 1024 /* * FIXME: Key normalization replaces headers like "DNT" with "Dnt". * FIXME: Errors are not reported to the user. */ @interface OFHTTPServer () <OFTCPSocketDelegate> @end @interface OFHTTPServerResponse: OFHTTPResponse <OFReadyForWritingObserving> { OF_KINDOF(OFTCPSocket *) _socket; OFHTTPServer *_server; OFHTTPRequest *_request; bool _chunked, _headersSent; } - (instancetype)initWithSocket: (OF_KINDOF(OFTCPSocket *))sock server: (OFHTTPServer *)server request: (OFHTTPRequest *)request; @end @interface OFHTTPServer_Connection: OFObject <OFTCPSocketDelegate> { @public OF_KINDOF(OFTCPSocket *) _socket; OFHTTPServer *_server; OFTimer *_timer; enum { AWAITING_PROLOG, PARSING_HEADERS, SEND_RESPONSE } _state; uint8_t _HTTPMinorVersion; of_http_request_method_t _method; OFString *_host, *_path; uint16_t _port; OFMutableDictionary *_headers; size_t _contentLength; OFStream *_requestBody; } - (instancetype)initWithSocket: (OF_KINDOF(OFTCPSocket *))sock server: (OFHTTPServer *)server; - (bool)parseProlog: (OFString *)line; - (bool)parseHeaders: (OFString *)line; - (bool)sendErrorAndClose: (short)statusCode; - (void)createResponse; @end @interface OFHTTPServerRequestBodyStream: OFStream <OFReadyForReadingObserving> { OF_KINDOF(OFTCPSocket *) _socket; uintmax_t _toRead; bool _atEndOfStream; } - (instancetype)initWithSocket: (OF_KINDOF(OFTCPSocket *))sock contentLength: (uintmax_t)contentLength; @end static const char * statusCodeToString(short code) { switch (code) { case 100: return "Continue"; case 101: return "Switching Protocols"; case 200: return "OK"; case 201: return "Created"; case 202: return "Accepted"; case 203: return "Non-Authoritative Information"; case 204: return "No Content"; case 205: return "Reset Content"; case 206: return "Partial Content"; case 300: return "Multiple Choices"; case 301: return "Moved Permanently"; case 302: return "Found"; case 303: return "See Other"; case 304: return "Not Modified"; case 305: return "Use Proxy"; case 307: return "Temporary Redirect"; case 400: return "Bad Request"; case 401: return "Unauthorized"; case 402: return "Payment Required"; case 403: return "Forbidden"; case 404: return "Not Found"; case 405: return "Method Not Allowed"; case 406: return "Not Acceptable"; case 407: return "Proxy Authentication Required"; case 408: return "Request Timeout"; case 409: return "Conflict"; case 410: return "Gone"; case 411: return "Length Required"; case 412: return "Precondition Failed"; case 413: return "Request Entity Too Large"; case 414: return "Request-URI Too Long"; case 415: return "Unsupported Media Type"; case 416: return "Requested Range Not Satisfiable"; case 417: return "Expectation Failed"; case 500: return "Internal Server Error"; case 501: return "Not Implemented"; case 502: return "Bad Gateway"; case 503: return "Service Unavailable"; case 504: return "Gateway Timeout"; case 505: return "HTTP Version Not Supported"; default: return NULL; } } static OF_INLINE OFString * normalizedKey(OFString *key) { char *cString = of_strdup([key UTF8String]); unsigned char *tmp = (unsigned char *)cString; bool firstLetter = true; if (cString == NULL) @throw [OFOutOfMemoryException exceptionWithRequestedSize: strlen([key UTF8String])]; while (*tmp != '\0') { if (!of_ascii_isalpha(*tmp)) { firstLetter = true; tmp++; continue; } *tmp = (firstLetter ? of_ascii_toupper(*tmp) : of_ascii_tolower(*tmp)); firstLetter = false; tmp++; } return [OFString stringWithUTF8StringNoCopy: cString freeWhenDone: true]; } @implementation OFHTTPServerResponse - (instancetype)initWithSocket: (OF_KINDOF(OFTCPSocket *))sock server: (OFHTTPServer *)server request: (OFHTTPRequest *)request { self = [super init]; _statusCode = 500; _socket = [sock retain]; _server = [server retain]; _request = [request retain]; return self; } - (void)dealloc { if (_socket != nil) [self close]; /* includes [_socket release] */ [_server release]; [_request release]; [super dealloc]; } - (void)of_sendHeaders { void *pool = objc_autoreleasePoolPush(); OFMutableDictionary OF_GENERIC(OFString *, OFString *) *headers; OFEnumerator *keyEnumerator, *valueEnumerator; OFString *key, *value; [_socket writeFormat: @"HTTP/%@ %d %s\r\n", [self protocolVersionString], _statusCode, statusCodeToString(_statusCode)]; headers = [[_headers mutableCopy] autorelease]; if ([headers objectForKey: @"Date"] == nil) { OFString *date = [[OFDate date] dateStringWithFormat: @"%a, %d %b %Y %H:%M:%S GMT"]; [headers setObject: date forKey: @"Date"]; } if ([headers objectForKey: @"Server"] == nil) { OFString *name = [_server name]; if (name != nil) [headers setObject: name forKey: @"Server"]; } keyEnumerator = [headers keyEnumerator]; valueEnumerator = [headers objectEnumerator]; while ((key = [keyEnumerator nextObject]) != nil && (value = [valueEnumerator nextObject]) != nil) [_socket writeFormat: @"%@: %@\r\n", key, value]; [_socket writeString: @"\r\n"]; _headersSent = true; _chunked = [[headers objectForKey: @"Transfer-Encoding"] isEqual: @"chunked"]; objc_autoreleasePoolPop(pool); } - (size_t)lowlevelWriteBuffer: (const void *)buffer length: (size_t)length { /* TODO: Use non-blocking writes */ void *pool; if (_socket == nil) @throw [OFNotOpenException exceptionWithObject: self]; if (!_headersSent) [self of_sendHeaders]; if (!_chunked) return [_socket writeBuffer: buffer length: length]; pool = objc_autoreleasePoolPush(); [_socket writeString: [OFString stringWithFormat: @"%zx\r\n", length]]; objc_autoreleasePoolPop(pool); [_socket writeBuffer: buffer length: length]; [_socket writeBuffer: "\r\n" length: 2]; return length; } - (void)close { if (_socket == nil) @throw [OFNotOpenException exceptionWithObject: self]; @try { if (!_headersSent) [self of_sendHeaders]; if (_chunked) [_socket writeBuffer: "0\r\n\r\n" length: 5]; } @catch (OFWriteFailedException *e) { id <OFHTTPServerDelegate> delegate = [_server delegate]; if ([delegate respondsToSelector: @selector(server: didReceiveExceptionForResponse:request:exception:)]) [delegate server: _server didReceiveExceptionForResponse: self request: _request exception: e]; } [_socket release]; _socket = nil; [super close]; } - (int)fileDescriptorForWriting { if (_socket == nil) return -1; return [_socket fileDescriptorForWriting]; } @end @implementation OFHTTPServer_Connection - (instancetype)initWithSocket: (OF_KINDOF(OFTCPSocket *))sock server: (OFHTTPServer *)server { self = [super init]; @try { _socket = [sock retain]; _server = [server retain]; _timer = [[OFTimer scheduledTimerWithTimeInterval: 10 target: _socket selector: @selector( cancelAsyncRequests) repeats: false] retain]; _state = AWAITING_PROLOG; } @catch (id e) { [self release]; @throw e; } return self; } - (void)dealloc { [_socket release]; [_server release]; [_timer invalidate]; [_timer release]; [_host release]; [_path release]; [_headers release]; [_requestBody release]; [super dealloc]; } - (bool)stream: (OF_KINDOF(OFStream *))sock didReadLine: (OFString *)line exception: (id)exception { if (line == nil || exception != nil) return false; @try { switch (_state) { case AWAITING_PROLOG: return [self parseProlog: line]; case PARSING_HEADERS: return [self parseHeaders: line]; default: return false; } } @catch (OFWriteFailedException *e) { return false; } OF_ENSURE(0); } - (bool)parseProlog: (OFString *)line { OFString *method; OFMutableString *path; size_t pos; @try { OFString *version = [line substringWithRange: of_range([line length] - 9, 9)]; of_unichar_t tmp; if (![version hasPrefix: @" HTTP/1."]) return [self sendErrorAndClose: 505]; tmp = [version characterAtIndex: 8]; if (tmp < '0' || tmp > '9') return [self sendErrorAndClose: 400]; _HTTPMinorVersion = (uint8_t)(tmp - '0'); } @catch (OFOutOfRangeException *e) { return [self sendErrorAndClose: 400]; } pos = [line rangeOfString: @" "].location; if (pos == OF_NOT_FOUND) return [self sendErrorAndClose: 400]; method = [line substringWithRange: of_range(0, pos)]; @try { _method = of_http_request_method_from_string( [method UTF8String]); } @catch (OFInvalidFormatException *e) { return [self sendErrorAndClose: 405]; } @try { of_range_t range = of_range(pos + 1, [line length] - pos - 10); path = [[[line substringWithRange: range] mutableCopy] autorelease]; } @catch (OFOutOfRangeException *e) { return [self sendErrorAndClose: 400]; } [path deleteEnclosingWhitespaces]; [path makeImmutable]; if (![path hasPrefix: @"/"]) return [self sendErrorAndClose: 400]; _headers = [[OFMutableDictionary alloc] init]; _path = [path copy]; _state = PARSING_HEADERS; return true; } - (bool)parseHeaders: (OFString *)line { OFString *key, *value, *old; size_t pos; if ([line length] == 0) { OFString *contentLengthString; if ((contentLengthString = [_headers objectForKey: @"Content-Length"]) != nil) { intmax_t contentLength; @try { contentLength = [contentLengthString decimalValue]; } @catch (OFInvalidFormatException *e) { return [self sendErrorAndClose: 400]; } if (contentLength < 0) return [self sendErrorAndClose: 400]; [_requestBody release]; _requestBody = nil; _requestBody = [[OFHTTPServerRequestBodyStream alloc] initWithSocket: _socket contentLength: contentLength]; [_timer invalidate]; [_timer release]; _timer = nil; } _state = SEND_RESPONSE; [self createResponse]; return false; } pos = [line rangeOfString: @":"].location; if (pos == OF_NOT_FOUND) return [self sendErrorAndClose: 400]; key = [line substringWithRange: of_range(0, pos)]; value = [line substringWithRange: of_range(pos + 1, [line length] - pos - 1)]; key = normalizedKey([key stringByDeletingTrailingWhitespaces]); value = [value stringByDeletingLeadingWhitespaces]; old = [_headers objectForKey: key]; if (old != nil) value = [old stringByAppendingFormat: @",%@", value]; [_headers setObject: value forKey: key]; if ([key isEqual: @"Host"]) { pos = [value rangeOfString: @":" options: OF_STRING_SEARCH_BACKWARDS].location; if (pos != OF_NOT_FOUND) { [_host release]; _host = [[value substringWithRange: of_range(0, pos)] retain]; @try { of_range_t range = of_range(pos + 1, [value length] - pos - 1); intmax_t portTmp = [[value substringWithRange: range] decimalValue]; if (portTmp < 1 || portTmp > UINT16_MAX) return [self sendErrorAndClose: 400]; _port = (uint16_t)portTmp; } @catch (OFInvalidFormatException *e) { return [self sendErrorAndClose: 400]; } } else { [_host release]; _host = [value retain]; _port = 80; } } return true; } - (bool)sendErrorAndClose: (short)statusCode { OFString *date = [[OFDate date] dateStringWithFormat: @"%a, %d %b %Y %H:%M:%S GMT"]; [_socket writeFormat: @"HTTP/1.1 %d %s\r\n" @"Date: %@\r\n" @"Server: %@\r\n" @"\r\n", statusCode, statusCodeToString(statusCode), date, [_server name]]; return false; } - (void)createResponse { void *pool = objc_autoreleasePoolPush(); OFMutableURL *URL; OFHTTPRequest *request; OFHTTPServerResponse *response; size_t pos; [_timer invalidate]; [_timer release]; _timer = nil; if (_host == nil || _port == 0) { if (_HTTPMinorVersion > 0) { [self sendErrorAndClose: 400]; return; } [_host release]; _host = [[_server host] retain]; _port = [_server port]; } URL = [OFMutableURL URL]; [URL setScheme: @"http"]; [URL setHost: _host]; if (_port != 80) [URL setPort: [OFNumber numberWithUInt16: _port]]; if ((pos = [_path rangeOfString: @"?"].location) != OF_NOT_FOUND) { OFString *path, *query; path = [_path substringWithRange: of_range(0, pos)]; query = [_path substringWithRange: of_range(pos + 1, [_path length] - pos - 1)]; [URL setURLEncodedPath: path]; [URL setURLEncodedQuery: query]; } else [URL setURLEncodedPath: _path]; [URL makeImmutable]; request = [OFHTTPRequest requestWithURL: URL]; [request setMethod: _method]; [request setProtocolVersion: (of_http_request_protocol_version_t){ 1, _HTTPMinorVersion }]; [request setHeaders: _headers]; [request setRemoteAddress: [_socket remoteAddress]]; response = [[[OFHTTPServerResponse alloc] initWithSocket: _socket server: _server request: request] autorelease]; [[_server delegate] server: _server didReceiveRequest: request requestBody: _requestBody response: response]; objc_autoreleasePoolPop(pool); } @end @implementation OFHTTPServerRequestBodyStream - (instancetype)initWithSocket: (OF_KINDOF(OFTCPSocket *))sock contentLength: (uintmax_t)contentLength { self = [super init]; @try { _socket = [sock retain]; _toRead = contentLength; } @catch (id e) { [self release]; @throw e; } return self; } - (void)dealloc { [self close]; [super dealloc]; } - (bool)lowlevelIsAtEndOfStream { return _atEndOfStream; } - (size_t)lowlevelReadIntoBuffer: (void *)buffer length: (size_t)length { size_t ret; if (_toRead == 0) { _atEndOfStream = true; return 0; } if (length > _toRead) length = (size_t)_toRead; ret = [_socket readIntoBuffer: buffer length: length]; _toRead -= ret; return ret; } - (bool)hasDataInReadBuffer { return ([super hasDataInReadBuffer] || [_socket hasDataInReadBuffer]); } - (int)fileDescriptorForReading { return [_socket fileDescriptorForReading]; } - (void)close { [_socket release]; _socket = nil; } @end @implementation OFHTTPServer @synthesize host = _host, port = _port, usesTLS = _usesTLS; @synthesize certificateFile = _certificateFile; @synthesize privateKeyFile = _privateKeyFile; @synthesize privateKeyPassphrase = _privateKeyPassphrase, delegate = _delegate; @synthesize name = _name; + (instancetype)server { return [[[self alloc] init] autorelease]; } - (instancetype)init { self = [super init]; _name = @"OFHTTPServer (ObjFW's HTTP server class " @"<https://heap.zone/objfw/>)"; return self; } - (void)dealloc { [_host release]; [_listeningSocket release]; [_name release]; [super dealloc]; } - (void)start { if (_host == nil) @throw [OFInvalidArgumentException exception]; if (_listeningSocket != nil) @throw [OFAlreadyConnectedException exception]; if (_usesTLS) { id <OFTLSSocket> listeningSocket; if (of_tls_socket_class == Nil) @throw [OFUnsupportedProtocolException exception]; _listeningSocket = [[of_tls_socket_class alloc] init]; listeningSocket = _listeningSocket; [listeningSocket setCertificateFile: _certificateFile]; [listeningSocket setPrivateKeyFile: _privateKeyFile]; [listeningSocket setPrivateKeyPassphrase: _privateKeyPassphrase]; } else _listeningSocket = [[OFTCPSocket alloc] init]; _port = [_listeningSocket bindToHost: _host port: _port]; [_listeningSocket listen]; [(OFTCPSocket *)_listeningSocket setDelegate: self]; [_listeningSocket asyncAccept]; } - (void)stop { [_listeningSocket cancelAsyncRequests]; [_listeningSocket release]; _listeningSocket = nil; } - (bool)socket: (OF_KINDOF(OFTCPSocket *))sock didAcceptSocket: (OF_KINDOF(OFTCPSocket *))acceptedSocket exception: (id)exception { OFHTTPServer_Connection *connection; if (exception != nil) { if (![_delegate respondsToSelector: @selector(server:didReceiveExceptionOnListeningSocket:)]) return false; return [_delegate server: self didReceiveExceptionOnListeningSocket: exception]; } connection = [[[OFHTTPServer_Connection alloc] initWithSocket: acceptedSocket server: self] autorelease]; [(OFTCPSocket *)acceptedSocket setDelegate: connection]; [acceptedSocket asyncReadLine]; return true; } @end