Fixed HTTP
[people/sha0/gpxe.git] / src / net / tcp / http.c
1 /*
2  * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 /**
20  * @file
21  *
22  * Hyper Text Transfer Protocol (HTTP)
23  *
24  */
25
26 #include <stddef.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <strings.h>
30 #include <vsprintf.h>
31 #include <assert.h>
32 #include <gpxe/async.h>
33 #include <gpxe/buffer.h>
34 #include <gpxe/http.h>
35
36 static inline struct http_request *
37 tcp_to_http ( struct tcp_application *app ) {
38         return container_of ( app, struct http_request, tcp );
39 }
40
41 /**
42  * Mark HTTP request as complete
43  *
44  * @v http              HTTP request
45  * @v rc                Return status code
46  *
47  */
48 static void http_done ( struct http_request *http, int rc ) {
49
50         /* Close TCP connection */
51         tcp_close ( &http->tcp );
52
53         /* Prevent further processing of any current packet */
54         http->rx_state = HTTP_RX_DEAD;
55
56         /* Free up any dynamically allocated storage */
57         empty_line_buffer ( &http->linebuf );
58
59         /* If we had a Content-Length, and the received content length
60          * isn't correct, flag an error
61          */
62         if ( http->content_length &&
63              ( http->content_length != http->buffer->fill ) ) {
64                 DBGC ( http, "HTTP %p incorrect length %zd, should be %zd\n",
65                        http, http->buffer->fill, http->content_length );
66                 rc = -EIO;
67         }
68
69         /* Mark async operation as complete */
70         async_done ( &http->aop, rc );
71 }
72
73 /**
74  * Convert HTTP response code to return status code
75  *
76  * @v response          HTTP response code
77  * @ret rc              Return status code
78  */
79 static int http_response_to_rc ( unsigned int response ) {
80         switch ( response ) {
81         case 200:
82                 return 0;
83         case 404:
84                 return -ENOENT;
85         case 403:
86                 return -EPERM;
87         default:
88                 return -EIO;
89         }
90 }
91
92 /**
93  * Handle HTTP response
94  *
95  * @v http              HTTP request
96  * @v response          HTTP response
97  */
98 static void http_rx_response ( struct http_request *http, char *response ) {
99         char *spc;
100         int rc = -EIO;
101
102         DBGC ( http, "HTTP %p response \"%s\"\n", http, response );
103
104         /* Check response starts with "HTTP/" */
105         if ( strncmp ( response, "HTTP/", 5 ) != 0 )
106                 goto err;
107
108         /* Locate and check response code */
109         spc = strchr ( response, ' ' );
110         if ( ! spc )
111                 goto err;
112         http->response = strtoul ( spc, NULL, 10 );
113         if ( ( rc = http_response_to_rc ( http->response ) ) != 0 )
114                 goto err;
115
116         /* Move to received headers */
117         http->rx_state = HTTP_RX_HEADER;
118         return;
119
120  err:
121         DBGC ( http, "HTTP %p bad response\n", http );
122         http_done ( http, rc );
123         return;
124 }
125
126 /**
127  * Handle HTTP Content-Length header
128  *
129  * @v http              HTTP request
130  * @v value             HTTP header value
131  * @ret rc              Return status code
132  */
133 static int http_rx_content_length ( struct http_request *http,
134                                     const char *value ) {
135         char *endp;
136
137         http->content_length = strtoul ( value, &endp, 10 );
138         if ( *endp != '\0' ) {
139                 DBGC ( http, "HTTP %p invalid Content-Length \"%s\"\n",
140                        http, value );
141                 return -EIO;
142         }
143
144         return 0;
145 }
146
147 /**
148  * An HTTP header handler
149  *
150  */
151 struct http_header_handler {
152         /** Name (e.g. "Content-Length") */
153         const char *header;
154         /** Handle received header
155          *
156          * @v http      HTTP request
157          * @v value     HTTP header value
158          * @ret rc      Return status code
159          */
160         int ( * rx ) ( struct http_request *http, const char *value );
161 };
162
163 /** List of HTTP header handlers */
164 struct http_header_handler http_header_handlers[] = {
165         {
166                 .header = "Content-Length",
167                 .rx = http_rx_content_length,
168         },
169         { NULL, NULL }
170 };
171
172 /**
173  * Handle HTTP header
174  *
175  * @v http              HTTP request
176  * @v header            HTTP header
177  */
178 static void http_rx_header ( struct http_request *http, char *header ) {
179         struct http_header_handler *handler;
180         char *separator;
181         char *value;
182         int rc = -EIO;
183
184         /* An empty header line marks the transition to the data phase */
185         if ( ! header[0] ) {
186                 DBGC ( http, "HTTP %p start of data\n", http );
187                 empty_line_buffer ( &http->linebuf );
188                 http->rx_state = HTTP_RX_DATA;
189                 return;
190         }
191
192         DBGC ( http, "HTTP %p header \"%s\"\n", http, header );
193
194         /* Split header at the ": " */
195         separator = strstr ( header, ": " );
196         if ( ! separator )
197                 goto err;
198         *separator = '\0';
199         value = ( separator + 2 );
200
201         /* Hand off to header handler, if one exists */
202         for ( handler = http_header_handlers ; handler->header ; handler++ ) {
203                 if ( strcasecmp ( header, handler->header ) == 0 ) {
204                         if ( ( rc = handler->rx ( http, value ) ) != 0 )
205                                 goto err;
206                         break;
207                 }
208         }
209         return;
210
211  err:
212         DBGC ( http, "HTTP %p bad header\n", http );
213         http_done ( http, rc );
214         return;
215 }
216
217 /**
218  * Handle new data arriving via HTTP connection in the data phase
219  *
220  * @v http              HTTP request
221  * @v data              New data
222  * @v len               Length of new data
223  */
224 static void http_rx_data ( struct http_request *http,
225                            const char *data, size_t len ) {
226         int rc;
227
228         /* Fill data buffer */
229         if ( ( rc = fill_buffer ( http->buffer, data,
230                                   http->buffer->fill, len ) ) != 0 ) {
231                 DBGC ( http, "HTTP %p failed to fill data buffer: %s\n",
232                        http, strerror ( rc ) );
233                 http_done ( http, rc );
234                 return;
235         }
236
237         /* If we have reached the content-length, stop now */
238         if ( http->content_length &&
239              ( http->buffer->fill >= http->content_length ) ) {
240                 http_done ( http, 0 );
241         }
242 }
243
244 /**
245  * Handle new data arriving via HTTP connection
246  *
247  * @v http              HTTP request
248  * @v data              New data
249  * @v len               Length of new data
250  */
251 static void http_newdata ( struct tcp_application *app,
252                            void *data, size_t len ) {
253         struct http_request *http = tcp_to_http ( app );
254         const char *buf = data;
255         char *line;
256         int rc;
257
258         while ( len ) {
259                 if ( http->rx_state == HTTP_RX_DEAD ) {
260                         /* Do no further processing */
261                         return;
262                 } else if ( http->rx_state == HTTP_RX_DATA ) {
263                         /* Once we're into the data phase, just fill
264                          * the data buffer
265                          */
266                         http_rx_data ( http, buf, len );
267                         return;
268                 } else {
269                         /* In the other phases, buffer and process a
270                          * line at a time
271                          */
272                         if ( ( rc = line_buffer ( &http->linebuf, &buf,
273                                                   &len ) ) != 0 ) {
274                                 DBGC ( http, "HTTP %p could not buffer line: "
275                                        "%s\n", http, strerror ( rc ) );
276                                 http_done ( http, rc );
277                                 return;
278                         }
279                         if ( ( line = buffered_line ( &http->linebuf ) ) ) {
280                                 switch ( http->rx_state ) {
281                                 case HTTP_RX_RESPONSE:
282                                         http_rx_response ( http, line );
283                                         break;
284                                 case HTTP_RX_HEADER:
285                                         http_rx_header ( http, line );
286                                         break;
287                                 default:
288                                         assert ( 0 );
289                                         break;
290                                 }
291                         }
292                 }
293         }
294 }
295
296 /**
297  * Send HTTP data
298  *
299  * @v app               TCP application
300  * @v buf               Temporary data buffer
301  * @v len               Length of temporary data buffer
302  */
303 static void http_senddata ( struct tcp_application *app,
304                             void *buf, size_t len ) {
305         struct http_request *http = tcp_to_http ( app );
306
307         len = snprintf ( buf, len,
308                          "GET /%s HTTP/1.1\r\n"
309                          "User-Agent: gPXE/" VERSION "\r\n"
310                          "Host: %s\r\n"
311                          "\r\n", http->filename, http->hostname );
312         tcp_send ( app, ( buf + http->tx_offset ), ( len - http->tx_offset ) );
313 }
314
315 /**
316  * HTTP data acknowledged
317  *
318  * @v app               TCP application
319  * @v len               Length of acknowledged data
320  */
321 static void http_acked ( struct tcp_application *app, size_t len ) {
322         struct http_request *http = tcp_to_http ( app );
323
324         http->tx_offset += len;
325 }
326
327 /**
328  * HTTP connection closed by network stack
329  *
330  * @v app               TCP application
331  */
332 static void http_closed ( struct tcp_application *app, int rc ) {
333         struct http_request *http = tcp_to_http ( app );
334
335         DBGC ( http, "HTTP %p connection closed: %s\n",
336                http, strerror ( rc ) );
337         
338         http_done ( http, rc );
339 }
340
341 /** HTTP TCP operations */
342 static struct tcp_operations http_tcp_operations = {
343         .closed         = http_closed,
344         .acked          = http_acked,
345         .newdata        = http_newdata,
346         .senddata       = http_senddata,
347 };
348
349 /**
350  * Initiate a HTTP connection
351  *
352  * @v http      a HTTP request
353  */
354 struct async_operation * http_get ( struct http_request *http ) {
355         int rc;
356
357         http->tcp.tcp_op = &http_tcp_operations;
358         if ( ( rc = tcp_connect ( &http->tcp, &http->server, 0 ) ) != 0 )
359                 async_done ( &http->aop, rc );
360
361         return &http->aop;
362 }