544 lines
12 KiB
C
Raw Normal View History

/* Copyright (C) 2010-2016 The RetroArch team
2015-01-23 01:20:56 +01:00
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (net_http.c).
* ---------------------------------------------------------------------------------------
2015-01-23 01:45:38 +01:00
*
* 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.
2015-01-23 01:45:38 +01:00
*/
2015-01-23 01:20:56 +01:00
#include <stdio.h>
2015-01-21 23:23:36 +01:00
#include <stdlib.h>
#include <ctype.h>
2015-09-05 21:13:58 +02:00
#include <net/net_http.h>
#include <net/net_compat.h>
2016-05-01 22:45:32 +02:00
#include <net/net_socket.h>
2015-01-24 01:56:55 +01:00
#include <compat/strl.h>
2015-01-21 23:23:36 +01:00
2015-01-23 01:20:56 +01:00
enum
{
2015-01-26 20:50:49 +01:00
P_HEADER_TOP = 0,
P_HEADER,
P_BODY,
P_BODY_CHUNKLEN,
P_DONE,
P_ERROR
2015-01-23 01:20:56 +01:00
};
enum
{
2015-01-26 20:47:59 +01:00
T_FULL = 0,
T_LEN,
T_CHUNK
2015-01-23 01:20:56 +01:00
};
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
2015-01-21 23:23:36 +01:00
{
char *domain;
char *location;
char *urlcopy;
2015-06-13 00:44:47 +02:00
char *scan;
int port;
};
2015-01-23 01:20:56 +01:00
2015-01-21 23:23:36 +01:00
2015-06-13 00:44:47 +02:00
static int net_http_new_socket(const char *domain, int port)
2015-01-21 23:23:36 +01:00
{
2016-04-28 11:58:44 +02:00
int ret;
2016-05-01 22:45:32 +02:00
struct addrinfo *addr = NULL;
int fd = socket_init((void**)&addr, port, domain, SOCKET_TYPE_STREAM);
if (fd < 0)
return -1;
2015-01-26 20:47:59 +01:00
2016-05-01 23:45:59 +02:00
ret = socket_connect(fd, (void*)addr, true);
2015-01-23 01:20:56 +01:00
freeaddrinfo_retro(addr);
if (ret < 0)
2016-04-28 11:58:44 +02:00
goto error;
if (!socket_nonblock(fd))
2016-04-28 11:58:44 +02:00
goto error;
2015-01-23 01:20:56 +01:00
2015-01-26 20:47:59 +01:00
return fd;
2016-04-28 11:58:44 +02:00
error:
socket_close(fd);
return -1;
2015-01-21 23:23:36 +01:00
}
static void net_http_send_str(int fd, bool *error, const char *text)
2015-01-21 23:23:36 +01:00
{
2016-05-02 02:10:45 +02:00
if (*error)
return;
if (!socket_send_all_blocking(fd, text, strlen(text), true))
*error = true;
2015-01-21 23:23:36 +01:00
}
2016-04-27 21:04:16 +02:00
static char* urlencode(const char* url)
{
unsigned i;
unsigned outpos = 0;
unsigned outlen = 0;
2016-05-02 02:09:22 +02:00
char *ret = NULL;
2016-04-28 11:58:44 +02:00
for (i = 0; url[i] != '\0'; i++)
{
outlen++;
if (url[i] == ' ')
2016-04-28 11:58:44 +02:00
outlen += 2;
}
ret = (char*)malloc(outlen + 1);
if (!ret)
2016-04-28 11:58:44 +02:00
return NULL;
for (i = 0; url[i]; i++)
{
if (url[i] == ' ')
{
ret[outpos++] = '%';
ret[outpos++] = '2';
ret[outpos++] = '0';
}
else
2016-04-28 11:58:44 +02:00
ret[outpos++] = url[i];
}
ret[outpos] = '\0';
return ret;
2016-04-27 21:04:16 +02:00
}
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;
2016-04-28 11:58:44 +02:00
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;
2016-04-28 11:58:44 +02:00
*domain = conn->scan;
return conn;
error:
if (conn->urlcopy)
free(conn->urlcopy);
conn->urlcopy = NULL;
2015-09-28 23:00:22 +02:00
free(conn);
return NULL;
}
bool net_http_connection_iterate(struct http_connection_t *conn)
{
2015-03-21 22:10:13 +01:00
if (!conn)
return false;
2016-04-28 11:58:44 +02:00
while (*conn->scan != '/' && *conn->scan != ':' && *conn->scan != '\0')
conn->scan++;
2016-04-28 11:58:44 +02:00
return true;
}
bool net_http_connection_done(struct http_connection_t *conn)
{
char **location = NULL;
if (!conn)
return false;
2016-04-28 11:58:44 +02:00
location = &conn->location;
if (*conn->scan == '\0')
return false;
2016-04-28 11:58:44 +02:00
*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)
2015-01-21 23:23:36 +01:00
{
2016-04-28 11:58:44 +02:00
bool error = false;
int fd = -1;
struct http_t *state = NULL;
2015-01-23 01:20:56 +01:00
if (!conn)
goto error;
2015-01-23 01:20:56 +01:00
fd = net_http_new_socket(conn->domain, conn->port);
if (fd < 0)
goto error;
2015-01-26 20:47:59 +01:00
error = false;
2015-01-26 20:47:59 +01:00
/* This is a bit lazy, but it works. */
net_http_send_str(fd, &error, "GET /");
net_http_send_str(fd, &error, conn->location);
2015-01-26 20:47:59 +01:00
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);
2015-01-26 20:47:59 +01:00
if (conn->port != 80)
2015-01-26 20:47:59 +01:00
{
2015-06-13 00:44:47 +02:00
char portstr[16] = {0};
2015-01-26 20:47:59 +01:00
snprintf(portstr, sizeof(portstr), ":%i", conn->port);
2015-01-26 20:47:59 +01:00
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;
2015-01-26 20:47:59 +01:00
state = (struct http_t*)malloc(sizeof(struct http_t));
2015-01-26 20:47:59 +01:00
state->fd = fd;
state->status = -1;
state->data = NULL;
2015-01-26 20:50:49 +01:00
state->part = P_HEADER_TOP;
2015-01-26 20:47:59 +01:00
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;
2015-01-26 20:47:59 +01:00
return state;
error:
if (fd >= 0)
socket_close(fd);
2015-01-26 20:47:59 +01:00
return NULL;
2015-01-21 23:23:36 +01:00
}
int net_http_fd(struct http_t *state)
2015-01-21 23:23:36 +01:00
{
if (!state)
return -1;
2015-01-26 20:47:59 +01:00
return state->fd;
2015-01-21 23:23:36 +01:00
}
bool net_http_update(struct http_t *state, size_t* progress, size_t* total)
2015-01-21 23:23:36 +01:00
{
2015-01-26 20:47:59 +01:00
ssize_t newlen = 0;
if (!state || state->error)
2015-01-23 01:20:56 +01:00
goto fail;
2015-01-26 20:47:59 +01:00
2015-01-26 20:50:49 +01:00
if (state->part < P_BODY)
2015-01-26 20:47:59 +01:00
{
2016-05-02 02:09:22 +02:00
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);
2015-01-23 01:20:56 +01:00
2015-01-26 20:47:59 +01:00
if (newlen < 0)
2015-01-23 01:20:56 +01:00
goto fail;
2015-01-26 20:47:59 +01:00
if (state->pos + newlen >= state->buflen - 64)
{
state->buflen *= 2;
state->data = (char*)realloc(state->data, state->buflen);
}
state->pos += newlen;
2015-01-23 01:20:56 +01:00
2015-01-26 20:50:49 +01:00
while (state->part < P_BODY)
2015-01-26 20:47:59 +01:00
{
char *dataend = state->data + state->pos;
char *lineend = (char*)memchr(state->data, '\n', state->pos);
2015-01-23 01:20:56 +01:00
2015-01-26 20:47:59 +01:00
if (!lineend)
2015-01-23 01:20:56 +01:00
break;
2015-01-26 20:47:59 +01:00
*lineend='\0';
if (lineend != state->data && lineend[-1]=='\r')
2015-01-23 01:20:56 +01:00
lineend[-1]='\0';
2015-01-26 20:47:59 +01:00
2015-01-26 20:50:49 +01:00
if (state->part == P_HEADER_TOP)
2015-01-26 20:47:59 +01:00
{
if (strncmp(state->data, "HTTP/1.", strlen("HTTP/1."))!=0)
2015-01-23 01:20:56 +01:00
goto fail;
2015-01-26 20:50:49 +01:00
state->status = strtoul(state->data + strlen("HTTP/1.1 "), NULL, 10);
state->part = P_HEADER;
2015-01-26 20:47:59 +01:00
}
else
{
if (!strncmp(state->data, "Content-Length: ",
2015-01-23 01:20:56 +01:00
strlen("Content-Length: ")))
2015-01-26 20:47:59 +01:00
{
state->bodytype = T_LEN;
state->len = strtol(state->data +
2015-01-23 01:20:56 +01:00
strlen("Content-Length: "), NULL, 10);
2015-01-26 20:47:59 +01:00
}
if (!strcmp(state->data, "Transfer-Encoding: chunked"))
state->bodytype = T_CHUNK;
/* TODO: save headers somewhere */
if (state->data[0]=='\0')
{
2015-01-26 20:50:49 +01:00
state->part = P_BODY;
2015-01-26 20:47:59 +01:00
if (state->bodytype == T_CHUNK)
2015-01-26 20:50:49 +01:00
state->part = P_BODY_CHUNKLEN;
2015-01-26 20:47:59 +01:00
}
}
memmove(state->data, lineend + 1, dataend-(lineend+1));
state->pos = (dataend-(lineend + 1));
}
2015-01-26 20:50:49 +01:00
if (state->part >= P_BODY)
2015-01-26 20:47:59 +01:00
{
newlen = state->pos;
state->pos = 0;
}
}
2015-01-26 20:50:49 +01:00
if (state->part >= P_BODY && state->part < P_DONE)
2015-01-26 20:47:59 +01:00
{
if (!newlen)
{
2016-05-02 02:09:22 +02:00
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);
2015-01-23 01:20:56 +01:00
2015-01-26 20:47:59 +01:00
if (newlen < 0)
{
if (state->bodytype == T_FULL)
{
2015-01-26 20:50:49 +01:00
state->part = P_DONE;
2015-01-23 22:48:31 +01:00
state->data = (char*)realloc(state->data, state->len);
2015-01-26 20:47:59 +01:00
}
else
2015-01-23 01:20:56 +01:00
goto fail;
2015-01-26 20:47:59 +01:00
newlen=0;
}
if (state->pos + newlen >= state->buflen - 64)
{
state->buflen *= 2;
state->data = (char*)realloc(state->data, state->buflen);
}
}
2015-01-23 00:10:56 +01:00
parse_again:
2015-01-26 20:47:59 +01:00
if (state->bodytype == T_CHUNK)
{
2015-01-26 20:50:49 +01:00
if (state->part == P_BODY_CHUNKLEN)
2015-01-26 20:47:59 +01:00
{
state->pos += newlen;
if (state->pos - state->len >= 2)
{
/*
2015-01-23 01:20:56 +01:00
* len=start of chunk including \r\n
* pos=end of data
*/
2015-01-26 20:47:59 +01:00
char *fullend = state->data + state->pos;
char *end = (char*)memchr(state->data + state->len + 2, '\n',
state->pos - state->len - 2);
2015-01-23 01:20:56 +01:00
2015-01-26 20:47:59 +01:00
if (end)
{
size_t chunklen = strtoul(state->data+state->len, NULL, 16);
state->pos = state->len;
end++;
2015-01-23 01:20:56 +01:00
2015-01-26 20:47:59 +01:00
memmove(state->data+state->len, end, fullend-end);
2015-01-23 01:20:56 +01:00
2015-01-26 20:47:59 +01:00
state->len = chunklen;
newlen = (fullend - end);
2015-01-23 01:20:56 +01:00
/*
2015-01-26 20:47:59 +01:00
len=num bytes
newlen=unparsed bytes after \n
pos=start of chunk including \r\n
*/
2015-01-26 20:50:49 +01:00
state->part = P_BODY;
2015-01-26 20:47:59 +01:00
if (state->len == 0)
{
2015-01-26 20:50:49 +01:00
state->part = P_DONE;
state->len = state->pos;
2015-01-26 20:47:59 +01:00
state->data = (char*)realloc(state->data, state->len);
}
goto parse_again;
}
}
}
2015-01-26 20:50:49 +01:00
else if (state->part == P_BODY)
2015-01-26 20:47:59 +01:00
{
if ((size_t)newlen >= state->len)
{
state->pos += state->len;
newlen -= state->len;
state->len = state->pos;
2015-01-26 20:50:49 +01:00
state->part = P_BODY_CHUNKLEN;
2015-01-26 20:47:59 +01:00
goto parse_again;
}
else
{
state->pos += newlen;
state->len -= newlen;
}
}
}
else
{
state->pos += newlen;
if (state->pos == state->len)
{
2015-01-26 20:50:49 +01:00
state->part = P_DONE;
2015-01-23 22:48:31 +01:00
state->data = (char*)realloc(state->data, state->len);
2015-01-26 20:47:59 +01:00
}
if (state->pos > state->len)
2015-01-23 01:20:56 +01:00
goto fail;
2015-01-26 20:47:59 +01:00
}
}
if (progress)
2015-01-23 01:20:56 +01:00
*progress = state->pos;
2015-01-26 20:47:59 +01:00
if (total)
{
if (state->bodytype == T_LEN)
2015-01-23 01:20:56 +01:00
*total=state->len;
2015-01-26 20:47:59 +01:00
else
2015-01-23 01:20:56 +01:00
*total=0;
2015-01-26 20:47:59 +01:00
}
2015-01-26 20:50:49 +01:00
return (state->part == P_DONE);
2015-01-26 20:47:59 +01:00
2015-01-21 23:23:36 +01:00
fail:
if (state)
{
state->error = true;
state->part = P_ERROR;
state->status = -1;
}
2015-01-23 01:20:56 +01:00
2015-11-23 22:30:57 -03:00
return true;
2015-01-21 23:23:36 +01:00
}
int net_http_status(struct http_t *state)
2015-01-21 23:23:36 +01:00
{
if (!state)
return -1;
return state->status;
2015-01-21 23:23:36 +01:00
}
uint8_t* net_http_data(struct http_t *state, size_t* len, bool accept_error)
2015-01-21 23:23:36 +01:00
{
if (!state)
return NULL;
2015-11-23 22:30:57 -03:00
if (!accept_error && net_http_error(state))
2015-01-26 20:47:59 +01:00
{
if (len)
2015-01-23 01:20:56 +01:00
*len=0;
2015-01-26 20:47:59 +01:00
return NULL;
}
2015-01-23 01:20:56 +01:00
2015-01-26 20:47:59 +01:00
if (len)
2015-01-23 01:20:56 +01:00
*len=state->len;
2015-01-26 20:47:59 +01:00
return (uint8_t*)state->data;
2015-01-21 23:23:36 +01:00
}
void net_http_delete(struct http_t *state)
2015-01-21 23:23:36 +01:00
{
if (!state)
return;
if (state->fd >= 0)
socket_close(state->fd);
2015-01-26 20:47:59 +01:00
free(state);
2015-01-21 23:23:36 +01:00
}
2015-11-23 22:30:57 -03:00
bool net_http_error(struct http_t *state)
{
return (state->error || state->status<200 || state->status>299);
}