mirror of
https://github.com/xcat2/xNBA.git
synced 2024-12-14 23:31:39 +00:00
207 lines
5.6 KiB
C
207 lines
5.6 KiB
C
#include "etherboot.h"
|
|
#include "http.h"
|
|
|
|
#ifdef DOWNLOAD_PROTO_HTTP
|
|
|
|
/* The block size is currently chosen to be 512 bytes. This means, we can
|
|
allocate the receive buffer on the stack, but it results in a noticeable
|
|
performance penalty.
|
|
This is what needs to be done in order to increase the block size:
|
|
- size negotiation needs to be implemented in TCP
|
|
- the buffer needs to be allocated on the heap
|
|
- path MTU discovery needs to be implemented
|
|
*/ /***/ /* FIXME */
|
|
#define BLOCKSIZE TFTP_DEFAULTSIZE_PACKET
|
|
|
|
/**************************************************************************
|
|
SEND_TCP_CALLBACK - Send data using TCP
|
|
**************************************************************************/
|
|
struct send_recv_state {
|
|
int (*fnc)(unsigned char *data, int block, int len, int eof);
|
|
char *send_buffer;
|
|
char *recv_buffer;
|
|
int send_length;
|
|
int recv_length;
|
|
int bytes_sent;
|
|
int block;
|
|
int bytes_received;
|
|
enum { RESULT_CODE, HEADER, DATA, ERROR, MOVED } recv_state;
|
|
int rc;
|
|
char location[MAX_URL+1];
|
|
};
|
|
|
|
static int send_tcp_request(int length, void *buffer, void *ptr) {
|
|
struct send_recv_state *state = (struct send_recv_state *)ptr;
|
|
|
|
if (length > state->send_length - state->bytes_sent)
|
|
length = state->send_length - state->bytes_sent;
|
|
memcpy(buffer, state->send_buffer + state->bytes_sent, length);
|
|
state->bytes_sent += length;
|
|
return (length);
|
|
}
|
|
|
|
/**************************************************************************
|
|
RECV_TCP_CALLBACK - Receive data using TCP
|
|
**************************************************************************/
|
|
static int recv_tcp_request(int length, const void *buffer, void *ptr) {
|
|
struct send_recv_state *state = (struct send_recv_state *)ptr;
|
|
|
|
/* Assume that the lines in an HTTP header do not straddle a packet */
|
|
/* boundary. This is probably a reasonable assumption */
|
|
if (state->recv_state == RESULT_CODE) {
|
|
while (length > 0) {
|
|
/* Find HTTP result code */
|
|
if (*(const char *)buffer == ' ') {
|
|
const char *ptr = ((const char *)buffer) + 1;
|
|
int rc = strtoul(ptr, &ptr, 10);
|
|
if (ptr >= (const char *)buffer + length) {
|
|
state->recv_state = ERROR;
|
|
return 0;
|
|
}
|
|
state->rc = rc;
|
|
state->recv_state = HEADER;
|
|
goto header;
|
|
}
|
|
++(const char *)buffer;
|
|
length--;
|
|
}
|
|
state->recv_state = ERROR;
|
|
return 0;
|
|
}
|
|
if (state->recv_state == HEADER) {
|
|
header: while (length > 0) {
|
|
/* Check for HTTP redirect */
|
|
if (state->rc >= 300 && state->rc < 400 &&
|
|
!memcmp(buffer, "Location: ", 10)) {
|
|
char *ptr = state->location;
|
|
int i;
|
|
memcpy(ptr, buffer + 10, MAX_URL);
|
|
for (i = 0; i < MAX_URL && *ptr > ' ';
|
|
i++, ptr++);
|
|
*ptr = '\000';
|
|
state->recv_state = MOVED;
|
|
return 1;
|
|
}
|
|
/* Find beginning of line */
|
|
while (length > 0) {
|
|
length--;
|
|
if (*((const char *)buffer)++ == '\n')
|
|
break;
|
|
}
|
|
/* Check for end of header */
|
|
if (length >= 2 && !memcmp(buffer, "\r\n", 2)) {
|
|
state->recv_state = DATA;
|
|
buffer += 2;
|
|
length -= 2;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (state->recv_state == DATA) {
|
|
state->bytes_received += length;
|
|
while (length > 0) {
|
|
int copy_length = BLOCKSIZE - state->recv_length;
|
|
if (copy_length > length)
|
|
copy_length = length;
|
|
memcpy(state->recv_buffer + state->recv_length,
|
|
buffer, copy_length);
|
|
if ((state->recv_length += copy_length) == BLOCKSIZE) {
|
|
if (!state->fnc(state->recv_buffer,
|
|
++state->block, BLOCKSIZE, 0))
|
|
return 0;
|
|
state->recv_length = 0;
|
|
}
|
|
length -= copy_length;
|
|
buffer += copy_length;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/**************************************************************************
|
|
HTTP_GET - Get data using HTTP
|
|
**************************************************************************/
|
|
int http(const char *url,
|
|
int (*fnc)(unsigned char *, unsigned int, unsigned int, int)) {
|
|
static const char GET[] = "GET /%s HTTP/1.0\r\n\r\n";
|
|
static char recv_buffer[BLOCKSIZE];
|
|
in_addr destip;
|
|
int port;
|
|
int length;
|
|
struct send_recv_state state;
|
|
|
|
state.fnc = fnc;
|
|
state.rc = -1;
|
|
state.block = 0;
|
|
state.recv_buffer = recv_buffer;
|
|
length = strlen(url);
|
|
if (length <= MAX_URL) {
|
|
memcpy(state.location, url, length+1);
|
|
destip = arptable[ARP_SERVER].ipaddr;
|
|
port = url_port;
|
|
if (port == -1)
|
|
port = 80;
|
|
goto first_time;
|
|
|
|
do {
|
|
state.rc = -1;
|
|
state.block = 0;
|
|
url = state.location;
|
|
if (memcmp("http://", url, 7))
|
|
break;
|
|
url += 7;
|
|
length = inet_aton(url, &destip);
|
|
if (!length) {
|
|
/* As we do not have support for DNS, assume*/
|
|
/* that HTTP redirects always point to the */
|
|
/* same machine */
|
|
if (state.recv_state == MOVED) {
|
|
while (*url &&
|
|
*url != ':' && *url != '/') url++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (*(url += length) == ':') {
|
|
port = strtoul(url, &url, 10);
|
|
} else {
|
|
port = 80;
|
|
}
|
|
if (!*url)
|
|
url = "/";
|
|
if (*url != '/')
|
|
break;
|
|
url++;
|
|
|
|
first_time:
|
|
length = strlen(url);
|
|
state.send_length = sizeof(GET) - 3 + length;
|
|
|
|
{ char buf[state.send_length + 1];
|
|
sprintf(state.send_buffer = buf, GET, url);
|
|
state.bytes_sent = 0;
|
|
|
|
state.bytes_received = 0;
|
|
state.recv_state = RESULT_CODE;
|
|
|
|
state.recv_length = 0;
|
|
tcp_transaction(destip.s_addr, 80, &state,
|
|
send_tcp_request, recv_tcp_request);
|
|
}
|
|
} while (state.recv_state == MOVED);
|
|
} else {
|
|
memcpy(state.location, url, MAX_URL);
|
|
state.location[MAX_URL] = '\000';
|
|
}
|
|
|
|
if (state.rc == 200) {
|
|
return fnc(recv_buffer, ++state.block, state.recv_length, 1);
|
|
} else {
|
|
printf("Failed to download %s (rc = %d)\n",
|
|
state.location, state.rc);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
#endif /* DOWNLOAD_PROTO_HTTP */
|