Moved protocols to proto/
[people/lynusvaz/gpxe.git] / src / proto / http.c
1 #include "etherboot.h"
2 #include "http.h"
3
4 #ifdef DOWNLOAD_PROTO_HTTP
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         int (*fnc)(unsigned char *data, int block, int len, int eof);
21         char *send_buffer;
22         char *recv_buffer;
23         int send_length;
24         int recv_length;
25         int bytes_sent;
26         int block;
27         int bytes_received;
28         enum { RESULT_CODE, HEADER, DATA, ERROR, MOVED } recv_state;
29         int rc;
30         char location[MAX_URL+1];
31 };
32
33 static int send_tcp_request(int length, void *buffer, void *ptr) {
34         struct send_recv_state *state = (struct send_recv_state *)ptr;
35
36         if (length > state->send_length - state->bytes_sent)
37                 length = state->send_length - state->bytes_sent;
38         memcpy(buffer, state->send_buffer + state->bytes_sent, length);
39         state->bytes_sent += length;
40         return (length);
41 }
42
43 /**************************************************************************
44 RECV_TCP_CALLBACK - Receive data using TCP
45 **************************************************************************/
46 static int recv_tcp_request(int length, const void *buffer, void *ptr) {
47         struct send_recv_state *state = (struct send_recv_state *)ptr;
48
49         /* Assume that the lines in an HTTP header do not straddle a packet */
50         /* boundary. This is probably a reasonable assumption */
51         if (state->recv_state == RESULT_CODE) {
52                 while (length > 0) {
53                         /* Find HTTP result code */
54                         if (*(const char *)buffer == ' ') {
55                                 const char *ptr = ((const char *)buffer) + 1;
56                                 int rc = strtoul(ptr, &ptr, 10);
57                                 if (ptr >= (const char *)buffer + length) {
58                                         state->recv_state = ERROR;
59                                         return 0;
60                                 }
61                                 state->rc = rc;
62                                 state->recv_state = HEADER;
63                                 goto header;
64                         }
65                         ++(const char *)buffer;
66                         length--;
67                 }
68                 state->recv_state = ERROR;
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 *ptr = state->location;
77                                 int i;
78                                 memcpy(ptr, buffer + 10, MAX_URL);
79                                 for (i = 0; i < MAX_URL && *ptr > ' ';
80                                      i++, ptr++);
81                                 *ptr = '\000';
82                                 state->recv_state = MOVED;
83                                 return 1;
84                         }
85                         /* Find beginning of line */
86                         while (length > 0) {
87                                 length--;
88                                 if (*((const char *)buffer)++ == '\n')
89                                         break;
90                         }
91                         /* Check for end of header */
92                         if (length >= 2 && !memcmp(buffer, "\r\n", 2)) {
93                                 state->recv_state = DATA;
94                                 buffer += 2;
95                                 length -= 2;
96                                 break;
97                         }
98                 }
99         }
100         if (state->recv_state == DATA) {
101                 state->bytes_received += length;
102                 while (length > 0) {
103                         int copy_length = BLOCKSIZE - state->recv_length;
104                         if (copy_length > length)
105                                 copy_length = length;
106                         memcpy(state->recv_buffer + state->recv_length,
107                                buffer, copy_length);
108                         if ((state->recv_length += copy_length) == BLOCKSIZE) {
109                                 if (!state->fnc(state->recv_buffer,
110                                                 ++state->block, BLOCKSIZE, 0))
111                                         return 0;
112                                 state->recv_length = 0;
113                         }
114                         length -= copy_length;
115                         buffer += copy_length;
116                 }
117         }
118         return 1;
119 }
120
121 /**************************************************************************
122 HTTP_GET - Get data using HTTP
123 **************************************************************************/
124 int http(const char *url,
125                 int (*fnc)(unsigned char *, unsigned int, unsigned int, int)) {
126         static const char GET[] = "GET /%s HTTP/1.0\r\n\r\n";
127         static char recv_buffer[BLOCKSIZE];
128         in_addr destip;
129         int port;
130         int length;
131         struct send_recv_state state;
132
133         state.fnc = fnc;
134         state.rc = -1;
135         state.block = 0;
136         state.recv_buffer = recv_buffer;
137         length = strlen(url);
138         if (length <= MAX_URL) {
139                 memcpy(state.location, url, length+1);
140                 destip = arptable[ARP_SERVER].ipaddr;
141                 port = url_port;
142                 if (port == -1)
143                   port = 80;
144                 goto first_time;
145
146                 do {
147                         state.rc = -1;
148                         state.block = 0;
149                         url = state.location;
150                         if (memcmp("http://", url, 7))
151                                 break;
152                         url += 7;
153                         length = inet_aton(url, &destip);
154                         if (!length) {
155                                 /* As we do not have support for DNS, assume*/
156                                 /* that HTTP redirects always point to the */
157                                 /* same machine */
158                                 if (state.recv_state == MOVED) {
159                                         while (*url &&
160                                         *url != ':' && *url != '/') url++;
161                                 } else {
162                                         break;
163                                 }
164                         }
165                         if (*(url += length) == ':') {
166                                 port = strtoul(url, &url, 10);
167                         } else {
168                                 port = 80;
169                         }
170                         if (!*url)
171                                 url = "/";
172                         if (*url != '/')
173                                 break;
174                         url++;
175
176                 first_time:
177                         length = strlen(url);
178                         state.send_length = sizeof(GET) - 3 + length;
179                         
180                         { char buf[state.send_length + 1];
181                         sprintf(state.send_buffer = buf, GET, url);
182                         state.bytes_sent = 0;
183                         
184                         state.bytes_received = 0;
185                         state.recv_state = RESULT_CODE;
186                         
187                         state.recv_length = 0;
188                         tcp_transaction(destip.s_addr, 80, &state,
189                                         send_tcp_request, recv_tcp_request);
190                         }
191                 } while (state.recv_state == MOVED);
192         } else {
193                 memcpy(state.location, url, MAX_URL);
194                 state.location[MAX_URL] = '\000';
195         }
196
197         if (state.rc == 200) {
198                 return fnc(recv_buffer, ++state.block, state.recv_length, 1);
199         } else {
200                 printf("Failed to download %s (rc = %d)\n",
201                        state.location, state.rc);
202                 return 0;
203         }
204 }
205
206 #endif /* DOWNLOAD_PROTO_HTTP */