--- mod_usertrack.c	2003-03-06 19:45:01.000000000 -0500
+++ mod_usertrack_2.0_fixed.c	2003-03-09 14:21:58.000000000 -0500
@@ -131,6 +131,8 @@
  * pretty unique.  We can base it on the pid, time, hostip */
 
 #define COOKIE_NAME "Apache"
+#define COOKIE_NOT_FOUND 0
+#define COOKIE_FOUND 1
 
 static void make_cookie(request_rec *r)
 {
@@ -193,35 +195,233 @@
     return;
 }
 
+
+
+static 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 = 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. */
+}
+
 static int spot_cookie(request_rec *r)
 {
     cookie_dir_rec *dcfg = ap_get_module_config(r->per_dir_config,
 						&usertrack_module);
-    const char *cookie;
-    const char *value;
+    const char *cookie_header;
+    char *found_value;
+    int cookie_was_found = 0;  /* bool: was the cookie in the cookie header? */
 
     if (!dcfg->enabled) {
         return DECLINED;
     }
 
-    if ((cookie = apr_table_get(r->headers_in,
-                                (dcfg->style == CT_COOKIE2
-                                 ? "Cookie2"
-                                 : "Cookie"))))
-        if ((value = ap_strstr_c(cookie, dcfg->cookie_name))) {
-            char *cookiebuf, *cookieend;
-
-            value += strlen(dcfg->cookie_name) + 1;  /* Skip over the '=' */
-            cookiebuf = apr_pstrdup(r->pool, value);
-            cookieend = strchr(cookiebuf, ';');
-            if (cookieend)
-                *cookieend = '\0';      /* Ignore anything after a ; */
-
+    if ((cookie_header = apr_table_get(r->headers_in, 
+		    (dcfg->style == CT_COOKIE2 
+		     ? "Cookie2" 
+		     : "Cookie")))) {
+	found_value = get_cookie_from_header(r->pool, cookie_header, dcfg->cookie_name, &cookie_was_found);
+	if (cookie_was_found && found_value != NULL) {
             /* Set the cookie in a note, for logging */
-            apr_table_setn(r->notes, "cookie", cookiebuf);
-
+            apr_table_setn(r->notes, "cookie", found_value);
             return DECLINED;    /* There's already a cookie, no new one */
-        }
+	}
+    }
     make_cookie(r);
     return OK;                  /* We set our cookie */
 }
