/* ch.c --> cookie header state machine */
#include <stdlib.h>
typedef int apr_pool_t;
typedef int apr_size_t;

char *apr_pstrndup(apr_pool_t *p, const char *pscv, apr_size_t length_of_val) {
    char *cookie_value;
    cookie_value = (char *) malloc(sizeof(char) * (length_of_val + 1));
    cookie_value = strncpy(cookie_value, pscv, length_of_val);
    cookie_value[length_of_val] = '\0';
    return cookie_value;
}

#define COOKIE_NOT_FOUND 0
#define COOKIE_FOUND 1

char *get_cookie_from_header(apr_pool_t *p, const char *cookie_header, const char *cookie_name, int *success) {
    int QUOTE = 0; /* bool */
    int cookie_names_match = 1;  /* bool; true if cookie_name is found
				    to match any cookie name we
				    read from the cookie_header */
    const char *c;  /* current char we are looking at in the cookie header */
    const char *ccc;  /* current char of the cookie_name we are comparing
		   to the current char of a cookie name in the cookie_header */
    const char *pscv;  /* possible start of cookie */
    apr_size_t length_of_val;  /* length, in char, of cookie value */
    char *cookie_value;  /* freshly allocated string to point at the found 
			    cookie value */

    c = cookie_header;

    /* It's easiest to begin by assuming we have found a matching cookie
     * name. */
    cookie_names_match = 1;

    /* If the cookie header is ridiculously long or not null terminated,
     * a denial of service attack could be staged by handing an
     * endlessly long cookie header to this while loop. I rely on Apache
     * to ensure the cookie header is null terminated and not
     * excessively long. */
    while (*c != '\0') {
	/* Look for the first non-whitespace char, which should be
	 * the first letter of a cookie name. */
	while (*c != '\0' && (*c == ' ' || *c == '\t')) { ++c; }
	if (*c == '\0') { *success = COOKIE_NOT_FOUND; return NULL; }
	if (*c == ';' || *c == ',') { 
	    /* Looks like an extra, illegal delimiter is here. Skip
	     * over it and begin searching for the start of a cookie
	     * name again. */
	    ++c;
	    continue;
	}
	if (*c == '=') {
	    /* An equal sign should not be here. This presumably means
	     * an empty cookie name (which is illegal) followed by
	     * a value. Indicate that we do not need to parse the
	     * value after this equal sign. */
	    cookie_names_match = 0;
	} else if (*c == '"') {  /* This must be a quoted cookie name. */
	    ++c;
	    if (*c == '\0') { /* Unterminated quote. */ *success = COOKIE_NOT_FOUND; return NULL; }
	    if (*c == '"') {
		/* An empty cookie name is illegal, but we can deal
		 * with this error by indicating we do not want to 
		 * parse the value. */
		cookie_names_match = 0;
	    } else {
		ccc = cookie_name;
		while (*c != '\0' && *c != '"') {
		    if (*c == *ccc) {
			/* If each char up 'til the quote matches,
			 * we keep assuming cookie_names_match should
			 * remain true. */
			++c;
			++ccc;
			continue;
		    } else {
			/* Not a match, so read to end of this cookie name
			 * and indicate we do not need to parse the value. */
			cookie_names_match = 0;
			while (*c != '\0' && *c != '"') { ++c; }
			if (*c == '\0') { *success = COOKIE_NOT_FOUND; return NULL; }
			break;
		    }
		}
	    }
	} else {  /* This must be an unquoted cookie name. */
	    ccc = cookie_name;
	    while (*c != '\0' && *c != '=' && *c != ' ' && *c != '\t' && *c != ';' && *c != ',') {
		if (*c == *ccc) {
		    /* If each char up 'til the quote matches,
		     * we keep assuming cookie_names_match should
		     * remain true. */
		    ++c;
		    ++ccc;
		    continue;
		} else {
		    cookie_names_match = 0;
		    break;
		}
	    }
	}

	/* Whether or not we have a match, we need to position ourselves
	 * on the next delimiter. */
	while (*c != '\0' && *c != '=' && *c != ';' && *c != ',') { ++c; }

	/* If cookie_names_match is still 1 (true) then
	 * we have read a cookie name from cookie_header that is a
	 * substring of cookie_name. If the next char of cookie_name
	 * is '\0', the substring is the whole string, and 
	 * we have an exact match. */
	if (cookie_names_match && *ccc == '\0') {
	    cookie_names_match = 1;
	} else {
	    cookie_names_match = 0;
	}

	/* We may have read to the end of the cookie header. */
	if (*c == '\0') {
	    if (cookie_names_match) {
		*success = COOKIE_FOUND; return NULL;
	    } else {
		*success = COOKIE_NOT_FOUND; return NULL;
	    }
	}

	/* So when we come out here, *c should be pointing at
	 * either a = or a ; or a , and nothing else. If it's a = 
	 * we have just finished reading a cookie name with
	 * a value. If it is ; or ,
	 * we have just finished reading a cookie name without
	 * a value. */
	if ( ! cookie_names_match) {  /* Read til the end of the value. */
	    if (*c == ';' || *c == ',') {
		/* If the delimiter is ; or , this
		 * cookie has no value; hence,
		 * we don't have to position
		 * our char pointer past the end of
		 * this non-existant value. */
	    } else {
		/* Advance to the first non-whitespace char, which
		 * is the first char of the cookie value. */
		while (*c != '\0' && (*c == ' ' || *c == '\t')) { ++c; }
		if (*c == '\0') { *success = COOKIE_NOT_FOUND; return NULL; }
		if (*c == ';' || *c == ',') {
		    /* A delimiter is not allowed to be here, but it's
		     * not the end of the world; just do nothing. */
		} else {
		    /* We are now at the start of a cookie value that may be
		     * quoted. */
		    if (*c == '"') {  /* This must be a quoted cookie value. */
			/* Read to the end of the quoted value. */
			while (*c != '\0' && *c != '"') { ++c; }
			if (*c == '\0') { *success = COOKIE_NOT_FOUND; return NULL; }
		    }
		    /* Now read 'til the next delimiter. */
		    while (*c != '\0' && *c != ';' && *c != ',') { ++c; }
		    if (*c == '\0') { *success = COOKIE_NOT_FOUND; return NULL; }
		}
	    }
	    /* Now we need to advance past this delimiter to get ready
	     * to start reading the next cookie name. */
	    ++c;
	    if (*c == '\0') { *success = COOKIE_NOT_FOUND; return NULL; }
	    /* Reset this to the assumption of truth. */
	    cookie_names_match = 1;
	    continue;
	} else {  /* cookie_names_match == 1 */
	    if (*c == ';' || *c == ',') {
		/* If the delimiter is ; or , this
		 * cookie is present but has no value. */
		*success = COOKIE_FOUND;
		return NULL;
	    } else {  /* There is an equal sign followed by a value. */
		/* Advance past the equal sign. */
		++c;
		/* The first non-whitespace chars is the first char
		 * of the cookie value. */
		while (*c != '\0' && (*c == ' ' || *c == '\t')) { ++c; }
		if (*c == '\0' || *c == ';' || *c == ',') {
		    /* A delimiter is not allowed to be here, but it's
		     * not the end of the world; just set an empty
		     * value. */
		    *success = COOKIE_FOUND;
		    return NULL;
		}

		/* We are now at the start of a cookie value that is either
		 * quoted or not quoted. Which is it? */
		if (*c == '"') {  /* We are dealing with a quoted cookie value. */
		    if (*(c + 1) == '\0' || *(c + 1) == '"') {
			*success = COOKIE_FOUND;
			return NULL;
		    }
		    ++c;
		    length_of_val = 0;
		    pscv = c;
		    /* Read to end of quoted string. */
		    while (*c != '\0' && *c != '"') { ++c; ++length_of_val; }
		} else {  /* We are dealing with a non-quoted cookie value. */
		    length_of_val = 0;
		    pscv = c;
		    while (*c != '\0' && *c != ';' && *c != ',' && *c != ' ' && *c != '\t') { ++c; ++length_of_val; }
		}
		/*
		cookie_value = (char *) malloc(sizeof(char) * (length_of_val + 1));
		cookie_value = strncpy(cookie_value, pscv, length_of_val);
		cookie_value[length_of_val] = '\0';
		*/
		cookie_value = apr_pstrndup(p, pscv, length_of_val);
		*success = COOKIE_FOUND;
		return cookie_value;
	    }
	    break; /* Should be superflous. */
	}
    } /* while (*c != '\0') { */

    *success = COOKIE_NOT_FOUND;
    return NULL;  /* If we made it this far, we did not find the cookie. */
}

#define TSM 127

int main(int argc, char *argv[]) {
    int success = 0; /* bool */
    int i = 0;
    int passed = 0;
    int failed = 0;
    char *cookie_name = "hello";
    char *cookie_val = NULL;
    struct test {
	char *cookie_header;
	int success;
	char *cookie_val;
    };

    struct test test_suite[TSM] = {
	/* begin cases */
	{ "hell", COOKIE_NOT_FOUND, NULL },
	{ "hello", COOKIE_FOUND, NULL },
	{ "\"hello\"", COOKIE_FOUND, NULL },
	{ "hello;", COOKIE_FOUND, NULL },
	{ "hello=OK", COOKIE_FOUND, "OK" },
	{ "hello=OK;", COOKIE_FOUND, "OK" },
	{ "\"hello\"=OK;", COOKIE_FOUND, "OK" },
	{ "hello=\"OK\";", COOKIE_FOUND, "OK" },
	{ "\"hello\"=\"OK\";", COOKIE_FOUND, "OK" },
	{ "\"hell\"", COOKIE_NOT_FOUND, NULL },
	{ "hell;", COOKIE_NOT_FOUND, NULL },
	{ "hell=OK;", COOKIE_NOT_FOUND, NULL },
	{ "\"hell\"=OK;", COOKIE_NOT_FOUND, NULL },
	{ "hell=\"OK\";", COOKIE_NOT_FOUND, NULL },
	{ "\"hell\"=\"OK\";", COOKIE_NOT_FOUND, NULL },
	/* leading space begin cases */
	{ " hell", COOKIE_NOT_FOUND, NULL },
	{ " hello", COOKIE_FOUND, NULL },
	{ " \"hello\"", COOKIE_FOUND, NULL },
	{ " hello;", COOKIE_FOUND, NULL },
	{ " hello=OK", COOKIE_FOUND, "OK" },
	{ " hello=OK;", COOKIE_FOUND, "OK" },
	{ " \"hello\"=OK;", COOKIE_FOUND, "OK" },
	{ " hello=\"OK\";", COOKIE_FOUND, "OK" },
	{ " \"hello\"=\"OK\";", COOKIE_FOUND, "OK" },
	{ " \"hell\"", COOKIE_NOT_FOUND, NULL },
	{ " hell;", COOKIE_NOT_FOUND, NULL },
	{ " hell=OK;", COOKIE_NOT_FOUND, NULL },
	{ " \"hell\"=OK;", COOKIE_NOT_FOUND, NULL },
	{ " hell=\"OK\";", COOKIE_NOT_FOUND, NULL },
	{ " \"hell\"=\"OK\";", COOKIE_NOT_FOUND, NULL },
	/* trailing space begin cases */
	{ "hell ", COOKIE_NOT_FOUND, NULL },
	{ "hello ", COOKIE_FOUND, NULL },
	{ "\"hello\" ", COOKIE_FOUND, NULL },
	{ "hello; ", COOKIE_FOUND, NULL },
	{ "hello=OK ", COOKIE_FOUND, "OK" },
	{ "hello=OK; ", COOKIE_FOUND, "OK" },
	{ "\"hello\"=OK; ", COOKIE_FOUND, "OK" },
	{ "hello=\"OK\"; ", COOKIE_FOUND, "OK" },
	{ "\"hello\"=\"OK\"; ", COOKIE_FOUND, "OK" },
	{ "\"hell\" ", COOKIE_NOT_FOUND, NULL },
	{ "hell; ", COOKIE_NOT_FOUND, NULL },
	{ "hell=OK; ", COOKIE_NOT_FOUND, NULL },
	{ "\"hell\"=OK; ", COOKIE_NOT_FOUND, NULL },
	{ "hell=\"OK\"; ", COOKIE_NOT_FOUND, NULL },
	{ "\"hell\"=\"OK\"; ", COOKIE_NOT_FOUND, NULL },
	/* leading and trailing space begin cases */
	{ " hell ", COOKIE_NOT_FOUND, NULL },
	{ " hello ", COOKIE_FOUND, NULL },
	{ " \"hello\" ", COOKIE_FOUND, NULL },
	{ " hello; ", COOKIE_FOUND, NULL },
	{ " hello=OK ", COOKIE_FOUND, "OK" },
	{ " hello=OK; ", COOKIE_FOUND, "OK" },
	{ " \"hello\"=OK; ", COOKIE_FOUND, "OK" },
	{ " hello=\"OK\"; ", COOKIE_FOUND, "OK" },
	{ " \"hello\"=\"OK\"; ", COOKIE_FOUND, "OK" },
	{ " \"hell\" ", COOKIE_NOT_FOUND, NULL },
	{ " hell; ", COOKIE_NOT_FOUND, NULL },
	{ " hell=OK; ", COOKIE_NOT_FOUND, NULL },
	{ " \"hell\"=OK; ", COOKIE_NOT_FOUND, NULL },
	{ " hell=\"OK\"; ", COOKIE_NOT_FOUND, NULL },
	{ " \"hell\"=\"OK\"; ", COOKIE_NOT_FOUND, NULL },
	/* space around equal sign */
	{ " hello = OK ", COOKIE_FOUND, "OK" },
	{ " hello = OK; ", COOKIE_FOUND, "OK" },
	{ " \"hello\" = OK; ", COOKIE_FOUND, "OK" },
	{ " hello = \"OK\"; ", COOKIE_FOUND, "OK" },
	{ " \"hello\" = \"OK\"; ", COOKIE_FOUND, "OK" },
	{ " hell = OK; ", COOKIE_NOT_FOUND, NULL },
	{ " \"hell\" = OK; ", COOKIE_NOT_FOUND, NULL },
	{ " hell = \"OK\"; ", COOKIE_NOT_FOUND, NULL },
	{ " \"hell\" = \"OK\"; ", COOKIE_NOT_FOUND, NULL },
	/* tabs everywhere */
	{ "\thello\t=\tOK\t", COOKIE_FOUND, "OK" },
	{ "\thello\t=\tOK;\t", COOKIE_FOUND, "OK" },
	{ "\t\"hello\"\t=\tOK;\t", COOKIE_FOUND, "OK" },
	{ "\thello\t=\t\"OK\";\t", COOKIE_FOUND, "OK" },
	{ "\t\"hello\"\t=\t\"OK\";\t", COOKIE_FOUND, "OK" },
	{ "\thell\t=\tOK;\t", COOKIE_NOT_FOUND, NULL },
	{ "\t\"hell\"\t=\tOK;\t", COOKIE_NOT_FOUND, NULL },
	{ "\thell\t=\t\"OK\";\t", COOKIE_NOT_FOUND, NULL },
	{ "\t\"hell\"\t=\t\"OK\";\t", COOKIE_NOT_FOUND, NULL },
	/* Totally invalid */
	{ "", COOKIE_NOT_FOUND, NULL },
	{ "\t \t", COOKIE_NOT_FOUND, NULL },
	{ "\"", COOKIE_NOT_FOUND, NULL },
	{ " this=that; foo=bar; \"", COOKIE_NOT_FOUND, NULL },
	{ ";", COOKIE_NOT_FOUND, NULL },
	{ ",", COOKIE_NOT_FOUND, NULL },
	/* delimiters inside values */
	{ " hello = OK;there; this=that ", COOKIE_FOUND, "OK" },
	{ " hello = \"OK;there\"; this=that ", COOKIE_FOUND, "OK;there" },
	{ " hello = \"OK;there; this=that ", COOKIE_FOUND, "OK;there; this=that " },
	{ " \"hello = OK,there; this=that ", COOKIE_NOT_FOUND, NULL },
	{ " hello = OK,there; this=that ", COOKIE_FOUND, "OK" },
	{ " hello = \"OK,there\"; this=that ", COOKIE_FOUND, "OK,there" },
	{ " hello = OK\"there; this=that ", COOKIE_FOUND, "OK\"there" },
	{ " hello = \"OK\"there\"; this=that ", COOKIE_FOUND, "OK" },
	{ " yo = OK\"hello; this=that ", COOKIE_NOT_FOUND, NULL },
	{ " yo = \"OK\"hello\"; this=that ", COOKIE_NOT_FOUND, NULL },
	{ " yo = OK\"hello; hello=that ", COOKIE_FOUND, "that" },
	{ " yo = \"OK\"hello\"; hello=that ", COOKIE_FOUND, "that" },
	/* starting to get nasty */
	{ "one completely invalid cookie header", COOKIE_NOT_FOUND, NULL },
	{ "one completely hello invalid cookie header", COOKIE_NOT_FOUND, NULL },
	{ "one completely hello; invalid cookie header", COOKIE_NOT_FOUND, NULL },
	{ "\"\"", COOKIE_NOT_FOUND, NULL },
	{ ";;", COOKIE_NOT_FOUND, NULL },
	{ ",,", COOKIE_NOT_FOUND, NULL },
	{ "hello=", COOKIE_FOUND, NULL },
	{ "hello= ", COOKIE_FOUND, NULL },
	{ "hello= ;", COOKIE_FOUND, NULL },
	{ "hello=;", COOKIE_FOUND, NULL },
	{ "hello=; ", COOKIE_FOUND, NULL },

	{ "me=you; \"foo bar\"=\"this that\"; hello=world", COOKIE_FOUND, "world"},
	{ "me=you; \"\"=\"this that\"; hello=world", COOKIE_FOUND, "world"},
	{ "me; \"foo bar\"=\"this that\"; hello=world", COOKIE_FOUND, "world"},
	{ "me = you; \"foo bar\"=\"this that\"; hello=world", COOKIE_FOUND, "world"},
	{ "\"me\" = you; \"foo bar\"=\"this that\"; hello=world", COOKIE_FOUND, "world"},
	{ "\"me\" ; \"foo bar\"=\"this that\"; hello=world", COOKIE_FOUND, "world"},
	{ "\"hello\" ; \"foo bar\"=\"this that\"; hello=world", COOKIE_FOUND, NULL},
	{ "hello = there; \"foo bar\"=\"this that\"; hello=world", COOKIE_FOUND, "there"},
	{ "hello = \"there\"; \"foo bar\"=\"this that\"; hello=world", COOKIE_FOUND, "there"},
	{ "hello=there; \"foo bar\"=\"this that\"; hello=world", COOKIE_FOUND, "there"},
	{ "=hello=there; \"foo bar\"=\"this that\"; hello=world", COOKIE_FOUND, "world"},
	{ "=hello=there; \"foo bar\"=\"this that\"; hello==world", COOKIE_FOUND, "=world"},
	{ "=hello=there;; \"foo bar\"=\"this that\"; ; hello==world", COOKIE_FOUND, "=world"},
	{ "hello==there; \"foo bar\"=\"this that\"; hello==world", COOKIE_FOUND, "=there"},
	{ "=hello=there;; \"foo bar\"=\"this that\"; ; = = hello==world", COOKIE_NOT_FOUND, NULL},
	{ "xhello=there; \"xhello\"=\"there\"; hellox=there; \"hellox\"=\"there\"; xhellox=there; \"xhellox\"=\"there\"", COOKIE_NOT_FOUND, NULL},
	{ "xhello = there; \"xhello\" = \"there\"; hellox = there; \"hellox\" = \"there\"; xhellox = there; \"xhellox\" = \"there\"", COOKIE_NOT_FOUND, NULL},
	{ "xhello\t=\tthere; \"xhello\"\t=\t\"there\"; hellox\t=\tthere; \"hellox\"\t=\t\"there\"; xhellox\t=\tthere; \"xhellox\"\t=\t\"there\"", COOKIE_NOT_FOUND, NULL},
	{ "this=that; foo=hello=there; bar=hello; baz=xhellox", COOKIE_NOT_FOUND, NULL},
	{ "this=that; hello=hello=there; bar=hello; baz=xhellox", COOKIE_FOUND, "hello=there"},
    };

    for (i = 0; i < TSM; ++i) {
	printf("\n");
	printf("Test %i, header <<%s>>\n", i, test_suite[i].cookie_header);
	cookie_val = get_cookie_from_header(NULL, test_suite[i].cookie_header, cookie_name, &success);
	if (success == COOKIE_NOT_FOUND) {
	    printf("Cookie not found... ");
	} else if (success == COOKIE_FOUND) {
	    printf("Cookie found. ");
	    if (cookie_val != NULL) {
		printf("Value: \"%s\"... ", cookie_val);
	    } else {
		printf("Empty value... ");
	    }
	}
	if (success != test_suite[i].success) {
	    printf("failed. Unmatched success values.\n"); 
	    ++failed;
	    if (cookie_val != NULL) free(cookie_val);
	    continue;
	}
	if (cookie_val == NULL && test_suite[i].cookie_val == NULL) {
	    printf("passed.\n");
	    ++passed;
	    continue;
	}
	if (cookie_val == NULL && test_suite[i].cookie_val != NULL) {
	    printf("failed. Not expecting null value.\n"); 
	    ++failed;
	    continue;
	} 
	if (cookie_val != NULL && test_suite[i].cookie_val == NULL) {
	    printf("failed. Expecting null value.\n"); 
	    ++failed;
	    free(cookie_val);
	    continue;
	} 
	if (strcmp(cookie_val, test_suite[i].cookie_val)) {
	    printf("failed. Unmatched cookie values.\n"); 
	    ++failed;
	    free(cookie_val);
	    continue;
	} 
	printf("passed.\n"); 
	++passed;
	if (cookie_val != NULL) free(cookie_val);
    }

    printf("%i tests passed. %i tests failed.\n", passed, failed);

    return 0;
}

