+ result = session->result_func(&session->result, session->user_data);
+
+ debug(session->web, "[result function] %s",
+ result == TRUE ? "continue" : "stop");
+}
+
+static inline void call_route_func(struct web_session *session)
+{
+ if (session->route_func != NULL)
+ session->route_func(session->address, session->addr->ai_family,
+ session->web->index, session->user_data);
+}
+
+static gboolean process_send_buffer(struct web_session *session)
+{
+ GString *buf;
+ gsize count, bytes_written;
+ GIOStatus status;
+
+ if (session == NULL)
+ return FALSE;
+
+ buf = session->send_buffer;
+ count = buf->len;
+
+ if (count == 0) {
+ if (session->request_started == TRUE &&
+ session->more_data == FALSE &&
+ session->fd == -1)
+ session->body_done = TRUE;
+
+ return FALSE;
+ }
+
+ status = g_io_channel_write_chars(session->transport_channel,
+ buf->str, count, &bytes_written, NULL);
+
+ debug(session->web, "status %u bytes to write %zu bytes written %zu",
+ status, count, bytes_written);
+
+ if (status != G_IO_STATUS_NORMAL && status != G_IO_STATUS_AGAIN)
+ return FALSE;
+
+ g_string_erase(buf, 0, bytes_written);
+
+ return TRUE;
+}
+
+static gboolean process_send_file(struct web_session *session)
+{
+ int sk;
+ off_t offset;
+ ssize_t bytes_sent;
+
+ if (session->fd == -1)
+ return FALSE;
+
+ if (session->request_started == FALSE || session->more_data == TRUE)
+ return FALSE;
+
+ sk = g_io_channel_unix_get_fd(session->transport_channel);
+ if (sk < 0)
+ return FALSE;
+
+ offset = session->offset;
+
+ bytes_sent = sendfile(sk, session->fd, &offset, session->length);
+
+ debug(session->web, "errno: %d, bytes to send %zu / bytes sent %zu",
+ errno, session->length, bytes_sent);
+
+ if (bytes_sent < 0 && errno != EAGAIN)
+ return FALSE;
+
+ session->offset = offset;
+ session->length -= bytes_sent;
+
+ if (session->length == 0) {
+ session->body_done = TRUE;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void process_next_chunk(struct web_session *session)
+{
+ GString *buf = session->send_buffer;
+ const guint8 *body;
+ gsize length;
+
+ if (session->input_func == NULL) {
+ session->more_data = FALSE;
+ return;
+ }
+
+ session->more_data = session->input_func(&body, &length,
+ session->user_data);
+
+ if (length > 0) {
+ g_string_append_printf(buf, "%zx\r\n", length);
+ g_string_append_len(buf, (char *) body, length);
+ g_string_append(buf, "\r\n");
+ }
+
+ if (session->more_data == FALSE)
+ g_string_append(buf, "0\r\n\r\n");
+}
+
+static void start_request(struct web_session *session)
+{
+ GString *buf = session->send_buffer;
+ const char *version;
+ const guint8 *body;
+ gsize length;
+
+ debug(session->web, "request %s from %s",
+ session->request, session->host);
+
+ g_string_truncate(buf, 0);
+
+ if (session->web->http_version == NULL)
+ version = "1.1";
+ else
+ version = session->web->http_version;
+
+ if (session->content_type == NULL)
+ g_string_append_printf(buf, "GET %s HTTP/%s\r\n",
+ session->request, version);
+ else
+ g_string_append_printf(buf, "POST %s HTTP/%s\r\n",
+ session->request, version);
+
+ g_string_append_printf(buf, "Host: %s\r\n", session->host);
+
+ if (session->web->user_agent != NULL)
+ g_string_append_printf(buf, "User-Agent: %s\r\n",
+ session->web->user_agent);
+
+ if (session->web->user_agent_profile != NULL) {
+ g_string_append_printf(buf, "x-wap-profile: %s\r\n",
+ session->web->user_agent_profile);
+ }
+
+ if (session->web->accept_option != NULL)
+ g_string_append_printf(buf, "Accept: %s\r\n",
+ session->web->accept_option);
+
+ if (session->content_type != NULL) {
+ g_string_append_printf(buf, "Content-Type: %s\r\n",
+ session->content_type);
+ if (session->input_func == NULL) {
+ session->more_data = FALSE;
+ length = session->length;
+ } else
+ session->more_data = session->input_func(&body, &length,
+ session->user_data);
+ if (session->more_data == FALSE)
+ g_string_append_printf(buf, "Content-Length: %zu\r\n",
+ length);
+ else
+ g_string_append(buf, "Transfer-Encoding: chunked\r\n");
+ }
+
+ if (session->web->close_connection == TRUE)
+ g_string_append(buf, "Connection: close\r\n");
+
+ g_string_append(buf, "\r\n");
+
+ if (session->content_type != NULL && length > 0) {
+ if (session->more_data == TRUE) {
+ g_string_append_printf(buf, "%zx\r\n", length);
+ g_string_append_len(buf, (char *) body, length);
+ g_string_append(buf, "\r\n");
+ } else if (session->fd == -1)
+ g_string_append_len(buf, (char *) body, length);
+ }
+}
+
+static gboolean send_data(GIOChannel *channel, GIOCondition cond,
+ gpointer user_data)
+{
+ struct web_session *session = user_data;
+
+ if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
+ session->send_watch = 0;
+ return FALSE;
+ }
+
+ if (process_send_buffer(session) == TRUE)
+ return TRUE;
+
+ if (process_send_file(session) == TRUE)
+ return TRUE;
+
+ if (session->request_started == FALSE) {
+ session->request_started = TRUE;
+ start_request(session);
+ } else if (session->more_data == TRUE)
+ process_next_chunk(session);
+
+ process_send_buffer(session);
+
+ if (session->body_done == TRUE) {
+ session->send_watch = 0;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static int decode_chunked(struct web_session *session,
+ const guint8 *buf, gsize len)
+{
+ const guint8 *ptr = buf;
+ gsize counter;
+
+ while (len > 0) {
+ guint8 *pos;
+ gsize count;
+ char *str;
+
+ switch (session->chunck_state) {
+ case CHUNK_SIZE:
+ pos = memchr(ptr, '\n', len);
+ if (pos == NULL) {
+ g_string_append_len(session->current_header,
+ (gchar *) ptr, len);
+ return 0;
+ }
+
+ count = pos - ptr;
+ if (count < 1 || ptr[count - 1] != '\r')
+ return -EILSEQ;
+
+ g_string_append_len(session->current_header,
+ (gchar *) ptr, count);
+
+ len -= count + 1;
+ ptr = pos + 1;
+
+ str = session->current_header->str;
+
+ counter = strtoul(str, NULL, 16);
+ if ((counter == 0 && errno == EINVAL) ||
+ counter == ULONG_MAX)
+ return -EILSEQ;
+
+ session->chunk_size = counter;
+ session->chunk_left = counter;
+
+ session->chunck_state = CHUNK_DATA;
+ break;
+ case CHUNK_R_BODY:
+ if (*ptr != '\r')
+ return -EILSEQ;
+ ptr++;
+ len--;
+ session->chunck_state = CHUNK_N_BODY;
+ break;
+ case CHUNK_N_BODY:
+ if (*ptr != '\n')
+ return -EILSEQ;
+ ptr++;
+ len--;
+ session->chunck_state = CHUNK_SIZE;
+ break;
+ case CHUNK_DATA:
+ if (session->chunk_size == 0) {
+ debug(session->web, "Download Done in chunk");
+ g_string_truncate(session->current_header, 0);
+ return 0;
+ }
+
+ if (session->chunk_left <= len) {
+ session->result.buffer = ptr;
+ session->result.length = session->chunk_left;
+ call_result_func(session, 0);
+
+ len -= session->chunk_left;
+ ptr += session->chunk_left;
+
+ session->total_len += session->chunk_left;
+ session->chunk_left = 0;
+
+ g_string_truncate(session->current_header, 0);
+ session->chunck_state = CHUNK_R_BODY;
+ break;
+ }
+ /* more data */
+ session->result.buffer = ptr;
+ session->result.length = len;
+ call_result_func(session, 0);
+
+ session->chunk_left -= len;
+ session->total_len += len;
+
+ len -= len;
+ ptr += len;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int handle_body(struct web_session *session,
+ const guint8 *buf, gsize len)
+{
+ int err;
+
+ debug(session->web, "[body] length %zu", len);
+
+ if (session->result.use_chunk == FALSE) {
+ if (len > 0) {
+ session->result.buffer = buf;
+ session->result.length = len;
+ call_result_func(session, 0);
+ }
+ return 0;
+ }
+
+ err = decode_chunked(session, buf, len);
+ if (err < 0) {
+ debug(session->web, "Error in chunk decode %d", err);
+
+ session->result.buffer = NULL;
+ session->result.length = 0;
+ call_result_func(session, 400);
+ }
+
+ return err;
+}
+
+static void handle_multi_line(struct web_session *session)
+{
+ gsize count;
+ char *str;
+ gchar *value;
+
+ str = session->current_header->str;
+
+ if (str[0] != ' ' && str[0] != '\t')
+ return;
+
+ while (str[0] == ' ' || str[0] == '\t')
+ str++;
+
+ count = str - session->current_header->str;
+ if (count > 0) {
+ g_string_erase(session->current_header, 0, count);
+ g_string_insert_c(session->current_header, 0, ' ');
+ }
+
+ value = g_hash_table_lookup(session->result.headers,
+ session->result.last_key);
+ if (value != NULL) {
+ g_string_insert(session->current_header, 0, value);
+
+ str = session->current_header->str;
+
+ g_hash_table_replace(session->result.headers,
+ g_strdup(session->result.last_key),
+ g_strdup(str));
+ }
+}
+
+static void add_header_field(struct web_session *session)
+{
+ gsize count;
+ guint8 *pos;
+ char *str;
+ gchar *value;
+ gchar *key;
+
+ str = session->current_header->str;
+
+ pos = memchr(str, ':', session->current_header->len);
+ if (pos != NULL) {
+ *pos = '\0';
+ pos++;
+
+ key = g_strdup(str);
+
+ /* remove preceding white spaces */
+ while (*pos == ' ')
+ pos++;
+
+ count = (char *) pos - str;
+
+ g_string_erase(session->current_header, 0, count);
+
+ value = g_hash_table_lookup(session->result.headers, key);
+ if (value != NULL) {
+ g_string_insert_c(session->current_header, 0, ' ');
+ g_string_insert_c(session->current_header, 0, ';');
+
+ g_string_insert(session->current_header, 0, value);
+ }
+
+ str = session->current_header->str;
+ g_hash_table_replace(session->result.headers, key,
+ g_strdup(str));
+
+ g_free(session->result.last_key);
+ session->result.last_key = g_strdup(key);
+ }