1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * soup-message-server-io.c: server-side request/response
4 *
5 * Copyright (C) 2000-2003, Ximian, Inc.
6 */
7
8 #ifdef HAVE_CONFIG_H
9 #include "config.h"
10 #endif
11
12 #include <stdlib.h>
13 #include <string.h>
14
15 #include <glib/gi18n-lib.h>
16
17 #include "soup.h"
18 #include "soup-message-private.h"
19 #include "soup-misc-private.h"
20 #include "soup-socket-private.h"
21
22 static SoupURI *
parse_connect_authority(const char * req_path)23 parse_connect_authority (const char *req_path)
24 {
25 SoupURI *uri;
26 char *fake_uri;
27
28 fake_uri = g_strdup_printf ("http://%s", req_path);
29 uri = soup_uri_new (fake_uri);
30 g_free (fake_uri);
31
32 if (uri->user || uri->password ||
33 uri->query || uri->fragment ||
34 !uri->host ||
35 (uri->port == 0) ||
36 (strcmp (uri->path, "/") != 0)) {
37 soup_uri_free (uri);
38 return NULL;
39 }
40
41 return uri;
42 }
43
44 static guint
parse_request_headers(SoupMessage * msg,char * headers,guint headers_len,SoupEncoding * encoding,gpointer sock,GError ** error)45 parse_request_headers (SoupMessage *msg, char *headers, guint headers_len,
46 SoupEncoding *encoding, gpointer sock, GError **error)
47 {
48 char *req_method, *req_path, *url;
49 SoupHTTPVersion version;
50 const char *req_host;
51 guint status;
52 SoupURI *uri;
53
54 status = soup_headers_parse_request (headers, headers_len,
55 msg->request_headers,
56 &req_method,
57 &req_path,
58 &version);
59 if (!SOUP_STATUS_IS_SUCCESSFUL (status)) {
60 if (status == SOUP_STATUS_MALFORMED) {
61 g_set_error_literal (error, SOUP_REQUEST_ERROR,
62 SOUP_REQUEST_ERROR_PARSING,
63 _("Could not parse HTTP request"));
64 }
65 return status;
66 }
67
68 g_object_set (G_OBJECT (msg),
69 SOUP_MESSAGE_METHOD, req_method,
70 SOUP_MESSAGE_HTTP_VERSION, version,
71 NULL);
72 g_free (req_method);
73
74 /* Handle request body encoding */
75 *encoding = soup_message_headers_get_encoding (msg->request_headers);
76 if (*encoding == SOUP_ENCODING_UNRECOGNIZED) {
77 if (soup_message_headers_get_list (msg->request_headers, "Transfer-Encoding"))
78 return SOUP_STATUS_NOT_IMPLEMENTED;
79 else
80 return SOUP_STATUS_BAD_REQUEST;
81 }
82
83 /* Generate correct context for request */
84 req_host = soup_message_headers_get_one (msg->request_headers, "Host");
85 if (req_host && strchr (req_host, '/')) {
86 g_free (req_path);
87 return SOUP_STATUS_BAD_REQUEST;
88 }
89
90 if (!strcmp (req_path, "*") && req_host) {
91 /* Eg, "OPTIONS * HTTP/1.1" */
92 url = g_strdup_printf ("%s://%s",
93 soup_socket_is_ssl (sock) ? "https" : "http",
94 req_host);
95 uri = soup_uri_new (url);
96 if (uri)
97 soup_uri_set_path (uri, "*");
98 g_free (url);
99 } else if (msg->method == SOUP_METHOD_CONNECT) {
100 /* Authority */
101 uri = parse_connect_authority (req_path);
102 } else if (*req_path != '/') {
103 /* Absolute URI */
104 uri = soup_uri_new (req_path);
105 } else if (req_host) {
106 url = g_strdup_printf ("%s://%s%s",
107 soup_socket_is_ssl (sock) ? "https" : "http",
108 req_host, req_path);
109 uri = soup_uri_new (url);
110 g_free (url);
111 } else if (soup_message_get_http_version (msg) == SOUP_HTTP_1_0) {
112 /* No Host header, no AbsoluteUri */
113 SoupAddress *addr = soup_socket_get_local_address (sock);
114
115 uri = soup_uri_new (NULL);
116 soup_uri_set_scheme (uri, soup_socket_is_ssl (sock) ?
117 SOUP_URI_SCHEME_HTTPS :
118 SOUP_URI_SCHEME_HTTP);
119 soup_uri_set_host (uri, soup_address_get_physical (addr));
120 soup_uri_set_port (uri, soup_address_get_port (addr));
121 soup_uri_set_path (uri, req_path);
122 } else
123 uri = NULL;
124
125 g_free (req_path);
126
127 if (!uri || !uri->host) {
128 if (uri)
129 soup_uri_free (uri);
130 return SOUP_STATUS_BAD_REQUEST;
131 }
132
133 soup_message_set_uri (msg, uri);
134 soup_uri_free (uri);
135
136 return SOUP_STATUS_OK;
137 }
138
139 static void
handle_partial_get(SoupMessage * msg)140 handle_partial_get (SoupMessage *msg)
141 {
142 SoupRange *ranges;
143 int nranges;
144 SoupBuffer *full_response;
145 guint status;
146
147 /* Make sure the message is set up right for us to return a
148 * partial response; it has to be a GET, the status must be
149 * 200 OK (and in particular, NOT already 206 Partial
150 * Content), and the SoupServer must have already filled in
151 * the response body
152 */
153 if (msg->method != SOUP_METHOD_GET ||
154 msg->status_code != SOUP_STATUS_OK ||
155 soup_message_headers_get_encoding (msg->response_headers) !=
156 SOUP_ENCODING_CONTENT_LENGTH ||
157 msg->response_body->length == 0 ||
158 !soup_message_body_get_accumulate (msg->response_body))
159 return;
160
161 /* Oh, and there has to have been a valid Range header on the
162 * request, of course.
163 */
164 status = soup_message_headers_get_ranges_internal (msg->request_headers,
165 msg->response_body->length,
166 TRUE,
167 &ranges, &nranges);
168 if (status == SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE) {
169 soup_message_set_status (msg, status);
170 soup_message_body_truncate (msg->response_body);
171 return;
172 } else if (status != SOUP_STATUS_PARTIAL_CONTENT)
173 return;
174
175 full_response = soup_message_body_flatten (msg->response_body);
176 if (!full_response) {
177 soup_message_headers_free_ranges (msg->request_headers, ranges);
178 return;
179 }
180
181 soup_message_set_status (msg, SOUP_STATUS_PARTIAL_CONTENT);
182 soup_message_body_truncate (msg->response_body);
183
184 if (nranges == 1) {
185 SoupBuffer *range_buf;
186
187 /* Single range, so just set Content-Range and fix the body. */
188
189 soup_message_headers_set_content_range (msg->response_headers,
190 ranges[0].start,
191 ranges[0].end,
192 full_response->length);
193 range_buf = soup_buffer_new_subbuffer (full_response,
194 ranges[0].start,
195 ranges[0].end - ranges[0].start + 1);
196 soup_message_body_append_buffer (msg->response_body, range_buf);
197 soup_buffer_free (range_buf);
198 } else {
199 SoupMultipart *multipart;
200 SoupMessageHeaders *part_headers;
201 SoupBuffer *part_body;
202 const char *content_type;
203 int i;
204
205 /* Multiple ranges, so build a multipart/byteranges response
206 * to replace msg->response_body with.
207 */
208
209 multipart = soup_multipart_new ("multipart/byteranges");
210 content_type = soup_message_headers_get_one (msg->response_headers,
211 "Content-Type");
212 for (i = 0; i < nranges; i++) {
213 part_headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
214 if (content_type) {
215 soup_message_headers_append (part_headers,
216 "Content-Type",
217 content_type);
218 }
219 soup_message_headers_set_content_range (part_headers,
220 ranges[i].start,
221 ranges[i].end,
222 full_response->length);
223 part_body = soup_buffer_new_subbuffer (full_response,
224 ranges[i].start,
225 ranges[i].end - ranges[i].start + 1);
226 soup_multipart_append_part (multipart, part_headers,
227 part_body);
228 soup_message_headers_free (part_headers);
229 soup_buffer_free (part_body);
230 }
231
232 soup_multipart_to_message (multipart, msg->response_headers,
233 msg->response_body);
234 soup_multipart_free (multipart);
235 }
236
237 soup_buffer_free (full_response);
238 soup_message_headers_free_ranges (msg->request_headers, ranges);
239 }
240
241 static void
get_response_headers(SoupMessage * msg,GString * headers,SoupEncoding * encoding,gpointer user_data)242 get_response_headers (SoupMessage *msg, GString *headers,
243 SoupEncoding *encoding, gpointer user_data)
244 {
245 SoupEncoding claimed_encoding;
246 SoupMessageHeadersIter iter;
247 const char *name, *value;
248
249 if (msg->status_code == 0)
250 soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
251
252 handle_partial_get (msg);
253
254 g_string_append_printf (headers, "HTTP/1.%c %d %s\r\n",
255 soup_message_get_http_version (msg) == SOUP_HTTP_1_0 ? '0' : '1',
256 msg->status_code, msg->reason_phrase);
257
258 claimed_encoding = soup_message_headers_get_encoding (msg->response_headers);
259 if ((msg->method == SOUP_METHOD_HEAD ||
260 msg->status_code == SOUP_STATUS_NO_CONTENT ||
261 msg->status_code == SOUP_STATUS_NOT_MODIFIED ||
262 SOUP_STATUS_IS_INFORMATIONAL (msg->status_code)) ||
263 (msg->method == SOUP_METHOD_CONNECT &&
264 SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)))
265 *encoding = SOUP_ENCODING_NONE;
266 else
267 *encoding = claimed_encoding;
268
269 if (claimed_encoding == SOUP_ENCODING_CONTENT_LENGTH &&
270 !soup_message_headers_get_content_length (msg->response_headers)) {
271 soup_message_headers_set_content_length (msg->response_headers,
272 msg->response_body->length);
273 }
274
275 soup_message_headers_iter_init (&iter, msg->response_headers);
276 while (soup_message_headers_iter_next (&iter, &name, &value))
277 g_string_append_printf (headers, "%s: %s\r\n", name, value);
278 g_string_append (headers, "\r\n");
279 }
280
281 void
soup_message_read_request(SoupMessage * msg,SoupSocket * sock,gboolean use_thread_context,SoupMessageCompletionFn completion_cb,gpointer user_data)282 soup_message_read_request (SoupMessage *msg,
283 SoupSocket *sock,
284 gboolean use_thread_context,
285 SoupMessageCompletionFn completion_cb,
286 gpointer user_data)
287 {
288 GMainContext *async_context;
289 GIOStream *iostream;
290
291 if (use_thread_context)
292 async_context = g_main_context_ref_thread_default ();
293 else {
294 g_object_get (sock,
295 SOUP_SOCKET_ASYNC_CONTEXT, &async_context,
296 NULL);
297 if (!async_context)
298 async_context = g_main_context_ref (g_main_context_default ());
299 }
300
301 iostream = soup_socket_get_iostream (sock);
302
303 soup_message_io_server (msg, iostream, async_context,
304 get_response_headers,
305 parse_request_headers,
306 sock,
307 completion_cb, user_data);
308 g_main_context_unref (async_context);
309 }
310