| /* Copyright 2014 Lieven Govaerts |
| * |
| * 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 "MockHTTP_private.h" |
| |
| #include <stdlib.h> |
| #include <string.h> |
| #include <ctype.h> |
| |
| #include <apr_strings.h> |
| #include <apr_lib.h> |
| |
| static char *serializeArrayOfIovecs(apr_pool_t *pool, |
| apr_array_header_t *blocks); |
| static mhResponse_t *initResponse(MockHTTP *mh); |
| |
| |
| /* private functions */ |
| |
| /* Header should be stored with their original case to use them in responses. |
| Search on header name is case-insensitive per RFC2616. */ |
| const char *getHeader(apr_table_t *hdrs, const char *hdr) |
| { |
| return apr_table_get(hdrs, hdr); |
| } |
| |
| void setHeader(apr_table_t *hdrs, const char *hdr, const char *val) |
| { |
| apr_table_add(hdrs, hdr, val); |
| } |
| |
| /* To enable calls like Assert(expected, Verify...(), ErrorMessage()), with the |
| evaluation order of the arguments not specified in C, we need the pointer to |
| where an error message will be stored before the call to Verify...(). |
| So we allocate up to ERRMSG_MAXSIZE bytes for the error message memory up |
| front and use it when needed */ |
| #define ERRMSG_MAXSIZE 65000 |
| |
| static void appendErrMessage(const MockHTTP *mh, const char *fmt, ...) |
| { |
| apr_pool_t *scratchpool; |
| apr_size_t startpos = strlen(mh->errmsg); |
| apr_size_t len; |
| const char *msg; |
| va_list argp; |
| |
| va_start(argp, fmt); |
| apr_pool_create(&scratchpool, mh->pool); |
| msg = apr_pvsprintf(scratchpool, fmt, argp); |
| va_end(argp); |
| |
| len = strlen(msg) + 1; /* include trailing \0 */ |
| len = startpos + len > ERRMSG_MAXSIZE ? ERRMSG_MAXSIZE - startpos - 1: len; |
| memcpy(mh->errmsg + startpos, msg, len); |
| |
| apr_pool_destroy(scratchpool); |
| } |
| |
| /* Define a MockHTTP context */ |
| MockHTTP *mhInit() |
| { |
| apr_pool_t *pool; |
| MockHTTP *__mh, *mh; |
| mhResponse_t *__resp; |
| |
| apr_initialize(); |
| atexit(apr_terminate); |
| |
| apr_pool_create(&pool, NULL); |
| __mh = mh = apr_pcalloc(pool, sizeof(struct MockHTTP)); |
| mh->pool = pool; |
| mh->errmsg = apr_palloc(pool, ERRMSG_MAXSIZE); |
| *mh->errmsg = '\0'; |
| mh->expectations = 0; |
| mh->verifyStats = apr_pcalloc(pool, sizeof(mhStats_t)); |
| |
| __resp = mhNewDefaultResponse(__mh); |
| mhConfigResponse(__resp, WithCode(200), WithBody("Default Response"), NULL); |
| |
| __resp = mh->defErrorResponse = initResponse(__mh); |
| mhConfigResponse(__resp, WithCode(500), |
| WithBody("Mock server error."), NULL); |
| mh->reqMatchers = apr_array_make(pool, 5, sizeof(ReqMatcherRespPair_t *)); |
| mh->incompleteReqMatchers = apr_array_make(pool, 5, |
| sizeof(ReqMatcherRespPair_t *)); |
| mh->ocspReqMatchers = apr_array_make(pool, 5, sizeof(ReqMatcherRespPair_t *)); |
| |
| return mh; |
| } |
| |
| void mhCleanup(MockHTTP *mh) |
| { |
| if (!mh) |
| return; |
| |
| if (mh->servCtx) { |
| mhStopServer(mh->servCtx); |
| } |
| |
| if (mh->proxyCtx) { |
| mhStopServer(mh->proxyCtx); |
| } |
| |
| |
| /* The MockHTTP * is also allocated from mh->pool, so this will destroy |
| the MockHTTP structure and all its allocated memory. */ |
| apr_pool_destroy(mh->pool); |
| |
| /* mh ptr is now invalid */ |
| } |
| |
| /** |
| * Runs both the proxy and the server loops. This function will block as long |
| * as there's data to read or write. |
| * |
| * reqState: NoReqsReceived if no requests were received |
| * PartialReqReceived if either proxy or server blocks on a partly |
| * read request. |
| * FullReqReceived if all requests have been read completely. |
| */ |
| static apr_status_t runServerLoop(MockHTTP *mh, loopRequestState_t *reqState) |
| { |
| apr_status_t status = APR_EGENERAL; |
| |
| *reqState = NoReqsReceived; |
| do { |
| if (mh->proxyCtx && mh->proxyCtx->threading != mhThreadSeparate) { |
| status = _mhRunServerLoop(mh->proxyCtx); |
| *reqState = mh->proxyCtx->reqState; |
| } |
| /* TODO: status? */ |
| if (mh->servCtx && mh->servCtx->threading != mhThreadSeparate) { |
| status = _mhRunServerLoop(mh->servCtx); |
| *reqState |= mh->servCtx->reqState; |
| } |
| } while (status == APR_SUCCESS); |
| |
| return status; |
| } |
| |
| mhError_t mhRunServerLoop(MockHTTP *mh) |
| { |
| loopRequestState_t dummy; |
| apr_status_t status = runServerLoop(mh, &dummy); |
| |
| if (status == MH_STATUS_WAITING) |
| return MOCKHTTP_WAITING; |
| |
| if (READ_ERROR(status) && !APR_STATUS_IS_TIMEUP(status)) |
| return MOCKHTTP_TEST_FAILED; |
| |
| return MOCKHTTP_NO_ERROR; |
| } |
| |
| mhError_t mhRunServerLoopCompleteRequests(MockHTTP *mh) |
| { |
| loopRequestState_t reqState = NoReqsReceived; |
| apr_status_t status = APR_EGENERAL; |
| apr_time_t finish_time = apr_time_now() + apr_time_from_sec(15); |
| |
| while (1) { |
| status = runServerLoop(mh, &reqState); |
| |
| if (status != APR_EAGAIN) |
| break; |
| if (apr_time_now() > finish_time) |
| break; |
| if (reqState == FullReqReceived) |
| break; |
| }; |
| |
| if (APR_STATUS_IS_EAGAIN(status)) |
| return MOCKHTTP_TIMEOUT; |
| |
| return status; |
| } |
| |
| static const builder_t NoopBuilder = { MagicKey, BuilderTypeNone }; |
| |
| const void *mhSetOnConditionThat(int condition, void *builder) |
| { |
| if (condition) |
| return builder; |
| |
| return &NoopBuilder; |
| } |
| |
| /******************************************************************************/ |
| /* Requests matchers: define criteria to match different aspects of a HTTP */ |
| /* request received by the MockHTTP server. */ |
| /******************************************************************************/ |
| |
| static mhReqMatcherBldr_t *createReqMatcherBldr(apr_pool_t *pool) |
| { |
| mhReqMatcherBldr_t *mp = apr_pcalloc(pool, sizeof(mhReqMatcherBldr_t)); |
| mp->builder.magic = MagicKey; |
| mp->builder.type = BuilderTypeReqMatcher; |
| return mp; |
| } |
| |
| /** |
| * Builder callback, checks that the response body chunks match what's expected. |
| */ |
| static bool |
| chunks_matcher(const mhReqMatcherBldr_t *mp, apr_array_header_t *chunks) |
| { |
| const char *ptr, *expected = mp->baton; |
| int i; |
| |
| ptr = expected; |
| for (i = 0 ; i < chunks->nelts; i++) { |
| struct iovec vec; |
| apr_size_t len; |
| |
| vec = APR_ARRAY_IDX(chunks, i, struct iovec); |
| /* iov_base can be incomplete, so shorter than iov_len */ |
| len = strlen(vec.iov_base); |
| if (strncmp(ptr, vec.iov_base, len) != 0) |
| return NO; |
| ptr += len; |
| } |
| |
| /* Up until now the body matches, but maybe the body is expected to be |
| longer. */ |
| if (*ptr != '\0') |
| return NO; |
| |
| return YES; |
| } |
| |
| /** |
| * Builder callback, checks that a string matches what's expected. |
| */ |
| static bool str_matcher(const mhReqMatcherBldr_t *mp, const char *actual) |
| { |
| const char *expected = mp->baton; |
| |
| if (expected == actual) |
| return YES; /* case where both are NULL, e.g. test for header not set */ |
| |
| if ((!expected && *actual == '\0') || |
| (!actual && *expected == '\0')) |
| return YES; /* "" and NULL are equal */ |
| |
| if (expected && actual && strcmp(expected, actual) == 0) |
| return YES; |
| |
| return NO; |
| } |
| |
| /** |
| * Builder callback, checks if the request url matches. |
| */ |
| static bool url_matcher(const mhReqMatcherBldr_t *mp, const mhRequest_t *req) |
| { |
| return str_matcher(mp, req->url); |
| } |
| |
| mhReqMatcherBldr_t * |
| mhMatchURLEqualTo(const MockHTTP *mh, const char *expected) |
| { |
| apr_pool_t *pool = mh->pool; |
| |
| mhReqMatcherBldr_t *mp = createReqMatcherBldr(pool); |
| mp->baton = apr_pstrdup(pool, expected); |
| mp->matcher = url_matcher; |
| mp->describe_key = "URL equal to"; |
| mp->describe_value = expected; |
| return mp; |
| } |
| |
| /** |
| * Builder callback, checks if the request url doesn't match. |
| */ |
| static bool |
| url_not_matcher(const mhReqMatcherBldr_t *mp, const mhRequest_t *req) |
| { |
| return !str_matcher(mp, req->url); |
| } |
| |
| mhReqMatcherBldr_t * |
| mhMatchURLNotEqualTo(const MockHTTP *mh, const char *expected) |
| { |
| apr_pool_t *pool = mh->pool; |
| |
| mhReqMatcherBldr_t *mp = createReqMatcherBldr(pool); |
| mp->baton = apr_pstrdup(pool, expected); |
| mp->matcher = url_not_matcher; |
| mp->describe_key = "URL not equal to"; |
| mp->describe_value = expected; |
| return mp; |
| } |
| |
| /** |
| * Builder callback, checks if the body (chunked or not chunked) matches what's |
| * expected. |
| */ |
| static bool |
| body_matcher(const mhReqMatcherBldr_t *mp, const mhRequest_t *req) |
| { |
| /* ignore chunked or not chunked */ |
| if (req->chunked == YES) |
| return chunks_matcher(mp, req->chunks); |
| else { |
| return chunks_matcher(mp, req->body); |
| } |
| } |
| |
| mhReqMatcherBldr_t * |
| mhMatchBodyEqualTo(const MockHTTP *mh, const char *expected) |
| { |
| apr_pool_t *pool = mh->pool; |
| |
| mhReqMatcherBldr_t *mp = createReqMatcherBldr(pool); |
| mp->baton = apr_pstrdup(pool, expected); |
| mp->matcher = body_matcher; |
| mp->describe_key = "Body equal to"; |
| mp->describe_value = expected; |
| return mp; |
| } |
| |
| |
| /** |
| * Builder callback, checks if the raw body matches what's expected. |
| */ |
| static bool |
| raw_body_matcher(const mhReqMatcherBldr_t *mp, const mhRequest_t *req) |
| { |
| return chunks_matcher(mp, req->body); |
| } |
| |
| mhReqMatcherBldr_t * |
| mhMatchRawBodyEqualTo(const MockHTTP *mh, const char *expected) |
| { |
| apr_pool_t *pool = mh->pool; |
| |
| mhReqMatcherBldr_t *mp = createReqMatcherBldr(pool); |
| mp->baton = apr_pstrdup(pool, expected); |
| mp->matcher = raw_body_matcher; |
| mp->describe_key = "Raw body equal to"; |
| mp->describe_value = expected; |
| return mp; |
| } |
| |
| mhReqMatcherBldr_t * |
| mhMatchIncompleteBodyEqualTo(const MockHTTP *mh, const char *expected) |
| { |
| apr_pool_t *pool = mh->pool; |
| |
| mhReqMatcherBldr_t *mp = createReqMatcherBldr(pool); |
| mp->baton = apr_pstrdup(pool, expected); |
| mp->matcher = body_matcher; |
| mp->match_incomplete = TRUE; |
| mp->describe_key = "Incomplete body equal to"; |
| mp->describe_value = expected; |
| return mp; |
| } |
| |
| |
| /** |
| * Builder callback, checks if the body is not chunked and it matches the |
| * expected body. |
| */ |
| static bool |
| body_notchunked_matcher(const mhReqMatcherBldr_t *mp, const mhRequest_t *req) |
| { |
| if (req->chunked == YES) |
| return NO; |
| return chunks_matcher(mp, req->body); |
| } |
| |
| mhReqMatcherBldr_t * |
| mhMatchBodyNotChunkedEqualTo(const MockHTTP *mh, const char *expected) |
| { |
| apr_pool_t *pool = mh->pool; |
| |
| mhReqMatcherBldr_t *mp = createReqMatcherBldr(pool); |
| mp->baton = apr_pstrdup(pool, expected); |
| mp->matcher = body_notchunked_matcher; |
| mp->describe_key = "Body not chunked equal to"; |
| mp->describe_value = expected; |
| return mp; |
| } |
| |
| |
| /** |
| * Builder callback, checks if the body is chunked and matches what's |
| * expected. |
| */ |
| static bool |
| chunked_body_matcher(const mhReqMatcherBldr_t *mp, const mhRequest_t *req) |
| { |
| if (req->chunked == NO) |
| return NO; |
| |
| return chunks_matcher(mp, req->chunks); |
| } |
| |
| mhReqMatcherBldr_t * |
| mhMatchChunkedBodyEqualTo(const MockHTTP *mh, const char *expected) |
| { |
| apr_pool_t *pool = mh->pool; |
| |
| mhReqMatcherBldr_t *mp = createReqMatcherBldr(pool); |
| mp->baton = apr_pstrdup(pool, expected); |
| mp->matcher = chunked_body_matcher; |
| mp->describe_key = "Chunked body equal to"; |
| mp->describe_value = expected; |
| return mp; |
| } |
| |
| /** |
| * Builder callback, checks if the body is chunk and each chunk matches exactly |
| * the expected chunks. |
| */ |
| static bool chunked_body_chunks_matcher(const mhReqMatcherBldr_t *mp, |
| const mhRequest_t *req) |
| { |
| const apr_array_header_t *chunks; |
| int i; |
| |
| if (req->chunked == NO) |
| return NO; |
| |
| chunks = mp->baton; |
| if (chunks->nelts != req->chunks->nelts) |
| return NO; |
| |
| for (i = 0 ; i < chunks->nelts; i++) { |
| struct iovec actual, expected; |
| |
| actual = APR_ARRAY_IDX(req->chunks, i, struct iovec); |
| expected = APR_ARRAY_IDX(chunks, i, struct iovec); |
| |
| if (actual.iov_len != expected.iov_len) |
| return NO; |
| |
| if (strncmp(expected.iov_base, actual.iov_base, actual.iov_len) != 0) |
| return NO; |
| } |
| |
| return YES; |
| } |
| |
| mhReqMatcherBldr_t * |
| mhMatchBodyChunksEqualTo(const MockHTTP *mh, ...) |
| { |
| apr_pool_t *pool = mh->pool; |
| apr_array_header_t *chunks; |
| mhReqMatcherBldr_t *mp; |
| va_list argp; |
| |
| chunks = apr_array_make(pool, 5, sizeof(struct iovec)); |
| va_start(argp, mh); |
| while (1) { |
| struct iovec vec; |
| vec.iov_base = (void *)va_arg(argp, const char *); |
| if (vec.iov_base == NULL) |
| break; |
| vec.iov_len = strlen(vec.iov_base); |
| *((struct iovec *)apr_array_push(chunks)) = vec; |
| } |
| va_end(argp); |
| |
| mp = createReqMatcherBldr(pool); |
| mp->baton = chunks; |
| mp->matcher = chunked_body_chunks_matcher; |
| mp->describe_key = "Chunked body with chunks"; |
| mp->describe_value = serializeArrayOfIovecs(pool, chunks); |
| return mp; |
| } |
| |
| |
| /** |
| * Builder callback, checks if a header's value matches the expected value. |
| * When a header isn't set its value will be NULL, so a NULL check can be used |
| * to check that the header doesn't exist. |
| */ |
| static bool |
| header_matcher(const mhReqMatcherBldr_t *mp, const mhRequest_t *req) |
| { |
| const char *actual; |
| int i, found = 0; |
| unsigned long exphash = mp->ibaton; |
| |
| /* exphash == 0 means testing that header is NOT set */ |
| if (exphash) { |
| for (i = 0; i < req->hdrHashes->nelts; i++) { |
| unsigned long acthash = APR_ARRAY_IDX(req->hdrHashes, i, unsigned long); |
| if (exphash == acthash) { |
| found = 1; |
| break; |
| } |
| } |
| if (found == 0) |
| return NO; |
| } |
| |
| actual = getHeader(req->hdrs, mp->baton2); |
| return str_matcher(mp, actual); |
| } |
| |
| mhReqMatcherBldr_t * |
| mhMatchHeaderEqualTo(const MockHTTP *mh, const char *hdr, const char *value) |
| { |
| apr_pool_t *pool = mh->pool; |
| |
| mhReqMatcherBldr_t *mp = createReqMatcherBldr(pool); |
| mp->baton = apr_pstrdup(pool, value); |
| mp->baton2 = apr_pstrdup(pool, hdr); |
| mp->ibaton = calculateHeaderHash(hdr, value); |
| mp->matcher = header_matcher; |
| mp->describe_key = "Header equal to"; |
| mp->describe_value = apr_psprintf(pool, "%s: %s", hdr, value); |
| return mp; |
| } |
| |
| /** |
| * Builder callback, checks if a header's value is not equal to the expected |
| * value. |
| * When a header isn't set its value will be NULL, so a non-NULL check can be |
| * used to check if the header exists. |
| */ |
| static bool |
| header_not_matcher(const mhReqMatcherBldr_t *mp, const mhRequest_t *req) |
| { |
| const char *actual = getHeader(req->hdrs, mp->baton2); |
| return !str_matcher(mp, actual); |
| } |
| |
| mhReqMatcherBldr_t * |
| mhMatchHeaderNotEqualTo(const MockHTTP *mh, const char *hdr, const char *value) |
| { |
| apr_pool_t *pool = mh->pool; |
| |
| mhReqMatcherBldr_t *mp = createReqMatcherBldr(pool); |
| mp->baton = apr_pstrdup(pool, value); |
| mp->baton2 = apr_pstrdup(pool, hdr); |
| mp->matcher = header_not_matcher; |
| mp->describe_key = "Header not equal to"; |
| mp->describe_value = apr_psprintf(pool, "%s: %s", hdr, value); |
| return mp; |
| } |
| |
| method_t methodToCode(const char *code) |
| { |
| char ch1, ch2; |
| ch1 = toupper(*code); |
| |
| if (ch1 == 'G' && strcasecmp(code, "GET") == 0) |
| return MethodGET; |
| if (ch1 == 'H' && strcasecmp(code, "HEAD") == 0) |
| return MethodHEAD; |
| |
| ch2 = toupper(*(code+1)); |
| if (ch1 == 'P') { |
| if (ch2 == 'O' && strcasecmp(code, "POST") == 0) |
| return MethodPOST; |
| if (ch2 == 'A' && strcasecmp(code, "PATCH") == 0) |
| return MethodPATCH; |
| if (ch2 == 'A' && strcasecmp(code, "PUT") == 0) |
| return MethodPUT; |
| if (ch2 == 'R') { |
| if (strcasecmp(code, "PROPFIND") == 0) |
| return MethodPROPFIND; |
| if (strcasecmp(code, "PROPPATCH") == 0) |
| return MethodPROPPATCH; |
| } |
| return MethodOther; |
| } |
| if (ch1 == 'O') { |
| if (ch2 == 'P' && strcasecmp(code, "OPTIONS") == 0) |
| return MethodOPTIONS; |
| if (ch2 == 'R' && strcasecmp(code, "ORDERPATCH") == 0) |
| return MethodORDERPATCH; |
| return MethodOther; |
| } |
| |
| if (ch1 == 'A' && strcasecmp(code, "ACL") == 0) |
| return MethodACL; |
| if (ch1 == 'B' && strcasecmp(code, "BASELINE-CONTROL") == 0) |
| return MethodBASELINE_CONTROL; |
| if (ch1 == 'L' && strcasecmp(code, "LABEL") == 0) |
| return MethodLABEL; |
| if (ch1 == 'D' && strcasecmp(code, "DELETE") == 0) |
| return MethodDELETE; |
| return MethodOther; |
| } |
| |
| /** |
| * Builder callback, checks if the request method matches the expected method. |
| * (case insensitive). |
| * TODO: not used at this time!! |
| */ |
| static bool method_matcher(const mhReqMatcherBldr_t *mp, const mhRequest_t *req) |
| { |
| const char *method = mp->baton; |
| method_t methodCode = mp->ibaton; |
| |
| if (methodCode != req->methodCode) { |
| return NO; |
| } else { |
| if (methodCode != MethodOther) |
| return YES; |
| if (strcasecmp(method, req->method) == 0) { |
| return YES; |
| } |
| return NO; |
| } |
| } |
| |
| mhReqMatcherBldr_t * |
| mhMatchMethodEqualTo(const MockHTTP *mh, const char *expected) |
| { |
| apr_pool_t *pool = mh->pool; |
| |
| mhReqMatcherBldr_t *mp = createReqMatcherBldr(pool); |
| mp->baton = apr_pstrdup(pool, expected); |
| mp->ibaton = methodToCode(expected); |
| mp->matcher = method_matcher; |
| mp->describe_key = "Method equal to"; |
| mp->describe_value = expected; |
| return mp; |
| } |
| |
| static bool any_matcher(const mhReqMatcherBldr_t *mp, const mhRequest_t *req) |
| { |
| return YES; |
| } |
| |
| mhReqMatcherBldr_t *mhMatchAny(const MockHTTP *mh) |
| { |
| apr_pool_t *pool = mh->pool; |
| |
| mhReqMatcherBldr_t *mp = createReqMatcherBldr(pool); |
| mp->matcher = any_matcher; |
| mp->describe_key = "Matches all"; |
| mp->describe_value = ""; |
| return mp; |
| } |
| |
| /** |
| * Takes a list of builders of type mhReqMatcherBldr_t *'s and stores them in |
| * a new request matcher. The builders will be evaluated later when a request |
| * arrives in the server. |
| */ |
| static mhRequestMatcher_t * |
| constructRequestMatcher(const MockHTTP *mh, requestType_t type, va_list argp) |
| { |
| apr_pool_t *pool = mh->pool; |
| |
| mhRequestMatcher_t *rm = apr_pcalloc(pool, sizeof(mhRequestMatcher_t)); |
| rm->pool = pool; |
| rm->type = type; |
| rm->matchers = apr_array_make(pool, 5, sizeof(mhReqMatcherBldr_t *)); |
| |
| while (1) { |
| mhReqMatcherBldr_t *rmb; |
| rmb = va_arg(argp, mhReqMatcherBldr_t *); |
| if (rmb == NULL) |
| break; |
| if (rmb->builder.type == BuilderTypeNone) |
| continue; |
| if (rmb->builder.type != BuilderTypeReqMatcher) { |
| _mhErrorUnexpectedBuilder(mh, rmb, BuilderTypeReqMatcher); |
| break; |
| } |
| *((mhReqMatcherBldr_t **)apr_array_push(rm->matchers)) = rmb; |
| } |
| return rm; |
| } |
| |
| mhRequestMatcher_t *mhGivenRequest(MockHTTP *mh, ...) |
| { |
| va_list argp; |
| mhRequestMatcher_t *rm; |
| |
| va_start(argp, mh); |
| rm = constructRequestMatcher(mh, RequestTypeHTTP, argp); |
| va_end(argp); |
| |
| return rm; |
| } |
| |
| mhRequestMatcher_t *mhGivenOCSPRequest(MockHTTP *mh, ...) |
| { |
| va_list argp; |
| mhRequestMatcher_t *rm; |
| |
| va_start(argp, mh); |
| rm = constructRequestMatcher(mh, RequestTypeOCSP, argp); |
| va_end(argp); |
| |
| return rm; |
| } |
| |
| bool |
| _mhRequestMatcherMatch(const mhRequestMatcher_t *rm, const mhRequest_t *req) |
| { |
| int i; |
| |
| if (rm->type != req->type) |
| return NO; |
| |
| for (i = 0 ; i < rm->matchers->nelts; i++) { |
| const mhReqMatcherBldr_t *mp; |
| |
| mp = APR_ARRAY_IDX(rm->matchers, i, mhReqMatcherBldr_t *); |
| if (mp->matcher(mp, req) == NO) |
| return NO; |
| } |
| |
| return YES; |
| } |
| |
| /******************************************************************************/ |
| /* Connection-level matchers: define criteria to match different aspects of a */ |
| /* HTTP or HTTPS connection. */ |
| /******************************************************************************/ |
| |
| static mhConnMatcherBldr_t *createConnMatcherBldr(apr_pool_t *pool) |
| { |
| mhConnMatcherBldr_t *mp = apr_pcalloc(pool, sizeof(mhConnMatcherBldr_t)); |
| mp->builder.magic = MagicKey; |
| mp->builder.type = BuilderTypeConnMatcher; |
| return mp; |
| } |
| |
| /* Stores a mhConnMatcherBldr_t * in the MockHTTP context */ |
| void mhGivenConnSetup(MockHTTP *mh, ...) |
| { |
| va_list argp; |
| mhConnMatcherBldr_t *cmb; |
| |
| mh->connMatchers = apr_array_make(mh->pool, 5, |
| sizeof(mhConnMatcherBldr_t *)); |
| |
| va_start(argp, mh); |
| while (1) { |
| cmb = va_arg(argp, mhConnMatcherBldr_t *); |
| if (cmb == NULL) |
| break; |
| if (cmb->builder.type == BuilderTypeNone) |
| continue; |
| if (cmb->builder.type != BuilderTypeConnMatcher) { |
| _mhErrorUnexpectedBuilder(mh, cmb, BuilderTypeConnMatcher); |
| break; |
| } |
| *((mhConnMatcherBldr_t **)apr_array_push(mh->connMatchers)) = cmb; |
| } |
| va_end(argp); |
| } |
| |
| mhConnMatcherBldr_t * |
| mhMatchClientCertCNEqualTo(const MockHTTP *mh, const char *expected) |
| { |
| apr_pool_t *pool = mh->pool; |
| |
| mhConnMatcherBldr_t *mp = createConnMatcherBldr(pool); |
| mp->baton = apr_pstrdup(pool, expected); |
| mp->connmatcher = _mhClientcertcn_matcher; |
| mp->describe_key = "Client Certificate CN equal to"; |
| mp->describe_value = expected; |
| return mp; |
| } |
| |
| mhConnMatcherBldr_t *mhMatchClientCertValid(const MockHTTP *mh) |
| { |
| apr_pool_t *pool = mh->pool; |
| |
| mhConnMatcherBldr_t *mp = createConnMatcherBldr(pool); |
| mp->connmatcher = _mhClientcert_valid_matcher; |
| mp->describe_key = "Client Certificate"; |
| mp->describe_value = "valid"; |
| return mp; |
| } |
| |
| /******************************************************************************/ |
| /* Response */ |
| /******************************************************************************/ |
| static mhResponse_t *initResponse(MockHTTP *mh) |
| { |
| apr_pool_t *pool; |
| mhResponse_t *resp; |
| |
| apr_pool_create(&pool, mh->pool); |
| |
| resp = apr_pcalloc(pool, sizeof(mhResponse_t)); |
| resp->pool = pool; |
| resp->mh = mh; |
| resp->code = 200; |
| resp->body = apr_array_make(pool, 5, sizeof(struct iovec)); |
| resp->hdrs = apr_table_make(pool, 5); |
| resp->builders = apr_array_make(pool, 5, sizeof(mhResponseBldr_t *)); |
| return resp; |
| } |
| |
| mhResponse_t *mhNewResponseForRequest(MockHTTP *mh, mhServCtx_t *ctx, |
| mhRequestMatcher_t *rm) |
| { |
| apr_array_header_t *matchers = NULL; |
| int i; |
| |
| mhResponse_t *resp = initResponse(mh); |
| |
| switch (rm->type) { |
| case RequestTypeHTTP: |
| if (ctx) |
| matchers = rm->incomplete ? ctx->incompleteReqMatchers : |
| ctx->reqMatchers; |
| else |
| matchers = rm->incomplete ? mh->incompleteReqMatchers : |
| mh->reqMatchers; |
| break; |
| case RequestTypeOCSP: |
| matchers = mh->ocspReqMatchers; |
| break; |
| } |
| |
| /* TODO: how can this not be the last element?? */ |
| for (i = matchers->nelts - 1 ; i >= 0; i--) { |
| ReqMatcherRespPair_t *pair; |
| |
| pair = APR_ARRAY_IDX(matchers, i, ReqMatcherRespPair_t *); |
| if (rm == pair->rm) { |
| pair->resp = resp; |
| break; |
| } |
| } |
| |
| return resp; |
| } |
| |
| void mhNewActionForRequest(MockHTTP *mh, mhServCtx_t *ctx, |
| mhRequestMatcher_t *rm, mhAction_t action) |
| { |
| apr_array_header_t *matchers; |
| int i; |
| |
| if (ctx) |
| matchers = rm->incomplete ? ctx->incompleteReqMatchers : ctx->reqMatchers; |
| else |
| matchers = rm->incomplete ? mh->incompleteReqMatchers : mh->reqMatchers; |
| for (i = matchers->nelts - 1 ; i >= 0; i--) { |
| ReqMatcherRespPair_t *pair; |
| |
| pair = APR_ARRAY_IDX(matchers, i, ReqMatcherRespPair_t *); |
| if (rm == pair->rm) { |
| pair->action = action; |
| break; |
| } |
| } |
| } |
| |
| mhResponse_t *mhNewDefaultResponse(MockHTTP *mh) |
| { |
| mh->defResponse = initResponse(mh); |
| return mh->defResponse; |
| } |
| |
| void mhConfigResponse(mhResponse_t *resp, ...) |
| { |
| va_list argp; |
| /* This is only needed for values that are only known when the request |
| is received, e.g. WithRequestBody. */ |
| va_start(argp, resp); |
| while (1) { |
| mhResponseBldr_t *rb; |
| rb = va_arg(argp, mhResponseBldr_t *); |
| if (rb == NULL) |
| break; |
| if (rb->builder.type == BuilderTypeNone) |
| continue; |
| if (rb->builder.type != BuilderTypeResponse) { |
| _mhErrorUnexpectedBuilder(resp->mh, rb, BuilderTypeResponse); |
| break; |
| } |
| |
| *((mhResponseBldr_t **)apr_array_push(resp->builders)) = rb; |
| } |
| va_end(argp); |
| } |
| |
| static mhResponseBldr_t *createResponseBldr(apr_pool_t *pool) |
| { |
| mhResponseBldr_t *rb = apr_pcalloc(pool, sizeof(mhResponseBldr_t)); |
| rb->builder.magic = MagicKey; |
| rb->builder.type = BuilderTypeResponse; |
| return rb; |
| } |
| |
| static bool resp_set_code(const mhResponseBldr_t *rb, mhResponse_t *resp) |
| { |
| resp->code = rb->ibaton; |
| return YES; |
| } |
| |
| mhResponseBldr_t *mhRespSetCode(mhResponse_t *resp, unsigned int code) |
| { |
| apr_pool_t *pool = resp->pool; |
| mhResponseBldr_t *rb = createResponseBldr(pool); |
| rb->respbuilder = resp_set_code; |
| rb->ibaton = code; |
| return rb; |
| } |
| |
| static bool resp_set_body(const mhResponseBldr_t *rb, mhResponse_t *resp) |
| { |
| struct iovec vec; |
| const char *body = rb->baton; |
| |
| vec.iov_base = (void *)body; |
| vec.iov_len = strlen(body); |
| *((struct iovec *)apr_array_push(resp->body)) = vec; |
| resp->bodyLen = vec.iov_len; |
| resp->chunked = NO; |
| setHeader(resp->hdrs, "Content-Length", |
| apr_itoa(resp->pool, resp->bodyLen)); |
| return YES; |
| } |
| |
| mhResponseBldr_t *mhRespSetBody(mhResponse_t *resp, const char *body) |
| { |
| apr_pool_t *pool = resp->pool; |
| mhResponseBldr_t *rb = createResponseBldr(pool); |
| rb->respbuilder = resp_set_body; |
| rb->baton = apr_pstrdup(pool, body); |
| return rb; |
| } |
| |
| |
| static bool |
| resp_set_chunked_body(const mhResponseBldr_t *rb, mhResponse_t *resp) |
| { |
| resp->chunks = rb->baton; |
| setHeader(resp->hdrs, "Transfer-Encoding", "chunked"); |
| resp->chunked = YES; |
| return YES; |
| } |
| |
| mhResponseBldr_t *mhRespSetChunkedBody(mhResponse_t *resp, ...) |
| { |
| apr_array_header_t *chunks; |
| va_list argp; |
| |
| apr_pool_t *pool = resp->pool; |
| mhResponseBldr_t *rb = createResponseBldr(pool); |
| |
| chunks = apr_array_make(resp->pool, 5, sizeof(struct iovec)); |
| va_start(argp, resp); |
| while (1) { |
| struct iovec vec; |
| vec.iov_base = (void *)va_arg(argp, const char *); |
| if (vec.iov_base == NULL) |
| break; |
| vec.iov_len = strlen(vec.iov_base); |
| *((struct iovec *)apr_array_push(chunks)) = vec; |
| } |
| va_end(argp); |
| |
| rb->baton = chunks; |
| rb->respbuilder = resp_set_chunked_body; |
| return rb; |
| } |
| |
| static bool |
| resp_add_header(const mhResponseBldr_t *rb, mhResponse_t *resp) |
| { |
| apr_hash_index_t *hi; |
| apr_hash_t *hdrs; |
| apr_pool_t *tmppool; |
| |
| apr_pool_create(&tmppool, resp->pool); |
| /* get rid of const for call to apr_hash_first */ |
| hdrs = apr_hash_copy(tmppool, rb->baton); |
| for (hi = apr_hash_first(tmppool, hdrs); hi; hi = apr_hash_next(hi)) { |
| void *val; |
| const void *key; |
| apr_ssize_t klen; |
| apr_hash_this(hi, &key, &klen, &val); |
| |
| setHeader(resp->hdrs, (const char *)key, (const char *)val); |
| } |
| apr_pool_destroy(tmppool); |
| |
| return YES; |
| } |
| |
| mhResponseBldr_t *mhRespAddHeader(mhResponse_t *resp, const char *header, |
| const char *value) |
| { |
| apr_pool_t *pool = resp->pool; |
| mhResponseBldr_t *rb = createResponseBldr(pool); |
| apr_hash_t *hdrs = apr_hash_make(resp->pool); |
| apr_hash_set(hdrs, header, APR_HASH_KEY_STRING, value); |
| rb->baton = hdrs; |
| rb->respbuilder = resp_add_header; |
| return rb; |
| } |
| |
| static bool |
| resp_set_close_conn_header(const mhResponseBldr_t *rb, mhResponse_t *resp) |
| { |
| setHeader(resp->hdrs, "Connection", "close"); |
| resp->closeConn = YES; |
| return YES; |
| } |
| |
| mhResponseBldr_t *mhRespSetConnCloseHdr(mhResponse_t *resp) |
| { |
| apr_pool_t *pool = resp->pool; |
| mhResponseBldr_t *rb = createResponseBldr(pool); |
| rb->respbuilder = resp_set_close_conn_header; |
| return rb; |
| } |
| |
| |
| static bool |
| resp_use_request_header(const mhResponseBldr_t *rb, mhResponse_t *resp) |
| { |
| mhRequest_t *req = resp->req; |
| const char *value = getHeader(req->hdrs, rb->baton); |
| if (value) |
| setHeader(resp->hdrs, rb->baton, value); |
| return YES; |
| } |
| |
| mhResponseBldr_t * |
| mhRespSetUseRequestHeader(mhResponse_t *resp, const char *header) |
| { |
| apr_pool_t *pool = resp->pool; |
| mhResponseBldr_t *rb = createResponseBldr(pool); |
| rb->respbuilder = resp_use_request_header; |
| rb->baton = header; |
| return rb; |
| } |
| |
| static bool |
| resp_use_request_body(const mhResponseBldr_t *rb, mhResponse_t *resp) |
| { |
| mhRequest_t *req = resp->req; |
| if (req->chunked) { |
| resp->chunks = req->chunks; |
| resp->chunked = YES; |
| setHeader(resp->hdrs, "Transfer-Encoding", "chunked"); |
| } else { |
| resp->body = req->body; |
| resp->bodyLen = req->bodyLen; |
| resp->chunked = NO; |
| setHeader(resp->hdrs, "Content-Length", |
| apr_itoa(resp->pool, resp->bodyLen)); |
| } |
| return YES; |
| } |
| |
| mhResponseBldr_t *mhRespSetUseRequestBody(mhResponse_t *resp) |
| { |
| apr_pool_t *pool = resp->pool; |
| mhResponseBldr_t *rb = createResponseBldr(pool); |
| rb->respbuilder = resp_use_request_body; |
| return rb; |
| } |
| |
| static bool resp_set_raw_data(const mhResponseBldr_t *rb, mhResponse_t *resp) |
| { |
| resp->raw_data = rb->baton; |
| resp->raw_data_length = rb->ibaton; |
| return YES; |
| } |
| |
| mhResponseBldr_t * |
| mhRespSetRawData(mhResponse_t *resp, const char *raw_data, size_t length) |
| { |
| apr_pool_t *pool = resp->pool; |
| mhResponseBldr_t *rb = createResponseBldr(pool); |
| rb->respbuilder = resp_set_raw_data; |
| rb->baton = apr_pstrdup(pool, raw_data); |
| rb->ibaton = length; |
| return rb; |
| } |
| |
| static bool |
| resp_set_repeat_pattern(const mhResponseBldr_t *rb, mhResponse_t *resp) |
| { |
| unsigned int i, n = rb->ibaton; |
| const char *pattern = rb->baton; |
| apr_size_t len = strlen(pattern); |
| apr_pool_t *tmppool; |
| struct iovec *vecs; |
| |
| /* TODO: the whole reponse body should be converted to buckets so that |
| we can generate the body on the fly. */ |
| apr_pool_create(&tmppool, resp->pool); |
| vecs = apr_pcalloc(tmppool, sizeof(struct iovec) * n); |
| |
| for (i = 0; i < n; i++) { |
| vecs[i].iov_base = (void *)pattern; |
| vecs[i].iov_len = len; |
| } |
| resp->raw_data = apr_pstrcatv(resp->pool, vecs, n, &resp->raw_data_length); |
| |
| return YES; |
| } |
| |
| mhResponseBldr_t *mhRespSetBodyPattern(mhResponse_t *resp, const char *pattern, |
| unsigned int n) |
| { |
| apr_pool_t *pool = resp->pool; |
| mhResponseBldr_t *rb = createResponseBldr(pool); |
| rb->respbuilder = resp_set_repeat_pattern; |
| rb->baton = apr_pstrdup(pool, pattern); |
| rb->ibaton = n; |
| return rb; |
| } |
| |
| static bool |
| resp_set_ocsp_response_status(const mhResponseBldr_t *rb, mhResponse_t *resp) |
| { |
| unsigned int status = rb->ibaton; |
| resp->ocsp_response_status = status; |
| return YES; |
| } |
| |
| mhResponseBldr_t * |
| mhRespOCSPResponseStatus(mhResponse_t *resp, mhOCSPRespnseStatus_t status) |
| { |
| apr_pool_t *pool = resp->pool; |
| mhResponseBldr_t *rb = createResponseBldr(pool); |
| rb->respbuilder = resp_set_ocsp_response_status; |
| rb->ibaton = status; |
| return rb; |
| |
| } |
| |
| void _mhBuildResponse(mhResponse_t *resp) |
| { |
| int i; |
| if (resp->built == YES) |
| return; |
| resp->built = YES; |
| for (i = 0 ; i < resp->builders->nelts; i++) { |
| mhResponseBldr_t *rb; |
| |
| rb = APR_ARRAY_IDX(resp->builders, i, mhResponseBldr_t *); |
| rb->respbuilder(rb, resp); |
| } |
| } |
| |
| /******************************************************************************/ |
| /* Expectations */ |
| /******************************************************************************/ |
| void mhExpectAllRequestsReceivedOnce(MockHTTP *mh) |
| { |
| mh->expectations |= RequestsReceivedOnce; |
| } |
| |
| void mhExpectAllRequestsReceivedInOrder(MockHTTP *mh) |
| { |
| mh->expectations |= RequestsReceivedInOrder; |
| } |
| |
| /******************************************************************************/ |
| /* Verify results */ |
| /******************************************************************************/ |
| static const char *serializeHeaders(apr_pool_t *pool, const mhRequest_t *req, |
| const char *indent) |
| { |
| const apr_table_entry_t *elts; |
| const apr_array_header_t *arr; |
| const char *hdrs = ""; |
| bool first = YES; |
| int i; |
| |
| arr = apr_table_elts(req->hdrs); |
| elts = (const apr_table_entry_t *)arr->elts; |
| |
| for (i = 0; i < arr->nelts; ++i) { |
| hdrs = apr_psprintf(pool, "%s%s%s: %s\n", hdrs, first ? "" : indent, |
| elts[i].key, elts[i].val); |
| first = NO; |
| } |
| return hdrs; |
| } |
| |
| static char *serializeArrayOfIovecs(apr_pool_t *pool, |
| apr_array_header_t *blocks) |
| { |
| int i; |
| char *str = ""; |
| for (i = 0 ; i < blocks->nelts; i++) { |
| struct iovec vec = APR_ARRAY_IDX(blocks, i, struct iovec); |
| str = apr_pstrcat(pool, str, vec.iov_base, NULL); |
| } |
| return str; |
| } |
| |
| static const char *formatBody(apr_pool_t *outpool, int indent, const char *body) |
| { |
| const char *newbody = ""; |
| char *nextkv, *line, *tmpbody; |
| apr_pool_t *tmppool; |
| |
| /* Need a copy cuz we're going to write NUL characters into the string. */ |
| apr_pool_create(&tmppool, outpool); |
| tmpbody = apr_pstrdup(tmppool, body); |
| |
| for ( ; (line = apr_strtok(tmpbody, "\n", &nextkv)) != NULL; tmpbody = NULL) |
| { |
| newbody = apr_psprintf(outpool, "%s%s\n%*s", newbody, line, |
| indent, *nextkv != '\0' ? "|" : ""); |
| } |
| apr_pool_destroy(tmppool); |
| |
| return newbody; |
| } |
| |
| static const char * |
| serializeRawBody(apr_pool_t *pool, int indent, const mhRequest_t *req) |
| { |
| char *body = serializeArrayOfIovecs(pool, req->body); |
| return formatBody(pool, indent, body); |
| } |
| |
| static const char *serializeRequest(apr_pool_t *pool, const mhRequest_t *req) |
| { |
| const char *str; |
| str = apr_psprintf(pool, " Method: %s\n" |
| " URL: %s\n" |
| " Version: HTTP/%d.%d\n" |
| " Headers: %s" |
| " Raw body size: %ld\n" |
| " %s:|%s\n", |
| req->method, req->url, |
| req->version / 10, req->version % 10, |
| serializeHeaders(pool, req, " "), |
| req->bodyLen, |
| req->chunked ? "Chunked Body" : " Body", |
| serializeRawBody(pool, 17, req)); |
| return str; |
| } |
| |
| static const char * |
| serializeRequestMatcher(apr_pool_t *pool, const mhRequestMatcher_t *rm, |
| const mhRequest_t *req) |
| { |
| const char *str = ""; |
| int i; |
| |
| for (i = 0 ; i < rm->matchers->nelts; i++) { |
| const mhReqMatcherBldr_t *mp; |
| bool matches; |
| |
| mp = APR_ARRAY_IDX(rm->matchers, i, mhReqMatcherBldr_t *); |
| matches = mp->matcher(mp, req); |
| |
| if (strstr(mp->describe_key, "body") != NULL) { |
| /* format the expected request body */ |
| str = apr_psprintf(pool, "%s%25s:|%s%s\n", str, |
| mp->describe_key, |
| formatBody(pool, 27, mp->describe_value), |
| matches ? "" : " <--- rule failed!"); |
| |
| } else { |
| str = apr_psprintf(pool, "%s%25s: %s%s\n", str, |
| mp->describe_key, mp->describe_value, |
| matches ? "" : " <--- rule failed!"); |
| } |
| } |
| return str; |
| } |
| |
| static int verifyAllRequestsReceivedInOrder(const MockHTTP *mh, |
| const mhServCtx_t *ctx) |
| { |
| int i; |
| |
| /* TODO: improve error message */ |
| if (ctx->reqsReceived->nelts > ctx->reqMatchers->nelts) { |
| appendErrMessage(mh, "More requests received than expected!\n"); |
| return NO; |
| } else if (ctx->reqsReceived->nelts < ctx->reqMatchers->nelts) { |
| appendErrMessage(mh, "Less requests received than expected!\n"); |
| return NO; |
| } |
| |
| for (i = 0; i < ctx->reqsReceived->nelts; i++) |
| { |
| const ReqMatcherRespPair_t *pair; |
| const mhRequest_t *req; |
| |
| pair = APR_ARRAY_IDX(ctx->reqMatchers, i, ReqMatcherRespPair_t *); |
| req = APR_ARRAY_IDX(ctx->reqsReceived, i, mhRequest_t *); |
| |
| if (_mhRequestMatcherMatch(pair->rm, req) == NO) { |
| apr_pool_t *tmppool; |
| apr_pool_create(&tmppool, mh->pool); |
| appendErrMessage(mh, "ERROR: Request wasn't expected!\n"); |
| appendErrMessage(mh, "=================================\n"); |
| appendErrMessage(mh, "Expected request with:\n"); |
| appendErrMessage(mh, serializeRequestMatcher(tmppool, pair->rm, req)); |
| appendErrMessage(mh, "---------------------------------\n"); |
| appendErrMessage(mh, "Actual request:\n"); |
| appendErrMessage(mh, serializeRequest(tmppool, req)); |
| appendErrMessage(mh, "=================================\n"); |
| apr_pool_destroy(tmppool); |
| return NO; |
| } |
| } |
| return YES; |
| } |
| |
| int mhVerifyAllRequestsReceivedInOrder(const MockHTTP *mh) |
| { |
| bool result = YES; |
| |
| if (mh->servCtx) |
| result &= verifyAllRequestsReceivedInOrder(mh, mh->servCtx); |
| if (mh->proxyCtx) |
| result &= verifyAllRequestsReceivedInOrder(mh, mh->proxyCtx); |
| return result; |
| } |
| |
| static bool |
| isArrayElement(apr_array_header_t *ary, const ReqMatcherRespPair_t *element) |
| { |
| int i; |
| for (i = 0; i < ary->nelts; i++) { |
| const ReqMatcherRespPair_t *pair; |
| pair = APR_ARRAY_IDX(ary, i, ReqMatcherRespPair_t *); |
| if (pair == element) |
| return YES; |
| } |
| return NO; |
| } |
| |
| static int verifyAllRequestsReceived(const MockHTTP *mh, const mhServCtx_t *ctx, |
| bool breakOnNotOnce) |
| { |
| int i; |
| apr_array_header_t *used; |
| apr_pool_t *pool; |
| bool result = YES; |
| |
| /* TODO: improve error message */ |
| if (breakOnNotOnce && ctx->reqsReceived->nelts > ctx->reqMatchers->nelts) { |
| appendErrMessage(mh, "More requests received than expected!\n"); |
| return NO; |
| } else if (ctx->reqsReceived->nelts < ctx->reqMatchers->nelts) { |
| appendErrMessage(mh, "Less requests received than expected!\n"); |
| return NO; |
| } |
| |
| apr_pool_create(&pool, mh->pool); |
| used = apr_array_make(mh->pool, ctx->reqsReceived->nelts, |
| sizeof(ReqMatcherRespPair_t *));; |
| |
| for (i = 0; i < ctx->reqsReceived->nelts; i++) |
| { |
| mhRequest_t *req = APR_ARRAY_IDX(ctx->reqsReceived, i, mhRequest_t *); |
| int j; |
| bool matched = NO; |
| |
| for (j = 0 ; j < ctx->reqMatchers->nelts; j++) { |
| const ReqMatcherRespPair_t *pair; |
| |
| pair = APR_ARRAY_IDX(ctx->reqMatchers, j, ReqMatcherRespPair_t *); |
| |
| if (breakOnNotOnce && isArrayElement(used, pair)) |
| continue; /* skip this match if request matched before */ |
| |
| if (_mhRequestMatcherMatch(pair->rm, req) == YES) { |
| *((const ReqMatcherRespPair_t **)apr_array_push(used)) = pair; |
| matched = YES; |
| break; |
| } |
| } |
| |
| if (matched == NO) { |
| apr_pool_t *tmppool; |
| apr_pool_create(&tmppool, mh->pool); |
| appendErrMessage(mh, "ERROR: No rule matched this request!\n"); |
| appendErrMessage(mh, "====================================\n"); |
| /* log all rules (yes this can be a long list) */ |
| for (j = 0 ; j < ctx->reqMatchers->nelts; j++) { |
| const ReqMatcherRespPair_t *pair; |
| |
| pair = APR_ARRAY_IDX(ctx->reqMatchers, j, ReqMatcherRespPair_t *); |
| |
| if (breakOnNotOnce && isArrayElement(used, pair)) |
| continue; /* skip this match if request matched before */ |
| appendErrMessage(mh, "Expected request(s) with:\n"); |
| appendErrMessage(mh, serializeRequestMatcher(tmppool, pair->rm, req)); |
| if (j + 1 < ctx->reqMatchers->nelts) |
| appendErrMessage(mh, " ------------------------\n"); |
| } |
| appendErrMessage(mh, "---------------------------------\n"); |
| appendErrMessage(mh, "Actual request:\n"); |
| appendErrMessage(mh, serializeRequest(tmppool, req)); |
| appendErrMessage(mh, "=================================\n"); |
| apr_pool_destroy(tmppool); |
| |
| result = NO; |
| break; |
| } |
| } |
| |
| apr_pool_destroy(pool); |
| |
| return result; |
| } |
| |
| int mhVerifyAllRequestsReceived(const MockHTTP *mh) |
| { |
| bool result = YES; |
| |
| if (mh->servCtx) |
| result &= verifyAllRequestsReceived(mh, mh->servCtx, NO); |
| if (mh->proxyCtx) |
| result &= verifyAllRequestsReceived(mh, mh->proxyCtx, NO); |
| return result; |
| } |
| |
| int mhVerifyAllRequestsReceivedOnce(const MockHTTP *mh) |
| { |
| bool result = YES; |
| |
| if (mh->servCtx) |
| result &= verifyAllRequestsReceived(mh, mh->servCtx, YES); |
| if (mh->proxyCtx) |
| result &= verifyAllRequestsReceived(mh, mh->proxyCtx, YES); |
| return result; |
| } |
| |
| const char *mhGetLastErrorString(const MockHTTP *mh) |
| { |
| return mh->errmsg; |
| } |
| |
| mhStats_t *mhVerifyStatistics(const MockHTTP *mh) |
| { |
| return mh->verifyStats; |
| } |
| |
| |
| |
| int mhVerifyAllExpectationsOk(const MockHTTP *mh) |
| { |
| if (mh->expectations & RequestsReceivedInOrder) |
| return mhVerifyAllRequestsReceivedInOrder(mh); |
| if (mh->expectations & RequestsReceivedOnce) |
| return mhVerifyAllRequestsReceivedOnce(mh); |
| |
| /* No expectations set. Consider this an error to avoid false positives */ |
| return NO; |
| } |
| |
| |
| int mhVerifyConnectionSetupOk(const MockHTTP *mh) |
| { |
| int i; |
| bool result = NO; |
| apr_pool_t *match_pool; |
| apr_array_header_t *clients = mh->servCtx->clients; |
| |
| apr_pool_create(&match_pool, mh->pool); |
| for (i = 0; i < clients->nelts; i++) { |
| _mhClientCtx_t *cctx = APR_ARRAY_IDX(clients, i, _mhClientCtx_t *); |
| |
| for (i = 0 ; i < mh->connMatchers->nelts; i++) { |
| const mhConnMatcherBldr_t *cmb; |
| |
| cmb = APR_ARRAY_IDX(mh->connMatchers, i, mhConnMatcherBldr_t *); |
| if (cmb->connmatcher(cmb, cctx) == NO) { |
| result = NO; |
| goto cleanup; |
| } |
| } |
| } |
| result = YES; |
| |
| cleanup: |
| apr_pool_destroy(match_pool); |
| |
| return result; |
| } |
| |
| static const char *buildertype_to_string(builderType_t type) |
| { |
| switch (type) { |
| case BuilderTypeReqMatcher: |
| return "Request Matcher"; |
| case BuilderTypeConnMatcher: |
| return "Connection Matcher"; |
| case BuilderTypeResponse: |
| return "Response Builder"; |
| case BuilderTypeServerSetup: |
| return "Server Setup"; |
| default: |
| break; |
| } |
| return "<unknown type>"; |
| } |
| |
| void _mhErrorUnexpectedBuilder(const MockHTTP *mh, void *actual, |
| builderType_t expected) |
| { |
| builder_t *builder = actual; |
| appendErrMessage(mh, "A builder of type %s was provided, where a builder of " |
| " type %s was expected!", |
| buildertype_to_string(builder->type), |
| buildertype_to_string(expected)); |
| } |
| |
| static void log_time(void) |
| { |
| apr_time_exp_t tm; |
| |
| apr_time_exp_lt(&tm, apr_time_now()); |
| fprintf(stderr, "%d-%02d-%02dT%02d:%02d:%02d.%06d%+03d ", |
| 1900 + tm.tm_year, 1 + tm.tm_mon, tm.tm_mday, |
| tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_usec, |
| tm.tm_gmtoff/3600); |
| } |
| |
| void _mhLog(int verbose_flag, apr_socket_t *skt, const char *fmt, ...) |
| { |
| va_list argp; |
| |
| if (verbose_flag) { |
| log_time(); |
| |
| if (skt) { |
| apr_sockaddr_t *sa; |
| apr_port_t lp = 0, rp = 0; |
| |
| /* Log client (remote) and server (local) port */ |
| if (apr_socket_addr_get(&sa, APR_LOCAL, skt) == APR_SUCCESS) |
| lp = sa->port; |
| if (apr_socket_addr_get(&sa, APR_REMOTE, skt) == APR_SUCCESS) |
| rp = sa->port; |
| fprintf(stderr, "[cp:%u sp:%u] ", rp, lp); |
| } |
| |
| va_start(argp, fmt); |
| vfprintf(stderr, fmt, argp); |
| va_end(argp); |
| } |
| } |