/* Copyright (C) 2010-2016 The RetroArch team * * --------------------------------------------------------------------------------------- * The following license statement only applies to this file (net_http.c). * --------------------------------------------------------------------------------------- * * Permission is hereby granted, free of charge, * to any person obtaining a copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include enum { P_HEADER_TOP = 0, P_HEADER, P_BODY, P_BODY_CHUNKLEN, P_DONE, P_ERROR }; enum { T_FULL = 0, T_LEN, T_CHUNK }; struct http_t { int fd; int status; char part; char bodytype; bool error; size_t pos; size_t len; size_t buflen; char * data; }; struct http_connection_t { char *domain; char *location; char *urlcopy; char *scan; int port; }; static int net_http_new_socket(const char *domain, int port) { int ret; struct addrinfo *addr = NULL; int fd = socket_init((void**)&addr, port, domain, SOCKET_TYPE_STREAM); if (fd < 0) return -1; ret = socket_connect(fd, (void*)addr, true); freeaddrinfo_retro(addr); if (ret < 0) goto error; if (!socket_nonblock(fd)) goto error; return fd; error: socket_close(fd); return -1; } static void net_http_send_str(int fd, bool *error, const char *text) { if (*error) return; if (!socket_send_all_blocking(fd, text, strlen(text), true)) *error = true; } static char* urlencode(const char* url) { unsigned i; unsigned outpos = 0; unsigned outlen = 0; char *ret = NULL; for (i = 0; url[i] != '\0'; i++) { outlen++; if (url[i] == ' ') outlen += 2; } ret = (char*)malloc(outlen + 1); if (!ret) return NULL; for (i = 0; url[i]; i++) { if (url[i] == ' ') { ret[outpos++] = '%'; ret[outpos++] = '2'; ret[outpos++] = '0'; } else ret[outpos++] = url[i]; } ret[outpos] = '\0'; return ret; } struct http_connection_t *net_http_connection_new(const char *url) { char **domain = NULL; struct http_connection_t *conn = (struct http_connection_t*)calloc(1, sizeof(struct http_connection_t)); if (!conn) return NULL; conn->urlcopy = urlencode(url); if (!conn->urlcopy) goto error; if (strncmp(url, "http://", strlen("http://")) != 0) goto error; conn->scan = conn->urlcopy + strlen("http://"); domain = &conn->domain; *domain = conn->scan; return conn; error: if (conn->urlcopy) free(conn->urlcopy); conn->urlcopy = NULL; free(conn); return NULL; } bool net_http_connection_iterate(struct http_connection_t *conn) { if (!conn) return false; while (*conn->scan != '/' && *conn->scan != ':' && *conn->scan != '\0') conn->scan++; return true; } bool net_http_connection_done(struct http_connection_t *conn) { char **location = NULL; if (!conn) return false; location = &conn->location; if (*conn->scan == '\0') return false; *conn->scan = '\0'; conn->port = 80; if (*conn->scan == ':') { if (!isdigit((int)conn->scan[1])) return false; conn->port = strtoul(conn->scan + 1, &conn->scan, 10); if (*conn->scan != '/') return false; } *location = conn->scan + 1; return true; } void net_http_connection_free(struct http_connection_t *conn) { if (!conn) return; if (conn->urlcopy) free(conn->urlcopy); free(conn); } const char *net_http_connection_url(struct http_connection_t *conn) { return conn->urlcopy; } struct http_t *net_http_new(struct http_connection_t *conn) { bool error = false; int fd = -1; struct http_t *state = NULL; if (!conn) goto error; fd = net_http_new_socket(conn->domain, conn->port); if (fd < 0) goto error; error = false; /* This is a bit lazy, but it works. */ net_http_send_str(fd, &error, "GET /"); net_http_send_str(fd, &error, conn->location); net_http_send_str(fd, &error, " HTTP/1.1\r\n"); net_http_send_str(fd, &error, "Host: "); net_http_send_str(fd, &error, conn->domain); if (conn->port != 80) { char portstr[16]; portstr[0] = '\0'; snprintf(portstr, sizeof(portstr), ":%i", conn->port); net_http_send_str(fd, &error, portstr); } net_http_send_str(fd, &error, "\r\n"); net_http_send_str(fd, &error, "Connection: close\r\n"); net_http_send_str(fd, &error, "\r\n"); if (error) goto error; state = (struct http_t*)malloc(sizeof(struct http_t)); state->fd = fd; state->status = -1; state->data = NULL; state->part = P_HEADER_TOP; state->bodytype= T_FULL; state->error = false; state->pos = 0; state->len = 0; state->buflen = 512; state->data = (char*)malloc(state->buflen); if (!state->data) goto error; return state; error: if (fd >= 0) socket_close(fd); if (state) free(state); return NULL; } int net_http_fd(struct http_t *state) { if (!state) return -1; return state->fd; } bool net_http_update(struct http_t *state, size_t* progress, size_t* total) { ssize_t newlen = 0; if (!state || state->error) goto fail; if (state->part < P_BODY) { if (state->error) newlen = -1; else newlen = socket_receive_all_nonblocking(state->fd, &state->error, (uint8_t*)state->data + state->pos, state->buflen - state->pos); if (newlen < 0) goto fail; if (state->pos + newlen >= state->buflen - 64) { state->buflen *= 2; state->data = (char*)realloc(state->data, state->buflen); } state->pos += newlen; while (state->part < P_BODY) { char *dataend = state->data + state->pos; char *lineend = (char*)memchr(state->data, '\n', state->pos); if (!lineend) break; *lineend='\0'; if (lineend != state->data && lineend[-1]=='\r') lineend[-1]='\0'; if (state->part == P_HEADER_TOP) { if (strncmp(state->data, "HTTP/1.", strlen("HTTP/1."))!=0) goto fail; state->status = strtoul(state->data + strlen("HTTP/1.1 "), NULL, 10); state->part = P_HEADER; } else { if (!strncmp(state->data, "Content-Length: ", strlen("Content-Length: "))) { state->bodytype = T_LEN; state->len = strtol(state->data + strlen("Content-Length: "), NULL, 10); } if (string_is_equal(state->data, "Transfer-Encoding: chunked")) state->bodytype = T_CHUNK; /* TODO: save headers somewhere */ if (state->data[0]=='\0') { state->part = P_BODY; if (state->bodytype == T_CHUNK) state->part = P_BODY_CHUNKLEN; } } memmove(state->data, lineend + 1, dataend-(lineend+1)); state->pos = (dataend-(lineend + 1)); } if (state->part >= P_BODY) { newlen = state->pos; state->pos = 0; } } if (state->part >= P_BODY && state->part < P_DONE) { if (!newlen) { if (state->error) newlen = -1; else newlen = socket_receive_all_nonblocking( state->fd, &state->error, (uint8_t*)state->data + state->pos, state->buflen - state->pos); if (newlen < 0) { if (state->bodytype == T_FULL) { state->part = P_DONE; state->data = (char*)realloc(state->data, state->len); } else goto fail; newlen=0; } if (state->pos + newlen >= state->buflen - 64) { state->buflen *= 2; state->data = (char*)realloc(state->data, state->buflen); } } parse_again: if (state->bodytype == T_CHUNK) { if (state->part == P_BODY_CHUNKLEN) { state->pos += newlen; if (state->pos - state->len >= 2) { /* * len=start of chunk including \r\n * pos=end of data */ char *fullend = state->data + state->pos; char *end = (char*)memchr(state->data + state->len + 2, '\n', state->pos - state->len - 2); if (end) { size_t chunklen = strtoul(state->data+state->len, NULL, 16); state->pos = state->len; end++; memmove(state->data+state->len, end, fullend-end); state->len = chunklen; newlen = (fullend - end); /* len=num bytes newlen=unparsed bytes after \n pos=start of chunk including \r\n */ state->part = P_BODY; if (state->len == 0) { state->part = P_DONE; state->len = state->pos; state->data = (char*)realloc(state->data, state->len); } goto parse_again; } } } else if (state->part == P_BODY) { if ((size_t)newlen >= state->len) { state->pos += state->len; newlen -= state->len; state->len = state->pos; state->part = P_BODY_CHUNKLEN; goto parse_again; } else { state->pos += newlen; state->len -= newlen; } } } else { state->pos += newlen; if (state->pos == state->len) { state->part = P_DONE; state->data = (char*)realloc(state->data, state->len); } if (state->pos > state->len) goto fail; } } if (progress) *progress = state->pos; if (total) { if (state->bodytype == T_LEN) *total=state->len; else *total=0; } return (state->part == P_DONE); fail: if (state) { state->error = true; state->part = P_ERROR; state->status = -1; } return true; } int net_http_status(struct http_t *state) { if (!state) return -1; return state->status; } uint8_t* net_http_data(struct http_t *state, size_t* len, bool accept_error) { if (!state) return NULL; if (!accept_error && net_http_error(state)) { if (len) *len=0; return NULL; } if (len) *len=state->len; return (uint8_t*)state->data; } void net_http_delete(struct http_t *state) { if (!state) return; if (state->fd >= 0) socket_close(state->fd); free(state); } bool net_http_error(struct http_t *state) { return (state->error || state->status<200 || state->status>299); }