@@ -1,7 +1,7 @@ /* - * Copyright (c) 2008-2022 Jonathan Schleifer + * Copyright (c) 2008-2024 Jonathan Schleifer * * 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 @@ -23,19 +23,19 @@ #import "OFHTTPClient.h" #import "OFData.h" #import "OFDictionary.h" #import "OFHTTPRequest.h" #import "OFHTTPResponse.h" +#import "OFIRI.h" #import "OFKernelEventObserver.h" #import "OFNumber.h" #import "OFRunLoop.h" #import "OFString.h" #import "OFTCPSocket.h" #import "OFTLSStream.h" -#import "OFURI.h" -#import "OFAlreadyConnectedException.h" +#import "OFAlreadyOpenException.h" #import "OFHTTPRequestFailedException.h" #import "OFInvalidArgumentException.h" #import "OFInvalidEncodingException.h" #import "OFInvalidFormatException.h" #import "OFInvalidServerResponseException.h" @@ -115,30 +115,30 @@ static OFString * constructRequestString(OFHTTPRequest *request) { void *pool = objc_autoreleasePoolPush(); OFHTTPRequestMethod method = request.method; - OFURI *URI = request.URI; + OFIRI *IRI = request.IRI.IRIByAddingPercentEncodingForUnicodeCharacters; OFString *path; - OFString *user = URI.user, *password = URI.password; + OFString *user = IRI.user, *password = IRI.password; OFMutableString *requestString; OFMutableDictionary OF_GENERIC(OFString *, OFString *) *headers; bool hasContentLength, chunked; OFEnumerator OF_GENERIC(OFString *) *keyEnumerator, *objectEnumerator; OFString *key, *object; - if (URI.path.length > 0) - path = URI.percentEncodedPath; + if (IRI.path.length > 0) + path = IRI.percentEncodedPath; else path = @"/"; requestString = [OFMutableString stringWithFormat: - @"%s %@", OFHTTPRequestMethodName(method), path]; + @"%@ %@", OFHTTPRequestMethodString(method), path]; - if (URI.query != nil) { + if (IRI.query != nil) { [requestString appendString: @"?"]; - [requestString appendString: URI.percentEncodedQuery]; + [requestString appendString: IRI.percentEncodedQuery]; } [requestString appendString: @" HTTP/"]; [requestString appendString: request.protocolVersionString]; [requestString appendString: @"\r\n"]; @@ -146,19 +146,19 @@ headers = [[request.headers mutableCopy] autorelease]; if (headers == nil) headers = [OFMutableDictionary dictionary]; if ([headers objectForKey: @"Host"] == nil) { - OFNumber *port = URI.port; + OFNumber *port = IRI.port; if (port != nil) { OFString *host = [OFString stringWithFormat: - @"%@:%@", URI.percentEncodedHost, port]; + @"%@:%@", IRI.percentEncodedHost, port]; [headers setObject: host forKey: @"Host"]; } else - [headers setObject: URI.percentEncodedHost + [headers setObject: IRI.percentEncodedHost forKey: @"Host"]; } if ((user.length > 0 || password.length > 0) && [headers objectForKey: @"Authorization"] == nil) { @@ -176,12 +176,12 @@ [headers setObject: authorization forKey: @"Authorization"]; } if ([headers objectForKey: @"User-Agent"] == nil) - [headers setObject: @"Something using ObjFW " - @"" + [headers setObject: @"OFHTTPClient (ObjFW's HTTP client class " + @")" forKey: @"User-Agent"]; if (request.protocolVersion.major == 1 && request.protocolVersion.minor == 0 && [headers objectForKey: @"Connection"] == nil) @@ -297,11 +297,11 @@ exception: exception]; } - (void)createResponseWithStreamOrThrow: (OFStream *)stream { - OFURI *URI = _request.URI; + OFIRI *IRI = _request.IRI; OFHTTPClientResponse *response; OFString *connectionHeader; bool keepAlive; OFString *location; id exception; @@ -328,44 +328,44 @@ if (keepAlive) { response.of_keepAlive = true; _client->_stream = [stream retain]; - _client->_lastURI = [URI copy]; + _client->_lastIRI = [IRI copy]; _client->_lastWasHEAD = (_request.method == OFHTTPRequestMethodHead); _client->_lastResponse = [response retain]; } if (_redirects > 0 && (_status == 301 || _status == 302 || _status == 303 || _status == 307) && (location = [_serverHeaders objectForKey: @"Location"]) != nil) { bool follow = true; - OFURI *newURI; - OFString *newURIScheme; + OFIRI *newIRI; + OFString *newIRIScheme; - newURI = [OFURI URIWithString: location relativeToURI: URI]; - newURIScheme = newURI.scheme; + newIRI = [OFIRI IRIWithString: location relativeToIRI: IRI]; + newIRIScheme = newIRI.scheme; - if ([newURIScheme caseInsensitiveCompare: @"http"] != + if ([newIRIScheme caseInsensitiveCompare: @"http"] != OFOrderedSame && - [newURIScheme caseInsensitiveCompare: @"https"] != + [newIRIScheme caseInsensitiveCompare: @"https"] != OFOrderedSame) follow = false; if (!_client->_allowsInsecureRedirects && - [URI.scheme caseInsensitiveCompare: @"https"] == + [IRI.scheme caseInsensitiveCompare: @"https"] == OFOrderedSame && - [newURIScheme caseInsensitiveCompare: @"http"] == + [newIRIScheme caseInsensitiveCompare: @"http"] == OFOrderedSame) follow = false; if (follow && [_client->_delegate respondsToSelector: - @selector(client:shouldFollowRedirectToURI:statusCode: + @selector(client:shouldFollowRedirectToIRI:statusCode: request:response:)]) follow = [_client->_delegate client: _client - shouldFollowRedirectToURI: newURI + shouldFollowRedirectToIRI: newIRI statusCode: _status request: _request response: response]; else if (follow) follow = defaultShouldFollow(_request.method, _status); @@ -376,37 +376,29 @@ OFHTTPRequest *newRequest = [[_request copy] autorelease]; OFMutableDictionary *newHeaders = [[headers mutableCopy] autorelease]; - if (![newURI.host isEqual: URI.host]) + if (![newIRI.host isEqual: IRI.host]) [newHeaders removeObjectForKey: @"Host"]; /* * 303 means the request should be converted to a GET * request before redirection. This also means stripping * the entity of the request. */ if (_status == 303) { - OFEnumerator *keyEnumerator, *objectEnumerator; - OFString *key, *object; - - keyEnumerator = [headers keyEnumerator]; - objectEnumerator = [headers objectEnumerator]; - while ((key = [keyEnumerator nextObject]) != - nil && - (object = [objectEnumerator nextObject]) != - nil) + for (OFString *key in headers) if ([key hasPrefix: @"Content-"] || [key hasPrefix: @"Transfer-"]) [newHeaders removeObjectForKey: key]; newRequest.method = OFHTTPRequestMethodGet; } - newRequest.URI = newURI; + newRequest.IRI = newIRI; newRequest.headers = newHeaders; _client->_inProgress = false; [_client asyncPerformRequest: newRequest @@ -638,27 +630,25 @@ if (exception != nil) { [self raiseException: exception]; return; } - sock.canBlock = false; - if ([_client->_delegate respondsToSelector: @selector(client:didCreateTCPSocket:request:)]) [_client->_delegate client: _client didCreateTCPSocket: sock request: _request]; - if ([_request.URI.scheme caseInsensitiveCompare: @"https"] == + if ([_request.IRI.scheme caseInsensitiveCompare: @"https"] == OFOrderedSame) { OFTLSStream *stream; @try { stream = [OFTLSStream streamWithStream: sock]; } @catch (OFNotImplementedException *e) { [self raiseException: [OFUnsupportedProtocolException - exceptionWithURI: _request.URI]]; + exceptionWithIRI: _request.IRI]]; return; } if ([_client->_delegate respondsToSelector: @selector(client:didCreateTLSStream:request:)]) @@ -665,11 +655,12 @@ [_client->_delegate client: _client didCreateTLSStream: stream request: _request]; stream.delegate = self; - [stream asyncPerformClientHandshakeWithHost: _request.URI.host]; + [stream asyncPerformClientHandshakeWithHost: _request.IRI + .IRIByAddingPercentEncodingForUnicodeCharacters.host]; } else { sock.delegate = self; [self performSelector: @selector(handleStream:) withObject: sock afterDelay: 0]; @@ -690,30 +681,30 @@ afterDelay: 0]; } - (void)start { - OFURI *URI = _request.URI; + OFIRI *IRI = _request.IRI; OFStream *stream; /* Can we reuse the last socket? */ if (_client->_stream != nil && !_client->_stream.atEndOfStream && - [_client->_lastURI.scheme isEqual: URI.scheme] && - [_client->_lastURI.host isEqual: URI.host] && - (_client->_lastURI.port == URI.port || - [_client->_lastURI.port isEqual: URI.port]) && + [_client->_lastIRI.scheme isEqual: IRI.scheme] && + [_client->_lastIRI.host isEqual: IRI.host] && + (_client->_lastIRI.port == IRI.port || + [_client->_lastIRI.port isEqual: IRI.port]) && (_client->_lastWasHEAD || _client->_lastResponse.atEndOfStream)) { /* * Set _stream to nil, so that in case of an error it won't be * reused. If everything is successful, we set _stream again * at the end. */ stream = [_client->_stream autorelease]; _client->_stream = nil; - [_client->_lastURI release]; - _client->_lastURI = nil; + [_client->_lastIRI release]; + _client->_lastIRI = nil; [_client->_lastResponse release]; _client->_lastResponse = nil; stream.delegate = self; @@ -726,31 +717,32 @@ } - (void)closeAndReconnect { @try { - OFURI *URI = _request.URI; + OFIRI *IRI = + _request.IRI.IRIByAddingPercentEncodingForUnicodeCharacters; OFTCPSocket *sock; uint16_t port; - OFNumber *URIPort; + OFNumber *IRIPort; [_client close]; sock = [OFTCPSocket socket]; - if ([URI.scheme caseInsensitiveCompare: @"https"] == + if ([IRI.scheme caseInsensitiveCompare: @"https"] == OFOrderedSame) port = 443; else port = 80; - URIPort = URI.port; - if (URIPort != nil) - port = URIPort.unsignedShortValue; + IRIPort = IRI.port; + if (IRIPort != nil) + port = IRIPort.unsignedShortValue; sock.delegate = self; - [sock asyncConnectToHost: URI.host port: port]; + [sock asyncConnectToHost: IRI.host port: port]; } @catch (id e) { [self raiseException: e]; } } @end @@ -1074,13 +1066,13 @@ return ((OFStream *)_stream) .fileDescriptorForReading; } -- (bool)hasDataInReadBuffer +- (bool)lowlevelHasDataInReadBuffer { - return (super.hasDataInReadBuffer || _stream.hasDataInReadBuffer); + return _stream.hasDataInReadBuffer; } - (void)close { if (_stream == nil) @@ -1201,19 +1193,19 @@ statusCode: statusCode request: request]; } - (bool)client: (OFHTTPClient *)client - shouldFollowRedirectToURI: (OFURI *)URI + shouldFollowRedirectToIRI: (OFIRI *)IRI statusCode: (short)statusCode request: (OFHTTPRequest *)request response: (OFHTTPResponse *)response { if ([_delegate respondsToSelector: @selector( - client:shouldFollowRedirectToURI:statusCode:request:response:)]) + client:shouldFollowRedirectToIRI:statusCode:request:response:)]) return [_delegate client: client - shouldFollowRedirectToURI: URI + shouldFollowRedirectToIRI: IRI statusCode: statusCode request: request response: response]; else return defaultShouldFollow(request.method, statusCode); @@ -1265,19 +1257,19 @@ - (void)asyncPerformRequest: (OFHTTPRequest *)request redirects: (unsigned int)redirects { void *pool = objc_autoreleasePoolPush(); - OFURI *URI = request.URI; - OFString *scheme = URI.scheme; + OFIRI *IRI = request.IRI; + OFString *scheme = IRI.scheme; if ([scheme caseInsensitiveCompare: @"http"] != OFOrderedSame && [scheme caseInsensitiveCompare: @"https"] != OFOrderedSame) - @throw [OFUnsupportedProtocolException exceptionWithURI: URI]; + @throw [OFUnsupportedProtocolException exceptionWithIRI: IRI]; if (_inProgress) - @throw [OFAlreadyConnectedException exception]; + @throw [OFAlreadyOpenException exceptionWithObject: self]; _inProgress = true; [[[[OFHTTPClientRequestHandler alloc] initWithClient: self @@ -1290,12 +1282,12 @@ - (void)close { [_stream release]; _stream = nil; - [_lastURI release]; - _lastURI = nil; + [_lastIRI release]; + _lastIRI = nil; [_lastResponse release]; _lastResponse = nil; } @end