diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/CuTest-README.txt | 225 | ||||
-rw-r--r-- | test/CuTest.c | 367 | ||||
-rw-r--r-- | test/CuTest.h | 146 | ||||
-rw-r--r-- | test/serf_bwtp.c | 635 | ||||
-rw-r--r-- | test/serf_get.c | 503 | ||||
-rw-r--r-- | test/serf_request.c | 79 | ||||
-rw-r--r-- | test/serf_response.c | 161 | ||||
-rw-r--r-- | test/serf_server.c | 147 | ||||
-rw-r--r-- | test/serf_spider.c | 826 | ||||
-rw-r--r-- | test/serftestca.pem | 66 | ||||
-rw-r--r-- | test/server/test_server.c | 359 | ||||
-rw-r--r-- | test/server/test_server.h | 70 | ||||
-rw-r--r-- | test/test_all.c | 101 | ||||
-rw-r--r-- | test/test_buckets.c | 425 | ||||
-rw-r--r-- | test/test_context.c | 1011 | ||||
-rw-r--r-- | test/test_serf.h | 126 | ||||
-rw-r--r-- | test/test_ssl.c | 112 | ||||
-rw-r--r-- | test/test_util.c | 214 | ||||
-rw-r--r-- | test/testcases/chunked-empty.response | 11 | ||||
-rw-r--r-- | test/testcases/chunked-trailers.response | 14 | ||||
-rw-r--r-- | test/testcases/chunked.response | 13 | ||||
-rw-r--r-- | test/testcases/deflate.response | bin | 0 -> 639 bytes | |||
-rw-r--r-- | test/testcases/simple.request | 1 | ||||
-rw-r--r-- | test/testcases/simple.response | 32 |
24 files changed, 5644 insertions, 0 deletions
diff --git a/test/CuTest-README.txt b/test/CuTest-README.txt new file mode 100644 index 0000000..2e90234 --- /dev/null +++ b/test/CuTest-README.txt @@ -0,0 +1,225 @@ +Originally obtained from "http://cutest.sourceforge.net/" version 1.4. + +HOW TO USE + +You can use CuTest to create unit tests to drive your development +in the style of Extreme Programming. You can also add unit tests to +existing code to ensure that it works as you suspect. + +Your unit tests are an investment. They let you to change your +code and add new features confidently without worrying about +accidentally breaking earlier features. + + +LICENSING + +Copyright (c) 2003 Asim Jalis + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not +claim that you wrote the original software. If you use this software in +a product, an acknowledgment in the product documentation would be +appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not +be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. + + +GETTING STARTED + +To add unit testing to your C code the only files you need are +CuTest.c and CuTest.h. + +CuTestTest.c and AllTests.c have been included to provide an +example of how to write unit tests and then how to aggregate them +into suites and into a single AllTests.c file. Suites allow you +to put group tests into logical sets. AllTests.c combines all the +suites and runs them. + +You should not have to look inside CuTest.c. Looking in +CuTestTest.c and AllTests.c (for example usage) should be +sufficient. + +After downloading the sources, run your compiler to create an +executable called AllTests.exe. For example, if you are using +Windows with the cl.exe compiler you would type: + + cl.exe AllTests.c CuTest.c CuTestTest.c + AllTests.exe + +This will run all the unit tests associated with CuTest and print +the output on the console. You can replace cl.exe with gcc or +your favorite compiler in the command above. + + +DETAILED EXAMPLE + +Here is a more detailed example. We will work through a simple +test first exercise. The goal is to create a library of string +utilities. First, lets write a function that converts a +null-terminated string to all upper case. + +Ensure that CuTest.c and CuTest.h are accessible from your C +project. Next, create a file called StrUtil.c with these +contents: + + #include "CuTest.h" + + char* StrToUpper(char* str) { + return str; + } + + void TestStrToUpper(CuTest *tc) { + char* input = strdup("hello world"); + char* actual = StrToUpper(input); + char* expected = "HELLO WORLD"; + CuAssertStrEquals(tc, expected, actual); + } + + CuSuite* StrUtilGetSuite() { + CuSuite* suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, TestStrToUpper); + return suite; + } + +Create another file called AllTests.c with these contents: + + #include "CuTest.h" + + CuSuite* StrUtilGetSuite(); + + void RunAllTests(void) { + CuString *output = CuStringNew(); + CuSuite* suite = CuSuiteNew(); + + CuSuiteAddSuite(suite, StrUtilGetSuite()); + + CuSuiteRun(suite); + CuSuiteSummary(suite, output); + CuSuiteDetails(suite, output); + printf("%s\n", output->buffer); + } + + int main(void) { + RunAllTests(); + } + +Then type this on the command line: + + gcc AllTests.c CuTest.c StrUtil.c + +to compile. You can replace gcc with your favorite compiler. +CuTest should be portable enough to handle all Windows and Unix +compilers. Then to run the tests type: + + a.out + +This will print an error because we haven't implemented the +StrToUpper function correctly. We are just returning the string +without changing it to upper case. + + char* StrToUpper(char* str) { + return str; + } + +Rewrite this as follows: + + char* StrToUpper(char* str) { + char* p; + for (p = str ; *p ; ++p) *p = toupper(*p); + return str; + } + +Recompile and run the tests again. The test should pass this +time. + + +WHAT TO DO NEXT + +At this point you might want to write more tests for the +StrToUpper function. Here are some ideas: + +TestStrToUpper_EmptyString : pass in "" +TestStrToUpper_UpperCase : pass in "HELLO WORLD" +TestStrToUpper_MixedCase : pass in "HELLO world" +TestStrToUpper_Numbers : pass in "1234 hello" + +As you write each one of these tests add it to StrUtilGetSuite +function. If you don't the tests won't be run. Later as you write +other functions and write tests for them be sure to include those +in StrUtilGetSuite also. The StrUtilGetSuite function should +include all the tests in StrUtil.c + +Over time you will create another file called FunkyStuff.c +containing other functions unrelated to StrUtil. Follow the same +pattern. Create a FunkyStuffGetSuite function in FunkyStuff.c. +And add FunkyStuffGetSuite to AllTests.c. + +The framework is designed in the way it is so that it is easy to +organize a lot of tests. + +THE BIG PICTURE + +Each individual test corresponds to a CuTest. These are grouped +to form a CuSuite. CuSuites can hold CuTests or other CuSuites. +AllTests.c collects all the CuSuites in the program into a single +CuSuite which it then runs as a single CuSuite. + +The project is open source so feel free to take a peek under the +hood at the CuTest.c file to see how it works. CuTestTest.c +contains tests for CuTest.c. So CuTest tests itself. + +Since AllTests.c has a main() you will need to exclude this when +you are building your product. Here is a nicer way to do this if +you want to avoid messing with multiple builds. Remove the main() +in AllTests.c. Note that it just calls RunAllTests(). Instead +we'll call this directly from the main program. + +Now in the main() of the actual program check to see if the +command line option "--test" was passed. If it was then I call +RunAllTests() from AllTests.c. Otherwise run the real program. + +Shipping the tests with the code can be useful. If you customers +complain about a problem you can ask them to run the unit tests +and send you the output. This can help you to quickly isolate the +piece of your system that is malfunctioning in the customer's +environment. + +CuTest offers a rich set of CuAssert functions. Here is a list: + +void CuAssert(CuTest* tc, char* message, int condition); +void CuAssertTrue(CuTest* tc, int condition); +void CuAssertStrEquals(CuTest* tc, char* expected, char* actual); +void CuAssertIntEquals(CuTest* tc, int expected, int actual); +void CuAssertPtrEquals(CuTest* tc, void* expected, void* actual); +void CuAssertPtrNotNull(CuTest* tc, void* pointer); + +The project is open source and so you can add other more powerful +asserts to make your tests easier to write and more concise. + + +AUTOMATING TEST SUITE GENERATION + +make-tests.sh will grep through all the .c files in the current +directory and generate the code to run all the tests contained in +them. Using this script you don't have to worry about writing +AllTests.c or dealing with any of the other suite code. + + +CREDITS + +[02.23.2003] Dave Glowacki has added +(1) file name and line numbers to the error messages, (2) +AssertDblEquals for doubles, (3) Assert<X>Equals_Msg version of +all the Assert<X>Equals to pass in optional message which is +printed out on assert failure. diff --git a/test/CuTest.c b/test/CuTest.c new file mode 100644 index 0000000..315f59f --- /dev/null +++ b/test/CuTest.c @@ -0,0 +1,367 @@ +/* + * Copyright (c) 2003 Asim Jalis + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + *-------------------------------------------------------------------------* + * + * Originally obtained from "http://cutest.sourceforge.net/" version 1.4. + * + * Modified for serf as follows + * 1) added CuStringFree(), CuTestFree(), CuSuiteFree(), and + * CuSuiteFreeDeep() + * 0) reformatted the whitespace (doh!) + */ +#include <assert.h> +#include <setjmp.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <math.h> + +#include "CuTest.h" + +/*-------------------------------------------------------------------------* + * CuStr + *-------------------------------------------------------------------------*/ + +char* CuStrAlloc(int size) +{ + char* newStr = (char*) malloc( sizeof(char) * (size) ); + return newStr; +} + +char* CuStrCopy(const char* old) +{ + int len = strlen(old); + char* newStr = CuStrAlloc(len + 1); + strcpy(newStr, old); + return newStr; +} + +/*-------------------------------------------------------------------------* + * CuString + *-------------------------------------------------------------------------*/ + +void CuStringInit(CuString* str) +{ + str->length = 0; + str->size = STRING_MAX; + str->buffer = (char*) malloc(sizeof(char) * str->size); + str->buffer[0] = '\0'; +} + +CuString* CuStringNew(void) +{ + CuString* str = (CuString*) malloc(sizeof(CuString)); + str->length = 0; + str->size = STRING_MAX; + str->buffer = (char*) malloc(sizeof(char) * str->size); + str->buffer[0] = '\0'; + return str; +} + +void CuStringFree(CuString *str) +{ + free(str->buffer); + free(str); +} + +void CuStringResize(CuString* str, int newSize) +{ + str->buffer = (char*) realloc(str->buffer, sizeof(char) * newSize); + str->size = newSize; +} + +void CuStringAppend(CuString* str, const char* text) +{ + int length; + + if (text == NULL) { + text = "NULL"; + } + + length = strlen(text); + if (str->length + length + 1 >= str->size) + CuStringResize(str, str->length + length + 1 + STRING_INC); + str->length += length; + strcat(str->buffer, text); +} + +void CuStringAppendChar(CuString* str, char ch) +{ + char text[2]; + text[0] = ch; + text[1] = '\0'; + CuStringAppend(str, text); +} + +void CuStringAppendFormat(CuString* str, const char* format, ...) +{ + va_list argp; + char buf[HUGE_STRING_LEN]; + va_start(argp, format); + vsprintf(buf, format, argp); + va_end(argp); + CuStringAppend(str, buf); +} + +void CuStringInsert(CuString* str, const char* text, int pos) +{ + int length = strlen(text); + if (pos > str->length) + pos = str->length; + if (str->length + length + 1 >= str->size) + CuStringResize(str, str->length + length + 1 + STRING_INC); + memmove(str->buffer + pos + length, str->buffer + pos, (str->length - pos) + 1); + str->length += length; + memcpy(str->buffer + pos, text, length); +} + +/*-------------------------------------------------------------------------* + * CuTest + *-------------------------------------------------------------------------*/ + +void CuTestInit(CuTest* t, const char* name, TestFunction function) +{ + t->name = CuStrCopy(name); + t->failed = 0; + t->ran = 0; + t->message = NULL; + t->function = function; + t->jumpBuf = NULL; +} + +CuTest* CuTestNew(const char* name, TestFunction function) +{ + CuTest* tc = CU_ALLOC(CuTest); + CuTestInit(tc, name, function); + return tc; +} + +void CuTestFree(CuTest* tc) +{ + free(tc->name); + free(tc); +} + +void CuTestRun(CuTest* tc) +{ + jmp_buf buf; + tc->jumpBuf = &buf; + if (setjmp(buf) == 0) + { + tc->ran = 1; + (tc->function)(tc); + } + tc->jumpBuf = 0; +} + +static void CuFailInternal(CuTest* tc, const char* file, int line, CuString* string) +{ + char buf[HUGE_STRING_LEN]; + + sprintf(buf, "%s:%d: ", file, line); + CuStringInsert(string, buf, 0); + + tc->failed = 1; + tc->message = string->buffer; + if (tc->jumpBuf != 0) longjmp(*(tc->jumpBuf), 0); +} + +void CuFail_Line(CuTest* tc, const char* file, int line, const char* message2, const char* message) +{ + CuString string; + + CuStringInit(&string); + if (message2 != NULL) + { + CuStringAppend(&string, message2); + CuStringAppend(&string, ": "); + } + CuStringAppend(&string, message); + CuFailInternal(tc, file, line, &string); +} + +void CuAssert_Line(CuTest* tc, const char* file, int line, const char* message, int condition) +{ + if (condition) return; + CuFail_Line(tc, file, line, NULL, message); +} + +void CuAssertStrEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message, + const char* expected, const char* actual) +{ + CuString string; + if ((expected == NULL && actual == NULL) || + (expected != NULL && actual != NULL && + strcmp(expected, actual) == 0)) + { + return; + } + + CuStringInit(&string); + if (message != NULL) + { + CuStringAppend(&string, message); + CuStringAppend(&string, ": "); + } + CuStringAppend(&string, "expected <"); + CuStringAppend(&string, expected); + CuStringAppend(&string, "> but was <"); + CuStringAppend(&string, actual); + CuStringAppend(&string, ">"); + CuFailInternal(tc, file, line, &string); +} + +void CuAssertIntEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message, + int expected, int actual) +{ + char buf[STRING_MAX]; + if (expected == actual) return; + sprintf(buf, "expected <%d> but was <%d>", expected, actual); + CuFail_Line(tc, file, line, message, buf); +} + +void CuAssertDblEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message, + double expected, double actual, double delta) +{ + char buf[STRING_MAX]; + if (fabs(expected - actual) <= delta) return; + sprintf(buf, "expected <%lf> but was <%lf>", expected, actual); + CuFail_Line(tc, file, line, message, buf); +} + +void CuAssertPtrEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message, + void* expected, void* actual) +{ + char buf[STRING_MAX]; + if (expected == actual) return; + sprintf(buf, "expected pointer <0x%p> but was <0x%p>", expected, actual); + CuFail_Line(tc, file, line, message, buf); +} + + +/*-------------------------------------------------------------------------* + * CuSuite + *-------------------------------------------------------------------------*/ + +void CuSuiteInit(CuSuite* testSuite) +{ + testSuite->count = 0; + testSuite->failCount = 0; +} + +CuSuite* CuSuiteNew(void) +{ + CuSuite* testSuite = CU_ALLOC(CuSuite); + CuSuiteInit(testSuite); + return testSuite; +} + +void CuSuiteFree(CuSuite *testSuite) +{ + free(testSuite); +} + +void CuSuiteFreeDeep(CuSuite *testSuite) +{ + int i; + for (i = 0 ; i < testSuite->count ; ++i) + { + CuTest* testCase = testSuite->list[i]; + CuTestFree(testCase); + } + free(testSuite); +} + +void CuSuiteAdd(CuSuite* testSuite, CuTest *testCase) +{ + assert(testSuite->count < MAX_TEST_CASES); + testSuite->list[testSuite->count] = testCase; + testSuite->count++; +} + +void CuSuiteAddSuite(CuSuite* testSuite, CuSuite* testSuite2) +{ + int i; + for (i = 0 ; i < testSuite2->count ; ++i) + { + CuTest* testCase = testSuite2->list[i]; + CuSuiteAdd(testSuite, testCase); + } +} + +void CuSuiteRun(CuSuite* testSuite) +{ + int i; + for (i = 0 ; i < testSuite->count ; ++i) + { + CuTest* testCase = testSuite->list[i]; + CuTestRun(testCase); + if (testCase->failed) { testSuite->failCount += 1; } + } +} + +void CuSuiteSummary(CuSuite* testSuite, CuString* summary) +{ + int i; + for (i = 0 ; i < testSuite->count ; ++i) + { + CuTest* testCase = testSuite->list[i]; + CuStringAppend(summary, testCase->failed ? "F" : "."); + } + CuStringAppend(summary, "\n\n"); +} + +void CuSuiteDetails(CuSuite* testSuite, CuString* details) +{ + int i; + int failCount = 0; + + if (testSuite->failCount == 0) + { + int passCount = testSuite->count - testSuite->failCount; + const char* testWord = passCount == 1 ? "test" : "tests"; + CuStringAppendFormat(details, "OK (%d %s)\n", passCount, testWord); + } + else + { + if (testSuite->failCount == 1) + CuStringAppend(details, "There was 1 failure:\n"); + else + CuStringAppendFormat(details, "There were %d failures:\n", testSuite->failCount); + + for (i = 0 ; i < testSuite->count ; ++i) + { + CuTest* testCase = testSuite->list[i]; + if (testCase->failed) + { + failCount++; + CuStringAppendFormat(details, "%d) %s: %s\n", + failCount, testCase->name, testCase->message); + } + } + CuStringAppend(details, "\n!!!FAILURES!!!\n"); + + CuStringAppendFormat(details, "Runs: %d ", testSuite->count); + CuStringAppendFormat(details, "Passes: %d ", testSuite->count - testSuite->failCount); + CuStringAppendFormat(details, "Fails: %d\n", testSuite->failCount); + } +} diff --git a/test/CuTest.h b/test/CuTest.h new file mode 100644 index 0000000..1895bec --- /dev/null +++ b/test/CuTest.h @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2003 Asim Jalis + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + *-------------------------------------------------------------------------* + * + * Originally obtained from "http://cutest.sourceforge.net/" version 1.4. + * + * Modified for serf as follows + * 2) removed const from struct CuTest.name + * 1) added CuStringFree(), CuTestFree(), CuSuiteFree(), and + * CuSuiteFreeDeep() + * 0) reformatted the whitespace (doh!) + */ +#ifndef CU_TEST_H +#define CU_TEST_H + +#include <setjmp.h> +#include <stdarg.h> + +/* CuString */ + +char* CuStrAlloc(int size); +char* CuStrCopy(const char* old); + +#define CU_ALLOC(TYPE) ((TYPE*) malloc(sizeof(TYPE))) + +#define HUGE_STRING_LEN 8192 +#define STRING_MAX 256 +#define STRING_INC 256 + +typedef struct +{ + int length; + int size; + char* buffer; +} CuString; + +void CuStringInit(CuString* str); +CuString* CuStringNew(void); +void CuStringFree(CuString *str); +void CuStringRead(CuString* str, const char* path); +void CuStringAppend(CuString* str, const char* text); +void CuStringAppendChar(CuString* str, char ch); +void CuStringAppendFormat(CuString* str, const char* format, ...); +void CuStringInsert(CuString* str, const char* text, int pos); +void CuStringResize(CuString* str, int newSize); + +/* CuTest */ + +typedef struct CuTest CuTest; + +typedef void (*TestFunction)(CuTest *); + +struct CuTest +{ + char* name; + TestFunction function; + int failed; + int ran; + const char* message; + jmp_buf *jumpBuf; +}; + +void CuTestInit(CuTest* t, const char* name, TestFunction function); +CuTest* CuTestNew(const char* name, TestFunction function); +void CuTestFree(CuTest* tc); +void CuTestRun(CuTest* tc); + +/* Internal versions of assert functions -- use the public versions */ +void CuFail_Line(CuTest* tc, const char* file, int line, const char* message2, const char* message); +void CuAssert_Line(CuTest* tc, const char* file, int line, const char* message, int condition); +void CuAssertStrEquals_LineMsg(CuTest* tc, + const char* file, int line, const char* message, + const char* expected, const char* actual); +void CuAssertIntEquals_LineMsg(CuTest* tc, + const char* file, int line, const char* message, + int expected, int actual); +void CuAssertDblEquals_LineMsg(CuTest* tc, + const char* file, int line, const char* message, + double expected, double actual, double delta); +void CuAssertPtrEquals_LineMsg(CuTest* tc, + const char* file, int line, const char* message, + void* expected, void* actual); + +/* public assert functions */ + +#define CuFail(tc, ms) CuFail_Line( (tc), __FILE__, __LINE__, NULL, (ms)) +#define CuAssert(tc, ms, cond) CuAssert_Line((tc), __FILE__, __LINE__, (ms), (cond)) +#define CuAssertTrue(tc, cond) CuAssert_Line((tc), __FILE__, __LINE__, "assert failed", (cond)) + +#define CuAssertStrEquals(tc,ex,ac) CuAssertStrEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac)) +#define CuAssertStrEquals_Msg(tc,ms,ex,ac) CuAssertStrEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac)) +#define CuAssertIntEquals(tc,ex,ac) CuAssertIntEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac)) +#define CuAssertIntEquals_Msg(tc,ms,ex,ac) CuAssertIntEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac)) +#define CuAssertDblEquals(tc,ex,ac,dl) CuAssertDblEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac),(dl)) +#define CuAssertDblEquals_Msg(tc,ms,ex,ac,dl) CuAssertDblEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac),(dl)) +#define CuAssertPtrEquals(tc,ex,ac) CuAssertPtrEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac)) +#define CuAssertPtrEquals_Msg(tc,ms,ex,ac) CuAssertPtrEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac)) + +#define CuAssertPtrNotNull(tc,p) CuAssert_Line((tc),__FILE__,__LINE__,"null pointer unexpected",(p != NULL)) +#define CuAssertPtrNotNullMsg(tc,msg,p) CuAssert_Line((tc),__FILE__,__LINE__,(msg),(p != NULL)) + +/* CuSuite */ + +#define MAX_TEST_CASES 1024 + +#define SUITE_ADD_TEST(SUITE,TEST) CuSuiteAdd(SUITE, CuTestNew(#TEST, TEST)) + +typedef struct +{ + int count; + CuTest* list[MAX_TEST_CASES]; + int failCount; + +} CuSuite; + + +void CuSuiteInit(CuSuite* testSuite); +CuSuite* CuSuiteNew(void); +void CuSuiteFree(CuSuite *testSuite); +void CuSuiteFreeDeep(CuSuite *testSuite); +void CuSuiteAdd(CuSuite* testSuite, CuTest *testCase); +void CuSuiteAddSuite(CuSuite* testSuite, CuSuite* testSuite2); +void CuSuiteRun(CuSuite* testSuite); +void CuSuiteSummary(CuSuite* testSuite, CuString* summary); +void CuSuiteDetails(CuSuite* testSuite, CuString* details); + +#endif /* CU_TEST_H */ diff --git a/test/serf_bwtp.c b/test/serf_bwtp.c new file mode 100644 index 0000000..0e73a2d --- /dev/null +++ b/test/serf_bwtp.c @@ -0,0 +1,635 @@ +/* Copyright 2002-2004 Justin Erenkrantz and Greg Stein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> + +#include <apr.h> +#include <apr_uri.h> +#include <apr_strings.h> +#include <apr_atomic.h> +#include <apr_base64.h> +#include <apr_getopt.h> +#include <apr_version.h> + +#include "serf.h" + +typedef struct { + int count; + int using_ssl; + serf_ssl_context_t *ssl_ctx; + serf_bucket_alloc_t *bkt_alloc; +} app_baton_t; + +typedef struct { +#if APR_MAJOR_VERSION > 0 + apr_uint32_t requests_outstanding; +#else + apr_atomic_t requests_outstanding; +#endif + int print_headers; + + serf_response_acceptor_t acceptor; + app_baton_t *acceptor_baton; + + serf_response_handler_t handler; + + const char *host; + const char *method; + const char *path; + const char *req_body_path; + const char *authn; +} handler_baton_t; + +/* Kludges for APR 0.9 support. */ +#if APR_MAJOR_VERSION == 0 +#define apr_atomic_inc32 apr_atomic_inc +#define apr_atomic_dec32 apr_atomic_dec +#define apr_atomic_read32 apr_atomic_read +#endif + +static void closed_connection(serf_connection_t *conn, + void *closed_baton, + apr_status_t why, + apr_pool_t *pool) +{ + if (why) { + abort(); + } +} + +static apr_status_t ignore_all_cert_errors(void *data, int failures, + const serf_ssl_certificate_t *cert) +{ + /* In a real application, you would normally would not want to do this */ + return APR_SUCCESS; +} + +static apr_status_t conn_setup(apr_socket_t *skt, + serf_bucket_t **input_bkt, + serf_bucket_t **output_bkt, + void *setup_baton, + apr_pool_t *pool) +{ + serf_bucket_t *c; + app_baton_t *ctx = setup_baton; + + c = serf_bucket_socket_create(skt, ctx->bkt_alloc); + if (ctx->using_ssl) { + c = serf_bucket_ssl_decrypt_create(c, ctx->ssl_ctx, ctx->bkt_alloc); + if (!ctx->ssl_ctx) { + ctx->ssl_ctx = serf_bucket_ssl_decrypt_context_get(c); + } + serf_ssl_server_cert_callback_set(ctx->ssl_ctx, ignore_all_cert_errors, NULL); + + *output_bkt = serf_bucket_ssl_encrypt_create(*output_bkt, ctx->ssl_ctx, + ctx->bkt_alloc); + } + + *input_bkt = c; + + return APR_SUCCESS; +} + +static serf_bucket_t* accept_response(serf_request_t *request, + serf_bucket_t *stream, + void *acceptor_baton, + apr_pool_t *pool) +{ + serf_bucket_t *c; + serf_bucket_alloc_t *bkt_alloc; + + /* get the per-request bucket allocator */ + bkt_alloc = serf_request_get_alloc(request); + + /* Create a barrier so the response doesn't eat us! */ + c = serf_bucket_barrier_create(stream, bkt_alloc); + + return serf_bucket_response_create(c, bkt_alloc); +} + +static serf_bucket_t* accept_bwtp(serf_request_t *request, + serf_bucket_t *stream, + void *acceptor_baton, + apr_pool_t *pool) +{ + serf_bucket_t *c; + app_baton_t *app_ctx = acceptor_baton; + + /* Create a barrier so the response doesn't eat us! */ + c = serf_bucket_barrier_create(stream, app_ctx->bkt_alloc); + + return serf_bucket_bwtp_incoming_frame_create(c, app_ctx->bkt_alloc); +} + +/* fwd declare */ +static apr_status_t handle_bwtp_upgrade(serf_request_t *request, + serf_bucket_t *response, + void *handler_baton, + apr_pool_t *pool); + +static apr_status_t setup_request(serf_request_t *request, + void *setup_baton, + serf_bucket_t **req_bkt, + serf_response_acceptor_t *acceptor, + void **acceptor_baton, + serf_response_handler_t *handler, + void **handler_baton, + apr_pool_t *pool) +{ + handler_baton_t *ctx = setup_baton; + serf_bucket_t *hdrs_bkt; + serf_bucket_t *body_bkt; + + if (ctx->req_body_path) { + apr_file_t *file; + apr_status_t status; + + status = apr_file_open(&file, ctx->req_body_path, APR_READ, + APR_OS_DEFAULT, pool); + + if (status) { + printf("Error opening file (%s)\n", ctx->req_body_path); + return status; + } + + body_bkt = serf_bucket_file_create(file, + serf_request_get_alloc(request)); + } + else { + body_bkt = NULL; + } + + /* + *req_bkt = serf_bucket_bwtp_message_create(0, body_bkt, + serf_request_get_alloc(request)); + */ + + *req_bkt = serf_bucket_bwtp_header_create(0, "MESSAGE", + serf_request_get_alloc(request)); + + hdrs_bkt = serf_bucket_bwtp_frame_get_headers(*req_bkt); + + /* FIXME: Shouldn't we be able to figure out the host ourselves? */ + serf_bucket_headers_setn(hdrs_bkt, "Host", ctx->host); + serf_bucket_headers_setn(hdrs_bkt, "User-Agent", + "Serf/" SERF_VERSION_STRING); + /* Shouldn't serf do this for us? */ + serf_bucket_headers_setn(hdrs_bkt, "Accept-Encoding", "gzip"); + + if (ctx->authn != NULL) { + serf_bucket_headers_setn(hdrs_bkt, "Authorization", ctx->authn); + } + + *acceptor = ctx->acceptor; + *acceptor_baton = ctx->acceptor_baton; + *handler = ctx->handler; + *handler_baton = ctx; + + return APR_SUCCESS; +} + +static apr_status_t setup_bwtp_upgrade(serf_request_t *request, + void *setup_baton, + serf_bucket_t **req_bkt, + serf_response_acceptor_t *acceptor, + void **acceptor_baton, + serf_response_handler_t *handler, + void **handler_baton, + apr_pool_t *pool) +{ + serf_bucket_t *hdrs_bkt; + handler_baton_t *ctx = setup_baton; + + *req_bkt = serf_bucket_request_create("OPTIONS", "*", NULL, + serf_request_get_alloc(request)); + + hdrs_bkt = serf_bucket_request_get_headers(*req_bkt); + + serf_bucket_headers_setn(hdrs_bkt, "Upgrade", "BWTP/1.0"); + serf_bucket_headers_setn(hdrs_bkt, "Connection", "Upgrade"); + + *acceptor = ctx->acceptor; + *acceptor_baton = ctx->acceptor_baton; + *handler = handle_bwtp_upgrade; + *handler_baton = ctx; + + return APR_SUCCESS; +} + +static apr_status_t setup_channel(serf_request_t *request, + void *setup_baton, + serf_bucket_t **req_bkt, + serf_response_acceptor_t *acceptor, + void **acceptor_baton, + serf_response_handler_t *handler, + void **handler_baton, + apr_pool_t *pool) +{ + handler_baton_t *ctx = setup_baton; + serf_bucket_t *hdrs_bkt; + + *req_bkt = serf_bucket_bwtp_channel_open(0, ctx->path, + serf_request_get_alloc(request)); + + hdrs_bkt = serf_bucket_bwtp_frame_get_headers(*req_bkt); + + /* FIXME: Shouldn't we be able to figure out the host ourselves? */ + serf_bucket_headers_setn(hdrs_bkt, "Host", ctx->host); + serf_bucket_headers_setn(hdrs_bkt, "User-Agent", + "Serf/" SERF_VERSION_STRING); + /* Shouldn't serf do this for us? */ + serf_bucket_headers_setn(hdrs_bkt, "Accept-Encoding", "gzip"); + + if (ctx->authn != NULL) { + serf_bucket_headers_setn(hdrs_bkt, "Authorization", ctx->authn); + } + + *acceptor = ctx->acceptor; + *acceptor_baton = ctx->acceptor_baton; + *handler = ctx->handler; + *handler_baton = ctx; + + return APR_SUCCESS; +} + +static apr_status_t setup_close(serf_request_t *request, + void *setup_baton, + serf_bucket_t **req_bkt, + serf_response_acceptor_t *acceptor, + void **acceptor_baton, + serf_response_handler_t *handler, + void **handler_baton, + apr_pool_t *pool) +{ + handler_baton_t *ctx = setup_baton; + + *req_bkt = serf_bucket_bwtp_channel_close(0, serf_request_get_alloc(request)); + + *acceptor = ctx->acceptor; + *acceptor_baton = ctx->acceptor_baton; + *handler = ctx->handler; + *handler_baton = ctx; + + return APR_SUCCESS; +} + +static apr_status_t handle_bwtp(serf_request_t *request, + serf_bucket_t *response, + void *handler_baton, + apr_pool_t *pool) +{ + const char *data; + apr_size_t len; + apr_status_t status; + + if (!response) { + /* A NULL response can come back if the request failed completely */ + return APR_EGENERAL; + } + status = serf_bucket_bwtp_incoming_frame_wait_for_headers(response); + if (SERF_BUCKET_READ_ERROR(status) || APR_STATUS_IS_EAGAIN(status)) { + return status; + } + printf("BWTP %p frame: %d %d %s\n", + response, serf_bucket_bwtp_frame_get_channel(response), + serf_bucket_bwtp_frame_get_type(response), + serf_bucket_bwtp_frame_get_phrase(response)); + + + while (1) { + status = serf_bucket_read(response, 2048, &data, &len); + if (SERF_BUCKET_READ_ERROR(status)) + return status; + + /* got some data. print it out. */ + if (len) { + puts("BWTP body:\n---"); + fwrite(data, 1, len, stdout); + puts("\n---"); + } + + /* are we done yet? */ + if (APR_STATUS_IS_EOF(status)) { + return APR_EOF; + } + + /* have we drained the response so far? */ + if (APR_STATUS_IS_EAGAIN(status)) + return status; + + /* loop to read some more. */ + } + /* NOTREACHED */ +} + +static apr_status_t handle_bwtp_upgrade(serf_request_t *request, + serf_bucket_t *response, + void *handler_baton, + apr_pool_t *pool) +{ + const char *data; + apr_size_t len; + serf_status_line sl; + apr_status_t status; + handler_baton_t *ctx = handler_baton; + + if (!response) { + /* A NULL response can come back if the request failed completely */ + return APR_EGENERAL; + } + status = serf_bucket_response_status(response, &sl); + if (status) { + return status; + } + + while (1) { + status = serf_bucket_read(response, 2048, &data, &len); + if (SERF_BUCKET_READ_ERROR(status)) + return status; + + /* got some data. print it out. */ + fwrite(data, 1, len, stdout); + + /* are we done yet? */ + if (APR_STATUS_IS_EOF(status)) { + int i; + serf_connection_t *conn; + serf_request_t *new_req; + + conn = serf_request_get_conn(request); + + serf_connection_set_async_responses(conn, + accept_bwtp, ctx->acceptor_baton, handle_bwtp, NULL); + + new_req = serf_connection_request_create(conn, setup_channel, ctx); + for (i = 0; i < ctx->acceptor_baton->count; i++) { + new_req = serf_connection_request_create(conn, + setup_request, + ctx); + } + + return APR_EOF; + } + + /* have we drained the response so far? */ + if (APR_STATUS_IS_EAGAIN(status)) + return status; + + /* loop to read some more. */ + } + /* NOTREACHED */ +} + +static apr_status_t handle_response(serf_request_t *request, + serf_bucket_t *response, + void *handler_baton, + apr_pool_t *pool) +{ + const char *data; + apr_size_t len; + serf_status_line sl; + apr_status_t status; + handler_baton_t *ctx = handler_baton; + + if (!response) { + /* A NULL response can come back if the request failed completely */ + return APR_EGENERAL; + } + status = serf_bucket_response_status(response, &sl); + if (status) { + return status; + } + + while (1) { + status = serf_bucket_read(response, 2048, &data, &len); + if (SERF_BUCKET_READ_ERROR(status)) + return status; + + /* got some data. print it out. */ + fwrite(data, 1, len, stdout); + + /* are we done yet? */ + if (APR_STATUS_IS_EOF(status)) { + if (ctx->print_headers) { + serf_bucket_t *hdrs; + hdrs = serf_bucket_response_get_headers(response); + while (1) { + status = serf_bucket_read(hdrs, 2048, &data, &len); + if (SERF_BUCKET_READ_ERROR(status)) + return status; + + fwrite(data, 1, len, stdout); + if (APR_STATUS_IS_EOF(status)) { + break; + } + } + } + + apr_atomic_dec32(&ctx->requests_outstanding); + if (!ctx->requests_outstanding) { + serf_connection_t *conn; + serf_request_t *new_req; + + conn = serf_request_get_conn(request); + new_req = + serf_connection_request_create(conn, setup_close, ctx); + } + return APR_EOF; + } + + /* have we drained the response so far? */ + if (APR_STATUS_IS_EAGAIN(status)) + return status; + + /* loop to read some more. */ + } + /* NOTREACHED */ +} + +static void print_usage(apr_pool_t *pool) +{ + puts("serf_get [options] URL"); + puts("-h\tDisplay this help"); + puts("-v\tDisplay version"); + puts("-H\tPrint response headers"); + puts("-n <count> Fetch URL <count> times"); + puts("-a <user:password> Present Basic authentication credentials"); + puts("-m <method> Use the <method> HTTP Method"); + puts("-f <file> Use the <file> as the request body"); +} + +int main(int argc, const char **argv) +{ + apr_status_t status; + apr_pool_t *pool; + apr_sockaddr_t *address; + serf_context_t *context; + serf_connection_t *connection; + serf_request_t *request; + app_baton_t app_ctx; + handler_baton_t handler_ctx; + apr_uri_t url; + const char *raw_url, *method, *req_body_path = NULL; + int i; + int print_headers; + char *authn = NULL; + apr_getopt_t *opt; + char opt_c; + const char *opt_arg; + + apr_initialize(); + atexit(apr_terminate); + + apr_pool_create(&pool, NULL); + /* serf_initialize(); */ + + /* Default to one round of fetching. */ + app_ctx.count = 1; + /* Default to GET. */ + method = "GET"; + /* Do not print headers by default. */ + print_headers = 0; + + apr_getopt_init(&opt, pool, argc, argv); + + while ((status = apr_getopt(opt, "a:f:hHm:n:v", &opt_c, &opt_arg)) == + APR_SUCCESS) { + int srclen, enclen; + + switch (opt_c) { + case 'a': + srclen = strlen(opt_arg); + enclen = apr_base64_encode_len(srclen); + authn = apr_palloc(pool, enclen + 6); + strcpy(authn, "Basic "); + (void) apr_base64_encode(&authn[6], opt_arg, srclen); + break; + case 'f': + req_body_path = opt_arg; + break; + case 'h': + print_usage(pool); + exit(0); + break; + case 'H': + print_headers = 1; + break; + case 'm': + method = opt_arg; + break; + case 'n': + errno = 0; + app_ctx.count = apr_strtoi64(opt_arg, NULL, 10); + if (errno) { + printf("Problem converting number of times to fetch URL (%d)\n", + errno); + return errno; + } + break; + case 'v': + puts("Serf version: " SERF_VERSION_STRING); + exit(0); + default: + break; + } + } + + if (opt->ind != opt->argc - 1) { + print_usage(pool); + exit(-1); + } + + raw_url = argv[opt->ind]; + + apr_uri_parse(pool, raw_url, &url); + if (!url.port) { + url.port = apr_uri_port_of_scheme(url.scheme); + } + if (!url.path) { + url.path = "/"; + } + + if (strcasecmp(url.scheme, "https") == 0) { + app_ctx.using_ssl = 1; + } + else { + app_ctx.using_ssl = 0; + } + + status = apr_sockaddr_info_get(&address, + url.hostname, APR_UNSPEC, url.port, 0, + pool); + if (status) { + printf("Error creating address: %d\n", status); + apr_pool_destroy(pool); + exit(1); + } + + context = serf_context_create(pool); + + /* ### Connection or Context should have an allocator? */ + app_ctx.bkt_alloc = serf_bucket_allocator_create(pool, NULL, NULL); + app_ctx.ssl_ctx = NULL; + + connection = serf_connection_create(context, address, + conn_setup, &app_ctx, + closed_connection, &app_ctx, + pool); + + handler_ctx.requests_outstanding = 0; + handler_ctx.print_headers = print_headers; + + handler_ctx.host = url.hostinfo; + handler_ctx.method = method; + handler_ctx.path = url.path; + handler_ctx.authn = authn; + + handler_ctx.req_body_path = req_body_path; + + handler_ctx.acceptor = accept_response; + handler_ctx.acceptor_baton = &app_ctx; + handler_ctx.handler = handle_response; + + request = serf_connection_request_create(connection, setup_bwtp_upgrade, + &handler_ctx); + + for (i = 0; i < app_ctx.count; i++) { + apr_atomic_inc32(&handler_ctx.requests_outstanding); + } + + while (1) { + status = serf_context_run(context, SERF_DURATION_FOREVER, pool); + if (APR_STATUS_IS_TIMEUP(status)) + continue; + if (status) { + char buf[200]; + + printf("Error running context: (%d) %s\n", status, + apr_strerror(status, buf, sizeof(buf))); + apr_pool_destroy(pool); + exit(1); + } + if (!apr_atomic_read32(&handler_ctx.requests_outstanding)) { + break; + } + /* Debugging purposes only! */ + serf_debug__closed_conn(app_ctx.bkt_alloc); + } + + serf_connection_close(connection); + + apr_pool_destroy(pool); + return 0; +} diff --git a/test/serf_get.c b/test/serf_get.c new file mode 100644 index 0000000..ac1bb8e --- /dev/null +++ b/test/serf_get.c @@ -0,0 +1,503 @@ +/* Copyright 2002-2004 Justin Erenkrantz and Greg Stein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> + +#include <apr.h> +#include <apr_uri.h> +#include <apr_strings.h> +#include <apr_atomic.h> +#include <apr_base64.h> +#include <apr_getopt.h> +#include <apr_version.h> + +#include "serf.h" + +typedef struct { + const char *hostinfo; + int using_ssl; + serf_ssl_context_t *ssl_ctx; + serf_bucket_alloc_t *bkt_alloc; +} app_baton_t; + +static void closed_connection(serf_connection_t *conn, + void *closed_baton, + apr_status_t why, + apr_pool_t *pool) +{ + if (why) { + abort(); + } +} + +static void print_ssl_cert_errors(int failures) +{ + if (failures) { + fprintf(stderr, "INVALID CERTIFICATE:\n"); + if (failures & SERF_SSL_CERT_NOTYETVALID) + fprintf(stderr, "* The certificate is not yet valid.\n"); + if (failures & SERF_SSL_CERT_EXPIRED) + fprintf(stderr, "* The certificate expired.\n"); + if (failures & SERF_SSL_CERT_SELF_SIGNED) + fprintf(stderr, "* The certificate is self-signed.\n"); + if (failures & SERF_SSL_CERT_UNKNOWNCA) + fprintf(stderr, "* The CA is unknown.\n"); + if (failures & SERF_SSL_CERT_UNKNOWN_FAILURE) + fprintf(stderr, "* Unknown failure.\n"); + } +} + +static apr_status_t ignore_all_cert_errors(void *data, int failures, + const serf_ssl_certificate_t *cert) +{ + print_ssl_cert_errors(failures); + + /* In a real application, you would normally would not want to do this */ + return APR_SUCCESS; +} + +static apr_status_t print_certs(void *data, int failures, int error_depth, + const serf_ssl_certificate_t * const * certs, + apr_size_t certs_len) +{ + apr_pool_t *pool; + const serf_ssl_certificate_t *current; + + apr_pool_create(&pool, NULL); + + fprintf(stderr, "Received certificate chain with length %d\n", + (int)certs_len); + print_ssl_cert_errors(failures); + if (failures) + fprintf(stderr, "Error at depth=%d\n", error_depth); + else + fprintf(stderr, "Chain provided with depth=%d\n", error_depth); + + while ((current = *certs) != NULL) + { + fprintf(stderr, "\n-----BEGIN CERTIFICATE-----\n"); + fprintf(stderr, "%s\n", serf_ssl_cert_export(current, pool)); + fprintf(stderr, "-----END CERTIFICATE-----\n"); + ++certs; + } + + apr_pool_destroy(pool); + return APR_SUCCESS; +} + +static apr_status_t conn_setup(apr_socket_t *skt, + serf_bucket_t **input_bkt, + serf_bucket_t **output_bkt, + void *setup_baton, + apr_pool_t *pool) +{ + serf_bucket_t *c; + app_baton_t *ctx = setup_baton; + + c = serf_bucket_socket_create(skt, ctx->bkt_alloc); + if (ctx->using_ssl) { + c = serf_bucket_ssl_decrypt_create(c, ctx->ssl_ctx, ctx->bkt_alloc); + if (!ctx->ssl_ctx) { + ctx->ssl_ctx = serf_bucket_ssl_decrypt_context_get(c); + } + serf_ssl_server_cert_chain_callback_set(ctx->ssl_ctx, + ignore_all_cert_errors, + print_certs, NULL); + serf_ssl_set_hostname(ctx->ssl_ctx, ctx->hostinfo); + + *output_bkt = serf_bucket_ssl_encrypt_create(*output_bkt, ctx->ssl_ctx, + ctx->bkt_alloc); + } + + *input_bkt = c; + + return APR_SUCCESS; +} + +static serf_bucket_t* accept_response(serf_request_t *request, + serf_bucket_t *stream, + void *acceptor_baton, + apr_pool_t *pool) +{ + serf_bucket_t *c; + serf_bucket_alloc_t *bkt_alloc; + + /* get the per-request bucket allocator */ + bkt_alloc = serf_request_get_alloc(request); + + /* Create a barrier so the response doesn't eat us! */ + c = serf_bucket_barrier_create(stream, bkt_alloc); + + return serf_bucket_response_create(c, bkt_alloc); +} + +typedef struct { +#if APR_MAJOR_VERSION > 0 + apr_uint32_t completed_requests; +#else + apr_atomic_t completed_requests; +#endif + int print_headers; + + serf_response_acceptor_t acceptor; + app_baton_t *acceptor_baton; + + serf_response_handler_t handler; + + const char *host; + const char *method; + const char *path; + const char *req_body_path; + const char *authn; +} handler_baton_t; + +/* Kludges for APR 0.9 support. */ +#if APR_MAJOR_VERSION == 0 +#define apr_atomic_inc32 apr_atomic_inc +#define apr_atomic_dec32 apr_atomic_dec +#define apr_atomic_read32 apr_atomic_read +#endif + +static apr_status_t handle_response(serf_request_t *request, + serf_bucket_t *response, + void *handler_baton, + apr_pool_t *pool) +{ + const char *data; + apr_size_t len; + serf_status_line sl; + apr_status_t status; + handler_baton_t *ctx = handler_baton; + + if (!response) { + /* A NULL response can come back if the request failed completely */ + return APR_EGENERAL; + } + status = serf_bucket_response_status(response, &sl); + if (status) { + return status; + } + + while (1) { + status = serf_bucket_read(response, 2048, &data, &len); + if (SERF_BUCKET_READ_ERROR(status)) + return status; + + /* got some data. print it out. */ + fwrite(data, 1, len, stdout); + + /* are we done yet? */ + if (APR_STATUS_IS_EOF(status)) { + if (ctx->print_headers) { + serf_bucket_t *hdrs; + hdrs = serf_bucket_response_get_headers(response); + while (1) { + status = serf_bucket_read(hdrs, 2048, &data, &len); + if (SERF_BUCKET_READ_ERROR(status)) + return status; + + fwrite(data, 1, len, stdout); + if (APR_STATUS_IS_EOF(status)) { + break; + } + } + } + + apr_atomic_inc32(&ctx->completed_requests); + return APR_EOF; + } + + /* have we drained the response so far? */ + if (APR_STATUS_IS_EAGAIN(status)) + return status; + + /* loop to read some more. */ + } + /* NOTREACHED */ +} + +static apr_status_t setup_request(serf_request_t *request, + void *setup_baton, + serf_bucket_t **req_bkt, + serf_response_acceptor_t *acceptor, + void **acceptor_baton, + serf_response_handler_t *handler, + void **handler_baton, + apr_pool_t *pool) +{ + handler_baton_t *ctx = setup_baton; + serf_bucket_t *hdrs_bkt; + serf_bucket_t *body_bkt; + + if (ctx->req_body_path) { + apr_file_t *file; + apr_status_t status; + + status = apr_file_open(&file, ctx->req_body_path, APR_READ, + APR_OS_DEFAULT, pool); + + if (status) { + printf("Error opening file (%s)\n", ctx->req_body_path); + return status; + } + + body_bkt = serf_bucket_file_create(file, + serf_request_get_alloc(request)); + } + else { + body_bkt = NULL; + } + + *req_bkt = serf_request_bucket_request_create(request, ctx->method, + ctx->path, body_bkt, + serf_request_get_alloc(request)); + + hdrs_bkt = serf_bucket_request_get_headers(*req_bkt); + + serf_bucket_headers_setn(hdrs_bkt, "User-Agent", + "Serf/" SERF_VERSION_STRING); + /* Shouldn't serf do this for us? */ + serf_bucket_headers_setn(hdrs_bkt, "Accept-Encoding", "gzip"); + + if (ctx->authn != NULL) { + serf_bucket_headers_setn(hdrs_bkt, "Authorization", ctx->authn); + } + + *acceptor = ctx->acceptor; + *acceptor_baton = ctx->acceptor_baton; + *handler = ctx->handler; + *handler_baton = ctx; + + return APR_SUCCESS; +} + +static void print_usage(apr_pool_t *pool) +{ + puts("serf_get [options] URL"); + puts("-h\tDisplay this help"); + puts("-v\tDisplay version"); + puts("-H\tPrint response headers"); + puts("-n <count> Fetch URL <count> times"); + puts("-a <user:password> Present Basic authentication credentials"); + puts("-m <method> Use the <method> HTTP Method"); + puts("-f <file> Use the <file> as the request body"); + puts("-p <hostname:port> Use the <host:port> as proxy server"); +} + +int main(int argc, const char **argv) +{ + apr_status_t status; + apr_pool_t *pool; + serf_context_t *context; + serf_connection_t *connection; + serf_request_t *request; + app_baton_t app_ctx; + handler_baton_t handler_ctx; + apr_uri_t url; + const char *proxy = NULL; + const char *raw_url, *method, *req_body_path = NULL; + int count; + int i; + int print_headers; + char *authn = NULL; + apr_getopt_t *opt; + char opt_c; + const char *opt_arg; + + apr_initialize(); + atexit(apr_terminate); + + apr_pool_create(&pool, NULL); + /* serf_initialize(); */ + + /* Default to one round of fetching. */ + count = 1; + /* Default to GET. */ + method = "GET"; + /* Do not print headers by default. */ + print_headers = 0; + + apr_getopt_init(&opt, pool, argc, argv); + + while ((status = apr_getopt(opt, "a:f:hHm:n:vp:", &opt_c, &opt_arg)) == + APR_SUCCESS) { + int srclen, enclen; + + switch (opt_c) { + case 'a': + srclen = strlen(opt_arg); + enclen = apr_base64_encode_len(srclen); + authn = apr_palloc(pool, enclen + 6); + strcpy(authn, "Basic "); + (void) apr_base64_encode(&authn[6], opt_arg, srclen); + break; + case 'f': + req_body_path = opt_arg; + break; + case 'h': + print_usage(pool); + exit(0); + break; + case 'H': + print_headers = 1; + break; + case 'm': + method = opt_arg; + break; + case 'n': + errno = 0; + count = apr_strtoi64(opt_arg, NULL, 10); + if (errno) { + printf("Problem converting number of times to fetch URL (%d)\n", + errno); + return errno; + } + break; + case 'p': + proxy = opt_arg; + break; + case 'v': + puts("Serf version: " SERF_VERSION_STRING); + exit(0); + default: + break; + } + } + + if (opt->ind != opt->argc - 1) { + print_usage(pool); + exit(-1); + } + + raw_url = argv[opt->ind]; + + apr_uri_parse(pool, raw_url, &url); + if (!url.port) { + url.port = apr_uri_port_of_scheme(url.scheme); + } + if (!url.path) { + url.path = "/"; + } + + if (strcasecmp(url.scheme, "https") == 0) { + app_ctx.using_ssl = 1; + } + else { + app_ctx.using_ssl = 0; + } + + app_ctx.hostinfo = url.hostinfo; + + context = serf_context_create(pool); + + if (proxy) + { + apr_sockaddr_t *proxy_address = NULL; + apr_port_t proxy_port; + char *proxy_host; + char *proxy_scope; + + status = apr_parse_addr_port(&proxy_host, &proxy_scope, &proxy_port, proxy, pool); + if (status) + { + printf("Cannot parse proxy hostname/port: %d\n", status); + apr_pool_destroy(pool); + exit(1); + } + + if (!proxy_host) + { + printf("Proxy hostname must be specified\n"); + apr_pool_destroy(pool); + exit(1); + } + + if (!proxy_port) + { + printf("Proxy port must be specified\n"); + apr_pool_destroy(pool); + exit(1); + } + + status = apr_sockaddr_info_get(&proxy_address, proxy_host, APR_UNSPEC, + proxy_port, 0, pool); + + if (status) + { + printf("Cannot resolve proxy address '%s': %d\n", proxy_host, status); + apr_pool_destroy(pool); + exit(1); + } + + serf_config_proxy(context, proxy_address); + } + + /* ### Connection or Context should have an allocator? */ + app_ctx.bkt_alloc = serf_bucket_allocator_create(pool, NULL, NULL); + app_ctx.ssl_ctx = NULL; + + status = serf_connection_create2(&connection, context, url, + conn_setup, &app_ctx, + closed_connection, &app_ctx, + pool); + if (status) { + printf("Error creating connection: %d\n", status); + apr_pool_destroy(pool); + exit(1); + } + + handler_ctx.completed_requests = 0; + handler_ctx.print_headers = print_headers; + + handler_ctx.host = url.hostinfo; + handler_ctx.method = method; + handler_ctx.path = url.path; + handler_ctx.authn = authn; + + handler_ctx.req_body_path = req_body_path; + + handler_ctx.acceptor = accept_response; + handler_ctx.acceptor_baton = &app_ctx; + handler_ctx.handler = handle_response; + + for (i = 0; i < count; i++) { + request = serf_connection_request_create(connection, setup_request, + &handler_ctx); + } + + while (1) { + status = serf_context_run(context, SERF_DURATION_FOREVER, pool); + if (APR_STATUS_IS_TIMEUP(status)) + continue; + if (status) { + char buf[200]; + + printf("Error running context: (%d) %s\n", status, + apr_strerror(status, buf, sizeof(buf))); + apr_pool_destroy(pool); + exit(1); + } + if (apr_atomic_read32(&handler_ctx.completed_requests) >= count) { + break; + } + /* Debugging purposes only! */ + serf_debug__closed_conn(app_ctx.bkt_alloc); + } + + serf_connection_close(connection); + + apr_pool_destroy(pool); + return 0; +} diff --git a/test/serf_request.c b/test/serf_request.c new file mode 100644 index 0000000..0bd6a11 --- /dev/null +++ b/test/serf_request.c @@ -0,0 +1,79 @@ +/* Copyright 2002-2004 Justin Erenkrantz and Greg Stein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> + +#include "serf.h" + +static apr_status_t drain_bucket(serf_bucket_t *bucket) +{ + apr_status_t status; + const char *data; + apr_size_t len; + + while (1) { + status = serf_bucket_read(bucket, 2048, &data, &len); + if (SERF_BUCKET_READ_ERROR(status)) + return status; + + /* got some data. print it out. */ + fwrite(data, 1, len, stdout); + + /* are we done yet? */ + if (APR_STATUS_IS_EOF(status)) { + return APR_EOF; + } + + /* have we drained the response so far? */ + if (APR_STATUS_IS_EAGAIN(status)) + return APR_SUCCESS; + + /* loop to read some more. */ + } + /* NOTREACHED */ +} + +int main(int argc, const char **argv) +{ + apr_pool_t *pool; + serf_bucket_t *req_bkt; + serf_bucket_t *hdrs_bkt; + serf_bucket_alloc_t *allocator; + + apr_initialize(); + atexit(apr_terminate); + + apr_pool_create(&pool, NULL); + /* serf_initialize(); */ + + allocator = serf_bucket_allocator_create(pool, NULL, NULL); + + req_bkt = serf_bucket_request_create("GET", "/", NULL, allocator); + + hdrs_bkt = serf_bucket_request_get_headers(req_bkt); + + /* FIXME: Shouldn't we be able to figure out the host ourselves? */ + serf_bucket_headers_setn(hdrs_bkt, "Host", "localhost"); + serf_bucket_headers_setn(hdrs_bkt, "User-Agent", + "Serf/" SERF_VERSION_STRING); + + (void) drain_bucket(req_bkt); + + serf_bucket_destroy(req_bkt); + + apr_pool_destroy(pool); + + return 0; +} diff --git a/test/serf_response.c b/test/serf_response.c new file mode 100644 index 0000000..a8d7d85 --- /dev/null +++ b/test/serf_response.c @@ -0,0 +1,161 @@ +/* Copyright 2002-2004 Justin Erenkrantz and Greg Stein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> + +#include <apr.h> +#include <apr_uri.h> +#include <apr_strings.h> +#include <apr_atomic.h> +#include <apr_version.h> + +#include "serf.h" + +typedef struct { + const char *resp_file; + serf_bucket_t *bkt; +} accept_baton_t; + +static serf_bucket_t* accept_response(void *acceptor_baton, + serf_bucket_alloc_t *bkt_alloc, + apr_pool_t *pool) +{ + accept_baton_t *ctx = acceptor_baton; + serf_bucket_t *c; + apr_file_t *file; + apr_status_t status; + + status = apr_file_open(&file, ctx->resp_file, + APR_READ, APR_OS_DEFAULT, pool); + if (status) { + return NULL; + } + + c = ctx->bkt = serf_bucket_file_create(file, bkt_alloc); + + c = serf_bucket_barrier_create(c, bkt_alloc); + + return serf_bucket_response_create(c, bkt_alloc); +} + +typedef struct { +#if APR_MAJOR_VERSION > 0 + apr_uint32_t requests_outstanding; +#else + apr_atomic_t requests_outstanding; +#endif +} handler_baton_t; + +/* Kludges for APR 0.9 support. */ +#if APR_MAJOR_VERSION == 0 +#define apr_atomic_inc32 apr_atomic_inc +#define apr_atomic_dec32 apr_atomic_dec +#define apr_atomic_read32 apr_atomic_read +#endif + +static apr_status_t handle_response(serf_request_t *request, + serf_bucket_t *response, + void *handler_baton, + apr_pool_t *pool) +{ + const char *data, *s; + apr_size_t len; + serf_status_line sl; + apr_status_t status; + handler_baton_t *ctx = handler_baton; + + status = serf_bucket_response_status(response, &sl); + if (status) { + if (APR_STATUS_IS_EAGAIN(status)) { + return APR_SUCCESS; + } + abort(); + } + + status = serf_bucket_read(response, 2048, &data, &len); + + if (!status || APR_STATUS_IS_EOF(status)) { + if (len) { + s = apr_pstrmemdup(pool, data, len); + printf("%s", s); + } + } + else if (APR_STATUS_IS_EAGAIN(status)) { + status = APR_SUCCESS; + } + if (APR_STATUS_IS_EOF(status)) { + serf_bucket_t *hdrs; + const char *v; + + hdrs = serf_bucket_response_get_headers(response); + v = serf_bucket_headers_get(hdrs, "Trailer-Test"); + if (v) { + printf("Trailer-Test: %s\n", v); + } + + apr_atomic_dec32(&ctx->requests_outstanding); + } + + return status; +} + +int main(int argc, const char **argv) +{ + apr_status_t status; + apr_pool_t *pool; + serf_bucket_t *resp_bkt; + accept_baton_t accept_ctx; + handler_baton_t handler_ctx; + serf_bucket_alloc_t *allocator; + + if (argc != 2) { + printf("%s: [Resp. File]\n", argv[0]); + exit(-1); + } + accept_ctx.resp_file = argv[1]; + accept_ctx.bkt = NULL; + + apr_initialize(); + atexit(apr_terminate); + + apr_pool_create(&pool, NULL); + apr_atomic_init(pool); + /* serf_initialize(); */ + + allocator = serf_bucket_allocator_create(pool, NULL, NULL); + + handler_ctx.requests_outstanding = 0; + apr_atomic_inc32(&handler_ctx.requests_outstanding); + + resp_bkt = accept_response(&accept_ctx, allocator, pool); + while (1) { + status = handle_response(NULL, resp_bkt, &handler_ctx, pool); + if (APR_STATUS_IS_TIMEUP(status)) + continue; + if (SERF_BUCKET_READ_ERROR(status)) { + printf("Error running context: %d\n", status); + exit(1); + } + if (!apr_atomic_read32(&handler_ctx.requests_outstanding)) { + break; + } + } + serf_bucket_destroy(resp_bkt); + serf_bucket_destroy(accept_ctx.bkt); + + apr_pool_destroy(pool); + + return 0; +} diff --git a/test/serf_server.c b/test/serf_server.c new file mode 100644 index 0000000..4d8a0c6 --- /dev/null +++ b/test/serf_server.c @@ -0,0 +1,147 @@ +/* Copyright 2002-2004 Justin Erenkrantz and Greg Stein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> + +#include <apr.h> +#include <apr_uri.h> +#include <apr_strings.h> +#include <apr_atomic.h> +#include <apr_base64.h> +#include <apr_getopt.h> +#include <apr_version.h> + +#include "serf.h" + +typedef struct { + int foo; +} app_baton_t; + + +static apr_status_t incoming_request(serf_context_t *ctx, + serf_incoming_request_t *req, + void *request_baton, + apr_pool_t *pool) +{ + printf("INCOMING REQUEST\n"); + return APR_SUCCESS; +} + + +static apr_status_t accept_fn(serf_context_t *ctx, + serf_listener_t *l, + void *baton, + apr_socket_t *insock, + apr_pool_t *pool) +{ + serf_incoming_t *client = NULL; + printf("new connection from \n"); + return serf_incoming_create(&client, ctx, insock, baton, incoming_request, pool); +} + +static void print_usage(apr_pool_t *pool) +{ + puts("serf_server [options] listen_address:listen_port"); + puts("-h\tDisplay this help"); + puts("-v\tDisplay version"); +} + +int main(int argc, const char **argv) +{ + apr_status_t rv; + apr_pool_t *pool; + serf_context_t *context; + serf_listener_t *listener; + app_baton_t app_ctx; + const char *listen_spec; + char *addr = NULL; + char *scope_id = NULL; + apr_port_t port; + apr_getopt_t *opt; + char opt_c; + const char *opt_arg; + + apr_initialize(); + atexit(apr_terminate); + + apr_pool_create(&pool, NULL); + + apr_getopt_init(&opt, pool, argc, argv); + + while ((rv = apr_getopt(opt, "hv", &opt_c, &opt_arg)) == + APR_SUCCESS) { + switch (opt_c) { + case 'h': + print_usage(pool); + exit(0); + break; + case 'v': + puts("Serf version: " SERF_VERSION_STRING); + exit(0); + default: + break; + } + } + + if (opt->ind != opt->argc - 1) { + print_usage(pool); + exit(-1); + } + + listen_spec = argv[opt->ind]; + + + rv = apr_parse_addr_port(&addr, &scope_id, &port, listen_spec, pool); + if (rv) { + printf("Error parsing listen address: %d\n", rv); + exit(1); + } + + if (!addr) { + addr = "0.0.0.0"; + } + + if (port == 0) { + port = 8080; + } + + context = serf_context_create(pool); + + /* TODO.... stuff */ + app_ctx.foo = 1; + rv = serf_listener_create(&listener, context, addr, port, + &app_ctx, accept_fn, pool); + if (rv) { + printf("Error parsing listener: %d\n", rv); + exit(1); + } + + while (1) { + rv = serf_context_run(context, SERF_DURATION_FOREVER, pool); + if (APR_STATUS_IS_TIMEUP(rv)) + continue; + if (rv) { + char buf[200]; + + printf("Error running context: (%d) %s\n", rv, + apr_strerror(rv, buf, sizeof(buf))); + apr_pool_destroy(pool); + exit(1); + } + } + + apr_pool_destroy(pool); + return 0; +} diff --git a/test/serf_spider.c b/test/serf_spider.c new file mode 100644 index 0000000..2945e98 --- /dev/null +++ b/test/serf_spider.c @@ -0,0 +1,826 @@ +/* Copyright 2002-2004 Justin Erenkrantz and Greg Stein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> + +#include <apr.h> +#include <apr_uri.h> +#include <apr_strings.h> +#include <apr_atomic.h> +#include <apr_base64.h> +#include <apr_getopt.h> +#include <apr_xml.h> +#include <apr_thread_proc.h> +#include <apr_thread_mutex.h> +#include <apr_thread_cond.h> +#include <apr_version.h> + +#include "serf.h" +#include "serf_bucket_util.h" + +/*#define SERF_VERBOSE*/ + +#if !APR_HAS_THREADS +#error serf spider needs threads. +#endif + +/* This is a rough-sketch example of how a multi-threaded spider could be + * constructed using serf. + * + * A network thread will read in a URL and feed it into an expat parser. + * After the entire response is read, the XML structure and the path is + * passed to a set of parser threads. These threads will scan the document + * for HTML href's and queue up any links that it finds. + * + * It does try to stay on the same server as it only uses one connection. + * + * Because we feed the responses into an XML parser, the documents must be + * well-formed XHTML. + * + * There is no duplicate link detection. You've been warned. + */ + +/* The structure passed to the parser thread after we've read the entire + * response. + */ +typedef struct { + apr_xml_doc *doc; + char *path; + apr_pool_t *pool; +} doc_path_t; + +typedef struct { + const char *authn; + int using_ssl; + serf_ssl_context_t *ssl_ctx; + serf_bucket_alloc_t *bkt_alloc; +} app_baton_t; + +static void closed_connection(serf_connection_t *conn, + void *closed_baton, + apr_status_t why, + apr_pool_t *pool) +{ + if (why) { + abort(); + } +} + +static apr_status_t conn_setup(apr_socket_t *skt, + serf_bucket_t **input_bkt, + serf_bucket_t **output_bkt, + void *setup_baton, + apr_pool_t *pool) +{ + serf_bucket_t *c; + app_baton_t *ctx = setup_baton; + + c = serf_bucket_socket_create(skt, ctx->bkt_alloc); + if (ctx->using_ssl) { + c = serf_bucket_ssl_decrypt_create(c, ctx->ssl_ctx, ctx->bkt_alloc); + } + + *input_bkt = c; + + return APR_SUCCESS; +} + +static serf_bucket_t* accept_response(serf_request_t *request, + serf_bucket_t *stream, + void *acceptor_baton, + apr_pool_t *pool) +{ + serf_bucket_t *c; + serf_bucket_alloc_t *bkt_alloc; + + /* get the per-request bucket allocator */ + bkt_alloc = serf_request_get_alloc(request); + + /* Create a barrier so the response doesn't eat us! */ + c = serf_bucket_barrier_create(stream, bkt_alloc); + + return serf_bucket_response_create(c, bkt_alloc); +} + +typedef struct { + serf_bucket_alloc_t *allocator; +#if APR_MAJOR_VERSION > 0 + apr_uint32_t *requests_outstanding; +#else + apr_atomic_t *requests_outstanding; +#endif + serf_bucket_alloc_t *doc_queue_alloc; + apr_array_header_t *doc_queue; + apr_thread_cond_t *doc_queue_condvar; + + const char *hostinfo; + + /* includes: path, query, fragment. */ + char *full_path; + apr_size_t full_path_len; + + char *path; + apr_size_t path_len; + + char *query; + apr_size_t query_len; + + char *fragment; + apr_size_t fragment_len; + + apr_xml_parser *parser; + apr_pool_t *parser_pool; + + int hdr_read; + int is_html; + + serf_response_acceptor_t acceptor; + void *acceptor_baton; + serf_response_handler_t handler; + + app_baton_t *app_ctx; +} handler_baton_t; + +/* Kludges for APR 0.9 support. */ +#if APR_MAJOR_VERSION == 0 +#define apr_atomic_inc32 apr_atomic_inc +#define apr_atomic_dec32 apr_atomic_dec +#define apr_atomic_read32 apr_atomic_read +#define apr_atomic_set32 apr_atomic_set +#endif + +static apr_status_t handle_response(serf_request_t *request, + serf_bucket_t *response, + void *handler_baton, + apr_pool_t *pool) +{ + const char *data; + apr_size_t len; + serf_status_line sl; + apr_status_t status; + handler_baton_t *ctx = handler_baton; + + if (!response) { + /* Oh no! We've been cancelled! */ + abort(); + } + + status = serf_bucket_response_status(response, &sl); + if (status) { + if (APR_STATUS_IS_EAGAIN(status)) { + return APR_SUCCESS; + } + abort(); + } + + while (1) { + status = serf_bucket_read(response, 2048, &data, &len); + + if (SERF_BUCKET_READ_ERROR(status)) + return status; + + /*fwrite(data, 1, len, stdout);*/ + + if (!ctx->hdr_read) { + serf_bucket_t *hdrs; + const char *val; + + printf("Processing %s\n", ctx->path); + + hdrs = serf_bucket_response_get_headers(response); + val = serf_bucket_headers_get(hdrs, "Content-Type"); + /* FIXME: This check isn't quite right because Content-Type could + * be decorated; ideally strcasestr would be correct. + */ + if (val && strcasecmp(val, "text/html") == 0) { + ctx->is_html = 1; + apr_pool_create(&ctx->parser_pool, NULL); + ctx->parser = apr_xml_parser_create(ctx->parser_pool); + } + else { + ctx->is_html = 0; + } + ctx->hdr_read = 1; + } + if (ctx->is_html) { + apr_status_t xs; + + xs = apr_xml_parser_feed(ctx->parser, data, len); + /* Uh-oh. */ + if (xs) { +#ifdef SERF_VERBOSE + printf("XML parser error (feed): %d\n", xs); +#endif + ctx->is_html = 0; + } + } + + /* are we done yet? */ + if (APR_STATUS_IS_EOF(status)) { + + if (ctx->is_html) { + apr_xml_doc *xmld; + apr_status_t xs; + doc_path_t *dup; + + xs = apr_xml_parser_done(ctx->parser, &xmld); + if (xs) { +#ifdef SERF_VERBOSE + printf("XML parser error (done): %d\n", xs); +#endif + return xs; + } + dup = (doc_path_t*) + serf_bucket_mem_alloc(ctx->doc_queue_alloc, + sizeof(doc_path_t)); + dup->doc = xmld; + dup->path = (char*)serf_bucket_mem_alloc(ctx->doc_queue_alloc, + ctx->path_len); + memcpy(dup->path, ctx->path, ctx->path_len); + dup->pool = ctx->parser_pool; + + *(doc_path_t **)apr_array_push(ctx->doc_queue) = dup; + + apr_thread_cond_signal(ctx->doc_queue_condvar); + } + + apr_atomic_dec32(ctx->requests_outstanding); + serf_bucket_mem_free(ctx->allocator, ctx->path); + if (ctx->query) { + serf_bucket_mem_free(ctx->allocator, ctx->query); + serf_bucket_mem_free(ctx->allocator, ctx->full_path); + } + if (ctx->fragment) { + serf_bucket_mem_free(ctx->allocator, ctx->fragment); + } + serf_bucket_mem_free(ctx->allocator, ctx); + return APR_EOF; + } + + /* have we drained the response so far? */ + if (APR_STATUS_IS_EAGAIN(status)) + return APR_SUCCESS; + + /* loop to read some more. */ + } + /* NOTREACHED */ +} + +typedef struct { + apr_uint32_t *requests_outstanding; + serf_connection_t *connection; + apr_array_header_t *doc_queue; + serf_bucket_alloc_t *doc_queue_alloc; + + apr_thread_cond_t *condvar; + apr_thread_mutex_t *mutex; + + /* Master host: for now, we'll stick to one host. */ + const char *hostinfo; + + app_baton_t *app_ctx; +} parser_baton_t; + +static apr_status_t setup_request(serf_request_t *request, + void *setup_baton, + serf_bucket_t **req_bkt, + serf_response_acceptor_t *acceptor, + void **acceptor_baton, + serf_response_handler_t *handler, + void **handler_baton, + apr_pool_t *pool) +{ + handler_baton_t *ctx = setup_baton; + serf_bucket_t *hdrs_bkt; + + *req_bkt = serf_bucket_request_create("GET", ctx->full_path, NULL, + serf_request_get_alloc(request)); + + hdrs_bkt = serf_bucket_request_get_headers(*req_bkt); + + /* FIXME: Shouldn't we be able to figure out the host ourselves? */ + serf_bucket_headers_setn(hdrs_bkt, "Host", ctx->hostinfo); + serf_bucket_headers_setn(hdrs_bkt, "User-Agent", + "Serf/" SERF_VERSION_STRING); + + /* Shouldn't serf do this for us? */ + serf_bucket_headers_setn(hdrs_bkt, "Accept-Encoding", "gzip"); + + if (ctx->app_ctx->authn != NULL) { + serf_bucket_headers_setn(hdrs_bkt, "Authorization", + ctx->app_ctx->authn); + } + + if (ctx->app_ctx->using_ssl) { + serf_bucket_alloc_t *req_alloc; + + req_alloc = serf_request_get_alloc(request); + + if (ctx->app_ctx->ssl_ctx == NULL) { + *req_bkt = serf_bucket_ssl_encrypt_create(*req_bkt, NULL, + ctx->app_ctx->bkt_alloc); + ctx->app_ctx->ssl_ctx = + serf_bucket_ssl_encrypt_context_get(*req_bkt); + } + else { + *req_bkt = + serf_bucket_ssl_encrypt_create(*req_bkt, ctx->app_ctx->ssl_ctx, + ctx->app_ctx->bkt_alloc); + } + } + + +#ifdef SERF_VERBOSE + printf("Url requesting: %s\n", ctx->full_path); +#endif + + *acceptor = ctx->acceptor; + *acceptor_baton = ctx->acceptor_baton; + *handler = ctx->handler; + *handler_baton = ctx; + + return APR_SUCCESS; +} + +static apr_status_t create_request(const char *hostinfo, + const char *path, + const char *query, + const char *fragment, + parser_baton_t *ctx, + apr_pool_t *tmppool) +{ + handler_baton_t *new_ctx; + + if (hostinfo) { + /* Yes, this is a pointer comparison; not a string comparison. */ + if (hostinfo != ctx->hostinfo) { + /* Not on the same host; ignore */ + return APR_SUCCESS; + } + } + + new_ctx = (handler_baton_t*)serf_bucket_mem_alloc(ctx->app_ctx->bkt_alloc, + sizeof(handler_baton_t)); + new_ctx->allocator = ctx->app_ctx->bkt_alloc; + new_ctx->requests_outstanding = ctx->requests_outstanding; + new_ctx->app_ctx = ctx->app_ctx; + + /* See above: this example restricts ourselves to the same vhost. */ + new_ctx->hostinfo = ctx->hostinfo; + + /* we need to copy it so it falls under the request's scope. */ + new_ctx->path_len = strlen(path); + new_ctx->path = (char*)serf_bucket_mem_alloc(ctx->app_ctx->bkt_alloc, + new_ctx->path_len + 1); + memcpy(new_ctx->path, path, new_ctx->path_len + 1); + + /* we need to copy it so it falls under the request's scope. */ + if (query) { + new_ctx->query_len = strlen(query); + new_ctx->query = (char*)serf_bucket_mem_alloc(ctx->app_ctx->bkt_alloc, + new_ctx->query_len + 1); + memcpy(new_ctx->query, query, new_ctx->query_len + 1); + } + else { + new_ctx->query = NULL; + new_ctx->query_len = 0; + } + + /* we need to copy it so it falls under the request's scope. */ + if (fragment) { + new_ctx->fragment_len = strlen(fragment); + new_ctx->fragment = + (char*)serf_bucket_mem_alloc(ctx->app_ctx->bkt_alloc, + new_ctx->fragment_len + 1); + memcpy(new_ctx->fragment, fragment, new_ctx->fragment_len + 1); + } + else { + new_ctx->fragment = NULL; + new_ctx->fragment_len = 0; + } + + if (!new_ctx->query) { + new_ctx->full_path = new_ctx->path; + new_ctx->full_path_len = new_ctx->path_len; + } + else { + new_ctx->full_path_len = new_ctx->path_len + new_ctx->query_len; + new_ctx->full_path = + (char*)serf_bucket_mem_alloc(ctx->app_ctx->bkt_alloc, + new_ctx->full_path_len + 1); + memcpy(new_ctx->full_path, new_ctx->path, new_ctx->path_len); + memcpy(new_ctx->full_path + new_ctx->path_len, new_ctx->query, + new_ctx->query_len + 1); + } + + new_ctx->hdr_read = 0; + + new_ctx->doc_queue_condvar = ctx->condvar; + new_ctx->doc_queue = ctx->doc_queue; + new_ctx->doc_queue_alloc = ctx->doc_queue_alloc; + + new_ctx->acceptor = accept_response; + new_ctx->acceptor_baton = &ctx->app_ctx; + new_ctx->handler = handle_response; + + apr_atomic_inc32(ctx->requests_outstanding); + + serf_connection_request_create(ctx->connection, setup_request, new_ctx); + + return APR_SUCCESS; +} + +static apr_status_t put_req(const char *c, const char *orig_path, + parser_baton_t *ctx, apr_pool_t *pool) +{ + apr_status_t status; + apr_uri_t url; + + /* Build url */ +#ifdef SERF_VERBOSE + printf("Url discovered: %s\n", c); +#endif + + status = apr_uri_parse(pool, c, &url); + + /* We got something that was minimally useful. */ + if (status == 0 && url.path) { + const char *path, *query, *fragment; + + /* This is likely a relative URL. So, merge and hope for the + * best. + */ + if (!url.hostinfo && url.path[0] != '/') { + struct iovec vec[2]; + char *c; + apr_size_t nbytes; + + c = strrchr(orig_path, '/'); + + /* assert c */ + if (!c) { + return APR_EGENERAL; + } + + vec[0].iov_base = (char*)orig_path; + vec[0].iov_len = c - orig_path + 1; + + /* If the HTML is cute and gives us ./foo - skip the ./ */ + if (url.path[0] == '.' && url.path[1] == '/') { + vec[1].iov_base = url.path + 2; + vec[1].iov_len = strlen(url.path + 2); + } + else if (url.path[0] == '.' && url.path[1] == '.') { + /* FIXME We could be cute and consolidate the path; we're a + * toy example. So no. + */ + vec[1].iov_base = url.path; + vec[1].iov_len = strlen(url.path); + } + else { + vec[1].iov_base = url.path; + vec[1].iov_len = strlen(url.path); + } + + path = apr_pstrcatv(pool, vec, 2, &nbytes); + } + else { + path = url.path; + } + + query = url.query; + fragment = url.fragment; + + return create_request(url.hostinfo, path, query, fragment, ctx, pool); + } + + return APR_SUCCESS; +} + +static apr_status_t find_href(apr_xml_elem *e, const char *orig_path, + parser_baton_t *ctx, apr_pool_t *pool) +{ + apr_status_t status; + + do { + /* print */ + if (e->name[0] == 'a' && e->name[1] == '\0') { + apr_xml_attr *a; + + a = e->attr; + while (a) { + if (strcasecmp(a->name, "href") == 0) { + break; + } + a = a->next; + } + if (a) { + status = put_req(a->value, orig_path, ctx, pool); + if (status) { + return status; + } + } + } + + if (e->first_child) { + status = find_href(e->first_child, orig_path, ctx, pool); + if (status) { + return status; + } + } + + e = e->next; + } + while (e); + + return APR_SUCCESS; +} + +static apr_status_t find_href_doc(apr_xml_doc *doc, const char *path, + parser_baton_t *ctx, + apr_pool_t *pool) +{ + return find_href(doc->root, path, ctx, pool); +} + +static void * APR_THREAD_FUNC parser_thread(apr_thread_t *thread, void *data) +{ + apr_status_t status; + apr_pool_t *pool, *subpool; + parser_baton_t *ctx; + + ctx = (parser_baton_t*)data; + pool = apr_thread_pool_get(thread); + + apr_pool_create(&subpool, pool); + + while (1) { + doc_path_t *dup; + + apr_pool_clear(subpool); + + /* Grab it. */ + apr_thread_mutex_lock(ctx->mutex); + /* Sleep. */ + apr_thread_cond_wait(ctx->condvar, ctx->mutex); + + /* Fetch the doc off the list. */ + if (ctx->doc_queue->nelts) { + dup = *(doc_path_t**)(apr_array_pop(ctx->doc_queue)); + /* dup = (ctx->doc_queue->conns->elts)[0]; */ + } + else { + dup = NULL; + } + + /* Don't need the mutex now. */ + apr_thread_mutex_unlock(ctx->mutex); + + /* Parse the doc/url pair. */ + if (dup) { + status = find_href_doc(dup->doc, dup->path, ctx, subpool); + if (status) { + printf("Error finding hrefs: %d %s\n", status, dup->path); + } + /* Free the doc pair and its pool. */ + apr_pool_destroy(dup->pool); + serf_bucket_mem_free(ctx->doc_queue_alloc, dup->path); + serf_bucket_mem_free(ctx->doc_queue_alloc, dup); + } + + /* Hey are we done? */ + if (!apr_atomic_read32(ctx->requests_outstanding)) { + break; + } + } + return NULL; +} + +static void print_usage(apr_pool_t *pool) +{ + puts("serf_get [options] URL"); + puts("-h\tDisplay this help"); + puts("-v\tDisplay version"); + puts("-H\tPrint response headers"); + puts("-a <user:password> Present Basic authentication credentials"); +} + +int main(int argc, const char **argv) +{ + apr_status_t status; + apr_pool_t *pool; + apr_sockaddr_t *address; + serf_context_t *context; + serf_connection_t *connection; + app_baton_t app_ctx; + handler_baton_t *handler_ctx; + apr_uri_t url; + const char *raw_url, *method; + int count; + apr_getopt_t *opt; + char opt_c; + char *authn = NULL; + const char *opt_arg; + + /* For the parser threads */ + apr_thread_t *thread[3]; + apr_threadattr_t *tattr; + apr_status_t parser_status; + parser_baton_t *parser_ctx; + + apr_initialize(); + atexit(apr_terminate); + + apr_pool_create(&pool, NULL); + apr_atomic_init(pool); + /* serf_initialize(); */ + + /* Default to one round of fetching. */ + count = 1; + /* Default to GET. */ + method = "GET"; + + apr_getopt_init(&opt, pool, argc, argv); + + while ((status = apr_getopt(opt, "a:hv", &opt_c, &opt_arg)) == + APR_SUCCESS) { + int srclen, enclen; + + switch (opt_c) { + case 'a': + srclen = strlen(opt_arg); + enclen = apr_base64_encode_len(srclen); + authn = apr_palloc(pool, enclen + 6); + strcpy(authn, "Basic "); + (void) apr_base64_encode(&authn[6], opt_arg, srclen); + break; + case 'h': + print_usage(pool); + exit(0); + break; + case 'v': + puts("Serf version: " SERF_VERSION_STRING); + exit(0); + default: + break; + } + } + + if (opt->ind != opt->argc - 1) { + print_usage(pool); + exit(-1); + } + + raw_url = argv[opt->ind]; + + apr_uri_parse(pool, raw_url, &url); + if (!url.port) { + url.port = apr_uri_port_of_scheme(url.scheme); + } + if (!url.path) { + url.path = "/"; + } + + if (strcasecmp(url.scheme, "https") == 0) { + app_ctx.using_ssl = 1; + } + else { + app_ctx.using_ssl = 0; + } + + status = apr_sockaddr_info_get(&address, + url.hostname, APR_UNSPEC, url.port, 0, + pool); + if (status) { + printf("Error creating address: %d\n", status); + exit(1); + } + + context = serf_context_create(pool); + + /* ### Connection or Context should have an allocator? */ + app_ctx.bkt_alloc = serf_bucket_allocator_create(pool, NULL, NULL); + app_ctx.ssl_ctx = NULL; + app_ctx.authn = authn; + + connection = serf_connection_create(context, address, + conn_setup, &app_ctx, + closed_connection, &app_ctx, + pool); + + handler_ctx = (handler_baton_t*)serf_bucket_mem_alloc(app_ctx.bkt_alloc, + sizeof(handler_baton_t)); + handler_ctx->allocator = app_ctx.bkt_alloc; + handler_ctx->doc_queue = apr_array_make(pool, 1, sizeof(doc_path_t*)); + handler_ctx->doc_queue_alloc = app_ctx.bkt_alloc; + + handler_ctx->requests_outstanding = + (apr_uint32_t*)serf_bucket_mem_alloc(app_ctx.bkt_alloc, + sizeof(apr_uint32_t)); + apr_atomic_set32(handler_ctx->requests_outstanding, 0); + handler_ctx->hdr_read = 0; + + parser_ctx = (void*)serf_bucket_mem_alloc(app_ctx.bkt_alloc, + sizeof(parser_baton_t)); + + parser_ctx->requests_outstanding = handler_ctx->requests_outstanding; + parser_ctx->connection = connection; + parser_ctx->app_ctx = &app_ctx; + parser_ctx->doc_queue = handler_ctx->doc_queue; + parser_ctx->doc_queue_alloc = handler_ctx->doc_queue_alloc; + /* Restrict ourselves to this host. */ + parser_ctx->hostinfo = url.hostinfo; + + status = apr_thread_mutex_create(&parser_ctx->mutex, + APR_THREAD_MUTEX_DEFAULT, pool); + if (status) { + printf("Couldn't create mutex %d\n", status); + return status; + } + + status = apr_thread_cond_create(&parser_ctx->condvar, pool); + if (status) { + printf("Couldn't create condvar: %d\n", status); + return status; + } + + /* Let the handler now which condvar to use. */ + handler_ctx->doc_queue_condvar = parser_ctx->condvar; + + apr_threadattr_create(&tattr, pool); + + /* Start the parser thread. */ + apr_thread_create(&thread[0], tattr, parser_thread, parser_ctx, pool); + + /* Deliver the first request. */ + create_request(url.hostinfo, url.path, NULL, NULL, parser_ctx, pool); + + /* Go run our normal thread. */ + while (1) { + int tries = 0; + + status = serf_context_run(context, SERF_DURATION_FOREVER, pool); + if (APR_STATUS_IS_TIMEUP(status)) + continue; + if (status) { + char buf[200]; + + printf("Error running context: (%d) %s\n", status, + apr_strerror(status, buf, sizeof(buf))); + exit(1); + } + + /* We run this check to allow our parser threads to add more + * requests to our queue. + */ + for (tries = 0; tries < 3; tries++) { + if (!apr_atomic_read32(handler_ctx->requests_outstanding)) { +#ifdef SERF_VERBOSE + printf("Waiting..."); +#endif + apr_sleep(100000); +#ifdef SERF_VERBOSE + printf("Done\n"); +#endif + } + else { + break; + } + } + if (tries >= 3) { + break; + } + /* Debugging purposes only! */ + serf_debug__closed_conn(app_ctx.bkt_alloc); + } + + printf("Quitting...\n"); + serf_connection_close(connection); + + /* wake up the parser via condvar signal */ + apr_thread_cond_signal(parser_ctx->condvar); + + status = apr_thread_join(&parser_status, thread[0]); + if (status) { + printf("Error joining thread: %d\n", status); + return status; + } + + serf_bucket_mem_free(app_ctx.bkt_alloc, handler_ctx->requests_outstanding); + serf_bucket_mem_free(app_ctx.bkt_alloc, parser_ctx); + + apr_pool_destroy(pool); + return 0; +} diff --git a/test/serftestca.pem b/test/serftestca.pem new file mode 100644 index 0000000..15aa004 --- /dev/null +++ b/test/serftestca.pem @@ -0,0 +1,66 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + c2:31:db:41:c9:7b:a9:46 + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=BE, ST=Antwerp, O=In Serf we trust, Inc., OU=Test Suite, CN=Serf/emailAddress=serf@example.com, L=Mechelen + Validity + Not Before: Mar 21 13:18:17 2008 GMT + Not After : Mar 21 13:18:17 2011 GMT + Subject: C=BE, ST=Antwerp, O=In Serf we trust, Inc., OU=Test Suite, CN=Serf/emailAddress=serf@example.com, L=Mechelen + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:a1:21:58:60:ea:79:ae:9f:0f:f4:69:b5:af:55: + 9a:8b:da:1d:74:80:88:44:42:46:64:59:98:3e:84: + e1:70:f7:18:e1:7c:8d:cc:42:27:cd:e6:31:47:51: + 66:3d:58:1c:f9:54:26:4f:12:b7:0e:46:a7:27:c1: + ca:ac:a7:38:0f:a1:00:fb:a8:20:77:37:14:6a:7b: + 65:34:1c:eb:30:fa:0b:9e:57:2c:7a:04:13:50:d4: + e2:7c:66:5f:97:45:75:78:47:f5:9e:68:9d:40:b9: + 94:d3:78:50:c8:19:10:50:52:fd:2f:b8:a1:75:74: + ad:73:95:46:9a:8e:95:b2:3d + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 15:BB:2D:8C:63:10:0D:31:01:D1:C6:26:01:27:43:A5:F4:57:6E:ED + X509v3 Authority Key Identifier: + keyid:15:BB:2D:8C:63:10:0D:31:01:D1:C6:26:01:27:43:A5:F4:57:6E:ED + DirName:/C=BE/ST=Antwerp/O=In Serf we trust, Inc./OU=Test Suite/CN=Serf/emailAddress=serf@example.com/L=Mechelen + serial:C2:31:DB:41:C9:7B:A9:46 + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 59:9c:b0:62:cc:a4:c0:98:68:4b:52:bf:fa:84:ee:b5:65:d5: + a7:51:39:77:a0:be:d6:14:b0:7a:64:2f:0d:ee:49:e8:b6:6a: + c7:1d:5f:bc:27:4c:25:4b:25:b7:69:5c:07:86:54:69:22:99: + d9:1a:5d:dd:38:c9:00:b4:29:89:7d:ce:df:b5:3f:57:05:ee: + 5b:0e:a4:f0:bc:7a:4f:1b:ba:84:85:e8:0f:e3:6b:fa:6f:cf: + 3f:23:7c:d0:dd:c8:95:91:46:8a:05:84:84:46:cf:e3:c8:fc: + 9c:94:c2:dd:15:d4:6e:d1:31:0b:d9:7b:ce:1e:13:72:c9:2e: + a9:86 +-----BEGIN CERTIFICATE----- +MIIDsjCCAxugAwIBAgIJAMIx20HJe6lGMA0GCSqGSIb3DQEBBQUAMIGYMQswCQYD +VQQGEwJCRTEQMA4GA1UECBMHQW50d2VycDEfMB0GA1UEChMWSW4gU2VyZiB3ZSB0 +cnVzdCwgSW5jLjETMBEGA1UECxMKVGVzdCBTdWl0ZTENMAsGA1UEAxMEU2VyZjEf +MB0GCSqGSIb3DQEJARYQc2VyZkBleGFtcGxlLmNvbTERMA8GA1UEBxMITWVjaGVs +ZW4wHhcNMDgwMzIxMTMxODE3WhcNMTEwMzIxMTMxODE3WjCBmDELMAkGA1UEBhMC +QkUxEDAOBgNVBAgTB0FudHdlcnAxHzAdBgNVBAoTFkluIFNlcmYgd2UgdHJ1c3Qs +IEluYy4xEzARBgNVBAsTClRlc3QgU3VpdGUxDTALBgNVBAMTBFNlcmYxHzAdBgkq +hkiG9w0BCQEWEHNlcmZAZXhhbXBsZS5jb20xETAPBgNVBAcTCE1lY2hlbGVuMIGf +MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQChIVhg6nmunw/0abWvVZqL2h10gIhE +QkZkWZg+hOFw9xjhfI3MQifN5jFHUWY9WBz5VCZPErcORqcnwcqspzgPoQD7qCB3 +NxRqe2U0HOsw+gueVyx6BBNQ1OJ8Zl+XRXV4R/WeaJ1AuZTTeFDIGRBQUv0vuKF1 +dK1zlUaajpWyPQIDAQABo4IBADCB/TAdBgNVHQ4EFgQUFbstjGMQDTEB0cYmASdD +pfRXbu0wgc0GA1UdIwSBxTCBwoAUFbstjGMQDTEB0cYmASdDpfRXbu2hgZ6kgZsw +gZgxCzAJBgNVBAYTAkJFMRAwDgYDVQQIEwdBbnR3ZXJwMR8wHQYDVQQKExZJbiBT +ZXJmIHdlIHRydXN0LCBJbmMuMRMwEQYDVQQLEwpUZXN0IFN1aXRlMQ0wCwYDVQQD +EwRTZXJmMR8wHQYJKoZIhvcNAQkBFhBzZXJmQGV4YW1wbGUuY29tMREwDwYDVQQH +EwhNZWNoZWxlboIJAMIx20HJe6lGMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF +BQADgYEAWZywYsykwJhoS1K/+oTutWXVp1E5d6C+1hSwemQvDe5J6LZqxx1fvCdM +JUslt2lcB4ZUaSKZ2Rpd3TjJALQpiX3O37U/VwXuWw6k8Lx6Txu6hIXoD+Nr+m/P +PyN80N3IlZFGigWEhEbP48j8nJTC3RXUbtExC9l7zh4TcskuqYY= +-----END CERTIFICATE----- diff --git a/test/server/test_server.c b/test/server/test_server.c new file mode 100644 index 0000000..1ad2c9e --- /dev/null +++ b/test/server/test_server.c @@ -0,0 +1,359 @@ +/* Copyright 2011 Justin Erenkrantz and Greg Stein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "apr.h" +#include "apr_pools.h" +#include <apr_poll.h> +#include <apr_version.h> +#include <stdlib.h> + +#include "serf.h" + +#include "test_server.h" + +struct serv_ctx_t { + /* Pool for resource allocation. */ + apr_pool_t *pool; + + apr_int32_t options; + + /* Array of actions which server will replay when client connected. */ + test_server_action_t *action_list; + /* Size of action_list array. */ + apr_size_t action_count; + /* Index of current action. */ + apr_size_t cur_action; + + /* Array of messages the server will receive from the client. */ + test_server_message_t *message_list; + /* Size of message_list array. */ + apr_size_t message_count; + /* Index of current message. */ + apr_size_t cur_message; + + /* Number of messages received that the server didn't respond to yet. */ + apr_size_t outstanding_responses; + + /* Position in message buffer (incoming messages being read). */ + apr_size_t message_buf_pos; + + /* Position in action buffer. (outgoing messages being sent). */ + apr_size_t action_buf_pos; + + /* Address for server binding. */ + apr_sockaddr_t *serv_addr; + apr_socket_t *serv_sock; + + /* Accepted client socket. NULL if there is no client socket. */ + apr_socket_t *client_sock; + +}; + +/* Replay support functions */ +static void next_message(serv_ctx_t *servctx) +{ + servctx->cur_message++; +} + +static void next_action(serv_ctx_t *servctx) +{ + servctx->cur_action++; + servctx->action_buf_pos = 0; +} + +/* Verify received requests and take the necessary actions + (return a response, kill the connection ...) */ +static apr_status_t replay(serv_ctx_t *servctx, + apr_int16_t rtnevents, + apr_pool_t *pool) +{ + apr_status_t status = APR_SUCCESS; + test_server_action_t *action; + + if (rtnevents & APR_POLLIN) { + if (servctx->message_list == NULL) { + /* we're not expecting any requests to reach this server! */ + printf("Received request where none was expected\n"); + + return APR_EGENERAL; + } + + if (servctx->cur_action >= servctx->action_count) { + char buf[128]; + apr_size_t len = sizeof(buf); + + status = apr_socket_recv(servctx->client_sock, buf, &len); + if (! APR_STATUS_IS_EAGAIN(status)) { + /* we're out of actions! */ + printf("Received more requests than expected.\n"); + + return APR_EGENERAL; + } + return status; + } + + action = &servctx->action_list[servctx->cur_action]; + + if (action->kind == SERVER_IGNORE_AND_KILL_CONNECTION) { + char buf[128]; + apr_size_t len = sizeof(buf); + + status = apr_socket_recv(servctx->client_sock, buf, &len); + + if (status == APR_EOF) { + apr_socket_close(servctx->client_sock); + servctx->client_sock = NULL; + next_action(servctx); + return APR_SUCCESS; + } + + return status; + } + else if (action->kind == SERVER_RECV || + (action->kind == SERVER_RESPOND && + servctx->outstanding_responses == 0)) { + apr_size_t msg_len, len; + char buf[128]; + test_server_message_t *message; + + message = &servctx->message_list[servctx->cur_message]; + msg_len = strlen(message->text); + + len = msg_len - servctx->message_buf_pos; + if (len > sizeof(buf)) + len = sizeof(buf); + + status = apr_socket_recv(servctx->client_sock, buf, &len); + if (status != APR_SUCCESS) + return status; + + if (servctx->options & TEST_SERVER_DUMP) + fwrite(buf, len, 1, stdout); + + if (strncmp(buf, message->text + servctx->message_buf_pos, len) != 0) { + /* ## TODO: Better diagnostics. */ + printf("Expected: (\n"); + fwrite(message->text + servctx->message_buf_pos, len, 1, stdout); + printf(")\n"); + printf("Actual: (\n"); + fwrite(buf, len, 1, stdout); + printf(")\n"); + + return APR_EGENERAL; + } + + servctx->message_buf_pos += len; + + if (servctx->message_buf_pos >= msg_len) { + next_message(servctx); + servctx->message_buf_pos -= msg_len; + if (action->kind == SERVER_RESPOND) + servctx->outstanding_responses++; + if (action->kind == SERVER_RECV) + next_action(servctx); + } + } + } + if (rtnevents & APR_POLLOUT) { + action = &servctx->action_list[servctx->cur_action]; + + if (action->kind == SERVER_RESPOND && servctx->outstanding_responses) { + apr_size_t msg_len; + apr_size_t len; + + msg_len = strlen(action->text); + len = msg_len - servctx->action_buf_pos; + + status = apr_socket_send(servctx->client_sock, + action->text + servctx->action_buf_pos, &len); + if (status != APR_SUCCESS) + return status; + + if (servctx->options & TEST_SERVER_DUMP) + fwrite(action->text + servctx->action_buf_pos, len, 1, stdout); + + servctx->action_buf_pos += len; + + if (servctx->action_buf_pos >= msg_len) { + next_action(servctx); + servctx->outstanding_responses--; + } + } + else if (action->kind == SERVER_KILL_CONNECTION || + action->kind == SERVER_IGNORE_AND_KILL_CONNECTION) { + apr_socket_close(servctx->client_sock); + servctx->client_sock = NULL; + next_action(servctx); + } + } + else if (rtnevents & APR_POLLIN) { + /* ignore */ + } + else { + printf("Unknown rtnevents: %d\n", rtnevents); + abort(); + } + + return status; +} + +apr_status_t test_server_run(serv_ctx_t *servctx, + apr_short_interval_time_t duration, + apr_pool_t *pool) +{ + apr_status_t status; + apr_pollset_t *pollset; + apr_int32_t num; + const apr_pollfd_t *desc; + + /* create a new pollset */ + status = apr_pollset_create(&pollset, 32, pool, 0); + if (status != APR_SUCCESS) + return status; + + /* Don't accept new connection while processing client connection. At + least for present time.*/ + if (servctx->client_sock) { + apr_pollfd_t pfd = { pool, APR_POLL_SOCKET, APR_POLLIN | APR_POLLOUT, 0, + { NULL }, NULL }; + pfd.desc.s = servctx->client_sock; + status = apr_pollset_add(pollset, &pfd); + if (status != APR_SUCCESS) + goto cleanup; + } + else { + apr_pollfd_t pfd = { pool, APR_POLL_SOCKET, APR_POLLIN, 0, + { NULL }, NULL }; + pfd.desc.s = servctx->serv_sock; + status = apr_pollset_add(pollset, &pfd); + if (status != APR_SUCCESS) + goto cleanup; + } + + status = apr_pollset_poll(pollset, APR_USEC_PER_SEC >> 1, &num, &desc); + if (status != APR_SUCCESS) + goto cleanup; + + while (num--) { + if (desc->desc.s == servctx->serv_sock) { + status = apr_socket_accept(&servctx->client_sock, servctx->serv_sock, + servctx->pool); + if (status != APR_SUCCESS) + goto cleanup; + + apr_socket_opt_set(servctx->client_sock, APR_SO_NONBLOCK, 1); + apr_socket_timeout_set(servctx->client_sock, 0); + + status = APR_SUCCESS; + goto cleanup; + } + + if (desc->desc.s == servctx->client_sock) { + /* Replay data to socket. */ + status = replay(servctx, desc->rtnevents, pool); + + if (APR_STATUS_IS_EOF(status)) { + apr_socket_close(servctx->client_sock); + servctx->client_sock = NULL; + } + else if (APR_STATUS_IS_EAGAIN(status)) { + status = APR_SUCCESS; + } + else if (status != APR_SUCCESS) { + /* Real error. */ + goto cleanup; + } + } + + desc++; + } + +cleanup: + apr_pollset_destroy(pollset); + + return status; +} + +/* Start a TCP server on port SERV_PORT in thread THREAD. srv_replay is a array + of action to replay when connection started. replay_count is count of + actions in srv_replay. */ +apr_status_t test_start_server(serv_ctx_t **servctx_p, + apr_sockaddr_t *address, + test_server_message_t *message_list, + apr_size_t message_count, + test_server_action_t *action_list, + apr_size_t action_count, + apr_int32_t options, + apr_pool_t *pool) +{ + apr_status_t status; + apr_socket_t *serv_sock; + serv_ctx_t *servctx; + + servctx = apr_pcalloc(pool, sizeof(*servctx)); + *servctx_p = servctx; + + servctx->serv_addr = address; + servctx->options = options; + servctx->pool = pool; + servctx->message_list = message_list; + servctx->message_count = message_count; + servctx->action_list = action_list; + servctx->action_count = action_count; + + /* create server socket */ +#if APR_VERSION_AT_LEAST(1, 0, 0) + status = apr_socket_create(&serv_sock, address->family, SOCK_STREAM, 0, + pool); +#else + status = apr_socket_create(&serv_sock, address->family, SOCK_STREAM, pool); +#endif + + if (status != APR_SUCCESS) + return status; + + apr_socket_opt_set(serv_sock, APR_SO_NONBLOCK, 1); + apr_socket_timeout_set(serv_sock, 0); + apr_socket_opt_set(serv_sock, APR_SO_REUSEADDR, 1); + + status = apr_socket_bind(serv_sock, servctx->serv_addr); + if (status != APR_SUCCESS) + return status; + + /* Start replay from first action. */ + servctx->cur_action = 0; + servctx->action_buf_pos = 0; + servctx->outstanding_responses = 0; + + /* listen for clients */ + apr_socket_listen(serv_sock, SOMAXCONN); + if (status != APR_SUCCESS) + return status; + + servctx->serv_sock = serv_sock; + servctx->client_sock = NULL; + return APR_SUCCESS; +} + +apr_status_t test_server_destroy(serv_ctx_t *servctx, apr_pool_t *pool) +{ + apr_socket_close(servctx->serv_sock); + + if (servctx->client_sock) { + apr_socket_close(servctx->client_sock); + } + + return APR_SUCCESS; +} diff --git a/test/server/test_server.h b/test/server/test_server.h new file mode 100644 index 0000000..9e8b0e2 --- /dev/null +++ b/test/server/test_server.h @@ -0,0 +1,70 @@ +/* Copyright 2011 Justin Erenkrantz and Greg Stein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TEST_SERVER_H +#define TEST_SERVER_H + +typedef struct serv_ctx_t serv_ctx_t; + +#define TEST_SERVER_DUMP 1 + +/* Default port for our test server. */ +#define SERV_PORT 12345 +#define SERV_PORT_STR "12345" + +#define PROXY_PORT 23456 + +typedef struct +{ + enum { + SERVER_RECV, + SERVER_SEND, + SERVER_RESPOND, + SERVER_IGNORE_AND_KILL_CONNECTION, + SERVER_KILL_CONNECTION + } kind; + + const char *text; +} test_server_action_t; + +typedef struct +{ + const char *text; +} test_server_message_t; + +apr_status_t test_start_server(serv_ctx_t **servctx_p, + apr_sockaddr_t *address, + test_server_message_t *message_list, + apr_size_t message_count, + test_server_action_t *action_list, + apr_size_t action_count, + apr_int32_t options, + apr_pool_t *pool); + +apr_status_t test_server_run(serv_ctx_t *servctx, + apr_short_interval_time_t duration, + apr_pool_t *pool); + +apr_status_t test_server_destroy(serv_ctx_t *servctx, apr_pool_t *pool); + +#ifndef APR_VERSION_AT_LEAST /* Introduced in APR 1.3.0 */ +#define APR_VERSION_AT_LEAST(major,minor,patch) \ +(((major) < APR_MAJOR_VERSION) \ + || ((major) == APR_MAJOR_VERSION && (minor) < APR_MINOR_VERSION) \ + || ((major) == APR_MAJOR_VERSION && (minor) == APR_MINOR_VERSION && \ + (patch) <= APR_PATCH_VERSION)) +#endif /* APR_VERSION_AT_LEAST */ + +#endif /* TEST_SERVER_H */ diff --git a/test/test_all.c b/test/test_all.c new file mode 100644 index 0000000..4cc4c8c --- /dev/null +++ b/test/test_all.c @@ -0,0 +1,101 @@ +/* Copyright 2002-2007 Justin Erenkrantz and Greg Stein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "apr.h" +#include "apr_pools.h" +#include "test_serf.h" +#include <stdlib.h> + +static const struct testlist { + const char *testname; + CuSuite *(*func)(void); +} tests[] = { + {"context", test_context}, + {"buckets", test_buckets}, + {"ssl", test_ssl}, + {"LastTest", NULL} +}; + +int main(int argc, char *argv[]) +{ + CuSuite *alltests = NULL; + CuString *output = CuStringNew(); + int i; + int list_provided = 0; + int exit_code; + + apr_initialize(); + atexit(apr_terminate); + + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "-v")) { + continue; + } + if (!strcmp(argv[i], "-l")) { + for (i = 0; tests[i].func != NULL; i++) { + printf("%s\n", tests[i].testname); + } + exit(0); + } + if (argv[i][0] == '-') { + fprintf(stderr, "invalid option: `%s'\n", argv[i]); + exit(1); + } + list_provided = 1; + } + + alltests = CuSuiteNew(); + if (!list_provided) { + /* add everything */ + for (i = 0; tests[i].func != NULL; i++) { + CuSuite *st = tests[i].func(); + CuSuiteAddSuite(alltests, st); + CuSuiteFree(st); + } + } + else { + /* add only the tests listed */ + for (i = 1; i < argc; i++) { + int j; + int found = 0; + + if (argv[i][0] == '-') { + continue; + } + for (j = 0; tests[j].func != NULL; j++) { + if (!strcmp(argv[i], tests[j].testname)) { + CuSuiteAddSuite(alltests, tests[j].func()); + found = 1; + } + } + if (!found) { + fprintf(stderr, "invalid test name: `%s'\n", argv[i]); + exit(1); + } + } + } + + CuSuiteRun(alltests); + CuSuiteSummary(alltests, output); + CuSuiteDetails(alltests, output); + printf("%s\n", output->buffer); + + exit_code = alltests->failCount > 0 ? 1 : 0; + + CuSuiteFreeDeep(alltests); + CuStringFree(output); + + return exit_code; +} diff --git a/test/test_buckets.c b/test/test_buckets.c new file mode 100644 index 0000000..7d4f7ac --- /dev/null +++ b/test/test_buckets.c @@ -0,0 +1,425 @@ +/* Copyright 2002-2007 Justin Erenkrantz and Greg Stein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <apr.h> +#include <apr_pools.h> +#include <apr_strings.h> + +#include "serf.h" +#include "test_serf.h" + +#define CRLF "\r\n" + +static void test_simple_bucket_readline(CuTest *tc) +{ + apr_status_t status; + serf_bucket_t *bkt; + const char *data; + int found; + apr_size_t len; + + apr_pool_t *test_pool = test_setup(); + serf_bucket_alloc_t *alloc = serf_bucket_allocator_create(test_pool, NULL, + NULL); + + bkt = SERF_BUCKET_SIMPLE_STRING( + "line1" CRLF + "line2", + alloc); + + /* Initialize parameters to check that they will be initialized. */ + len = 0x112233; + data = 0; + status = serf_bucket_readline(bkt, SERF_NEWLINE_CRLF, &found, &data, &len); + + CuAssertIntEquals(tc, APR_SUCCESS, status); + CuAssertIntEquals(tc, SERF_NEWLINE_CRLF, found); + CuAssertIntEquals(tc, 7, len); + CuAssert(tc, data, strncmp("line1" CRLF, data, len) == 0); + + /* Initialize parameters to check that they will be initialized. */ + len = 0x112233; + data = 0; + status = serf_bucket_readline(bkt, SERF_NEWLINE_CRLF, &found, &data, &len); + + CuAssertIntEquals(tc, APR_EOF, status); + CuAssertIntEquals(tc, SERF_NEWLINE_NONE, found); + CuAssertIntEquals(tc, 5, len); + CuAssert(tc, data, strncmp("line2", data, len) == 0); + test_teardown(test_pool); +} + +/* Reads bucket until EOF found and compares read data with zero terminated + string expected. Report all failures using CuTest. */ +static void read_and_check_bucket(CuTest *tc, serf_bucket_t *bkt, + const char *expected) +{ + apr_status_t status; + do + { + const char *data; + apr_size_t len; + + status = serf_bucket_read(bkt, SERF_READ_ALL_AVAIL, &data, &len); + CuAssert(tc, "Got error during bucket reading.", + !SERF_BUCKET_READ_ERROR(status)); + CuAssert(tc, "Read more data than expected.", + strlen(expected) >= len); + CuAssert(tc, "Read data is not equal to expected.", + strncmp(expected, data, len) == 0); + + expected += len; + } while(!APR_STATUS_IS_EOF(status)); + + CuAssert(tc, "Read less data than expected.", strlen(expected) == 0); +} + +static void test_response_bucket_read(CuTest *tc) +{ + serf_bucket_t *bkt, *tmp; + + apr_pool_t *test_pool = test_setup(); + serf_bucket_alloc_t *alloc = serf_bucket_allocator_create(test_pool, NULL, + NULL); + + tmp = SERF_BUCKET_SIMPLE_STRING( + "HTTP/1.1 200 OK" CRLF + "Content-Length: 7" CRLF + CRLF + "abc1234", + alloc); + + bkt = serf_bucket_response_create(tmp, alloc); + + /* Read all bucket and check it content. */ + read_and_check_bucket(tc, bkt, "abc1234"); + test_teardown(test_pool); +} + +static void test_response_bucket_headers(CuTest *tc) +{ + serf_bucket_t *bkt, *tmp, *hdr; + + apr_pool_t *test_pool = test_setup(); + serf_bucket_alloc_t *alloc = serf_bucket_allocator_create(test_pool, NULL, + NULL); + + tmp = SERF_BUCKET_SIMPLE_STRING( + "HTTP/1.1 405 Method Not Allowed" CRLF + "Date: Sat, 12 Jun 2010 14:17:10 GMT" CRLF + "Server: Apache" CRLF + "Allow: " CRLF + "Content-Length: 7" CRLF + "Content-Type: text/html; charset=iso-8859-1" CRLF + "NoSpace:" CRLF + CRLF + "abc1234", + alloc); + + bkt = serf_bucket_response_create(tmp, alloc); + + /* Read all bucket and check it content. */ + read_and_check_bucket(tc, bkt, "abc1234"); + + hdr = serf_bucket_response_get_headers(bkt); + CuAssertStrEquals(tc, + "", + serf_bucket_headers_get(hdr, "Allow")); + CuAssertStrEquals(tc, + "7", + serf_bucket_headers_get(hdr, "Content-Length")); + CuAssertStrEquals(tc, + "", + serf_bucket_headers_get(hdr, "NoSpace")); + test_teardown(test_pool); +} + +static void test_response_bucket_chunked_read(CuTest *tc) +{ + serf_bucket_t *bkt, *tmp, *hdrs; + + apr_pool_t *test_pool = test_setup(); + serf_bucket_alloc_t *alloc = serf_bucket_allocator_create(test_pool, NULL, + NULL); + + tmp = SERF_BUCKET_SIMPLE_STRING( + "HTTP/1.1 200 OK" CRLF + "Transfer-Encoding: chunked" CRLF + CRLF + "3" CRLF + "abc" CRLF + "4" CRLF + "1234" CRLF + "0" CRLF + "Footer: value" CRLF + CRLF, + alloc); + + bkt = serf_bucket_response_create(tmp, alloc); + + /* Read all bucket and check it content. */ + read_and_check_bucket(tc, bkt, "abc1234"); + + hdrs = serf_bucket_response_get_headers(bkt); + CuAssertTrue(tc, hdrs != NULL); + + /* Check that trailing headers parsed correctly. */ + CuAssertStrEquals(tc, "value", serf_bucket_headers_get(hdrs, "Footer")); + test_teardown(test_pool); +} + +static void test_bucket_header_set(CuTest *tc) +{ + apr_pool_t *test_pool = test_setup(); + serf_bucket_alloc_t *alloc = serf_bucket_allocator_create(test_pool, NULL, + NULL); + serf_bucket_t *hdrs = serf_bucket_headers_create(alloc); + + CuAssertTrue(tc, hdrs != NULL); + + serf_bucket_headers_set(hdrs, "Foo", "bar"); + + CuAssertStrEquals(tc, "bar", serf_bucket_headers_get(hdrs, "Foo")); + + serf_bucket_headers_set(hdrs, "Foo", "baz"); + + CuAssertStrEquals(tc, "bar,baz", serf_bucket_headers_get(hdrs, "Foo")); + + serf_bucket_headers_set(hdrs, "Foo", "test"); + + CuAssertStrEquals(tc, "bar,baz,test", serf_bucket_headers_get(hdrs, "Foo")); + + // headers are case insensitive. + CuAssertStrEquals(tc, "bar,baz,test", serf_bucket_headers_get(hdrs, "fOo")); + test_teardown(test_pool); +} + +static apr_status_t read_requested_bytes(serf_bucket_t *bkt, + apr_size_t requested, + const char **buf, + apr_size_t *len, + apr_pool_t *pool) +{ + apr_size_t current = 0; + const char *tmp; + const char *data; + apr_status_t status = APR_SUCCESS; + + tmp = apr_pcalloc(pool, requested); + while (current < requested) { + status = serf_bucket_read(bkt, requested, &data, len); + memcpy((void*)(tmp + current), (void*)data, *len); + current += *len; + if (APR_STATUS_IS_EOF(status)) + break; + } + + *buf = tmp; + *len = current; + return status; +} + + +static void test_iovec_buckets(CuTest *tc) +{ + apr_status_t status; + serf_bucket_t *bkt, *iobkt; + const char *data; + apr_size_t len; + struct iovec vecs[32]; + struct iovec tgt_vecs[32]; + int i; + int vecs_used; + + apr_pool_t *test_pool = test_setup(); + serf_bucket_alloc_t *alloc = serf_bucket_allocator_create(test_pool, NULL, + NULL); + + /* Test 1: Read a single string in an iovec, store it in a iovec_bucket + and then read it back. */ + bkt = SERF_BUCKET_SIMPLE_STRING( + "line1" CRLF + "line2", + alloc); + + status = serf_bucket_read_iovec(bkt, SERF_READ_ALL_AVAIL, 32, vecs, + &vecs_used); + + iobkt = serf_bucket_iovec_create(vecs, vecs_used, alloc); + + /* Check available data */ + status = serf_bucket_peek(iobkt, &data, &len); + CuAssertIntEquals(tc, APR_EOF, status); + CuAssertIntEquals(tc, strlen("line1" CRLF "line2"), len); + + /* Try to read only a few bytes (less than what's in the first buffer). */ + status = serf_bucket_read_iovec(iobkt, 3, 32, tgt_vecs, &vecs_used); + CuAssertIntEquals(tc, APR_SUCCESS, status); + CuAssertIntEquals(tc, 1, vecs_used); + CuAssertIntEquals(tc, 3, tgt_vecs[0].iov_len); + CuAssert(tc, tgt_vecs[0].iov_base, + strncmp("lin", tgt_vecs[0].iov_base, tgt_vecs[0].iov_len) == 0); + + /* Read the rest of the data. */ + status = serf_bucket_read_iovec(iobkt, SERF_READ_ALL_AVAIL, 32, tgt_vecs, + &vecs_used); + CuAssertIntEquals(tc, APR_EOF, status); + CuAssertIntEquals(tc, 1, vecs_used); + CuAssertIntEquals(tc, strlen("e1" CRLF "line2"), tgt_vecs[0].iov_len); + CuAssert(tc, tgt_vecs[0].iov_base, + strncmp("e1" CRLF "line2", tgt_vecs[0].iov_base, tgt_vecs[0].iov_len - 3) == 0); + + /* Bucket should now be empty */ + status = serf_bucket_peek(iobkt, &data, &len); + CuAssertIntEquals(tc, APR_EOF, status); + CuAssertIntEquals(tc, 0, len); + + /* Test 2: Read multiple character bufs in an iovec, then read them back + in bursts. */ + for (i = 0; i < 32 ; i++) { + vecs[i].iov_base = apr_psprintf(test_pool, "data %02d 901234567890", i); + vecs[i].iov_len = strlen(vecs[i].iov_base); + } + + iobkt = serf_bucket_iovec_create(vecs, 32, alloc); + + /* Check that some data is in the buffer. Don't verify the actual data, the + amount of data returned is not guaranteed to be the full buffer. */ + status = serf_bucket_peek(iobkt, &data, &len); + CuAssertTrue(tc, len > 0); + CuAssertIntEquals(tc, APR_SUCCESS, status); /* this assumes not all data is + returned at once, + not guaranteed! */ + + /* Read 1 buf. 20 = sizeof("data %2d 901234567890") */ + status = serf_bucket_read_iovec(iobkt, 1 * 20, 32, + tgt_vecs, &vecs_used); + CuAssertIntEquals(tc, APR_SUCCESS, status); + CuAssertIntEquals(tc, 1, vecs_used); + CuAssert(tc, tgt_vecs[0].iov_base, + strncmp("data 00 901234567890", tgt_vecs[0].iov_base, tgt_vecs[0].iov_len) == 0); + + /* Read 2 bufs. */ + status = serf_bucket_read_iovec(iobkt, 2 * 20, 32, + tgt_vecs, &vecs_used); + CuAssertIntEquals(tc, APR_SUCCESS, status); + CuAssertIntEquals(tc, 2, vecs_used); + + /* Read the remaining 29 bufs. */ + vecs_used = 400; /* test if iovec code correctly resets vecs_used */ + status = serf_bucket_read_iovec(iobkt, SERF_READ_ALL_AVAIL, 32, + tgt_vecs, &vecs_used); + CuAssertIntEquals(tc, APR_EOF, status); + CuAssertIntEquals(tc, 29, vecs_used); + + /* Test 3: use serf_bucket_read */ + for (i = 0; i < 32 ; i++) { + vecs[i].iov_base = apr_psprintf(test_pool, "DATA %02d 901234567890", i); + vecs[i].iov_len = strlen(vecs[i].iov_base); + } + + iobkt = serf_bucket_iovec_create(vecs, 32, alloc); + + status = serf_bucket_read(iobkt, 10, &data, &len); + CuAssertIntEquals(tc, APR_SUCCESS, status); + CuAssertIntEquals(tc, 10, len); + CuAssert(tc, data, + strncmp("DATA 00 90", data, len) == 0); + + status = serf_bucket_read(iobkt, 10, &data, &len); + CuAssertIntEquals(tc, APR_SUCCESS, status); + CuAssertIntEquals(tc, 10, len); + CuAssert(tc, tgt_vecs[0].iov_base, + strncmp("1234567890", data, len) == 0); + + for (i = 1; i < 31 ; i++) { + const char *exp = apr_psprintf(test_pool, "DATA %02d 901234567890", i); + status = serf_bucket_read(iobkt, SERF_READ_ALL_AVAIL, &data, &len); + CuAssertIntEquals(tc, APR_SUCCESS, status); + CuAssertIntEquals(tc, 20, len); + CuAssert(tc, data, + strncmp(exp, data, len) == 0); + + } + + status = serf_bucket_read(iobkt, 20, &data, &len); + CuAssertIntEquals(tc, APR_EOF, status); + CuAssertIntEquals(tc, 20, len); + CuAssert(tc, data, + strncmp("DATA 31 901234567890", data, len) == 0); + + /* Test 3: read an empty iovec */ + iobkt = serf_bucket_iovec_create(vecs, 0, alloc); + status = serf_bucket_read_iovec(iobkt, SERF_READ_ALL_AVAIL, 32, + tgt_vecs, &vecs_used); + CuAssertIntEquals(tc, APR_EOF, status); + CuAssertIntEquals(tc, 0, vecs_used); + + status = serf_bucket_read(iobkt, SERF_READ_ALL_AVAIL, &data, &len); + CuAssertIntEquals(tc, APR_EOF, status); + CuAssertIntEquals(tc, 0, len); + + /* Test 4: read 0 bytes from an iovec */ + bkt = SERF_BUCKET_SIMPLE_STRING("line1" CRLF, alloc); + status = serf_bucket_read_iovec(bkt, SERF_READ_ALL_AVAIL, 32, vecs, + &vecs_used); + iobkt = serf_bucket_iovec_create(vecs, vecs_used, alloc); + status = serf_bucket_read_iovec(iobkt, 0, 32, + tgt_vecs, &vecs_used); + CuAssertIntEquals(tc, APR_SUCCESS, status); + CuAssertIntEquals(tc, 0, vecs_used); + + test_teardown(test_pool); +} + +static void test_aggregate_buckets(CuTest *tc) +{ + apr_status_t status; + serf_bucket_t *bkt, *aggbkt; + struct iovec tgt_vecs[32]; + int vecs_used; + + apr_pool_t *test_pool = test_setup(); + serf_bucket_alloc_t *alloc = serf_bucket_allocator_create(test_pool, NULL, + NULL); + + /* Test 1: read 0 bytes from an aggregate */ + aggbkt = serf_bucket_aggregate_create(alloc); + + bkt = SERF_BUCKET_SIMPLE_STRING("line1" CRLF, alloc); + serf_bucket_aggregate_append(aggbkt, bkt); + + status = serf_bucket_read_iovec(aggbkt, 0, 32, + tgt_vecs, &vecs_used); + CuAssertIntEquals(tc, APR_SUCCESS, status); + CuAssertIntEquals(tc, 0, vecs_used); + + test_teardown(test_pool); +} + +CuSuite *test_buckets(void) +{ + CuSuite *suite = CuSuiteNew(); + + SUITE_ADD_TEST(suite, test_simple_bucket_readline); + SUITE_ADD_TEST(suite, test_response_bucket_read); + SUITE_ADD_TEST(suite, test_response_bucket_headers); + SUITE_ADD_TEST(suite, test_response_bucket_chunked_read); + SUITE_ADD_TEST(suite, test_bucket_header_set); + SUITE_ADD_TEST(suite, test_iovec_buckets); + SUITE_ADD_TEST(suite, test_aggregate_buckets); + + return suite; +} diff --git a/test/test_context.c b/test/test_context.c new file mode 100644 index 0000000..d2a24d0 --- /dev/null +++ b/test/test_context.c @@ -0,0 +1,1011 @@ +/* Copyright 2002-2007 Justin Erenkrantz and Greg Stein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> + +#include <apr.h> +#include <apr_pools.h> +#include <apr_strings.h> +#include <apr_version.h> + +#include "serf.h" + +#include "test_serf.h" +#include "server/test_server.h" + +typedef struct { + serf_response_acceptor_t acceptor; + void *acceptor_baton; + + serf_response_handler_t handler; + + apr_array_header_t *sent_requests; + apr_array_header_t *accepted_requests; + apr_array_header_t *handled_requests; + int req_id; + + const char *method; + const char *path; + int done; + + test_baton_t *tb; +} handler_baton_t; + +static serf_bucket_t* accept_response(serf_request_t *request, + serf_bucket_t *stream, + void *acceptor_baton, + apr_pool_t *pool) +{ + serf_bucket_t *c; + serf_bucket_alloc_t *bkt_alloc; + handler_baton_t *ctx = acceptor_baton; + + /* get the per-request bucket allocator */ + bkt_alloc = serf_request_get_alloc(request); + + /* Create a barrier so the response doesn't eat us! */ + c = serf_bucket_barrier_create(stream, bkt_alloc); + + APR_ARRAY_PUSH(ctx->accepted_requests, int) = ctx->req_id; + + return serf_bucket_response_create(c, bkt_alloc); +} + +static apr_status_t setup_request(serf_request_t *request, + void *setup_baton, + serf_bucket_t **req_bkt, + serf_response_acceptor_t *acceptor, + void **acceptor_baton, + serf_response_handler_t *handler, + void **handler_baton, + apr_pool_t *pool) +{ + handler_baton_t *ctx = setup_baton; + serf_bucket_t *body_bkt; + + /* create a simple body text */ + const char *str = apr_psprintf(pool, "%d", ctx->req_id); + body_bkt = serf_bucket_simple_create(str, strlen(str), NULL, NULL, + serf_request_get_alloc(request)); + *req_bkt = + serf_request_bucket_request_create(request, + ctx->method, ctx->path, + body_bkt, + serf_request_get_alloc(request)); + + APR_ARRAY_PUSH(ctx->sent_requests, int) = ctx->req_id; + + *acceptor = ctx->acceptor; + *acceptor_baton = ctx; + *handler = ctx->handler; + *handler_baton = ctx; + + return APR_SUCCESS; +} + +static apr_status_t handle_response(serf_request_t *request, + serf_bucket_t *response, + void *handler_baton, + apr_pool_t *pool) +{ + handler_baton_t *ctx = handler_baton; + + if (! response) { + serf_connection_request_create(ctx->tb->connection, + setup_request, + ctx); + return APR_SUCCESS; + } + + while (1) { + apr_status_t status; + const char *data; + apr_size_t len; + + status = serf_bucket_read(response, 2048, &data, &len); + if (SERF_BUCKET_READ_ERROR(status)) + return status; + + if (APR_STATUS_IS_EOF(status)) { + APR_ARRAY_PUSH(ctx->handled_requests, int) = ctx->req_id; + ctx->done = TRUE; + return APR_EOF; + } + + if (APR_STATUS_IS_EAGAIN(status)) { + return status; + } + + } + + return APR_SUCCESS; +} + +/* Validate that requests are sent and completed in the order of creation. */ +static void test_serf_connection_request_create(CuTest *tc) +{ + test_baton_t *tb; + serf_request_t *request1, *request2; + handler_baton_t handler_ctx, handler2_ctx; + apr_status_t status; + apr_pool_t *iter_pool; + apr_array_header_t *accepted_requests, *handled_requests, *sent_requests; + int i; + test_server_message_t message_list[] = { + {CHUNKED_REQUEST(1, "1")}, + {CHUNKED_REQUEST(1, "2")}, + }; + + test_server_action_t action_list[] = { + {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE}, + {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE}, + }; + apr_pool_t *test_pool = test_setup(); + + accepted_requests = apr_array_make(test_pool, 2, sizeof(int)); + sent_requests = apr_array_make(test_pool, 2, sizeof(int)); + handled_requests = apr_array_make(test_pool, 2, sizeof(int)); + + /* Set up a test context with a server */ + status = test_server_setup(&tb, + message_list, 2, + action_list, 2, 0, NULL, + test_pool); + CuAssertIntEquals(tc, APR_SUCCESS, status); + + handler_ctx.method = "GET"; + handler_ctx.path = "/"; + handler_ctx.done = FALSE; + + handler_ctx.acceptor = accept_response; + handler_ctx.acceptor_baton = NULL; + handler_ctx.handler = handle_response; + handler_ctx.req_id = 1; + handler_ctx.accepted_requests = accepted_requests; + handler_ctx.sent_requests = sent_requests; + handler_ctx.handled_requests = handled_requests; + + request1 = serf_connection_request_create(tb->connection, + setup_request, + &handler_ctx); + + handler2_ctx = handler_ctx; + handler2_ctx.req_id = 2; + + request2 = serf_connection_request_create(tb->connection, + setup_request, + &handler2_ctx); + + apr_pool_create(&iter_pool, test_pool); + + while (!handler_ctx.done || !handler2_ctx.done) + { + apr_pool_clear(iter_pool); + + status = test_server_run(tb->serv_ctx, 0, iter_pool); + if (APR_STATUS_IS_TIMEUP(status)) + status = APR_SUCCESS; + CuAssertIntEquals(tc, APR_SUCCESS, status); + + status = serf_context_run(tb->context, 0, iter_pool); + if (APR_STATUS_IS_TIMEUP(status)) + status = APR_SUCCESS; + CuAssertIntEquals(tc, APR_SUCCESS, status); + } + apr_pool_destroy(iter_pool); + + /* Check that all requests were received */ + CuAssertIntEquals(tc, 2, sent_requests->nelts); + CuAssertIntEquals(tc, 2, accepted_requests->nelts); + CuAssertIntEquals(tc, 2, handled_requests->nelts); + + /* Check that the requests were sent in the order we created them */ + for (i = 0; i < sent_requests->nelts; i++) { + int req_nr = APR_ARRAY_IDX(sent_requests, i, int); + CuAssertIntEquals(tc, i + 1, req_nr); + } + + /* Check that the requests were received in the order we created them */ + for (i = 0; i < handled_requests->nelts; i++) { + int req_nr = APR_ARRAY_IDX(handled_requests, i, int); + CuAssertIntEquals(tc, i + 1, req_nr); + } + + test_server_teardown(tb, test_pool); + test_teardown(test_pool); +} + +/* Validate that priority requests are sent and completed before normal + requests. */ +static void test_serf_connection_priority_request_create(CuTest *tc) +{ + test_baton_t *tb; + serf_request_t *request1, *request2, *request3; + handler_baton_t handler_ctx, handler2_ctx, handler3_ctx; + apr_status_t status; + apr_pool_t *iter_pool; + apr_array_header_t *accepted_requests, *handled_requests, *sent_requests; + int i; + + test_server_message_t message_list[] = { + {CHUNKED_REQUEST(1, "1")}, + {CHUNKED_REQUEST(1, "2")}, + {CHUNKED_REQUEST(1, "3")}, + }; + + test_server_action_t action_list[] = { + {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE}, + {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE}, + {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE}, + }; + + apr_pool_t *test_pool = test_setup(); + + accepted_requests = apr_array_make(test_pool, 3, sizeof(int)); + sent_requests = apr_array_make(test_pool, 3, sizeof(int)); + handled_requests = apr_array_make(test_pool, 3, sizeof(int)); + + /* Set up a test context with a server */ + status = test_server_setup(&tb, + message_list, 3, + action_list, 3, 0, NULL, + test_pool); + CuAssertIntEquals(tc, APR_SUCCESS, status); + + handler_ctx.method = "GET"; + handler_ctx.path = "/"; + handler_ctx.done = FALSE; + + handler_ctx.acceptor = accept_response; + handler_ctx.acceptor_baton = NULL; + handler_ctx.handler = handle_response; + handler_ctx.req_id = 2; + handler_ctx.accepted_requests = accepted_requests; + handler_ctx.sent_requests = sent_requests; + handler_ctx.handled_requests = handled_requests; + + request1 = serf_connection_request_create(tb->connection, + setup_request, + &handler_ctx); + + handler2_ctx = handler_ctx; + handler2_ctx.req_id = 3; + + request2 = serf_connection_request_create(tb->connection, + setup_request, + &handler2_ctx); + handler3_ctx = handler_ctx; + handler3_ctx.req_id = 1; + + request3 = serf_connection_priority_request_create(tb->connection, + setup_request, + &handler3_ctx); + + apr_pool_create(&iter_pool, test_pool); + + while (!handler_ctx.done || !handler2_ctx.done || !handler3_ctx.done) + { + apr_pool_clear(iter_pool); + + status = test_server_run(tb->serv_ctx, 0, iter_pool); + if (APR_STATUS_IS_TIMEUP(status)) + status = APR_SUCCESS; + CuAssertIntEquals(tc, APR_SUCCESS, status); + + status = serf_context_run(tb->context, 0, iter_pool); + if (APR_STATUS_IS_TIMEUP(status)) + status = APR_SUCCESS; + CuAssertIntEquals(tc, APR_SUCCESS, status); + + /* Debugging purposes only! */ + serf_debug__closed_conn(tb->bkt_alloc); + } + apr_pool_destroy(iter_pool); + + /* Check that all requests were received */ + CuAssertIntEquals(tc, 3, sent_requests->nelts); + CuAssertIntEquals(tc, 3, accepted_requests->nelts); + CuAssertIntEquals(tc, 3, handled_requests->nelts); + + /* Check that the requests were sent in the order we created them */ + for (i = 0; i < sent_requests->nelts; i++) { + int req_nr = APR_ARRAY_IDX(sent_requests, i, int); + CuAssertIntEquals(tc, i + 1, req_nr); + } + + /* Check that the requests were received in the order we created them */ + for (i = 0; i < handled_requests->nelts; i++) { + int req_nr = APR_ARRAY_IDX(handled_requests, i, int); + CuAssertIntEquals(tc, i + 1, req_nr); + } + + test_server_teardown(tb, test_pool); + test_teardown(test_pool); +} + +/* Test that serf correctly handles the 'Connection:close' header when the + server is planning to close the connection. */ +#define NUM_REQUESTS 10 +static void test_serf_closed_connection(CuTest *tc) +{ + test_baton_t *tb; + apr_array_header_t *accepted_requests, *handled_requests, *sent_requests; + apr_status_t status; + handler_baton_t handler_ctx[NUM_REQUESTS]; + int done = FALSE, i; + + test_server_message_t message_list[] = { + {CHUNKED_REQUEST(1, "1")}, + {CHUNKED_REQUEST(1, "2")}, + {CHUNKED_REQUEST(1, "3")}, + {CHUNKED_REQUEST(1, "4")}, + {CHUNKED_REQUEST(1, "5")}, + {CHUNKED_REQUEST(1, "6")}, + {CHUNKED_REQUEST(1, "7")}, + {CHUNKED_REQUEST(1, "8")}, + {CHUNKED_REQUEST(1, "9")}, + {CHUNKED_REQUEST(2, "10")} + }; + + test_server_action_t action_list[] = { + {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE}, + {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE}, + {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE}, + {SERVER_RESPOND, + "HTTP/1.1 200 OK" CRLF + "Transfer-Encoding: chunked" CRLF + "Connection: close" CRLF + CRLF + "0" CRLF + CRLF + }, + {SERVER_IGNORE_AND_KILL_CONNECTION}, + {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE}, + {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE}, + {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE}, + {SERVER_RESPOND, + "HTTP/1.1 200 OK" CRLF + "Transfer-Encoding: chunked" CRLF + "Connection: close" CRLF + CRLF + "0" CRLF + CRLF + }, + {SERVER_IGNORE_AND_KILL_CONNECTION}, + {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE}, + {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE}, + }; + + apr_pool_t *test_pool = test_setup(); + + accepted_requests = apr_array_make(test_pool, NUM_REQUESTS, sizeof(int)); + sent_requests = apr_array_make(test_pool, NUM_REQUESTS, sizeof(int)); + handled_requests = apr_array_make(test_pool, NUM_REQUESTS, sizeof(int)); + + /* Set up a test context with a server. */ + status = test_server_setup(&tb, + message_list, 10, + action_list, 12, + 0, + NULL, + test_pool); + CuAssertIntEquals(tc, APR_SUCCESS, status); + + for (i = 0 ; i < NUM_REQUESTS ; i++) { + /* Send some requests on the connections */ + handler_ctx[i].method = "GET"; + handler_ctx[i].path = "/"; + handler_ctx[i].done = FALSE; + + handler_ctx[i].acceptor = accept_response; + handler_ctx[i].acceptor_baton = NULL; + handler_ctx[i].handler = handle_response; + handler_ctx[i].req_id = i+1; + handler_ctx[i].accepted_requests = accepted_requests; + handler_ctx[i].sent_requests = sent_requests; + handler_ctx[i].handled_requests = handled_requests; + handler_ctx[i].tb = tb; + + serf_connection_request_create(tb->connection, + setup_request, + &handler_ctx[i]); + } + + while (1) { + status = test_server_run(tb->serv_ctx, 0, test_pool); + if (APR_STATUS_IS_TIMEUP(status)) + status = APR_SUCCESS; + CuAssertIntEquals(tc, APR_SUCCESS, status); + + status = serf_context_run(tb->context, 0, test_pool); + if (APR_STATUS_IS_TIMEUP(status)) + status = APR_SUCCESS; + CuAssertIntEquals(tc, APR_SUCCESS, status); + + /* Debugging purposes only! */ + serf_debug__closed_conn(tb->bkt_alloc); + + done = TRUE; + for (i = 0 ; i < NUM_REQUESTS ; i++) + if (handler_ctx[i].done == FALSE) { + done = FALSE; + break; + } + if (done) + break; + } + + /* Check that all requests were received */ + CuAssertTrue(tc, sent_requests->nelts >= NUM_REQUESTS); + CuAssertIntEquals(tc, NUM_REQUESTS, accepted_requests->nelts); + CuAssertIntEquals(tc, NUM_REQUESTS, handled_requests->nelts); + + /* Cleanup */ + test_server_teardown(tb, test_pool); + test_teardown(test_pool); +} +#undef NUM_REQUESTS + +/* Test if serf is sending the request to the proxy, not to the server + directly. */ +static void test_serf_setup_proxy(CuTest *tc) +{ + test_baton_t *tb; + serf_request_t *request; + handler_baton_t handler_ctx; + apr_status_t status; + apr_pool_t *iter_pool; + apr_array_header_t *accepted_requests, *handled_requests, *sent_requests; + int i; + int numrequests = 1; + + test_server_message_t message_list[] = { + {"GET http://localhost:" SERV_PORT_STR " HTTP/1.1" CRLF\ + "Host: localhost:" SERV_PORT_STR CRLF\ + "Transfer-Encoding: chunked" CRLF\ + CRLF\ + "1" CRLF\ + "1" CRLF\ + "0" CRLF\ + CRLF} + }; + + test_server_action_t action_list_proxy[] = { + {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE}, + }; + + apr_pool_t *test_pool = test_setup(); + + accepted_requests = apr_array_make(test_pool, numrequests, sizeof(int)); + sent_requests = apr_array_make(test_pool, numrequests, sizeof(int)); + handled_requests = apr_array_make(test_pool, numrequests, sizeof(int)); + + /* Set up a test context with a server, no messages expected. */ + status = test_server_proxy_setup(&tb, + /* server messages and actions */ + NULL, 0, + NULL, 0, + /* server messages and actions */ + message_list, 1, + action_list_proxy, 1, + 0, + NULL, test_pool); + CuAssertIntEquals(tc, APR_SUCCESS, status); + + handler_ctx.method = "GET"; + handler_ctx.path = "/"; + handler_ctx.done = FALSE; + + handler_ctx.acceptor = accept_response; + handler_ctx.acceptor_baton = NULL; + handler_ctx.handler = handle_response; + handler_ctx.req_id = 1; + handler_ctx.accepted_requests = accepted_requests; + handler_ctx.sent_requests = sent_requests; + handler_ctx.handled_requests = handled_requests; + + request = serf_connection_request_create(tb->connection, + setup_request, + &handler_ctx); + + apr_pool_create(&iter_pool, test_pool); + + while (!handler_ctx.done) + { + apr_pool_clear(iter_pool); + + status = test_server_run(tb->serv_ctx, 0, iter_pool); + if (APR_STATUS_IS_TIMEUP(status)) + status = APR_SUCCESS; + CuAssertIntEquals(tc, APR_SUCCESS, status); + + status = test_server_run(tb->proxy_ctx, 0, iter_pool); + if (APR_STATUS_IS_TIMEUP(status)) + status = APR_SUCCESS; + CuAssertIntEquals(tc, APR_SUCCESS, status); + + status = serf_context_run(tb->context, 0, iter_pool); + if (APR_STATUS_IS_TIMEUP(status)) + status = APR_SUCCESS; + CuAssertIntEquals(tc, APR_SUCCESS, status); + + /* Debugging purposes only! */ + serf_debug__closed_conn(tb->bkt_alloc); + } + apr_pool_destroy(iter_pool); + + /* Check that all requests were received */ + CuAssertIntEquals(tc, numrequests, sent_requests->nelts); + CuAssertIntEquals(tc, numrequests, accepted_requests->nelts); + CuAssertIntEquals(tc, numrequests, handled_requests->nelts); + + /* Check that the requests were sent in the order we created them */ + for (i = 0; i < sent_requests->nelts; i++) { + int req_nr = APR_ARRAY_IDX(sent_requests, i, int); + CuAssertIntEquals(tc, i + 1, req_nr); + } + + /* Check that the requests were received in the order we created them */ + for (i = 0; i < handled_requests->nelts; i++) { + int req_nr = APR_ARRAY_IDX(handled_requests, i, int); + CuAssertIntEquals(tc, i + 1, req_nr); + } + + test_server_teardown(tb, test_pool); + test_teardown(test_pool); +} + +/***************************************************************************** + * Test if we can make serf send requests one by one. + *****************************************************************************/ + +/* Resend the first request 4 times by reducing the pipeline bandwidth to + one request at a time, and by adding the first request again at the start of + the outgoing queue. */ +static apr_status_t +handle_response_keepalive_limit(serf_request_t *request, + serf_bucket_t *response, + void *handler_baton, + apr_pool_t *pool) +{ + handler_baton_t *ctx = handler_baton; + + if (! response) { + return APR_SUCCESS; + } + + while (1) { + apr_status_t status; + const char *data; + apr_size_t len; + + status = serf_bucket_read(response, 2048, &data, &len); + if (SERF_BUCKET_READ_ERROR(status)) { + return status; + } + + if (APR_STATUS_IS_EOF(status)) { + APR_ARRAY_PUSH(ctx->handled_requests, int) = ctx->req_id; + ctx->done = TRUE; + if (ctx->req_id == 1 && ctx->handled_requests->nelts < 3) { + serf_connection_priority_request_create(ctx->tb->connection, + setup_request, + ctx); + ctx->done = FALSE; + } + return APR_EOF; + } + } + + return APR_SUCCESS; +} + +#define SEND_REQUESTS 5 +#define RCVD_REQUESTS 7 +static void test_keepalive_limit_one_by_one(CuTest *tc) +{ + test_baton_t *tb; + apr_array_header_t *accepted_requests, *handled_requests, *sent_requests; + apr_status_t status; + handler_baton_t handler_ctx[SEND_REQUESTS]; + int done = FALSE, i; + + test_server_message_t message_list[] = { + {CHUNKED_REQUEST(1, "1")}, + {CHUNKED_REQUEST(1, "1")}, + {CHUNKED_REQUEST(1, "1")}, + {CHUNKED_REQUEST(1, "2")}, + {CHUNKED_REQUEST(1, "3")}, + {CHUNKED_REQUEST(1, "4")}, + {CHUNKED_REQUEST(1, "5")}, + }; + + test_server_action_t action_list[] = { + {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE}, + {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE}, + {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE}, + {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE}, + {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE}, + {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE}, + {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE}, + }; + + apr_pool_t *test_pool = test_setup(); + + accepted_requests = apr_array_make(test_pool, RCVD_REQUESTS, sizeof(int)); + sent_requests = apr_array_make(test_pool, RCVD_REQUESTS, sizeof(int)); + handled_requests = apr_array_make(test_pool, RCVD_REQUESTS, sizeof(int)); + + /* Set up a test context with a server. */ + status = test_server_setup(&tb, + message_list, 7, + action_list, 7, 0, NULL, + test_pool); + CuAssertIntEquals(tc, APR_SUCCESS, status); + + for (i = 0 ; i < SEND_REQUESTS ; i++) { + /* Send some requests on the connections */ + handler_ctx[i].method = "GET"; + handler_ctx[i].path = "/"; + handler_ctx[i].done = FALSE; + + handler_ctx[i].acceptor = accept_response; + handler_ctx[i].acceptor_baton = NULL; + handler_ctx[i].handler = handle_response_keepalive_limit; + handler_ctx[i].req_id = i+1; + handler_ctx[i].accepted_requests = accepted_requests; + handler_ctx[i].sent_requests = sent_requests; + handler_ctx[i].handled_requests = handled_requests; + handler_ctx[i].tb = tb; + + serf_connection_request_create(tb->connection, + setup_request, + &handler_ctx[i]); + serf_connection_set_max_outstanding_requests(tb->connection, 1); + } + + while (1) { + status = test_server_run(tb->serv_ctx, 0, test_pool); + if (APR_STATUS_IS_TIMEUP(status)) + status = APR_SUCCESS; + CuAssertIntEquals(tc, APR_SUCCESS, status); + + status = serf_context_run(tb->context, 0, test_pool); + if (APR_STATUS_IS_TIMEUP(status)) + status = APR_SUCCESS; + CuAssertIntEquals(tc, APR_SUCCESS, status); + + /* Debugging purposes only! */ + serf_debug__closed_conn(tb->bkt_alloc); + + done = TRUE; + for (i = 0 ; i < SEND_REQUESTS ; i++) + if (handler_ctx[i].done == FALSE) { + done = FALSE; + break; + } + if (done) + break; + } + + /* Check that all requests were received */ + CuAssertIntEquals(tc, RCVD_REQUESTS, sent_requests->nelts); + CuAssertIntEquals(tc, RCVD_REQUESTS, accepted_requests->nelts); + CuAssertIntEquals(tc, RCVD_REQUESTS, handled_requests->nelts); + + /* Cleanup */ + test_server_teardown(tb, test_pool); + test_teardown(test_pool); +} +#undef SEND_REQUESTS +#undef RCVD_REQUESTS + +/***************************************************************************** + * Test if we can make serf first send requests one by one, and then change + * back to burst mode. + *****************************************************************************/ +#define SEND_REQUESTS 5 +#define RCVD_REQUESTS 7 +/* Resend the first request 2 times by reducing the pipeline bandwidth to + one request at a time, and by adding the first request again at the start of + the outgoing queue. */ +static apr_status_t +handle_response_keepalive_limit_burst(serf_request_t *request, + serf_bucket_t *response, + void *handler_baton, + apr_pool_t *pool) +{ + handler_baton_t *ctx = handler_baton; + + if (! response) { + return APR_SUCCESS; + } + + while (1) { + apr_status_t status; + const char *data; + apr_size_t len; + + status = serf_bucket_read(response, 2048, &data, &len); + if (SERF_BUCKET_READ_ERROR(status)) { + return status; + } + + if (APR_STATUS_IS_EOF(status)) { + APR_ARRAY_PUSH(ctx->handled_requests, int) = ctx->req_id; + ctx->done = TRUE; + if (ctx->req_id == 1 && ctx->handled_requests->nelts < 3) { + serf_connection_priority_request_create(ctx->tb->connection, + setup_request, + ctx); + ctx->done = FALSE; + } + else { + /* No more one-by-one. */ + serf_connection_set_max_outstanding_requests(ctx->tb->connection, + 0); + } + return APR_EOF; + } + + if (APR_STATUS_IS_EAGAIN(status)) { + return status; + } + } + + return APR_SUCCESS; +} + +static void test_keepalive_limit_one_by_one_and_burst(CuTest *tc) +{ + test_baton_t *tb; + apr_array_header_t *accepted_requests, *handled_requests, *sent_requests; + apr_status_t status; + handler_baton_t handler_ctx[SEND_REQUESTS]; + int done = FALSE, i; + + test_server_message_t message_list[] = { + {CHUNKED_REQUEST(1, "1")}, + {CHUNKED_REQUEST(1, "1")}, + {CHUNKED_REQUEST(1, "1")}, + {CHUNKED_REQUEST(1, "2")}, + {CHUNKED_REQUEST(1, "3")}, + {CHUNKED_REQUEST(1, "4")}, + {CHUNKED_REQUEST(1, "5")}, + }; + + test_server_action_t action_list[] = { + {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE}, + {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE}, + {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE}, + {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE}, + {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE}, + {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE}, + {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE}, + }; + + apr_pool_t *test_pool = test_setup(); + + accepted_requests = apr_array_make(test_pool, RCVD_REQUESTS, sizeof(int)); + sent_requests = apr_array_make(test_pool, RCVD_REQUESTS, sizeof(int)); + handled_requests = apr_array_make(test_pool, RCVD_REQUESTS, sizeof(int)); + + /* Set up a test context with a server. */ + status = test_server_setup(&tb, + message_list, 7, + action_list, 7, 0, NULL, + test_pool); + CuAssertIntEquals(tc, APR_SUCCESS, status); + + for (i = 0 ; i < SEND_REQUESTS ; i++) { + /* Send some requests on the connections */ + handler_ctx[i].method = "GET"; + handler_ctx[i].path = "/"; + handler_ctx[i].done = FALSE; + + handler_ctx[i].acceptor = accept_response; + handler_ctx[i].acceptor_baton = NULL; + handler_ctx[i].handler = handle_response_keepalive_limit_burst; + handler_ctx[i].req_id = i+1; + handler_ctx[i].accepted_requests = accepted_requests; + handler_ctx[i].sent_requests = sent_requests; + handler_ctx[i].handled_requests = handled_requests; + handler_ctx[i].tb = tb; + + serf_connection_request_create(tb->connection, + setup_request, + &handler_ctx[i]); + serf_connection_set_max_outstanding_requests(tb->connection, 1); + } + + while (1) { + status = test_server_run(tb->serv_ctx, 0, test_pool); + if (APR_STATUS_IS_TIMEUP(status)) + status = APR_SUCCESS; + CuAssertIntEquals(tc, APR_SUCCESS, status); + + status = serf_context_run(tb->context, 0, test_pool); + if (APR_STATUS_IS_TIMEUP(status)) + status = APR_SUCCESS; + CuAssertIntEquals(tc, APR_SUCCESS, status); + + /* Debugging purposes only! */ + serf_debug__closed_conn(tb->bkt_alloc); + + done = TRUE; + for (i = 0 ; i < SEND_REQUESTS ; i++) + if (handler_ctx[i].done == FALSE) { + done = FALSE; + break; + } + if (done) + break; + } + + /* Check that all requests were received */ + CuAssertIntEquals(tc, RCVD_REQUESTS, sent_requests->nelts); + CuAssertIntEquals(tc, RCVD_REQUESTS, accepted_requests->nelts); + CuAssertIntEquals(tc, RCVD_REQUESTS, handled_requests->nelts); + + /* Cleanup */ + test_server_teardown(tb, test_pool); + test_teardown(test_pool); +} +#undef SEND_REQUESTS +#undef RCVD_REQUESTS + +#define NUM_REQUESTS 5 +typedef struct { + apr_off_t read; + apr_off_t written; +} progress_baton_t; + +static void +progress_cb(void *progress_baton, apr_off_t read, apr_off_t written) +{ + test_baton_t *tb = progress_baton; + progress_baton_t *pb = tb->user_baton; + + pb->read = read; + pb->written = written; +} + +static apr_status_t progress_conn_setup(apr_socket_t *skt, + serf_bucket_t **input_bkt, + serf_bucket_t **output_bkt, + void *setup_baton, + apr_pool_t *pool) +{ + test_baton_t *tb = setup_baton; + *input_bkt = serf_context_bucket_socket_create(tb->context, skt, tb->bkt_alloc); + return APR_SUCCESS; +} + +static void test_serf_progress_callback(CuTest *tc) +{ + test_baton_t *tb; + apr_array_header_t *accepted_requests, *handled_requests, *sent_requests; + apr_status_t status; + handler_baton_t handler_ctx[NUM_REQUESTS]; + int done = FALSE, i; + progress_baton_t *pb; + + test_server_message_t message_list[] = { + {CHUNKED_REQUEST(1, "1")}, + {CHUNKED_REQUEST(1, "2")}, + {CHUNKED_REQUEST(1, "3")}, + {CHUNKED_REQUEST(1, "4")}, + {CHUNKED_REQUEST(1, "5")}, + }; + + test_server_action_t action_list[] = { + {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE}, + {SERVER_RESPOND, CHUNKED_RESPONSE(1, "2")}, + {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE}, + {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE}, + {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE}, + }; + + apr_pool_t *test_pool = test_setup(); + + accepted_requests = apr_array_make(test_pool, NUM_REQUESTS, sizeof(int)); + sent_requests = apr_array_make(test_pool, NUM_REQUESTS, sizeof(int)); + handled_requests = apr_array_make(test_pool, NUM_REQUESTS, sizeof(int)); + + /* Set up a test context with a server. */ + status = test_server_setup(&tb, + message_list, 5, + action_list, 5, 0, + progress_conn_setup, test_pool); + CuAssertIntEquals(tc, APR_SUCCESS, status); + + /* Set up the progress callback. */ + pb = apr_pcalloc(test_pool, sizeof(*pb)); + tb->user_baton = pb; + serf_context_set_progress_cb(tb->context, progress_cb, tb); + + for (i = 0 ; i < NUM_REQUESTS ; i++) { + /* Send some requests on the connections */ + handler_ctx[i].method = "GET"; + handler_ctx[i].path = "/"; + handler_ctx[i].done = FALSE; + + handler_ctx[i].acceptor = accept_response; + handler_ctx[i].acceptor_baton = NULL; + handler_ctx[i].handler = handle_response; + handler_ctx[i].req_id = i+1; + handler_ctx[i].accepted_requests = accepted_requests; + handler_ctx[i].sent_requests = sent_requests; + handler_ctx[i].handled_requests = handled_requests; + handler_ctx[i].tb = tb; + + serf_connection_request_create(tb->connection, + setup_request, + &handler_ctx[i]); + } + + while (1) { + status = test_server_run(tb->serv_ctx, 0, test_pool); + if (APR_STATUS_IS_TIMEUP(status)) + status = APR_SUCCESS; + CuAssertIntEquals(tc, APR_SUCCESS, status); + + status = serf_context_run(tb->context, 0, test_pool); + if (APR_STATUS_IS_TIMEUP(status)) + status = APR_SUCCESS; + CuAssertIntEquals(tc, APR_SUCCESS, status); + + /* Debugging purposes only! */ + serf_debug__closed_conn(tb->bkt_alloc); + + done = TRUE; + for (i = 0 ; i < NUM_REQUESTS ; i++) + if (handler_ctx[i].done == FALSE) { + done = FALSE; + break; + } + if (done) + break; + } + + /* Check that all requests were received */ + CuAssertTrue(tc, sent_requests->nelts >= NUM_REQUESTS); + CuAssertIntEquals(tc, NUM_REQUESTS, accepted_requests->nelts); + CuAssertIntEquals(tc, NUM_REQUESTS, handled_requests->nelts); + + /* Check that progress was reported. */ + CuAssertTrue(tc, pb->written > 0); + CuAssertTrue(tc, pb->read > 0); + + /* Cleanup */ + test_server_teardown(tb, test_pool); + test_teardown(test_pool); +} +#undef NUM_REQUESTS + +CuSuite *test_context(void) +{ + CuSuite *suite = CuSuiteNew(); + + SUITE_ADD_TEST(suite, test_serf_connection_request_create); + SUITE_ADD_TEST(suite, test_serf_connection_priority_request_create); + SUITE_ADD_TEST(suite, test_serf_closed_connection); + SUITE_ADD_TEST(suite, test_serf_setup_proxy); + SUITE_ADD_TEST(suite, test_keepalive_limit_one_by_one); + SUITE_ADD_TEST(suite, test_keepalive_limit_one_by_one_and_burst); + SUITE_ADD_TEST(suite, test_serf_progress_callback); + + return suite; +} diff --git a/test/test_serf.h b/test/test_serf.h new file mode 100644 index 0000000..454dfcc --- /dev/null +++ b/test/test_serf.h @@ -0,0 +1,126 @@ +/* Copyright 2002-2007 Justin Erenkrantz and Greg Stein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TEST_SERF_H +#define TEST_SERF_H + +#include "CuTest.h" + +#include <apr.h> +#include <apr_pools.h> +#include <apr_uri.h> + +#include "serf.h" +#include "server/test_server.h" + +/** These macros are provided by APR itself from version 1.3. + * Definitions are provided here for when using older versions of APR. + */ + +/** index into an apr_array_header_t */ +#ifndef APR_ARRAY_IDX +#define APR_ARRAY_IDX(ary,i,type) (((type *)(ary)->elts)[i]) +#endif + +/** easier array-pushing syntax */ +#ifndef APR_ARRAY_PUSH +#define APR_ARRAY_PUSH(ary,type) (*((type *)apr_array_push(ary))) +#endif + +/* CuTest declarations */ +CuSuite *getsuite(void); + +CuSuite *test_context(void); +CuSuite *test_buckets(void); +CuSuite *test_ssl(void); + +/* Test setup declarations */ + +#define CRLF "\r\n" + +#define CHUNKED_REQUEST(len, body)\ + "GET / HTTP/1.1" CRLF\ + "Host: localhost:12345" CRLF\ + "Transfer-Encoding: chunked" CRLF\ + CRLF\ + #len CRLF\ + body CRLF\ + "0" CRLF\ + CRLF + +#define CHUNKED_RESPONSE(len, body)\ + "HTTP/1.1 200 OK" CRLF\ + "Transfer-Encoding: chunked" CRLF\ + CRLF\ + #len CRLF\ + body CRLF\ + "0" CRLF\ + CRLF + +#define CHUNKED_EMPTY_RESPONSE\ + "HTTP/1.1 200 OK" CRLF\ + "Transfer-Encoding: chunked" CRLF\ + CRLF\ + "0" CRLF\ + CRLF + +typedef struct { + /* Pool for resource allocation. */ + apr_pool_t *pool; + + serf_context_t *context; + serf_connection_t *connection; + serf_bucket_alloc_t *bkt_alloc; + + serv_ctx_t *serv_ctx; + apr_sockaddr_t *serv_addr; + + serv_ctx_t *proxy_ctx; + apr_sockaddr_t *proxy_addr; + + /* An extra baton which can be freely used by tests. */ + void *user_baton; + +} test_baton_t; + +apr_status_t test_server_setup(test_baton_t **tb_p, + test_server_message_t *message_list, + apr_size_t message_count, + test_server_action_t *action_list, + apr_size_t action_count, + apr_int32_t options, + serf_connection_setup_t conn_setup, + apr_pool_t *pool); + +apr_status_t test_server_proxy_setup( + test_baton_t **tb_p, + test_server_message_t *serv_message_list, + apr_size_t serv_message_count, + test_server_action_t *serv_action_list, + apr_size_t serv_action_count, + test_server_message_t *proxy_message_list, + apr_size_t proxy_message_count, + test_server_action_t *proxy_action_list, + apr_size_t proxy_action_count, + apr_int32_t options, + serf_connection_setup_t conn_setup, + apr_pool_t *pool); + +apr_status_t test_server_teardown(test_baton_t *tb, apr_pool_t *pool); + +apr_pool_t *test_setup(void); +void test_teardown(apr_pool_t *test_pool); + +#endif /* TEST_SERF_H */ diff --git a/test/test_ssl.c b/test/test_ssl.c new file mode 100644 index 0000000..81b939d --- /dev/null +++ b/test/test_ssl.c @@ -0,0 +1,112 @@ +/* Copyright 2008 Justin Erenkrantz and Greg Stein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <apr.h> +#include <apr_pools.h> +#include <apr_strings.h> +#include <apr_env.h> + +#include "serf.h" +#include "serf_bucket_types.h" + +#include "test_serf.h" + +#if defined(WIN32) && defined(_DEBUG) +/* Include this file to allow running a Debug build of serf with a Release + build of OpenSSL. */ +#include <openssl/applink.c> +#endif + +/* Test setting up the openssl library. */ +static void test_ssl_init(CuTest *tc) +{ + serf_bucket_t *bkt, *stream; + serf_ssl_context_t *ssl_context; + apr_status_t status; + + apr_pool_t *test_pool = test_setup(); + serf_bucket_alloc_t *alloc = serf_bucket_allocator_create(test_pool, NULL, + NULL); + + stream = SERF_BUCKET_SIMPLE_STRING("", alloc); + + bkt = serf_bucket_ssl_decrypt_create(stream, NULL, + alloc); + ssl_context = serf_bucket_ssl_decrypt_context_get(bkt); + + bkt = serf_bucket_ssl_encrypt_create(stream, ssl_context, + alloc); + + status = serf_ssl_use_default_certificates(ssl_context); + + CuAssertIntEquals(tc, APR_SUCCESS, status); + test_teardown(test_pool); +} + +/* Test that loading a custom CA certificate file works. */ +static void test_ssl_load_cert_file(CuTest *tc) +{ + serf_ssl_certificate_t *cert = NULL; + + apr_pool_t *test_pool = test_setup(); + apr_status_t status = serf_ssl_load_cert_file(&cert, "test/serftestca.pem", + test_pool); + + CuAssertIntEquals(tc, APR_SUCCESS, status); + CuAssertPtrNotNull(tc, cert); + test_teardown(test_pool); +} + +/* Test that reading a custom CA certificate file works. */ +static void test_ssl_cert_subject(CuTest *tc) +{ + apr_hash_t *subject; + serf_ssl_certificate_t *cert = NULL; + apr_status_t status; + + apr_pool_t *test_pool = test_setup(); + + status = serf_ssl_load_cert_file(&cert, "test/serftestca.pem", test_pool); + + CuAssertIntEquals(tc, APR_SUCCESS, status); + CuAssertPtrNotNull(tc, cert); + + subject = serf_ssl_cert_subject(cert, test_pool); + CuAssertStrEquals(tc, "Test Suite", + apr_hash_get(subject, "OU", APR_HASH_KEY_STRING)); + CuAssertStrEquals(tc, "In Serf we trust, Inc.", + apr_hash_get(subject, "O", APR_HASH_KEY_STRING)); + CuAssertStrEquals(tc, "Mechelen", + apr_hash_get(subject, "L", APR_HASH_KEY_STRING)); + CuAssertStrEquals(tc, "Antwerp", + apr_hash_get(subject, "ST", APR_HASH_KEY_STRING)); + CuAssertStrEquals(tc, "BE", + apr_hash_get(subject, "C", APR_HASH_KEY_STRING)); + CuAssertStrEquals(tc, "serf@example.com", + apr_hash_get(subject, "E", APR_HASH_KEY_STRING)); + + test_teardown(test_pool); +} + +CuSuite *test_ssl(void) +{ + CuSuite *suite = CuSuiteNew(); + + SUITE_ADD_TEST(suite, test_ssl_init); + SUITE_ADD_TEST(suite, test_ssl_load_cert_file); + SUITE_ADD_TEST(suite, test_ssl_cert_subject); + + return suite; +} diff --git a/test/test_util.c b/test/test_util.c new file mode 100644 index 0000000..9496195 --- /dev/null +++ b/test/test_util.c @@ -0,0 +1,214 @@ +/* Copyright 2002-2007 Justin Erenkrantz and Greg Stein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "apr.h" +#include "apr_pools.h" +#include <stdlib.h> + +#include "serf.h" + +#include "test_serf.h" +#include "server/test_server.h" + + +/*****************************************************************************/ +/* Server setup function(s) + */ + +#define SERV_URL "http://localhost:" SERV_PORT_STR + +static apr_status_t default_server_address(apr_sockaddr_t **address, + apr_pool_t *pool) +{ + return apr_sockaddr_info_get(address, + "localhost", APR_UNSPEC, SERV_PORT, 0, + pool); +} + +static apr_status_t default_proxy_address(apr_sockaddr_t **address, + apr_pool_t *pool) +{ + return apr_sockaddr_info_get(address, + "localhost", APR_UNSPEC, PROXY_PORT, 0, + pool); +} + +/* Default implementation of a serf_connection_closed_t callback. */ +static void default_closed_connection(serf_connection_t *conn, + void *closed_baton, + apr_status_t why, + apr_pool_t *pool) +{ + if (why) { + abort(); + } +} + +/* Default implementation of a serf_connection_setup_t callback. */ +static apr_status_t default_conn_setup(apr_socket_t *skt, + serf_bucket_t **input_bkt, + serf_bucket_t **output_bkt, + void *setup_baton, + apr_pool_t *pool) +{ + test_baton_t *ctx = setup_baton; + + *input_bkt = serf_bucket_socket_create(skt, ctx->bkt_alloc); + return APR_SUCCESS; +} + + +static apr_status_t setup(test_baton_t **tb_p, + serf_connection_setup_t conn_setup, + int use_proxy, + apr_pool_t *pool) +{ + apr_status_t status; + test_baton_t *tb; + apr_uri_t url; + + tb = apr_pcalloc(pool, sizeof(*tb)); + *tb_p = tb; + + tb->pool = pool; + tb->context = serf_context_create(pool); + tb->bkt_alloc = serf_bucket_allocator_create(pool, NULL, NULL); + + status = default_server_address(&tb->serv_addr, pool); + if (status != APR_SUCCESS) + return status; + + if (use_proxy) { + status = default_proxy_address(&tb->proxy_addr, pool); + if (status != APR_SUCCESS) + return status; + + /* Configure serf to use the proxy server */ + serf_config_proxy(tb->context, tb->proxy_addr); + } + + status = apr_uri_parse(pool, SERV_URL, &url); + if (status != APR_SUCCESS) + return status; + + status = serf_connection_create2(&tb->connection, tb->context, + url, + conn_setup ? conn_setup : + default_conn_setup, + tb, + default_closed_connection, + tb, + pool); + + return status; +} + + + +apr_status_t test_server_setup(test_baton_t **tb_p, + test_server_message_t *message_list, + apr_size_t message_count, + test_server_action_t *action_list, + apr_size_t action_count, + apr_int32_t options, + serf_connection_setup_t conn_setup, + apr_pool_t *pool) +{ + apr_status_t status; + test_baton_t *tb; + + status = setup(tb_p, + conn_setup, + FALSE, + pool); + if (status != APR_SUCCESS) + return status; + + tb = *tb_p; + + /* Prepare a server. */ + status = test_start_server(&tb->serv_ctx, tb->serv_addr, + message_list, message_count, + action_list, action_count, options, pool); + + return status; +} + +apr_status_t +test_server_proxy_setup(test_baton_t **tb_p, + test_server_message_t *serv_message_list, + apr_size_t serv_message_count, + test_server_action_t *serv_action_list, + apr_size_t serv_action_count, + test_server_message_t *proxy_message_list, + apr_size_t proxy_message_count, + test_server_action_t *proxy_action_list, + apr_size_t proxy_action_count, + apr_int32_t options, + serf_connection_setup_t conn_setup, + apr_pool_t *pool) +{ + apr_status_t status; + test_baton_t *tb; + + status = setup(tb_p, + conn_setup, + TRUE, + pool); + if (status != APR_SUCCESS) + return status; + + tb = *tb_p; + + /* Prepare the server. */ + status = test_start_server(&tb->serv_ctx, tb->serv_addr, + serv_message_list, serv_message_count, + serv_action_list, serv_action_count, + options, pool); + if (status != APR_SUCCESS) + return status; + + /* Prepare the proxy. */ + status = test_start_server(&tb->proxy_ctx, tb->proxy_addr, + proxy_message_list, proxy_message_count, + proxy_action_list, proxy_action_count, + options, pool); + + return status; +} + +apr_status_t test_server_teardown(test_baton_t *tb, apr_pool_t *pool) +{ + serf_connection_close(tb->connection); + + if (tb->serv_ctx) + test_server_destroy(tb->serv_ctx, pool); + if (tb->proxy_ctx) + test_server_destroy(tb->proxy_ctx, pool); + + return APR_SUCCESS; +} + +apr_pool_t *test_setup(void) +{ + apr_pool_t *test_pool; + apr_pool_create(&test_pool, NULL); + return test_pool; +} + +void test_teardown(apr_pool_t *test_pool) +{ + apr_pool_destroy(test_pool); +} diff --git a/test/testcases/chunked-empty.response b/test/testcases/chunked-empty.response new file mode 100644 index 0000000..8794459 --- /dev/null +++ b/test/testcases/chunked-empty.response @@ -0,0 +1,11 @@ +HTTP/1.1 200 OK
+Date: Sat, 04 Sep 2004 07:57:30 GMT
+Server: Apache/2.0.49-dev
+Last-Modified: Fri, 20 Sep 2002 01:33:12 GMT
+ETag: "19f5cb-240-48f26600"
+Accept-Ranges: bytes
+Transfer-Encoding: chunked
+Content-Type: text/html; charset=ISO-8859-1
+
+0
+
diff --git a/test/testcases/chunked-trailers.response b/test/testcases/chunked-trailers.response new file mode 100644 index 0000000..8d66687 --- /dev/null +++ b/test/testcases/chunked-trailers.response @@ -0,0 +1,14 @@ +HTTP/1.1 200 OK
+Transfer-Encoding: chunked
+
+1d
+this is 1 test.
+i am a test.
+f
+this is a test.
+2
+
+
+0
+Trailer-Test: f
+
diff --git a/test/testcases/chunked.response b/test/testcases/chunked.response new file mode 100644 index 0000000..16c9d33 --- /dev/null +++ b/test/testcases/chunked.response @@ -0,0 +1,13 @@ +HTTP/1.1 200 OK
+Transfer-Encoding: chunked
+
+1d
+this is 1 test.
+i am a test.
+f
+this is a test.
+2
+
+
+0
+
diff --git a/test/testcases/deflate.response b/test/testcases/deflate.response Binary files differnew file mode 100644 index 0000000..0c277bf --- /dev/null +++ b/test/testcases/deflate.response diff --git a/test/testcases/simple.request b/test/testcases/simple.request new file mode 100644 index 0000000..faab00e --- /dev/null +++ b/test/testcases/simple.request @@ -0,0 +1 @@ +this is a test. diff --git a/test/testcases/simple.response b/test/testcases/simple.response new file mode 100644 index 0000000..aa66cc3 --- /dev/null +++ b/test/testcases/simple.response @@ -0,0 +1,32 @@ +HTTP/1.1 200 OK
+Date: Sat, 04 Sep 2004 07:57:30 GMT
+Server: Apache/2.0.49-dev
+Last-Modified: Fri, 20 Sep 2002 01:33:12 GMT
+ETag: "19f5cb-240-48f26600"
+Accept-Ranges: bytes
+Content-Length: 599
+Content-Type: text/html; charset=ISO-8859-1
+
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<head>
+<title>scotch.ics.uci.edu</title>
+<!--base href="http://scotch.ics.uci.edu/" /-->
+<link href="default.css" rel="stylesheet" type="text/css" />
+</head>
+
+<body>
+
+<p>More to come!</p>
+
+<p><a href="manual/">Apache httpd 2.0 manual</a></p>
+
+<p><a href="CA.cert.pem">Trust our CA!</a></p>
+
+<p><img src="apache_pb.gif" alt="Powered by Apache!" /></p>
+
+</body>
+
+</html>
|