/*
* Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im>
*
* 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 <inttypes.h>
#include <string.h>
#import "ObjFW.h"
#import "ObjFWTest.h"
@interface OFHTTPClientTests: OTTestCase <OFHTTPClientDelegate>
{
OFHTTPResponse *_response;
}
@end
@interface HTTPClientTestsServer: OFThread
{
OFCondition *_condition;
uint16_t _port;
}
@property (readonly, nonatomic) OFCondition *condition;
@property (readonly) uint16_t port;
@end
@implementation OFHTTPClientTests
- (void)dealloc
{
[_response release];
[super dealloc];
}
- (void)client: (OFHTTPClient *)client
wantsRequestBody: (OFStream *)body
request: (OFHTTPRequest *)request
{
[body writeString: @"Hello"];
}
- (void)client: (OFHTTPClient *)client
didPerformRequest: (OFHTTPRequest *)request
response: (OFHTTPResponse *)response_
exception: (id)exception
{
OTAssertNil(exception);
[_response release];
_response = [response_ retain];
[[OFRunLoop mainRunLoop] stop];
}
- (void)testClient
{
HTTPClientTestsServer *server;
OFIRI *IRI;
OFHTTPRequest *request;
OFHTTPClient *client;
OFData *data;
server = [[[HTTPClientTestsServer alloc] init] autorelease];
server.supportsSockets = true;
[server.condition lock];
[server start];
[server.condition wait];
[server.condition unlock];
IRI = [OFIRI IRIWithString:
[OFString stringWithFormat: @"http://127.0.0.1:%" @PRIu16 "/foo",
server.port]];
request = [OFHTTPRequest requestWithIRI: IRI];
request.headers = [OFDictionary
dictionaryWithObject: @"5"
forKey: @"Content-Length"];
client = [OFHTTPClient client];
client.delegate = self;
[client asyncPerformRequest: request];
[[OFRunLoop mainRunLoop] runUntilDate:
[OFDate dateWithTimeIntervalSinceNow: 2]];
OTAssertNotNil(_response);
OTAssertNotNil([_response.headers objectForKey: @"Content-Length"]);
data = [_response readDataUntilEndOfStream];
OTAssertEqual(data.count, 7);
OTAssertEqual(data.itemSize, 1);
OTAssertEqual(memcmp(data.items, "foo\nbar", 7), 0);
OTAssertNil([server join]);
}
@end
@implementation HTTPClientTestsServer
@synthesize condition = _condition, port = _port;
- (instancetype)init
{
self = [super init];
@try {
_condition = [[OFCondition alloc] init];
} @catch (id e) {
[self release];
@throw e;
}
return self;
}
- (void)dealloc
{
[_condition release];
[super dealloc];
}
- (id)main
{
OFTCPSocket *listener, *client;
OFSocketAddress address;
bool sawHost = false, sawContentLength = false, sawContentType = false;
bool sawUserAgent = false;
char buffer[5];
[_condition lock];
listener = [OFTCPSocket socket];
address = [listener bindToHost: @"127.0.0.1" port: 0];
_port = OFSocketAddressIPPort(&address);
[listener listen];
[_condition signal];
[_condition unlock];
client = [listener accept];
if (![[client readLine] isEqual: @"GET /foo HTTP/1.1"])
return @"Wrong request";
for (size_t i = 0; i < 4; i++) {
OFString *line = [client readLine];
if ([line isEqual: [OFString stringWithFormat:
@"Host: 127.0.0.1:%" @PRIu16, _port]])
sawHost = true;
else if ([line isEqual: @"Content-Length: 5"])
sawContentLength = true;
if ([line isEqual: @"Content-Type: application/"
@"x-www-form-urlencoded; charset=UTF-8"])
sawContentType = true;
else if ([line hasPrefix: @"User-Agent:"])
sawUserAgent = true;
}
if (!sawHost)
return @"Missing host";
if (!sawContentLength)
return @"Missing content length";
if (!sawContentType)
return @"Missing content type";
if (!sawUserAgent)
return @"Missing user agent";
if (![[client readLine] isEqual: @""])
return @"Missing empty line";
[client readIntoBuffer: buffer exactLength: 5];
if (memcmp(buffer, "Hello", 5) != 0)
return @"Missing body";
[client writeString: @"HTTP/1.0 200 OK\r\n"
@"cONTeNT-lENgTH: 7\r\n"
@"\r\n"
@"foo\n"
@"bar"];
[client close];
return nil;
}
@end