/** * soup_headers_parse_request: * @str: the headers (up to, but not including, the trailing blank line) * @len: length of @str * @req_headers: #SoupMessageHeaders to store the header values in * @req_method: (out) (allow-none): if non-%NULL, will be filled in with the * request method * @req_path: (out) (allow-none): if non-%NULL, will be filled in with the * request path * @ver: (out) (allow-none): if non-%NULL, will be filled in with the HTTP * version * * Parses the headers of an HTTP request in @str and stores the * results in @req_method, @req_path, @ver, and @req_headers. * * Beware that @req_headers may be modified even on failure. * * Return value: %SOUP_STATUS_OK if the headers could be parsed, or an * HTTP error to be returned to the client if they could not be. **/ guint soup_headers_parse_request (const char *str, int len, SoupMessageHeaders *req_headers, char **req_method, char **req_path, SoupHTTPVersion *ver) { const char *method, *method_end, *path, *path_end; const char *version, *version_end, *headers; unsigned long major_version, minor_version; char *p; g_return_val_if_fail (str != NULL, SOUP_STATUS_MALFORMED); /* RFC 2616 4.1 "servers SHOULD ignore any empty line(s) * received where a Request-Line is expected." */ while ((*str == '\r' || *str == '\n') && len > 0) { str++; len--; } if (!len) return SOUP_STATUS_BAD_REQUEST; /* RFC 2616 19.3 "[servers] SHOULD accept any amount of SP or * HT characters between [Request-Line] fields" */ method = method_end = str; while (method_end < str + len && *method_end != ' ' && *method_end != '\t') method_end++; if (method_end >= str + len) return SOUP_STATUS_BAD_REQUEST; path = method_end; while (path < str + len && (*path == ' ' || *path == '\t')) path++; if (path >= str + len) return SOUP_STATUS_BAD_REQUEST; path_end = path; while (path_end < str + len && *path_end != ' ' && *path_end != '\t') path_end++; if (path_end >= str + len) return SOUP_STATUS_BAD_REQUEST; version = path_end; while (version < str + len && (*version == ' ' || *version == '\t')) version++; if (version + 8 >= str + len) return SOUP_STATUS_BAD_REQUEST; if (strncmp (version, "HTTP/", 5) != 0 || !g_ascii_isdigit (version[5])) return SOUP_STATUS_BAD_REQUEST; major_version = strtoul (version + 5, &p, 10); if (*p != '.' || !g_ascii_isdigit (p[1])) return SOUP_STATUS_BAD_REQUEST; minor_version = strtoul (p + 1, &p, 10); version_end = p; if (major_version != 1) return SOUP_STATUS_HTTP_VERSION_NOT_SUPPORTED; if (minor_version > 1) return SOUP_STATUS_HTTP_VERSION_NOT_SUPPORTED; headers = version_end; while (headers < str + len && (*headers == '\r' || *headers == ' ')) headers++; if (headers >= str + len || *headers != '\n') return SOUP_STATUS_BAD_REQUEST; if (!soup_headers_parse (str, len, req_headers)) return SOUP_STATUS_BAD_REQUEST; if (soup_message_headers_get_expectations (req_headers) & SOUP_EXPECTATION_UNRECOGNIZED) return SOUP_STATUS_EXPECTATION_FAILED; /* RFC 2616 14.10 */ if (minor_version == 0) soup_message_headers_clean_connection_headers (req_headers); if (req_method) *req_method = g_strndup (method, method_end - method); if (req_path) *req_path = g_strndup (path, path_end - path); if (ver) *ver = (minor_version == 0) ? SOUP_HTTP_1_0 : SOUP_HTTP_1_1; return SOUP_STATUS_OK; }
/* Attempts to push forward the reading side of @msg's I/O. Returns * %TRUE if it manages to make some progress, and it is likely that * further progress can be made. Returns %FALSE if it has reached a * stopping point of some sort (need input from the application, * socket not readable, read is complete, etc). */ static gboolean io_read (SoupMessage *msg, gboolean blocking, GCancellable *cancellable, GError **error) { SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); SoupMessageIOData *io = priv->io_data; guchar *stack_buf = NULL; gssize nread; SoupBuffer *buffer; guint status; switch (io->read_state) { case SOUP_MESSAGE_IO_STATE_HEADERS: if (!read_headers (msg, blocking, cancellable, error)) return FALSE; status = io->parse_headers_cb (msg, (char *)io->read_header_buf->data, io->read_header_buf->len, &io->read_encoding, io->header_data, error); g_byte_array_set_size (io->read_header_buf, 0); if (status != SOUP_STATUS_OK) { /* Either we couldn't parse the headers, or they * indicated something that would mean we wouldn't * be able to parse the body. (Eg, unknown * Transfer-Encoding.). Skip the rest of the * reading, and make sure the connection gets * closed when we're done. */ soup_message_set_status (msg, status); soup_message_headers_append (msg->request_headers, "Connection", "close"); io->read_state = SOUP_MESSAGE_IO_STATE_FINISHING; break; } if (io->mode == SOUP_MESSAGE_IO_CLIENT && SOUP_STATUS_IS_INFORMATIONAL (msg->status_code)) { if (msg->status_code == SOUP_STATUS_CONTINUE && io->write_state == SOUP_MESSAGE_IO_STATE_BLOCKING) { /* Pause the reader, unpause the writer */ io->read_state = SOUP_MESSAGE_IO_STATE_BLOCKING; io->write_state = SOUP_MESSAGE_IO_STATE_BODY_START; } else { /* Just stay in HEADERS */ io->read_state = SOUP_MESSAGE_IO_STATE_HEADERS; } /* Informational responses have no bodies, so * bail out here rather than parsing encoding, etc */ soup_message_got_informational (msg); /* If this was "101 Switching Protocols", then * the session may have stolen the connection... */ if (io != priv->io_data) return FALSE; soup_message_cleanup_response (msg); break; } else if (io->mode == SOUP_MESSAGE_IO_SERVER && soup_message_headers_get_expectations (msg->request_headers) & SOUP_EXPECTATION_CONTINUE) { /* We must return a status code and response * headers to the client; either an error to * be set by a got-headers handler below, or * else %SOUP_STATUS_CONTINUE otherwise. */ io->write_state = SOUP_MESSAGE_IO_STATE_HEADERS; io->read_state = SOUP_MESSAGE_IO_STATE_BLOCKING; } else { io->read_state = SOUP_MESSAGE_IO_STATE_BODY_START; /* If the client was waiting for a Continue * but got something else, then it's done * writing. */ if (io->mode == SOUP_MESSAGE_IO_CLIENT && io->write_state == SOUP_MESSAGE_IO_STATE_BLOCKING) io->write_state = SOUP_MESSAGE_IO_STATE_FINISHING; } if (io->read_encoding == SOUP_ENCODING_CONTENT_LENGTH) { SoupMessageHeaders *hdrs = (io->mode == SOUP_MESSAGE_IO_CLIENT) ? msg->response_headers : msg->request_headers; io->read_length = soup_message_headers_get_content_length (hdrs); if (io->mode == SOUP_MESSAGE_IO_CLIENT && !soup_message_is_keepalive (msg)) { /* Some servers suck and send * incorrect Content-Length values, so * allow EOF termination in this case * (iff the message is too short) too. */ io->read_encoding = SOUP_ENCODING_EOF; } } else io->read_length = -1; soup_message_got_headers (msg); break; case SOUP_MESSAGE_IO_STATE_BODY_START: if (!io->body_istream) { GInputStream *body_istream = soup_body_input_stream_new (G_INPUT_STREAM (io->istream), io->read_encoding, io->read_length); /* TODO: server-side messages do not have a io->item. This means * that we cannot use content processors for them right now. */ if (io->mode == SOUP_MESSAGE_IO_CLIENT) { io->body_istream = soup_message_setup_body_istream (body_istream, msg, io->item->session, SOUP_STAGE_MESSAGE_BODY); g_object_unref (body_istream); } else { io->body_istream = body_istream; } } if (priv->sniffer) { SoupContentSnifferStream *sniffer_stream = SOUP_CONTENT_SNIFFER_STREAM (io->body_istream); const char *content_type; GHashTable *params; if (!soup_content_sniffer_stream_is_ready (sniffer_stream, blocking, cancellable, error)) return FALSE; content_type = soup_content_sniffer_stream_sniff (sniffer_stream, ¶ms); soup_message_content_sniffed (msg, content_type, params); } io->read_state = SOUP_MESSAGE_IO_STATE_BODY; break; case SOUP_MESSAGE_IO_STATE_BODY: if (priv->chunk_allocator) { buffer = priv->chunk_allocator (msg, io->read_length, priv->chunk_allocator_data); if (!buffer) { g_return_val_if_fail (!io->item || !io->item->new_api, FALSE); soup_message_io_pause (msg); return FALSE; } } else { if (!stack_buf) stack_buf = alloca (RESPONSE_BLOCK_SIZE); buffer = soup_buffer_new (SOUP_MEMORY_TEMPORARY, stack_buf, RESPONSE_BLOCK_SIZE); } nread = g_pollable_stream_read (io->body_istream, (guchar *)buffer->data, buffer->length, blocking, cancellable, error); if (nread > 0) { buffer->length = nread; soup_message_body_got_chunk (io->read_body, buffer); soup_message_got_chunk (msg, buffer); soup_buffer_free (buffer); break; } soup_buffer_free (buffer); if (nread == -1) return FALSE; /* else nread == 0 */ io->read_state = SOUP_MESSAGE_IO_STATE_BODY_DONE; break; case SOUP_MESSAGE_IO_STATE_BODY_DONE: io->read_state = SOUP_MESSAGE_IO_STATE_FINISHING; soup_message_got_body (msg); break; case SOUP_MESSAGE_IO_STATE_FINISHING: io->read_state = SOUP_MESSAGE_IO_STATE_DONE; if (io->mode == SOUP_MESSAGE_IO_SERVER) io->write_state = SOUP_MESSAGE_IO_STATE_HEADERS; break; default: g_return_val_if_reached (FALSE); } return TRUE; }
/* Attempts to push forward the writing side of @msg's I/O. Returns * %TRUE if it manages to make some progress, and it is likely that * further progress can be made. Returns %FALSE if it has reached a * stopping point of some sort (need input from the application, * socket not writable, write is complete, etc). */ static gboolean io_write (SoupMessage *msg, gboolean blocking, GCancellable *cancellable, GError **error) { SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); SoupMessageIOData *io = priv->io_data; SoupBuffer *chunk; gssize nwrote; if (io->async_close_error) { g_propagate_error (error, io->async_close_error); io->async_close_error = NULL; return FALSE; } else if (io->async_close_wait) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK, _("Operation would block")); return FALSE; } switch (io->write_state) { case SOUP_MESSAGE_IO_STATE_HEADERS: if (io->mode == SOUP_MESSAGE_IO_SERVER && io->read_state == SOUP_MESSAGE_IO_STATE_BLOCKING && msg->status_code == 0) { /* Client requested "Expect: 100-continue", and * server did not set an error. */ soup_message_set_status (msg, SOUP_STATUS_CONTINUE); } if (!io->write_buf->len) { io->get_headers_cb (msg, io->write_buf, &io->write_encoding, io->header_data); } while (io->written < io->write_buf->len) { nwrote = g_pollable_stream_write (io->ostream, io->write_buf->str + io->written, io->write_buf->len - io->written, blocking, cancellable, error); if (nwrote == -1) return FALSE; io->written += nwrote; } io->written = 0; g_string_truncate (io->write_buf, 0); if (io->mode == SOUP_MESSAGE_IO_SERVER && SOUP_STATUS_IS_INFORMATIONAL (msg->status_code)) { if (msg->status_code == SOUP_STATUS_CONTINUE) { /* Stop and wait for the body now */ io->write_state = SOUP_MESSAGE_IO_STATE_BLOCKING; io->read_state = SOUP_MESSAGE_IO_STATE_BODY_START; } else { /* We just wrote a 1xx response * header, so stay in STATE_HEADERS. * (The caller will pause us from the * wrote_informational callback if he * is not ready to send the final * response.) */ } soup_message_wrote_informational (msg); /* If this was "101 Switching Protocols", then * the server probably stole the connection... */ if (io != priv->io_data) return FALSE; soup_message_cleanup_response (msg); break; } if (io->write_encoding == SOUP_ENCODING_CONTENT_LENGTH) { SoupMessageHeaders *hdrs = (io->mode == SOUP_MESSAGE_IO_CLIENT) ? msg->request_headers : msg->response_headers; io->write_length = soup_message_headers_get_content_length (hdrs); } if (io->mode == SOUP_MESSAGE_IO_CLIENT && soup_message_headers_get_expectations (msg->request_headers) & SOUP_EXPECTATION_CONTINUE) { /* Need to wait for the Continue response */ io->write_state = SOUP_MESSAGE_IO_STATE_BLOCKING; io->read_state = SOUP_MESSAGE_IO_STATE_HEADERS; } else { io->write_state = SOUP_MESSAGE_IO_STATE_BODY_START; /* If the client was waiting for a Continue * but we sent something else, then they're * now done writing. */ if (io->mode == SOUP_MESSAGE_IO_SERVER && io->read_state == SOUP_MESSAGE_IO_STATE_BLOCKING) io->read_state = SOUP_MESSAGE_IO_STATE_DONE; } soup_message_wrote_headers (msg); break; case SOUP_MESSAGE_IO_STATE_BODY_START: io->body_ostream = soup_body_output_stream_new (io->ostream, io->write_encoding, io->write_length); io->write_state = SOUP_MESSAGE_IO_STATE_BODY; break; case SOUP_MESSAGE_IO_STATE_BODY: if (!io->write_length && io->write_encoding != SOUP_ENCODING_EOF && io->write_encoding != SOUP_ENCODING_CHUNKED) { io->write_state = SOUP_MESSAGE_IO_STATE_BODY_FLUSH; break; } if (!io->write_chunk) { io->write_chunk = soup_message_body_get_chunk (io->write_body, io->write_body_offset); if (!io->write_chunk) { g_return_val_if_fail (!io->item || !io->item->new_api, FALSE); soup_message_io_pause (msg); return FALSE; } if (!io->write_chunk->length) { io->write_state = SOUP_MESSAGE_IO_STATE_BODY_FLUSH; break; } } nwrote = g_pollable_stream_write (io->body_ostream, io->write_chunk->data + io->written, io->write_chunk->length - io->written, blocking, cancellable, error); if (nwrote == -1) return FALSE; chunk = soup_buffer_new_subbuffer (io->write_chunk, io->written, nwrote); io->written += nwrote; if (io->write_length) io->write_length -= nwrote; if (io->written == io->write_chunk->length) io->write_state = SOUP_MESSAGE_IO_STATE_BODY_DATA; soup_message_wrote_body_data (msg, chunk); soup_buffer_free (chunk); break; case SOUP_MESSAGE_IO_STATE_BODY_DATA: io->written = 0; if (io->write_chunk->length == 0) { io->write_state = SOUP_MESSAGE_IO_STATE_BODY_FLUSH; break; } if (io->mode == SOUP_MESSAGE_IO_SERVER || priv->msg_flags & SOUP_MESSAGE_CAN_REBUILD) soup_message_body_wrote_chunk (io->write_body, io->write_chunk); io->write_body_offset += io->write_chunk->length; soup_buffer_free (io->write_chunk); io->write_chunk = NULL; io->write_state = SOUP_MESSAGE_IO_STATE_BODY; soup_message_wrote_chunk (msg); break; case SOUP_MESSAGE_IO_STATE_BODY_FLUSH: if (io->body_ostream) { if (blocking || io->write_encoding != SOUP_ENCODING_CHUNKED) { if (!g_output_stream_close (io->body_ostream, cancellable, error)) return FALSE; g_clear_object (&io->body_ostream); } else { io->async_close_wait = g_cancellable_new (); if (io->async_context) g_main_context_push_thread_default (io->async_context); g_output_stream_close_async (io->body_ostream, G_PRIORITY_DEFAULT, cancellable, closed_async, g_object_ref (msg)); if (io->async_context) g_main_context_pop_thread_default (io->async_context); } } io->write_state = SOUP_MESSAGE_IO_STATE_BODY_DONE; break; case SOUP_MESSAGE_IO_STATE_BODY_DONE: io->write_state = SOUP_MESSAGE_IO_STATE_FINISHING; soup_message_wrote_body (msg); break; case SOUP_MESSAGE_IO_STATE_FINISHING: io->write_state = SOUP_MESSAGE_IO_STATE_DONE; if (io->mode == SOUP_MESSAGE_IO_CLIENT) io->read_state = SOUP_MESSAGE_IO_STATE_HEADERS; break; default: g_return_val_if_reached (FALSE); } return TRUE; }
static void print_request (SoupLogger *logger, SoupMessage *msg, SoupSession *session, SoupSocket *socket, gboolean restarted) { SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger); SoupLoggerLogLevel log_level; SoupMessageHeadersIter iter; const char *name, *value; SoupURI *uri; if (priv->request_filter) { log_level = priv->request_filter (logger, msg, priv->request_filter_data); } else log_level = priv->level; if (log_level == SOUP_LOGGER_LOG_NONE) return; uri = soup_message_get_uri (msg); if (msg->method == SOUP_METHOD_CONNECT) { soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '>', "CONNECT %s:%u HTTP/1.%d", uri->host, uri->port, soup_message_get_http_version (msg)); } else { soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '>', "%s %s%s%s HTTP/1.%d", msg->method, uri->path, uri->query ? "?" : "", uri->query ? uri->query : "", soup_message_get_http_version (msg)); } soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '>', "Soup-Debug-Timestamp: %lu", (unsigned long)time (0)); soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '>', "Soup-Debug: %s %u (%p), %s %u (%p), %s %u (%p)%s", g_type_name_from_instance ((GTypeInstance *)session), soup_logger_get_id (logger, session), session, g_type_name_from_instance ((GTypeInstance *)msg), soup_logger_get_id (logger, msg), msg, g_type_name_from_instance ((GTypeInstance *)socket), soup_logger_get_id (logger, socket), socket, restarted ? ", restarted" : ""); if (log_level == SOUP_LOGGER_LOG_MINIMAL) return; if (!soup_message_headers_get_one (msg->request_headers, "Host")) { char *uri_host; if (strchr (uri->host, ':')) uri_host = g_strdup_printf ("[%s]", uri->host); else if (g_hostname_is_non_ascii (uri->host)) uri_host = g_hostname_to_ascii (uri->host); else uri_host = uri->host; soup_logger_print (logger, SOUP_LOGGER_LOG_HEADERS, '>', "Host: %s%c%u", uri_host, soup_uri_uses_default_port (uri) ? '\0' : ':', uri->port); if (uri_host != uri->host) g_free (uri_host); } soup_message_headers_iter_init (&iter, msg->request_headers); while (soup_message_headers_iter_next (&iter, &name, &value)) { if (!g_ascii_strcasecmp (name, "Authorization") && !g_ascii_strncasecmp (value, "Basic ", 6)) soup_logger_print_basic_auth (logger, value); else { soup_logger_print (logger, SOUP_LOGGER_LOG_HEADERS, '>', "%s: %s", name, value); } } if (log_level == SOUP_LOGGER_LOG_HEADERS) return; if (msg->request_body->length && soup_message_body_get_accumulate (msg->request_body)) { SoupBuffer *request; request = soup_message_body_flatten (msg->request_body); g_return_if_fail (request != NULL); soup_buffer_free (request); if (soup_message_headers_get_expectations (msg->request_headers) != SOUP_EXPECTATION_CONTINUE) { soup_logger_print (logger, SOUP_LOGGER_LOG_BODY, '>', "\n%s", msg->request_body->data); } } }
static void io_read (SoupSocket *sock, SoupMessage *msg) { SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); SoupMessageIOData *io = priv->io_data; guint status; read_more: switch (io->read_state) { case SOUP_MESSAGE_IO_STATE_NOT_STARTED: return; case SOUP_MESSAGE_IO_STATE_HEADERS: if (!read_metadata (msg, TRUE)) return; /* We need to "rewind" io->read_meta_buf back one line. * That SHOULD be two characters (CR LF), but if the * web server was stupid, it might only be one. */ if (io->read_meta_buf->len < 3 || io->read_meta_buf->data[io->read_meta_buf->len - 2] == '\n') io->read_meta_buf->len--; else io->read_meta_buf->len -= 2; io->read_meta_buf->data[io->read_meta_buf->len] = '\0'; status = io->parse_headers_cb (msg, (char *)io->read_meta_buf->data, io->read_meta_buf->len, &io->read_encoding, io->user_data); g_byte_array_set_size (io->read_meta_buf, 0); if (status != SOUP_STATUS_OK) { /* Either we couldn't parse the headers, or they * indicated something that would mean we wouldn't * be able to parse the body. (Eg, unknown * Transfer-Encoding.). Skip the rest of the * reading, and make sure the connection gets * closed when we're done. */ soup_message_set_status (msg, status); soup_message_headers_append (msg->request_headers, "Connection", "close"); io->read_state = SOUP_MESSAGE_IO_STATE_FINISHING; break; } if (io->read_encoding == SOUP_ENCODING_CONTENT_LENGTH) { SoupMessageHeaders *hdrs = (io->mode == SOUP_MESSAGE_IO_CLIENT) ? msg->response_headers : msg->request_headers; io->read_length = soup_message_headers_get_content_length (hdrs); } if (io->mode == SOUP_MESSAGE_IO_CLIENT && SOUP_STATUS_IS_INFORMATIONAL (msg->status_code)) { if (msg->status_code == SOUP_STATUS_CONTINUE && io->write_state == SOUP_MESSAGE_IO_STATE_BLOCKING) { /* Pause the reader, unpause the writer */ io->read_state = SOUP_MESSAGE_IO_STATE_BLOCKING; io->write_state = io_body_state (io->write_encoding); } else { /* Just stay in HEADERS */ io->read_state = SOUP_MESSAGE_IO_STATE_HEADERS; } } else if (io->mode == SOUP_MESSAGE_IO_SERVER && soup_message_headers_get_expectations (msg->request_headers) & SOUP_EXPECTATION_CONTINUE) { /* The client requested a Continue response. The * got_headers handler may change this to something * else though. */ soup_message_set_status (msg, SOUP_STATUS_CONTINUE); io->write_state = SOUP_MESSAGE_IO_STATE_HEADERS; io->read_state = SOUP_MESSAGE_IO_STATE_BLOCKING; } else { io->read_state = io_body_state (io->read_encoding); /* If the client was waiting for a Continue * but got something else, then it's done * writing. */ if (io->mode == SOUP_MESSAGE_IO_CLIENT && io->write_state == SOUP_MESSAGE_IO_STATE_BLOCKING) io->write_state = SOUP_MESSAGE_IO_STATE_FINISHING; } if (io->mode == SOUP_MESSAGE_IO_CLIENT && SOUP_STATUS_IS_INFORMATIONAL (msg->status_code)) { SOUP_MESSAGE_IO_PREPARE_FOR_CALLBACK; soup_message_got_informational (msg); soup_message_cleanup_response (msg); SOUP_MESSAGE_IO_RETURN_IF_CANCELLED_OR_PAUSED; } else { SOUP_MESSAGE_IO_PREPARE_FOR_CALLBACK; soup_message_got_headers (msg); SOUP_MESSAGE_IO_RETURN_IF_CANCELLED_OR_PAUSED; } break; case SOUP_MESSAGE_IO_STATE_BLOCKING: io_write (sock, msg); /* As in the io_write case, we *must* return here. */ return; case SOUP_MESSAGE_IO_STATE_BODY: if (!read_body_chunk (msg)) return; got_body: if (!io_handle_sniffing (msg, TRUE)) { /* If the message was paused (as opposed to * cancelled), we need to make sure we wind up * back here when it's unpaused, even if it * was doing a chunked or EOF-terminated read * before. */ if (io == priv->io_data) { io->read_state = SOUP_MESSAGE_IO_STATE_BODY; io->read_encoding = SOUP_ENCODING_CONTENT_LENGTH; io->read_length = 0; } return; } io->read_state = SOUP_MESSAGE_IO_STATE_FINISHING; SOUP_MESSAGE_IO_PREPARE_FOR_CALLBACK; soup_message_got_body (msg); SOUP_MESSAGE_IO_RETURN_IF_CANCELLED_OR_PAUSED; break; case SOUP_MESSAGE_IO_STATE_CHUNK_SIZE: if (!read_metadata (msg, FALSE)) return; io->read_length = strtoul ((char *)io->read_meta_buf->data, NULL, 16); g_byte_array_set_size (io->read_meta_buf, 0); if (io->read_length > 0) io->read_state = SOUP_MESSAGE_IO_STATE_CHUNK; else io->read_state = SOUP_MESSAGE_IO_STATE_TRAILERS; break; case SOUP_MESSAGE_IO_STATE_CHUNK: if (!read_body_chunk (msg)) return; io->read_state = SOUP_MESSAGE_IO_STATE_CHUNK_END; break; case SOUP_MESSAGE_IO_STATE_CHUNK_END: if (!read_metadata (msg, FALSE)) return; g_byte_array_set_size (io->read_meta_buf, 0); io->read_state = SOUP_MESSAGE_IO_STATE_CHUNK_SIZE; break; case SOUP_MESSAGE_IO_STATE_TRAILERS: if (!read_metadata (msg, FALSE)) return; if (io->read_meta_buf->len <= SOUP_MESSAGE_IO_EOL_LEN) goto got_body; /* FIXME: process trailers */ g_byte_array_set_size (io->read_meta_buf, 0); break; case SOUP_MESSAGE_IO_STATE_FINISHING: if (io->read_tag) { g_signal_handler_disconnect (io->sock, io->read_tag); io->read_tag = 0; } io->read_state = SOUP_MESSAGE_IO_STATE_DONE; if (io->mode == SOUP_MESSAGE_IO_SERVER) { io->write_state = SOUP_MESSAGE_IO_STATE_HEADERS; io_write (sock, msg); } else soup_message_io_finished (msg); return; case SOUP_MESSAGE_IO_STATE_DONE: default: g_return_if_reached (); } goto read_more; }
static void io_write (SoupSocket *sock, SoupMessage *msg) { SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); SoupMessageIOData *io = priv->io_data; write_more: switch (io->write_state) { case SOUP_MESSAGE_IO_STATE_NOT_STARTED: return; case SOUP_MESSAGE_IO_STATE_HEADERS: if (!io->write_buf->len) { io->get_headers_cb (msg, io->write_buf, &io->write_encoding, io->user_data); if (!io->write_buf->len) { soup_message_io_pause (msg); return; } } if (!write_data (msg, io->write_buf->str, io->write_buf->len, FALSE)) return; g_string_truncate (io->write_buf, 0); if (io->write_encoding == SOUP_ENCODING_CONTENT_LENGTH) { SoupMessageHeaders *hdrs = (io->mode == SOUP_MESSAGE_IO_CLIENT) ? msg->request_headers : msg->response_headers; io->write_length = soup_message_headers_get_content_length (hdrs); } if (io->mode == SOUP_MESSAGE_IO_SERVER && SOUP_STATUS_IS_INFORMATIONAL (msg->status_code)) { if (msg->status_code == SOUP_STATUS_CONTINUE) { /* Stop and wait for the body now */ io->write_state = SOUP_MESSAGE_IO_STATE_BLOCKING; io->read_state = io_body_state (io->read_encoding); } else { /* We just wrote a 1xx response * header, so stay in STATE_HEADERS. * (The caller will pause us from the * wrote_informational callback if he * is not ready to send the final * response.) */ } } else if (io->mode == SOUP_MESSAGE_IO_CLIENT && soup_message_headers_get_expectations (msg->request_headers) & SOUP_EXPECTATION_CONTINUE) { /* Need to wait for the Continue response */ io->write_state = SOUP_MESSAGE_IO_STATE_BLOCKING; io->read_state = SOUP_MESSAGE_IO_STATE_HEADERS; } else { io->write_state = io_body_state (io->write_encoding); /* If the client was waiting for a Continue * but we sent something else, then they're * now done writing. */ if (io->mode == SOUP_MESSAGE_IO_SERVER && io->read_state == SOUP_MESSAGE_IO_STATE_BLOCKING) io->read_state = SOUP_MESSAGE_IO_STATE_FINISHING; } SOUP_MESSAGE_IO_PREPARE_FOR_CALLBACK; if (SOUP_STATUS_IS_INFORMATIONAL (msg->status_code)) { soup_message_wrote_informational (msg); soup_message_cleanup_response (msg); } else soup_message_wrote_headers (msg); SOUP_MESSAGE_IO_RETURN_IF_CANCELLED_OR_PAUSED; break; case SOUP_MESSAGE_IO_STATE_BLOCKING: io_read (sock, msg); /* If io_read reached a point where we could write * again, it would have recursively called io_write. * So (a) we don't need to try to keep writing, and * (b) we can't anyway, because msg may have been * destroyed. */ return; case SOUP_MESSAGE_IO_STATE_BODY: if (!io->write_length && io->write_encoding != SOUP_ENCODING_EOF) { wrote_body: io->write_state = SOUP_MESSAGE_IO_STATE_FINISHING; SOUP_MESSAGE_IO_PREPARE_FOR_CALLBACK; soup_message_wrote_body (msg); SOUP_MESSAGE_IO_RETURN_IF_CANCELLED_OR_PAUSED; break; } if (!io->write_chunk) { io->write_chunk = soup_message_body_get_chunk (io->write_body, io->write_body_offset); if (!io->write_chunk) { soup_message_io_pause (msg); return; } if (io->write_chunk->length > io->write_length && io->write_encoding != SOUP_ENCODING_EOF) { /* App is trying to write more than it * claimed it would; we have to truncate. */ SoupBuffer *truncated = soup_buffer_new_subbuffer (io->write_chunk, 0, io->write_length); soup_buffer_free (io->write_chunk); io->write_chunk = truncated; } else if (io->write_encoding == SOUP_ENCODING_EOF && !io->write_chunk->length) goto wrote_body; } if (!write_data (msg, io->write_chunk->data, io->write_chunk->length, TRUE)) return; if (io->mode == SOUP_MESSAGE_IO_SERVER) soup_message_body_wrote_chunk (io->write_body, io->write_chunk); io->write_body_offset += io->write_chunk->length; soup_buffer_free (io->write_chunk); io->write_chunk = NULL; SOUP_MESSAGE_IO_PREPARE_FOR_CALLBACK; soup_message_wrote_chunk (msg); SOUP_MESSAGE_IO_RETURN_IF_CANCELLED_OR_PAUSED; break; case SOUP_MESSAGE_IO_STATE_CHUNK_SIZE: if (!io->write_chunk) { io->write_chunk = soup_message_body_get_chunk (io->write_body, io->write_body_offset); if (!io->write_chunk) { soup_message_io_pause (msg); return; } g_string_append_printf (io->write_buf, "%lx\r\n", (unsigned long) io->write_chunk->length); io->write_body_offset += io->write_chunk->length; } if (!write_data (msg, io->write_buf->str, io->write_buf->len, FALSE)) return; g_string_truncate (io->write_buf, 0); if (io->write_chunk->length == 0) { /* The last chunk has no CHUNK_END... */ io->write_state = SOUP_MESSAGE_IO_STATE_TRAILERS; break; } io->write_state = SOUP_MESSAGE_IO_STATE_CHUNK; /* fall through */ case SOUP_MESSAGE_IO_STATE_CHUNK: if (!write_data (msg, io->write_chunk->data, io->write_chunk->length, TRUE)) return; if (io->mode == SOUP_MESSAGE_IO_SERVER) soup_message_body_wrote_chunk (io->write_body, io->write_chunk); soup_buffer_free (io->write_chunk); io->write_chunk = NULL; io->write_state = SOUP_MESSAGE_IO_STATE_CHUNK_END; SOUP_MESSAGE_IO_PREPARE_FOR_CALLBACK; soup_message_wrote_chunk (msg); SOUP_MESSAGE_IO_RETURN_IF_CANCELLED_OR_PAUSED; /* fall through */ case SOUP_MESSAGE_IO_STATE_CHUNK_END: if (!write_data (msg, SOUP_MESSAGE_IO_EOL, SOUP_MESSAGE_IO_EOL_LEN, FALSE)) return; io->write_state = SOUP_MESSAGE_IO_STATE_CHUNK_SIZE; break; case SOUP_MESSAGE_IO_STATE_TRAILERS: if (!write_data (msg, SOUP_MESSAGE_IO_EOL, SOUP_MESSAGE_IO_EOL_LEN, FALSE)) return; io->write_state = SOUP_MESSAGE_IO_STATE_FINISHING; SOUP_MESSAGE_IO_PREPARE_FOR_CALLBACK; soup_message_wrote_body (msg); SOUP_MESSAGE_IO_RETURN_IF_CANCELLED_OR_PAUSED; /* fall through */ case SOUP_MESSAGE_IO_STATE_FINISHING: if (io->write_tag) { g_signal_handler_disconnect (io->sock, io->write_tag); io->write_tag = 0; } io->write_state = SOUP_MESSAGE_IO_STATE_DONE; if (io->mode == SOUP_MESSAGE_IO_CLIENT) { io->read_state = SOUP_MESSAGE_IO_STATE_HEADERS; io_read (sock, msg); } else soup_message_io_finished (msg); return; case SOUP_MESSAGE_IO_STATE_DONE: default: g_return_if_reached (); } goto write_more; }