6f47fc14dba801dcc6efc23ec481cf3df9ca3932
[people/pcmattman/gpxe.git] / src / proto / http.c
1 #include "proto.h"
2 #include "tcp.h"
3 #include "url.h"
4 #include "etherboot.h"
5
6 /* The block size is currently chosen to be 512 bytes. This means, we can
7    allocate the receive buffer on the stack, but it results in a noticeable
8    performance penalty.
9    This is what needs to be done in order to increase the block size:
10      - size negotiation needs to be implemented in TCP
11      - the buffer needs to be allocated on the heap
12      - path MTU discovery needs to be implemented
13 */ /***/ /* FIXME */
14 #define BLOCKSIZE TFTP_DEFAULTSIZE_PACKET
15
16 /**************************************************************************
17 SEND_TCP_CALLBACK - Send data using TCP
18 **************************************************************************/
19 struct send_recv_state {
20         struct buffer *recv_buffer;
21         char *send_buffer;
22         int send_length;
23         int bytes_sent;
24         int bytes_received;
25         enum { RESULT_CODE, HEADER, DATA, ERROR, MOVED } recv_state;
26         int rc;
27         char *url;
28 };
29
30 static int send_tcp_request(int length, void *buffer, void *ptr) {
31         struct send_recv_state *state = (struct send_recv_state *)ptr;
32
33         if (length > state->send_length - state->bytes_sent)
34                 length = state->send_length - state->bytes_sent;
35         memcpy(buffer, state->send_buffer + state->bytes_sent, length);
36         state->bytes_sent += length;
37         return (length);
38 }
39
40 /**************************************************************************
41 RECV_TCP_CALLBACK - Receive data using TCP
42 **************************************************************************/
43 static int recv_tcp_request(int length, const void *buffer, void *ptr) {
44         struct send_recv_state *state = (struct send_recv_state *)ptr;
45
46         /* Assume that the lines in an HTTP header do not straddle a packet */
47         /* boundary. This is probably a reasonable assumption */
48         if (state->recv_state == RESULT_CODE) {
49                 while (length > 0) {
50                         /* Find HTTP result code */
51                         if (*(const char *)buffer == ' ') {
52                                 const char *ptr = ((const char *)buffer) + 1;
53                                 int rc = strtoul(ptr, &ptr, 10);
54                                 if (ptr >= (const char *)buffer + length) {
55                                         state->recv_state = ERROR;
56                                         DBG ( "HTTP got bad result code\n" );
57                                         return 0;
58                                 }
59                                 state->rc = rc;
60                                 state->recv_state = HEADER;
61                                 DBG ( "HTTP got result code %d\n", rc );
62                                 goto header;
63                         }
64                         ++(const char *)buffer;
65                         length--;
66                 }
67                 state->recv_state = ERROR;
68                 DBG ( "HTTP got no result code\n" );
69                 return 0;
70         }
71         if (state->recv_state == HEADER) {
72         header: while (length > 0) {
73                         /* Check for HTTP redirect */
74                         if (state->rc >= 300 && state->rc < 400 &&
75                             !memcmp(buffer, "Location: ", 10)) {
76                                 char *p;
77                                 
78                                 state->url = p = ( char * ) buffer + 10;
79                                 while ( *p > ' ' ) {
80                                         p++;
81                                 }
82                                 *p = '\0';
83                                 state->recv_state = MOVED;
84                                 DBG ( "HTTP got redirect to %s\n",
85                                       state->url );
86                                 return 1;
87                         }
88                         /* Find beginning of line */
89                         while (length > 0) {
90                                 length--;
91                                 if (*((const char *)buffer)++ == '\n')
92                                         break;
93                         }
94                         /* Check for end of header */
95                         if (length >= 2 && !memcmp(buffer, "\r\n", 2)) {
96                                 state->recv_state = DATA;
97                                 buffer += 2;
98                                 length -= 2;
99                                 break;
100                         }
101                 }
102         }
103         if (state->recv_state == DATA) {
104                 DBG2 ( "HTTP received %d bytes\n", length );
105                 if ( ! fill_buffer ( state->recv_buffer, buffer,
106                                      state->bytes_received, length ) )
107                         return 0;
108                 state->bytes_received += length;
109         }
110         return 1;
111 }
112
113 /**************************************************************************
114 HTTP_GET - Get data using HTTP
115 **************************************************************************/
116 static int http ( char *url, struct sockaddr_in *server __unused,
117                   char *file __unused, struct buffer *buffer ) {
118         struct protocol *proto;
119         struct sockaddr_in http_server = *server;
120         char *filename;
121         static const char GET[] = "GET /%s HTTP/1.0\r\n\r\n";
122         struct send_recv_state state;
123         int length;
124
125         state.rc = -1;
126         state.url = url;
127         state.recv_buffer = buffer;
128         while ( 1 ) {
129                 length = strlen ( filename ) + strlen ( GET );
130                 {
131                         char send_buf[length];
132
133                         sprintf ( send_buf, GET, filename );
134                         state.send_buffer = send_buf;
135                         state.send_length = strlen ( send_buf );
136                         state.bytes_sent = 0;
137                         
138                         state.bytes_received = 0;
139                         state.recv_state = RESULT_CODE;
140                         
141                         tcp_transaction ( server->sin_addr.s_addr,
142                                           server->sin_port, &state,
143                                           send_tcp_request, recv_tcp_request );
144                 }
145
146                 if ( state.recv_state == MOVED ) {
147                         if ( ! parse_url ( state.url, &proto,
148                                            &http_server, &filename ) ) {
149                                 printf ( "Invalid redirect URL %s\n",
150                                          state.url );
151                                 return 0;
152                         }
153                         continue;
154                 }
155                 
156                 break;
157         }
158
159         if ( state.rc != 200 ) {
160                 printf ( "Failed to download %s (rc = %d)\n",
161                          state.url, state.rc );
162                 return 0;
163         }
164
165         return 1;
166 }
167
168 static struct protocol http_protocol __protocol = {
169         .name = "http",
170         .default_port = 80,
171         .load = http,
172 };