1 | // MIT License |
2 | |
3 | // Copyright (c) 2017 Vadim Grigoruk @nesbox // grigoruk@gmail.com |
4 | |
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy |
6 | // of this software and associated documentation files (the "Software"), to deal |
7 | // in the Software without restriction, including without limitation the rights |
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
9 | // copies of the Software, and to permit persons to whom the Software is |
10 | // furnished to do so, subject to the following conditions: |
11 | |
12 | // The above copyright notice and this permission notice shall be included in all |
13 | // copies or substantial portions of the Software. |
14 | |
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
21 | // SOFTWARE. |
22 | |
23 | #include "net.h" |
24 | #include "defines.h" |
25 | |
26 | #include <stdlib.h> |
27 | #include <stdio.h> |
28 | #include <string.h> |
29 | |
30 | #define URL_SIZE 2048 |
31 | |
32 | #if defined(__EMSCRIPTEN__) |
33 | |
34 | #include <emscripten/fetch.h> |
35 | |
36 | typedef struct |
37 | { |
38 | net_get_callback callback; |
39 | void* calldata; |
40 | } FetchData; |
41 | |
42 | struct tic_net |
43 | { |
44 | emscripten_fetch_attr_t attr; |
45 | }; |
46 | |
47 | static void downloadSucceeded(emscripten_fetch_t *fetch) |
48 | { |
49 | FetchData* data = (FetchData*)fetch->userData; |
50 | |
51 | net_get_data getData = |
52 | { |
53 | .type = net_get_done, |
54 | .done = |
55 | { |
56 | .size = fetch->numBytes, |
57 | .data = (u8*)fetch->data, |
58 | }, |
59 | .calldata = data->calldata, |
60 | .url = fetch->url, |
61 | }; |
62 | |
63 | data->callback(&getData); |
64 | |
65 | free((void*)fetch->data); |
66 | free(data); |
67 | |
68 | emscripten_fetch_close(fetch); |
69 | } |
70 | |
71 | static void downloadFailed(emscripten_fetch_t *fetch) |
72 | { |
73 | FetchData* data = (FetchData*)fetch->userData; |
74 | |
75 | net_get_data getData = |
76 | { |
77 | .type = net_get_error, |
78 | .error = |
79 | { |
80 | .code = fetch->status, |
81 | }, |
82 | .calldata = data->calldata, |
83 | .url = fetch->url, |
84 | }; |
85 | |
86 | data->callback(&getData); |
87 | |
88 | free(data); |
89 | |
90 | emscripten_fetch_close(fetch); |
91 | } |
92 | |
93 | static void downloadProgress(emscripten_fetch_t *fetch) |
94 | { |
95 | FetchData* data = (FetchData*)fetch->userData; |
96 | |
97 | net_get_data getData = |
98 | { |
99 | .type = net_get_progress, |
100 | .progress = |
101 | { |
102 | .size = fetch->dataOffset + fetch->numBytes, |
103 | .total = fetch->totalBytes, |
104 | }, |
105 | .calldata = data->calldata, |
106 | .url = fetch->url, |
107 | }; |
108 | |
109 | data->callback(&getData); |
110 | } |
111 | |
112 | void tic_net_get(tic_net* net, const char* path, net_get_callback callback, void* calldata) |
113 | { |
114 | FetchData* data = calloc(1, sizeof(FetchData)); |
115 | *data = (FetchData) |
116 | { |
117 | .callback = callback, |
118 | .calldata = calldata, |
119 | }; |
120 | |
121 | net->attr.userData = data; |
122 | emscripten_fetch(&net->attr, path); |
123 | } |
124 | |
125 | void tic_net_start(tic_net *net) {} |
126 | void tic_net_end(tic_net *net) {} |
127 | |
128 | tic_net* tic_net_create(const char* host) |
129 | { |
130 | tic_net* net = (tic_net*)malloc(sizeof(tic_net)); |
131 | |
132 | emscripten_fetch_attr_init(&net->attr); |
133 | strcpy(net->attr.requestMethod, "GET" ); |
134 | net->attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; |
135 | net->attr.onsuccess = downloadSucceeded; |
136 | net->attr.onerror = downloadFailed; |
137 | net->attr.onprogress = downloadProgress; |
138 | |
139 | return net; |
140 | } |
141 | |
142 | void tic_net_close(tic_net* net) |
143 | { |
144 | free(net); |
145 | } |
146 | |
147 | #elif defined(_3DS) |
148 | |
149 | #include <3ds.h> |
150 | |
151 | struct tic_net |
152 | { |
153 | LightLock tick_lock; |
154 | const char* host; |
155 | }; |
156 | |
157 | typedef struct { |
158 | char url[URL_SIZE]; |
159 | |
160 | tic_net *net; |
161 | httpcContext httpc; |
162 | net_get_data data; |
163 | net_get_callback callback; |
164 | |
165 | void *buffer; |
166 | s32 size; |
167 | } net_ctx; |
168 | |
169 | // #define DEBUG |
170 | |
171 | static int n3ds_net_setup_context(httpcContext *httpc, const char *url) { |
172 | if (httpcOpenContext(httpc, HTTPC_METHOD_GET, url, 1)) return -101; |
173 | if (httpcSetSSLOpt(httpc, SSLCOPT_DisableVerify)) { httpcCloseContext(httpc); return -102; } |
174 | if (httpcSetKeepAlive(httpc, HTTPC_KEEPALIVE_ENABLED)) { httpcCloseContext(httpc); return -103; } |
175 | if (httpcAddRequestHeaderField(httpc, "User-Agent" , "tic80-n3ds/1.0.0 (httpc)" )) { httpcCloseContext(httpc); return -104; } |
176 | if (httpcAddRequestHeaderField(httpc, "Connection" , "Keep-Alive" )) { httpcCloseContext(httpc); return -105; } |
177 | return 0; |
178 | } |
179 | |
180 | #define NET_PAGE_SIZE 4096 |
181 | |
182 | static inline bool ctx_resize_buf(net_ctx *ctx, s32 new_size) { |
183 | if (ctx->buffer == NULL) { |
184 | ctx->buffer = malloc(new_size); |
185 | if (ctx->buffer == NULL) { |
186 | return false; |
187 | } |
188 | } else if (ctx->size != new_size) { |
189 | void *old_buf = ctx->buffer; |
190 | ctx->buffer = realloc(ctx->buffer, new_size); |
191 | if (ctx->buffer == NULL) { |
192 | free(old_buf); |
193 | return false; |
194 | } |
195 | } |
196 | |
197 | ctx->size = new_size; |
198 | return true; |
199 | } |
200 | |
201 | #define NET_EXEC_ERROR_CHECK \ |
202 | if (status_code != 200) { \ |
203 | printf("net_httpc: error %d\n", status_code); \ |
204 | if (ctx->callback != NULL) { \ |
205 | ctx->data.type = net_get_error; \ |
206 | ctx->data.error.code = status_code; \ |
207 | if (!ignore_lock) LightLock_Lock(&ctx->net->tick_lock); \ |
208 | ctx->callback(&ctx->data); \ |
209 | if (!ignore_lock) LightLock_Unlock(&ctx->net->tick_lock); \ |
210 | } \ |
211 | httpcCloseContext(&ctx->httpc); \ |
212 | if (ctx->buffer != NULL) { free(ctx->buffer); ctx->size = 0; } \ |
213 | return; \ |
214 | } |
215 | |
216 | static void n3ds_net_execute(net_ctx *ctx, bool ignore_lock) { |
217 | bool redirecting = true; |
218 | s32 status_code = -1; |
219 | |
220 | ctx->data.url = ctx->url; |
221 | while (redirecting) { |
222 | #ifdef DEBUG |
223 | printf("url: %s\n" , ctx->url); |
224 | #endif |
225 | redirecting = false; |
226 | |
227 | status_code = n3ds_net_setup_context(&ctx->httpc, ctx->url); |
228 | if (status_code < 0) { |
229 | break; |
230 | } |
231 | |
232 | if (httpcBeginRequest(&ctx->httpc)) { |
233 | status_code = -2; |
234 | break; |
235 | } |
236 | |
237 | if (httpcGetResponseStatusCode(&ctx->httpc, &status_code)) { |
238 | status_code = -3; |
239 | break; |
240 | } |
241 | |
242 | if ((status_code >= 301 && status_code <= 303) || (status_code >= 307 && status_code <= 308)) { |
243 | if (httpcGetResponseHeader(&ctx->httpc, "Location" , ctx->url, URL_SIZE - 1)) { |
244 | status_code = -4; |
245 | break; |
246 | } |
247 | |
248 | redirecting = true; |
249 | httpcCloseContext(&ctx->httpc); |
250 | } |
251 | } |
252 | |
253 | NET_EXEC_ERROR_CHECK; |
254 | |
255 | s32 state = HTTPC_RESULTCODE_DOWNLOADPENDING; |
256 | s32 read_size; |
257 | while (state == HTTPC_RESULTCODE_DOWNLOADPENDING) { |
258 | s32 old_size = ctx->size; |
259 | if (!ctx_resize_buf(ctx, ctx->size + NET_PAGE_SIZE)) { |
260 | httpcCloseContext(&ctx->httpc); |
261 | status_code = -5; |
262 | break; |
263 | } |
264 | u8 *old_ptr = ((u8*) ctx->buffer) + old_size; |
265 | state = httpcDownloadData(&ctx->httpc, old_ptr, NET_PAGE_SIZE, &read_size); |
266 | if (state == HTTPC_RESULTCODE_DOWNLOADPENDING || state == 0) { |
267 | ctx_resize_buf(ctx, old_size + read_size); |
268 | if (ctx->callback != NULL) { |
269 | if (ignore_lock || !LightLock_TryLock(&ctx->net->tick_lock)) { |
270 | ctx->data.type = net_get_progress; |
271 | if (!httpcGetDownloadSizeState(&ctx->httpc, &ctx->data.progress.size, &ctx->data.progress.total)) { |
272 | if (ctx->data.progress.total < ctx->data.progress.size) { |
273 | ctx->data.progress.total = ctx->data.progress.size; |
274 | } |
275 | ctx->callback(&ctx->data); |
276 | } |
277 | if (!ignore_lock) LightLock_Unlock(&ctx->net->tick_lock); |
278 | } |
279 | } |
280 | } |
281 | } |
282 | |
283 | #ifdef DEBUG |
284 | printf("downloaded: %d bytes\n" , ctx->size); |
285 | #endif |
286 | |
287 | if (status_code == 200 && state != 0) { |
288 | status_code = -6; |
289 | } |
290 | NET_EXEC_ERROR_CHECK; |
291 | |
292 | if (ctx->callback != NULL) { |
293 | ctx->data.type = net_get_done; |
294 | ctx->data.done.data = ctx->buffer; |
295 | ctx->data.done.size = ctx->size; |
296 | if (!ignore_lock) LightLock_Lock(&ctx->net->tick_lock); |
297 | ctx->callback(&ctx->data); |
298 | if (!ignore_lock) LightLock_Unlock(&ctx->net->tick_lock); |
299 | } |
300 | httpcCloseContext(&ctx->httpc); |
301 | } |
302 | |
303 | static void n3ds_net_init(tic_net *net) { |
304 | httpcInit(0); |
305 | |
306 | memset(net, 0, sizeof(tic_net)); |
307 | LightLock_Init(&net->tick_lock); |
308 | } |
309 | |
310 | static void n3ds_net_free(tic_net *net) { |
311 | httpcExit(); |
312 | } |
313 | |
314 | static void n3ds_net_get_thread(net_ctx *ctx) { |
315 | n3ds_net_execute(ctx, false); |
316 | |
317 | if (ctx->buffer != NULL) { |
318 | free(ctx->buffer); |
319 | } |
320 | free(ctx); |
321 | } |
322 | |
323 | static void n3ds_net_apply_url(net_ctx *ctx, const char *url) { |
324 | strncpy(ctx->url, ctx->net->host, URL_SIZE - 1); |
325 | strncat(ctx->url, url, URL_SIZE - 1); |
326 | } |
327 | |
328 | tic_net* tic_net_create(const char* host) |
329 | { |
330 | tic_net* net = (tic_net*)malloc(sizeof(tic_net)); |
331 | |
332 | n3ds_net_init(net); |
333 | net->host = host; |
334 | |
335 | return net; |
336 | } |
337 | |
338 | void tic_net_get(tic_net* net, const char* url, net_get_callback callback, void* calldata) |
339 | { |
340 | net_ctx ctx = |
341 | { |
342 | .net = net, |
343 | .callback = callback, |
344 | .data = {.calldata = calldata}, |
345 | }; |
346 | |
347 | n3ds_net_apply_url(&ctx, url); |
348 | |
349 | s32 priority; |
350 | svcGetThreadPriority(&priority, CUR_THREAD_HANDLE); |
351 | threadCreate((ThreadFunc) n3ds_net_get_thread, MOVE(ctx), 16 * 1024, priority - 1, -1, true); |
352 | } |
353 | |
354 | void tic_net_close(tic_net* net) |
355 | { |
356 | n3ds_net_free(net); |
357 | free(net); |
358 | } |
359 | |
360 | void tic_net_start(tic_net *net) |
361 | { |
362 | LightLock_Lock(&net->tick_lock); |
363 | } |
364 | |
365 | void tic_net_end(tic_net *net) |
366 | { |
367 | LightLock_Unlock(&net->tick_lock); |
368 | } |
369 | |
370 | #elif defined(BAREMETALPI) |
371 | |
372 | tic_net* tic_net_create(const char* host) {return NULL;} |
373 | void tic_net_get(tic_net* net, const char* url, net_get_callback callback, void* calldata) {} |
374 | void tic_net_close(tic_net* net) {} |
375 | void tic_net_start(tic_net *net) {} |
376 | void tic_net_end(tic_net *net) {} |
377 | |
378 | #elif defined(USE_LIBUV) |
379 | |
380 | #include "defines.h" |
381 | |
382 | #include <uv.h> |
383 | #include <http_parser.h> |
384 | |
385 | typedef struct |
386 | { |
387 | const char* host; |
388 | const char* path; |
389 | |
390 | net_get_callback callback; |
391 | void* calldata; |
392 | |
393 | uv_tcp_t tcp; |
394 | http_parser parser; |
395 | |
396 | struct |
397 | { |
398 | u8* data; |
399 | s32 size; |
400 | s32 total; |
401 | } content; |
402 | |
403 | } NetRequest; |
404 | |
405 | struct tic_net |
406 | { |
407 | const char* host; |
408 | }; |
409 | |
410 | static s32 onBody(http_parser* parser, const char *at, size_t length) |
411 | { |
412 | NetRequest* req = parser->data; |
413 | |
414 | req->content.data = realloc(req->content.data, req->content.size + length); |
415 | memcpy(req->content.data + req->content.size, at, length); |
416 | |
417 | req->content.size += length; |
418 | |
419 | req->callback(&(net_get_data) |
420 | { |
421 | .calldata = req->calldata, |
422 | .type = net_get_progress, |
423 | .progress = {req->content.size, req->content.total}, |
424 | .url = req->path |
425 | }); |
426 | |
427 | return 0; |
428 | } |
429 | |
430 | static void onClose(uv_handle_t* handle) |
431 | { |
432 | NetRequest* req = handle->data; |
433 | if (req){ |
434 | FREE(req->content.data); |
435 | free((void*)req->path); |
436 | free(req); |
437 | } |
438 | } |
439 | |
440 | static void freeRequest(NetRequest* req) |
441 | { |
442 | uv_close((uv_handle_t*)&req->tcp, onClose); |
443 | } |
444 | |
445 | static s32 onMessageComplete(http_parser* parser) |
446 | { |
447 | NetRequest* req = parser->data; |
448 | |
449 | if (parser->status_code == HTTP_STATUS_OK) |
450 | { |
451 | req->callback(&(net_get_data) |
452 | { |
453 | .calldata = req->calldata, |
454 | .type = net_get_done, |
455 | .done = { .data = req->content.data, .size = req->content.size }, |
456 | .url = req->path |
457 | }); |
458 | } |
459 | |
460 | freeRequest(req); |
461 | |
462 | return 0; |
463 | } |
464 | |
465 | static s32 (http_parser* parser) |
466 | { |
467 | NetRequest* req = parser->data; |
468 | |
469 | bool hasBody = parser->flags & F_CHUNKED || (parser->content_length > 0 && parser->content_length != ULLONG_MAX); |
470 | |
471 | ZEROMEM(req->content); |
472 | req->content.total = parser->content_length; |
473 | |
474 | // !TODO: handle HTTP_STATUS_MOVED_PERMANENTLY here |
475 | if (!hasBody || parser->status_code != HTTP_STATUS_OK) |
476 | return 1; |
477 | |
478 | return 0; |
479 | } |
480 | |
481 | static s32 onStatus(http_parser* parser, const char* at, size_t length) |
482 | { |
483 | return parser->status_code != HTTP_STATUS_OK; |
484 | } |
485 | |
486 | static void onError(NetRequest* req, s32 code) |
487 | { |
488 | req->callback(&(net_get_data) |
489 | { |
490 | .calldata = req->calldata, |
491 | .type = net_get_error, |
492 | .error = { .code = code } |
493 | }); |
494 | |
495 | freeRequest(req); |
496 | } |
497 | |
498 | static void allocBuffer(uv_handle_t *handle, size_t size, uv_buf_t *buf) |
499 | { |
500 | buf->base = malloc(size); |
501 | buf->len = size; |
502 | } |
503 | |
504 | static void onResponse(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) |
505 | { |
506 | NetRequest* req = stream->data; |
507 | |
508 | if(nread > 0) |
509 | { |
510 | static const http_parser_settings ParserSettings = |
511 | { |
512 | .on_status = onStatus, |
513 | .on_body = onBody, |
514 | .on_message_complete = onMessageComplete, |
515 | .on_headers_complete = onHeadersComplete, |
516 | }; |
517 | |
518 | s32 parsed = http_parser_execute(&req->parser, &ParserSettings, buf->base, nread); |
519 | |
520 | if(parsed != nread) |
521 | onError(req, req->parser.status_code); |
522 | |
523 | free(buf->base); |
524 | } |
525 | else onError(req, 0); |
526 | } |
527 | |
528 | static void (uv_write_t *write, s32 status) |
529 | { |
530 | NetRequest* req = write->data; |
531 | http_parser_init(&req->parser, HTTP_RESPONSE); |
532 | req->parser.data = req; |
533 | |
534 | uv_stream_t* handle = write->handle; |
535 | free(write); |
536 | |
537 | handle->data = req; |
538 | uv_read_start(handle, allocBuffer, onResponse); |
539 | } |
540 | |
541 | static void onConnect(uv_connect_t *con, s32 status) |
542 | { |
543 | NetRequest* req = con->data; |
544 | |
545 | char httpReq[2048]; |
546 | snprintf(httpReq, sizeof httpReq, "GET %s HTTP/1.1\nHost: %s\n\n" , req->path, req->host); |
547 | |
548 | uv_buf_t http = uv_buf_init(httpReq, strlen(httpReq)); |
549 | uv_write(MOVE((uv_write_t){.data = req}), con->handle, &http, 1, onHeaderSent); |
550 | |
551 | free(con); |
552 | } |
553 | |
554 | static void onResolved(uv_getaddrinfo_t *resolver, s32 status, struct addrinfo *res) |
555 | { |
556 | NetRequest* req = resolver->data; |
557 | |
558 | if (res) |
559 | { |
560 | uv_tcp_connect(MOVE((uv_connect_t){.data = req}), &req->tcp, res->ai_addr, onConnect); |
561 | uv_freeaddrinfo(res); |
562 | } |
563 | else onError(req, 0); |
564 | |
565 | free(resolver); |
566 | } |
567 | |
568 | void tic_net_get(tic_net* net, const char* path, net_get_callback callback, void* calldata) |
569 | { |
570 | uv_loop_t* loop = uv_default_loop(); |
571 | |
572 | NetRequest* req = MOVE((NetRequest) |
573 | { |
574 | .host = net->host, |
575 | .callback = callback, |
576 | .calldata = calldata, |
577 | .path = strdup(path), |
578 | }); |
579 | |
580 | uv_tcp_init(loop, &req->tcp); |
581 | uv_getaddrinfo(loop, MOVE((uv_getaddrinfo_t){.data = req}), onResolved, net->host, "80" , NULL); |
582 | } |
583 | |
584 | void tic_net_start(tic_net *net) {} |
585 | |
586 | void tic_net_end(tic_net *net) |
587 | { |
588 | uv_run(uv_default_loop(), UV_RUN_NOWAIT); |
589 | } |
590 | |
591 | tic_net* tic_net_create(const char* host) |
592 | { |
593 | tic_net* net = NEW(tic_net); |
594 | memset(net, 0, sizeof(tic_net)); |
595 | |
596 | net->host = host; |
597 | |
598 | static const char Http[] = "http://" ; |
599 | if(strstr(host, Http) == host) |
600 | net->host += sizeof Http - 1; |
601 | |
602 | return net; |
603 | } |
604 | |
605 | void tic_net_close(tic_net* net) |
606 | { |
607 | free(net); |
608 | } |
609 | |
610 | #endif |
611 | |