mirror of
https://github.com/gnustep/libs-base.git
synced 2025-05-30 16:30:41 +00:00
Add NSURLConnection patch by Sergei Golovin
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@38214 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
parent
ca81536515
commit
4935550f1b
21 changed files with 4457 additions and 27 deletions
|
@ -1,3 +1,8 @@
|
||||||
|
2014-11-29 Sergei Golovin <Golovin.SV@gmail.com>
|
||||||
|
|
||||||
|
* Tests/base/NSURLConnection: Test helper tool plus a load of
|
||||||
|
tests for NSURLConnection.
|
||||||
|
|
||||||
2014-11-29 Richard Frith-Macdonald <rfm@gnu.org>
|
2014-11-29 Richard Frith-Macdonald <rfm@gnu.org>
|
||||||
|
|
||||||
* Source/NSScanner.m: Fixup error in scanning doubles with excess
|
* Source/NSScanner.m: Fixup error in scanning doubles with excess
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
# __GENERATED__ makefile marker
|
|
||||||
#
|
|
||||||
|
|
||||||
include $(GNUSTEP_MAKEFILES)/common.make
|
|
||||||
-include ../GNUmakefile.super
|
|
||||||
|
|
||||||
GNUSTEP_OBJ_DIR=./obj
|
|
||||||
|
|
||||||
TEST_TOOL_NAME = basic
|
|
||||||
|
|
||||||
ifeq ($(gcov),yes)
|
|
||||||
ADDITIONAL_OBJCFLAGS += -ftest-coverage -fprofile-arcs
|
|
||||||
ADDITIONAL_OBJCCFLAGS += -ftest-coverage -fprofile-arcs
|
|
||||||
ADDITIONAL_LDFLAGS += -ftest-coverage -fprofile-arcs
|
|
||||||
ADDITIONAL_TOOL_LIBS+=-lgcov
|
|
||||||
endif
|
|
||||||
|
|
||||||
|
|
||||||
basic_OBJC_FILES=basic.m
|
|
||||||
|
|
||||||
-include GNUmakefile.preamble
|
|
||||||
include $(GNUSTEP_MAKEFILES)/test-tool.make
|
|
||||||
-include GNUmakefile.postamble
|
|
||||||
|
|
||||||
after-clean::
|
|
||||||
rm -f core core.* *.core tests.log tests.sum oldtests.log oldtests.sum
|
|
||||||
|
|
20
Tests/base/NSURLConnection/Helpers/GNUmakefile
Normal file
20
Tests/base/NSURLConnection/Helpers/GNUmakefile
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
include $(GNUSTEP_MAKEFILES)/common.make
|
||||||
|
|
||||||
|
BUNDLE_NAME = TestConnection
|
||||||
|
|
||||||
|
TestConnection_OBJC_FILES = TestCase.m SimpleWebServer.m TestWebServer.m NSURLConnectionTest.m RequestHandler.m
|
||||||
|
#TestConnection_OBJC_LIBS += -lWebServer -lPerformance
|
||||||
|
TestConnection_RESOURCE_FILES += testKey.pem testCert.pem
|
||||||
|
TestConnection_PRINCIPAL_CLASS = NSURLConnectionTest
|
||||||
|
|
||||||
|
StatusServer_NEEDS_GUI = NO
|
||||||
|
|
||||||
|
TOOL_NAME = testTestWebServer
|
||||||
|
testTestWebServer_OBJC_FILES += testTestWebServer.m
|
||||||
|
testTestWebServer_NEEDS_GUI = NO
|
||||||
|
|
||||||
|
-include GNUmakefile.preamble
|
||||||
|
include $(GNUSTEP_MAKEFILES)/bundle.make
|
||||||
|
include $(GNUSTEP_MAKEFILES)/tool.make
|
||||||
|
-include GNUmakefile.postamble
|
||||||
|
|
219
Tests/base/NSURLConnection/Helpers/NSURLConnectionTest.h
Normal file
219
Tests/base/NSURLConnection/Helpers/NSURLConnectionTest.h
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
/** -*- objc -*-
|
||||||
|
*
|
||||||
|
* Author: Sergei Golovin <Golovin.SV@gmail.com>
|
||||||
|
*
|
||||||
|
* The class is intended to test the class NSURLConnection. It is designed to start
|
||||||
|
* a TestWebServer instance and make NSURLConnection to it getting TestWebServerDelegate's
|
||||||
|
* and NSURLConnection's callbacks. As TestCase's child it has two flag sets, the actual
|
||||||
|
* one and the reference one (See TestCase.h). It sets/unsets flags of the actual set on
|
||||||
|
* the execution path at the 'checkpoints' listed below as macros.
|
||||||
|
*
|
||||||
|
* The method -[isSuccess] is called when the NSURLConnection finishes (fails). It makes
|
||||||
|
* a comparison between the two flag sets. The result signifies a success/failure of the
|
||||||
|
* test.
|
||||||
|
*
|
||||||
|
* The test case which the NSURLConnectionTest implements by default is connecting
|
||||||
|
* to the http://localhost:54321/ whith the HTTP method 'GET'. You can change variable
|
||||||
|
* parts of process by supplying a custom dictionary to the method -[setUpTest:]
|
||||||
|
* before the test case is started by a call of the -[startTest:]. The use pattern:
|
||||||
|
* --------------------------------------------------------------------------
|
||||||
|
* #import "NSURLConnectionTest.h"
|
||||||
|
* #import "Testing.h"
|
||||||
|
*
|
||||||
|
* NSURLConnectionTest *testCase = [NSURLConnectionTest new];
|
||||||
|
* // the extra dictionary with test cases's parameters
|
||||||
|
* NSDictionary *d = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
* @"/somepath", @"Path",
|
||||||
|
* @"POST", @"Method",
|
||||||
|
* @"https", @"Protocol",
|
||||||
|
* nil];
|
||||||
|
* [testCase setUpTest: d];
|
||||||
|
* [testCase startTest: d];
|
||||||
|
* PASS([testCase isSuccess], "a diagnostic message about the test");
|
||||||
|
* [testCase tearDownTest: d];
|
||||||
|
* DESTROY(testCase);
|
||||||
|
* --------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* The method -[setUpTest:] recognises the following variable parts (supplied as key-value
|
||||||
|
* pairs):
|
||||||
|
*
|
||||||
|
* 'Instance'
|
||||||
|
* 'AuxInstance' - holds an already running main/auxilliary TestWebServer instance.
|
||||||
|
* If nil the class will run one main instance of TestWebServer
|
||||||
|
* and no auxilliary one. The key 'IsAuxilliary' with the value 'YES'
|
||||||
|
* should be supplied to run an own auxilliary instance.
|
||||||
|
* Useful to run several tests consequently. The test won't stop
|
||||||
|
* on its own the supplied instance(s). It leaves the calling code
|
||||||
|
* to make the decision. The auxilliary instance 'AuxInstance' is
|
||||||
|
* used in tests on redirecting where two web servers are needed.
|
||||||
|
* Default: nil
|
||||||
|
* 'IsAuxilliary' - whether the NSURLConnectionTest should run it's own auxilliary
|
||||||
|
* TestWebServer instance. You must supply YES as a value to run it.
|
||||||
|
* It will be run with the same parameters (address/protocol/detached)
|
||||||
|
* as the main one except of the port which is assigned from the value
|
||||||
|
* of the key 'AuxPort'.
|
||||||
|
* Default: NO
|
||||||
|
* 'IsDetached' - whether to run the own(and auxilliary) instance in the detached mode
|
||||||
|
* (the instance will be run within a detached thread).
|
||||||
|
* Default: NO
|
||||||
|
* 'Address' - the address of the remote side.
|
||||||
|
* Default: localhost
|
||||||
|
* 'Port' - the port of the remote side.
|
||||||
|
* Default: 54321
|
||||||
|
* 'AuxPort' - the port of the auxilliary remote side (where the connection
|
||||||
|
* to be redirected).
|
||||||
|
* Default: 54322
|
||||||
|
* 'Protocol' - the network protocol (supports currently http, https).
|
||||||
|
* Default: http
|
||||||
|
* 'Path' - the path of the URL.
|
||||||
|
* Default: '/'
|
||||||
|
* 'RedirectPath' - the path where request should be redirected in corresponding tests.
|
||||||
|
* Default: '/'
|
||||||
|
* 'StatusCode' - the status code expected from the remote side if the test
|
||||||
|
* is successful.
|
||||||
|
* Default: 204
|
||||||
|
* 'RedirectCode' - the status code expected from the remote side on the connection's first
|
||||||
|
* stage in a test with redirect.
|
||||||
|
* Default: 301
|
||||||
|
* 'Method' - the request's method.
|
||||||
|
* Default: GET
|
||||||
|
* 'Payload' - the request's payload. It can be of NSString or of NSData class.
|
||||||
|
* The class produces NSData from NSString using NSUTF8StringEncoding.
|
||||||
|
* Default: nil
|
||||||
|
* 'Content' - the expected content. It can be of NSString or of NSData class.
|
||||||
|
* The class produces NSData from NSString using NSUTF8StringEncoding.
|
||||||
|
* Default: nil
|
||||||
|
* 'ReferenceFlags' - the dictionary to correct the guessed reference flag set.
|
||||||
|
* Default: nil
|
||||||
|
* The class tries to infer the reference flag set from
|
||||||
|
* the test request. If that guessed (default) set is wrong then this
|
||||||
|
* dictionary allows to correct it.
|
||||||
|
* The class recognises the following keys:
|
||||||
|
*
|
||||||
|
* 'SENTREQUEST'
|
||||||
|
* 'AUTHORIZED'
|
||||||
|
* 'NOTAUTHORIZED'
|
||||||
|
* 'GOTUNAUTHORIZED'
|
||||||
|
* 'GOTREQUEST'
|
||||||
|
* 'SENTRESPONSE'
|
||||||
|
* 'GOTRESPONSE'
|
||||||
|
* 'GOTCONTENT'
|
||||||
|
* 'GOTFINISH'
|
||||||
|
* 'GOTFAIL'
|
||||||
|
* 'GOTREDIRECT'
|
||||||
|
*
|
||||||
|
* the 'YES' as a value means the corresponding reference flag
|
||||||
|
* must be set, otherwise 'NO' means it must not be set.
|
||||||
|
*
|
||||||
|
* The NSURLConnectionTest can raise it's verbosity level by the method call -[setDebug: YES].
|
||||||
|
* In this case whole connection session will be showed in the log.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "TestWebServer.h"
|
||||||
|
#import "TestCase.h"
|
||||||
|
|
||||||
|
/* the test's checkpoint list */
|
||||||
|
|
||||||
|
/* the request has been sent by the client and reaches the server */
|
||||||
|
#define SENTREQUEST 1
|
||||||
|
/* the client's request has been authorized */
|
||||||
|
#define AUTHORIZED 2
|
||||||
|
/* the client's request hasn't been authorized */
|
||||||
|
#define NOTAUTHORIZED 4
|
||||||
|
/* the client has got Unauthorized response */
|
||||||
|
#define GOTUNAUTHORIZED 8
|
||||||
|
/* the server has got a right request */
|
||||||
|
#define GOTREQUEST 16
|
||||||
|
/* the server is ready to send the response */
|
||||||
|
#define SENTRESPONSE 32
|
||||||
|
/* the client has got the response from the server with valid headers/properties */
|
||||||
|
#define GOTRESPONSE 64
|
||||||
|
/* the client has got the expected content from the server's response */
|
||||||
|
#define GOTCONTENT 128
|
||||||
|
/* the test's execution has reached client's -[connectionDidFinishLoading:] */
|
||||||
|
#define GOTFINISH 256
|
||||||
|
/* the test's execution has reached client's -[connection:didFailWithError:] */
|
||||||
|
#define GOTFAIL 512
|
||||||
|
/* the test's execution has reached client's -[connection:willSendRequest:redirectResponse:]*/
|
||||||
|
#define GOTREDIRECT 1024
|
||||||
|
/* the end of the test's checkpoint list */
|
||||||
|
|
||||||
|
@interface NSURLConnectionTest : TestCase <TestWebServerDelegate>
|
||||||
|
{
|
||||||
|
/* the main TestWebServer instance */
|
||||||
|
TestWebServer *_server;
|
||||||
|
/* tha auxilliary TestWebServer instance needed in tests on redirecting */
|
||||||
|
TestWebServer *_auxServer;
|
||||||
|
/* the custom request (made by the instance or supplied externally) */
|
||||||
|
NSURLRequest *_request;
|
||||||
|
/* the redirect request (made by the instance) */
|
||||||
|
NSURLRequest *_redirectRequest;
|
||||||
|
/* the data accumulator for the received response's content */
|
||||||
|
NSMutableData *_received;
|
||||||
|
/* the expected status code */
|
||||||
|
NSUInteger _expectedStatusCode;
|
||||||
|
/* the expected redirect status code of the connection's first stage
|
||||||
|
* in tests with redirect... the resulting (on the second stage)
|
||||||
|
* expected status code is stored in the ivar _expectedStatusCode */
|
||||||
|
NSUInteger _redirectStatusCode;
|
||||||
|
/* the expected response's content */
|
||||||
|
NSData *_expectedContent;
|
||||||
|
/* the connection */
|
||||||
|
NSURLConnection *_conn;
|
||||||
|
/* to store the error supplied with the -[connection:didFailWithError:] */
|
||||||
|
NSError *_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a TestWebServer-like class used by this test case class. A descendant can
|
||||||
|
* return more advanced implementation of TestWebServer's functionality.
|
||||||
|
*/
|
||||||
|
+ (Class)testWebServerClass;
|
||||||
|
|
||||||
|
+ (void)initialize;
|
||||||
|
|
||||||
|
- (id)init;
|
||||||
|
|
||||||
|
/* See the super's description */
|
||||||
|
- (void)setUpTest:(id)extra;
|
||||||
|
- (void)startTest:(id)extra;
|
||||||
|
- (void)tearDownTest:(id)extra;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The only difference from the super's one is issuing of a diagnostic message
|
||||||
|
* if the test is failed.
|
||||||
|
*/
|
||||||
|
- (BOOL)isSuccess;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Issues log messages describing the actual and reference flag sets' states.
|
||||||
|
*/
|
||||||
|
- (void)logFlags;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the error stored by the -[connection:didFailWithError:].
|
||||||
|
*/
|
||||||
|
- (NSError *)error;
|
||||||
|
|
||||||
|
/* NSURLConnectionDelegate */
|
||||||
|
- (NSURLRequest *)connection:(NSURLConnection *)connection
|
||||||
|
willSendRequest:(NSURLRequest *)request
|
||||||
|
redirectResponse:(NSURLResponse *)redirectResponse;
|
||||||
|
|
||||||
|
- (void)connection:(NSURLConnection *)connection
|
||||||
|
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
|
||||||
|
|
||||||
|
- (void)connection:(NSURLConnection *)connection
|
||||||
|
didReceiveResponse:(NSURLResponse *)response;
|
||||||
|
|
||||||
|
- (void)connection:(NSURLConnection *)connection
|
||||||
|
didReceiveData:(NSData *)data;
|
||||||
|
|
||||||
|
- (void)connectionDidFinishLoading:(NSURLConnection *)connection;
|
||||||
|
|
||||||
|
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
|
||||||
|
|
||||||
|
/* end of NSURLConnectionDelegate */
|
||||||
|
|
||||||
|
@end /* NSURLConnectionTest */
|
784
Tests/base/NSURLConnection/Helpers/NSURLConnectionTest.m
Normal file
784
Tests/base/NSURLConnection/Helpers/NSURLConnectionTest.m
Normal file
|
@ -0,0 +1,784 @@
|
||||||
|
/*
|
||||||
|
* Author: Sergei Golovin <Golovin.SV@gmail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "NSURLConnectionTest.h"
|
||||||
|
|
||||||
|
/* the runloop's time slice */
|
||||||
|
#define TIMING 0.1
|
||||||
|
/* the max duration of a test */
|
||||||
|
#define MAXDURATION 3.0
|
||||||
|
|
||||||
|
@interface NSURLConnectionTest (Private)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method tries to guess the request which the test case should make.
|
||||||
|
* It recognizes as an argument an NSDictionary with custom variable parts
|
||||||
|
* of connecting process (see the class's description). It also can be supplied
|
||||||
|
* with the exact request which must be made during test execution.
|
||||||
|
* The result of the method is stored in the ivar _request.
|
||||||
|
*/
|
||||||
|
- (void)_makeRequest:(id)extra;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method analyzes the ivar _request and tries to guess the execution path
|
||||||
|
* inferring from it the reference flag set. If there is a deviation from the guessed
|
||||||
|
* execution path the caller can supply NSDictionary as the argument 'extra' with
|
||||||
|
* the key 'ReferenceFlags' is set to have a custom NSDictionary as a value explicitly
|
||||||
|
* setting the particular flags (see the class's description).
|
||||||
|
*/
|
||||||
|
- (void)_makeReferenceFlags:(id)extra;
|
||||||
|
|
||||||
|
@end /* NSURLConnectionTest (Private) */
|
||||||
|
|
||||||
|
@implementation NSURLConnectionTest
|
||||||
|
|
||||||
|
+ (Class)testWebServerClass
|
||||||
|
{
|
||||||
|
return [TestWebServer class];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The flag map connects flags's string names with flags... used to log actions */
|
||||||
|
static NSMapTable *_flagMap = nil;
|
||||||
|
|
||||||
|
+ (void)initialize
|
||||||
|
{
|
||||||
|
if(nil == _flagMap)
|
||||||
|
{
|
||||||
|
_flagMap = NSCreateMapTable(NSObjectMapKeyCallBacks,
|
||||||
|
NSIntegerMapValueCallBacks,
|
||||||
|
0);
|
||||||
|
NSMapInsert(_flagMap, @"SENTREQUEST", (void *)1);
|
||||||
|
NSMapInsert(_flagMap, @"AUTHORIZED", (void *)2);
|
||||||
|
NSMapInsert(_flagMap, @"NOTAUTHORIZED", (void *)4);
|
||||||
|
NSMapInsert(_flagMap, @"GOTUNAUTHORIZED", (void *)8);
|
||||||
|
NSMapInsert(_flagMap, @"GOTREQUEST", (void *)16);
|
||||||
|
NSMapInsert(_flagMap, @"SENTRESPONSE", (void *)32);
|
||||||
|
NSMapInsert(_flagMap, @"GOTRESPONSE", (void *)64);
|
||||||
|
NSMapInsert(_flagMap, @"GOTCONTENT", (void *)128);
|
||||||
|
NSMapInsert(_flagMap, @"GOTFINISH", (void *)256);
|
||||||
|
NSMapInsert(_flagMap, @"GOTFAIL", (void *)512);
|
||||||
|
NSMapInsert(_flagMap, @"GOTREDIRECT", (void *)1024);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id)init
|
||||||
|
{
|
||||||
|
if((self = [super init]) != nil)
|
||||||
|
{
|
||||||
|
_conn = nil;
|
||||||
|
_error = nil;
|
||||||
|
_server = nil;
|
||||||
|
_auxServer = nil;
|
||||||
|
_received = nil;
|
||||||
|
_request = nil;
|
||||||
|
_redirectRequest = nil;
|
||||||
|
_expectedStatusCode = 204;
|
||||||
|
_expectedContent = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
// super's -[dealloc] calls the -[tearDownTest:]
|
||||||
|
// so place clearing there
|
||||||
|
|
||||||
|
- (void)setUpTest:(id)extra
|
||||||
|
{
|
||||||
|
[super setUpTest: extra];
|
||||||
|
|
||||||
|
[self _makeRequest: extra];
|
||||||
|
[self _makeReferenceFlags: extra];
|
||||||
|
_received = [NSMutableData new];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)startTest:(id)extra
|
||||||
|
{
|
||||||
|
CREATE_AUTORELEASE_POOL(arp);
|
||||||
|
NSTimeInterval duration = 0.0;
|
||||||
|
|
||||||
|
if(YES == _debug)
|
||||||
|
{
|
||||||
|
NSLog(@"%@: started with request:\n%@", self, _request);
|
||||||
|
[self logFlags];
|
||||||
|
}
|
||||||
|
|
||||||
|
_conn = [[NSURLConnection alloc] initWithRequest: _request
|
||||||
|
delegate: self];
|
||||||
|
|
||||||
|
while(!_done && !_failed)
|
||||||
|
{
|
||||||
|
[[NSRunLoop currentRunLoop]
|
||||||
|
runUntilDate: [NSDate dateWithTimeIntervalSinceNow: TIMING]];
|
||||||
|
duration += TIMING;
|
||||||
|
if(duration >= MAXDURATION)
|
||||||
|
{
|
||||||
|
_failed = YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(YES == _debug)
|
||||||
|
{
|
||||||
|
NSLog(@"%@: stopped with request:\n%@", self, _request);
|
||||||
|
[self logFlags];
|
||||||
|
}
|
||||||
|
DESTROY(arp);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)tearDownTest:(id)extra
|
||||||
|
{
|
||||||
|
BOOL isToStop = YES; // whether to stop the main TestWebServer instance
|
||||||
|
BOOL isToStopAux = YES; // whether to stop the auxilliary TestWebServer instance
|
||||||
|
if([_extra isKindOfClass: [NSDictionary class]])
|
||||||
|
{
|
||||||
|
NSDictionary *d = _extra;
|
||||||
|
isToStop = ([d objectForKey: @"Instance"] == nil);
|
||||||
|
isToStopAux = ([d objectForKey: @"AuxInstance"] == nil &&
|
||||||
|
[[d objectForKey: @"IsAuxilliary"] isEqualToString: @"YES"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isToStop)
|
||||||
|
{
|
||||||
|
if(nil != _server)
|
||||||
|
{
|
||||||
|
[_server stop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(isToStopAux)
|
||||||
|
{
|
||||||
|
if(nil != _auxServer)
|
||||||
|
{
|
||||||
|
[_auxServer stop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DESTROY(_server);
|
||||||
|
DESTROY(_auxServer);
|
||||||
|
DESTROY(_received);
|
||||||
|
DESTROY(_request);
|
||||||
|
DESTROY(_redirectRequest);
|
||||||
|
DESTROY(_expectedContent);
|
||||||
|
[_conn cancel];
|
||||||
|
DESTROY(_conn);
|
||||||
|
DESTROY(_error);
|
||||||
|
|
||||||
|
[super tearDownTest: extra];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)isSuccess
|
||||||
|
{
|
||||||
|
BOOL ret = [super isSuccess];
|
||||||
|
|
||||||
|
|
||||||
|
if(!ret)
|
||||||
|
{
|
||||||
|
/* log the flags that differ */
|
||||||
|
NSString *key;
|
||||||
|
NSUInteger flag;
|
||||||
|
NSMapEnumerator en = NSEnumerateMapTable(_flagMap);
|
||||||
|
while(NSNextMapEnumeratorPair(&en, (void **)&key, (void **)&flag))
|
||||||
|
{
|
||||||
|
if([self isReferenceFlagSet: flag] != [self isFlagSet: flag])
|
||||||
|
{
|
||||||
|
NSLog(@"ERR: %@", key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NSEndMapTableEnumeration(&en);
|
||||||
|
|
||||||
|
if(_failed)
|
||||||
|
{
|
||||||
|
NSLog(@"FAILED for unknown reason possibly not related to the test (e.g. a timer has expired)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)logFlags
|
||||||
|
{
|
||||||
|
NSString *value;
|
||||||
|
NSString *key;
|
||||||
|
NSUInteger flag;
|
||||||
|
NSMapEnumerator en = NSEnumerateMapTable(_flagMap);
|
||||||
|
while(NSNextMapEnumeratorPair(&en, (void **)&key, (void **)&flag))
|
||||||
|
{
|
||||||
|
if([self isFlagSet: flag])
|
||||||
|
{
|
||||||
|
value = @"YES";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
value = @"NO";
|
||||||
|
}
|
||||||
|
|
||||||
|
NSLog(@"ACTUAL: %@ -> %@", key, value);
|
||||||
|
|
||||||
|
if([self isReferenceFlagSet: flag])
|
||||||
|
{
|
||||||
|
value = @"YES";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
value = @"NO";
|
||||||
|
}
|
||||||
|
|
||||||
|
NSLog(@"REFERENCE: %@ -> %@", key, value);
|
||||||
|
}
|
||||||
|
NSEndMapTableEnumeration(&en);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSError *)error
|
||||||
|
{
|
||||||
|
return _error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TestWebServerDelegate */
|
||||||
|
- (void)handler:(id)handler
|
||||||
|
gotRequest:(GSMimeDocument *)request
|
||||||
|
with:(TestWebServer *)server
|
||||||
|
{
|
||||||
|
NSString *method = [_request HTTPMethod];
|
||||||
|
NSData *content = [request convertToData];
|
||||||
|
|
||||||
|
[self setFlags: SENTREQUEST];
|
||||||
|
if(YES == _debug)
|
||||||
|
{
|
||||||
|
NSLog(@"%@: set SENTREQUEST (-[%@])", self, NSStringFromSelector(_cmd));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: more comparisons of _request and request
|
||||||
|
if([method isEqualToString: @"POST"] ||
|
||||||
|
[method isEqualToString: @"PUT"])
|
||||||
|
{
|
||||||
|
if([content isEqualToData: [_request HTTPBody]])
|
||||||
|
{
|
||||||
|
[self setFlags: GOTREQUEST];
|
||||||
|
if(YES == _debug)
|
||||||
|
{
|
||||||
|
NSLog(@"%@: set GOTREQUEST (-[%@])", self, NSStringFromSelector(_cmd));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
[self setFlags: GOTREQUEST];
|
||||||
|
if(YES == _debug)
|
||||||
|
{
|
||||||
|
NSLog(@"%@: set GOTREQUEST (-[%@])", self, NSStringFromSelector(_cmd));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)handler:(id)handler
|
||||||
|
willSendUnauthorized:(GSMimeDocument *)response
|
||||||
|
with:(TestWebServer *)server
|
||||||
|
{
|
||||||
|
[self setFlags: NOTAUTHORIZED];
|
||||||
|
if(YES == _debug)
|
||||||
|
{
|
||||||
|
NSLog(@"%@: set NOTAUTHORIZED (-[%@])", self, NSStringFromSelector(_cmd));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)handler:(id)handler
|
||||||
|
gotAuthorized:(GSMimeDocument *)request
|
||||||
|
with:(TestWebServer *)server
|
||||||
|
{
|
||||||
|
[self setFlags: AUTHORIZED];
|
||||||
|
if(YES == _debug)
|
||||||
|
{
|
||||||
|
NSLog(@"%@: set AUTHORIZED (-[%@])", self, NSStringFromSelector(_cmd));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)handler:(id)handler
|
||||||
|
willSend:(GSMimeDocument *)response
|
||||||
|
with:(TestWebServer *)server
|
||||||
|
{
|
||||||
|
[self setFlags: SENTRESPONSE];
|
||||||
|
if(YES == _debug)
|
||||||
|
{
|
||||||
|
NSLog(@"%@: set SENTRESPONSE (-[%@])", self, NSStringFromSelector(_cmd));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)timeoutExceededByHandler:(id)handler
|
||||||
|
{
|
||||||
|
_failed = YES;
|
||||||
|
}
|
||||||
|
/* end of TestWebServerDelegate */
|
||||||
|
|
||||||
|
/* NSURLConnectionDelegate */
|
||||||
|
- (NSURLRequest *)connection:(NSURLConnection *)connection
|
||||||
|
willSendRequest:(NSURLRequest *)request
|
||||||
|
redirectResponse:(NSURLResponse *)redirectResponse
|
||||||
|
{
|
||||||
|
if([redirectResponse isKindOfClass: [NSHTTPURLResponse class]])
|
||||||
|
{
|
||||||
|
if([(NSHTTPURLResponse *)redirectResponse statusCode] == _redirectStatusCode)
|
||||||
|
{
|
||||||
|
[self setFlags: GOTREDIRECT];
|
||||||
|
if(YES == _debug)
|
||||||
|
{
|
||||||
|
NSLog(@"%@: set GOTREDIRECT (-[%@])", self, NSStringFromSelector(_cmd));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
[self setFlags: GOTREDIRECT];
|
||||||
|
if(YES == _debug)
|
||||||
|
{
|
||||||
|
NSLog(@"%@: set GOTREDIRECT (-[%@])", self, NSStringFromSelector(_cmd));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _redirectRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)connection:(NSURLConnection *)connection
|
||||||
|
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
|
||||||
|
{
|
||||||
|
[self setFlags: GOTUNAUTHORIZED];
|
||||||
|
if(YES == _debug)
|
||||||
|
{
|
||||||
|
NSLog(@"%@: set GOTUNAUTHORIZED (-[%@])", self, NSStringFromSelector(_cmd));
|
||||||
|
}
|
||||||
|
|
||||||
|
if([challenge previousFailureCount] == 0)
|
||||||
|
{
|
||||||
|
NSURLCredential *cred;
|
||||||
|
// TODO: _auxServer?
|
||||||
|
NSString *login = [_server login];
|
||||||
|
NSString *password = [_server password];
|
||||||
|
|
||||||
|
if(nil == login)
|
||||||
|
{
|
||||||
|
if([_extra isKindOfClass: [NSDictionary class]])
|
||||||
|
{
|
||||||
|
login = [(NSDictionary *)_extra objectForKey: @"Login"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(nil == password)
|
||||||
|
{
|
||||||
|
if([_extra isKindOfClass: [NSDictionary class]])
|
||||||
|
{
|
||||||
|
password = [(NSDictionary *)_extra objectForKey: @"Password"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cred = [NSURLCredential credentialWithUser: login
|
||||||
|
password: password
|
||||||
|
persistence: NSURLCredentialPersistenceNone];
|
||||||
|
[[challenge sender] useCredential: cred
|
||||||
|
forAuthenticationChallenge: challenge];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
[[challenge sender] cancelAuthenticationChallenge: challenge];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)connection:(NSURLConnection *)connection
|
||||||
|
didReceiveResponse:(NSURLResponse *)response
|
||||||
|
{
|
||||||
|
if(YES == _debug)
|
||||||
|
{
|
||||||
|
if([response isKindOfClass: [NSHTTPURLResponse class]])
|
||||||
|
{
|
||||||
|
NSLog(@"%@: received response (-[%@]):\nStatus Code: %u",
|
||||||
|
self, NSStringFromSelector(_cmd), [(NSHTTPURLResponse *)response statusCode]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if([response isKindOfClass: [NSHTTPURLResponse class]])
|
||||||
|
{
|
||||||
|
if([(NSHTTPURLResponse *)response statusCode] == _expectedStatusCode)
|
||||||
|
{
|
||||||
|
[self setFlags: GOTRESPONSE];
|
||||||
|
if(YES == _debug)
|
||||||
|
{
|
||||||
|
NSLog(@"%@: set GOTRESPONSE (-[%@])", self, NSStringFromSelector(_cmd));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
[self setFlags: GOTRESPONSE];
|
||||||
|
if(YES == _debug)
|
||||||
|
{
|
||||||
|
NSLog(@"%@: set GOTRESPONSE (-[%@])", self, NSStringFromSelector(_cmd));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)connection:(NSURLConnection *)connection
|
||||||
|
didReceiveData:(NSData *)data
|
||||||
|
{
|
||||||
|
[_received appendData: data];
|
||||||
|
if(YES == _debug)
|
||||||
|
{
|
||||||
|
NSLog(@"%@: received data '%@' (-[%@])", self, data, NSStringFromSelector(_cmd));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
|
||||||
|
{
|
||||||
|
if(nil != _expectedContent &&
|
||||||
|
[_received isEqualToData: _expectedContent])
|
||||||
|
{
|
||||||
|
[self setFlags: GOTCONTENT];
|
||||||
|
if(YES == _debug)
|
||||||
|
{
|
||||||
|
NSLog(@"%@: set GOTCONTENT (-[%@])", self, NSStringFromSelector(_cmd));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[self setFlags: GOTFINISH];
|
||||||
|
if(YES == _debug)
|
||||||
|
{
|
||||||
|
NSLog(@"%@: set GOTFINISH (-[%@])", self, NSStringFromSelector(_cmd));
|
||||||
|
}
|
||||||
|
|
||||||
|
_done = YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
|
||||||
|
{
|
||||||
|
ASSIGN(_error, error);
|
||||||
|
|
||||||
|
/* if((nil != _expectedContent &&
|
||||||
|
[_received isEqualToData: _expectedContent]) ||
|
||||||
|
(nil == _expectedContent && [_received length] == 0))
|
||||||
|
{
|
||||||
|
[self setFlags: GOTCONTENT];
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
[self setFlags: GOTFAIL];
|
||||||
|
if(YES == _debug)
|
||||||
|
{
|
||||||
|
NSLog(@"%@: set GOTFAIL (-[%@])", self, NSStringFromSelector(_cmd));
|
||||||
|
NSLog(@"%@: error %@", self, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
_done = YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* end of NSURLConnectionDelegate */
|
||||||
|
|
||||||
|
@end /* NSURLConnectionTest */
|
||||||
|
|
||||||
|
@implementation NSURLConnectionTest (Private)
|
||||||
|
|
||||||
|
- (void)_makeRequest:(id)extra
|
||||||
|
{
|
||||||
|
BOOL isOwnServer = YES;
|
||||||
|
BOOL isOwnAuxServer = NO;
|
||||||
|
NSString *tmp;
|
||||||
|
BOOL isDetached = NO;
|
||||||
|
TestWebServer *instance = nil;
|
||||||
|
TestWebServer *auxInstance = nil;
|
||||||
|
NSString *protocol = nil;
|
||||||
|
NSString *address = nil;
|
||||||
|
NSString *auxPort = nil;
|
||||||
|
NSString *port = nil;
|
||||||
|
// NSString *login = nil;
|
||||||
|
// NSString *password = nil;
|
||||||
|
NSString *path = nil;
|
||||||
|
NSString *redirectPath = nil;
|
||||||
|
NSString *statusCode = nil;
|
||||||
|
NSString *redirectCode;
|
||||||
|
NSString *method = nil;
|
||||||
|
id payload = nil;
|
||||||
|
id content = nil;
|
||||||
|
NSURL *url;
|
||||||
|
NSDictionary *d = nil;
|
||||||
|
|
||||||
|
if([extra isKindOfClass: [NSDictionary class]])
|
||||||
|
{
|
||||||
|
d = extra;
|
||||||
|
|
||||||
|
instance = [d objectForKey: @"Instance"];
|
||||||
|
if(nil != instance)
|
||||||
|
{
|
||||||
|
NSString *value;
|
||||||
|
|
||||||
|
protocol = [instance isSecure] ? @"https" : @"http";
|
||||||
|
if((value = [d objectForKey: @"Protocol"]) == nil ||
|
||||||
|
![[value lowercaseString] isEqualToString: protocol])
|
||||||
|
{
|
||||||
|
d = [d mutableCopy];
|
||||||
|
[(NSMutableDictionary *)d setObject: protocol forKey: @"Protocol"];
|
||||||
|
[d autorelease];
|
||||||
|
}
|
||||||
|
|
||||||
|
address = [instance address];
|
||||||
|
port = [instance port];
|
||||||
|
ASSIGN(_server, instance);
|
||||||
|
[_server setDelegate: self];
|
||||||
|
[_server setDebug: _debug];
|
||||||
|
isOwnServer = NO;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
protocol = [[d objectForKey: @"Protocol"] lowercaseString];
|
||||||
|
address = [d objectForKey: @"Address"];
|
||||||
|
port = [d objectForKey: @"Port"];
|
||||||
|
}
|
||||||
|
|
||||||
|
auxInstance = [d objectForKey: @"AuxInstance"];
|
||||||
|
if(nil != auxInstance)
|
||||||
|
{
|
||||||
|
auxPort = [auxInstance port];
|
||||||
|
ASSIGN(_auxServer, auxInstance);
|
||||||
|
[_auxServer setDelegate: self];
|
||||||
|
[_auxServer setDebug: _debug];
|
||||||
|
isOwnAuxServer = NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isOwnServer)
|
||||||
|
{
|
||||||
|
if((tmp = [d objectForKey: @"IsDetached"]) != nil)
|
||||||
|
{
|
||||||
|
if([tmp isEqualToString: @"YES"])
|
||||||
|
{
|
||||||
|
isDetached = YES;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
isDetached = NO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isOwnAuxServer = [[d objectForKey: @"IsAuxilliary"] isEqualToString: @"YES"] &&
|
||||||
|
nil == _auxServer;
|
||||||
|
if(isOwnAuxServer)
|
||||||
|
{
|
||||||
|
auxPort = [d objectForKey: @"AuxPort"];
|
||||||
|
}
|
||||||
|
|
||||||
|
path = [d objectForKey: @"Path"];
|
||||||
|
if(isOwnAuxServer || nil != _auxServer)
|
||||||
|
{
|
||||||
|
redirectPath = [d objectForKey: @"RedirectPath"];
|
||||||
|
}
|
||||||
|
statusCode = [d objectForKey: @"StatusCode"];
|
||||||
|
// TODO: conditions?
|
||||||
|
redirectCode = [d objectForKey: @"RedirectCode"];
|
||||||
|
method = [d objectForKey: @"Method"];
|
||||||
|
payload = [d objectForKey: @"Payload"];
|
||||||
|
content = [d objectForKey: @"Content"];
|
||||||
|
} // Is extra NSDictionary?
|
||||||
|
else if([extra isKindOfClass: [NSURLRequest class]])
|
||||||
|
{
|
||||||
|
ASSIGN(_request, extra);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(nil == _request)
|
||||||
|
{
|
||||||
|
if(nil == protocol)
|
||||||
|
{
|
||||||
|
protocol = @"http";
|
||||||
|
}
|
||||||
|
if(nil == address)
|
||||||
|
{
|
||||||
|
address = @"localhost";
|
||||||
|
}
|
||||||
|
if(nil == port)
|
||||||
|
{
|
||||||
|
port = @"54321";
|
||||||
|
}
|
||||||
|
if(nil == auxPort)
|
||||||
|
{
|
||||||
|
auxPort = @"54322";
|
||||||
|
}
|
||||||
|
if(nil == path)
|
||||||
|
{
|
||||||
|
path = @"/";
|
||||||
|
}
|
||||||
|
if((isOwnAuxServer || nil != _auxServer) && nil == redirectPath)
|
||||||
|
{
|
||||||
|
redirectPath = path;
|
||||||
|
}
|
||||||
|
if(nil != redirectPath && nil == redirectCode)
|
||||||
|
{
|
||||||
|
_redirectStatusCode = 301;
|
||||||
|
}
|
||||||
|
if(nil == statusCode)
|
||||||
|
{
|
||||||
|
_expectedStatusCode = 204;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_expectedStatusCode = [statusCode intValue];
|
||||||
|
if(_expectedStatusCode == 0)
|
||||||
|
{
|
||||||
|
[NSException raise: NSInvalidArgumentException
|
||||||
|
format: @"Invalid expected 'StatusCode' supplied %@", statusCode];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(nil == redirectCode)
|
||||||
|
{
|
||||||
|
_redirectStatusCode = 301;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_redirectStatusCode = [redirectCode intValue];
|
||||||
|
if(_redirectStatusCode == 0)
|
||||||
|
{
|
||||||
|
[NSException raise: NSInvalidArgumentException
|
||||||
|
format: @"Invalid expected 'RedirectCode' supplied %@", redirectCode];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(nil == method)
|
||||||
|
{
|
||||||
|
method = @"GET";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(![path hasPrefix: @"/"])
|
||||||
|
{
|
||||||
|
// path MUST begin with '/'
|
||||||
|
path = [@"/" stringByAppendingString: path];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(![redirectPath hasPrefix: @"/"])
|
||||||
|
{
|
||||||
|
// path MUST begin with '/'
|
||||||
|
redirectPath = [@"/" stringByAppendingString: redirectPath];
|
||||||
|
}
|
||||||
|
|
||||||
|
url = [NSURL URLWithString:
|
||||||
|
[NSString stringWithFormat: @"%@://%@:%@%@", protocol, address, auxPort, redirectPath]];
|
||||||
|
_redirectRequest = [NSMutableURLRequest requestWithURL: url];
|
||||||
|
RETAIN(_redirectRequest);
|
||||||
|
|
||||||
|
url = [NSURL URLWithString:
|
||||||
|
[NSString stringWithFormat: @"%@://%@:%@%@", protocol, address, port, path]];
|
||||||
|
_request = [NSMutableURLRequest requestWithURL: url];
|
||||||
|
RETAIN(_request);
|
||||||
|
|
||||||
|
[(NSMutableURLRequest *)_request setHTTPMethod: method];
|
||||||
|
if(nil != payload)
|
||||||
|
{
|
||||||
|
if([payload isKindOfClass: [NSString class]])
|
||||||
|
{
|
||||||
|
payload = [payload dataUsingEncoding: NSUTF8StringEncoding];
|
||||||
|
}
|
||||||
|
if(![payload isKindOfClass: [NSData class]])
|
||||||
|
{
|
||||||
|
[NSException raise: NSInvalidArgumentException
|
||||||
|
format: @"invalid payload"];
|
||||||
|
}
|
||||||
|
[(NSMutableURLRequest *)_request setHTTPBody: payload];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(nil != content)
|
||||||
|
{
|
||||||
|
if([content isKindOfClass: [NSString class]])
|
||||||
|
{
|
||||||
|
content = [content dataUsingEncoding: NSUTF8StringEncoding];
|
||||||
|
}
|
||||||
|
if(![content isKindOfClass: [NSData class]])
|
||||||
|
{
|
||||||
|
[NSException raise: NSInvalidArgumentException
|
||||||
|
format: @"invalid content"];
|
||||||
|
}
|
||||||
|
ASSIGN(_expectedContent, content);
|
||||||
|
}
|
||||||
|
if(nil != _expectedContent && nil == statusCode)
|
||||||
|
{
|
||||||
|
_expectedStatusCode = 200;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isOwnServer)
|
||||||
|
{
|
||||||
|
_server = [[TestWebServer alloc] initWithAddress: address
|
||||||
|
port: port
|
||||||
|
mode: isDetached
|
||||||
|
extra: d];
|
||||||
|
[_server setDebug: _debug];
|
||||||
|
[_server setDelegate: self];
|
||||||
|
[_server start: d];
|
||||||
|
}
|
||||||
|
if(isOwnAuxServer)
|
||||||
|
{
|
||||||
|
_auxServer = [[TestWebServer alloc] initWithAddress: address
|
||||||
|
port: auxPort
|
||||||
|
mode: isDetached
|
||||||
|
extra: d];
|
||||||
|
[_auxServer setDebug: _debug];
|
||||||
|
[_auxServer setDelegate: self];
|
||||||
|
[_auxServer start: d];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)_makeReferenceFlags:(id)extra
|
||||||
|
{
|
||||||
|
// trying to guess the execution path
|
||||||
|
// and to infer the reference flag set
|
||||||
|
if(nil != _request)
|
||||||
|
{
|
||||||
|
// default reference set
|
||||||
|
[self setReferenceFlags: SENTREQUEST];
|
||||||
|
[self setReferenceFlags: NOTAUTHORIZED];
|
||||||
|
[self setReferenceFlags: AUTHORIZED];
|
||||||
|
[self setReferenceFlags: GOTUNAUTHORIZED];
|
||||||
|
[self setReferenceFlags: GOTREQUEST];
|
||||||
|
[self setReferenceFlags: SENTRESPONSE];
|
||||||
|
[self setReferenceFlags: GOTRESPONSE];
|
||||||
|
if(nil != _expectedContent)
|
||||||
|
{
|
||||||
|
[self setReferenceFlags: GOTCONTENT];
|
||||||
|
}
|
||||||
|
[self setReferenceFlags: GOTFINISH];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
[NSException raise: NSInvalidArgumentException
|
||||||
|
format: @"no request"];
|
||||||
|
}
|
||||||
|
|
||||||
|
if([extra isKindOfClass: [NSDictionary class]])
|
||||||
|
{
|
||||||
|
// make correction in the reference flag set
|
||||||
|
NSDictionary *d = extra;
|
||||||
|
if((d = [d objectForKey: @"ReferenceFlags"]) != nil)
|
||||||
|
{
|
||||||
|
NSEnumerator *en = [d keyEnumerator];
|
||||||
|
NSString *key;
|
||||||
|
NSString *value;
|
||||||
|
SEL sel;
|
||||||
|
|
||||||
|
while((key = [en nextObject]) != nil)
|
||||||
|
{
|
||||||
|
value = [d objectForKey: key];
|
||||||
|
NSString *originalKey = nil;
|
||||||
|
NSUInteger flag = NORESULTS;
|
||||||
|
// NSUInteger flag = (NSUInteger)NSMapGet(_flagMap, key);
|
||||||
|
if(NSMapMember(_flagMap, key, (void **)&originalKey, (void **)&flag))
|
||||||
|
{
|
||||||
|
if([value isEqualToString: @"YES"])
|
||||||
|
{
|
||||||
|
sel = @selector(setReferenceFlags:);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sel = @selector(unsetReferenceFlags:);
|
||||||
|
}
|
||||||
|
|
||||||
|
[self performSelector: sel withObject: (void *)flag];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
[NSException raise: NSInternalInconsistencyException
|
||||||
|
format: @"flag codes corrupted (key %@)", key];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@end /* NSURLConnectionTest (Private) */
|
29
Tests/base/NSURLConnection/Helpers/README
Normal file
29
Tests/base/NSURLConnection/Helpers/README
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
The directory contains classes useful in testing of web-clients.
|
||||||
|
They are written mainly with the necessity to test the class NSURLConnection
|
||||||
|
in mind.
|
||||||
|
|
||||||
|
TestCase
|
||||||
|
NSURLConnectionTest
|
||||||
|
is a child of TestCase and represents a test of NSURLConnection.
|
||||||
|
It starts a main instance and (if needed) an auxillary instance
|
||||||
|
of TestWebServer (or uses externally supplied ones). Then it makes
|
||||||
|
a custom request to check various functionality of NSURLConnection.
|
||||||
|
|
||||||
|
TestWebServer
|
||||||
|
maintains it's own instance of SimpleWebServer and implements
|
||||||
|
SimpleWebServer delegate's protocol to dispatch any request
|
||||||
|
to a corresponding handler. It can call it's delegate during
|
||||||
|
proceeding of requests.
|
||||||
|
|
||||||
|
RequestHandler
|
||||||
|
TestHandler and it's descendants
|
||||||
|
handle requests (dispatched by TestWebServer) and produces
|
||||||
|
a custom response. It also calls it's delegate (using TestWebServer
|
||||||
|
delegate's protocol) during request handling.
|
||||||
|
|
||||||
|
SimpleWebServer
|
||||||
|
The own implementation of a web server.
|
||||||
|
|
||||||
|
The key protocols important for understanding are TestProgress (TestCase.h)
|
||||||
|
and TestWebServerDelegate (TestWebServer.h). Alternatively the file
|
||||||
|
NSURLConnectionTest.h briefly describes the whole picture.
|
304
Tests/base/NSURLConnection/Helpers/RequestHandler.h
Normal file
304
Tests/base/NSURLConnection/Helpers/RequestHandler.h
Normal file
|
@ -0,0 +1,304 @@
|
||||||
|
/** -*- objc -*-
|
||||||
|
*
|
||||||
|
* Author: Sergei Golovin <Golovin.SV@gmail.com>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: there is lack of session handling
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "TestWebServer.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The protocol RequestHandler describes how a request receiver should interact
|
||||||
|
* with a request handler.
|
||||||
|
*/
|
||||||
|
@protocol RequestHandler
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does all job needed to prepare for request handling. Returns YES if
|
||||||
|
* the handler still can proceed the supplied request later.
|
||||||
|
* The reason this method was introduced is possible session
|
||||||
|
* handling in the future.
|
||||||
|
*/
|
||||||
|
- (BOOL)prehandleRequest:(GSMimeDocument *)request
|
||||||
|
response:(GSMimeDocument *)response
|
||||||
|
for:(TestWebServer *)server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a custom response. Returns YES if the handler has recognized
|
||||||
|
* it is it's responsibility to handle the request.
|
||||||
|
*/
|
||||||
|
- (BOOL) handleRequest:(GSMimeDocument *)request
|
||||||
|
response:(GSMimeDocument *)response
|
||||||
|
for:(TestWebServer *)server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does all job needed to be done after request handling. Returns NO
|
||||||
|
* if the handler has discovered some inconsistency after handling.
|
||||||
|
* The reason this method was introduced is possible session
|
||||||
|
* handling in the future.
|
||||||
|
*/
|
||||||
|
- (BOOL)posthandleRequest:(GSMimeDocument *)request
|
||||||
|
response:(GSMimeDocument *)response
|
||||||
|
for:(TestWebServer *)server;
|
||||||
|
|
||||||
|
@end /* RequestHandler */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The abstract class TestHandler. Custom handlers must be derived from it.
|
||||||
|
* The class implements the protocol RequestHandler's methods that are called
|
||||||
|
* by an instance of TestWebServer. The protocol's method -[handleRequest:response:for:]
|
||||||
|
* produces a custom response. It also calls it's delegate's callback methods of
|
||||||
|
* the protocol TestWebServerDelegate (if implemented) during proceeding of a request.
|
||||||
|
*
|
||||||
|
* On use of the -[handleRequest:response:for:]
|
||||||
|
* ---------------------------------------------
|
||||||
|
*
|
||||||
|
* The TestHandler's implementation of the -[handleRequest:response:for:] checks
|
||||||
|
* whether the supplied request is authorized. It returns YES if no processing is
|
||||||
|
* required (by a child) so the response must be returned intact. If the method
|
||||||
|
* returns NO then the child MAY process the request on it's own. If the request is
|
||||||
|
* authorized the method also sets the flag _isAuthorized to YES.
|
||||||
|
*
|
||||||
|
* Child's implementation MUST call this super's one as in the following
|
||||||
|
* example code:
|
||||||
|
* ------------------------------------------------------------------------
|
||||||
|
* - (BOOL) handleRequest: (GSMimeDocument*)request
|
||||||
|
* response: (GSMimeDocument*)response
|
||||||
|
* for: (TestWebServer *)server
|
||||||
|
* {
|
||||||
|
* BOOL ret = [super handleRequest: request response: response for: server];
|
||||||
|
* if(NO == ret)
|
||||||
|
* {
|
||||||
|
* // process on it's own with possible checking _isAuthorized if needed
|
||||||
|
* ...
|
||||||
|
* _done = YES;
|
||||||
|
* ret = YES;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* return ret;
|
||||||
|
* }
|
||||||
|
* ------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* The method recognizes the case when the request's path contains '/withoutauth'.
|
||||||
|
* In this case no authorization is required and the method -[handleRequest:response:for:]
|
||||||
|
* just returns NO and sets the flag _isAuthorized to YES.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@interface TestHandler : NSObject <RequestHandler>
|
||||||
|
{
|
||||||
|
/* the debug mode switch */
|
||||||
|
BOOL _debug;
|
||||||
|
/* the handler's delegate... NOT RETAINED */
|
||||||
|
id _delegate;
|
||||||
|
/* the flag signifies the handler has got an authorized request */
|
||||||
|
BOOL _isAuthorized;
|
||||||
|
/* the login for basic authentication */
|
||||||
|
NSString *_login;
|
||||||
|
/* the password for basic authentication */
|
||||||
|
NSString *_password;
|
||||||
|
/* the flag for successful handling...
|
||||||
|
can be used in the -[posthandleRequest:response:for:] */
|
||||||
|
BOOL _done;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id)init;
|
||||||
|
- (void)dealloc;
|
||||||
|
|
||||||
|
/* getters */
|
||||||
|
/* end of getters */
|
||||||
|
|
||||||
|
/* setters */
|
||||||
|
/**
|
||||||
|
* Switches the debug mode on/off.
|
||||||
|
*/
|
||||||
|
- (void)setDebug:(BOOL)flag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the delegate implementing TestWebServerDelegate protocol.
|
||||||
|
*/
|
||||||
|
- (void)setDelegate:(id)delegate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the login for requests with authorization.
|
||||||
|
*/
|
||||||
|
- (void)setLogin:(NSString *)login;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the password for requests with authorization.
|
||||||
|
*/
|
||||||
|
- (void)setPassword:(NSString *)password;
|
||||||
|
|
||||||
|
/* end of setters */
|
||||||
|
|
||||||
|
@end /* TestHandler */
|
||||||
|
|
||||||
|
/* The handler collection */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The handler returns the status code 200 for any request.
|
||||||
|
* The response's content is 'OK'. The status line is the default one
|
||||||
|
* returned by the TestWebServer. The header 'Content-Type' is set to 'plain/text'.
|
||||||
|
*/
|
||||||
|
@interface Handler200 : TestHandler
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@end /* Handler200 */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The handler returns the status code 204 for any request.
|
||||||
|
* The response's content is empty. The status line is 'HTTP/1.1 204 No Content'.
|
||||||
|
*/
|
||||||
|
@interface Handler204 : TestHandler
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@end /* Handler204 */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The handler returns the status code 301 for any request.
|
||||||
|
* The response's content is an html page like 'Redirect to <a href="URL">'.
|
||||||
|
* The status line is 'HTTP/1.1 301 Moved permanently'. It also sets
|
||||||
|
* the response's header 'Location' to the custom URL stored by the
|
||||||
|
* ivar _URLString. If the -[setURLString:] wasn't called with a proper URL
|
||||||
|
* string before the first request arrives then the handler will raise
|
||||||
|
* NSInternalInconsistencyException.
|
||||||
|
*/
|
||||||
|
@interface Handler301 : TestHandler
|
||||||
|
{
|
||||||
|
NSString *_URLString;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the URL to which the handler should redirect.
|
||||||
|
*/
|
||||||
|
- (void)setURLString:(NSString *)URLString;
|
||||||
|
|
||||||
|
@end /* Handler301 */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The handler returns unconditionally the status code 400 for any request.
|
||||||
|
* The response's content is 'You have issued a request with invalid data'.
|
||||||
|
* The status line is 'HTTP/1.1 400 Bad Request'.
|
||||||
|
*/
|
||||||
|
@interface Handler400 : TestHandler
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@end /* Handler400 */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The handler returns unconditionally the status code 401 for any request.
|
||||||
|
* The response's content is 'Invalid login or password'.
|
||||||
|
* The status line is 'HTTP/1.1 401 Unauthorized'. It also sets the response's
|
||||||
|
* header 'WWW-Authenticate' to the value "Basic realm='TestSuit'".
|
||||||
|
*/
|
||||||
|
@interface Handler401 : TestHandler
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@end /* Handler401 */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The handler returns unconditionally the status code 402 for any request.
|
||||||
|
* The response's content is 'Check your balance'.
|
||||||
|
* The status line is 'HTTP/1.1 402 Payment required'.
|
||||||
|
*/
|
||||||
|
@interface Handler402 : TestHandler
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@end /* Handler402 */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The handler returns unconditionally the status code 404 for any request.
|
||||||
|
* The response's content is 'The resource you are trying to access is not found'.
|
||||||
|
* The status line is 'HTTP/1.1 404 Not found'.
|
||||||
|
*/
|
||||||
|
@interface Handler404 : TestHandler
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@end /* Handler404 */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The handler returns unconditionally the status code 405 for any request.
|
||||||
|
* The response's content is 'You can't do that'.
|
||||||
|
* The status line is 'HTTP/1.1 405 Method not allowed'.
|
||||||
|
*/
|
||||||
|
@interface Handler405 : TestHandler
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@end /* Handler405 */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The handler returns unconditionally the status code 409 for any request.
|
||||||
|
* The response's content is 'The resource you are trying to access is busy'.
|
||||||
|
* The status line is 'HTTP/1.1 409 Conflict'.
|
||||||
|
*/
|
||||||
|
@interface Handler409 : TestHandler
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@end /* Handler409 */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The handler returns unconditionally the status code 500 for any request.
|
||||||
|
* The response's content is 'System error'.
|
||||||
|
* The status line is 'HTTP/1.1 500 Internal Server Error'.
|
||||||
|
*/
|
||||||
|
@interface Handler500 : TestHandler
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@end /* Handler500 */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The handler returns unconditionally the status code 505 for any request.
|
||||||
|
* The response's content is 'There is network protocol inconsistency'.
|
||||||
|
* The status line is 'HTTP/1.1 505 Network Protocol Error'.
|
||||||
|
*/
|
||||||
|
@interface Handler505 : TestHandler
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@end /* Handler505 */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The handler returns unconditionally the status code 507 for any request.
|
||||||
|
* The response's content is 'Insufficient storage'.
|
||||||
|
* The status line is 'HTTP/1.1 507 Insufficient storage'.
|
||||||
|
*/
|
||||||
|
@interface Handler507 : TestHandler
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@end /* Handler507 */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The handler returns the index page with a list of possible URLs for any request.
|
||||||
|
* The URLs are constructed from the base URL stored by the ivar _URLString. If
|
||||||
|
* the -[setURLString:] wasn't called with a proper URL string before the first
|
||||||
|
* request arrives then the handler will raise NSInternalInconsistencyException.
|
||||||
|
*/
|
||||||
|
@interface HandlerIndex : TestHandler
|
||||||
|
{
|
||||||
|
NSString *_URLString;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the URL to which the handler should redirect.
|
||||||
|
*/
|
||||||
|
- (void)setURLString:(NSString *)URLString;
|
||||||
|
|
||||||
|
@end /* HandlerIndex */
|
||||||
|
|
||||||
|
/* The end of the handler collection */
|
601
Tests/base/NSURLConnection/Helpers/RequestHandler.m
Normal file
601
Tests/base/NSURLConnection/Helpers/RequestHandler.m
Normal file
|
@ -0,0 +1,601 @@
|
||||||
|
/** -*- objc -*-
|
||||||
|
*
|
||||||
|
* Author: Sergei Golovin <Golovin.SV@gmail.com>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "RequestHandler.h"
|
||||||
|
#import "TestWebServer.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The abstract class TestHandler. All custom handlers must be derived from it.
|
||||||
|
*/
|
||||||
|
@implementation TestHandler
|
||||||
|
|
||||||
|
- (id)init
|
||||||
|
{
|
||||||
|
if((self = [super init]) != nil)
|
||||||
|
{
|
||||||
|
_debug = NO;
|
||||||
|
_delegate = nil;
|
||||||
|
_isAuthorized = NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
_delegate = nil;
|
||||||
|
[super dealloc];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* getters */
|
||||||
|
/* end of getters */
|
||||||
|
|
||||||
|
/* setters */
|
||||||
|
- (void)setDebug:(BOOL)flag
|
||||||
|
{
|
||||||
|
_debug = flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setDelegate:(id)delegate
|
||||||
|
{
|
||||||
|
_delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setLogin:(NSString *)login
|
||||||
|
{
|
||||||
|
ASSIGN(_login, login);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setPassword:(NSString *)password
|
||||||
|
{
|
||||||
|
ASSIGN(_password, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* end of setters */
|
||||||
|
|
||||||
|
/* RequestHandler */
|
||||||
|
|
||||||
|
- (BOOL)prehandleRequest:(GSMimeDocument *)request
|
||||||
|
response:(GSMimeDocument *)response
|
||||||
|
for:(TestWebServer *)server
|
||||||
|
{
|
||||||
|
_isAuthorized = NO; // TODO: move to something like -[reset]
|
||||||
|
_done = NO;
|
||||||
|
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL) handleRequest: (GSMimeDocument*)request
|
||||||
|
response: (GSMimeDocument*)response
|
||||||
|
for: (TestWebServer*)server
|
||||||
|
{
|
||||||
|
NSString *auth;
|
||||||
|
NSString *path;
|
||||||
|
BOOL ret = NO;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if(YES == _debug)
|
||||||
|
{
|
||||||
|
NSLog(@"%@: BEGIN\n%@", self, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(nil != _delegate && [_delegate respondsToSelector: @selector(handler:gotRequest:with:)])
|
||||||
|
{
|
||||||
|
[_delegate handler: self gotRequest: request with: server];
|
||||||
|
}
|
||||||
|
|
||||||
|
// analyze what the client wants
|
||||||
|
path = [[request headerNamed:@"x-http-path"] value];
|
||||||
|
|
||||||
|
if([path rangeOfString: @"withoutauth"].location == NSNotFound)
|
||||||
|
{
|
||||||
|
auth = [[request headerNamed: @"Authorization"] value];
|
||||||
|
if(!auth) // not authorized
|
||||||
|
{
|
||||||
|
[response setHeader: @"http" value: @"HTTP/1.1 401 Unauthorized" parameters: nil];
|
||||||
|
[response setHeader: @"WWW-Authenticate" value: @"Basic realm='TestSuit'" parameters: nil];
|
||||||
|
[response setContent: @"Please give login and password"];
|
||||||
|
|
||||||
|
if(nil != _delegate && [_delegate respondsToSelector: @selector(handler:willSendUnauthorized:with:)])
|
||||||
|
{
|
||||||
|
[_delegate handler: self willSendUnauthorized: response with: server];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(YES == _debug)
|
||||||
|
{
|
||||||
|
NSLog(@"%@: about to send Unauthorized\n%@", self, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
else // the auth header is set
|
||||||
|
{
|
||||||
|
NSArray *credentials;
|
||||||
|
|
||||||
|
credentials = [auth componentsSeparatedByString: @" "];
|
||||||
|
if([[credentials objectAtIndex:0] isEqualToString: @"Basic"])
|
||||||
|
{
|
||||||
|
credentials = [[GSMimeDocument decodeBase64String: [credentials objectAtIndex:1]]
|
||||||
|
componentsSeparatedByString: @":"];
|
||||||
|
if([[credentials objectAtIndex:0] isEqualToString: _login] &&
|
||||||
|
[[credentials objectAtIndex:1] isEqualToString: _password])
|
||||||
|
{
|
||||||
|
if(YES == _debug)
|
||||||
|
{
|
||||||
|
NSLog(@"%@: got valid credentials", self);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(nil != _delegate && [_delegate respondsToSelector: @selector(handler:gotAuthorized:with:)])
|
||||||
|
{
|
||||||
|
[_delegate handler: self gotAuthorized: request with: server];
|
||||||
|
}
|
||||||
|
|
||||||
|
// sets the flag
|
||||||
|
_isAuthorized = YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
[response setHeader: @"http" value: @"HTTP/1.1 401 Unauthorized" parameters: nil];
|
||||||
|
[response setHeader: @"WWW-Authenticate" value: @"Basic realm='TestSuit'" parameters: nil];
|
||||||
|
[response setContent: @"Try another login or password"];
|
||||||
|
|
||||||
|
if(nil != _delegate && [_delegate respondsToSelector: @selector(handler:willSendUnauthorized:with:)])
|
||||||
|
{
|
||||||
|
[_delegate handler: self willSendUnauthorized: response with: server];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(YES == _debug)
|
||||||
|
{
|
||||||
|
NSLog(@"%@: about to send Unauthorized\n%@", self, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // ! withoutauth
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// just sets the flag
|
||||||
|
_isAuthorized = YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)posthandleRequest:(GSMimeDocument *)request
|
||||||
|
response:(GSMimeDocument *)response
|
||||||
|
for:(TestWebServer *)server
|
||||||
|
{
|
||||||
|
BOOL ret = NO;
|
||||||
|
|
||||||
|
if(YES == _done &&
|
||||||
|
nil != _delegate &&
|
||||||
|
[_delegate respondsToSelector: @selector(handler:willSend:with:)])
|
||||||
|
{
|
||||||
|
[_delegate handler: self willSend: response with: server];
|
||||||
|
|
||||||
|
ret = YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: may be to move at another place?
|
||||||
|
if(YES == _debug)
|
||||||
|
{
|
||||||
|
NSLog(@"%@: about to send\n%@", self, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* end of RequestHandler */
|
||||||
|
|
||||||
|
|
||||||
|
@end /* TestHandler */
|
||||||
|
|
||||||
|
@implementation Handler200
|
||||||
|
|
||||||
|
- (BOOL) handleRequest: (GSMimeDocument*)request
|
||||||
|
response: (GSMimeDocument*)response
|
||||||
|
for: (TestWebServer*)server
|
||||||
|
{
|
||||||
|
BOOL ret = [super handleRequest: request response: response for: server];
|
||||||
|
if(NO == ret)
|
||||||
|
{
|
||||||
|
BOOL isToSetResponse = (nil == [response content]) || ([[response content] length] == 0);
|
||||||
|
if(isToSetResponse)
|
||||||
|
{
|
||||||
|
[response setContent: @"OK"];
|
||||||
|
}
|
||||||
|
[response setHeader: @"HTTP" value: @"HTTP/1.1 200 OK" parameters:nil];
|
||||||
|
[response setHeader: @"content-type" value: @"plain/text" parameters: nil];
|
||||||
|
_done = YES;
|
||||||
|
ret = YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end /* Handler200 */
|
||||||
|
|
||||||
|
@implementation Handler204
|
||||||
|
|
||||||
|
- (BOOL) handleRequest: (GSMimeDocument*)request
|
||||||
|
response: (GSMimeDocument*)response
|
||||||
|
for: (TestWebServer*)server
|
||||||
|
{
|
||||||
|
BOOL ret = [super handleRequest: request response: response for: server];
|
||||||
|
if(NO == ret)
|
||||||
|
{
|
||||||
|
[response setContent: @""];
|
||||||
|
[response setHeader: @"HTTP" value: @"HTTP/1.1 204 No Content" parameters:nil];
|
||||||
|
|
||||||
|
_done = YES;
|
||||||
|
ret = YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end /* Handler204 */
|
||||||
|
|
||||||
|
@implementation Handler301
|
||||||
|
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
DESTROY(_URLString);
|
||||||
|
[super dealloc];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setURLString:(NSString *)URLString
|
||||||
|
{
|
||||||
|
ASSIGN(_URLString, URLString);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL) handleRequest: (GSMimeDocument*)request
|
||||||
|
response: (GSMimeDocument*)response
|
||||||
|
for: (TestWebServer*)server
|
||||||
|
{
|
||||||
|
BOOL ret = [super handleRequest: request response: response for: server];
|
||||||
|
if(NO == ret)
|
||||||
|
{
|
||||||
|
if(nil != _URLString)
|
||||||
|
{
|
||||||
|
BOOL isToSetResponse = (nil == [response content]) || ([[response content] length] == 0);
|
||||||
|
[response setHeader: @"HTTP" value: @"HTTP/1.1 301 Moved Permanently" parameters: nil];
|
||||||
|
[response setHeader: @"Location" value: _URLString parameters: nil];
|
||||||
|
if(isToSetResponse)
|
||||||
|
{
|
||||||
|
NSString *method = [[request headerNamed: @"x-http-method"] value];
|
||||||
|
if(![method isEqualToString: @"HEAD"])
|
||||||
|
{
|
||||||
|
NSString *note = [NSString stringWithFormat: @"<html><head></head><body>Redirect to <a href='%@'/></body></html>",
|
||||||
|
_URLString];
|
||||||
|
[response setContent: note];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
[NSException raise: NSInternalInconsistencyException
|
||||||
|
format: @"%@: URL isn't set", self];
|
||||||
|
}
|
||||||
|
|
||||||
|
_done = YES;
|
||||||
|
ret = YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end /* Handler301 */
|
||||||
|
|
||||||
|
@implementation Handler400
|
||||||
|
|
||||||
|
- (BOOL) handleRequest: (GSMimeDocument*)request
|
||||||
|
response: (GSMimeDocument*)response
|
||||||
|
for: (TestWebServer*)server
|
||||||
|
{
|
||||||
|
BOOL ret = [super handleRequest: request response: response for: server];
|
||||||
|
if(NO == ret)
|
||||||
|
{
|
||||||
|
BOOL isToSetResponse = (nil == [response content]) || ([[response content] length] == 0);
|
||||||
|
|
||||||
|
if(isToSetResponse)
|
||||||
|
{
|
||||||
|
[response setContent: @"You have issued a request with invalid data"];
|
||||||
|
}
|
||||||
|
[response setHeader:@"HTTP" value:@"HTTP/1.1 400 Bad Request" parameters:nil];
|
||||||
|
|
||||||
|
_done = YES;
|
||||||
|
ret = YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end /* Handler400 */
|
||||||
|
|
||||||
|
@implementation Handler401
|
||||||
|
|
||||||
|
- (BOOL) handleRequest: (GSMimeDocument*)request
|
||||||
|
response: (GSMimeDocument*)response
|
||||||
|
for: (TestWebServer*)server
|
||||||
|
{
|
||||||
|
BOOL ret = [super handleRequest: request response: response for: server];
|
||||||
|
if(NO == ret)
|
||||||
|
{
|
||||||
|
BOOL isToSetResponse = (nil == [response content]) || ([[response content] length] == 0);
|
||||||
|
|
||||||
|
[response setHeader:@"HTTP" value: @"HTTP/1.1 401 Unauthorized" parameters:nil];
|
||||||
|
[response setHeader:@"WWW-Authenticate" value: @"Basic realm='TestSuit'" parameters:nil];
|
||||||
|
if(isToSetResponse)
|
||||||
|
{
|
||||||
|
[response setContent: @"Invalid login or password"];
|
||||||
|
}
|
||||||
|
|
||||||
|
_done = YES;
|
||||||
|
ret = YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end /* Handler401 */
|
||||||
|
|
||||||
|
@implementation Handler402
|
||||||
|
|
||||||
|
- (BOOL) handleRequest: (GSMimeDocument*)request
|
||||||
|
response: (GSMimeDocument*)response
|
||||||
|
for: (TestWebServer*)server
|
||||||
|
{
|
||||||
|
BOOL ret = [super handleRequest: request response: response for: server];
|
||||||
|
if(NO == ret)
|
||||||
|
{
|
||||||
|
BOOL isToSetResponse = (nil == [response content]) || ([[response content] length] == 0);
|
||||||
|
|
||||||
|
[response setHeader:@"HTTP" value:@"HTTP/1.1 402 Payment required" parameters:nil];
|
||||||
|
if(isToSetResponse)
|
||||||
|
{
|
||||||
|
[response setContent: @"Check your balance"];
|
||||||
|
}
|
||||||
|
|
||||||
|
_done = YES;
|
||||||
|
ret = YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end /* Handler402 */
|
||||||
|
|
||||||
|
@implementation Handler404
|
||||||
|
|
||||||
|
- (BOOL) handleRequest: (GSMimeDocument*)request
|
||||||
|
response: (GSMimeDocument*)response
|
||||||
|
for: (TestWebServer*)server
|
||||||
|
{
|
||||||
|
BOOL ret = [super handleRequest: request response: response for: server];
|
||||||
|
if(NO == ret)
|
||||||
|
{
|
||||||
|
BOOL isToSetResponse = (nil == [response content]) || ([[response content] length] == 0);
|
||||||
|
|
||||||
|
[response setHeader:@"HTTP" value:@"HTTP/1.1 404 Not found" parameters:nil];
|
||||||
|
if(isToSetResponse)
|
||||||
|
{
|
||||||
|
[response setContent: @"The resource you are trying to access is not found"];
|
||||||
|
}
|
||||||
|
|
||||||
|
_done = YES;
|
||||||
|
ret = YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end /* Handler404 */
|
||||||
|
|
||||||
|
@implementation Handler405
|
||||||
|
|
||||||
|
- (BOOL) handleRequest: (GSMimeDocument*)request
|
||||||
|
response: (GSMimeDocument*)response
|
||||||
|
for: (TestWebServer*)server
|
||||||
|
{
|
||||||
|
BOOL ret = [super handleRequest: request response: response for: server];
|
||||||
|
if(NO == ret)
|
||||||
|
{
|
||||||
|
BOOL isToSetResponse = (nil == [response content]) || ([[response content] length] == 0);
|
||||||
|
|
||||||
|
[response setHeader:@"HTTP" value:@"HTTP/1.1 405 Method not allowed" parameters:nil];
|
||||||
|
if(isToSetResponse)
|
||||||
|
{
|
||||||
|
[response setContent: @"You can't do that"];
|
||||||
|
}
|
||||||
|
|
||||||
|
_done = YES;
|
||||||
|
ret = YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end /* Handler405 */
|
||||||
|
|
||||||
|
@implementation Handler409
|
||||||
|
|
||||||
|
- (BOOL) handleRequest: (GSMimeDocument*)request
|
||||||
|
response: (GSMimeDocument*)response
|
||||||
|
for: (TestWebServer*)server
|
||||||
|
{
|
||||||
|
BOOL ret = [super handleRequest: request response: response for: server];
|
||||||
|
if(NO == ret)
|
||||||
|
{
|
||||||
|
BOOL isToSetResponse = (nil == [response content]) || ([[response content] length] == 0);
|
||||||
|
|
||||||
|
[response setHeader:@"HTTP" value:@"HTTP/1.1 409 Conflict" parameters:nil];
|
||||||
|
if(isToSetResponse)
|
||||||
|
{
|
||||||
|
[response setContent: @"The resource you are trying to access is busy"];
|
||||||
|
}
|
||||||
|
|
||||||
|
_done = YES;
|
||||||
|
ret = YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end /* Handler409 */
|
||||||
|
|
||||||
|
@implementation Handler500
|
||||||
|
|
||||||
|
- (BOOL) handleRequest: (GSMimeDocument*)request
|
||||||
|
response: (GSMimeDocument*)response
|
||||||
|
for: (TestWebServer*)server
|
||||||
|
{
|
||||||
|
BOOL ret = [super handleRequest: request response: response for: server];
|
||||||
|
if(NO == ret)
|
||||||
|
{
|
||||||
|
BOOL isToSetResponse = (nil == [response content]) || ([[response content] length] == 0);
|
||||||
|
|
||||||
|
[response setHeader:@"HTTP" value:@"HTTP/1.1 500 Internal Server Error" parameters:nil];
|
||||||
|
if(isToSetResponse)
|
||||||
|
{
|
||||||
|
[response setContent: @"System error"];
|
||||||
|
}
|
||||||
|
|
||||||
|
_done = YES;
|
||||||
|
ret = YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end /* Handler500 */
|
||||||
|
|
||||||
|
@implementation Handler505
|
||||||
|
|
||||||
|
- (BOOL) handleRequest: (GSMimeDocument*)request
|
||||||
|
response: (GSMimeDocument*)response
|
||||||
|
for: (TestWebServer*)server
|
||||||
|
{
|
||||||
|
BOOL ret = [super handleRequest: request response: response for: server];
|
||||||
|
if(NO == ret)
|
||||||
|
{
|
||||||
|
BOOL isToSetResponse = (nil == [response content]) || ([[response content] length] == 0);
|
||||||
|
|
||||||
|
[response setHeader:@"HTTP" value:@"HTTP/1.1 505 Network Protocol Error" parameters:nil];
|
||||||
|
if(isToSetResponse)
|
||||||
|
{
|
||||||
|
[response setContent: @"There is network protocol inconsistency"];
|
||||||
|
}
|
||||||
|
|
||||||
|
_done = YES;
|
||||||
|
ret = YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end /* Handler505 */
|
||||||
|
|
||||||
|
@implementation Handler507
|
||||||
|
|
||||||
|
- (BOOL) handleRequest: (GSMimeDocument*)request
|
||||||
|
response: (GSMimeDocument*)response
|
||||||
|
for: (TestWebServer*)server
|
||||||
|
{
|
||||||
|
BOOL ret = [super handleRequest: request response: response for: server];
|
||||||
|
if(NO == ret)
|
||||||
|
{
|
||||||
|
BOOL isToSetResponse = (nil == [response content]) || ([[response content] length] == 0);
|
||||||
|
|
||||||
|
[response setHeader:@"HTTP" value: @"HTTP/1.1 507 Insufficient storage" parameters:nil];
|
||||||
|
if(isToSetResponse)
|
||||||
|
{
|
||||||
|
[response setContent: @"Insufficient storage"];
|
||||||
|
}
|
||||||
|
|
||||||
|
_done = YES;
|
||||||
|
ret = YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end /* Handler507 */
|
||||||
|
|
||||||
|
@implementation HandlerIndex
|
||||||
|
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
DESTROY(_URLString);
|
||||||
|
[super dealloc];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setURLString:(NSString *)URLString
|
||||||
|
{
|
||||||
|
ASSIGN(_URLString, URLString);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL) handleRequest: (GSMimeDocument*)request
|
||||||
|
response: (GSMimeDocument*)response
|
||||||
|
for: (TestWebServer*)server
|
||||||
|
{
|
||||||
|
BOOL ret = [super handleRequest: request response: response for: server];
|
||||||
|
if(NO == ret)
|
||||||
|
{
|
||||||
|
if(nil != _URLString)
|
||||||
|
{
|
||||||
|
BOOL isToSetResponse = (nil == [response content]) || ([[response content] length] == 0);
|
||||||
|
if(isToSetResponse)
|
||||||
|
{
|
||||||
|
NSString *pattern = @"<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'><head></head><body>\
|
||||||
|
<a href='%@%@'>This page</a><br> \
|
||||||
|
<a href='%@%@'>Without authentication</a><br> \
|
||||||
|
<a href='%@%@'>301</a><br> \
|
||||||
|
<a href='%@%@'>400</a><br> \
|
||||||
|
<a href='%@%@'>401</a><br> \
|
||||||
|
<a href='%@%@'>402</a><br> \
|
||||||
|
<a href='%@%@'>404</a><br> \
|
||||||
|
<a href='%@%@'>405</a><br> \
|
||||||
|
<a href='%@%@'>409</a><br> \
|
||||||
|
<a href='%@%@'>500</a><br> \
|
||||||
|
<a href='%@%@'>505</a><br> \
|
||||||
|
<a href='%@%@'>507</a><br> \
|
||||||
|
</body></html>";
|
||||||
|
NSString *index = [NSString stringWithFormat: pattern,
|
||||||
|
_URLString, @"index",
|
||||||
|
_URLString, @"withoutauth",
|
||||||
|
_URLString, @"301",
|
||||||
|
_URLString, @"400",
|
||||||
|
_URLString, @"401",
|
||||||
|
_URLString, @"402",
|
||||||
|
_URLString, @"404",
|
||||||
|
_URLString, @"405",
|
||||||
|
_URLString, @"409",
|
||||||
|
_URLString, @"500",
|
||||||
|
_URLString, @"505",
|
||||||
|
_URLString, @"507"];
|
||||||
|
[response setContent: index];
|
||||||
|
[response setHeader: @"content-type" value: @"text/html" parameters: nil];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
[NSException raise: NSInternalInconsistencyException
|
||||||
|
format: @"%@: URL isn't set", self];
|
||||||
|
}
|
||||||
|
|
||||||
|
_done = YES;
|
||||||
|
ret = YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end /* HandlerIndex */
|
||||||
|
|
131
Tests/base/NSURLConnection/Helpers/SimpleWebServer.h
Normal file
131
Tests/base/NSURLConnection/Helpers/SimpleWebServer.h
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
/** -*- objc -*-
|
||||||
|
*
|
||||||
|
* Author: Sergei Golovin <Golovin.SV@gmail.com>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import <GNUstepBase/GSMime.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a simple web server with delegate interaction which mimic of
|
||||||
|
* the WebServer's delegate protocol.
|
||||||
|
*
|
||||||
|
* The SimpleWebServer class currently has many limitations (deficiencies).
|
||||||
|
* The following is a list of most important ones:
|
||||||
|
* - supports only one connection simultaneously.
|
||||||
|
* - doesn't support any transfer-content-encoding (more precisely it uses
|
||||||
|
* only 'identity' transfer-content-encoding that is without any modification
|
||||||
|
* of the content/message);
|
||||||
|
* - the class uses UTF8 by default. It expects a request and produces responses
|
||||||
|
* in that encoding;
|
||||||
|
* - it expects an explicit request for closing of the connection (that is
|
||||||
|
* the request's header 'Connection' must be 'close') or implicitly does it
|
||||||
|
* if no 'Connection' has been supplied;
|
||||||
|
* - doesn't support pipelining of requests;
|
||||||
|
*
|
||||||
|
* Use the -[setDebug: YES] to raise verbosity.
|
||||||
|
*/
|
||||||
|
@interface SimpleWebServer : NSObject
|
||||||
|
{
|
||||||
|
/* holds the GSServerStream accepting incoming connections */
|
||||||
|
GSServerStream *_serverStream;
|
||||||
|
/* the delegate ... NOT RETAINED...
|
||||||
|
* see below the protocol SimpleWebServerDelegate */
|
||||||
|
id _delegate;
|
||||||
|
/* the debug mode */
|
||||||
|
BOOL _debug;
|
||||||
|
/* the address to bind with */
|
||||||
|
NSString *_address;
|
||||||
|
/* the port to listen to */
|
||||||
|
NSString *_port;
|
||||||
|
/* SSL configuration and options */
|
||||||
|
NSDictionary *_secure;
|
||||||
|
|
||||||
|
/* The following web-server code is derived/stolen from NSURL/Helpers/capture.m */
|
||||||
|
|
||||||
|
/* the stream to send */
|
||||||
|
NSOutputStream *_op;
|
||||||
|
/* the stream to receive */
|
||||||
|
NSInputStream *_ip;
|
||||||
|
/* the collector of received bytes from a client */
|
||||||
|
NSMutableData *_capture;
|
||||||
|
/* the number of sent bytes to a client */
|
||||||
|
unsigned _written;
|
||||||
|
/* the flag indicating the instance is collecting bytes from a client */
|
||||||
|
BOOL _readable;
|
||||||
|
/* the flag indicating the instance is sending bytes to a client */
|
||||||
|
BOOL _writable;
|
||||||
|
/* whether to use a secure TLS/SSL connection */
|
||||||
|
BOOL _isSecure;
|
||||||
|
/* the request is read */
|
||||||
|
BOOL _doRespond;
|
||||||
|
/* the response is written */
|
||||||
|
BOOL _done;
|
||||||
|
/* end of the stolen */
|
||||||
|
|
||||||
|
/* wether the output stream is ready to write */
|
||||||
|
BOOL _canRespond;
|
||||||
|
|
||||||
|
/* holds the current request */
|
||||||
|
GSMimeDocument *_request;
|
||||||
|
/* holds the current response */
|
||||||
|
GSMimeDocument *_response;
|
||||||
|
}
|
||||||
|
- (void)dealloc;
|
||||||
|
|
||||||
|
/* getters */
|
||||||
|
/**
|
||||||
|
* Returns the string of the port number if the instance is accepting
|
||||||
|
* connections (is started). Otherwise returns nil.
|
||||||
|
*/
|
||||||
|
- (NSString *)port;
|
||||||
|
/* end of getters */
|
||||||
|
|
||||||
|
/* setters */
|
||||||
|
/**
|
||||||
|
* Starts the simple web server listening on the supplied address and port.
|
||||||
|
* The dictionary 'dict' is supplied with additional configuration parameters.
|
||||||
|
* connections. The dictionary's keys are:
|
||||||
|
* CertificateFile
|
||||||
|
* the path to a certificate (if the web server should wait for HTTPS)
|
||||||
|
* KeyFile
|
||||||
|
* the path to a key (if the web server should wait for HTTPS)
|
||||||
|
*/
|
||||||
|
- (BOOL)setAddress:(NSString *)address
|
||||||
|
port:(NSString *)port
|
||||||
|
secure:(NSDictionary *)dict;
|
||||||
|
/**
|
||||||
|
* Sets the debug mode.
|
||||||
|
*/
|
||||||
|
- (void)setDebug:(BOOL)flag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the delegate responding to the selector -[processRequest:response:].
|
||||||
|
*/
|
||||||
|
- (void)setDelegate:(id)delegate;
|
||||||
|
/* end of setters */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Commands the web server to stop listening.
|
||||||
|
*/
|
||||||
|
- (void)stop;
|
||||||
|
|
||||||
|
/* The method is derived/stolen from NSURL/Helpers/capture.m */
|
||||||
|
- (void) stream: (NSStream *)theStream handleEvent: (NSStreamEvent)streamEvent;
|
||||||
|
|
||||||
|
@end /* SimpleWebServer */
|
||||||
|
|
||||||
|
@protocol SimpleWebServerDelegate
|
||||||
|
/**
|
||||||
|
* An implementor gets the supplied request for processing and
|
||||||
|
* modifies the supplied response which the supplied SimpleWebServer
|
||||||
|
* server should send back to it's peer if the return value is set
|
||||||
|
* to YES. Otherwise SimpleWebServer sends the predetermined response
|
||||||
|
* (TODO).
|
||||||
|
*/
|
||||||
|
- (BOOL)processRequest:(GSMimeDocument *)request
|
||||||
|
response:(GSMimeDocument *)response
|
||||||
|
for:(SimpleWebServer *)server;
|
||||||
|
|
||||||
|
@end /* SimpleWebServerDelegate */
|
606
Tests/base/NSURLConnection/Helpers/SimpleWebServer.m
Normal file
606
Tests/base/NSURLConnection/Helpers/SimpleWebServer.m
Normal file
|
@ -0,0 +1,606 @@
|
||||||
|
/*
|
||||||
|
* Author: Sergei Golovin <Golovin.SV@gmail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "SimpleWebServer.h"
|
||||||
|
|
||||||
|
/* the time step for the runloop */
|
||||||
|
#define TIMING 0.1
|
||||||
|
|
||||||
|
@interface SimpleWebServer (Private)
|
||||||
|
/**
|
||||||
|
* Starts a listening (server) stream.
|
||||||
|
*/
|
||||||
|
- (void)_openServerStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the listening (server) stream.
|
||||||
|
*/
|
||||||
|
- (void)_closeServerStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset to prepare for the next request-response cycle.
|
||||||
|
*/
|
||||||
|
- (void)_resetCycle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the input _ip and output _op streams, schedules them
|
||||||
|
* on the current runloop. Requires the _ip and _op are not nil.
|
||||||
|
*/
|
||||||
|
- (void)_openIOStreams;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the input _ip and output _op streams, unschedules them
|
||||||
|
* on the current runloop and clears that ivars.
|
||||||
|
*/
|
||||||
|
- (void)_closeIOStreams;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to recognise if the request's bytes have been read (HTTP message's
|
||||||
|
* headers and body) and if so then it proceeds the request and produces
|
||||||
|
* a response. If the response is ready it returns YES.
|
||||||
|
*/
|
||||||
|
- (BOOL)_tryCaptured;
|
||||||
|
|
||||||
|
@end /* SimpleWebServer (Private) */
|
||||||
|
|
||||||
|
@implementation SimpleWebServer
|
||||||
|
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
_delegate = nil;
|
||||||
|
DESTROY(_capture);
|
||||||
|
DESTROY(_request);
|
||||||
|
DESTROY(_response);
|
||||||
|
[self _closeIOStreams];
|
||||||
|
[self _closeServerStream];
|
||||||
|
DESTROY(_address);
|
||||||
|
DESTROY(_port);
|
||||||
|
DESTROY(_secure);
|
||||||
|
[super dealloc];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id)init
|
||||||
|
{
|
||||||
|
if((self = [super init]) != nil)
|
||||||
|
{
|
||||||
|
_debug = NO;
|
||||||
|
_delegate = nil;
|
||||||
|
_doRespond = NO;
|
||||||
|
_done = NO;
|
||||||
|
_canRespond = NO;
|
||||||
|
_ip = nil;
|
||||||
|
_op = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* getters */
|
||||||
|
- (NSString *)port
|
||||||
|
{
|
||||||
|
NSStream *s = _serverStream;
|
||||||
|
|
||||||
|
if(nil == s) s = _ip;
|
||||||
|
if(nil == s) s = _op;
|
||||||
|
if(nil == s) return nil;
|
||||||
|
|
||||||
|
return [s streamStatus] == NSStreamStatusOpen ? _port : nil;
|
||||||
|
}
|
||||||
|
/* end of getters */
|
||||||
|
|
||||||
|
/* setters */
|
||||||
|
- (BOOL)setAddress:(NSString *)address
|
||||||
|
port:(NSString *)port
|
||||||
|
secure:(NSDictionary *)dict
|
||||||
|
{
|
||||||
|
BOOL ret = NO;
|
||||||
|
|
||||||
|
if(nil == _serverStream)
|
||||||
|
{
|
||||||
|
ASSIGN(_address, address);
|
||||||
|
ASSIGN(_port, port);
|
||||||
|
ASSIGN(_secure, dict);
|
||||||
|
[self _openServerStream];
|
||||||
|
if(nil != _serverStream)
|
||||||
|
{
|
||||||
|
ret = YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NSLog(@"%@: already started '%@' on '%@'", self, _serverStream, [NSThread currentThread]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setDebug:(BOOL)flag
|
||||||
|
{
|
||||||
|
_debug = flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setDelegate:(id)delegate
|
||||||
|
{
|
||||||
|
_delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* end of setters */
|
||||||
|
|
||||||
|
- (void)stop
|
||||||
|
{
|
||||||
|
NSRunLoop *rl = [NSRunLoop currentRunLoop];;
|
||||||
|
|
||||||
|
if(nil != _serverStream)
|
||||||
|
{
|
||||||
|
[self _closeServerStream];
|
||||||
|
DESTROY(_address);
|
||||||
|
DESTROY(_port);
|
||||||
|
DESTROY(_secure);
|
||||||
|
}
|
||||||
|
|
||||||
|
[self _closeIOStreams];
|
||||||
|
|
||||||
|
// give a time slice to free all resources
|
||||||
|
[rl runUntilDate: [NSDate dateWithTimeIntervalSinceNow: TIMING]];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GSStream's delegate */
|
||||||
|
|
||||||
|
/* The method is derived/stolen from NSURL/Helpers/capture.m */
|
||||||
|
- (void) stream: (NSStream *)theStream handleEvent: (NSStreamEvent)streamEvent
|
||||||
|
{
|
||||||
|
NSRunLoop *rl = [NSRunLoop currentRunLoop];
|
||||||
|
|
||||||
|
// NSLog(@"Event %p %d", theStream, streamEvent);
|
||||||
|
|
||||||
|
switch (streamEvent)
|
||||||
|
{
|
||||||
|
case NSStreamEventHasBytesAvailable:
|
||||||
|
{
|
||||||
|
if (_ip == nil)
|
||||||
|
{
|
||||||
|
[self _resetCycle];
|
||||||
|
[(GSServerStream*)_serverStream acceptWithInputStream: &_ip
|
||||||
|
outputStream: &_op];
|
||||||
|
if (_ip) // it is ok to accept nothing
|
||||||
|
{
|
||||||
|
RETAIN(_ip);
|
||||||
|
RETAIN(_op);
|
||||||
|
[self _openIOStreams];
|
||||||
|
[self _closeServerStream];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (theStream == _ip)
|
||||||
|
{
|
||||||
|
_readable = YES;
|
||||||
|
while (_readable == YES)
|
||||||
|
{
|
||||||
|
unsigned char buffer[BUFSIZ];
|
||||||
|
int readSize;
|
||||||
|
|
||||||
|
readSize = [_ip read: buffer maxLength: sizeof(buffer)];
|
||||||
|
if (readSize <= 0)
|
||||||
|
{
|
||||||
|
_readable = NO;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
[_capture appendBytes: buffer length: readSize];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_doRespond = [self _tryCaptured];
|
||||||
|
if(_doRespond)
|
||||||
|
{
|
||||||
|
// reset the output stream to trigger polling
|
||||||
|
[_op write: NULL maxLength: 0];
|
||||||
|
if(YES == _debug)
|
||||||
|
{
|
||||||
|
NSLog(@"%@: about to send response\n%@", self, _response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case NSStreamEventHasSpaceAvailable:
|
||||||
|
{
|
||||||
|
if(_doRespond && _canRespond)
|
||||||
|
{
|
||||||
|
NSMutableData *data;
|
||||||
|
NSString *status;
|
||||||
|
NSData *statusData;
|
||||||
|
char *crlf = "\r\n";
|
||||||
|
id content;
|
||||||
|
NSData *contentData;
|
||||||
|
NSUInteger cLength = 0; // content-length
|
||||||
|
NSString *connection;
|
||||||
|
BOOL close = YES;
|
||||||
|
|
||||||
|
NSAssert(theStream == _op, @"Wrong stream for writing");
|
||||||
|
_writable = YES;
|
||||||
|
|
||||||
|
// adding the 'Connection' to the response
|
||||||
|
connection = [[_request headerNamed: @"Connection"] value];
|
||||||
|
if(nil == connection)
|
||||||
|
{
|
||||||
|
connection = [[_request headerNamed: @"connection"] value];
|
||||||
|
}
|
||||||
|
// if the client didn't supply the header 'Connection' or explicitly stated
|
||||||
|
// to close the current connection
|
||||||
|
close = (nil == connection || [[connection lowercaseString] isEqualToString: @"close"]);
|
||||||
|
|
||||||
|
// adding the 'Content-Length' to the response
|
||||||
|
content = [_response content];
|
||||||
|
if([content isKindOfClass: [NSString class]])
|
||||||
|
{
|
||||||
|
contentData = [(NSString *)content dataUsingEncoding: NSUTF8StringEncoding];
|
||||||
|
}
|
||||||
|
else if([content isKindOfClass: [NSData class]])
|
||||||
|
{
|
||||||
|
contentData = (NSData *)content;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// yet unsupported
|
||||||
|
}
|
||||||
|
if(nil != content)
|
||||||
|
{
|
||||||
|
cLength = [contentData length];
|
||||||
|
if(cLength > 0)
|
||||||
|
{
|
||||||
|
[_response setHeader: @"Content-Length"
|
||||||
|
value: [NSString stringWithFormat: @"%u", cLength]
|
||||||
|
parameters: nil];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(cLength == 0)
|
||||||
|
{
|
||||||
|
[_response setHeader: @"Content-Length"
|
||||||
|
value: @"0"
|
||||||
|
parameters: nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
// adding the status line
|
||||||
|
status = [[_response headerNamed: @"http"] value];
|
||||||
|
if(nil == status)
|
||||||
|
{
|
||||||
|
status = [[_response headerNamed: @"HTTP"] value];
|
||||||
|
}
|
||||||
|
if(nil == status)
|
||||||
|
{
|
||||||
|
status = [[_response headerNamed: @"Http"] value];
|
||||||
|
}
|
||||||
|
statusData = [status dataUsingEncoding: NSUTF8StringEncoding];
|
||||||
|
data = [NSMutableData dataWithData: statusData];
|
||||||
|
[_response deleteHeaderNamed: @"http"];
|
||||||
|
[data appendBytes: crlf length: 2];
|
||||||
|
|
||||||
|
// actual sending
|
||||||
|
[data appendData: [_response rawMimeData]];
|
||||||
|
while (_writable == YES && _written < [data length])
|
||||||
|
{
|
||||||
|
int result = [_op write: [data bytes] + _written
|
||||||
|
maxLength: [data length] - _written];
|
||||||
|
|
||||||
|
if (result <= 0)
|
||||||
|
{
|
||||||
|
_writable = NO;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_written += result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_written == [data length])
|
||||||
|
{
|
||||||
|
if(close)
|
||||||
|
{
|
||||||
|
// if the client didn't supply the header 'Connection' or explicitly stated
|
||||||
|
// to close the current connection
|
||||||
|
[self _closeIOStreams];
|
||||||
|
[self _openServerStream];
|
||||||
|
}
|
||||||
|
// ready for another request-response cycle
|
||||||
|
[self _resetCycle];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_canRespond = YES;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case NSStreamEventEndEncountered:
|
||||||
|
{
|
||||||
|
if(theStream == _ip || theStream == _op)
|
||||||
|
{
|
||||||
|
[self _closeIOStreams];
|
||||||
|
[self _resetCycle];
|
||||||
|
[self _openServerStream];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
[theStream close];
|
||||||
|
[theStream removeFromRunLoop: rl forMode: NSDefaultRunLoopMode];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case NSStreamEventErrorOccurred:
|
||||||
|
{
|
||||||
|
int code = [[theStream streamError] code];
|
||||||
|
|
||||||
|
if(theStream == _ip || theStream == _op)
|
||||||
|
{
|
||||||
|
[self _closeIOStreams];
|
||||||
|
[self _resetCycle];
|
||||||
|
[self _openServerStream];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
[theStream close];
|
||||||
|
[theStream removeFromRunLoop: rl forMode: NSDefaultRunLoopMode];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSAssert1(1, @"Error! code is %d", code);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* end of GSStream's delegate */
|
||||||
|
|
||||||
|
@end /* SimpleWebServer */
|
||||||
|
|
||||||
|
@implementation SimpleWebServer (Private)
|
||||||
|
|
||||||
|
- (void)_openServerStream
|
||||||
|
{
|
||||||
|
NSString *certFile = nil;
|
||||||
|
NSString *keyFile = nil;
|
||||||
|
NSRunLoop *rl;
|
||||||
|
|
||||||
|
if(nil == _serverStream)
|
||||||
|
{
|
||||||
|
rl = [NSRunLoop currentRunLoop];
|
||||||
|
|
||||||
|
_serverStream = [GSServerStream serverStreamToAddr: _address port: [_port intValue]];
|
||||||
|
RETAIN(_serverStream);
|
||||||
|
if (_serverStream == nil)
|
||||||
|
{
|
||||||
|
NSLog(@"Failed to create server stream (address: %@, port: %@)", _address, _port);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(nil != _secure &&
|
||||||
|
(certFile = [_secure objectForKey: @"CertificateFile"]) != nil &&
|
||||||
|
(keyFile = [_secure objectForKey: @"KeyFile"]) != nil)
|
||||||
|
{
|
||||||
|
_isSecure = YES;
|
||||||
|
[_serverStream setProperty: NSStreamSocketSecurityLevelTLSv1 forKey: NSStreamSocketSecurityLevelKey];
|
||||||
|
[_serverStream setProperty: certFile forKey: GSTLSCertificateFile];
|
||||||
|
[_serverStream setProperty: keyFile forKey: GSTLSCertificateKeyFile];
|
||||||
|
}
|
||||||
|
|
||||||
|
_capture = [NSMutableData new];
|
||||||
|
|
||||||
|
[_serverStream setDelegate: self];
|
||||||
|
[_serverStream scheduleInRunLoop: rl forMode: NSDefaultRunLoopMode];
|
||||||
|
[_serverStream open];
|
||||||
|
if(YES == _debug)
|
||||||
|
{
|
||||||
|
NSLog(@"%@: started '%@' on '%@'", self, _serverStream, [NSThread currentThread]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NSLog(@"%@: already started '%@' on '%@'", self, _serverStream, [NSThread currentThread]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)_closeServerStream
|
||||||
|
{
|
||||||
|
NSRunLoop *rl;
|
||||||
|
|
||||||
|
if(nil != _serverStream)
|
||||||
|
{
|
||||||
|
rl = [NSRunLoop currentRunLoop];
|
||||||
|
|
||||||
|
[_serverStream close];
|
||||||
|
[_serverStream removeFromRunLoop: rl forMode: NSDefaultRunLoopMode];
|
||||||
|
if(YES == _debug)
|
||||||
|
{
|
||||||
|
NSLog(@"%@: stopped server stream %@", self, _serverStream);
|
||||||
|
}
|
||||||
|
DESTROY(_serverStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)_resetCycle
|
||||||
|
{
|
||||||
|
_written = 0;
|
||||||
|
_doRespond = NO;
|
||||||
|
_done = NO;
|
||||||
|
_canRespond = NO;
|
||||||
|
[_capture setLength: 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)_openIOStreams
|
||||||
|
{
|
||||||
|
NSRunLoop *rl;
|
||||||
|
|
||||||
|
if(_ip != nil && _op != nil)
|
||||||
|
{
|
||||||
|
rl = [NSRunLoop currentRunLoop];
|
||||||
|
|
||||||
|
[_ip scheduleInRunLoop: rl forMode: NSDefaultRunLoopMode];
|
||||||
|
[_op scheduleInRunLoop: rl forMode: NSDefaultRunLoopMode];
|
||||||
|
[_ip setDelegate: self];
|
||||||
|
[_op setDelegate: self];
|
||||||
|
[_ip open];
|
||||||
|
[_op open];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
[NSException raise: NSInternalInconsistencyException
|
||||||
|
format: @"%@: IO streams not properly initialized", self];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)_closeIOStreams
|
||||||
|
{
|
||||||
|
NSRunLoop *rl;
|
||||||
|
|
||||||
|
if(nil != _ip || nil != _op)
|
||||||
|
{
|
||||||
|
rl = [NSRunLoop currentRunLoop];
|
||||||
|
|
||||||
|
[_ip close];
|
||||||
|
[_ip removeFromRunLoop: rl forMode: NSDefaultRunLoopMode];
|
||||||
|
|
||||||
|
[_op close];
|
||||||
|
[_op removeFromRunLoop: rl forMode: NSDefaultRunLoopMode];
|
||||||
|
|
||||||
|
DESTROY(_ip);
|
||||||
|
DESTROY(_op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)_tryCaptured
|
||||||
|
{
|
||||||
|
BOOL ret = NO;
|
||||||
|
NSRange r1;
|
||||||
|
NSRange r2;
|
||||||
|
NSString *headers;
|
||||||
|
NSString *tmp1;
|
||||||
|
NSString *tmp2;
|
||||||
|
NSUInteger contentLength;
|
||||||
|
|
||||||
|
// the following chunk ensures that the captured data are written only
|
||||||
|
// when all request's bytes are read... it waits for full headers and
|
||||||
|
// reads the Content-Length's value then waits for the number of bytes
|
||||||
|
// equal to that value is read
|
||||||
|
tmp1 = [[NSString alloc] initWithData: _capture
|
||||||
|
encoding: NSUTF8StringEncoding];
|
||||||
|
// whether the headers are read
|
||||||
|
if((r1 = [tmp1 rangeOfString: @"\r\n\r\n"]).location != NSNotFound)
|
||||||
|
{
|
||||||
|
headers = [tmp1 substringToIndex: r1.location + 2];
|
||||||
|
if((r2 = [[headers lowercaseString] rangeOfString: @"content-length:"]).location != NSNotFound)
|
||||||
|
{
|
||||||
|
tmp2 = [headers substringFromIndex: r2.location + r2.length]; // content-length:<tmp2><end of headers>
|
||||||
|
if((r2 = [tmp2 rangeOfString: @"\r\n"]).location != NSNotFound)
|
||||||
|
{
|
||||||
|
// full line with content-length is present
|
||||||
|
tmp2 = [tmp2 substringToIndex: r2.location]; // number of content's bytes
|
||||||
|
contentLength = [tmp2 intValue];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
contentLength = 0; // no header 'content-length'
|
||||||
|
}
|
||||||
|
if(r1.location + 4 + contentLength == [_capture length]) // Did we get headers + body?
|
||||||
|
{
|
||||||
|
// The request has been received
|
||||||
|
NSString *method = @"";
|
||||||
|
NSString *query = @"";
|
||||||
|
NSString *version = @"";
|
||||||
|
NSString *scheme = _isSecure ? @"https" : @"http";
|
||||||
|
NSString *path = @"";
|
||||||
|
NSData *data;
|
||||||
|
|
||||||
|
// TODO: currently no checks
|
||||||
|
r2 = [headers rangeOfString: @"\r\n"];
|
||||||
|
while(r2.location == 0)
|
||||||
|
{
|
||||||
|
// ignore an empty line before the request line
|
||||||
|
headers = [headers substringFromIndex: 2];
|
||||||
|
r2 = [headers rangeOfString: @"\r\n"];
|
||||||
|
}
|
||||||
|
// the request line has been caught
|
||||||
|
tmp2 = [tmp1 substringFromIndex: r2.location + 2]; // whole request without the first line
|
||||||
|
data = [tmp2 dataUsingEncoding: NSUTF8StringEncoding];
|
||||||
|
_request = [GSMimeParser documentFromData: data];
|
||||||
|
RETAIN(_request);
|
||||||
|
|
||||||
|
// x-http-...
|
||||||
|
tmp2 = [headers substringToIndex: r2.location]; // the request line
|
||||||
|
tmp2 = [tmp2 stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet]];
|
||||||
|
|
||||||
|
// find the method
|
||||||
|
r2 = [tmp2 rangeOfString: @" "];
|
||||||
|
method = [[tmp2 substringToIndex: r2.location] uppercaseString];
|
||||||
|
tmp2 = [[tmp2 substringFromIndex: r2.location + 1]
|
||||||
|
stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet]];
|
||||||
|
|
||||||
|
r2 = [tmp2 rangeOfString: @"?"]; // path?query
|
||||||
|
if(r2.location != NSNotFound)
|
||||||
|
{
|
||||||
|
// path?query
|
||||||
|
path = [tmp2 substringToIndex: r2.location];
|
||||||
|
tmp2 = [tmp2 substringFromIndex: r2.location + 1]; // without '?'
|
||||||
|
r2 = [tmp2 rangeOfString: @" "];
|
||||||
|
query = [tmp2 substringToIndex: r2.location];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// only path
|
||||||
|
r2 = [tmp2 rangeOfString: @" "];
|
||||||
|
path = [tmp2 substringToIndex: r2.location];
|
||||||
|
}
|
||||||
|
tmp2 = [[tmp2 substringFromIndex: r2.location + 1]
|
||||||
|
stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet]];
|
||||||
|
// tmp2 == 'HTTP/<version>'
|
||||||
|
version = [tmp2 substringFromIndex: 5];
|
||||||
|
|
||||||
|
|
||||||
|
[_request setHeader: @"x-http-method"
|
||||||
|
value: method
|
||||||
|
parameters: nil];
|
||||||
|
|
||||||
|
[_request setHeader: @"x-http-path"
|
||||||
|
value: path
|
||||||
|
parameters: nil];
|
||||||
|
|
||||||
|
[_request setHeader: @"x-http-query"
|
||||||
|
value: query
|
||||||
|
parameters: nil];
|
||||||
|
|
||||||
|
[_request setHeader: @"x-http-scheme"
|
||||||
|
value: scheme
|
||||||
|
parameters: nil];
|
||||||
|
|
||||||
|
[_request setHeader: @"x-http-version"
|
||||||
|
value: version
|
||||||
|
parameters: nil];
|
||||||
|
|
||||||
|
if(YES == _debug)
|
||||||
|
{
|
||||||
|
NSLog(@"%@: got request\n%@", self, _request);
|
||||||
|
}
|
||||||
|
_response = [GSMimeDocument new];
|
||||||
|
if(nil != _delegate && [_delegate respondsToSelector: @selector(processRequest:response:for:)])
|
||||||
|
{
|
||||||
|
ret = [_delegate processRequest: _request response: _response for: self];
|
||||||
|
}
|
||||||
|
if(!ret)
|
||||||
|
{
|
||||||
|
DESTROY(_response);
|
||||||
|
_response = [GSMimeDocument new];
|
||||||
|
[_response setHeader: @"HTTP" value: @"HTTP/1.1 204 No Content" parameters: nil];
|
||||||
|
[_response setHeader: @"Content-Length" value: @"0" parameters: nil];
|
||||||
|
ret = YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DESTROY(tmp1);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end /* SimpleWebServer (Private) */
|
129
Tests/base/NSURLConnection/Helpers/TestCase.h
Normal file
129
Tests/base/NSURLConnection/Helpers/TestCase.h
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
/* -*- objc -*-
|
||||||
|
*
|
||||||
|
* Author: Sergei Golovin <Golovin.SV@gmail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
/* the initial (reset) state of any flag set */
|
||||||
|
#define NORESULTS 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class representing some test case and implementing
|
||||||
|
* the protocol TestProgress allows descendants to have two sets
|
||||||
|
* of flags: the reference one and the actual one.
|
||||||
|
* The reference flag set is what the class's instance's
|
||||||
|
* state supposed to be if the test was successfull.
|
||||||
|
* The actual flag set is the current state of the class's
|
||||||
|
* instance during execution of the test.
|
||||||
|
* Both can be used in conjuction to decide about test's
|
||||||
|
* success or fail by checking at the end of the test
|
||||||
|
* whether the sets are identical.
|
||||||
|
*/
|
||||||
|
@protocol TestProgress
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the actual flags to the pristine state (NORESULTS).
|
||||||
|
*/
|
||||||
|
- (void)resetFlags;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the actual flags defined by the supplied mask.
|
||||||
|
*/
|
||||||
|
- (void)setFlags:(NSUInteger)mask;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unset the actual flags defined by the supplied mask.
|
||||||
|
*/
|
||||||
|
- (void)unsetFlags:(NSUInteger)mask;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns YES if the actual flags defined by the supplied mask are
|
||||||
|
* set.
|
||||||
|
*/
|
||||||
|
- (BOOL)isFlagSet:(NSUInteger)mask;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the reference flags to the pristine state (NORESULTS).
|
||||||
|
*/
|
||||||
|
- (void)resetReferenceFlags;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the supplied mask as the reference flags state.
|
||||||
|
* The actual flags state equal to the reference one signifies
|
||||||
|
* a success.
|
||||||
|
*/
|
||||||
|
- (void)setReferenceFlags:(NSUInteger)mask;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unset the reference flags defined by the supplied mask.
|
||||||
|
*/
|
||||||
|
- (void)unsetReferenceFlags:(NSUInteger)mask;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns YES if the reference flags defined by the supplied mask are
|
||||||
|
* set.
|
||||||
|
*/
|
||||||
|
- (BOOL)isReferenceFlagSet:(NSUInteger)mask;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns YES if all conditions are met (by default if the actual flag set
|
||||||
|
* is equal to the reference flag set).
|
||||||
|
*/
|
||||||
|
- (BOOL)isSuccess;
|
||||||
|
|
||||||
|
@end /* TestProgress */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The abstract class representing a test case and implementing
|
||||||
|
* the protocol TestProgress.
|
||||||
|
*/
|
||||||
|
@interface TestCase : NSObject <TestProgress>
|
||||||
|
{
|
||||||
|
/* the flags indicating test progress (the actual one) */
|
||||||
|
NSUInteger _flags;
|
||||||
|
/* the reference flag set signifying the test's successful state */
|
||||||
|
NSUInteger _refFlags;
|
||||||
|
/* the particular flag indicating a failure not related to the test */
|
||||||
|
BOOL _failed;
|
||||||
|
/* the particular flag signifying the test has been done */
|
||||||
|
BOOL _done;
|
||||||
|
/* keeps the extra which can likely be of NSDictionary class in which case
|
||||||
|
* it's keys hold parameters of the test */
|
||||||
|
id _extra;
|
||||||
|
/* the debug mode flag */
|
||||||
|
BOOL _debug;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id)init;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A descendant class can place in the own implementation of the method
|
||||||
|
* some preliminary code needed to conduct the test case. The argument
|
||||||
|
* 'extra' is for future enhancements. The extra is only retained within
|
||||||
|
* the method for descendants (the abstarct class doesn't do anything
|
||||||
|
* with this argument).
|
||||||
|
* So descendant's implementation MUST call the super's one.
|
||||||
|
*/
|
||||||
|
- (void)setUpTest:(id)extra;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A descendant class should place the code conducting the test in
|
||||||
|
* the own implementation of the method. The argument 'extra' is for
|
||||||
|
* future enhancements.
|
||||||
|
*/
|
||||||
|
- (void)startTest:(id)extra;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A descendant class can place in the own implementation of the method
|
||||||
|
* some cleaning code. The argument 'extra' is for future enhancements.
|
||||||
|
* Descendant's implementation MUST call the super's one.
|
||||||
|
*/
|
||||||
|
- (void)tearDownTest:(id)extra;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raises the verbosity level if supplied with YES.
|
||||||
|
*/
|
||||||
|
- (void)setDebug:(BOOL)flag;
|
||||||
|
|
||||||
|
@end /* TestCase */
|
106
Tests/base/NSURLConnection/Helpers/TestCase.m
Normal file
106
Tests/base/NSURLConnection/Helpers/TestCase.m
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
/*
|
||||||
|
* Author: Sergei Golovin <Golovin.SV@gmail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "TestCase.h"
|
||||||
|
|
||||||
|
@implementation TestCase
|
||||||
|
|
||||||
|
- (id)init
|
||||||
|
{
|
||||||
|
if((self = [super init]) != nil)
|
||||||
|
{
|
||||||
|
[self resetFlags];
|
||||||
|
[self setReferenceFlags: NORESULTS];
|
||||||
|
_failed = NO;
|
||||||
|
_done = NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
[self tearDownTest: _extra];
|
||||||
|
|
||||||
|
[super dealloc];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TestProgress */
|
||||||
|
- (void)resetFlags
|
||||||
|
{
|
||||||
|
_flags = NORESULTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setFlags:(NSUInteger)mask
|
||||||
|
{
|
||||||
|
_flags = _flags | mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)unsetFlags:(NSUInteger)mask
|
||||||
|
{
|
||||||
|
_flags = _flags & ~mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)isFlagSet:(NSUInteger)mask
|
||||||
|
{
|
||||||
|
return ((_flags & mask) == mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)resetReferenceFlags
|
||||||
|
{
|
||||||
|
_refFlags = NORESULTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setReferenceFlags:(NSUInteger)mask
|
||||||
|
{
|
||||||
|
_refFlags = _refFlags | mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)unsetReferenceFlags:(NSUInteger)mask
|
||||||
|
{
|
||||||
|
_refFlags = _refFlags & ~mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)isReferenceFlagSet:(NSUInteger)mask
|
||||||
|
{
|
||||||
|
return ((_refFlags & mask) == mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)isSuccess
|
||||||
|
{
|
||||||
|
if(!_failed && (_flags == _refFlags))
|
||||||
|
{
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
/* end of TestProgress */
|
||||||
|
|
||||||
|
- (void)setUpTest:(id)extra
|
||||||
|
{
|
||||||
|
[self resetFlags];
|
||||||
|
[self resetReferenceFlags];
|
||||||
|
_failed = NO;
|
||||||
|
_done = NO;
|
||||||
|
|
||||||
|
ASSIGN(_extra, extra);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)startTest:(id)extra
|
||||||
|
{
|
||||||
|
// does nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)tearDownTest:(id)extra
|
||||||
|
{
|
||||||
|
DESTROY(_extra);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setDebug:(BOOL)flag
|
||||||
|
{
|
||||||
|
_debug = flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end /* TestCase */
|
274
Tests/base/NSURLConnection/Helpers/TestWebServer.h
Normal file
274
Tests/base/NSURLConnection/Helpers/TestWebServer.h
Normal file
|
@ -0,0 +1,274 @@
|
||||||
|
|
||||||
|
/** -*- objc -*-
|
||||||
|
*
|
||||||
|
* Author: Sergei Golovin <Golovin.SV@gmail.com>
|
||||||
|
*
|
||||||
|
* TestWebServer is intended to be used in the tests where a web service is
|
||||||
|
* needed. The class's instance is instantiated with the method
|
||||||
|
* -[initWithAddress:port:mode:extra] and then started with the call -[start:].
|
||||||
|
* When started the TestWebServer starts in it's turn a SimpleWebServer instance
|
||||||
|
* in the same thread unless it is initialized with the argument 'mode' set to
|
||||||
|
* 'YES' to run it in a detached thread.
|
||||||
|
*
|
||||||
|
* It is designed to call the delegate's callbacks (if implemented) of
|
||||||
|
* the TestWebServerDelegate protocol for every request made to
|
||||||
|
* the SimpleWebServer's assigned address on the SimpleWebServer's assigned port
|
||||||
|
* (by default 127.0.0.1 and 54321 respectively). However it currently doesn't
|
||||||
|
* handle any request on it's own. Instead the class uses a collection of
|
||||||
|
* handlers. Every handler makes a custom response. So the TestWebServer only
|
||||||
|
* has to dispatch a request to a corresponding handler and then to send back
|
||||||
|
* the handler's response to the SimpleWebServer instance. See RequestHandler
|
||||||
|
* for more information.
|
||||||
|
*
|
||||||
|
* TestWebServer can be supplied with additional parameters via the argument
|
||||||
|
* 'extra' of the initializer -[initWithAddress:port:mode:extra].
|
||||||
|
* See that method's description for the currently supported key-value pairs.
|
||||||
|
*
|
||||||
|
* The pattern of use:
|
||||||
|
* ----------------------------------------------------------------------------
|
||||||
|
* NSDictionary *extra = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
* @"https", @"Protocol",
|
||||||
|
* nil];
|
||||||
|
* TestWebServer *server = [[TestWebServer alloc] initWithAddress: @"127.0.0.1"
|
||||||
|
* port: @"54321"
|
||||||
|
* mode: NO
|
||||||
|
* extra: extra];
|
||||||
|
* ADelegateClass *del = [ADelegateClass new];
|
||||||
|
* [server setDelegate: del];
|
||||||
|
* [server start: extra];
|
||||||
|
* ....
|
||||||
|
* <runloop>
|
||||||
|
* ....
|
||||||
|
* [server stop];
|
||||||
|
* DESTROY(server);
|
||||||
|
* DESTROY(del);
|
||||||
|
* ----------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* The TestWebServer can response to the following resource paths:
|
||||||
|
*
|
||||||
|
* /index
|
||||||
|
* displays the page with all supported resources
|
||||||
|
* /withoutauth/
|
||||||
|
* if it is a part of the request's URL then the TestWebServer
|
||||||
|
* will NOT check authentication/authorization of the client.
|
||||||
|
*
|
||||||
|
* /204/
|
||||||
|
* /301/
|
||||||
|
* /400/
|
||||||
|
* /401/
|
||||||
|
* /402/
|
||||||
|
* /404/
|
||||||
|
* /405/
|
||||||
|
* /409/
|
||||||
|
* /500/
|
||||||
|
* /505/
|
||||||
|
* /507/
|
||||||
|
* a try to access to this resources will lead the TestWebServer
|
||||||
|
* to produce one of the pre-defined responses with the status code
|
||||||
|
* is equal to the corresponding number.
|
||||||
|
* The pre-defined responses are:
|
||||||
|
* 204 "" (empty string)
|
||||||
|
* 301 "Redirect to <URL>"
|
||||||
|
* Returns in the header 'Location' a new <URL> which by default
|
||||||
|
* has the same protocol, address but the port is incremented by
|
||||||
|
* 1 (e.g. http://localhost:54322/ with other parameters set to
|
||||||
|
* their default values).
|
||||||
|
* 400 "You have issued a request with invalid data"
|
||||||
|
* 401 "Invalid login or password"
|
||||||
|
* 403 "Check your balance"
|
||||||
|
* 404 "The resource you are trying to access is not found"
|
||||||
|
* 405 "You can't do that"
|
||||||
|
* 409 "The resource you are trying to access is busy"
|
||||||
|
* 500 "System error"
|
||||||
|
* 505 "There is network protocol inconsistency"
|
||||||
|
* 507 "Insufficient storage"
|
||||||
|
*
|
||||||
|
* If you want more verbosity call the method -[setDebug: YES].
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "SimpleWebServer.h"
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
@class RequestHandler;
|
||||||
|
|
||||||
|
@interface TestWebServer : NSObject <SimpleWebServerDelegate>
|
||||||
|
{
|
||||||
|
/* the debug mode flag */
|
||||||
|
BOOL _debug;
|
||||||
|
/* the IP address to attach to... see DEFAULTADDRESS at the beginning
|
||||||
|
* of the implementaion */
|
||||||
|
NSString *_address;
|
||||||
|
/* the port to listen on... see DEFAULTPORT in the beginning
|
||||||
|
* of the implementaion */
|
||||||
|
NSString *_port;
|
||||||
|
/* whether the TestWebServer listens for HTTPS requests */
|
||||||
|
BOOL _isSecure;
|
||||||
|
/* the login for basic authentication */
|
||||||
|
NSString *_login;
|
||||||
|
/* the password for basic authentication */
|
||||||
|
NSString *_password;
|
||||||
|
/* holds the extra argument which is for a future use...
|
||||||
|
* Currently it is expected to be a dictionary with predetermined
|
||||||
|
* keys ( see the description of -[initWithAddress:port:mode:extra:]) */
|
||||||
|
id _extra;
|
||||||
|
/* the flag used as a trigger to stop the detached thread */
|
||||||
|
BOOL _threadToQuit;
|
||||||
|
/* holds the SimpleWebServer accepting incoming connections */
|
||||||
|
SimpleWebServer *_server;
|
||||||
|
/* holds the detached thread the SimpleWebServer is running on...
|
||||||
|
* nil if it runs in the same thread as the calling code */
|
||||||
|
NSThread *_serverThread;
|
||||||
|
/* the delegate ... NOT RETAINED...
|
||||||
|
* see below the protocol TestWebServerDelegate */
|
||||||
|
id _delegate;
|
||||||
|
/* the lock used for synchronization between threads mainly
|
||||||
|
* to wait for the SimpleWebServer is started/stopped...
|
||||||
|
* the condition list:
|
||||||
|
* READY - the initial condition
|
||||||
|
* STARTED - the SimpleWebServer has been started;
|
||||||
|
* STOPPED - the SimpleWebServer has been stopped;
|
||||||
|
*/
|
||||||
|
NSConditionLock *_lock;
|
||||||
|
/* the traversal map used to pick a right handler from the request's path */
|
||||||
|
NSMutableDictionary *_traversalMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the intance with default parameters.
|
||||||
|
*/
|
||||||
|
- (id)init;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes an instance with it's SimpleWebServer accepting connection on
|
||||||
|
* the specified address and port. The mode decides whether to run
|
||||||
|
* the SimpleWebServer on the detached thread (NO means it won't be detached).
|
||||||
|
* The argument extra is for future enhancement and could be of the class
|
||||||
|
* NSDictionary whose key-value pairs set various parameters.
|
||||||
|
* The current code supports the following extra keys:
|
||||||
|
* 'Login' - the login for basic authentication
|
||||||
|
* 'Password' - the password for basic authentication
|
||||||
|
* 'Protocol' - 'http' means waiting for HTTP requests
|
||||||
|
* 'https' - for HTTPS requests
|
||||||
|
*/
|
||||||
|
- (id)initWithAddress:(NSString *)address
|
||||||
|
port:(NSString *)port
|
||||||
|
mode:(BOOL)detached
|
||||||
|
extra:(id)extra;
|
||||||
|
- (void)dealloc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the SimpleWebServer accepting incoming connections.
|
||||||
|
* The supplied argument is for future enhancement and currently has no meaning.
|
||||||
|
* It isn't retained by the method.
|
||||||
|
*/
|
||||||
|
- (void)start:(id)extra;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the SimpleWebServer. The instance ceases to accept incoming connections.
|
||||||
|
*/
|
||||||
|
- (void)stop;
|
||||||
|
|
||||||
|
/* SimpleWebServerDelegate */
|
||||||
|
/**
|
||||||
|
* The main job of request-response cycle is done here. The method is called
|
||||||
|
* when all request's bytes are read. It must be supplied with the incoming
|
||||||
|
* request and the response document which is about to be sent back to the web
|
||||||
|
* client. The handling code within the method would change the supplied response.
|
||||||
|
*/
|
||||||
|
- (BOOL) processRequest:(GSMimeDocument *)request
|
||||||
|
response:(GSMimeDocument *)response
|
||||||
|
for:(SimpleWebServer *)server;
|
||||||
|
/* end of SimpleWebServerDelegate */
|
||||||
|
|
||||||
|
/* getters */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the address which SimpleWebServer is bound to.
|
||||||
|
*/
|
||||||
|
- (NSString *)address;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the port which the SimpleWebServer listens on.
|
||||||
|
*/
|
||||||
|
- (NSString *)port;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the login for basic authentication.
|
||||||
|
*/
|
||||||
|
- (NSString *)login;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the password for basic authentication.
|
||||||
|
*/
|
||||||
|
- (NSString *)password;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns YES if the instance waits for HTTPS requests.
|
||||||
|
*/
|
||||||
|
- (BOOL)isSecure;
|
||||||
|
|
||||||
|
/* end of getters */
|
||||||
|
|
||||||
|
/* setters */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the delegate implementing TestWebServerDelegate protocol.
|
||||||
|
* The argument isn't retained by the method.
|
||||||
|
*/
|
||||||
|
- (void)setDelegate:(id)delegate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the debug mode (more verbose).
|
||||||
|
*/
|
||||||
|
- (void)setDebug:(BOOL)mode;
|
||||||
|
|
||||||
|
/* end of setters */
|
||||||
|
|
||||||
|
@end /* TestWebServer */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The protocol's methods are called during processing of a request.
|
||||||
|
*/
|
||||||
|
@protocol TestWebServerDelegate
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the handler when it has got the supplied request
|
||||||
|
* from the supplied TestWebServer instance.
|
||||||
|
*/
|
||||||
|
- (void)handler:(id)handler
|
||||||
|
gotRequest:(GSMimeDocument *)request
|
||||||
|
with:(TestWebServer *)server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the handler when it is going to send the response
|
||||||
|
* on an unauthorized request (an invalid request or no credentials are supplied)
|
||||||
|
* via the supplied TestWebServer.
|
||||||
|
*/
|
||||||
|
- (void)handler:(id)handler
|
||||||
|
willSendUnauthorized:(GSMimeDocument *)response
|
||||||
|
with:(TestWebServer *)server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the handler when it has got the supplied request
|
||||||
|
* with valid credentials from the supplied TestWebServer instance.
|
||||||
|
*/
|
||||||
|
- (void)handler:(id)handler
|
||||||
|
gotAuthorized:(GSMimeDocument *)request
|
||||||
|
with:(TestWebServer *)server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the handler when it is going to send the response
|
||||||
|
* on any request except an unauthorized one via the supplied TestWebServer.
|
||||||
|
*/
|
||||||
|
- (void)handler:(id)handler
|
||||||
|
willSend:(GSMimeDocument *)response
|
||||||
|
with:(TestWebServer *)server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the timeout is exceeded.
|
||||||
|
*/
|
||||||
|
- (void)timeoutExceededByHandler:(id)handler;
|
||||||
|
|
||||||
|
@end /* TestWebServerDelegate */
|
468
Tests/base/NSURLConnection/Helpers/TestWebServer.m
Normal file
468
Tests/base/NSURLConnection/Helpers/TestWebServer.m
Normal file
|
@ -0,0 +1,468 @@
|
||||||
|
/*
|
||||||
|
* Author: Sergei Golovin <Golovin.SV@gmail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "TestWebServer.h"
|
||||||
|
#import "RequestHandler.h"
|
||||||
|
|
||||||
|
@interface TestWebServer (Private)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the detached thread. It is the entry point of the thread.
|
||||||
|
*/
|
||||||
|
- (void)_startDetached:(id)extra;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the SimpleWebServer.
|
||||||
|
*/
|
||||||
|
- (void)_startHTTPServer:(id)extra;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the SimpleWebServer.
|
||||||
|
*/
|
||||||
|
- (void)_stopHTTPServer;
|
||||||
|
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
/* default 'constants' */
|
||||||
|
#define DEFAULTADDRESS @"127.0.0.1"
|
||||||
|
#define DEFAULTPORT @"54321"
|
||||||
|
#define DEFAULTMODE NO
|
||||||
|
#define DEFAULTLOGIN @"login"
|
||||||
|
#define DEFAULTPASSWORD @"password"
|
||||||
|
|
||||||
|
/* lock's conditions */
|
||||||
|
#define READY 0 /* the initial condition */
|
||||||
|
#define STARTED 1 /* the SimpleWebServer has been started */
|
||||||
|
#define STOPPED 2 /* the SimpleWebServer has been stopped */
|
||||||
|
|
||||||
|
/* the time step for the runloop */
|
||||||
|
#define TIMING 0.1
|
||||||
|
/* the maximum duration of running of the SimpleWebServer instance
|
||||||
|
after which the server must be shut down */
|
||||||
|
#define MAXDURATION 3.0
|
||||||
|
|
||||||
|
@implementation TestWebServer
|
||||||
|
|
||||||
|
- (id)init
|
||||||
|
{
|
||||||
|
return [self initWithAddress: DEFAULTADDRESS
|
||||||
|
port: DEFAULTPORT
|
||||||
|
mode: DEFAULTMODE
|
||||||
|
extra: nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id)initWithAddress:(NSString *)address
|
||||||
|
port:(NSString *)port
|
||||||
|
mode:(BOOL)detached
|
||||||
|
extra:(id)extra
|
||||||
|
{
|
||||||
|
if((self = [super init]) != nil)
|
||||||
|
{
|
||||||
|
_debug = NO;
|
||||||
|
_address = [[NSHost hostWithName: address] address];
|
||||||
|
RETAIN(_address);
|
||||||
|
ASSIGN(_port, port);
|
||||||
|
ASSIGN(_extra, extra);
|
||||||
|
_password = nil;
|
||||||
|
_login = nil;
|
||||||
|
_isSecure = NO;
|
||||||
|
|
||||||
|
if([extra isKindOfClass: [NSDictionary class]])
|
||||||
|
{
|
||||||
|
NSDictionary *d = extra;
|
||||||
|
NSString *proto;
|
||||||
|
|
||||||
|
if((_password = [d objectForKey: @"Password"]) != nil)
|
||||||
|
{
|
||||||
|
RETAIN(_password);
|
||||||
|
}
|
||||||
|
if((_login = [d objectForKey: @"Login"]) != nil)
|
||||||
|
{
|
||||||
|
RETAIN(_login);
|
||||||
|
}
|
||||||
|
if((proto = [d objectForKey: @"Protocol"]) != nil &&
|
||||||
|
[[proto lowercaseString] isEqualToString: @"https"])
|
||||||
|
{
|
||||||
|
_isSecure = YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(nil == _login)
|
||||||
|
{
|
||||||
|
_login = DEFAULTLOGIN; RETAIN(_login);
|
||||||
|
}
|
||||||
|
if(nil == _password)
|
||||||
|
{
|
||||||
|
_password = DEFAULTPASSWORD; RETAIN(_password);
|
||||||
|
}
|
||||||
|
|
||||||
|
_traversalMap = [[NSMutableDictionary alloc]
|
||||||
|
initWithObjectsAndKeys:
|
||||||
|
[Handler200 class], @"200",
|
||||||
|
[Handler204 class], @"204",
|
||||||
|
[Handler301 class], @"301",
|
||||||
|
[Handler400 class], @"400",
|
||||||
|
[Handler401 class], @"401",
|
||||||
|
[Handler402 class], @"402",
|
||||||
|
[Handler404 class], @"404",
|
||||||
|
[Handler405 class], @"405",
|
||||||
|
[Handler409 class], @"409",
|
||||||
|
[Handler500 class], @"500",
|
||||||
|
[Handler505 class], @"505",
|
||||||
|
[Handler507 class], @"507",
|
||||||
|
[HandlerIndex class], @"index",
|
||||||
|
nil];
|
||||||
|
|
||||||
|
_lock = [[NSConditionLock alloc] initWithCondition: READY];
|
||||||
|
_threadToQuit = NO;
|
||||||
|
if(detached)
|
||||||
|
{
|
||||||
|
_serverThread = [[NSThread alloc] initWithTarget: self
|
||||||
|
selector: @selector(_startDetached:)
|
||||||
|
object: extra];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_serverThread = nil;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
DESTROY(_lock);
|
||||||
|
DESTROY(_address);
|
||||||
|
DESTROY(_port);
|
||||||
|
DESTROY(_extra);
|
||||||
|
DESTROY(_login);
|
||||||
|
DESTROY(_password);
|
||||||
|
_delegate = nil;
|
||||||
|
DESTROY(_traversalMap);
|
||||||
|
[super dealloc];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)start:(id)extra
|
||||||
|
{
|
||||||
|
if([_server port] != nil)
|
||||||
|
{
|
||||||
|
if(YES == _debug)
|
||||||
|
{
|
||||||
|
NSWarnMLog(@"SimpleWebServer already started");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(nil != _serverThread)
|
||||||
|
{
|
||||||
|
if(![_serverThread isExecuting])
|
||||||
|
{
|
||||||
|
NSTimeInterval duration = 0.0;
|
||||||
|
[_serverThread start];
|
||||||
|
|
||||||
|
// wait for the SimpleWebServer is started
|
||||||
|
while(![_lock tryLockWhenCondition: STARTED] &&
|
||||||
|
duration < MAXDURATION)
|
||||||
|
{
|
||||||
|
[[NSRunLoop currentRunLoop]
|
||||||
|
runUntilDate: [NSDate dateWithTimeIntervalSinceNow: TIMING]];
|
||||||
|
duration += TIMING;
|
||||||
|
}
|
||||||
|
[_lock unlock];
|
||||||
|
if(duration >= MAXDURATION &&
|
||||||
|
nil != _delegate &&
|
||||||
|
[_delegate respondsToSelector: @selector(timeoutExceededByHandler:)])
|
||||||
|
{
|
||||||
|
[_delegate timeoutExceededByHandler: self];
|
||||||
|
[self stop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NSWarnMLog(@"the detached thread is already started");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
[self _startHTTPServer: extra];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)stop
|
||||||
|
{
|
||||||
|
if([_server port] == nil)
|
||||||
|
{
|
||||||
|
if(YES == _debug)
|
||||||
|
{
|
||||||
|
NSWarnMLog(@"SimpleWebServer already stopped");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(nil != _serverThread)
|
||||||
|
{
|
||||||
|
if([_serverThread isExecuting])
|
||||||
|
{
|
||||||
|
NSTimeInterval duration = 0.0;
|
||||||
|
_threadToQuit = YES; // this makes the detached thread quiting from it's runloop
|
||||||
|
|
||||||
|
// wait for the SimpleWebServer is stopped
|
||||||
|
while(![_lock tryLockWhenCondition: STOPPED] &&
|
||||||
|
duration < MAXDURATION)
|
||||||
|
{
|
||||||
|
[[NSRunLoop currentRunLoop]
|
||||||
|
runUntilDate: [NSDate dateWithTimeIntervalSinceNow: TIMING]];
|
||||||
|
duration += TIMING;
|
||||||
|
}
|
||||||
|
[_lock unlockWithCondition: READY];
|
||||||
|
if(duration >= MAXDURATION &&
|
||||||
|
nil != _delegate &&
|
||||||
|
[_delegate respondsToSelector: @selector(timeoutExceededByHandler:)])
|
||||||
|
{
|
||||||
|
[_delegate timeoutExceededByHandler: self];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
[self _stopHTTPServer];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL) processRequest:(GSMimeDocument *)request
|
||||||
|
response:(GSMimeDocument *)response
|
||||||
|
for:(SimpleWebServer *)server
|
||||||
|
{
|
||||||
|
NSString *path;
|
||||||
|
NSString *component;
|
||||||
|
NSEnumerator *en;
|
||||||
|
BOOL ret = NO;
|
||||||
|
id handler;
|
||||||
|
|
||||||
|
// analyze the path to infer what the client wants
|
||||||
|
path = [[request headerNamed:@"x-http-path"] value];
|
||||||
|
|
||||||
|
// traverse over the path to search the right handler
|
||||||
|
en = [[path pathComponents] objectEnumerator];
|
||||||
|
while((component = [en nextObject]) != nil)
|
||||||
|
{
|
||||||
|
if((handler = [_traversalMap objectForKey: component]) != nil)
|
||||||
|
{
|
||||||
|
// the handler has been found
|
||||||
|
break; // while
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(nil == component)
|
||||||
|
{
|
||||||
|
// no component found... default 204
|
||||||
|
component = @"204";
|
||||||
|
handler = [_traversalMap objectForKey: component];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(handler == [handler class])
|
||||||
|
{
|
||||||
|
// it is a Class not an instance... instantiate and substitute
|
||||||
|
// instead of the Class
|
||||||
|
Class cls = (Class)handler;
|
||||||
|
handler = [[cls alloc] init];
|
||||||
|
[_traversalMap setObject: handler forKey: component];
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the handler
|
||||||
|
[handler setDelegate: _delegate];
|
||||||
|
[handler setLogin: _login];
|
||||||
|
[handler setPassword: _password];
|
||||||
|
if([handler respondsToSelector: @selector(setURLString:)])
|
||||||
|
{
|
||||||
|
if([handler isKindOfClass: [HandlerIndex class]])
|
||||||
|
{
|
||||||
|
NSString *urlString = [NSString stringWithFormat: @"%@://%@:%@/",
|
||||||
|
_isSecure ? @"https" : @"http",
|
||||||
|
_address,
|
||||||
|
_port];
|
||||||
|
|
||||||
|
[(HandlerIndex *)handler setURLString: urlString];
|
||||||
|
}
|
||||||
|
else if([handler isKindOfClass: [Handler301 class]])
|
||||||
|
{
|
||||||
|
// by default http://localhost:54322/
|
||||||
|
NSString *port = [NSString stringWithFormat: @"%u", [_port intValue] + 1]; // the TestWebServer's port + 1
|
||||||
|
NSString *urlString = [NSString stringWithFormat: @"%@://%@:%@/",
|
||||||
|
_isSecure ? @"https" : @"http",
|
||||||
|
_address,
|
||||||
|
port];
|
||||||
|
|
||||||
|
[(Handler301 *)handler setURLString: urlString];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: add session-related conditions here
|
||||||
|
[handler prehandleRequest: request
|
||||||
|
response: response
|
||||||
|
for: self];
|
||||||
|
|
||||||
|
// the main job
|
||||||
|
ret = [handler handleRequest: request
|
||||||
|
response: response
|
||||||
|
for: self];
|
||||||
|
// TODO: analyze 'ret'... if NO a default handler must be called
|
||||||
|
|
||||||
|
// TODO: add session-related conditions here
|
||||||
|
[handler posthandleRequest: request
|
||||||
|
response: response
|
||||||
|
for: self];
|
||||||
|
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* end of SimpleWebServer delegate */
|
||||||
|
|
||||||
|
|
||||||
|
/* getters */
|
||||||
|
|
||||||
|
- (NSString *)address
|
||||||
|
{
|
||||||
|
return _address;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)port
|
||||||
|
{
|
||||||
|
return _port;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)login
|
||||||
|
{
|
||||||
|
return _login;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)password
|
||||||
|
{
|
||||||
|
return _password;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)isSecure
|
||||||
|
{
|
||||||
|
return _isSecure;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* end of getters */
|
||||||
|
|
||||||
|
/* setters */
|
||||||
|
- (void)setDelegate:(id)delegate
|
||||||
|
{
|
||||||
|
_delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setDebug:(BOOL)mode
|
||||||
|
{
|
||||||
|
_debug = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* end of setters */
|
||||||
|
|
||||||
|
@end /* TestWebServer */
|
||||||
|
|
||||||
|
|
||||||
|
@implementation TestWebServer (Private)
|
||||||
|
|
||||||
|
- (void)_startHTTPServer:(id)extra
|
||||||
|
{
|
||||||
|
NSString *certPath;
|
||||||
|
NSString *keyPath;
|
||||||
|
NSDictionary *secure = nil;
|
||||||
|
BOOL status;
|
||||||
|
|
||||||
|
_server = [SimpleWebServer new];
|
||||||
|
[_server setDebug: _debug];
|
||||||
|
[_server setDelegate: self];
|
||||||
|
if(_isSecure)
|
||||||
|
{
|
||||||
|
if([_address isEqualToString: @"127.0.0.1"] ||
|
||||||
|
[_address isEqualToString: @"localhost"])
|
||||||
|
{
|
||||||
|
certPath = [[NSBundle bundleForClass: [self class]]
|
||||||
|
pathForResource: @"testCert"
|
||||||
|
ofType: @"pem"];
|
||||||
|
keyPath = [[NSBundle bundleForClass: [self class]]
|
||||||
|
pathForResource: @"testKey"
|
||||||
|
ofType: @"pem"];
|
||||||
|
secure = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
certPath, @"CertificateFile",
|
||||||
|
keyPath, @"KeyFile",
|
||||||
|
nil];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// NOTE: generate corresponding certificates for any address differing
|
||||||
|
// from 127.0.0.1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
status = [_server setAddress: _address port: _port secure: secure]; // run the server
|
||||||
|
if(!status)
|
||||||
|
{
|
||||||
|
[NSException raise: NSInternalInconsistencyException
|
||||||
|
format: @"The server hasn't run. Perhaps the port %@ is invalid", DEFAULTPORT];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)_stopHTTPServer
|
||||||
|
{
|
||||||
|
if(nil != _server && [_server port] != nil)
|
||||||
|
{
|
||||||
|
[_server stop]; // shut down the server
|
||||||
|
if(YES == _debug)
|
||||||
|
{
|
||||||
|
NSLog(@"%@: stopped SimpleWebServer %@", self, _server);
|
||||||
|
}
|
||||||
|
DESTROY(_server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)_startDetached:(id)extra
|
||||||
|
{
|
||||||
|
CREATE_AUTORELEASE_POOL(arp);
|
||||||
|
NSTimeInterval duration = 0.0;
|
||||||
|
|
||||||
|
[_lock lockWhenCondition: READY];
|
||||||
|
[self _startHTTPServer: extra];
|
||||||
|
[_lock unlockWithCondition: STARTED];
|
||||||
|
|
||||||
|
if(YES == _debug)
|
||||||
|
{
|
||||||
|
NSLog(@"%@: enter into runloop in detached thread %@", self, [NSThread currentThread]);
|
||||||
|
}
|
||||||
|
|
||||||
|
while(!_threadToQuit && duration < MAXDURATION)
|
||||||
|
{
|
||||||
|
[[NSRunLoop currentRunLoop]
|
||||||
|
runUntilDate: [NSDate dateWithTimeIntervalSinceNow: TIMING]];
|
||||||
|
duration += TIMING;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(YES == _debug)
|
||||||
|
{
|
||||||
|
NSLog(@"%@: exit from runloop in detached thread %@", self, [NSThread currentThread]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(duration >= MAXDURATION &&
|
||||||
|
nil != _delegate &&
|
||||||
|
[_delegate respondsToSelector: @selector(timeoutExceededByHandler:)])
|
||||||
|
{
|
||||||
|
[_delegate timeoutExceededByHandler: self];
|
||||||
|
}
|
||||||
|
|
||||||
|
[_lock lockWhenCondition: STARTED];
|
||||||
|
[self _stopHTTPServer];
|
||||||
|
[_lock unlockWithCondition: STOPPED];
|
||||||
|
|
||||||
|
DESTROY(arp);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@end /* TestWebServer (Private) */
|
23
Tests/base/NSURLConnection/Helpers/testCert.pem
Normal file
23
Tests/base/NSURLConnection/Helpers/testCert.pem
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIID6TCCAtGgAwIBAgIJAP02s2/x3i8ZMA0GCSqGSIb3DQEBCwUAMIGKMQswCQYD
|
||||||
|
VQQGEwJYWDEOMAwGA1UECAwFV29ybGQxEDAOBgNVBAcMB0dOVXN0ZXAxITAfBgNV
|
||||||
|
BAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0
|
||||||
|
MSIwIAYJKoZIhvcNAQkBFhNnbnVzdGVwLWRldkBnbnUub3JnMB4XDTE0MDgwNzEx
|
||||||
|
MTIxN1oXDTI0MDgwNDExMTIxN1owgYoxCzAJBgNVBAYTAlhYMQ4wDAYDVQQIDAVX
|
||||||
|
b3JsZDEQMA4GA1UEBwwHR05Vc3RlcDEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
|
||||||
|
cyBQdHkgTHRkMRIwEAYDVQQDDAlsb2NhbGhvc3QxIjAgBgkqhkiG9w0BCQEWE2du
|
||||||
|
dXN0ZXAtZGV2QGdudS5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
|
||||||
|
AQDxFVEHh137hyl0juYvbXuAOUIXRSVwk92mAGJIIn0g0Dm6KIAJW1EGR5LeHY3L
|
||||||
|
vrkxEAvGxb7Ypqtg1F4OcwoZE/1Y0xKjHBnqtMJcw7DgN9F1dIkQ9HxHkYPiHzTF
|
||||||
|
d7floomsjyt0BcqAqE1Qf0ahsnveq9E6KTIYRTZ91RGHQrAW4KBxFM30ieHYQYdn
|
||||||
|
2vDgH/8wyLjwQ+89P5SrBJdt+eKHPjHvjs1WZe0i660hvLa19l/DQ5ZxlV5LJ6tZ
|
||||||
|
yjvr+OjT9tE86r9n34vk+ZZBCQwvzZ+dHwLpufz3VgPap54bCCLY21BZj15nqITN
|
||||||
|
k+8XJz+aavFe3zdMvT9cGiZNAgMBAAGjUDBOMB0GA1UdDgQWBBRB6fstnCsIvg3r
|
||||||
|
9f3EmiZhLS867TAfBgNVHSMEGDAWgBRB6fstnCsIvg3r9f3EmiZhLS867TAMBgNV
|
||||||
|
HRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQC0fy/lWgBZRTXfAUOfZp5sWJcQ
|
||||||
|
mQKQiPfXYxiUALq6iI1SyQq90kJ1DyLEGJJ9HEaP3s3jyFQgLXoi1J/8qyOESUDy
|
||||||
|
ogC8nIod6vfA9g8eWcFeOEd6YnNWykPjGCqA/mzrzN3abFkERap8ivx4RWVYX9bP
|
||||||
|
ZNhJVxcoqVCjtFmIh6ATNG/0+xfxax4U4GitcHNYD0Ij+qdXqNJHym67uVponLYE
|
||||||
|
SnxxF3lZ6FzB51SyKtJF5j4/fEeyDXR8Uy3zJdrb6mhSUJNX5RYcrdiR9RflrfGy
|
||||||
|
qYOXFtWmFv/+w/OaqugXQfx9By8uI49U9BG8Q7xSsZSXmoEuMGYt+UwE7UP+
|
||||||
|
-----END CERTIFICATE-----
|
27
Tests/base/NSURLConnection/Helpers/testKey.pem
Normal file
27
Tests/base/NSURLConnection/Helpers/testKey.pem
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpAIBAAKCAQEA8RVRB4dd+4cpdI7mL217gDlCF0UlcJPdpgBiSCJ9INA5uiiA
|
||||||
|
CVtRBkeS3h2Ny765MRALxsW+2KarYNReDnMKGRP9WNMSoxwZ6rTCXMOw4DfRdXSJ
|
||||||
|
EPR8R5GD4h80xXe35aKJrI8rdAXKgKhNUH9GobJ73qvROikyGEU2fdURh0KwFuCg
|
||||||
|
cRTN9Inh2EGHZ9rw4B//MMi48EPvPT+UqwSXbfnihz4x747NVmXtIuutIby2tfZf
|
||||||
|
w0OWcZVeSyerWco76/jo0/bRPOq/Z9+L5PmWQQkML82fnR8C6bn891YD2qeeGwgi
|
||||||
|
2NtQWY9eZ6iEzZPvFyc/mmrxXt83TL0/XBomTQIDAQABAoIBAAZmlnwor+oZsJQT
|
||||||
|
pzDjK0BAROzxPQk8I8pggDuCDuhsHtw+bwfQkNol1FRpXHZoXepbjrR8U5DU+//a
|
||||||
|
I5UmoMIBsdxF3lzORjHhErf7yhpp4PnJWkpE83fC+Ulroq8LeqpyIk2ej3zJGpNH
|
||||||
|
5KWae3mXj4pd7XQp29ahH80/dvOsWGZyYOXqh7jO6UdXfhPHKIhLN6wd9nzmRy44
|
||||||
|
PLFcQYo/nTM19ackz9joovyGPy87BGsj3+rALRXd+GMPVtRFFPMtm6PnSTsMsoDg
|
||||||
|
HEsm76CygnmL7/ywIXSVY/rfomm73SV9FgFalkUSpNWdLqiXiLICVTKC2P1ZUCdg
|
||||||
|
eL/A59ECgYEA/nfw/9Vh86LVQ6Vqfp9W/pAut9ik8SecqodOptBSVV2YBYcfBejL
|
||||||
|
dqksW3LaARabGMJh1z5tHfIw+buF7tl8OIy+TNYRNdrUlsNpI7lWVCVp5F4Znr3c
|
||||||
|
a7GsGE82/XA3eiEZhUfaQeCctOMXoPnZO32mdDal3rYYQ7yKMvAO8gsCgYEA8ojA
|
||||||
|
pK80FURe+kM0Zd/ftTAzMkGJNWnKkSSqd+sI7iCwzd9KAX3d+EM4/vqguMXzw+MP
|
||||||
|
FMkXOv4PUP0iV1XyBt1hn+OSdfax4mD9ALOyJoCHvLPsRjlIv7iEsD2oi84mJ07v
|
||||||
|
3fFuku+xDrPwMBJgeIWjC2ml9J3YYrB5ppPRmAcCgYEA+64/Y5l1ttW/XpeVm8UW
|
||||||
|
8tJCEr2obYfDMPqAtQZn2FyohhcdfOfBjQxHfe87ZUYpgjSHNq9clvi6rdVl41Wh
|
||||||
|
wgCaGz7CaOSVzMNbEuU1WCZk9GSJrHKWNsHUt3pppgK+LAHezu7BFNUFyPauoR1c
|
||||||
|
WLWu01RVe8/Yce5hNX4vGf8CgYEA5w7pmPthfzFX2szTyopyMcftvl85PK3A0m5A
|
||||||
|
CWbdZx+10Sx88Nbc9Xv1fNWA8QeFqIVVBNRfUVBhfyLp6JJ0tZ2LOCwyiDeyWJ1V
|
||||||
|
66lGe+/PYTN4UZ6ZdC1yHAVh4W9QYfqOAr/UPCAman96wBGB3tBR+Ll55YXLdJn0
|
||||||
|
C4KgF1kCgYAHjE0j82DF+6sKw+ggUcMgLM0z6HcYE975T/c4KNqb11Mu2+38CQvY
|
||||||
|
pvlzOgQxzbjq59reek3gRtj3u0UCaXzTnIvgLl+sC0/l4f4qEVwcEcnMiJyUAIsR
|
||||||
|
VR7Mw874clbF8TK8tLlcgCipmDmWxFlrgxOPs1ikcObvTOR/oxacUQ==
|
||||||
|
-----END RSA PRIVATE KEY-----
|
101
Tests/base/NSURLConnection/Helpers/testTestWebServer.m
Normal file
101
Tests/base/NSURLConnection/Helpers/testTestWebServer.m
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Author: Sergei Golovin <Golovin.SV@gmail.com>
|
||||||
|
*
|
||||||
|
* Runs two TestWebServer instances to check how the class TestWebServer
|
||||||
|
* behaves. Visit http://localhost:54321/index to see all supported resources.
|
||||||
|
*
|
||||||
|
* If you visit the main TestWebServer instance with the following command:
|
||||||
|
*
|
||||||
|
* wget -O - --user=login --password=password http://localhost:54321/301 2>&1
|
||||||
|
*
|
||||||
|
* you should get a session log like this:
|
||||||
|
*
|
||||||
|
* --2014-08-13 12:08:01-- http://localhost:54321/301
|
||||||
|
* Resolving localhost (localhost)... 127.0.0.1
|
||||||
|
* Connecting to localhost (localhost)|127.0.0.1|:54321... connected.
|
||||||
|
* HTTP request sent, awaiting response... 401 Unauthorized
|
||||||
|
* Reusing existing connection to localhost:54321.
|
||||||
|
* HTTP request sent, awaiting response... 301 Moved Permanently
|
||||||
|
* Location: http://127.0.0.1:54322/ [following]
|
||||||
|
* --2014-08-13 12:08:01-- http://127.0.0.1:54322/
|
||||||
|
* Connecting to 127.0.0.1:54322... connected.
|
||||||
|
* HTTP request sent, awaiting response... 401 Unauthorized
|
||||||
|
* Reusing existing connection to 127.0.0.1:54322.
|
||||||
|
* HTTP request sent, awaiting response... 204 No Content
|
||||||
|
* Length: 0
|
||||||
|
* Saving to: ‘STDOUT’
|
||||||
|
*
|
||||||
|
* 0K 0.00 =0s
|
||||||
|
*
|
||||||
|
* 2014-08-13 12:08:01 (0.00 B/s) - written to stdout [0/0]
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import "TestWebServer.h"
|
||||||
|
#import "NSURLConnectionTest.h"
|
||||||
|
|
||||||
|
#define TIMING 0.1
|
||||||
|
|
||||||
|
int main(int argc, char **argv, char **env)
|
||||||
|
{
|
||||||
|
CREATE_AUTORELEASE_POOL(arp);
|
||||||
|
NSFileManager *fm;
|
||||||
|
NSBundle *bundle;
|
||||||
|
BOOL loaded;
|
||||||
|
NSString *helperPath;
|
||||||
|
|
||||||
|
fm = [NSFileManager defaultManager];
|
||||||
|
helperPath = [[fm currentDirectoryPath]
|
||||||
|
stringByAppendingString: @"/TestConnection.bundle"];
|
||||||
|
bundle = [NSBundle bundleWithPath: helperPath];
|
||||||
|
loaded = [bundle load];
|
||||||
|
|
||||||
|
if(loaded)
|
||||||
|
{
|
||||||
|
TestWebServer *server1;
|
||||||
|
TestWebServer *server2;
|
||||||
|
Class testClass;
|
||||||
|
BOOL debug = YES;
|
||||||
|
NSDictionary *d;
|
||||||
|
|
||||||
|
testClass = [bundle principalClass]; // NSURLConnectionTest
|
||||||
|
d = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
// @"https", @"Protocol",
|
||||||
|
nil];
|
||||||
|
server1 = [[[testClass testWebServerClass] alloc] initWithAddress: @"localhost"
|
||||||
|
port: @"54321"
|
||||||
|
mode: NO
|
||||||
|
extra: d];
|
||||||
|
[server1 setDebug: debug];
|
||||||
|
[server1 start: d]; // 127.0.0.1:54321 HTTP
|
||||||
|
|
||||||
|
server2 = [[[testClass testWebServerClass] alloc] initWithAddress: @"localhost"
|
||||||
|
port: @"54322"
|
||||||
|
mode: NO
|
||||||
|
extra: d];
|
||||||
|
[server2 setDebug: debug];
|
||||||
|
[server2 start: d]; // 127.0.0.1:54322 HTTP
|
||||||
|
|
||||||
|
while(YES)
|
||||||
|
{
|
||||||
|
[[NSRunLoop currentRunLoop]
|
||||||
|
runUntilDate: [NSDate dateWithTimeIntervalSinceNow: TIMING]];
|
||||||
|
}
|
||||||
|
// [server1 stop];
|
||||||
|
// DESTROY(server1);
|
||||||
|
// [server2 stop];
|
||||||
|
// DESTROY(server2);
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
[NSException raise: NSInternalInconsistencyException
|
||||||
|
format: @"can't load bundle TestConnection"];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
DESTROY(arp);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
137
Tests/base/NSURLConnection/test02.m
Normal file
137
Tests/base/NSURLConnection/test02.m
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
/**
|
||||||
|
* Tests for HTTP.
|
||||||
|
*/
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import "Helpers/NSURLConnectionTest.h"
|
||||||
|
#import "Helpers/TestWebServer.h"
|
||||||
|
#import <Testing.h>
|
||||||
|
|
||||||
|
int main(int argc, char **argv, char **env)
|
||||||
|
{
|
||||||
|
CREATE_AUTORELEASE_POOL(arp);
|
||||||
|
NSFileManager *fm;
|
||||||
|
NSBundle *bundle;
|
||||||
|
BOOL loaded;
|
||||||
|
NSString *helperPath;
|
||||||
|
|
||||||
|
// load the test suite's classes
|
||||||
|
fm = [NSFileManager defaultManager];
|
||||||
|
helperPath = [[fm currentDirectoryPath]
|
||||||
|
stringByAppendingString: @"/Helpers/TestConnection.bundle"];
|
||||||
|
bundle = [NSBundle bundleWithPath: helperPath];
|
||||||
|
loaded = [bundle load];
|
||||||
|
|
||||||
|
if(loaded)
|
||||||
|
{
|
||||||
|
NSDictionary *d;
|
||||||
|
Class testClass;
|
||||||
|
NSDictionary *refs;
|
||||||
|
TestWebServer *server;
|
||||||
|
NSURLConnectionTest *testCase;
|
||||||
|
BOOL debug = NO;
|
||||||
|
|
||||||
|
testClass = [bundle principalClass]; // NSURLConnectionTest
|
||||||
|
|
||||||
|
// create a shared TestWebServer instance for performance
|
||||||
|
server = [[testClass testWebServerClass] new];
|
||||||
|
[server setDebug: debug];
|
||||||
|
[server start: nil]; // 127.0.0.1:54321 HTTP
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Simple GET via HTTP with empty response's body and
|
||||||
|
* the response's status code 204 (by default)
|
||||||
|
*/
|
||||||
|
testCase = [testClass new];
|
||||||
|
[testCase setDebug: debug];
|
||||||
|
// the extra dictionary with test case's parameters
|
||||||
|
d = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
server, @"Instance", // we use the shared TestWebServer instance
|
||||||
|
nil];
|
||||||
|
[testCase setUpTest: d];
|
||||||
|
[testCase startTest: d];
|
||||||
|
PASS([testCase isSuccess], "GET http://localhost:54321");
|
||||||
|
[testCase tearDownTest: d];
|
||||||
|
DESTROY(testCase);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Simple GET via HTTP with the response's status code 400 and
|
||||||
|
* non-empty response's body
|
||||||
|
*/
|
||||||
|
testCase = [testClass new];
|
||||||
|
[testCase setDebug: debug];
|
||||||
|
// the extra dictionary with test case's parameters
|
||||||
|
d = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
server, @"Instance", // we use the shared TestWebServer instance
|
||||||
|
@"400", @"Path", // request the handler responding with 400
|
||||||
|
@"400", @"StatusCode", // the expected status code
|
||||||
|
@"You have issued a request with invalid data", @"Content", // the expected response's body
|
||||||
|
nil];
|
||||||
|
[testCase setUpTest: d];
|
||||||
|
[testCase startTest: d];
|
||||||
|
PASS([testCase isSuccess], "response 400 .... GET http://localhost:54321/400");
|
||||||
|
[testCase tearDownTest: d];
|
||||||
|
DESTROY(testCase);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Simple POST via HTTP with the response's status code 400 and
|
||||||
|
* non-empty response's body
|
||||||
|
*/
|
||||||
|
testCase = [testClass new];
|
||||||
|
[testCase setDebug: debug];
|
||||||
|
// the extra dictionary with test case's parameters
|
||||||
|
d = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
server, @"Instance", // we use the shared TestWebServer instance
|
||||||
|
@"400", @"Path", // request the handler responding with 400
|
||||||
|
@"400", @"StatusCode", // the expected status code
|
||||||
|
@"You have issued a request with invalid data", @"Content", // the expected response's body
|
||||||
|
@"Some payload", @"Payload", // the custom payload
|
||||||
|
@"POST", @"Method", // use POST
|
||||||
|
nil];
|
||||||
|
[testCase setUpTest: d];
|
||||||
|
[testCase startTest: d];
|
||||||
|
PASS([testCase isSuccess], "payload... response 400 .... POST http://localhost:54321/400");
|
||||||
|
[testCase tearDownTest: d];
|
||||||
|
DESTROY(testCase);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tests redirecting... it uses an auxilliary TestWebServer instance and proceeds
|
||||||
|
* in two stages. The first one is to get the status code 301 and go to the URL
|
||||||
|
* given in the response's header 'Location'. The second stage is a simple GET on
|
||||||
|
* the given URL with the status code 204 and empty response's body.
|
||||||
|
*/
|
||||||
|
testCase = [testClass new];
|
||||||
|
[testCase setDebug: debug];
|
||||||
|
// the reference set difference (from the default reference set) we expect
|
||||||
|
refs = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
@"YES", @"GOTREDIRECT",
|
||||||
|
nil];
|
||||||
|
// the extra dictionary with test case's parameters
|
||||||
|
d = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
server, @"Instance", // we use the shared TestWebServer instance
|
||||||
|
@"/301", @"Path", // request the handler responding with a redirect
|
||||||
|
@"/", @"RedirectPath", // the URL's path of redirecting
|
||||||
|
@"YES", @"IsAuxilliary", // start an auxilliary TestWebServer instance
|
||||||
|
refs, @"ReferenceFlags", // the expected reference set difference
|
||||||
|
nil];
|
||||||
|
[testCase setUpTest: d];
|
||||||
|
[testCase startTest: d];
|
||||||
|
PASS([testCase isSuccess], "redirecting... GET http://localhost:54321/301");
|
||||||
|
[testCase tearDownTest: d];
|
||||||
|
DESTROY(testCase);
|
||||||
|
|
||||||
|
// cleaning
|
||||||
|
[server stop];
|
||||||
|
DESTROY(server);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// no classes no tests
|
||||||
|
[NSException raise: NSInternalInconsistencyException
|
||||||
|
format: @"can't load bundle TestConnection"];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
DESTROY(arp);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
145
Tests/base/NSURLConnection/test03.m
Normal file
145
Tests/base/NSURLConnection/test03.m
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
/**
|
||||||
|
* Tests for HTTPS
|
||||||
|
* (must be the same as test02.m except for the use of HTTPS).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import "Helpers/NSURLConnectionTest.h"
|
||||||
|
#import "Helpers/TestWebServer.h"
|
||||||
|
#import <Testing.h>
|
||||||
|
|
||||||
|
int main(int argc, char **argv, char **env)
|
||||||
|
{
|
||||||
|
CREATE_AUTORELEASE_POOL(arp);
|
||||||
|
NSFileManager *fm;
|
||||||
|
NSBundle *bundle;
|
||||||
|
BOOL loaded;
|
||||||
|
NSString *helperPath;
|
||||||
|
|
||||||
|
// load the test suite's classes
|
||||||
|
fm = [NSFileManager defaultManager];
|
||||||
|
helperPath = [[fm currentDirectoryPath]
|
||||||
|
stringByAppendingString: @"/Helpers/TestConnection.bundle"];
|
||||||
|
bundle = [NSBundle bundleWithPath: helperPath];
|
||||||
|
loaded = [bundle load];
|
||||||
|
|
||||||
|
if(loaded)
|
||||||
|
{
|
||||||
|
NSDictionary *d;
|
||||||
|
Class testClass;
|
||||||
|
NSDictionary *refs;
|
||||||
|
TestWebServer *server;
|
||||||
|
NSURLConnectionTest *testCase;
|
||||||
|
BOOL debug = NO;
|
||||||
|
|
||||||
|
testClass = [bundle principalClass]; // NSURLConnectionTest
|
||||||
|
|
||||||
|
// the extra dictionary commanding to use HTTPS
|
||||||
|
d = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
@"HTTPS", @"Protocol",
|
||||||
|
nil];
|
||||||
|
// create a shared TestWebServer instance for performance
|
||||||
|
server = [[[testClass testWebServerClass] alloc] initWithAddress: @"localhost"
|
||||||
|
port: @"54321"
|
||||||
|
mode: NO
|
||||||
|
extra: d];
|
||||||
|
[server setDebug: debug];
|
||||||
|
[server start: d]; // 127.0.0.1:54321 HTTPS
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Simple GET via HTTPS with empty response's body and
|
||||||
|
* the response's status code 204 (by default)
|
||||||
|
*/
|
||||||
|
testCase = [testClass new];
|
||||||
|
[testCase setDebug: debug];
|
||||||
|
// the extra dictionary with test case's parameters
|
||||||
|
d = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
server, @"Instance", // we use the shared TestWebServer instance
|
||||||
|
nil];
|
||||||
|
[testCase setUpTest: d];
|
||||||
|
[testCase startTest: d];
|
||||||
|
PASS([testCase isSuccess], "HTTPS... GET https://localhost:54321");
|
||||||
|
[testCase tearDownTest: d];
|
||||||
|
DESTROY(testCase);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Simple GET via HTTPS with the response's status code 400 and
|
||||||
|
* non-empty response's body
|
||||||
|
*/
|
||||||
|
testCase = [testClass new];
|
||||||
|
[testCase setDebug: debug];
|
||||||
|
// the extra dictionary with test case's parameters
|
||||||
|
d = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
server, @"Instance", // we use the shared TestWebServer instance
|
||||||
|
@"400", @"Path", // request the handler responding with 400
|
||||||
|
@"400", @"StatusCode", // the expected status code
|
||||||
|
@"You have issued a request with invalid data", @"Content", // the expected response's body
|
||||||
|
nil];
|
||||||
|
[testCase setUpTest: d];
|
||||||
|
[testCase startTest: d];
|
||||||
|
PASS([testCase isSuccess], "HTTPS... response 400 .... GET https://localhost:54321/400");
|
||||||
|
[testCase tearDownTest: d];
|
||||||
|
DESTROY(testCase);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Simple POST via HTTPS with the response's status code 400 and
|
||||||
|
* non-empty response's body
|
||||||
|
*/
|
||||||
|
testCase = [testClass new];
|
||||||
|
[testCase setDebug: debug];
|
||||||
|
// the extra dictionary with test case's parameters
|
||||||
|
d = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
server, @"Instance", // we use the shared TestWebServer instance
|
||||||
|
@"400", @"Path", // request the handler responding with 400
|
||||||
|
@"400", @"StatusCode", // the expected status code
|
||||||
|
@"You have issued a request with invalid data", @"Content", // the expected response's body
|
||||||
|
@"Some payload", @"Payload", // the custom payload
|
||||||
|
@"POST", @"Method", // use POST
|
||||||
|
nil];
|
||||||
|
[testCase setUpTest: d];
|
||||||
|
[testCase startTest: d];
|
||||||
|
PASS([testCase isSuccess], "HTTPS... payload... response 400 .... POST https://localhost:54321/400");
|
||||||
|
[testCase tearDownTest: d];
|
||||||
|
DESTROY(testCase);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tests redirecting... it uses an auxilliary TestWebServer instance and proceeds
|
||||||
|
* in two stages. The first one is to get the status code 301 and go to the URL
|
||||||
|
* given in the response's header 'Location'. The second stage is a simple GET on
|
||||||
|
* the given URL with the status code 204 and empty response's body.
|
||||||
|
*/
|
||||||
|
testCase = [testClass new];
|
||||||
|
[testCase setDebug: debug];
|
||||||
|
// the reference set difference (from the default reference set) we expect
|
||||||
|
refs = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
@"YES", @"GOTREDIRECT",
|
||||||
|
nil];
|
||||||
|
// the extra dictionary with test case's parameters
|
||||||
|
d = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
server, @"Instance", // we use the shared TestWebServer instance
|
||||||
|
@"/301", @"Path", // request the handler responding with a redirect
|
||||||
|
@"/", @"RedirectPath", // the URL's path of redirecting
|
||||||
|
@"YES", @"IsAuxilliary", // start an auxilliary TestWebServer instance
|
||||||
|
refs, @"ReferenceFlags", // the expected reference set difference
|
||||||
|
nil];
|
||||||
|
[testCase setUpTest: d];
|
||||||
|
[testCase startTest: d];
|
||||||
|
PASS([testCase isSuccess], "HTTPS... redirecting... GET https://localhost:54321/301");
|
||||||
|
[testCase tearDownTest: d];
|
||||||
|
DESTROY(testCase);
|
||||||
|
|
||||||
|
// cleaning
|
||||||
|
[server stop];
|
||||||
|
DESTROY(server);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// no classes no tests
|
||||||
|
[NSException raise: NSInternalInconsistencyException
|
||||||
|
format: @"can't load bundle TestConnection"];
|
||||||
|
}
|
||||||
|
|
||||||
|
DESTROY(arp);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
170
Tests/base/NSURLConnection/test04.m
Normal file
170
Tests/base/NSURLConnection/test04.m
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
/**
|
||||||
|
* Tests for HTTP without authorization.
|
||||||
|
*/
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import "Helpers/NSURLConnectionTest.h"
|
||||||
|
#import "Helpers/TestWebServer.h"
|
||||||
|
#import <Testing.h>
|
||||||
|
|
||||||
|
int main(int argc, char **argv, char **env)
|
||||||
|
{
|
||||||
|
CREATE_AUTORELEASE_POOL(arp);
|
||||||
|
NSFileManager *fm;
|
||||||
|
NSBundle *bundle;
|
||||||
|
BOOL loaded;
|
||||||
|
NSString *helperPath;
|
||||||
|
|
||||||
|
// load the test suite's classes
|
||||||
|
fm = [NSFileManager defaultManager];
|
||||||
|
helperPath = [[fm currentDirectoryPath]
|
||||||
|
stringByAppendingString: @"/Helpers/TestConnection.bundle"];
|
||||||
|
bundle = [NSBundle bundleWithPath: helperPath];
|
||||||
|
loaded = [bundle load];
|
||||||
|
|
||||||
|
if(loaded)
|
||||||
|
{
|
||||||
|
NSDictionary *d;
|
||||||
|
Class testClass;
|
||||||
|
NSDictionary *refs;
|
||||||
|
TestWebServer *server;
|
||||||
|
NSURLConnectionTest *testCase;
|
||||||
|
BOOL debug = NO;
|
||||||
|
|
||||||
|
testClass = [bundle principalClass]; // NSURLConnectionTest
|
||||||
|
|
||||||
|
// create a shared TestWebServer instance for performance
|
||||||
|
server = [[testClass testWebServerClass] new];
|
||||||
|
[server setDebug: debug];
|
||||||
|
[server start: nil]; // 127.0.0.1:54321 HTTP
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Simple GET via HTTP without authorization with empty response's body and
|
||||||
|
* the response's status code 204 (by default)
|
||||||
|
*/
|
||||||
|
testCase = [testClass new];
|
||||||
|
[testCase setDebug: debug];
|
||||||
|
// the reference set difference (from the default reference set) we expect
|
||||||
|
// the flags must not be set because we request a path with no authorization
|
||||||
|
// See NSURLConnectionTest.h for details (the key words are 'TestCase' and 'ReferenceFlags')
|
||||||
|
refs = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
@"NO", @"GOTUNAUTHORIZED",
|
||||||
|
@"NO", @"AUTHORIZED",
|
||||||
|
@"NO", @"NOTAUTHORIZED",
|
||||||
|
nil];
|
||||||
|
// the extra dictionary with test case's parameters
|
||||||
|
d = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
server, @"Instance", // we use the shared TestWebServer instance
|
||||||
|
@"/withoutauth", @"Path", // the path commands to use no authorization
|
||||||
|
refs, @"ReferenceFlags", // the expected reference set difference
|
||||||
|
nil];
|
||||||
|
[testCase setUpTest: d];
|
||||||
|
[testCase startTest: d];
|
||||||
|
PASS([testCase isSuccess], "no auth... GET http://localhost:54321/withoutauth");
|
||||||
|
[testCase tearDownTest: d];
|
||||||
|
DESTROY(testCase);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Simple GET via HTTP without authorization with the response's status code 400
|
||||||
|
* and non-empty response's body
|
||||||
|
*/
|
||||||
|
testCase = [testClass new];
|
||||||
|
[testCase setDebug: debug];
|
||||||
|
// the reference set difference (from the default reference set) we expect
|
||||||
|
// the flags must not be set because we request a path with no authorization
|
||||||
|
// See NSURLConnectionTest.h for details (the key words are 'TestCase' and 'ReferenceFlags')
|
||||||
|
refs = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
@"NO", @"GOTUNAUTHORIZED",
|
||||||
|
@"NO", @"AUTHORIZED",
|
||||||
|
@"NO", @"NOTAUTHORIZED",
|
||||||
|
nil];
|
||||||
|
// the extra dictionary with test case's parameters
|
||||||
|
d = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
server, @"Instance", // we use the shared TestWebServer instance
|
||||||
|
@"400/withoutauth", @"Path", // request the handler responding with 400
|
||||||
|
@"400", @"StatusCode", // the expected status code
|
||||||
|
@"You have issued a request with invalid data", @"Content", // the expected response's body
|
||||||
|
refs, @"ReferenceFlags", // the expected reference set difference
|
||||||
|
nil];
|
||||||
|
[testCase setUpTest: d];
|
||||||
|
[testCase startTest: d];
|
||||||
|
PASS([testCase isSuccess], "no auth... response 400 .... GET http://localhost:54321/400/withoutauth");
|
||||||
|
[testCase tearDownTest: d];
|
||||||
|
DESTROY(testCase);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Simple POST via HTTP with the response's status code 400 and
|
||||||
|
* non-empty response's body
|
||||||
|
*/
|
||||||
|
testCase = [testClass new];
|
||||||
|
[testCase setDebug: debug];
|
||||||
|
// the reference set difference (from the default reference set) we expect
|
||||||
|
// the flags must not be set because we request a path with no authorization
|
||||||
|
// See NSURLConnectionTest.h for details (the key words are 'TestCase' and 'ReferenceFlags')
|
||||||
|
refs = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
@"NO", @"GOTUNAUTHORIZED",
|
||||||
|
@"NO", @"AUTHORIZED",
|
||||||
|
@"NO", @"NOTAUTHORIZED",
|
||||||
|
nil];
|
||||||
|
// the extra dictionary with test case's parameters
|
||||||
|
d = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
server, @"Instance", // we use the shared TestWebServer instance
|
||||||
|
@"400/withoutauth", @"Path", // request the handler responding with 400
|
||||||
|
@"400", @"StatusCode", // the expected status code
|
||||||
|
@"You have issued a request with invalid data", @"Content", // the expected response's body
|
||||||
|
@"Some payload", @"Payload", // the custom payload
|
||||||
|
@"POST", @"Method", // use POST
|
||||||
|
refs, @"ReferenceFlags", // the expected reference set difference
|
||||||
|
nil];
|
||||||
|
[testCase setUpTest: d];
|
||||||
|
[testCase startTest: d];
|
||||||
|
PASS([testCase isSuccess], "no auth... payload... response 400 .... POST http://localhost:54321/400/withoutauth");
|
||||||
|
[testCase tearDownTest: d];
|
||||||
|
DESTROY(testCase);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tests redirecting... it uses an auxilliary TestWebServer instance and proceeds
|
||||||
|
* in two stages. The first one is to get the status code 301 and go to the URL
|
||||||
|
* given in the response's header 'Location'. The second stage is a simple GET on
|
||||||
|
* the given URL with the status code 204 and empty response's body.
|
||||||
|
*/
|
||||||
|
testCase = [testClass new];
|
||||||
|
[testCase setDebug: debug];
|
||||||
|
// the reference set difference (from the default reference set) we expect
|
||||||
|
// the flags must not be set because we request a path with no authorization
|
||||||
|
// See NSURLConnectionTest.h for details (the key words are 'TestCase' and 'ReferenceFlags')
|
||||||
|
refs = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
@"NO", @"GOTUNAUTHORIZED",
|
||||||
|
@"NO", @"AUTHORIZED",
|
||||||
|
@"NO", @"NOTAUTHORIZED",
|
||||||
|
@"YES", @"GOTREDIRECT",
|
||||||
|
nil];
|
||||||
|
// the extra dictionary with test case's parameters
|
||||||
|
d = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
server, @"Instance", // we use the shared TestWebServer instance
|
||||||
|
@"/301/withoutauth", @"Path", // request the handler responding with a redirect
|
||||||
|
@"/withoutauth", @"RedirectPath", // the URL's path of redirecting
|
||||||
|
@"YES", @"IsAuxilliary", // start an auxilliary TestWebServer instance
|
||||||
|
refs, @"ReferenceFlags", // the expected reference set difference
|
||||||
|
nil];
|
||||||
|
[testCase setUpTest: d];
|
||||||
|
[testCase startTest: d];
|
||||||
|
PASS([testCase isSuccess], "no auth... redirecting... GET http://localhost:54321/301/withoutauth");
|
||||||
|
[testCase tearDownTest: d];
|
||||||
|
DESTROY(testCase);
|
||||||
|
|
||||||
|
// cleaning
|
||||||
|
[server stop];
|
||||||
|
DESTROY(server);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// no classes no tests
|
||||||
|
[NSException raise: NSInternalInconsistencyException
|
||||||
|
format: @"can't load bundle TestConnection"];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
DESTROY(arp);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
178
Tests/base/NSURLConnection/test05.m
Normal file
178
Tests/base/NSURLConnection/test05.m
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
/**
|
||||||
|
* Tests for HTTPS without authorization.
|
||||||
|
* (must be the same as test04.m except for the use of HTTPS).
|
||||||
|
*/
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import "Helpers/NSURLConnectionTest.h"
|
||||||
|
#import "Helpers/TestWebServer.h"
|
||||||
|
#import <Testing.h>
|
||||||
|
|
||||||
|
int main(int argc, char **argv, char **env)
|
||||||
|
{
|
||||||
|
CREATE_AUTORELEASE_POOL(arp);
|
||||||
|
NSFileManager *fm;
|
||||||
|
NSBundle *bundle;
|
||||||
|
BOOL loaded;
|
||||||
|
NSString *helperPath;
|
||||||
|
|
||||||
|
// load the test suite's classes
|
||||||
|
fm = [NSFileManager defaultManager];
|
||||||
|
helperPath = [[fm currentDirectoryPath]
|
||||||
|
stringByAppendingString: @"/Helpers/TestConnection.bundle"];
|
||||||
|
bundle = [NSBundle bundleWithPath: helperPath];
|
||||||
|
loaded = [bundle load];
|
||||||
|
|
||||||
|
if(loaded)
|
||||||
|
{
|
||||||
|
NSDictionary *d;
|
||||||
|
Class testClass;
|
||||||
|
NSDictionary *refs;
|
||||||
|
TestWebServer *server;
|
||||||
|
NSURLConnectionTest *testCase;
|
||||||
|
BOOL debug = NO;
|
||||||
|
|
||||||
|
testClass = [bundle principalClass]; // NSURLConnectionTest
|
||||||
|
|
||||||
|
// the extra dictionary commanding to use HTTPS
|
||||||
|
d = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
@"HTTPS", @"Protocol",
|
||||||
|
nil];
|
||||||
|
// create a shared TestWebServer instance for performance
|
||||||
|
server = [[[testClass testWebServerClass] alloc] initWithAddress: @"localhost"
|
||||||
|
port: @"54321"
|
||||||
|
mode: NO
|
||||||
|
extra: d];
|
||||||
|
[server setDebug: debug];
|
||||||
|
[server start: d]; // 127.0.0.1:54321 HTTPS
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Simple GET via HTTPS without authorization with empty response's body and
|
||||||
|
* the response's status code 204 (by default)
|
||||||
|
*/
|
||||||
|
testCase = [testClass new];
|
||||||
|
[testCase setDebug: debug];
|
||||||
|
// the reference set difference (from the default reference set) we expect
|
||||||
|
// the flags must not be set because we request a path with no authorization
|
||||||
|
// See NSURLConnectionTest.h for details (the key words are 'TestCase' and 'ReferenceFlags')
|
||||||
|
refs = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
@"NO", @"GOTUNAUTHORIZED",
|
||||||
|
@"NO", @"AUTHORIZED",
|
||||||
|
@"NO", @"NOTAUTHORIZED",
|
||||||
|
nil];
|
||||||
|
// the extra dictionary with test case's parameters
|
||||||
|
d = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
server, @"Instance", // we use the shared TestWebServer instance
|
||||||
|
@"/withoutauth", @"Path", // the path commands to use no authorization
|
||||||
|
refs, @"ReferenceFlags", // the expected reference set difference
|
||||||
|
nil];
|
||||||
|
[testCase setUpTest: d];
|
||||||
|
[testCase startTest: d];
|
||||||
|
PASS([testCase isSuccess], "HTTPS... no auth...GET https://localhost:54321/withoutauth");
|
||||||
|
[testCase tearDownTest: d];
|
||||||
|
DESTROY(testCase);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Simple GET via HTTPS without authorization with the response's status code 400
|
||||||
|
* and non-empty response's body
|
||||||
|
*/
|
||||||
|
testCase = [testClass new];
|
||||||
|
[testCase setDebug: debug];
|
||||||
|
// the reference set difference (from the default reference set) we expect
|
||||||
|
// the flags must not be set because we request a path with no authorization
|
||||||
|
// See NSURLConnectionTest.h for details (the key words are 'TestCase' and 'ReferenceFlags')
|
||||||
|
refs = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
@"NO", @"GOTUNAUTHORIZED",
|
||||||
|
@"NO", @"AUTHORIZED",
|
||||||
|
@"NO", @"NOTAUTHORIZED",
|
||||||
|
nil];
|
||||||
|
// the extra dictionary with test case's parameters
|
||||||
|
d = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
server, @"Instance", // we use the shared TestWebServer instance
|
||||||
|
@"400/withoutauth", @"Path", // request the handler responding with 400
|
||||||
|
@"400", @"StatusCode", // the expected status code
|
||||||
|
@"You have issued a request with invalid data", @"Content", // the expected response's body
|
||||||
|
refs, @"ReferenceFlags", // the expected reference set difference
|
||||||
|
nil];
|
||||||
|
[testCase setUpTest: d];
|
||||||
|
[testCase startTest: d];
|
||||||
|
PASS([testCase isSuccess], "HTTPS... no auth... response 400... GET https://localhost:54321/400/withoutauth");
|
||||||
|
[testCase tearDownTest: d];
|
||||||
|
DESTROY(testCase);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Simple POST via HTTPS with the response's status code 400 and
|
||||||
|
* non-empty response's body
|
||||||
|
*/
|
||||||
|
testCase = [testClass new];
|
||||||
|
[testCase setDebug: debug];
|
||||||
|
// the reference set difference (from the default reference set) we expect
|
||||||
|
// the flags must not be set because we request a path with no authorization
|
||||||
|
// See NSURLConnectionTest.h for details (the key words are 'TestCase' and 'ReferenceFlags')
|
||||||
|
refs = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
@"NO", @"GOTUNAUTHORIZED",
|
||||||
|
@"NO", @"AUTHORIZED",
|
||||||
|
@"NO", @"NOTAUTHORIZED",
|
||||||
|
nil];
|
||||||
|
// the extra dictionary with test case's parameters
|
||||||
|
d = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
server, @"Instance", // we use the shared TestWebServer instance
|
||||||
|
@"400/withoutauth", @"Path", // request the handler responding with 400
|
||||||
|
@"400", @"StatusCode", // the expected status code
|
||||||
|
@"You have issued a request with invalid data", @"Content", // the expected response's body
|
||||||
|
@"Some payload", @"Payload", // the custom payload
|
||||||
|
@"POST", @"Method", // use POST
|
||||||
|
refs, @"ReferenceFlags", // the expected reference set difference
|
||||||
|
nil];
|
||||||
|
[testCase setUpTest: d];
|
||||||
|
[testCase startTest: d];
|
||||||
|
PASS([testCase isSuccess], "HTTPS... no auth... payload... response 400 .... POST https://localhost:54321/400/withoutauth");
|
||||||
|
[testCase tearDownTest: d];
|
||||||
|
DESTROY(testCase);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tests redirecting... it uses an auxilliary TestWebServer instance and proceeds
|
||||||
|
* in two stages. The first one is to get the status code 301 and go to the URL
|
||||||
|
* given in the response's header 'Location'. The second stage is a simple GET on
|
||||||
|
* the given URL with the status code 204 and empty response's body.
|
||||||
|
*/
|
||||||
|
testCase = [testClass new];
|
||||||
|
[testCase setDebug: debug];
|
||||||
|
// the reference set difference (from the default reference set) we expect
|
||||||
|
// the flags must not be set because we request a path with no authorization
|
||||||
|
// See NSURLConnectionTest.h for details (the key words are 'TestCase' and 'ReferenceFlags')
|
||||||
|
refs = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
@"NO", @"GOTUNAUTHORIZED",
|
||||||
|
@"NO", @"AUTHORIZED",
|
||||||
|
@"NO", @"NOTAUTHORIZED",
|
||||||
|
@"YES", @"GOTREDIRECT",
|
||||||
|
nil];
|
||||||
|
// the extra dictionary with test case's parameters
|
||||||
|
d = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
server, @"Instance", // we use the shared TestWebServer instance
|
||||||
|
@"/301/withoutauth", @"Path", // request the handler responding with a redirect
|
||||||
|
@"/withoutauth", @"RedirectPath", // the URL's path of redirecting
|
||||||
|
@"YES", @"IsAuxilliary", // start an auxilliary TestWebServer instance
|
||||||
|
refs, @"ReferenceFlags", // the expected reference set difference
|
||||||
|
nil];
|
||||||
|
[testCase setUpTest: d];
|
||||||
|
[testCase startTest: d];
|
||||||
|
PASS([testCase isSuccess], "HTTPS... no auth... redirecting... GET https://localhost:54321/301/withoutauth");
|
||||||
|
[testCase tearDownTest: d];
|
||||||
|
DESTROY(testCase);
|
||||||
|
|
||||||
|
// cleaning
|
||||||
|
[server stop];
|
||||||
|
DESTROY(server);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// no classes no tests
|
||||||
|
[NSException raise: NSInternalInconsistencyException
|
||||||
|
format: @"can't load bundle TestConnection"];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
DESTROY(arp);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue