
#include <lighttpd/base.h>

static void _http_header_free(gpointer p) {
	liHttpHeader *h = (liHttpHeader*) p;
	g_string_free(h->data, TRUE);
	g_slice_free(liHttpHeader, h);
}

/* remove folding */
static void _http_header_sanitize(liHttpHeader *h) {
	guint i, j, len = h->data->len;
	gboolean folding = FALSE;
	char *str = h->data->str;
	for (i = h->keylen + 2; i < len; ++i) {
		switch (str[i]) {
		case '\r':
		case '\n':
			goto cleanup;
		default:
			break;
		}
	}
	return;

cleanup:
	for (j = i; i < len; ++i) {
		switch (str[i]) {
		case '\r':
		case '\n':
			folding = TRUE;
			break;
		case ' ':
		case '\t':
			if (!folding) str[j++] = str[i];
			break;
		default:
			if (folding) {
				str[j++] = ' ';
				folding = FALSE;
			}
			str[j++] = str[i];
			break;
		}
	}
	g_string_truncate(h->data, j);
}

static liHttpHeader* _http_header_new(const gchar *key, size_t keylen, const gchar *val, size_t valuelen) {
	liHttpHeader *h = g_slice_new0(liHttpHeader);
	gchar *s;
	h->data = g_string_sized_new(keylen + valuelen + 2);
	g_string_set_size(h->data, keylen + valuelen + 2);
	h->keylen = keylen;
	s = h->data->str;
	memcpy(s, key, keylen);
	s += keylen;
	memcpy(s, ": ", 2);
	s += 2;
	memcpy(s, val, valuelen);
	_http_header_sanitize(h);
	return h;
}

static void _header_queue_free(gpointer data, gpointer userdata) {
	UNUSED(userdata);
	_http_header_free((liHttpHeader*) data);
}

liHttpHeaders* li_http_headers_new(void) {
	liHttpHeaders* headers = g_slice_new0(liHttpHeaders);
	g_queue_init(&headers->entries);
	return headers;
}

void li_http_headers_reset(liHttpHeaders* headers) {
	g_queue_foreach(&headers->entries, _header_queue_free, NULL);
	g_queue_clear(&headers->entries);
}

void li_http_headers_free(liHttpHeaders* headers) {
	if (!headers) return;
	g_queue_foreach(&headers->entries, _header_queue_free, NULL);
	g_queue_clear(&headers->entries);
	g_slice_free(liHttpHeaders, headers);
}

/** just insert normal header, allow duplicates */
void li_http_header_insert(liHttpHeaders *headers, const gchar *key, size_t keylen, const gchar *val, size_t valuelen) {
	liHttpHeader *h = _http_header_new(key, keylen, val, valuelen);
	g_queue_push_tail(&headers->entries, h);
}

GList* li_http_header_find_first(liHttpHeaders *headers, const gchar *key, size_t keylen) {
	liHttpHeader *h;
	GList *l;

	for (l = g_queue_peek_head_link(&headers->entries); l; l = g_list_next(l)) {
		h = (liHttpHeader*) l->data;
		if (h->keylen == keylen && 0 == g_ascii_strncasecmp(key, h->data->str, keylen)) return l;
	}
	return NULL;
}

GList* li_http_header_find_next(GList *l, const gchar *key, size_t keylen) {
	liHttpHeader *h;

	for (l = g_list_next(l); l; l = g_list_next(l)) {
		h = (liHttpHeader*) l->data;
		if (h->keylen == keylen && 0 == g_ascii_strncasecmp(key, h->data->str, keylen)) return l;
	}
	return NULL;
}

GList* li_http_header_find_last(liHttpHeaders *headers, const gchar *key, size_t keylen) {
	liHttpHeader *h;
	GList *l;

	for (l = g_queue_peek_tail_link(&headers->entries); l; l = g_list_previous(l)) {
		h = (liHttpHeader*) l->data;
		if (h->keylen == keylen && 0 == g_ascii_strncasecmp(key, h->data->str, keylen)) return l;
	}
	return NULL;
}

/** If header does not exist, just insert normal header. If it exists, append (", %s", value) to the last inserted one */
void li_http_header_append(liHttpHeaders *headers, const gchar *key, size_t keylen, const gchar *val, size_t valuelen) {
	GList *l;
	liHttpHeader *h;

	l = li_http_header_find_last(headers, key, keylen);
	if (NULL == l) {
		li_http_header_insert(headers, key, keylen, val, valuelen);
	} else {
		gsize oldlen;
		gchar *s;
		h = (liHttpHeader*) l->data;
		oldlen = h->data->len;
		g_string_set_size(h->data, oldlen + 2 + valuelen);
		s = h->data->str + oldlen;
		memcpy(s, ", ", 2);
		memcpy(s+2, val, valuelen);
	}
}

/** If header does not exist, just insert normal header. If it exists, overwrite the last occurrence */
void li_http_header_overwrite(liHttpHeaders *headers, const gchar *key, size_t keylen, const gchar *val, size_t valuelen) {
	GList *l;
	liHttpHeader *h;

	l = li_http_header_find_last(headers, key, keylen);
	if (NULL == l) {
		li_http_header_insert(headers, key, keylen, val, valuelen);
	} else {
		h = (liHttpHeader*) l->data;
		g_string_set_size(h->data, keylen + 2 + valuelen);
		/* only overwrite value */
		memcpy(h->data->str + keylen + 2, val, valuelen);
	}
}

void li_http_header_remove_link(liHttpHeaders *headers, GList *l) {
	_http_header_free(l->data);
	g_queue_delete_link(&headers->entries, l);
}

gboolean li_http_header_remove(liHttpHeaders *headers, const gchar *key, size_t keylen) {
	GList *l, *lp = NULL;
	gboolean res = FALSE;

	for (l = li_http_header_find_first(headers, key, keylen); l; l = li_http_header_find_next(l, key, keylen)) {
		if (lp) {
			li_http_header_remove_link(headers, lp);
			res = TRUE;
			lp = NULL;
		}
		lp = l;
	}
	if (lp) {
		li_http_header_remove_link(headers, lp);
		res = TRUE;
		lp = NULL;
	}
	return res;
}

liHttpHeader* li_http_header_lookup(liHttpHeaders *headers, const gchar *key, size_t keylen) {
	GList *l;

	l = li_http_header_find_last(headers, key, keylen);
	return NULL == l ? NULL : (liHttpHeader*) l->data;
}

gboolean li_http_header_is(liHttpHeaders *headers, const gchar *key, size_t keylen, const gchar *val, size_t valuelen) {
	GList *l;
	UNUSED(valuelen);

	for (l = li_http_header_find_first(headers, key, keylen); l; l = li_http_header_find_next(l, key, keylen)) {
		liHttpHeader *h = (liHttpHeader*) l->data;
		if (h->data->len - (h->keylen + 2) != valuelen) continue;
		if (0 == g_ascii_strcasecmp( &h->data->str[h->keylen+2], val )) return TRUE;
	}
	return FALSE;
}

void li_http_header_get_all(GString *dest, liHttpHeaders *headers, const gchar *key, size_t keylen) {
	GList *l;
	g_string_truncate(dest, 0);

	for (l = li_http_header_find_first(headers, key, keylen); l; l = li_http_header_find_next(l, key, keylen)) {
		liHttpHeader *h = (liHttpHeader*) l->data;
		if (dest->len) li_g_string_append_len(dest, CONST_STR_LEN(", "));
		li_g_string_append_len(dest, &h->data->str[h->keylen+2], h->data->len - (h->keylen + 2));
	}
}

void li_http_header_tokenizer_start(liHttpHeaderTokenizer *tokenizer, liHttpHeaders *headers, const gchar *key, size_t keylen) {
	if (NULL != (tokenizer->cur = li_http_header_find_first(headers, key, keylen))) {
		liHttpHeader *h = (liHttpHeader*) tokenizer->cur->data;
		tokenizer->pos = h->keylen + 2;
	} else {
		tokenizer->pos = 0;
	}
}

gboolean li_http_header_tokenizer_next(liHttpHeaderTokenizer *tokenizer, GString *token) {
	liHttpHeader *h;
	guint len;
	guint pos = tokenizer->pos;
	gchar *str;

	g_string_truncate(token, 0);

	if (NULL == tokenizer->cur) return FALSE;
	h = (liHttpHeader*) tokenizer->cur->data;
	len = h->data->len;
	str = h->data->str;

	for (;;++pos) {
		while (pos >= len) {
			if (token->len > 0) {
				tokenizer->pos = pos;
				return TRUE;
			}
			if (NULL != (tokenizer->cur = li_http_header_find_next(tokenizer->cur, LI_HEADER_KEY_LEN(h)))) {
				h = (liHttpHeader*) tokenizer->cur->data;
				pos = tokenizer->pos = h->keylen + 2;
				len = h->data->len;
				str = h->data->str;
			} else {
				tokenizer->pos = 0;
				return FALSE;
			}
		}

		switch (str[pos]) {
		case '"':
			++pos;
			if (token->len > 0) return FALSE; /* either the complete token is quoted or nothing */
			goto quoted;
		case ' ':
		case ',':
			if (token->len == 0) continue;
			tokenizer->pos = pos+1;
			return TRUE;
		case '\\':
			++pos;
			if (pos >= len) return FALSE; /* no character after backslash */
			/* append whatever comes */
			HEDLEY_FALL_THROUGH;
		default:
			g_string_append_c(token, str[pos]);
			break;
		}
	}

quoted:
	for (; pos < len; ++pos) {
		switch (str[pos]) {
		case '"':
			++pos;
			tokenizer->pos = pos;
			return TRUE;
		case '\\':
			++pos;
			if (pos >= len) return FALSE; /* no character after backslash */
			/* append whatever comes */
			HEDLEY_FALL_THROUGH;
		default:
			g_string_append_c(token, str[pos]);
			break;
		}
	}
	return FALSE; /* no terminating quote found */
}
