/**************************************************************************** * FCE Ultra * Nintendo Wii/Gamecube Port * * Tantric December 2008 * * http.cpp * * HTTP operations * Written by dhewg/bushing, modified by Tantric ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include "menu.h" #include "http.h" static s32 tcp_socket(void) { s32 s, res; s = net_socket(PF_INET, SOCK_STREAM, 0); if (s < 0) { return s; } res = net_fcntl(s, F_GETFL, 0); if (res < 0) { net_close(s); return res; } res = net_fcntl(s, F_SETFL, res | 4); if (res < 0) { net_close(s); return res; } return s; } static s32 tcp_connect(char *host, const u16 port) { struct hostent *hp; struct sockaddr_in sa; s32 s, res; s64 t; hp = net_gethostbyname(host); if (!hp || !(hp->h_addrtype == PF_INET)) { return errno; } s = tcp_socket(); if (s < 0) return s; memset(&sa, 0, sizeof(struct sockaddr_in)); sa.sin_family = PF_INET; sa.sin_len = sizeof(struct sockaddr_in); sa.sin_port = htons(port); memcpy((char *) &sa.sin_addr, hp->h_addr_list[0], hp->h_length); t = gettime(); while (true) { if (ticks_to_millisecs(diff_ticks(t, gettime())) > TCP_CONNECT_TIMEOUT) { net_close(s); return -ETIMEDOUT; } res = net_connect(s, (struct sockaddr *) &sa, sizeof(struct sockaddr_in)); if (res < 0) { if (res == -EISCONN) break; if (res == -EINPROGRESS || res == -EALREADY) { usleep(20 * 1000); continue; } net_close(s); return res; } break; } return s; } static char * tcp_readln(const s32 s, const u16 max_length, const u64 start_time, const u16 timeout) { char *buf; u16 c; s32 res; char *ret; buf = (char *) malloc(max_length); c = 0; ret = NULL; while (true) { if (ticks_to_millisecs(diff_ticks(start_time, gettime())) > timeout) break; res = net_read(s, &buf[c], 1); if ((res == 0) || (res == -EAGAIN)) { usleep(20 * 1000); continue; } if (res < 0) { break; } if ((c > 0) && (buf[c - 1] == '\r') && (buf[c] == '\n')) { if (c == 1) { ret = strdup(""); break; } ret = strndup(buf, c - 1); break; } c++; if (c == max_length) break; } free(buf); return ret; } static int tcp_read(const s32 s, u8 **buffer, const u32 length) { u8 *p; u32 step, left, block, received; s64 t; s32 res; step = 0; p = *buffer; left = length; received = 0; t = gettime(); while (left) { if (ticks_to_millisecs(diff_ticks(t, gettime())) > TCP_BLOCK_RECV_TIMEOUT) { break; } block = left; if (block > 2048) block = 2048; res = net_read(s, p, block); if ((res == 0) || (res == -EAGAIN)) { usleep(20 * 1000); continue; } if (res < 0) { break; } received += res; left -= res; p += res; if ((received / TCP_BLOCK_SIZE) > step) { t = gettime(); step++; } } return left == 0; } static int tcp_write(const s32 s, const u8 *buffer, const u32 length) { const u8 *p; u32 step, left, block, sent; s64 t; s32 res; step = 0; p = buffer; left = length; sent = 0; t = gettime(); while (left) { if (ticks_to_millisecs(diff_ticks(t, gettime())) > TCP_BLOCK_SEND_TIMEOUT) { break; } block = left; if (block > 2048) block = 2048; res = net_write(s, p, block); if ((res == 0) || (res == -56)) { usleep(20 * 1000); continue; } if (res < 0) { break; } sent += res; left -= res; p += res; if ((sent / TCP_BLOCK_SIZE) > step) { t = gettime(); step++; } } return left == 0; } static bool http_split_url(char **host, char **path, const char *url) { const char *p; char *c; if (strncasecmp(url, "http://", 7)) return false; p = url + 7; c = strchr(p, '/'); if (c[0] == 0) return false; *host = strndup(p, c - p); *path = strdup(c); return true; } /**************************************************************************** * http_request * Retrieves the specified URL, and stores it in the specified file or buffer ***************************************************************************/ bool http_request(const char *url, FILE * hfile, u8 * buffer, const u32 max_size) { int res = 0; char *http_host; u16 http_port; char *http_path; http_res result; u32 http_status; u32 content_length = 0; int linecount; if (!http_split_url(&http_host, &http_path, url)) return false; if (hfile == NULL && buffer == NULL) return false; http_port = 80; http_status = 404; int s = tcp_connect(http_host, http_port); if (s < 0) { result = HTTPR_ERR_CONNECT; return false; } char *request = (char *) malloc(1024); char *r = request; r += sprintf(r, "GET %s HTTP/1.1\r\n", http_path); r += sprintf(r, "Host: %s\r\n", http_host); r += sprintf(r, "Cache-Control: no-cache\r\n\r\n"); res = tcp_write(s, (u8 *) request, strlen(request)); free(request); for (linecount = 0; linecount < 32; linecount++) { char *line = tcp_readln(s, 0xff, gettime(), (const u16) HTTP_TIMEOUT); if (!line) { http_status = 404; result = HTTPR_ERR_REQUEST; break; } if (strlen(line) < 1) { free(line); line = NULL; break; } sscanf(line, "HTTP/1.%*u %u", &http_status); sscanf(line, "Content-Length: %u", &content_length); free(line); line = NULL; } if (linecount == 32 || !content_length) http_status = 404; if (http_status != 200) { result = HTTPR_ERR_STATUS; net_close(s); return false; } if (content_length > max_size) { result = HTTPR_ERR_TOOBIG; net_close(s); return false; } if (buffer != NULL) { res = tcp_read(s, &buffer, content_length); } else { // read into file u32 bufSize = (1026 * 256); u32 bytesLeft = content_length; u32 readSize; ShowProgress("Downloading...", 0, content_length); u8 * fbuffer = (u8 *) malloc(bufSize); if(fbuffer) { while (bytesLeft > 0) { if (bytesLeft < bufSize) readSize = bytesLeft; else readSize = bufSize; res = tcp_read(s, &fbuffer, readSize); if (!res) break; res = fwrite(fbuffer, 1, readSize, hfile); if (!res) break; bytesLeft -= readSize; ShowProgress("Downloading...", (content_length - bytesLeft), content_length); } free(fbuffer); } CancelAction(); } net_close(s); if (!res) { result = HTTPR_ERR_RECEIVE; return false; } result = HTTPR_OK; return true; }