hbc/channel/channelapp/source/http.c
2019-01-09 21:40:28 +09:00

456 lines
8.7 KiB
C

#include <malloc.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <ogcsys.h>
#include <network.h>
#include <ogc/machine/processor.h>
#include <ogc/lwp_watchdog.h>
#include <ogc/cond.h>
#include "../config.h"
#ifdef ENABLE_UPDATES
#include "tcp.h"
#include "panic.h"
#include "http.h"
extern u32 ms_id;
extern u32 ng_id;
typedef enum {
HTTPCMD_IDLE = 0,
HTTPCMD_EXIT,
HTTPCMD_FETCH
} http_cmd;
typedef struct {
bool running;
bool done;
http_cmd cmd;
char *host;
u16 port;
char *path;
u32 max_size;
u16 sysmenu_version;
char *region;
char *area;
mutex_t mutex;
mutex_t cmutex;
cond_t cond;
http_state state;
http_res res;
u32 http_status;
u32 content_length;
u32 progress;
u8 *data;
} http_arg;
static lwp_t http_thread;
static u8 http_stack[HTTP_THREAD_STACKSIZE] ATTRIBUTE_ALIGN (32);
static http_arg ta_http;
static u16 get_tmd_version(u64 title) {
STACK_ALIGN(u8, tmdbuf, 1024, 32);
u32 tmd_view_size = 0;
s32 res;
res = ES_GetTMDViewSize(title, &tmd_view_size);
if (res < 0)
return 0;
if (tmd_view_size > 1024)
return 0;
res = ES_GetTMDView(title, tmdbuf, tmd_view_size);
if (res < 0)
return 0;
return (tmdbuf[88] << 8) | tmdbuf[89];
}
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 = pstrdup (c);
return true;
}
static void * http_func (void *arg) {
http_arg *ta = (http_arg *) arg;
int s;
char *request, *r;
int linecount;
char *line;
bool b;
s64 t;
ta->running = true;
LWP_MutexLock (ta->cmutex);
LWP_MutexLock (ta_http.mutex);
ta->state = HTTPS_IDLE;
ta->done = true;
LWP_MutexUnlock (ta_http.mutex);
while (true) {
while (ta->cmd == HTTPCMD_IDLE)
LWP_CondWait(ta->cond, ta->cmutex);
if (ta->cmd == HTTPCMD_EXIT)
break;
ta->cmd = HTTPCMD_IDLE;
s = tcp_connect (ta->host, ta->port);
LWP_MutexLock (ta_http.mutex);
if (s < 0) {
ta->state = HTTPS_IDLE;
ta->done = true;
ta->res = HTTPR_ERR_CONNECT;
LWP_MutexUnlock (ta_http.mutex);
continue;
}
ta->state = HTTPS_REQUESTING;
LWP_MutexUnlock (ta_http.mutex);
request = (char *) pmalloc (2048);
r = request;
r += sprintf (r, "GET %s HTTP/1.1\r\n", ta->path);
r += sprintf (r, "Host: %s\r\n", ta->host);
r += sprintf (r, "Cache-Control: no-cache\r\n");
r += sprintf (r, "User-Agent: TheHomebrewChannel/%s Wii/%08lx"
" (%u; %u; %s-%s)\r\n", CHANNEL_VERSION_STR,
ng_id, ms_id, ta->sysmenu_version, ta->region,
ta->area);
strcat (r, "\r\n");
b = tcp_write (s, (u8 *) request, strlen (request), NULL, NULL);
free (request);
if (!b) {
LWP_MutexLock (ta_http.mutex);
ta->state = HTTPS_IDLE;
ta->done = true;
ta->res = HTTPR_ERR_REQUEST;
LWP_MutexUnlock (ta_http.mutex);
net_close (s);
continue;
}
linecount = 0;
t = gettime ();
while (true) {
line = tcp_readln (s, 0xff, t, HTTP_TIMEOUT);
if (!line) {
LWP_MutexLock (ta_http.mutex);
ta->http_status = 404;
ta->res = HTTPR_ERR_REQUEST;
break;
}
if (strlen (line) < 1) {
free (line);
LWP_MutexLock (ta_http.mutex);
break;
}
sscanf (line, "HTTP/1.%*u %u", &ta->http_status);
sscanf (line, "Content-Length: %u", &ta->content_length);
free (line);
linecount++;
if (linecount == 32) {
LWP_MutexLock (ta_http.mutex);
ta->http_status = 404;
ta->res = HTTPR_ERR_REQUEST;
break;
}
}
if (ta->content_length < 1)
ta->http_status = 404;
if (ta->http_status != 200) {
ta->res = HTTPR_ERR_STATUS;
ta->state = HTTPS_IDLE;
ta->done = true;
LWP_MutexUnlock (ta_http.mutex);
net_close (s);
continue;
}
if (ta->content_length > ta->max_size) {
ta->res = HTTPR_ERR_TOOBIG;
ta->state = HTTPS_IDLE;
ta->done = true;
LWP_MutexUnlock (ta_http.mutex);
net_close (s);
continue;
}
ta->state = HTTPS_RECEIVING;
// safety, for char[] parsing
ta->data = (u8 *) pmalloc (ta->content_length + 1);
LWP_MutexUnlock (ta_http.mutex);
b = tcp_read (s, ta->data, ta->content_length, &ta->mutex,
&ta->progress);
if (!b) {
LWP_MutexLock (ta_http.mutex);
free (ta->data);
ta->data = NULL;
ta->res = HTTPR_ERR_RECEIVE;
ta->state = HTTPS_IDLE;
ta->done = true;
LWP_MutexUnlock (ta_http.mutex);
net_close (s);
continue;
}
gprintf("done with download\n");
LWP_MutexLock (ta_http.mutex);
ta->data[ta->content_length] = 0;
ta->res = HTTPR_OK;
ta->state = HTTPS_IDLE;
ta->done = true;
LWP_MutexUnlock (ta_http.mutex);
net_close (s);
}
LWP_MutexUnlock (ta->cmutex);
gprintf("http thread done\n");
ta->running = false;
return NULL;
}
void http_init (void) {
s32 res;
gprintf ("starting http thread\n");
memset (&ta_http, 0, sizeof (http_arg));
ta_http.sysmenu_version = get_tmd_version(0x0000000100000002ll);
switch (CONF_GetRegion()) {
case CONF_REGION_JP:
ta_http.region = pstrdup("JP");
break;
case CONF_REGION_US:
ta_http.region = pstrdup("US");
break;
case CONF_REGION_EU:
ta_http.region = pstrdup("EU");
break;
case CONF_REGION_KR:
ta_http.region = pstrdup("KR");
break;
case CONF_REGION_CN:
ta_http.region = pstrdup("CN");
break;
default:
ta_http.region = pstrdup("UNK");
break;
}
switch (CONF_GetArea()) {
case CONF_AREA_JPN:
ta_http.area = pstrdup("JPN");
break;
case CONF_AREA_USA:
ta_http.area = pstrdup("USA");
break;
case CONF_AREA_EUR:
ta_http.area = pstrdup("EUR");
break;
case CONF_AREA_AUS:
ta_http.area = pstrdup("AUS");
break;
case CONF_AREA_BRA:
ta_http.area = pstrdup("BRA");
break;
case CONF_AREA_TWN:
ta_http.area = pstrdup("TWN");
break;
case CONF_AREA_ROC:
ta_http.area = pstrdup("ROC");
break;
case CONF_AREA_KOR:
ta_http.area = pstrdup("KOR");
break;
case CONF_AREA_HKG:
ta_http.area = pstrdup("HKG");
break;
case CONF_AREA_ASI:
ta_http.area = pstrdup("ASI");
break;
case CONF_AREA_LTN:
ta_http.area = pstrdup("LTN");
break;
case CONF_AREA_SAF:
ta_http.area = pstrdup("SAF");
break;
case CONF_AREA_CHN:
ta_http.area = pstrdup("CHN");
break;
default:
ta_http.area = pstrdup("UNK");
break;
}
res = LWP_MutexInit (&ta_http.mutex, false);
if (res) {
gprintf ("error creating mutex: %d\n", res);
return;
}
res = LWP_MutexInit (&ta_http.cmutex, false);
if (res) {
gprintf ("error creating cmutex: %d\n", res);
return;
}
res = LWP_CondInit (&ta_http.cond);
if (res) {
gprintf ("error creating cond: %d\n", res);
return;
}
memset (&http_stack, 0, HTTP_THREAD_STACKSIZE);
res = LWP_CreateThread (&http_thread, http_func, &ta_http, http_stack,
HTTP_THREAD_STACKSIZE, HTTP_THREAD_PRIO);
gprintf("created http thread\n");
if (res) {
gprintf ("error creating thread: %d\n", res);
}
}
void http_deinit (void) {
int i;
if (ta_http.running) {
gprintf ("stopping http thread\n");
for (i = 0; i < 25; ++i) {
if (LWP_MutexTryLock (ta_http.cmutex) == 0) {
break;
}
usleep (20 * 1000);
}
if (i < 25) {
gprintf ("sending http entry thread the exit cmd\n");
ta_http.cmd = HTTPCMD_EXIT;
LWP_SetThreadPriority (http_thread, LWP_PRIO_HIGHEST);
LWP_CondBroadcast (ta_http.cond);
LWP_MutexUnlock (ta_http.cmutex);
LWP_JoinThread(http_thread, NULL);
LWP_CondDestroy (ta_http.cond);
LWP_MutexDestroy (ta_http.cmutex);
LWP_MutexDestroy (ta_http.mutex);
free(ta_http.region);
free(ta_http.area);
return;
}
gprintf("http thread didn't shutdown gracefully!\n");
}
return;
}
bool http_ready (void) {
return ta_http.running && ta_http.done;
}
bool http_request (const char *url, u32 max_size) {
if (!http_ready () || !http_split_url (&ta_http.host, &ta_http.path, url))
return false;
LWP_MutexLock (ta_http.cmutex);
ta_http.done = false;
ta_http.cmd = HTTPCMD_FETCH;
ta_http.port = 80;
ta_http.max_size = max_size;
LWP_CondBroadcast (ta_http.cond);
LWP_MutexUnlock (ta_http.cmutex);
return true;
}
bool http_get_state (http_state *state, u32 *content_length, u32 *progress) {
if (!ta_http.running)
return false;
LWP_MutexLock (ta_http.mutex);
*state = ta_http.state;
*content_length = ta_http.content_length;
*progress = ta_http.progress;
LWP_MutexUnlock (ta_http.mutex);
return true;
}
bool http_get_result (http_res *res, u32 *http_status, u8 **content,
u32 *length) {
if (!http_ready())
return false;
*res = ta_http.res;
*http_status = ta_http.http_status;
if (ta_http.res == HTTPR_OK) {
*content = ta_http.data;
*length = ta_http.content_length;
} else {
*content = NULL;
*length = 0;
}
free (ta_http.host);
ta_http.host = NULL;
free (ta_http.path);
ta_http.path = NULL;
return true;
}
#endif