#include bool parse_query(void *qbuf, size_t length, bool is_url, bool is_cookie, HDF **hdf, struct evkeyvalq *keyval) { char *query = NULL, *c = NULL, *end = NULL; char *key = NULL, *value = NULL, *key_start = NULL, *value_start = NULL; char unknown_buf[11]; uint32_t unknowns = 0; bool save = false; enum states_t { STATE_0_0 = 0, /* beginning */ STATE_0_1, /* beg -> key */ STATE_0_2, /* beg -> eq */ STATE_0_3, /* beg -> amp */ STATE_0_5, /* beg -> end */ STATE_1_1, /* key -> key */ STATE_1_2, /* key -> eq */ STATE_1_3, /* key -> amp */ STATE_1_5, /* key -> NUL */ STATE_2_3, /* eq -> amp */ STATE_2_4, /* eq -> value */ STATE_2_5, /* eq -> nul */ STATE_3_1, /* amp -> key */ STATE_3_2, /* amp -> eq */ STATE_3_3, /* amp -> amp */ STATE_3_5, /* amp -> nul */ STATE_4_3, /* val -> amp */ STATE_4_4, /* val -> val */ STATE_4_5, /* val -> nul */ STATE_5_5, /* end (nul, hash)*/ } state = STATE_0_0; /* Done! */ if (length == 0) return true; assert(qbuf); if (hdf != NULL) { hdf_init(hdf); if (!*hdf) return false; } else if (keyval == NULL) { // no valid destination set. return false; } query = malloc(sizeof(char[length+1])); if (!query) { hdf_destroy(hdf); return false; } strncpy(query, qbuf, length); end = query + length; *end = '\0'; c = query; while (state != STATE_5_5) { /* evhttp_decode_uri doesn't clean up +s */ if (c < end) { if (*c == '+') { *c = ' '; /* # is the same as a \0. remap it. * RFC 3986 - of course this impl is a bit more laid back. */ } else if (*c == '#' && is_url) { *c = '\0'; } else if (*c == ':' && is_cookie) { /* : is the separator for cookie hashes */ *c = '&'; } } switch(state) { case STATE_0_0: switch (*c) { case '=': state = STATE_0_2; break; case '&': state = STATE_0_3; break; case '\0': state = STATE_0_5; break; default: state = STATE_0_1; key_start = c; } break; case STATE_0_1: /* beg -> key */ switch (*c) { case '=': state = STATE_1_2; *c = '\0'; break; case '&': state = STATE_1_3; *c = '\0'; save = true; break; case '\0': state = STATE_1_5; save = true; break; default: state = STATE_1_1; } if (save) { key = evhttp_decode_uri(key_start); if (hdf) hdf_set_value(*hdf, key, ""); if (keyval) evhttp_add_header(keyval, key, ""); free(key); key = key_start = NULL; save = false; } break; case STATE_0_2: /* beg -> eq */ switch (*c) { case '=': /* values always start after the first = */ state = STATE_2_4; value_start = c; break; case '&': state = STATE_2_3; break; case '\0': state = STATE_2_5; break; default: state = STATE_2_4; value_start = c; } break; case STATE_0_3: /* beg -> amp */ switch (*c) { case '=': state = STATE_3_2; break; case '&': state = STATE_3_3; break; case '\0': state = STATE_3_5; break; default: state = STATE_3_1; key_start = c; } break; case STATE_0_5: /* beg -> end */ /* terminal case - absolutely do not deref c. */ state = STATE_5_5; break; case STATE_1_1: /* key -> key */ switch (*c) { case '=': state = STATE_1_2; *c = '\0'; break; case '&': state = STATE_1_3; *c = '\0'; save = true; break; case '\0': state = STATE_1_5; save = true; break; default: state = STATE_1_1; } if (save) { key = evhttp_decode_uri(key_start); if (hdf) hdf_set_value(*hdf, key, ""); if (keyval) evhttp_add_header(keyval, key, ""); free(key); key = key_start = NULL; save = false; } break; case STATE_1_2: /* key -> eq */ switch (*c) { case '=': /* values always start after the first equals */ state = STATE_2_4; value_start = c; break; case '&': state = STATE_2_3; save = true; break; case '\0': state = STATE_2_5; save = true; break; default: state = STATE_2_4; value_start = c; } if (save) { key = evhttp_decode_uri(key_start); if (hdf) hdf_set_value(*hdf, key, ""); if (keyval) evhttp_add_header(keyval, key, ""); free(key); key = key_start = NULL; save = false; }; break; case STATE_1_3: /* key -> amp */ switch (*c) { case '=': state = STATE_3_2; break; case '&': state = STATE_3_3; break; case '\0': state = STATE_3_5; break; default: state = STATE_3_1; key_start = c; } break; case STATE_1_5: /* key -> NUL */ state = STATE_5_5; break; case STATE_2_3: /* eq -> amp */ switch (*c) { case '=': state = STATE_3_2; break; case '&': state = STATE_3_3; save = true; break; case '\0': state = STATE_3_5; save = true; break; default: state = STATE_3_1; key_start = c; } if (save && key_start) { key = evhttp_decode_uri(key_start); if (hdf) hdf_set_value(*hdf, key, ""); if (keyval) evhttp_add_header(keyval, key, ""); free(key); key = key_start = NULL; save = false; } break; case STATE_2_4: /* eq -> value */ switch (*c) { case '=': /* Be generous: include trailing equals in values */ state = STATE_4_4; break; case '&': state = STATE_4_3; *c = '\0'; save = true; break; case '\0': state = STATE_4_5; save = true; break; default: state = STATE_4_4; break; } if (save) { // One character can't be uri decoded so skip it. // This will allow %&, %=, and %\0s to slip through. value = value_start; if (key_start != NULL) key = evhttp_decode_uri(key_start); if (key) { if (hdf) hdf_set_value(*hdf, key, value); if (keyval) evhttp_add_header(keyval, key, value); free(key); } else { snprintf(unknown_buf, sizeof(unknown_buf), "%u", unknowns++); if (hdf) hdf_set_value(*hdf, unknown_buf, value); if (keyval) evhttp_add_header(keyval, unknown_buf, value); } key = key_start = NULL; value = value_start = NULL; save = false; } break; case STATE_2_5: /* eq -> nul */ state = STATE_5_5; /* save off the key if there is one */ if (key_start) { key = evhttp_decode_uri(key_start); if (hdf) hdf_set_value(*hdf, key, ""); if (keyval) evhttp_add_header(keyval, key, ""); free(key); key = key_start = NULL; save = false; } break; case STATE_3_1: /* amp -> key */ switch (*c) { case '=': state = STATE_1_2; *c = '\0'; break; case '&': state = STATE_1_3; *c = '\0'; save = true; break; case '\0': state = STATE_1_5; save = true; break; default: state = STATE_1_1; break; } if (save) { key = evhttp_decode_uri(key_start); if (hdf) hdf_set_value(*hdf, key, ""); if (keyval) evhttp_add_header(keyval, key, ""); free(key); key = key_start = NULL; save = false; } break; case STATE_3_2: /* amp -> eq */ switch (*c) { case '=': /* Values always start after the first equals */ state = STATE_2_4; value_start = c; break; case '&': state = STATE_2_3; *c = '\0'; save = true; break; case '\0': state = STATE_2_5; save = true; break; default: state = STATE_2_4; value_start = c; break; } break; case STATE_3_3: /* amp -> amp */ switch (*c) { case '=': state = STATE_3_2; break; case '&': state = STATE_3_3; break; case '\0': state = STATE_3_5; break; default: state = STATE_3_1; key_start = c; break; } break; case STATE_3_5: /* amp -> nul */ state = STATE_5_5; break; case STATE_4_3: /* val -> amp */ switch (*c) { case '=': state = STATE_3_2; break; case '&': state = STATE_3_3; break; case '\0': state = STATE_3_5; break; default: state = STATE_3_1; key_start = c; break; } break; case STATE_4_4: /* val -> val */ switch (*c) { case '=': /* Be generous: include trailing equals */ state = STATE_4_4; break; case '&': state = STATE_4_3; save = true; *c = '\0'; break; case '\0': state = STATE_4_5; save = true; break; default: state = STATE_4_4; break; } if (save) { value = evhttp_decode_uri(value_start); if (key_start != NULL) key = evhttp_decode_uri(key_start); if (key) { if (hdf) hdf_set_value(*hdf, key, value); if (keyval) evhttp_add_header(keyval, key, value); free(key); } else { snprintf(unknown_buf, sizeof(unknown_buf), "%u", unknowns); if (hdf) hdf_set_value(*hdf, unknown_buf, value); if (keyval) evhttp_add_header(keyval, unknown_buf, value); } free(value); key = key_start = NULL; value = value_start = NULL; save = false; } break; case STATE_4_5: /* val -> nul */ state = STATE_5_5; break; case STATE_5_5: /* end */ break; } ++c; } free(query); return true; }